다음을 통해 공유


KMDF 주변 장치 드라이버를 직렬 포트에 연결

SerCx2 관리 직렬 포트의 주변 디바이스에 대한 KMDF 드라이버를 사용하려면 특정 하드웨어 리소스가 디바이스를 작동해야 합니다. 이러한 리소스에는 드라이버가 직렬 포트에 대한 논리적 연결을 여는 데 필요한 정보가 포함되어 있습니다. 추가 리소스에는 인터럽트와 하나 이상의 GPIO 입력 또는 출력 핀이 포함될 수 있습니다.

이 드라이버는 플러그 앤 플레이 및 전원 관리 이벤트 콜백 함수 집합을 구현합니다. KMDF에 이러한 함수를 등록하기 위해 드라이버의 EvtDriverDeviceAdd 이벤트 콜백 함수 는 WdfDeviceInitSetPnpPowerEventCallbacks 메서드를 호출합니다. 프레임워크는 전원 관리 이벤트 콜백 함수를 호출하여 드라이버에 주변 디바이스의 전원 상태 변경 내용을 알립니다. 이러한 함수에는 드라이버에서 디바이스에 액세스할 수 있도록 하는 데 필요한 모든 작업을 수행하는 EvtDevicePrepareHardware 함수가 포함되어 있습니다.

직렬로 연결된 주변 장치 디바이스가 초기화되지 않은 D0 디바이스 전원 상태가 된 후 드라이버 프레임워크는 EvtDevicePrepareHardware 함수를 호출하여 주변 드라이버에 디바이스 사용을 준비하도록 지시합니다. 이 호출 중에 드라이버는 두 개의 하드웨어 리소스 목록을 입력 매개 변수로 받습니다. ResourcesRaw 매개 변수는 원시 리소스 목록에 대한 WDFCMRESLIST 개체 핸들이며, ResourcesTranslated 매개 변수는 번역된 리소스 목록에 대한 WDFCMRESLIST 개체 핸들입니다. 변환된 리소스에는 드라이버가 주변 디바이스에 대한 논리적 연결을 설정하는 데 필요한 연결 ID 가 포함됩니다.

다음 코드 예제에서는 EvtDevicePrepareHardware 함수가 ResourcesTranslated 매개 변수에서 연결 ID를 가져오는 방법을 보여 줍니다.

BOOLEAN fConnectionIdFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;
NTSTATUS status = STATUS_SUCCESS;

resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);

// Loop through the resources and save the relevant ones.

for (ULONG ix = 0; ix < resourceCount; ix++)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;

    pDescriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, ix);

    if (pDescriptor == NULL)
    {
        status = E_POINTER;
        break;
    }

    // Determine the resource type.
    switch (pDescriptor->Type)
    {
    case CmResourceTypeConnection:
        {
            // Check against the expected connection types.

            UCHAR Class = pDescriptor->u.Connection.Class;
            UCHAR Type = pDescriptor->u.Connection.Type;

            if (Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL)
            {
                if (Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_UART)
                {
                    if (fConnectionIdFound == FALSE)
                    {
                        // Save the connection ID.

                        connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
                        connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
                        fConnectionIdFound = TRUE;
                    }
                }
            }

            if (Class == CM_RESOURCE_CONNECTION_CLASS_GPIO)
            {
                // Check for GPIO pin resource.
                ...
            }
        }
        break;

    case CmResourceTypeInterrupt:
        {
            // Check for interrupt resource.
            ...
        }
        break;

    default:
        // Don't care about other resource descriptors.
        break;
    }
}

앞의 코드 예제에서는 직렬로 연결된 주변 디바이스의 연결 ID를 라는 connectionId변수에 복사합니다.

다음 코드 예제에서는 주변 디바이스에 대한 논리적 연결을 여는 데 사용할 수 있는 디바이스 경로 이름에 이 연결 ID를 통합하는 방법을 보여 줍니다. 이 디바이스 경로 이름은 리소스 허브를 주변 디바이스에 액세스하는 데 필요한 매개 변수를 가져올 시스템 구성 요소로 식별합니다.

// Use the connection ID to create the full device path name.
 
DECLARE_UNICODE_STRING_SIZE(szDeviceName, RESOURCE_HUB_PATH_SIZE);

status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&szDeviceName,
                                          connectionId.LowPart,
                                          connectionId.HighPart);

if (!NT_SUCCESS(status))
{
     // Error handling
     ...
}

앞의 코드 예제에서 DECLARE_UNICODE_STRING_SIZE 매크로는 리소스 허브에서 사용하는 형식으로 디바이스 경로 이름을 포함할 수 있을 만큼 큰 버퍼가 있는 라는 szDeviceName 초기화된 UNICODE_STRING 변수의 선언을 만듭니다. 이 매크로는 Ntdef.h 헤더 파일에 정의되어 있습니다. RESOURCE_HUB_PATH_SIZE 상수는 디바이스 경로 이름의 바이트 수를 지정합니다. RESOURCE_HUB_CREATE_PATH_FROM_ID 매크로는 연결 ID에서 디바이스 경로 이름을 생성합니다. RESOURCE_HUB_PATH_SIZERESOURCE_HUB_CREATE_PATH_FROM_ID Reshub.h 헤더 파일에 정의되어 있습니다.

