USB Type-C ポート コントローラー ドライバーを作成する

USB Type-C ハードウェアが USB Type-C または Power Delivery (PD) 物理層を実装しているが、Power Delivery に必要なステート マシンを実装していない場合は、USB Type-C ポート コントローラー ドライバーを作成する必要があります。

Windows 10 バージョン 1703 では、USB Type-C アーキテクチャが改良され、USB Type-C または Power Delivery (PD) 物理層を実装しているものの、対応する PD ポリシー エンジンまたはプロトコル層の実装がないハードウェア設計をサポートするようになりました。 これらの設計のため、Windows 10 バージョン 1703 では「USB Connector Manager Type-C Port Controller Interface Class Extension」 (UcmTcpciCx) と呼ばれる新しいクラス拡張機能を通じて、ソフトウェア ベースの PD ポリシー エンジンとデバイス ポリシー マネージャーが提供されます。 IHV または OEM/ODM によって作成されたクライアント ドライバーは、UcmTcpciCx と通信して、UcmTcpciCx の PD ポリシー エンジンとデバイス ポリシー マネージャーが機能するために必要なハードウェア イベントに関する情報を提供します。 この通信は、この記事とリファレンス セクションで説明している一連のプログラミング インターフェイスを通じて可能になります。

Diagram of USB connector manager.

UcmTcpciCx クラス拡張機能自体は UcmCx のクライアント ドライバーです。 電力コントラクト、データ ロールに関するポリシーの決定は UcmCx で行われ、UcmTcpciCx に転送されます。 UcmTcpciCx はこれらのポリシーを実装し、UcmTcpciCx クライアント ドライバーによって提供されるポート コントローラー インターフェイスを使用して、Type-C および PD ステート マシンを管理します。

まとめ

  • UcmTcpci クラス拡張機能によって提供されるサービス
  • クライアント ドライバーの想定される振る舞い

公式の仕様

重要な API

USB Type-C ポート コントローラー インターフェイス ドライバー クラス拡張機能リファレンス

UcmTcpciCx クライアント ドライバー テンプレート

UcmTcpciCx クライアント ドライバー テンプレート

開始する前に

  • ハードウェアまたはファームウェアが PD ステート マシンを実装しているかどうかに応じて、作成する必要があるドライバーのタイプを決定します。 詳細については、「USB Type-C コネクタ用の Windows ドライバーの開発」を参照してください。

  • USB Type-C コネクタを備えたターゲット コンピューターまたは Windows 10 Mobile にデスクトップ エディション (Home、Pro、Enterprise、および Education) の Windows 10 をインストールします。

  • 最新の Windows Driver Kit (WDK) を開発用コンピューターにインストールします。 このキットには、クライアント ドライバーを作成するために必要なヘッダー ファイルとライブラリが含まれています。具体的には、以下が必要になります。

    • スタブ ライブラリ (UcmTcpciCxStub.lib)。 このライブラリは、クライアント ドライバーによって行われた呼び出しを変換し、クラス拡張機能に渡します。
    • ヘッダーファイル、UcmTcpciCx.h。

    クライアント ドライバーはカーネル モードで実行され、KMDF 1.15 ライブラリにバインドされます。

    Screen shot of Visual Studio configuration for UCM.

  • クライアント ドライバーがアラートをサポートするかどうかを決定します。

  • ポート コントローラーが TCPCI に準拠している必要はありません。 このインターフェイスはどの Type-C ポート コントローラーの機能にも対応しています。 TCPCI に準拠していないハードウェア用の UcmTcpciCx クライアント ドライバーを作成するには、TCPCI 仕様のレジスタとコマンドの意味をハードウェアの意味にマッピングする必要があります。

  • ほとんどの TCPCI コントローラーは I2C で接続されています。 クライアント ドライバーは SPB (Serial Peripheral Bus) 接続リソースと割り込みラインを使用してハードウェアと通信します。 ドライバーは SPB Framework Extension (SpbCx) プログラミング インターフェイスを使用します。 次の記事を読んで SpbCx についてよく理解してください。

    • [Simple Peripheral Bus (SPB) ドライバー設計ガイド]
    • [SPB ドライバー プログラミング リファレンス]
  • Windows Driver Foundation (WDF) についてよく理解します。 推奨資料: Developing Drivers with Windows Driver Foundation Guy Smith、Penny Orwick 共著

