Написание драйвера клиента UCSI

Драйвер программного интерфейса USB Type-C Connector System Software Interface (UCSI) служит драйвером контроллера для системы USB Type-C с встроенным контроллером (EC).

Если ваша система, реализующая диспетчер политик платформы (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, может взаимодействовать с USB Type-C оборудованием через нестандартный транспорт, не связанный с ACPI. В этом разделе описываются службы, предоставляемые расширением класса 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.

Диспетчер политик ОС (OPM)

Диспетчер политик ОС (OPM) реализует логику для взаимодействия с PPM, как описано в спецификации UCSI. OPM отвечает за:

  • Преобразуем политики UCM в команды UCSI и уведомления UCSI в уведомления UCM.
  • Отправка команд UCSI, необходимых для инициализации PPM, обнаружения ошибок и механизмов восстановления.

Обработка команд UCSI

Типичная операция включает несколько команд для выполнения оборудованием UCSI-complicant. Например, рассмотрим команду GET_CONNECTOR_STATUS.

  1. Встроенное ПО PPM отправляет уведомление об изменении подключения к драйверу UcmUcsiCx/client.
  2. В ответ драйвер UcmUcsiCx/client отправляет команду GET_CONNECTOR_STATUS обратно в встроенное ПО PPM.
  3. Встроенное ПО PPM выполняет GET_CONNECTOR_STATUS и асинхронно отправляет уведомление о выполнении команды драйверу UcmUcsiCx/client. Это уведомление содержит данные о фактическом состоянии подключения.
  4. Драйвер UcmUcsiCx/client обрабатывает сведения о состоянии и отправляет ACK_CC_CI встроенному ПО PPM.
  5. Встроенное ПО PPM выполняет ACK_CC_CI и асинхронно отправляет уведомление о выполнении команды драйверу UcmUcsiCx/client.
  6. Драйвер UcmUcsiCx/client считает, что команда GET_CONNECTOR_STATUS будет завершена.

Взаимодействие с диспетчером политик платформы (PPM)

UcmUcsiCx абстрагирует сведения об отправке команд UCSI из OPM в встроенное ПО PPM и о получении уведомлений из встроенного ПО PPM. Он преобразует команды PPM в объекты WDFREQUEST и перенаправит их в драйвер клиента.

  • Уведомления PPM

    Драйвер клиента уведомляет UcmUcsiCx о уведомлениях PPM из встроенного ПО. Драйвер предоставляет блок данных UCSI, содержащий CCI. UcmUcsiCx перенаправляет уведомления в OPM и другие компоненты, которые выполняют соответствующие действия на основе данных.

  • IoCTLs для драйвера клиента

    UcmUcsiCx отправляет команды UCSI (через запросы IOCTL) драйверу клиента для передачи прошивке PPM. Драйвер отвечает за выполнение запроса после отправки команды UCSI встроенному ПО.

Обработка переходов питания

Драйвер клиента является владельцем политики питания.

Если драйвер клиента переходит в состояние Dx из-за S0-Idle, WDF переводит драйвер в D0, когда UcmUcsiCx отправляет IOCTL, содержащую команду UCSI, в управляемую драйвером клиента очередь. Драйвер клиента в S0-Idle должен повторно включать состояние питания при наличии уведомления PPM из встроенного ПО, так как в S0-Idle уведомления PPM по-прежнему включены.

Перед тем как начать

  • Определите тип драйвера, который необходимо написать, в зависимости от того, реализована ли машина состояний PD в вашем оборудовании или встроенном ПО, а также используемого транспортного механизма.

    Решение о выборе правильного расширения класса. Дополнительные сведения см. в статье "Разработка драйверов Windows для соединителей USB-Type-C".

  • Установите Windows 10 для настольных версий (Home, Pro, Enterprise и Education).

  • Установите последнюю версию комплекта драйверов Windows (WDK) на компьютере разработки. В комплекте есть необходимые файлы заголовков и библиотеки для написания драйвера клиента, в частности, вам потребуется:

    • Библиотека-заглушка (UcmUcsiCxStub.lib). Библиотека преобразует вызовы, выполненные драйвером клиента, и передает их в расширение класса.
    • Файл заголовка Ucmucsicx.h.
    • Драйвер клиента выполняется в режиме ядра и привязывается к библиотеке KMDF 1.27.
  • Ознакомьтесь с Windows Driver Foundation (WDF). Рекомендуемое чтение: разработка драйверов с помощью Windows Driver Foundation , написанная Пенни Орвик и Гай Смит.

1. Регистрация драйвера клиента в UcmUcsiCx

В реализации EVT_WDF_DRIVER_DEVICE_ADD .

  1. После настройки функций обратного вызова событий Plug and Play и управления питанием (WdfDeviceInitSetPnpPowerEventCallbacks), вызовите UcmUcsiDeviceInitInitialize, чтобы инициализировать непрозрачную структуру WDFDEVICE_INIT. Вызов связывает драйвер клиента с платформой.

  2. После создания объекта устройства фреймворка (WDFDEVICE) вызовите UcmUcsiDeviceInitialize, чтобы зарегистрировать клиентский драйвер в UcmUcsiCx.

2. Создание объекта PPM с помощью UcmUcsiCx

В реализации EVT_WDF_DEVICE_PREPARE_HARDWARE после получения списка необработанных и переведенных ресурсов используйте ресурсы для подготовки оборудования. Например, если ваш транспорт I2C, проверьте аппаратные ресурсы, чтобы открыть канал связи. Затем создайте объект PPM. Чтобы создать объект, необходимо задать определенные параметры конфигурации.

  1. Предоставьте идентификатор коллекции соединителей на устройстве.

    1. Создайте коллекцию соединителей, вызвав UcmUcsiConnectorCollectionCreate.

    2. Перечислите соединители на устройстве и добавьте их в коллекцию путем вызова 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);
      
  2. Определите, следует ли включить контроллер устройства.

  3. Настройте и создайте объект PPM.

    1. Инициализируйте структуру UCMUCSI_PPM_CONFIG путем предоставления дескриптора соединителя, созданного на шаге 1.

    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. Настройка очередей ввода-вывода

