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