KMDF 周辺機器ドライバーをシリアル ポートに接続する
SerCx2 が管理するシリアル ポート上の周辺デバイス用の KMDF ドライバーは、デバイスを動作させるために特定のハードウェア リソースを必要とします。 これらのリソースには、ドライバーがシリアル ポートへの論理接続を開くために必要な情報が含まれています。 追加のリソースには、割り込み、および 1 つ以上の GPIO 入力ピンまたは出力ピンが含まれる場合があります。
このドライバーは、プラグ アンド プレイおよび電源管理イベント コールバック関数のセットを実装します。 これらの関数を KMDF に登録するには、ドライバーの EvtDriverDeviceAdd イベント コールバック関数が WdfDeviceInitSetPnpPowerEventCallbacks メソッドを呼び出します。 フレームワークは、電源管理イベント コールバック関数を呼び出して、周辺デバイスの電源状態の変化をドライバーに通知します。 これらの関数には、EvtDevicePrepareHardware 関数が含まれており、ドライバーがデバイスにアクセスできるようにするために必要な操作を実行します。
シリアル接続された周辺デバイスが初期化されていない D0 デバイス電源状態に入ると、ドライバー フレームワークは EvtDevicePrepareHardware 関数を呼び出して、デバイスを使用できるように準備するように周辺ドライバーに指示します。 この呼び出し中に、ドライバーはハードウェア リソースの 2 つのリストを入力パラメーターとして受け取ります。 ResourcesRaw パラメータは、raw resources のリストへのWDFCMRESLISTオブジェクトハンドルで、ResourcesTranslated パラメータは、翻訳されたリソースのリストへの WDFCMRESLIST オブジェクトハンドルです。 変換されたリソースには、ドライバーが周辺デバイスへの論理接続を確立するために必要な接続 ID が含まれます。
次のコード例は、EvtDevicePrepareHardware 関数が ResourcesTranslatedパラメーターから接続 ID を取得する方法を示しています。
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;
}
}
前述のコード例では、シリアル接続された周辺デバイスの接続 ID をconnectionId
という名前の変数にコピーします。
次のコード例は、周辺デバイスへの論理接続を開くために使用できるデバイス パス名にこの接続 ID を組み込む方法を示しています。 このデバイス パス名は、周辺デバイスへのアクセスに必要なパラメータを取得するシステム コンポーネントとしてリソース ハブを識別します。
// 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 マクロは、接続 ID からデバイス パス名を生成します。 RESOURCE_HUB_PATH_SIZE と RESOURCE_HUB_CREATE_PATH_FROM_ID は、Reshub.h ヘッダー ファイルで定義されています。
次のコード例では、デバイス パス名を使用して、シリアル接続された周辺デバイスへのファイル ハンドル (SerialIoTarget
という名前) を開きます。
// 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
...
}
前述のコード例では、WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME 関数が WDF_IO_TARGET_OPEN_PARAMS 構造体を初期化して、ドライバーがシリアル接続されたペリフェラルへの論理接続を開くことができるようにします。デバイスの名前を指定してデバイスを選択します。 SerialIoTarget
変数は、フレームワーク I/O ターゲット オブジェクトへの WDFIOTARGET ハンドルです。 このハンドルは、WdfIoTargetCreate メソッドへの以前の呼び出しから取得されましたが、この例には示されていません。 WdfIoTargetOpen メソッドの呼び出しが成功すると、ドライバーはSerialIoTarget
ハンドルを使用して I/O 要求を周辺デバイスに送信できます。
EvtDriverDeviceAdd イベント コールバック関数で、周辺機器ドライバーは WdfRequestCreate メソッドを呼び出して、ドライバーが使用するフレームワーク要求オブジェクトを割り当てることができます。 その後、オブジェクトが不要になると、ドライバーは WdfObjectDelete メソッドを呼び出してオブジェクトを削除します。 ドライバーは、WdfRequestCreate 呼び出しから取得したフレームワーク要求オブジェクトを複数回再利用して、I/O 要求を周辺デバイスに送信できます。 読み込み、書き込み、または IOCTL リクエストを同期的に送信するには、ドライバは WdfIoTargetSendReadSynchronously、WdfIoTargetSendWriteSynchronously、または WdfIoTargetSendIoctlSynchronously メソッドを呼び出します。
次のコード例では、ドライバーは WdfIoTargetSendWriteSynchronously を呼び出して、IRP_MJ_WRITE リクエストを周辺デバイスに同期的に送信します。 この例の開始時に、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(SerialIoTarget,
SerialRequest,
&memoryDescriptor,
NULL,
&requestOptions,
&bytesWritten);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
前述のコード例では次の処理が行われます:
- WDF_MEMORY_DESCRIPTOR_INIT_BUFFER 関数呼び出しは、入力バッファを記述する WDF_MEMORY_DESCRIPTOR 構造体である
memoryDescriptor
変数を初期化します。 以前は、ドライバーは ExAllocatePoolWithTag などのルーチンを呼び出して非ページ プールからバッファーを割り当て、書き込みデータをこのバッファーにコピーしていました。 - WDF_REQUEST_SEND_OPTIONS_INIT 関数呼び出しは、書き込みリクエストのオプション設定を含む WDF_REQUEST_SEND_OPTIONS 構造体である
requestOptions
変数を初期化します。 この例では、リクエストが 2 秒後に完了しなければタイムアウトするように構成されています。 - WdfIoTargetSendWriteSynchronously メソッドを呼び出すと、書き込みリクエストが周辺デバイスに送信されます。 このメソッドは、書き込み操作が完了するかタイムアウトした後、同期的に戻ります。必要に応じて、別のドライバー スレッドが WdfRequestCancelSentRequest を呼び出してリクエストをキャンセルできます。
WdfIoTargetSendWriteSynchronously 呼び出しでは、ドライバーは、ドライバーが以前に作成したフレームワーク要求オブジェクトへのハンドルであるSerialRequest
という名前の変数を提供します。 WdfIoTargetSendWriteSynchronously 呼び出しの後、ドライバーは通常、WdfRequestReuse メソッドを呼び出して、フレームワーク リクエスト オブジェクトを再度使用する準備をします。