Read data from a serial port in KERNEL MODE (KMDF)

I am creating a driver in KMDF mode. I need to constantly read data (loop) from a serial COM port (that is, a device that connects via bluetooth and constantly sends data while connected).

Search and find these informations:

  1. Use ZwReadFile. I was able to read data from the serial COM port, but I don’t know how to make it read constantly. I thought of a solution using Timer, but I think there is another alternative that the WDK offers.

  2. Use WDFIOTARGET. In fact, this was suggested to me at the Microsoft Q&A. But, I looked for how to use and I saw what they use in many examples. None of them understood how to use it, if you know a simple example it would be very helpful.

link

Either by ZwReadFile or WDFIOTARGET, any information would be very helpful.

For drivers, UMDF or KMDF, the Zw functions aren’t really the way to go [https://community.osr.com/discussion/97540/zwreadfile-zwwritefile-within-ioctl] and [https://community.osr.com/discussion/221820/zwreadfile-issue] … they go through a different control path, the WdfIoXXX functions are your only option. You might be basing your work on the MS nonPnP driver sample … if so then don’t do that, it’s time has long, long, long since passed …

I just so happen to be working on a project that involved accessing a serial port from driverland, here’s what you’ll need … [note that this is an amended snippet, be sure you google each of the API’s before you drop it into production code]

— snip —

    [partial function body]

WDF_OBJECT_ATTRIBUTES attribs;
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
attribs.ParentObject = m_device;		// WDFDEVICE of the driver
status = WdfIoTargetCreate(m_device, &attribs, &m_ioTarget);  // we will need to save the ioTarget for later ...

if (NT_SUCCESS(status))
{
	constexpr WCHAR rootPath[] = L"\\DosDevices\\";		// this is going to be different under UMDF
	constexpr size_t rootPathSize = sizeof(rootPath) / sizeof(rootPath[0]);
	size_t size = info->NameLength + rootPathSize;
	WCHAR* portNameBuffer = new (NonPagedPoolNx , 'gnaB') WCHAR[size+1];
	UNICODE_STRING portName = { 0 };
	IO_STATUS_BLOCK ioStatusBlock = { 0 };
	WDF_IO_TARGET_OPEN_PARAMS params;

	wcscpy(portNameBuffer, rootPath);

	wcsncat(portNameBuffer, info->Name, info->NameLength);
	portNameBuffer[size] = L'\0';
	RtlInitUnicodeString(&portName, portNameBuffer);

	WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(
	&params,
	&portName,
	MAXIMUM_ALLOWED);  // this should be pared down to specific rights

	params.EvtIoTargetQueryRemove = EvtIoTargetQueryRemove;				// these can be NULL, will take the OS defaults then
	params.EvtIoTargetRemoveCanceled = EvtWdfIoTargetRemoveCanceled;
	params.EvtIoTargetRemoveComplete = EvtWdfIoTargetRemoveComplete;
	status = WdfIoTargetOpen(m_ioTarget, &params);

	if (!NT_SUCCESS(status))
	{
		DbgPrintEx(DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "%s: Failed WdfIoTargetOpen 0x%x\n",
		__FUNCTION__, status);
	}
	else
	{
		found = true;
		WDFREQUEST request;
		status = WdfRequestCreate(WDF_NO_OBJECT_ATTRIBUTES, m_ioTarget, &request);
		if (NT_SUCCESS(status))
		{
			// Set the baud rate.
			SERIAL_BAUD_RATE baudRate = { 115200 };
			WDF_MEMORY_DESCRIPTOR memDesc;
			WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memDesc, &baudRate, sizeof(baudRate));
			WDF_REQUEST_SEND_OPTIONS sendOptions;
			WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0);
			ULONG_PTR bytesReturned = 0;
			status = WdfIoTargetSendIoctlSynchronously(m_ioTarget, request, IOCTL_SERIAL_SET_BAUD_RATE, &memDesc, nullptr, &sendOptions, &bytesReturned);
			if (!NT_SUCCESS(status))
			{
			DbgPrintEx(DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "%s: serial set baud rate failed 0x%x\n",
			__FUNCTION__, status);
			}
		}
	}

	delete[] portNameBuffer;
	portNameBuffer = nullptr;
}

}

