共用方式為


撰寫 UCSI 用戶端驅動程式

USB Type-C 連線 or 系統軟體介面 (UCSI) 驅動程式可作為內嵌控制器 (EC) 的 USB Type-C 系統的控制器驅動程式。

如果您的系統實作平台原則管理員 (PPM),如 UCSI 規格中所述,在透過下列方式連線到系統的 EC 中:

  • ACPI 傳輸,您不需要撰寫驅動程式。 載入 Microsoft 提供的內建驅動程式(UcmUcsiCx.sys和UcmUcsiAcpiClient.sys)。 (請參閱 UCSI 驅動程式)。

  • 非 ACPI 傳輸,例如 USB、PCI、I2C 或 UART,您必須為控制器撰寫用戶端驅動程式。

注意

如果您的 USB Type-C 硬體沒有處理電源傳遞 (PD) 狀態機器的功能,請考慮撰寫 USB Type-C 連接埠控制器驅動程式。 如需詳細資訊,請參閱 撰寫USB Type-C埠控制器驅動程式

從 Windows 10 版本 1809 開始,已新增 UCSI 的新類別擴充功能(UcmUcsiCx.sys),它會以傳輸無關的方式實作 UCSI 規格。 使用最少的程式代碼數量,您的驅動程式是UcmUcsiCx的用戶端,可以透過非ACPI傳輸與USB Type-C 硬體通訊。 本主題描述 UCSI 類別延伸模組所提供的服務,以及用戶端驅動程式的預期行為。

官方規格

適用於:

  • Windows 10 版本 1809

WDF 版本

  • KMDF 1.27 版

重要 API

UcmUcsiCx 類別延伸模組參考

範例

UcmUcsiCx 用戶端驅動程式範例

將 ACPI 部分取代為必要總線的實作。

UCSI 類別擴充架構

UCSI 類別延伸模組UcmUcsiCx可讓您使用非 ACPI 傳輸來撰寫與內嵌控制器通訊的驅動程式。 控制器驅動程式是UcmUcsiCx的客戶端驅動程式。 UcmUcsiCx 接著是 USB 連接器管理員 (UCM) 的用戶端。 因此,UcmUcsiCx 不會自行做出任何原則決策。 相反地,它會實作UCM所提供的原則。 UcmUcsiCx 會實作狀態機器來處理來自用戶端驅動程式的平台原則管理員 (PPM) 通知,並傳送命令以實作 UCM 原則決策,以允許更可靠的問題偵測和錯誤處理。

UCSI 類別擴充架構。

OS 原則管理員 (OPM)

OS 原則管理員 (OPM) 會實作邏輯來與 PPM 互動,如 UCSI 規格中所述。 OPM 負責:

  • 將UCM原則轉換成UCSI命令,並將UCSI通知轉換成UCM通知轉換成UCM通知。
  • 傳送初始化 PPM、偵測錯誤和復原機制所需的 UCSI 命令。

處理UCSI命令

一般作業牽涉到由UCSI-complicant硬體完成的數個命令。 例如,讓我們考慮 GET_CONNECTOR_STATUS 命令。

  1. PPM 韌體會將連線變更通知傳送至 UcmUcsiCx/用戶端驅動程式。
  2. 回應中,UcmUcsiCx/用戶端驅動程式會將GET_CONNECTOR_STATUS命令傳回 PPM 韌體。
  3. PPM 韌體會執行GET_CONNECTOR_STATUS,並以異步方式將命令完成通知傳送至 UcmUcsiCx/用戶端驅動程式。 該通知包含有關實際連線狀態的數據。
  4. UcmUcsiCx/用戶端驅動程式會處理狀態資訊,並將ACK_CC_CI傳送至 PPM 韌體。
  5. PPM 韌體會執行ACK_CC_CI,並以異步方式將命令完成通知傳送至 UcmUcsiCx/用戶端驅動程式。
  6. UcmUcsiCx/用戶端驅動程式會將GET_CONNECTOR_STATUS命令視為已完成。

與平台原則管理員通訊 (PPM)

UcmUcsiCx 會抽象化將 UCSI 命令從 OPM 傳送至 PPM 韌體,以及從 PPM 韌體接收通知的詳細數據。 它會將 PPM 命令轉換成 WDFREQUEST 物件,並將其轉送至用戶端驅動程式。

  • PPM 通知

    用戶端驅動程式會通知UcmUcsiCx來自韌體中的 PPM 通知。 驅動程式會提供包含CCI的UCSI資料區塊。 UcmUcsiCx 會將通知轉送給 OPM 和其他元件,這些元件會根據數據採取適當的動作。

  • 用戶端驅動程式的IOCTL

    UcmUcsiCx 會將 UCSI 命令(透過 IOCTL 要求)傳送至用戶端驅動程式,以傳送至 PPM 韌體。 驅動程式負責在將 UCSI 命令傳送至韌體之後完成要求。

處理電源轉換

用戶端驅動程式是電源原則擁有者。

