Partilhar via


Conectando um driver periférico UMDF a uma porta serial

O driver UMDF para um dispositivo periférico em uma porta serial gerenciada pelo SerCx2 requer determinados recursos de hardware para operar o dispositivo. Incluídas 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 gpio.

Esse driver implementa uma interface IPnpCallbackHardware2 e registra essa interface com a estrutura de driver do Windows durante a chamada para o método IDriverEntry::OnDeviceAdd do driver. A estrutura chama os métodos na interface IPnpCallbackHardware2 para notificar o driver de alterações no estado de energia do dispositivo.

Depois que o dispositivo periférico conectado serialmente entra em um estado de energia do dispositivo D0 não inicializado, a estrutura do driver chama o método IPnpCallbackHardware2::OnPrepareHardware do driver para instruir o driver a preparar esse dispositivo para uso. Durante essa chamada, o driver recebe duas listas de recursos de hardware como parâmetros de entrada. O parâmetro pWdfResourcesRaw aponta para a lista de recursos brutos e o parâmetro pWdfResourcesTranslated aponta para a lista de recursos traduzidos. Ambos os parâmetros são ponteiros para objetos IWDFCmResourceList . Os recursos traduzidos incluem a ID de conexão que o driver periférico precisa para estabelecer a conexão lógica com o dispositivo periférico serialmente conectado.

Para permitir que um driver periférico UMDF receba IDs de conexão em sua lista de recursos, o arquivo INF que instala o driver deve incluir a seguinte diretiva em sua seção DDInstall específica do WDF :

UmdfDirectHardwareAccess = AllowDirectHardwareAccess Para obter mais informações sobre essa diretiva, consulte Especificando diretivas WDF em arquivos INF. Para obter um exemplo de um arquivo INX (usado para criar o arquivo INF correspondente) que usa essa diretiva, consulte o SpbAccelerometer nos exemplos de driver do WDK.

O exemplo de código a seguir mostra como o método OnPrepareHardware do driver obtém a ID de conexão do parâmetro pWdfResourcesTranslated .

BOOLEAN fConnectIdFound = FALSE;
BOOLEAN fDuplicateFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;

resourceCount = pWdfResourcesTranslated->GetCount();

// Loop through the resources and save the relevant ones.
for (ULONG ix = 0; ix < resourceCount; ix++)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;

    pDescriptor = pWdfResourcesTranslated->GetDescriptor(ix);

    if (pDescriptor == NULL)
    {
        hr = 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 (fConnIdFound == FALSE)
                        {
                            // Save the serial controller's connection ID.
                            connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
                            connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
                            fConnectIdFound = TRUE;
                        }
                        else
                        {
                            fDuplicateFound = TRUE;
                        }
                    }
                }

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

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

        default:
            // Ignore all other resource descriptors.
            break;
    }
}

O exemplo de código anterior copia a ID de conexão do dispositivo periférico serialmente conectado em uma variável chamada connectionId. O exemplo de código a seguir mostra como incorporar a ID de conexão em um nome de caminho do dispositivo que pode ser usado para identificar o controlador serial ao qual o dispositivo periférico está conectado.

WCHAR szTargetPath[100];
HRESULT hres;

// Create the device path using the well-known resource hub
// path name and the connection ID.
//
hres = StringCbPrintfW(&szTargetPath[0],
                       sizeof(DevicePath),
                       L"\\\\.\\RESOURCE_HUB\\%0*I64x",
                       (size_t)(sizeof(LARGE_INTEGER) * 2),
                       connectionId.QuadPart);
if (FAILED(hres))
{
     // Error handling
     ...
}

O exemplo de código anterior grava o nome do caminho do dispositivo para o controlador serial na szTargetPath matriz. O exemplo de código a seguir usa esse nome de caminho para abrir um identificador de arquivo para o controlador serial.

UMDF_IO_TARGET_OPEN_PARAMS openParams;

openParams.dwShareMode = 0;
openParams.dwCreationDisposition = OPEN_EXISTING;
openParams.dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED;
hres = pRemoteTarget->OpenFileByName(&szTargetPath[0],
                                     (GENERIC_READ | GENERIC_WRITE),
                                     &openParams);
if (FAILED(hres))
{
    // Error handling
    ...
}

No exemplo de código anterior, o pRemoteTarget parâmetro é um ponteiro para um objeto IWDFRemoteTarget . Se a chamada para o método IWDFRemoteTarget::OpenFileByName for bem-sucedida, o driver do dispositivo periférico serialmente conectado poderá usar o objeto IWDFRemoteTarget para enviar solicitações de E/S ao controlador serial.

Para enviar uma solicitação de leitura ou gravação para o dispositivo periférico, o driver primeiro chama o método IWDFRemoteTarget::FormatRequestForRead ou IWDFRemoteTarget::FormatRequestForWrite para formatar a solicitação. (A interface IWDFRemoteTarget herda esses dois métodos da interface IWDFIoTarget .)

