Kernel-Mode SPB 周邊驅動程式的硬體資源
本主題中的程式碼範例示範 核心模式驅動程式架構 (KMDF) 驅動程式如何在 簡單的周邊匯流排 上 (SPB) 取得操作裝置所需的硬體資源。 這些資源中包含驅動程式用來建立裝置邏輯連線的資訊。 其他資源可能包括中斷和一或多個 GPIO 輸入或輸出針腳。 (GPIO 針腳是設定為輸入或輸出的一般用途 I/O 控制器裝置上的針腳;如需詳細資訊,請參閱 一般用途 I/O (GPIO) Drivers.) 不同于記憶體對應的裝置,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 方法來傳送要求。
在下列程式碼範例中,驅動程式會以同步方式呼叫 WdfIoTargetSendWriteSynchronous, 以同步方式將 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 來取消要求。
在 WdfIoTargetSendWriteSynchronous 呼叫 中,驅動程式會提供名為 SpbRequest
的變數,這是驅動程式先前建立之架構要求物件的控制碼。 在 WdfIoTargetSendWriteSynchronous 呼叫之後,驅動程式通常應該呼叫WdfRequestReuse方法來準備要再次使用的架構要求物件。