Verbinden eines KMDF-Peripherietreibers mit einem seriellen Port
Der KMDF-Treiber für ein Peripheriegerät an einem von SerCx2 verwalteten seriellen Port erfordert bestimmte Hardwareressourcen, um das Gerät zu betreiben. In diesen Ressourcen enthalten sind die Informationen, die der Treiber benötigt, um eine logische Verbindung mit dem seriellen Port zu öffnen. Zusätzliche Ressourcen können einen Interrupt und mindestens einen GPIO-Eingabe- oder Ausgabepins enthalten.
Dieser Treiber implementiert eine Reihe von Plug & Play- und Energieverwaltungs-Ereignisrückruffunktionen. Um diese Funktionen bei KMDF zu registrieren, ruft die EvtDriverDeviceAdd-Ereignisrückruffunktion des Treibers die WdfDeviceInitSetPnpPowerEventCallbacks-Methode auf. Das Framework ruft die Rückruffunktionen des Energieverwaltungsereignisses auf, um den Treiber über Änderungen im Energiezustand des Peripheriegeräts zu benachrichtigen. In diesen Funktionen ist die EvtDevicePrepareHardware-Funktion enthalten, die alle Vorgänge ausführt, die erforderlich sind, um das Gerät für den Treiber zugänglich zu machen.
Nachdem das serielle Peripheriegerät in einen nicht initialisierten D0-Gerätestromzustand wechselt, ruft das Treiberframework die EvtDevicePrepareHardware-Funktion auf, um den Peripherietreiber anzurufen, das Gerät für die Verwendung vorzubereiten. Während dieses Aufrufs empfängt der Treiber zwei Listen von Hardwareressourcen als Eingabeparameter. Der ResourcesRaw-Parameter ist ein WDFCMRESLIST-Objekthandle für die Liste der Rohressourcen, und der ResourcesTranslated-Parameter ist ein WDFCMRESLIST-Objekthandle für die Liste der übersetzten Ressourcen. Die übersetzten Ressourcen enthalten die Verbindungs-ID , die der Treiber benötigt, um eine logische Verbindung mit dem Peripheriegerät herzustellen.
Das folgende Codebeispiel zeigt, wie die EvtDevicePrepareHardware-Funktion die Verbindungs-ID aus dem ResourcesTranslated-Parameter abruft .
BOOLEAN fConnectionIdFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;
NTSTATUS status = STATUS_SUCCESS;
resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);
// Loop through the resources and save the relevant ones.
for (ULONG ix = 0; ix < resourceCount; ix++)
{
PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;
pDescriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, ix);
if (pDescriptor == NULL)
{
status = E_POINTER;
break;
}
// Determine the resource type.
switch (pDescriptor->Type)
{
case CmResourceTypeConnection:
{
// Check against the expected connection types.
UCHAR Class = pDescriptor->u.Connection.Class;
UCHAR Type = pDescriptor->u.Connection.Type;
if (Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL)
{
if (Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_UART)
{
if (fConnectionIdFound == FALSE)
{
// Save the connection ID.
connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
fConnectionIdFound = TRUE;
}
}
}
if (Class == CM_RESOURCE_CONNECTION_CLASS_GPIO)
{
// Check for GPIO pin resource.
...
}
}
break;
case CmResourceTypeInterrupt:
{
// Check for interrupt resource.
...
}
break;
default:
// Don't care about other resource descriptors.
break;
}
}
Im vorherigen Codebeispiel wird die Verbindungs-ID für ein serielles Peripheriegerät in eine Variable namens connectionId
kopiert.
Das folgende Codebeispiel zeigt, wie Sie diese Verbindungs-ID in einen Gerätepfadnamen integrieren, der zum Öffnen einer logischen Verbindung mit dem Peripheriegerät verwendet werden kann. Dieser Gerätepfadname identifiziert den Ressourcenhub als Systemkomponente, von der aus die parameter abgerufen werden können, die für den Zugriff auf das Peripheriegerät erforderlich sind.
// Use the connection ID to create the full device path name.
DECLARE_UNICODE_STRING_SIZE(szDeviceName, RESOURCE_HUB_PATH_SIZE);
status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&szDeviceName,
connectionId.LowPart,
connectionId.HighPart);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
Im vorherigen Codebeispiel erstellt das makro DECLARE_UNICODE_STRING_SIZE die Deklaration einer initialisierten UNICODE_STRING Variable namens szDeviceName
, die über einen Puffer verfügt, der groß genug ist, um einen Gerätepfadnamen im vom Ressourcenhub verwendeten Format zu enthalten. Dieses Makro wird in der Headerdatei Ntdef.h definiert. Die RESOURCE_HUB_PATH_SIZE-Konstante gibt die Anzahl der Bytes im Gerätepfadnamen an. Das RESOURCE_HUB_CREATE_PATH_FROM_ID Makro generiert den Gerätepfadnamen aus der Verbindungs-ID. RESOURCE_HUB_PATH_SIZE und RESOURCE_HUB_CREATE_PATH_FROM_ID werden in der Reshub.h-Headerdatei definiert.
Im folgenden Codebeispiel wird der Gerätepfadname verwendet, um ein Dateihandle (namens SerialIoTarget
) für das serielle Peripheriegerät zu öffnen.
// Open the peripheral device on the serial port as a remote I/O target.
WDF_IO_TARGET_OPEN_PARAMS openParams;
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&openParams,
&szDeviceName,
(GENERIC_READ | GENERIC_WRITE));
openParams.ShareAccess = 0;
openParams.CreateDisposition = FILE_OPEN;
openParams.FileAttributes = FILE_ATTRIBUTE_NORMAL;
status = WdfIoTargetOpen(SerialIoTarget, &openParams);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
Im vorherigen Codebeispiel initialisiert die WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME-Funktion die WDF_IO_TARGET_OPEN_PARAMS-Struktur , damit der Treiber eine logische Verbindung mit dem seriellen Peripheriegerät herstellen kann, indem er den Namen des Geräts angibt. Die SerialIoTarget
Variable ist ein WDFIOTARGET-Handle für ein Framework-E/A-Zielobjekt. Dieses Handle wurde aus einem vorherigen Aufruf der WdfIoTargetCreate-Methode abgerufen, die im Beispiel nicht gezeigt wird. Wenn der Aufruf der WdfIoTargetOpen-Methode erfolgreich ist, kann der Treiber das SerialIoTarget
Handle verwenden, um E/A-Anforderungen an das Peripheriegerät zu senden.
In der Ereignisrückruffunktion EvtDriverDeviceAdd kann der Peripherietreiber die WdfRequestCreate-Methode aufrufen, um ein Frameworkanforderungsobjekt für die Verwendung durch den Treiber zuzuweisen. Wenn das Objekt später nicht mehr benötigt wird, ruft der Treiber die WdfObjectDelete-Methode auf, um das Objekt zu löschen. Der Treiber kann das Vom WdfRequestCreate-Aufruf abgerufene Frameworkanforderungsobjekt mehrmals wiederverwenden, um E/A-Anforderungen an das Peripheriegerät zu senden. Zum synchronen Senden einer Lese-, Schreib- oder IOCTL-Anforderung ruft der Treiber die WdfIoTargetSendReadSynchronously-, WdfIoTargetSendWriteSynchronously- oder WdfIoTargetSendIoctlSynchronously-Methode auf.
Im folgenden Codebeispiel ruft der Treiber WdfIoTargetSendWriteSynchronously auf, um synchron eine IRP_MJ_WRITE Anforderung an das Peripheriegerät zu senden. Zu Beginn dieses Beispiels zeigt die pBuffer
Variable auf einen nicht auslagerten Puffer, der die Daten enthält, die auf das Peripheriegerät geschrieben werden sollen, und die dataSize
Variable gibt die Größe dieser Daten in Bytes an.
ULONG_PTR bytesWritten;
NTSTATUS status;
// Describe the input buffer.
WDF_MEMORY_DESCRIPTOR memoryDescriptor;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, pBuffer, dataSize);
// Configure the write request to time out after 2 seconds.
WDF_REQUEST_SEND_OPTIONS requestOptions;
WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT);
requestOptions.Timeout = WDF_REL_TIMEOUT_IN_SEC(2);
// Send the write request synchronously.
status = WdfIoTargetSendWriteSynchronously(SerialIoTarget,
SerialRequest,
&memoryDescriptor,
NULL,
&requestOptions,
&bytesWritten);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
Im vorherigen Codebeispiel wird Folgendes ausgeführt:
- Der WDF_MEMORY_DESCRIPTOR_INIT_BUFFER Funktionsaufruf initialisiert die
memoryDescriptor
Variable. Dabei handelt es sich um eine WDF_MEMORY_DESCRIPTOR-Struktur , die den Eingabepuffer beschreibt. Zuvor hat der Treiber eine Routine wie ExAllocatePoolWithTag aufgerufen, um den Puffer aus einem nicht auszugebenden Pool zuzuweisen, und die Schreibdaten in diesen Puffer kopiert. - Der WDF_REQUEST_SEND_OPTIONS_INIT Funktionsaufruf initialisiert die
requestOptions
Variable. Hierbei handelt es sich um eine WDF_REQUEST_SEND_OPTIONS-Struktur , die die optionalen Einstellungen für die Schreibanforderung enthält. In diesem Beispiel konfiguriert die Struktur die Anforderung so, dass ein Timeout erfolgt, wenn sie nicht nach zwei Sekunden abgeschlossen ist. - Der Aufruf der WdfIoTargetSendWriteSynchronously-Methode sendet die Schreibanforderung an das Peripheriegerät. Die Methode gibt synchron zurück, nachdem der Schreibvorgang abgeschlossen oder ein Zeitüberschreitungsvorgang ausgeführt wurde. Bei Bedarf kann ein anderer Treiberthread WdfRequestCancelSentRequest aufrufen, um die Anforderung abzubrechen.
Im WdfIoTargetSendWriteSynchronously-Aufruf stellt der Treiber eine Variable mit dem Namen SerialRequest
bereit, die ein Handle für ein Frameworkanforderungsobjekt ist, das der Treiber zuvor erstellt hat. Nach dem Aufruf von WdfIoTargetSendWriteSynchronously sollte der Treiber in der Regel die WdfRequestReuse-Methode aufrufen, um das Frameworkanforderungsobjekt für die erneute Verwendung vorzubereiten.