Para enviar uma solicitação de controle de E/S para o controlador serial, o driver primeiro chama o método IWDFRemoteTarget::FormatRequestForIoctl para formatar a solicitação. (A interface IWDFRemoteTarget herda esse método da interface IWDFIoTarget .) Em seguida, o driver chama o método IWDFIoRequest::Send para enviar a solicitação de controle de E/S para o dispositivo periférico serialmente conectado.

No exemplo de código a seguir, o driver periférico envia uma solicitação de controle de E/S para o controlador serial.

HRESULT hres;
IWDFMemory *pInputMemory = NULL;

// Create a new I/O request.
if (SUCCEEDED(hres))
{
    hres = pWdfDevice->CreateRequest(NULL, 
                                     pWdfDevice, 
                                     &pWdfIoRequest);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Allocate memory for the input buffer.
if (SUCCEEDED(hres))
{
    hres = pWdfDriver->CreatePreallocatedWdfMemory(pInBuffer, 
                                                   inBufferSize, 
                                                   NULL,
                                                   pWdfIoRequest,
                                                   &pInputMemory);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Format the request to be an I/O control request.
if (SUCCEEDED(hres))
{
    hres = pRemoteTarget->FormatRequestForIoctl(pWdfIoRequest,
                                                ioctlCode,
                                                NULL,
                                                pInputMemory, 
                                                NULL, 
                                                NULL,
                                                NULL);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Send the request to the serial controller.
if (SUCCEEDED(hres))
{
    ULONG Flags = fSynchronous ? WDF_REQUEST_SEND_OPTION_SYNCHRONOUS : 0;

    if (!fSynchronous)
    {
        pWdfIoRequest->SetCompletionCallback(pCallback, NULL); 
    }

    hres = pWdfIoRequest->Send(pRemoteTarget, Flags, 0);

    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

if (fSynchronous || FAILED(hres))
{
    pWdfIoRequest->DeleteWdfObject();
    SAFE_RELEASE(pWdfIoRequest);
}

O exemplo de código anterior faz o seguinte:

  1. A pWdfDevice variável é um ponteiro para a interface IWDFDevice do objeto de dispositivo de estrutura que representa o dispositivo periférico serialmente conectado. O método IWDFDevice::CreateRequest cria uma solicitação de E/S e encapsula essa solicitação na instância da interface IWDFIoRequest que é apontada pelo pWdfIoRequest parâmetro . A solicitação de E/S é excluída posteriormente (consulte a etapa 6). Essa implementação é um pouco ineficiente porque cria e exclui um objeto de solicitação para cada solicitação de E/S enviada. Uma abordagem mais eficiente é reutilizar o mesmo objeto de solicitação para uma série de solicitações de E/S. Para obter mais informações, consulte Reutilizando objetos de solicitação de estrutura.

  2. A pWdfDriver variável é um ponteiro para a interface IWDFDriver do objeto de driver de estrutura que representa o driver periférico. As pInBuffer variáveis e inBufferSize especificam o endereço e o tamanho do buffer de entrada para a solicitação de controle de E/S. O método IWDFDriver::CreatePreallocatedWdfMemory cria um objeto de memória de estrutura para o buffer de entrada e designa o objeto IWDFIoRequest apontado por pWdfIoRequest como o objeto pai do objeto de memória.

  3. A pWdfRemoteTarget variável é o ponteiro de destino remoto obtido da chamada OpenFileByName em um exemplo de código anterior. O método IWDFRemoteTarget::FormatRequestForIoctl formata a solicitação para uma operação de controle de E/S. A ioctlCode variável é definida como um dos códigos de controle de E/S listados na tabela na Interface de Solicitação de E/S Serial.

  4. A fSynchronous variável será TRUE se a solicitação de controle de E/S for enviada de forma síncrona e for FALSE se for enviada de forma assíncrona. A pCallback variável é um ponteiro para uma interface IRequestCallbackRequestCompletion criada anteriormente. Se a solicitação for enviada de forma assíncrona, a chamada para o método IWDFIoRequest::SetCompletionCallback registrará essa interface. Posteriormente, o método IRequestCallbackRequestCompletion::OnCompletion é chamado para notificar o driver quando a solicitação for concluída de forma assíncrona.

  5. O método Send envia a solicitação de gravação formatada para o dispositivo periférico serialmente conectado. A Flags variável indica se a solicitação de gravação deve ser enviada de forma síncrona ou assíncrona.

  6. Se a solicitação for enviada de forma síncrona, o método IWDFIoRequest::D eleteWdfObject excluirá o objeto de solicitação de E/S apontado por pWdfIoRequest e o objeto filho apontado por pInputMemory. A interface IWDFIoRequest herda esse método da interface IWDFObject . Se a solicitação for enviada de forma assíncrona, a chamada para o método DeleteWdfObject deverá ocorrer posteriormente, no método OnCompletion do driver.