Write a UCSI client driver (UCSI クライアント ドライバーの作成)
USB Type-C Connector System Software Interface (UCSI) ドライバーは、組み込みコントローラー (EC) を搭載した USB Type-C システムのコントローラー ドライバーとして機能します。
システムが Platform Policy Manager (PPM) を実装しており、UCSI の仕様に記載されているように、以下を介してシステムに接続されている EC 内にある場合:
ACPI トランスポート。ドライバーを作成する必要はありません。 Microsoft 提供のインボックス ドライバー (UcmUcsiCx.sys および UcmUcsiAcpiClient.sys) を読み込みます (「UCSI ドライバー」を参照)。
非 ACPI トランスポート (USB、PCI、I2C、UART など) では、コントローラー用のクライアント ドライバーを作成する必要があります。
Note
USB Type-C ハードウェアに PD (Power Delivery) ステート マシンを処理する機能がない場合は、USB Type-C ポート コントローラー ドライバーの作成を検討してください。 詳細については、「USB Type-C ポート コントローラー ドライバーの作成」を参照してください。
Windows 10 バージョン 1809 以降、UCSI の新しいクラス拡張機能 (UcmUcsiCx.sys) が追加され、トランスポートに依存しない方法で UCSI 仕様を実装します。 最小限のコードで、UcmUcsiCx へのクライアントであるドライバーが非 ACPI トランスポート経由で USB Type-C ハードウェアと通信できます。 このトピックでは、UCSI クラス拡張機能で提供されるサービスと、クライアント ドライバーの想定される振る舞いについて説明します。
公式の仕様
適用対象:
- Windows 10 Version 1809
WDF のバージョン
- KMDF バージョン 1.27
重要な API
サンプル
ACPI 部分を必要なバスの実装に置き換えます。
UCSI クラス拡張機能アーキテクチャ
UCSI クラス拡張機能である UcmUcsiCx を使用すると、非 ACPI トランスポートを使用して組み込みコントローラーと通信するドライバーを作成できます。 コントローラー ドライバーは UcmUcsiCx のクライアント ドライバーです。 UcmUcsiCx は USB コネクタ マネージャー (UCM) のクライアントです。 したがって、UcmUcsiCx は独自のポリシーを決定しません。 代わりに、UCM によって提供されるポリシーを実装します。 UcmUcsiCx は、クライアント ドライバーからの Platform Policy Manager (PPM) の通知を処理するためのステート マシンを実装し、UCM ポリシー決定を実装するコマンドを送信します。これにより、より信頼性の高い問題検出とエラー処理を可能にします。
OS Policy Manager (OPM)
OS Policy Manager (OPM) は、UCSI 仕様に記載されているように、PPM とやり取りするロジックを実装します。 OPM では次を担当します。
- UCM ポリシーを UCSI コマンドに変換し、UCSI 通知を UCM 通知に変換する。
- PPM の初期化、エラーの検出、回復メカニズムに必要な UCSI コマンドを送信する。
UCSI コマンドの処理
一般的な操作には、UCSI 準拠のハードウェアによって実行されるいくつかのコマンドが含まれます。 たとえば、GET_CONNECTOR_STATUS コマンドを考えてみましょう。
- PPM ファームウェアは、接続変更通知を UcmUcsiCx/クライアント ドライバーに送信します。
- これに応答して、UcmUcsiCx/クライアント ドライバーは GET_CONNECTOR_STATUS コマンドを PPM ファームウェアに返送します。
- PPM ファームウェアは GET_CONNECTOR_STATUS を実行し、コマンド完了通知を UcmUcsiCx/client ドライバーに非同期的に送信します。 この通知には、実際の接続ステータスに関するデータが含まれています。
- UcmUcsiCx/クライアント ドライバーはそのステータス情報を処理し、ACK_CC_CI を PPM ファームウェアに送信します。
- PPM ファームウェアは ACK_CC_CI を実行し、コマンド完了通知を UcmUcsiCx/クライアント ドライバーに非同期的に送信します。
- UcmUcsiCx/クライアント ドライバーは、GET_CONNECTOR_STATUS コマンドが完了したものと見なします。
Platform Policy Manager (PPM) との通信
UcmUcsiCx は、OPM から PPM ファームウェアへの UCSI コマンドの送信と、PPM ファームウェアからの通知の受信の詳細を抽象化します。 PPM コマンドを WDFREQUEST オブジェクトに変換し、クライアント ドライバーに転送します。
PPM 通知
クライアント ドライバーは、ファームウェアからの PPM 通知について UcmUcsiCx に通知します。 ドライバーは、CCI を含む UCSI データ ブロックを提供します。 UcmUcsiCx は、通知を OPM や他のコンポーネントに転送し、OPM や他のコンポーネントはそのデータに基づいて適切な処理を行います。
クライアント ドライバーへの IOCTL
UcmUcsiCx は、PPM ファームウェアに送信する UCSI コマンドを (IOCTL 要求を通じて) クライアント ドライバーに送信します。 ドライバーは、UCSI コマンドをファームウェアに送信した後、この要求を完了する必要があります。
電力遷移の処理
クライアント ドライバーは電力ポリシーの所有者です。
S0-Idle が原因でクライアント ドライバーが Dx 状態になる場合、UcmUcsiCx が UCSI コマンドを含む IOCTL をクライアント ドライバーの電源管理キューに送信すると、WDF はドライバーを D0 にします。 S0-Idle のクライアント ドライバーは、ファームウェアから PPM 通知があると、再び電源が入った状態に戻る必要があります。なぜなら、S0-Idle では PPM 通知がまだ有効であるためです。
開始する前に
ハードウェアまたはファームウェアが PD ステート マシンを実装しているかどうか、また、トランスポートに応じて、作成する必要があるドライバーのタイプを決定します。
詳細については、「USB Type-C コネクタ用の Windows ドライバーの開発」を参照してください。
Windows 10 デスクトップ エディション (Home、Pro、Enterprise、Education) をインストールします。
最新の Windows Driver Kit (WDK) を開発用コンピューターにインストールします。 このキットには、クライアント ドライバーを作成するために必要なヘッダー ファイルとライブラリが含まれています。具体的には、以下が必要になります。
- スタブ ライブラリ (UcmUcsiCxStub.lib)。 このライブラリは、クライアント ドライバーによって行われた呼び出しを変換し、クラス拡張機能に渡します。
- ヘッダー ファイル Ucmucsicx.h。
- クライアント ドライバーはカーネル モードで実行され、KMDF 1.27 ライブラリにバインドされます。
Windows Driver Foundation (WDF) についてよく理解します。 推奨資料: Windows Driver Foundation を使用したドライバーの開発 Guy Smith および Penny Orwick 共著
1. クライアント ドライバーを UcmUcsiCx に登録する
EVT_WDF_DRIVER_DEVICE_ADD 実装内。
プラグ アンド プレイおよび電源管理イベント コールバック関数 (WdfDeviceInitSetPnpPowerEventCallbacks) を設定した後、UcmUcsiDeviceInitInitialize を呼び出して WDFDEVICE_INIT 不透明構造体を初期化します。 この呼び出しにより、クライアント ドライバーがフレームワークに関連付けられます。
フレームワーク デバイス オブジェクト (WDFDEVICE) を作成した後、UcmUcsiDeviceInitialize を呼び出してクライアント ドライバーを UcmUcsiCx に登録します。
2. UcmUcsiCx を使用して PPM オブジェクトを作成する
EVT_WDF_DEVICE_PREPARE_HARDWAREの実装では、未加工のリソースと翻訳されたリソースの一覧を受け取った後、リソースを使用してハードウェアを準備します。 たとえば、トランスポートが I2C の場合は、ハードウェア リソースを読み取って通信チャンネルを開きます。 次に、PPM オブジェクトを作成します。 オブジェクトを作成するために、特定の構成オプションを設定する必要があります。
デバイス上のコネクタ コレクションへのハンドルを指定します。
UcmUcsiConnectorCollectionCreate を呼び出して、コネクタ コレクションを作成します。
デバイス上のコネクタを列挙し、UcmUcsiConnectorCollectionAddConnector を呼び出してコレクションに追加します。
// Create the connector collection. UCMUCSI_CONNECTOR_COLLECTION* ConnectorCollectionHandle; status = UcmUcsiConnectorCollectionCreate(Device, //WDFDevice WDF_NO_OBJECT_ATTRIBUTES, ConnectorCollectionHandle); // Enumerate the connectors on the device. // ConnectorId of 0 is reserved for the parent device. // In this example, we assume the parent has no children connectors. UCMUCSI_CONNECTOR_INFO_INIT(&connectorInfo); connectorInfo.ConnectorId = 0; status = UcmUcsiConnectorCollectionAddConnector ( &ConnectorCollectionHandle, &connectorInfo);
デバイス コントローラーを有効にするかどうかを決定します。
PPM オブジェクトを構成して作成します。
手順 1 で作成したコネクタ ハンドルを指定して、UCMUCSI_PPM_CONFIG 構造体を初期化します。
UsbDeviceControllerEnabled メンバーを、手順 2 で決定したブール値に設定します。
WDF_OBJECT_ATTRIBUTES でイベント コールバックを設定します。
構成されているすべての構造体を渡して UcmUcsiPpmCreate を呼び出します。
UCMUCSIPPM ppmObject = WDF_NO_HANDLE; PUCMUCSI_PPM_CONFIG UcsiPpmConfig; WDF_OBJECT_ATTRIBUTES attrib; UCMUCSI_PPM_CONFIG_INIT(UcsiPpmConfig, ConnectorCollectionHandle); UcsiPpmConfig->UsbDeviceControllerEnabled = TRUE; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attrib, Ppm); attrib->EvtDestroyCallback = &EvtObjectContextDestroy; status = UcmUcsiPpmCreate(wdfDevice, UcsiPpmConfig, &attrib, &ppmObject);
3. IO キューを設定する
UcmUcsiCx は、PPM ファームウェアに送信する UCSI コマンドをクライアント ドライバーに送信します。 これらのコマンドは次の IOCTL 要求の形式で WDF キューに送信されます。
クライアント ドライバーは、UcmUcsiPpmSetUcsiCommandRequestQueue を呼び出して、そのキューを作成し、UcmUcsiCx に登録する必要があります。 キューは電源管理に対応している必要があります。
UcmUcsiCx により、WDF キュー内に未処理の要求が同時に複数存在することはありません。 クライアント ドライバーは、UCSI コマンドをファームウェアに送信した後、WDF 要求を完了する必要もあります。
通常、ドライバーは EVT_WDF_DEVICE_PREPARE_HARDWARE の実装内でキューを設定します。
WDFQUEUE UcsiCommandRequestQueue = WDF_NO_HANDLE;
WDF_OBJECT_ATTRIBUTES attrib;
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_OBJECT_ATTRIBUTES_INIT(&attrib);
attrib.ParentObject = GetObjectHandle();
// In this example, even though the driver creates a sequential queue,
// UcmUcsiCx guarantees that will not send another request
// until the previous one has been completed.
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
// The queue must be power-managed.
queueConfig.PowerManaged = WdfTrue;
queueConfig.EvtIoDeviceControl = EvtIoDeviceControl;
status = WdfIoQueueCreate(device, &queueConfig, &attrib, &UcsiCommandRequestQueue);
UcmUcsiPpmSetUcsiCommandRequestQueue(ppmObject, UcsiCommandRequestQueue);
また、クライアント ドライバーは UcmUcsiPpmStart を呼び出して、IOCTL 要求を受信する準備ができていることを UcmUcsiCx に通知する必要もあります。 UcmUcsiPpmSetUcsiCommandRequestQueue を通じて UCSI コマンドを受信するための WDFQUEUE ハンドルを作成した後に、EVT_WDF_DEVICE_PREPARE_HARDWARE 内でこの呼び出しを行うことをお勧めします。 逆に、ドライバーがこれ以上要求を処理しないようにする場合は、UcmUcsiPpmStop を呼び出す必要があります。 この呼び出しは EVT_WDF_DEVICE_RELEASE_HARDWARE 実装内で行います。
4. IOCTL 要求を処理する
USB Type-C パートナーがコネクタに接続されているときに発生する一連のイベントの例を考えてみましょう。
- PPM ファームウェアは接続イベントを判断し、クライアント ドライバーに通知を送信します。
- クライアント ドライバーは UcmUcsiPpmNotification を呼び出して、その通知を UcmUcsiCx に送信します。
- UcmUcsiCx は OPM ステート マシンに通知し、Get Connector Status コマンドを UcmUcsiCx に送信します。
- UcmUcsiCx は要求を作成し、IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK をクライアント ドライバーに送信します。
- クライアント ドライバーはその要求を処理し、コマンドを PPM ファームウェアに送信します。 ドライバーはこの要求を非同期で完了し、別の通知を UcmUcsiCx に送信します。
- コマンド完了通知が成功すると、OPM ステート マシンはペイロード (コネクタ ステータス情報を含む) を読み取り、UCM に Type-C 接続イベントを通知します。
この例では、ペイロードは、ファームウェアとポート パートナー間の電力ネゴシエーション ステータスの変更が成功したことも示しています。 OPM ステート マシンは別の UCSI コマンドとして Get PDOs を送信します。 Get Connector Status コマンドと同様に、Get PDOs コマンドが正常に完了すると、OPM ステート マシンはこのイベントを UCM に通知します。
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL のクライアント ドライバーのハンドラーはこのコード例に似ています。 要求の処理については、「要求ハンドラー」を参照してください。
void EvtIoDeviceControl(
_In_ WDFREQUEST Request,
_In_ ULONG IoControlCode
)
{
...
switch (IoControlCode)
{
case IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK:
EvtSendData(Request);
break;
case IOCTL_UCMUCSI_PPM_GET_UCSI_DATA_BLOCK:
EvtReceiveData(Request);
break;
default:
status = STATUS_NOT_SUPPORTED;
goto Exit;
}
status = STATUS_SUCCESS;
Exit:
if (!NT_SUCCESS(status))
{
WdfRequestComplete(Request, status);
}
}
VOID EvtSendData(
WDFREQUEST Request
)
{
NTSTATUS status;
PUCMUCSI_PPM_SEND_UCSI_DATA_BLOCK_IN_PARAMS inParams;
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*inParams),
reinterpret_cast<PVOID*>(&inParams), nullptr);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// Build a UCSI command request and send to the PPM firmware.
Exit:
WdfRequestComplete(Request, status);
}
VOID EvtReceiveData(
WDFREQUEST Request
)
{
NTSTATUS status;
PUCMUCSI_PPM_GET_UCSI_DATA_BLOCK_IN_PARAMS inParams;
PUCMUCSI_PPM_GET_UCSI_DATA_BLOCK_OUT_PARAMS outParams;
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*inParams),
reinterpret_cast<PVOID*>(&inParams), nullptr);
if (!NT_SUCCESS(status))
{
goto Exit;
}
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*outParams),
reinterpret_cast<PVOID*>(&outParams), nullptr);
if (!NT_SUCCESS(status))
{
goto Exit;
}
// Receive data from the PPM firmware.
if (!NT_SUCCESS(status))
{
goto Exit;
}
WdfRequestSetInformation(Request, sizeof(*outParams));
Exit:
WdfRequestComplete(Request, status);
}