Creating a drive program to control the Arduino ESP32 s3 microcontroller

Dear members,

This is my first time on the forum, and I am seeking some assistance. I am working on my final bachelor's degree exam, but I've hit a wall right at the end. My mentor has confirmed that the project is fine, yet it’s not functioning as expected.
As you can see, I am working on creating a device driver (KMDF without WDF) that interacts with an application (similar to Termit). The task is to control the fan speed thats connect on an Arduino S3 controller via an application. Sending commands over USB works fine, but I run into issues during the reading process.

The Arduino is programmed to return a string (the content isn’t important) if I send a speed greater than 1000 RPM, but I am unable to read any data. I would like to highlight that I have placed the reading function in a thread to ensure uninterrupted sending from the application without any congestion. When I connect to the port using Putty, it returns text in its terminal, so I believe the issue lies with my approach.
Your assistance would mean a lot to me.

CODE:

NTSTATUS ConfigureSerialPort(PDEVICE_OBJECT SerialDeviceObject, PFILE_OBJECT SerialFileObject, PSERIAL_CONFIG SerialConfig) {
NTSTATUS status;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
PIRP irp;
SERIAL_BAUD_RATE baudRate;
SERIAL_LINE_CONTROL lineControl;

baudRate.BaudRate = SerialConfig->BaudRate;
baudRate.BaudRate = SerialConfig->BaudRate;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(IOCTL_SERIAL_SET_BAUD_RATE, SerialDeviceObject, &baudRate, sizeof(baudRate), NULL, 0, FALSE, &event, &ioStatus);
if (irp == NULL) {
    return STATUS_INSUFFICIENT_RESOURCES;
}
status = IoCallDriver(SerialDeviceObject, irp);
if (status == STATUS_PENDING) {
    KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
    status = ioStatus.Status;
}
if (!NT_SUCCESS(status)) {
    KdPrint(("IOCTL_SERIAL_SET_BAUD_RATE: neuspjesno postavljen seriski port, status: %x\n", status));
    return status;
}

lineControl.WordLength = SerialConfig->WordLength;
lineControl.StopBits = SerialConfig->StopBits;
lineControl.Parity = SerialConfig->Parity;
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(IOCTL_SERIAL_SET_LINE_CONTROL, SerialDeviceObject, &lineControl, sizeof(lineControl), NULL, 0, FALSE, &event, &ioStatus);
if (irp == NULL) {
    KdPrint(("IOCTL_SERIAL_SET_LINE_CONTROL: neuspjesno postavljen seriski port"));
    return STATUS_INSUFFICIENT_RESOURCES;
}
status = IoCallDriver(SerialDeviceObject, irp);
if (status == STATUS_PENDING) {
    status = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
    if (!NT_SUCCESS(status)) {
        KdPrint(("ConfigureSerialPort: Cekanje na event nije uspjelo, status: %x\n", status));
        IoCancelIrp(irp);
    }
    else {
        status = ioStatus.Status;
    }
}

return status;

}

NTSTATUS AsyncReadCompletionRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context) {
UNREFERENCED_PARAMETER(DeviceObject);

PKEVENT event = (PKEVENT)Context;
KeSetEvent(event, IO_NO_INCREMENT, FALSE);

KdPrint(("AsyncReadCompletionRoutine: IRP zavrsen sa statusom: %x\n", Irp->IoStatus.Status));

if (Irp->IoStatus.Status == STATUS_CANCELLED) {
    KdPrint(("AsyncReadCompletionRoutine: IRP otkazan\n"));
}

return STATUS_MORE_PROCESSING_REQUIRED;

}

NTSTATUS ReceiveThreadFunction(PVOID Context) {
UNREFERENCED_PARAMETER(Context);
NTSTATUS status;
UCHAR* buffer = NULL;
IO_STATUS_BLOCK ioStatus;
LARGE_INTEGER timeout;
PIRP irp;
KEVENT event;

KdPrint(("ReceiveThreadFunction zapoceo\n"));

while (TRUE) {
    timeout.QuadPart = -RECEIVE_TIMEOUT * 10 * 1000;
    if (KeReadStateEvent(g_StopThreadEvent) != 0) {
        KdPrint(("ReceiveThreadFunction: Zaustavni dogadaj signaliziran, izlazak iz petlje\n"));
        break;
    }

    if (g_SerialPortDeviceObject == NULL || g_SerialPortFileObject == NULL) {
        LARGE_INTEGER delayInterval;
        delayInterval.QuadPart = -2 * 1000 * 1000 * 10;
        KeDelayExecutionThread(KernelMode, FALSE, &delayInterval);
        continue;
    }

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    buffer = (UCHAR*)ExAllocatePoolWithTag(NonPagedPool, BUFFER_SIZE, 'tag1');
    if (buffer == NULL) {
        KdPrint(("ReceiveThreadFunction: Neuspjela alokacija memorije za meduspremnik\n"));
        continue;
    }

    KdPrint(("ReceiveThreadFunction: Kreiranje IRP za citanje podataka\n"));
    irp = IoBuildAsynchronousFsdRequest(IRP_MJ_READ, g_SerialPortDeviceObject, buffer, BUFFER_SIZE, NULL, &ioStatus);
    if (irp == NULL) {
        KdPrint(("ReceiveThreadFunction: Neuspjela alokacija IRP-a\n"));
        ExFreePoolWithTag(buffer, 'tag1');
        continue;
    }

    IoGetNextIrpStackLocation(irp)->FileObject = g_SerialPortFileObject;

    IoSetCompletionRoutine(irp, AsyncReadCompletionRoutine, &event, TRUE, TRUE, TRUE);
    status = IoCallDriver(g_SerialPortDeviceObject, irp);
    if (status == STATUS_PENDING) {
        status = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);
        if (status == STATUS_TIMEOUT) {
            if (IoCancelIrp(irp)) {
                KdPrint(("ReceiveThreadFunction: Vrijeme cekanja isteklo\n"));
                ExFreePoolWithTag(buffer, 'tag1');
                continue;
            }
        }
        status = ioStatus.Status;
    }

    if (NT_SUCCESS(status) && ioStatus.Information > 0) {
        KdPrint(("ReceiveThreadFunction: Primljeni string: "));
        for (ULONG i = 0; i < ioStatus.Information; i++) {
            if (buffer[i] >= 32 && buffer[i] <= 126) {
                KdPrint(("%c", buffer[i]));
            }
        }
        KdPrint(("\n"));
    }
    else if (ioStatus.Information == 0) {
        KdPrint(("ReceiveThreadFunction: Primljeni prazan buffer\n"));
    }
    else {
        KdPrint(("ReceiveThreadFunction: Primanje neuspjesno ili nema primljenih podataka, status: %x, Informacija: %I64u\n", status, ioStatus.Information));
    }
    ExFreePoolWithTag(buffer, 'tag1');
}