— snip —

… to read from that target …

— snip —

NTSTATUS IoTarget::Read(ULONG BytesToRead, PVOID ReadBuffer, ULONG* BytesRead, ULONG Timeout)
{
WDFREQUEST request;
NTSTATUS status = WdfRequestCreate(WDF_NO_OBJECT_ATTRIBUTES, m_ioTarget, &request);

if (NT_SUCCESS(status))
{
    WDF_MEMORY_DESCRIPTOR memoryDesc;
    WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDesc, ReadBuffer, BytesToRead);
    WDF_REQUEST_SEND_OPTIONS sendOptions;
    WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0);

    if (Timeout != 0)
    {
        WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_MS(Timeout));
    }

    ULONG_PTR bytesRead = 0;

    status = WdfIoTargetSendReadSynchronously(m_ioTarget, request, &memoryDesc, 0, &sendOptions, &bytesRead);

    if (NT_SUCCESS(status) && BytesRead != nullptr)
    {
        *BytesRead = static_cast<ULONG>(bytesRead);
    } else {
        TRACE2 ("Failed to read %i bytes %s\n", BytesToRead, Bus_GetNTStatusString(status));  // custom debugging functions
        *BytesRead = 0;
    }

    WdfObjectDelete(request);
}
return status;

}

— snip —

… and to write to that object …

— snip —

NTSTATUS IoTarget::Write(ULONG BytesToWrite, const PVOID WriteBuffer, ULONG* BytesWritten, ULONG Timeout)
{
WDFREQUEST request;

NTSTATUS status = WdfRequestCreate(WDF_NO_OBJECT_ATTRIBUTES, m_ioTarget, &request);

if (NT_SUCCESS(status))
{
    WDF_MEMORY_DESCRIPTOR memoryDesc;
    WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDesc, WriteBuffer, BytesToWrite);
    WDF_REQUEST_SEND_OPTIONS sendOptions;
    WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, 0);

    if (Timeout != 0)
    {
        WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_MS(Timeout));
    }

    ULONG_PTR bytesWritten = 0;

    status = WdfIoTargetSendWriteSynchronously(m_ioTarget, request, &memoryDesc, 0, &sendOptions, &bytesWritten);

    if (NT_SUCCESS(status) && BytesWritten != nullptr)
    {
        *BytesWritten = static_cast<ULONG>(bytesWritten);
    } else {
        TRACE2 ("Failed to write %i bytes %s\n", BytesToWrite, Bus_GetNTStatusString(status));  // custom debugging functions
        *BytesWritten = 0;
    }

    WdfObjectDelete(request);
}
return status;

}

— snip —

Basically you want to do is create an IoTarget, then open something that will use that target (here a serial port), then tell the OS how to communicate with that something (synchronous or asynchronous) …

There is much GoogleFu is in your future, very much like with a chainsaw you will need to understand what the tool does before you can really safely use it/ them …

For drivers, UMDF or KMDF, the Zw functions aren’t really the way to go … they go through a different control path,
the WdfIoXXX functions are your only option.

I’m not sure what you were trying to say here, but this wasn’t it. KMDF changes nothing in the “control path” of a driver. If the Zw functions worked in a WDM driver, then they will work in a KMDF driver. KMDF might provide a BETTER mechanism, but it certainly doesn’t eliminate any existing mechanisms.

Umm … well … not quite …

