本主題中的程式碼範例示範 Kernel-Mode 驅動程式框架(KMDF)如何為 簡單周邊匯流排(SPB)上的周邊裝置的驅動程式取得操作裝置所需的硬體資源。 這些資源中包含驅動程式用來建立裝置邏輯連線的資訊。 其他資源可能包括中斷和一或多個 GPIO 輸入或輸出引腳。 (GPIO 針腳是一般用途 I/O 控制器裝置上的針腳,設定為輸入或輸出;如需詳細資訊,請參閱 General-Purpose I/O (GPIO) 驅動程式。 與記憶體映射的裝置不同,通過 SPB 連接的周邊裝置不需要系統記憶體位址區塊來映射其暫存器。
此驅動程式會實作一組隨插即用和電源管理事件回呼函式。 若要向 KMDF 註冊這些函式,驅動程式的 EvtDriverDeviceAdd 事件回呼函式會呼叫 WdfDeviceInitSetPnpPowerEventCallbacks 方法。 架構會呼叫電源管理事件回呼函式,以通知驅動程式周邊裝置電源狀態的變更。 這些函式中包含的是 EvtDevicePrepareHardware 函式,它會執行讓驅動程式存取裝置所需的任何作業。
當電源還原至周邊裝置時,驅動程式架構會呼叫 EvtDevicePrepareHardware 函式,以通知 SPB 周邊驅動程式必須準備好此裝置才能使用。 在此呼叫期間,驅動程式會收到兩份硬體資源清單作為輸入參數。 ResourcesRaw 參數是原始資源清單的 WDFCMRESLIST 物件句柄,ResourcesTranslated 參數是轉譯資源清單的 WDFCMRESLIST 物件句柄。 翻譯的資源包含驅動程式建立周邊裝置邏輯連線所需的 連線標識碼 。 如需詳細資訊,請參閱 SPB-Connected 周邊裝置的連線識別碼。
下列程式代碼範例示範 EvtDevicePrepareHardware 函式如何從 ResourcesTranslated 參數取得連線標識碼。
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;
}
}
上述程式碼範例會將 SPB 連線周邊裝置的連線識別碼複製到名為 connectionId的變數中。
下列程式代碼範例示範如何將此聯機標識碼併入裝置路徑名稱,以用來開啟周邊裝置的邏輯連線。 此裝置路徑名稱會將資源中樞識別為系統元件,以從中取得存取周邊裝置所需的參數。
// 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
...
}
在上述程式碼範例中,DECLARE_UNICODE_STRING_SIZE巨集會建立名為 szDeviceName 的初始化UNICODE_STRING變數的宣告,其緩衝區足夠大,可以包含資源中樞所使用格式的裝置路徑名稱。 此巨集定義於 Ntdef.h 頭檔中。
RESOURCE_HUB_PATH_SIZE常數會指定裝置路徑名稱中的位元元組數目。
RESOURCE_HUB_CREATE_PATH_FROM_ID巨集會從連線標識碼產生裝置路徑名稱。
RESOURCE_HUB_PATH_SIZE 和 RESOURCE_HUB_CREATE_PATH_FROM_ID 定義於 Reshub.h 頭檔中。
下列程式碼範例會使用裝置路徑名稱來開啟 SPB 連線周邊裝置的檔案句柄 (名為 SpbIoTarget) 。
// 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
...
}
在上述程式碼範例中, WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME 函式會初始化 WDF_IO_TARGET_OPEN_PARAMS 結構,讓驅動程式藉由指定裝置名稱來開啟周邊裝置的邏輯連線。 變數 SpbIoTarget 是架構 I/O 目標物件的 WDFIOTARGET 句柄。 此句柄是從先前對 WdfIoTargetCreate 方法的呼叫取得的,此方法不會顯示在範例中。 如果 呼叫 WdfIoTargetOpen 方法成功,驅動程式可以使用 SpbIoTarget 句柄將 I/O 要求傳送至周邊裝置。
在 EvtDriverDeviceAdd 事件回呼函式中,SPB 周邊驅動程式可以呼叫 WdfRequestCreate 方法,以配置驅動程式使用的架構要求物件。 稍後,當不再需要物件時,驅動程式會呼叫 WdfObjectDelete 方法來刪除物件。 驅動程式可以重複使用從 WdfRequestCreate 呼叫取得的架構要求物件多次,以將 I/O 要求傳送至周邊裝置。 針對讀取、寫入或 IOCTL 要求,驅動程式會呼叫 WdfIoTargetSendReadSynchronously、 WdfIoTargetSendWriteSynchronously 或 WdfIoTargetSendIoctlSynchronously 方法來傳送要求。
在下列程式碼範例中,驅動程式會呼叫 WdfIoTargetSendWriteSynchronously ,以同步方式將 IRP_MJ_WRITE 要求傳送至 SPB 連線的周邊裝置。 在此範例開始時, pBuffer 變數會指向包含要寫入周邊裝置之數據的非分頁緩衝區,而變數會 dataSize 指定此數據的大小,以位元組為單位。
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
...
}
上述程式代碼範例會執行下列動作:
-
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER函數調用會
memoryDescriptor初始化 變數,這是描述輸入緩衝區的WDF_MEMORY_DESCRIPTOR結構。 先前,驅動程式會呼叫 ExAllocatePoolWithTag 之類的例程,以從非分頁集區配置緩衝區,並將寫入數據複製到此緩衝區。 -
WDF_REQUEST_SEND_OPTIONS_INIT函數調用會
requestOptions初始化 變數,這是包含寫入要求選擇性設定的WDF_REQUEST_SEND_OPTIONS結構。 在此範例中,如果要求在兩秒后未完成,則結構會將要求設定為逾時。 - 呼叫 WdfIoTargetSendWriteSynchronously 方法會將寫入要求傳送至 SPB 連線的周邊裝置。 在寫入作業完成或逾時之後,方法會以同步方式傳回。如有必要,另一個驅動程式線程可以呼叫 WdfRequestCancelSentRequest 來取消要求。
在 WdfIoTargetSendWriteSynchronously 呼叫中,驅動程式會提供名為 SpbRequest的變數,這是驅動程式先前建立之架構要求物件的句柄。
在 WdfIoTargetSendWriteSynchronous 呼叫之後,驅動程式通常應該呼叫 WdfRequestReuse 方法來準備要再次使用的架構要求物件。