UcmUcsiCx отправляет команды UCSI драйверу клиента для их передачи микропрограммному обеспечению PPM. Команды отправляются в виде этих запросов IOCTL в очереди WDF.

Драйвер клиента отвечает за создание и регистрацию этой очереди в UcmUcsiCx путем вызова UcmUcsiPpmSetUcsiCommandRequestQueue. Очередь должна быть с поддержкой управления питанием.

UcmUcsiCx гарантирует, что в очереди WDF может быть не более одного ожидающего запроса. Драйвер клиента также отвечает за выполнение запроса WDF после отправки команды UCSI встроенному ПО.

Как правило, драйвер настраивает очереди в реализации 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. Мы рекомендуем выполнить этот вызов в EVT_WDF_DEVICE_PREPARE_HARDWARE после создания дескриптора WDFQUEUE для получения команд UCSI через UcmUcsiPpmSetUcsiCommandRequestQueue. И наоборот, если драйвер не хочет обрабатывать больше запросов, он должен вызывать UcmUcsiPpmStop. Сделайте это в вашей EVT_WDF_DEVICE_RELEASE_HARDWARE реализации.

4. Обработка запросов IOCTL

Рассмотрим эту последовательность событий, возникающих при подключении партнера USB-Type-C к соединителю.

  1. Встроенное ПО PPM определяет событие подключения и отправляет уведомление драйверу клиента.
  2. Драйвер клиента вызывает UcmUcsiPpmNotification , чтобы отправить это уведомление в UcmUcsiCx.
  3. UcmUcsiCx уведомляет машину состояний OPM и отправляет команду "Получить состояние соединителя" обратно в UcmUcsiCx.
  4. UcmUcsiCx создает запрос и отправляет IOCTL_UCMUCSI_PPM_SEND_UCSI_DATA_BLOCK драйверу клиента.
  5. Драйвер клиента обрабатывает запрос и отправляет команду встроенному ПО PPM. Драйвер завершает этот запрос асинхронно и отправляет другое уведомление в UcmUcsiCx.
  6. После успешного выполнения команды автомат состояния OPM считывает полезные данные (содержащие сведения о состоянии соединителя) и уведомляет UCM о событии подключения Type-C.

В этом примере полезные данные также указывают на то, что изменение состояния переговоров о подаче питания между встроенным ПО и партнером порта прошло успешно. Компьютер состояния OPM отправляет другую команду UCSI: получить 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);
}