Поделиться через


Подключение периферийного драйвера KMDF к последовательному порту

Драйвер KMDF для периферийного устройства на управляемом последовательном порту SerCx2 требует определенных аппаратных ресурсов для работы устройства. В этих ресурсах содержатся сведения, необходимые драйверу для открытия логического подключения к последовательному порту. Дополнительные ресурсы могут включать прерывание, а также один или несколько входных или выходных пинов GPIO.

Этот драйвер реализует набор функций обратного вызова событий Plug and Play и управления питанием. Чтобы зарегистрировать эти функции в рамках KMDF, функция обратного вызова события EvtDriverDeviceAdd вызывает метод WdfDeviceInitSetPnpPowerEventCallbacks. Платформа вызывает функции обратного вызова событий управления питанием, чтобы уведомить драйвер изменений в состоянии питания периферийных устройств. В эти функции входит функция EvtDevicePrepareHardware , которая выполняет все операции, необходимые для обеспечения доступа устройства к драйверу.

После того как последовательно подключенное периферийное устройство входит в неинициализированное состояние питания устройства D0, платформа драйверов вызывает функцию EvtDevicePrepareHardware , чтобы сообщить периферийным драйверу подготовить устройство к использованию. Во время этого вызова драйвер получает два списка аппаратных ресурсов в качестве входных параметров. Параметр ResourcesRaw — это дескриптор объекта WDFCMRESLIST в список необработанных ресурсов, а параметр ResourcesTranslated — это дескриптор объекта WDFCMRESLIST в список преобразованных ресурсов. Преобразованные ресурсы включают идентификатор подключения , необходимый драйверу для установления логического подключения к периферийным устройствам.

В следующем примере кода показано, как функция EvtDevicePrepareHardware получает идентификатор подключения из параметра ResourcesTranslated .

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;
    }
}

Приведенный выше пример кода копирует идентификатор подключения для последовательно подключенного периферийного устройства в переменную с именем connectionId.

В следующем примере кода показано, как включить этот идентификатор подключения в имя пути устройства, которое можно использовать для открытия логического подключения к периферийным устройствам. Это имя пути устройства определяет концентратор ресурсов как системный компонент, из которого необходимо получить параметры, необходимые для доступа к периферийным устройствам.

// 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 создает объявление инициализированной переменной UNICODE_STRING с szDeviceName именем буфера, достаточно большим, чтобы содержать имя пути устройства в формате, используемом концентратором ресурсов. Этот макрос определен в файле заголовка Ntdef.h. Константа RESOURCE_HUB_PATH_SIZE указывает количество байтов в имени пути устройства. Макрос RESOURCE_HUB_CREATE_PATH_FROM_ID создает имя пути устройства из идентификатора подключения. RESOURCE_HUB_PATH_SIZE и RESOURCE_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 — это дескриптор WDFIOTARGET для целевого объекта ввода-вывода платформы. Этот дескриптор был получен из предыдущего вызова метода WdfIoTargetCreate , который не показан в примере. Если вызов метода WdfIoTargetOpen выполнен успешно, драйвер может использовать дескриптор SerialIoTarget для отправки запросов ввода-вывода на периферийное устройство.

В функции обратного вызова событий EvtDriverDeviceAdd периферийный драйвер может вызвать метод WdfRequestCreate , чтобы выделить объект запроса платформы для использования драйвером. Позже, когда объект больше не нужен, драйвер вызывает метод WdfObjectDelete для удаления объекта. Драйвер может повторно использовать объект запроса платформы, полученный из вызова WdfRequestCreate несколько раз для отправки запросов ввода-вывода на периферийное устройство. Чтобы синхронно отправлять запрос на чтение, запись или IOCTL, драйвер вызывает метод WdfIoTargetSendReadSynchronously, WdfIoTargetSendWriteSynchronously или WdfIoTargetSendIoctlSynchronously .

В следующем примере кода драйвер вызывает WdfIoTargetSendWriteSynchronous для синхронной отправки запроса 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 инициализирует memoryDescriptor переменную, которая представляет собой структуру WDF_MEMORY_DESCRIPTOR , описывающую входной буфер. Ранее драйвер вызывал подпрограмму, например ExAllocatePoolWithTag, чтобы из непагированного пула выделить буфер и скопировать данные для записи в этот буфер.
  2. Вызов функции WDF_REQUEST_SEND_OPTIONS_INIT инициализирует requestOptions переменную, которая представляет собой WDF_REQUEST_SEND_OPTIONS структуру , содержащую необязательные параметры для запроса на запись. В этом примере структура настраивает время ожидания запроса, если оно не завершается через две секунды.
  3. Вызов метода WdfIoTargetSendWriteSynchronously отправляет запрос на запись на периферийное устройство. Метод возвращается синхронно после завершения операции записи или времени ожидания. При необходимости другой поток драйвера может вызвать WdfRequestCancelSentRequest , чтобы отменить запрос.

В вызове WdfIoTargetSendWriteSynchronous драйвер предоставляет переменную с именем SerialRequest, которая является дескриптором объекта запроса платформы, созданного ранее драйвером. После вызова WdfIoTargetSendWriteSynchronous драйвер обычно вызывает метод WdfRequestRequestReuse , чтобы подготовить объект запроса платформы, который будет использоваться снова.