As Doron rightly pointed out a few years ago here [https://community.osr.com/discussion/133284/how-to-access-com-port-from-a-driver] there are subtle differences between the ZwXX functions and the WdfIoTargetXX functions and where they go; that’s why the ZwXX functions have to be called at PASSIVE_LEVEL and with special APC’s enabled. The OP of course is free to use the ZwXX functions, he inquired about the WdfXX functions to accomplish the task and that’s what I posted. The final decision about what API to use, based on GoogleFu research into ZwXX and WdfIoXX functions, is entirely his …

In a WDF driver using io targets is really much simpler.

there are subtle differences between the ZwXX functions and the WdfIoTargetXX functions and where they go; that’s why the ZwXX functions have to be called at PASSIVE_LEVEL and with special APC’s enabled.

So… #1, I agree that the OP probably would be better off using IO Targets.

But, #2… I’m with Tim. In the end, it’s all about sending an IRP to a Device Object. The mechanics for constructing those IRPs may be different, but there’s no different “control path” over which those IRPs flow… there’s no monkey magic involved.

Peter

The OP somehow thought he would get better answers from what I gave on the MSFT QA site. In reality, he is not looking for guidance to learn from, he just wants a sample from which he can copy and paste from and declare victory.

With that said, the one advantage of the wdfiotarget is that you can can send IO from IRQL > PASSIVE_LEVEL, as pointed out the Zw APIs have the IRQL restriction which can limit the context in which you initiate the IO.

@Doron_Holan said:
The OP somehow thought he would get better answers from what I gave on the MSFT QA site. In reality, he is not looking for guidance to learn from, he just wants a sample from which he can copy and paste from and declare victory.

With that said, the one advantage of the wdfiotarget is that you can can send IO from IRQL > PASSIVE_LEVEL, as pointed out the Zw APIs have the IRQL restriction which can limit the context in which you initiate the IO.

Thank you very much for the help. And I’m sorry, Dolon Holan, if I asked the wrong way for help.
I am not asking for a copy & paste and create without any effort and have the victory. In fact, I was with this every night until late to understand him and create this driver.
But, I honestly did not understand him and that is why he asked for a simple example to analyze and understand.
I will keep looking for information and trying to understand it. Thanks!!

@Doron_Holan said:
… In reality, he is not looking for guidance to learn from, he just wants a sample from which he can copy and paste from and declare victory.

Why not? This is how the software development runs these days, no?
One can google for [the other OS] drivers and find a lot of them.

This is how the software development runs these days, no?

Yes. But kernel-mode code, especially for Windows, is NOT amenable to this approach. I’ve written about this before.

KMDF puts Windows driver development within the reach of any software engineer who knows C and wants to spend a reasonable amount of time — from a few days to a few weeks, depending on how it’s done and the dev’s background — becoming familiar with Windows kernel-mode principles. But it is most assuredly not reasonable to cut/paste without understanding. This isn’t like needed a particular “left join” that you can’t figure out. And getting it wrong jeopardizes the stability of the system.

Peter

@Pavel_A said:

@Doron_Holan said:
… In reality, he is not looking for guidance to learn from, he just wants a sample from which he can copy and paste from and declare victory.

Why not? This is how the software development runs these days, no?
One can google for [the other OS] drivers and find a lot of them.

I’m pretty sure you’re being facetious, but I’ll play … you could develop like that, and many people do … and if you’re a grad student trying to get something done for a class assignment that’s fine, as long as it works one time in one circumstance then the teacher is happy and you can move onto your Mersienne Twister for that SSH project due next week …

If you’re working for a company, though, and you do that … well, I derive a significant amount of my income helping companies dig themselves out of those kinds of cut and past holes and it usually ends badly for the cut-and-paster when I present my report … (and I do bill hourly, Peter, because like the contractor you hire to fix a constantly wet floor under the sink until I’m there poking around I don’t know if I’m just replacing a faucet washer or tearing out your walls to fix a failed pipe sweat joint and I’m not doing that investigation work for free. I worked a project recently that finally traced back to a single USB chip with some badly written firmware with a lot of workarounds for that firmware but it took about a month of digging to find and document that 'lil bugger, then give a time and materials estimate to fix that mess) …

To the OP, your best resource is GoogleFu … and this list is good too! Post what you are trying to do (and not how you are trying to do it), see what people say back and Google the stuffing out of every single word in those replies … if there’s something you don’t understand, then ask that too (but be sure to search the archives here first, most likely that question has already been asked and answered in the past) …