KdPrint(("ReceiveThreadFunction zavrsava\n"));
PsTerminateSystemThread(STATUS_SUCCESS);
return STATUS_SUCCESS;

}

NTSTATUS DriverEntry(In PDRIVER_OBJECT DriverObject, In PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status;
HANDLE threadHandle;

status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &DeviceObject);
if (!NT_SUCCESS(status)) {
    KdPrint(("Neuspjelo stvaranje uredjaja\n"));
    return status;
}

status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);
if (!NT_SUCCESS(status)) {
    IoDeleteDevice(DeviceObject);
    return status;
}

for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
    DriverObject->MajorFunction[i] = DispatchPassTrue;
}
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDevCTL;
DriverObject->DriverUnload = Unload;

g_StopThreadEvent = (PKEVENT)ExAllocatePoolWithTag(NonPagedPool, sizeof(KEVENT), 'Stop');
if (g_StopThreadEvent == NULL) {
KdPrint(("Neuspjela alokacija memorije za stop thread event\n"));
IoDeleteSymbolicLink(&SymLinkName);
IoDeleteDevice(DeviceObject);
return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeEvent(g_StopThreadEvent, NotificationEvent, FALSE);
KdPrint(("Stop thread event created\n"));

status = PsCreateSystemThread(&threadHandle, (ACCESS_MASK)0, NULL, NULL, NULL, ReceiveThreadFunction, NULL);
if (!NT_SUCCESS(status)) {
KdPrint(("Neuspjelo stvaranje receive threada: %x\n", status));
ExFreePoolWithTag(g_StopThreadEvent, 'Stop');
IoDeleteSymbolicLink(&SymLinkName);
IoDeleteDevice(DeviceObject);
return status;
}

status = ObReferenceObjectByHandle(threadHandle, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*)&g_ReceiveThreadHandle, NULL);
if (!NT_SUCCESS(status)) {
KdPrint(("Neuspjelo referenciranje receive threada: %x\n", status));
ZwClose(threadHandle);
ExFreePoolWithTag(g_StopThreadEvent, 'Stop');
IoDeleteSymbolicLink(&SymLinkName);
IoDeleteDevice(DeviceObject);
return status;
}
ZwClose(threadHandle);

KdPrint(("Driver loaded\n"));
return STATUS_SUCCESS;

}

So, just to be clear, your Arduino is exposed as a serial-over-USB device? Who is calling "ConfigSerialPort" here? Are you actually getting a read queued up? Does the read ever complete? You aren't checking the status code in your completion routine.

And why are you building and sending IRPs if you're writing a KMDF driver (you're making things waaaaaay harder than they need to be)?

Thank you for your response. To begin, I may have confused you with ConfigSerialPort (I just gave an example of configuring the serial port; ConfigSerialPort is called by an MFC application where the user has the option to configure the port). As for the reading, it goes into the queue but reads nothing. The only problem is I don’t know if

irp = IoBuildAsynchronousFsdRequest(IRP_MJ_READ, g_SerialPortDeviceObject, buffer, BUFFER_SIZE, NULL, &ioStatus);

is the correct way to try read.
The gentleman below mentioned that I am overcomplicating things, but I am unsure how else to approach this.
Also, ICallDriver has been returning the status: 103, and AsyncReadCompletionRoutine reports the IRP completed with the status: c0000120.

Additionally, IoBuildAsynchronousFsdRequest is in a loop for constant reading.

it's because that's how I've been taught from the beginning. If there is an easier method, I would be willing to learn; I just need some guidance on where and how to start. My mentor worked on this a long time ago and doesn't remember such details anymore.

Did you look up either of those codes? A return of 0x103 means STATUS_PENDING. That's what I'd expect. Your request is waiting to be processed. 0xc0000120 is STATUS_CANCELED.

that's how I've been taught from the beginning.

Well, that's how it was done in the beginning. That all changed in 2005, which by now was a VERY long time ago. Look at WdfRequestCreate.