Hardwareressourcen für Kernel-Mode SPB-Peripherietreiber
Die Codebeispiele in diesem Thema zeigen, wie der KmDF-Treiber (Kernel-Mode Driver Framework ) für ein Peripheriegerät auf einem einfachen Peripheriebus (SPB) die Hardwareressourcen abruft, die für den Betrieb des Geräts erforderlich sind. In diesen Ressourcen enthalten sind die Informationen, die der Treiber verwendet, um eine logische Verbindung mit dem Gerät herzustellen. Zusätzliche Ressourcen können einen Interrupt und mindestens einen GPIO-Eingabe- oder Ausgabenadel enthalten. (Ein GPIO-Pin ist ein Pin an einem universellen E/A-Controllergerät, das als Eingabe oder Ausgabe konfiguriert ist. Weitere Informationen finden Sie unter GpIO-Treiber (General-Purpose E/O). Im Gegensatz zu einem Gerät mit Speicherzuordnung benötigt ein mit SPB verbundenes Peripheriegerät keinen Block von Systemspeicheradressen, um seine Register zuzuordnen.
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.
Wenn die Stromversorgung des Peripheriegeräts wiederhergestellt wird, ruft das Treiberframework die EvtDevicePrepareHardware-Funktion auf, um den SPB-Peripherietreiber zu benachrichtigen, dass dieses Gerät für die Verwendung vorbereitet sein muss. 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. Weitere Informationen finden Sie unter Verbindungs-IDs für SPB-Connected Peripheriegeräte.
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_I2C)
{
if (fConnectionIdFound == FALSE)
{
// Save the SPB 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 mit SPB verbundenes 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 SpbIoTarget
) für das mit SPB verbundene Peripheriegerät zu öffnen.
// Open the SPB peripheral device 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(SpbIoTarget, &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 Peripheriegerät herstellen kann, indem er den Namen des Geräts angibt. Die SpbIoTarget
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 SpbIoTarget
Handle verwenden, um E/A-Anforderungen an das Peripheriegerät zu senden.
In der Ereignisrückruffunktion EvtDriverDeviceAdd kann der SPB-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. Für eine Lese-, Schreib- oder IOCTL-Anforderung ruft der Treiber die WdfIoTargetSendReadSynchronously-, WdfIoTargetSendWriteSynchronously- oder WdfIoTargetSendIoctlSynchronously-Methode auf, um die Anforderung zu senden.
Im folgenden Codebeispiel ruft der Treiber WdfIoTargetSendWriteSynchronously auf, um synchron eine IRP_MJ_WRITE Anforderung an das mit SPB verbundene 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(SpbIoTarget,
SpbRequest,
&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 mit SPB verbundene 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 SpbRequest
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.