Partilhar via


Conectando um driver periférico KMDF a uma porta serial

O driver KMDF para um dispositivo periférico em uma porta serial gerenciada pelo SerCx2 requer determinados recursos de hardware para operar o dispositivo. Incluídos nesses recursos estão as informações de que o driver precisa para abrir uma conexão lógica com a porta serial. Recursos adicionais podem incluir uma interrupção e um ou mais pinos de entrada ou saída de GPIO.

Esse driver implementa um conjunto de Plug and Play e funções de retorno de chamada de eventos de gerenciamento de energia. Para registrar essas funções com KMDF, a função de retorno de chamada de evento EvtDriverDeviceAdd do driver chama o método WdfDeviceInitSetPnpPowerEventCallbacks do driver. A estrutura chama as funções de retorno de chamada de evento de gerenciamento de energia para notificar o driver de alterações no estado de energia do dispositivo periférico. Incluída nessas funções está a função EvtDevicePrepareHardware , que executa todas as operações necessárias para tornar o dispositivo acessível ao driver.

Depois que o dispositivo periférico conectado serialmente entra em um estado de energia do dispositivo D0 não inicializado, a estrutura de driver chama a função EvtDevicePrepareHardware para informar ao driver periférico para preparar o dispositivo para uso. Durante essa chamada, o driver recebe duas listas de recursos de hardware como parâmetros de entrada. O parâmetro ResourcesRaw é um identificador de objeto WDFCMRESLIST para a lista de recursos brutos e o parâmetro ResourcesTranslated é um identificador de objeto WDFCMRESLIST para a lista de recursos traduzidos. Os recursos traduzidos incluem a ID de conexão que o driver requer para estabelecer uma conexão lógica com o dispositivo periférico.

O exemplo de código a seguir mostra como a função EvtDevicePrepareHardware obtém a ID de conexão do parâmetro 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;
    }
}

O exemplo de código anterior copia a ID de conexão de um dispositivo periférico serialmente conectado em uma variável chamada connectionId.

O exemplo de código a seguir mostra como incorporar essa ID de conexão em um nome de caminho de dispositivo que pode ser usado para abrir uma conexão lógica com o dispositivo periférico. Esse nome de caminho do dispositivo identifica o hub de recursos como o componente do sistema do qual obter os parâmetros necessários para acessar o dispositivo periférico.

// 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
     ...
}

No exemplo de código anterior, a macro DECLARE_UNICODE_STRING_SIZE cria a declaração de uma variável de UNICODE_STRING inicializada chamada szDeviceName que tem um buffer grande o suficiente para conter um nome de caminho de dispositivo no formato usado pelo hub de recursos. Essa macro é definida no arquivo de cabeçalho Ntdef.h. A constante RESOURCE_HUB_PATH_SIZE especifica o número de bytes no nome do caminho do dispositivo. A macro RESOURCE_HUB_CREATE_PATH_FROM_ID gera o nome do caminho do dispositivo a partir da ID da conexão. RESOURCE_HUB_PATH_SIZE e RESOURCE_HUB_CREATE_PATH_FROM_ID são definidos no arquivo de cabeçalho Reshub.h.

O exemplo de código a seguir usa o nome do caminho do dispositivo para abrir um identificador de arquivo (chamado SerialIoTarget) para o dispositivo periférico conectado serialmente.

// 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
    ...
}

No exemplo de código anterior, a função WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME inicializa a estrutura WDF_IO_TARGET_OPEN_PARAMS para que o driver possa abrir uma conexão lógica com o dispositivo periférico conectado serialmente especificando o nome do dispositivo. A SerialIoTarget variável é um identificador WDFIOTARGET para um objeto de destino de E/S da estrutura. Esse identificador foi obtido de uma chamada anterior para o método WdfIoTargetCreate , que não é mostrado no exemplo. Se a chamada para o método WdfIoTargetOpen for bem-sucedida, o driver poderá usar o SerialIoTarget identificador para enviar solicitações de E/S para o dispositivo periférico.

Na função de retorno de chamada de evento EvtDriverDeviceAdd , o driver periférico pode chamar o método WdfRequestCreate para alocar um objeto de solicitação de estrutura para uso pelo driver. Posteriormente, quando o objeto não for mais necessário, o driver chamará o método WdfObjectDelete para excluir o objeto. O driver pode reutilizar o objeto de solicitação de estrutura obtido da chamada WdfRequestCreate várias vezes para enviar solicitações de E/S para o dispositivo periférico. Para enviar de forma síncrona uma solicitação de leitura, gravação ou IOCTL, o driver chama o método WdfIoTargetSendReadSynchronously, WdfIoTargetSendWriteSynchronously ou WdfIoTargetSendIoctlSynchronously .

No exemplo de código a seguir, o driver chama WdfIoTargetSendWriteSynchronously para enviar de forma síncrona uma solicitação IRP_MJ_WRITE para o dispositivo periférico. No início deste exemplo, a pBuffer variável aponta para um buffer nãopagado que contém os dados que devem ser gravados no dispositivo periférico e a dataSize variável especifica o tamanho, em bytes, desses dados.

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

O exemplo de código anterior faz o seguinte:

  1. A chamada de função WDF_MEMORY_DESCRIPTOR_INIT_BUFFER inicializa a memoryDescriptor variável, que é uma estrutura WDF_MEMORY_DESCRIPTOR que descreve o buffer de entrada. Anteriormente, o driver chamava uma rotina como ExAllocatePoolWithTag para alocar o buffer do pool nãopagado e copiava os dados de gravação para esse buffer.
  2. A chamada de função WDF_REQUEST_SEND_OPTIONS_INIT inicializa a requestOptions variável, que é uma estrutura WDF_REQUEST_SEND_OPTIONS que contém as configurações opcionais para a solicitação de gravação. Neste exemplo, a estrutura configura a solicitação para atingir o tempo limite se ela não for concluída após dois segundos.
  3. A chamada para o método WdfIoTargetSendWriteSynchronously envia a solicitação de gravação para o dispositivo periférico. O método retorna de forma síncrona, depois que a operação de gravação é concluída ou atinge o tempo limite. Se necessário, outro thread de driver pode chamar WdfRequestCancelSentRequest para cancelar a solicitação.

Na chamada WdfIoTargetSendWriteSynchronously , o driver fornece uma variável chamada SerialRequest, que é um identificador para um objeto de solicitação de estrutura que o driver criou anteriormente. Após a chamada WdfIoTargetSendWriteSynchronously , o driver normalmente deve chamar o método WdfRequestReuse para preparar o objeto de solicitação de estrutura a ser usado novamente.