如果客戶端驅動程式因為 S0-Idle 而進入 Dx 狀態,當 UcmUcsiCx 將包含 UCSI 命令的 IOCTL 傳送至用戶端驅動程式的電源受控佇列時,WDF 會將驅動程式帶到 D0。 S0-Idle 中的用戶端驅動程式應該在韌體收到 PPM 通知時重新進入電源狀態,因為在 S0-Idle 中,仍會啟用 PPM 通知。

開始之前

  • 根據硬體或韌體是否實作 PD 狀態機器和傳輸,判斷您需要撰寫的驅動程序類型。

    選擇正確類別擴充功能的決策。 如需詳細資訊,請參閱 開發適用於USB Type-C連接器的 Windows 驅動程式。

  • 安裝 Windows 10 傳統型版本(家用版、專業版、企業版和教育版)。

  • 在您的 開發電腦上安裝最新的 Windows 驅動程式套件 (WDK)。 套件具有撰寫客戶端驅動程式所需的頭檔與連結庫,特別是您需要:

    • 存根連結庫 (UcmUcsiCxStub.lib)。 連結庫會轉譯客戶端驅動程式所進行的呼叫,並將其傳遞至類別延伸模組。
    • 頭檔 Ucmucsicx.h。
    • 用戶端驅動程式會在核心模式中執行,並系結至 KMDF 1.27 連結庫。
  • 熟悉 Windows Driver Foundation (WDF)。 建議閱讀: 使用佩妮·奧里克和蓋伊·史密斯撰寫的 Windows Driver Foundation 開發驅動程式。

1.向 UcmUcsiCx 註冊用戶端驅動程式

在您的 EVT_WDF_DRIVER_DEVICE_ADD 實作中。

  1. 設定 隨插即用 和電源管理事件回呼函式 (WdfDeviceInitSetPnpPowerEventCallbacks), 呼叫 UcmUcsiDeviceInitInitialize 以初始化WDFDEVICE_INIT不透明結構。 呼叫會將客戶端驅動程式與架構產生關聯。

  2. 建立架構裝置物件 (WDFDEVICE) 之後,呼叫 UcmUcsiDeviceInitialize 向 UcmUcsiCx 註冊用戶端 diver。

2.使用 UcmUcsiCx 建立 PPM 物件

在實作 EVT_WDF_DEVICE_PREPARE_HARDWARE之後,在您收到原始和翻譯的資源清單之後,請使用資源來準備硬體。 例如,如果您的傳輸是I2C,請讀取硬體資源以開啟通道。 接下來,建立 PPM 物件。 若要建立物件,您必須設定特定組態選項。

  1. 提供裝置上連接器集合的句柄。

    1. 呼叫UcmUcsi 連線 orCollectionCreate 來建立連接器集合。

    2. 列舉裝置上的連接器,並藉由呼叫UcmUcsi 連線 orCollectionAdd 將其新增至集合 連線 or

      // 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);
      
  2. 決定是否要啟用裝置控制器。

  3. 設定並建立 PPM 物件。

    1. 提供您在步驟 1 中建立的連接器句柄,以初始化UCMUCSI_PPM_CONFIG結構。

    2. 將 UsbDeviceControllerEnabled 成員設定為步驟 2 中決定的布爾值。

    3. 在 WDF_OBJECT_ATTRIBUTES 中設定事件回呼。

    4. 藉由傳遞所有已設定的結構,呼叫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 會將 UCSI 命令傳送至用戶端驅動程式,以傳送至 PPM 韌體。 命令會以 WDF 佇列中的這些 IOCTL 要求形式傳送。

用戶端驅動程序負責呼叫 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 ,以通知UcmUcsiCx驅動程式已準備好接收IOCTL要求。 建議您在建立 WDFQUEUE 句柄以接收 UCSI 命令之後,透過 UcmUcsiPpmSetUcsiCommandRequestQueue,在 EVT_WDF_DEVICE_PREPARE_HARDWARE 中呼叫該呼叫。 相反地,當驅動程式不想再處理任何要求時,它必須呼叫UcmUcsiPpmStop 在您的EVT_WDF_DEVICE_RELEASE_HARDWARE實作中,請執行此動作。

4.處理 IOCTL 要求

請考慮此範例序列的事件序列,這些事件會在USB Type-C夥伴附加至連接器時發生。

  1. PPM 韌體會決定附加事件,並將通知傳送給客戶端驅動程式。
  2. 用戶端驅動程式會呼叫 UcmUcsiPpmNotification ,將該通知傳送至UcmUcsiCx。
  3. UcmUcsiCx 會通知 OPM 狀態機器,並將 Get 連線 or Status 命令傳送至 UcmUcsiCx。
  4. UcmUcsiCx 會建立要求,並將IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK傳送至客戶端驅動程式。
  5. 用戶端驅動程序會處理要求並將命令傳送至 PPM 韌體。 驅動程式會以異步方式完成此要求,並將另一個通知傳送至UcmUcsiCx。
  6. 在命令完成通知成功時,OPM 狀態機器會讀取承載(包含連接器狀態資訊),並通知 UCM Type-C 附加事件。

在此範例中,承載也表示韌體與埠夥伴之間的電源傳遞交涉狀態變更成功。 OPM 狀態機器會傳送另一個 UCSI 命令:取得 PDO。 與取得 連線 or Status 命令類似,當取得 PDO 命令順利完成時,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);
}