다음을 통해 공유


UCSI 클라이언트 드라이버 작성

USB Type-C 커넥트or UCSI(시스템 소프트웨어 인터페이스) 드라이버는 EC(임베디드 컨트롤러)가 있는 USB Type-C 시스템의 컨트롤러 드라이버 역할을 합니다.

UCSI 사양에 설명된 대로 PPM(플랫폼 정책 관리자)을 구현하는 시스템의 경우 시스템에 연결된 EC에서 다음을 수행합니다.

  • ACPI 전송은 드라이버를 작성할 필요가 없습니다 . Microsoft에서 제공하는 기본 제공 드라이버(UcmUcsiCx.sys 및 UcmUcsiAcpiClient.sys)를 로드합니다. (참조) UCSI 드라이버).

  • USB, PCI, I2C 또는 UART와 같은 비 ACPI 전송은 컨트롤러에 대한 클라이언트 드라이버를 작성해야 합니다.

참고 항목

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는 차례로 클라이언트에서 UCM(USB 커넥터 관리자)으로 전환됩니다. 따라서 UcmUcsiCx는 자체 정책 결정을 내리지 않습니다. 대신 UCM에서 제공하는 정책을 구현합니다. UcmUcsiCx는 클라이언트 드라이버에서 PPM(플랫폼 정책 관리자) 알림을 처리하기 위한 상태 머신을 구현하고 UCM 정책 결정을 구현하는 명령을 전송하여 보다 안정적인 문제 검색 및 오류 처리를 허용합니다.

UCSI 클래스 확장 아키텍처입니다.

OS 정책 관리자(OPM)

OPM(OS 정책 관리자)은 UCSI 사양에 설명된 대로 PPM과 상호 작용하는 논리를 구현합니다. OPM은 다음을 담당합니다.

  • UCM 정책을 UCSI 명령 및 UCSI 알림으로 UCM 알림으로 변환
  • PPM 초기화, 오류 검색 및 복구 메커니즘에 필요한 UCSI 명령 보내기

UCSI 명령 처리

일반적인 작업에는 UCSI 규격 하드웨어에서 완료해야 하는 여러 명령이 포함됩니다. 예를 들어 GET_CONNECTOR_STATUS 명령을 살펴보겠습니다.

  1. PPM 펌웨어는 UcmUcsiCx/클라이언트 드라이버에 연결 변경 알림을 보냅니다.
  2. 이에 대한 응답으로 UcmUcsiCx/클라이언트 드라이버는 GET_CONNECTOR_STATUS 명령을 PPM 펌웨어로 다시 보냅니다.
  3. PPM 펌웨어는 GET_CONNECTOR_STATUS 실행하고 명령 완료 알림을 UcmUcsiCx/클라이언트 드라이버에 비동기적으로 보냅니다. 해당 알림에는 실제 연결 상태 대한 데이터가 포함됩니다.
  4. UcmUcsiCx/클라이언트 드라이버는 정보를 상태 처리하고 PPM 펌웨어에 ACK_CC_CI 보냅니다.
  5. PPM 펌웨어는 ACK_CC_CI 실행하고 명령 완료 알림을 UcmUcsiCx/클라이언트 드라이버에 비동기적으로 보냅니다.
  6. UcmUcsiCx/클라이언트 드라이버는 GET_CONNECTOR_STATUS 명령을 완료하는 것으로 간주합니다.

PPM(플랫폼 정책 관리자)과의 통신

UcmUcsiCx는 OPM에서 PPM 펌웨어로 UCSI 명령을 보내고 PPM 펌웨어에서 알림을 받는 세부 정보를 추상화합니다. PPM 명령을 WDFREQUEST 개체로 변환하고 클라이언트 드라이버에 전달합니다.

  • PPM 알림

    클라이언트 드라이버는 펌웨어의 PPM 알림에 대해 UcmUcsiCx에 알깁니다. 드라이버는 CCI를 포함하는 UCSI 데이터 블록을 제공합니다. UcmUcsiCx는 데이터를 기반으로 적절한 작업을 수행하는 OPM 및 기타 구성 요소에 알림을 전달합니다.

  • 클라이언트 드라이버에 대한 IOCTL

    UcmUcsiCx는 UCSI 명령(IOCTL 요청을 통해)을 클라이언트 드라이버에 보내 PPM 펌웨어로 보냅니다. 드라이버는 UCSI 명령을 펌웨어로 보낸 후 요청을 완료해야 합니다.

전원 전환 처리

클라이언트 드라이버는 전원 정책 소유자입니다.

클라이언트 드라이버가 S0-Idle로 인해 Dx 상태로 전환되면 UcmUcsiCx가 UCSI 명령이 포함된 IOCTL을 클라이언트 드라이버의 전원 관리 큐에 보낼 때 WDF는 드라이버를 D0으로 가져옵니다. S0-Idle의 클라이언트 드라이버는 S0-Idle에서 PPM 알림이 계속 사용되므로 펌웨어에서 PPM 알림이 있을 때 전원이 켜진 상태를 다시 입력해야 합니다.

시작하기 전에

  • 하드웨어 또는 펌웨어가 PD 상태 컴퓨터를 구현하는지 여부 및 전송에 따라 작성해야 하는 드라이버 유형을 결정합니다.

    올바른 클래스 확장을 선택하기 위한 결정입니다. 자세한 내용은 USB Type-C 커넥터용 Windows 드라이버 개발을 참조 하세요.

  • 데스크톱 버전(Home, Pro, Enterprise 및 Education)용 Windows 10을 설치합니다.

  • 개발 컴퓨터에 최신 WDK(Windows 드라이버 키트)를 설치 합니다. 이 키트에는 클라이언트 드라이버를 작성하는 데 필요한 헤더 파일 및 라이브러리가 있습니다. 특히 다음이 필요합니다.

    • 스텁 라이브러리(UcmUcsiCxStub.lib)입니다. 라이브러리는 클라이언트 드라이버의 호출을 변환하고 클래스 확장에 전달합니다.
    • 헤더 파일인 Ucmucsicx.h입니다.
    • 클라이언트 드라이버는 커널 모드에서 실행되며 KMDF 1.27 라이브러리에 바인딩됩니다.
  • WDF(Windows 드라이버 파운데이션)를 숙지하세요. 권장 읽기: 페니 오윅과 가이 스미스가 쓴 Windows 드라이버 파운데이션 을 사용하여 드라이버 개발.

1. UcmUcsiCx에 클라이언트 드라이버 등록

EVT_WDF_DRIVER_DEVICE_ADD 구현에서.

  1. 플러그 앤 플레이 및 전원 관리 이벤트 콜백 함수(WdfDeviceInitSetPnpPowerEventCallbacks)를 설정한 후 UcmUcsiDeviceInitInitialize를 호출하여 WDFDEVICE_INIT 불투명 구조를 초기화합니다. 호출은 클라이언트 드라이버를 프레임워크와 연결합니다.

  2. WDFDEVICE(프레임워크 디바이스 개체)를 만든 후 UcmUcsiDeviceInitialize를 호출하여 UcmUcsiCx에 클라이언트 다이버를 등록합니다.

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는 PPM 펌웨어로 보내기 위해 UCSI 명령을 클라이언트 드라이버에 보냅니다. 명령은 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 요청을 받을 준비가 되었음을 알려야 합니다. UcmUcsiPpmSetUcsiCommandRequestQueue를 통해 UCSI 명령을 수신하기 위한 WDFQUEUE 핸들을 만든 후 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 가져오기를 보냅니다. get 커넥트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);
}