다음 코드 예제에서는 디바이스 경로 이름을 사용하여 직렬로 연결된 주변 디바이스에 대한 파일 핸들(라는 ) SerialIoTarget을 엽니다.

// Open the peripheral device on the serial port as a remote I/O target.
 
WDF_IO_TARGET_OPEN_PARAMS openParams;
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&openParams,
                                            &szDeviceName,
                                            (GENERIC_READ | GENERIC_WRITE));

openParams.ShareAccess = 0;
openParams.CreateDisposition = FILE_OPEN;
openParams.FileAttributes = FILE_ATTRIBUTE_NORMAL;

status = WdfIoTargetOpen(SerialIoTarget, &openParams);

if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

앞의 코드 예제에서 WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME 함수는 드라이버 가 디바이스의 이름을 지정하여 직렬로 연결된 주변 디바이스에 대한 논리적 연결을 열 수 있도록 WDF_IO_TARGET_OPEN_PARAMS 구조를 초기화합니다. 변수는 SerialIoTarget 프레임워크 I/O 대상 개체에 대한 WDFIOTARGET 핸들입니다. 이 핸들은 예제에 표시되지 않는 WdfIoTargetCreate 메서드에 대한 이전 호출에서 가져옵니다. WdfIoTargetOpen 메서드에 대한 호출이 성공하면 드라이버는 핸들을 SerialIoTarget 사용하여 주변 디바이스에 I/O 요청을 보낼 수 있습니다.

EvtDriverDeviceAdd 이벤트 콜백 함수에서 주변 장치 드라이버는 WdfRequestCreate 메서드를 호출하여 드라이버에서 사용할 프레임워크 요청 개체를 할당할 수 있습니다. 나중에 개체가 더 이상 필요하지 않으면 드라이버는 WdfObjectDelete 메서드를 호출하여 개체를 삭제합니다. 드라이버는 WdfRequestCreate 호출에서 가져온 프레임워크 요청 개체를 여러 번 다시 사용하여 주변 디바이스에 I/O 요청을 보낼 수 있습니다. 읽기, 쓰기 또는 IOCTL 요청을 동기적으로 보내기 위해 드라이버는 WdfIoTargetSendReadSynchronousously, WdfIoTargetSendWriteSynchronously 또는 WdfIoTargetSendIoctlSynchronously 메서드를 호출합니다.

다음 코드 예제에서 드라이버는 WdfIoTargetSendWriteSynchronously 를 호출하여 주변 디바이스에 IRP_MJ_WRITE 요청을 동기적으로 보냅니다. 이 예제의 시작 부분에 변수는 pBuffer 주변 디바이스에 쓸 데이터가 포함된 페이지가 지정되지 않은 버퍼를 가리키고 dataSize 변수는 이 데이터의 크기를 바이트 단위로 지정합니다.

ULONG_PTR bytesWritten;
NTSTATUS status;

// Describe the input buffer.

WDF_MEMORY_DESCRIPTOR memoryDescriptor;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, pBuffer, dataSize);

// Configure the write request to time out after 2 seconds.

WDF_REQUEST_SEND_OPTIONS requestOptions;
WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT);
requestOptions.Timeout = WDF_REL_TIMEOUT_IN_SEC(2);

// Send the write request synchronously.

status = WdfIoTargetSendWriteSynchronously(SerialIoTarget,
                                           SerialRequest,
                                           &memoryDescriptor,
                                           NULL,
                                           &requestOptions,
                                           &bytesWritten);
if (!NT_SUCCESS(status))
{
    // Error handling
    ...
}

앞의 코드 예제에서는 다음을 수행합니다.

  1. WDF_MEMORY_DESCRIPTOR_INIT_BUFFER 함수 호출은 입력 버퍼를 설명하는 WDF_MEMORY_DESCRIPTOR 구조인 변수를 초기화 memoryDescriptor 합니다. 이전에는 드라이버가 ExAllocatePoolWithTag 와 같은 루틴을 호출하여 비페이지 풀에서 버퍼를 할당하고 쓰기 데이터를 이 버퍼에 복사했습니다.
  2. WDF_REQUEST_SEND_OPTIONS_INIT 함수 호출은 쓰기 요청에 대한 선택적 설정을 포함하는 WDF_REQUEST_SEND_OPTIONS 구조인 변수를 초기화 requestOptions 합니다. 이 예제에서 구조체는 요청이 2초 후에 완료되지 않으면 시간 초과하도록 구성합니다.
  3. WdfIoTargetSendWriteSynchronously 메서드를 호출하면 쓰기 요청이 주변 디바이스로 전송됩니다. 메서드는 쓰기 작업이 완료되거나 시간이 초과된 후 동기적으로 반환됩니다. 필요한 경우 다른 드라이버 스레드 가 WdfRequestCancelSentRequest 를 호출하여 요청을 취소할 수 있습니다.

WdfIoTargetSendWriteSynchronousously 호출에서 드라이버는 드라이버가 이전에 만든 프레임워크 요청 개체에 대한 핸들인 라는 SerialRequest변수를 제공합니다. WdfIoTargetSendWriteSynchronousously 호출 후 드라이버는 일반적으로 WdfRequestReuse 메서드를 호출하여 다시 사용할 프레임워크 요청 개체를 준비해야 합니다.