UcmTcpci クラス拡張機能の振る舞い

  • ステート マシンの実行の一部として、UcmTcpciCx は IOCTL 要求をポート コントローラーに送信します。 たとえば、PD メッセージングでは、送信バッファーを設定する IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_TRANSMIT_BUFFER 要求を送信します。 その要求 (TRANSMIT_BUFFER) はクライアント ドライバーに渡されます。 その後、ドライバーは、クラス拡張機能によって提供される詳細を使用して送信バッファーを設定します。

  • UcmTcpciCx は電力コントラクト、データ ロールなどに関するポリシーを実装します。

クライアント ドライバーの想定される振る舞い

UcmTcpciCx に対するクライアント ドライバーには次の振る舞いが想定されます。

  • 電源ポリシーの所有者になる。 UcmTcpciCx はポートコントローラーの電源管理には関与しません。

  • UcmTcpciCx から受信した要求をハードウェアの読み取りまたは書き込みコマンドに変換する。 コマンドは非同期である必要があります。DPM はハードウェア転送完了の待機をブロックできないためです。

  • フレームワーク要求オブジェクトを含むフレームワーク キュー オブジェクトを提供する。 UcmTcpci クラス拡張機能がクライアント ドライバーに送信する要求ごとに、拡張機能はドライバーのキュー オブジェクトに要求オブジェクトを追加します。 ドライバーは要求の処理を完了すると、WdfRequestComplete を呼び出します。 クライアント ドライバーは、適切なタイミングで、要求を完了する必要があります。

  • ポート コントローラーの機能を検出し、レポートします。 これらの機能には、ポート コントローラーが動作できるロール (たとえばソースのみ、シンクのみ、DRP) などの情報が含まれます。 しかし、USB Type-C および PD ポリシーを適切に実装するために DPM が知る必要がある、コネクタの能力 (「Capability Store に関する注意」を参照) やシステム全体の能力は他にもあります。 たとえば、DPM はシステム/コネクタのソース能力を知り、その情報をポート パートナーに伝達する必要があります。

    Capability Store

    クライアント ドライバー関連の能力に加えて、追加情報はシステム グローバルな場所 (Capability Store) から取得されます。 このシステム グローバルな Capability Store は ACPI に保存されています。 これは、システムとその各 USB Type-C コネクタの能力に関する静的な記述であり、DPM が実装するポリシーを決定するために使用します。

    システムの機能の記述をポート コントローラーのクライアント ドライバーから分離することで、1 つのドライバーをさまざまな機能のシステムで使用できるように設計されています。 UcmTcpciCx ではなく、UcmCx が Capability Store と直接やり取りをします。 UcmTcpciCx (またはそのクライアント ドライバー) は Capability Store とやり取りしません。

    必要に応じて、Capability Store からの情報が、ポート コントローラー クライアント ドライバーから直接送信される情報よりも優先されます。 たとえば、ポート コントローラーはシンクのみの操作が可能であり、クライアント ドライバーはその情報をレポートします。 しかし、システムの残りの部分がシンクのみの操作用に正しく構成されていない可能性があります。 その場合、システム製造元は、コネクタがソースのみの操作が可能であることを Capability Store でレポートできます。 Capability Store の設定は、ドライバーがレポートする情報よりも優先されます。

  • アラートに関連するすべての関連データを UcmTcpciCx に通知します。

  • 省略可能。 代替モードの開始/終了後に追加の処理を実行します。 ドライバーは、IOCTL 要求を通じてクラス拡張機能によってこれらの状態を通知されます。

クライアント ドライバーを UcmTcpciCx に登録する

参考例: Device.cppEvtPrepareHardware を参照してください。

  1. EVT_WDF_DRIVER_DEVICE_ADD 実装で、UcmTcpciDeviceInitInitialize を呼び出して、WDFDEVICE_INIT 不透明構造体を初期化します。 この呼び出しにより、クライアント ドライバーがフレームワークに関連付けられます。

  2. フレームワーク デバイス オブジェクト (WDFDEVICE) を作成した後、UcmTcpciDeviceInitialize を呼び出してクライアント ドライバーを UcmTcpciCx に登録します。

ポート コントローラー ハードウェアへの I2C 通信チャンネルを初期化します。

参考例: Device.cppEvtCreateDevice を参照してください。

EVT_WDF_DEVICE_PREPARE_HARDWARE 実装で、ハードウェア リソースを読み取って通信チャンネルを開きます。 これは、PD 機能を取得し、アラートの通知を受け取るために必要です。

ほとんどの TCPCI コントローラーは I2C で接続されています。 参考例では、クライアント ドライバーは SPB Framework Extension (SpbCx) プログラミング インターフェイスを使用して I2 チャンネルを開きます。

クライアント ドライバーは WdfCmResourceListGetDescriptor を呼び出してハードウェア リソースを列挙します。

アラートは割り込みとして受信されます。 したがって、ドライバーはフレームワーク割り込みオブジェクトを作成し、アラートを処理する ISR を登録します。 ISR はハードウェアの読み取りおよび書き込み操作を実行しますが、ハードウェア アクセスが完了するまでブロックされます。 DIRQL では待機が受け入れられないため、ドライバーは PASSIVE_LEVEL で ISR を実行します。

ポートコントローラーの Type-C および PD 機能を初期化する

参考例: Device.cppEvtDeviceD0Entry を参照してください。

EVT_WDF_DEVICE_D0_EXIT 実装内。

  1. ポート コントローラー ハードウェアと通信し、さまざまなレジスタを読み取ることでデバイスの ID と機能を取得します。

  2. 取得した情報を使用して UCMTCPCI_PORT_CONTROLLER_IDENTIFICATION と UCMTCPCI_PORT_CONTROLLER_CAPABILITIES を初期化します。

  3. 初期化した構造体を UCMTCPCI_PORT_CONTROLLER_CONFIG_INIT に渡して、前述の情報を使用して UCMTCPCI_PORT_CONTROLLER_CONFIG 構造体を初期化します。

  4. UcmTcpciPortControllerCreate を呼び出してポート コントローラー オブジェクトを作成し、UCMTCPCIPORTCONTROLLER ハンドルを取得します。

UcmTcpciCx から要求を受信するためのフレームワーク キュー オブジェクトをセットアップする

参考例: Device.cppEvtDeviceD0Entry および Queue.cppHardwareRequestQueueInitialize を参照してください。

  1. EVT_WDF_DEVICE_D0_EXIT 実装で、WdfIoQueueCreate を呼び出してフレームワーク キュー オブジェクトを作成します。 その呼び出しでは、UcmTpciCx によって送信された IOCTL 要求を処理するためのコールバック実装を登録する必要があります。 クライアント ドライバーは電源管理キューを使用できます。

    Type-C および PD ステート マシンの実行中、UcmTpciCx は実行するコマンドをクライアント ドライバーに送信します。 UcmTcpciCx により、未処理のポート コントローラー要求が同時に複数存在することはありません。

  2. UcmTcpciPortControllerSetHardwareRequestQueue を呼び出して、新しいフレームワーク キュー オブジェクトを UcmTpciCx に登録します。 この呼び出しが成功した後、ドライバーからのアクションが必要な場合、UcmTcpciCx はフレームワーク キュー オブジェクト (WDFREQUEST) をこのキューに入れます。

  3. これらの IOCTL を処理するための EvtIoDeviceControl コールバック関数を実装します。

制御コード 説明
IOCTL_UCMTCPCI_PORT_CONTROLLER_GET_STATUS ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義されたすべてのステータス レジスタの値を取得します。 クライアント ドライバーは CC_STATUS、POWER_STATUS、および FAULT_STATUS レジスタの値を取得する必要があります。
IOCTL_UCMTCPCI_PORT_CONTROLLER_GET_CONTROL ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従った定義されたすべての制御レジスタの値を取得します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_CONTROL ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義されたすべての制御レジスタの値を設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_TRANSMIT ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義された TRANSMIT レジスタを設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_TRANSMIT_BUFFER ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義された TRANSMIT_BUFER レジスタを設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_RECEIVE_DETECT ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義された RECEIVE_DETECT レジスタを設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_CONFIG_STANDARD_OUTPUT ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義された CONFIG_STANDARD_OUTPUT レジスタを設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_COMMAND ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義されたすべてのコマンド レジスタの値を設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_MESSAGE_HEADER_INFO ユニバーサル シリアル バス Type-C ポート コントローラー インターフェイス仕様に従って定義された MESSAGE_HEADER_INFO レジスタの値を設定します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_ALTERNATE_MODE_ENTERED ドライバーが他のタスクを実行できるように、代替モードに移行したことをクライアント ドライバーに通知します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_ALTERNATE_MODE_EXITED ドライバーが他のタスクを実行できるように、代替モードが終了したことをクライアント ドライバーに通知します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_DISPLAYPORT_CONFIGURED ドライバーが他のタスクを実行できるように、パートナー デバイスの DisplayPort 代替モードがピン割り当てで構成されていることをクライアント ドライバーに通知します。
IOCTL_UCMTCPCI_PORT_CONTROLLER_DISPLAYPORT_HPD_STATUS_CHANGED ドライバーが他のタスクを実行できるように、DisplayPort 接続のホットプラグ検出ステータスが変更されたことをクライアント ドライバーに通知します。
  1. UcmTcpciPortControllerStart を呼び出して、UcmTcpciCx にポート コントローラーを開始するように指示します。 UcmTcpciCx は USB Type-C と Power Delivery の制御を引き受けます。 ポート コントローラーが開始された後、UcmTcpciCx はハードウェア要求キューに要求を入れ始めることができます。

ポート コントローラー ハードウェアからのアラートを処理する

参考例: Alert.cppProcessAndSendAlerts を参照してください。

クライアント ドライバーは、ポート コントローラー ハードウェアから受信したアラート (またはイベント) を処理し、イベントに関連するデータと共に UcmTcpciCx に送信する必要があります。

ハードウェア アラートが発生すると、ポート コントローラー ハードウェアは ALERT ピンを High にします。 これにより、クライアント ドライバーの ISR (手順 2 で登録) が呼び出されます。 このルーチンは、PASSIVE_LEVEL でハードウェア割り込みを処理します。 このルーチンは、割り込みがポートコントローラー ハードウェアからのアラートかどうかを判断し、アラートであれば、アラートの処理を完了し、UcmTcpciPortControllerAlert を呼び出して UcmTcpciCx に通知します。

UcmTcpciPortControllerAlert を呼び出す前に、クライアントはアラートに関連するすべてのデータを UCMTCPCI_PORT_CONTROLLER_ALERT_DATA 構造体に格納する必要があります。 ハードウェアが複数のアラートを同時にアサートする可能性があるため、クライアントはアクティブなすべてのアラートの配列を提供します。

CC ステータスの変更をレポートするタスクのフローの例を次に示します。

  1. クライアントはハードウェア アラートを受信します。

  2. クライアントは ALERT レジスタを読み取り、アクティブなアラートのタイプを判断します。

  3. クライアントは CC STATUS レジスタを読み取り、UCMTCPCI_PORT_CONTROLLER_ALERT_DATA に CC STATUS レジスタの内容を記述します。 ドライバーは、AlertType メンバーを UcmTcpciPortControllerAlertCCStatus に設定し、レジスタの CCStatus メンバーから値を読み取ります。

  4. クライアントは UcmPortControllerAlert を呼び出し、ハードウェア アラートの配列を UcmTcpciCx に送信します。

  5. クライアントがアラートをクリアします (これは、クライアントがアラート情報を取得した後に任意のタイミングで行われる可能性があります)。

UcmTcpciCx から受信した要求を処理する

参考例: PortControllerInterface.cpp を参照してください。

ステート マシンの実行の一部として、UcmTcpciCx は IOCTL 要求をポート コントローラーに送信する必要があります。 たとえば、TRANSMIT_BUFFER を設定する必要があります。 この要求はクライアント ドライバーに渡されます。 ドライバーは、UcmTcpciCx によって提供される詳細を使用して送信バッファーを設定します。 これらの要求のほとんどは、クライアント ドライバーによるハードウェアの読み取りまたは書き込みに変換されます。 コマンドは非同期である必要があります。DPM はハードウェア転送完了の待機をブロックできないためです。

UcmTcpciCx は、クライアント ドライバーから要求される get/set 操作を記述した I/O Control Code としてコマンドを送信します。 クライアント ドライバーのキュー設定で、ドライバーはそのキューを UcmTcpciCx に登録しました。 UcmTcpciCx はドライバーからの操作が必要なキューにフレームワーク要求オブジェクトを入れ始めます。 I/O 制御コードは手順 4 の表に示しています。

クライアント ドライバーは、適切なタイミングで、要求を完了する必要があります。

クライアント ドライバーは要求された操作を完了すると、フレームワーク要求オブジェクトに完了ステータスを指定して、WdfRequestComplete を呼び出します。

クライアント ドライバーは場合によって、ハードウェア操作を実行するために、他のドライバーに I/O 要求を送信する必要があります。 たとえば、サンプルでは、ドライバーは SPB 要求を I2C 接続のポート コントローラーに送信しています。 その場合、ドライバーは UcmTcpciCx から受け取ったフレームワーク要求オブジェクトを転送できません。なぜなら、WDM IRP 内でのその要求オブジェクトのスタック位置の数が正しくない可能性があるためです。 クライアント ドライバーは、別のフレームワーク要求オブジェクトを作成し、別のドライバーに転送する必要があります。 クライアント ドライバーは、UcmTcpciCx から要求を受け取るたびに要求オブジェクトを作成するのではなく、初期化中に必要な要求オブジェクトを事前に割り当てることができます。 なぜなら、UcmTcpciCx により、未処理の要求が同時に複数存在することがないためです。

参照