Partilhar via


Como registrar um dispositivo composto

Este artigo descreve como um driver de um dispositivo multifuncional USB, chamado de driver composto, pode registrar e cancelar o registro do dispositivo composto com a pilha de driver USB subjacente. O Windows carrega o driver fornecido pela Microsoft, Usbccgp.sys como o driver composto padrão. O procedimento neste artigo se aplica a um driver composto baseado em WDM (Modelo de Driver do Windows) personalizado que substitui Usbccgp.sys.

Um dispositivo USB (Universal Serial Bus) pode fornecer várias funções que estão ativas simultaneamente. Esses dispositivos multifuncionais também são conhecidos como dispositivos compostos. Por exemplo, um dispositivo composto pode definir uma função para a funcionalidade do teclado e outra função para o mouse. O driver composto enumera as funções do dispositivo. O driver composto pode gerenciar essas funções em um modelo monolítico ou criar PDOs (objetos de dispositivo físico) para cada uma das funções. Os drivers de função USB, como o driver do teclado e o driver do mouse, administram seus respectivos PDOs individuais.

A especificação USB 3.0 define o recurso de suspensão de função e ativação remota, permitindo que funções individuais entrem e saiam de estados de baixo consumo de energia, sem afetar o estado de energia de outras funções ou de todo o dispositivo. Para obter mais informações sobre o recurso, consulte Como Implementar a Suspensão de Função para um Driver Composto.

Para usar o recurso, o driver composto precisa registrar o dispositivo com a pilha de driver USB subjacente. Como o recurso se aplica a dispositivos USB 3.0, o driver composto deve garantir que a pilha subjacente dê suporte à versão USBD_INTERFACE_VERSION_602. Por meio da solicitação de registro, o driver composto:

  • Informa à pilha de driver USB subjacente que o driver é responsável por enviar uma solicitação para criar uma função para ativação remota. A pilha de driver USB processa a solicitação de ativação remota, que envia as solicitações de protocolo necessárias para o dispositivo.
  • Obtém uma lista de identificadores de função (um por função) atribuídos pela pilha de driver USB. Em seguida, o driver composto pode usar um identificador de função na solicitação do driver para ativação remota da função associada ao identificador.

Normalmente, um driver composto envia a solicitação de registro no AddDevice do driver ou na rotina do dispositivo inicial para lidar com IRP_MN_START_DEVICE. Portanto, o driver composto libera os recursos alocados para o registro nas rotinas de descarregamento do driver, como stop-device (IRP_MN_STOP_DEVICE) ou remove-device routine (IRP_MN_REMOVE_DEVICE).

Pré-requisitos

Antes de enviar a solicitação de registro, certifique-se de que:

  • Você tem o número de funções no dispositivo. Esse número pode ser derivado dos descritores recuperados pela solicitação get-configuration.
  • Você obteve um identificador USBD em uma chamada anterior para USBD_CreateHandle.
  • A pilha de driver USB subjacente dá suporte a dispositivos USB 3.0. Para fazer isso, ligue para USBD_IsInterfaceVersionSupported e passe USBD_INTERFACE_VERSION_602 como a versão a ser verificada.

Para obter um exemplo de código, consulte Como Implementar a Suspensão de Função para um Driver Composto.

Registrar um dispositivo composto

O procedimento a seguir descreve como você deve criar e enviar uma solicitação de registro para associar um driver composto à pilha de driver USB.

  1. Aloque uma estrutura COMPOSITE_DEVICE_CAPABILITIES e inicialize-a chamando a macro COMPOSITE_DEVICE_CAPABILITIES_INIT.

  2. Defina o membro CapabilityFunctionSuspend de COMPOSITE_DEVICE_CAPABILITIES como 1.

  3. Aloque uma estrutura REGISTER_COMPOSITE_DEVICE e inicialize a estrutura chamando a rotina USBD_BuildRegisterCompositeDevice. Na chamada, especifique o identificador USBD, a estrutura COMPOSITE_DEVICE_CAPABILITIES chamada e o número de funções.

  4. Aloque um IRP (pacote de solicitação de E/S) chamando IoAllocateIrp e obtenha um ponteiro para o primeiro local de pilha (IO_STACK_LOCATION) chamando IoGetNextIrpStackLocation.

  5. Aloque memória para um buffer grande o suficiente para conter uma matriz de identificadores de função (USBD_FUNCTION_HANDLE). O número de elementos na matriz deve ser o número de PDOs.

  6. Crie a solicitação definindo os seguintes membros de IO_STACK_LOCATION:

    • Especifique o tipo de solicitação definindo Parameters.DeviceIoControl.IoControlCode como IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE.
    • Especifique o parâmetro de entrada definindo Parameters.Others.Argument1 como o endereço da estrutura REGISTER_COMPOSITE_DEVICE inicializada.
    • Especifique o parâmetro de saída definindo AssociatedIrp.SystemBuffer como o buffer que foi alocado na etapa 5.
  7. Chame IoCallDriver para enviar a solicitação passando o IRP para o próximo local da pilha.

Após a conclusão, inspecione a matriz de identificadores de função retornados pela pilha de driver USB. Você pode armazenar a matriz no contexto do dispositivo do driver para uso futuro.

O exemplo de código a seguir mostra como criar e enviar uma solicitação de registro. O exemplo pressupõe que o driver composto armazena o número de funções obtido anteriormente e o identificador USBD no contexto do dispositivo do driver.

VOID  RegisterCompositeDriver(PPARENT_FDO_EXT parentFdoExt)
{
    PIRP                            irp;
    REGISTER_COMPOSITE_DRIVER       registerInfo;
    COMPOSITE_DRIVER_CAPABILITIES   capabilities;
    NTSTATUS                        status;
    PVOID                           buffer;
    ULONG                           bufSize;
    PIO_STACK_LOCATION              nextSp;

    buffer = NULL;

    COMPOSITE_DRIVER_CAPABILITIES_INIT(&capabilities);
    capabilities.CapabilityFunctionSuspend = 1;

    USBD_BuildRegisterCompositeDriver(parentFdoExt->usbdHandle,
        capabilities,
        parentFdoExt->numFunctions,
        &registerInfo);

    irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp == NULL)
    {
        //IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto ExitRegisterCompositeDriver;
    }

    nextSp = IoGetNextIrpStackLocation(irp);

    bufSize = parentFdoExt->numFunctions * sizeof(USBD_FUNCTION_HANDLE);

    buffer = ExAllocatePoolWithTag (NonPagedPool, bufSize, POOL_TAG);

    if (buffer == NULL)
    {
        // Memory alloc for function-handles failed.
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto ExitRegisterCompositeDriver;
    }

    nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DRIVER;

    //Set the input buffer in Argument1
    nextSp->Parameters.Others.Argument1 = &registerInfo;

    //Set the output buffer in SystemBuffer field for USBD_FUNCTION_HANDLE.
    irp->AssociatedIrp.SystemBuffer = buffer;

    // Pass the IRP down to the next device object in the stack. Not shown.
    status = CallNextDriverSync(parentFdoExt, irp, FALSE);

    if (!NT_SUCCESS(status))
    {
        //Failed to register the composite driver.
        goto ExitRegisterCompositeDriver;
    }

    parentFdoExt->compositeDriverRegistered = TRUE;

    parentFdoExt->functionHandleArray = (PUSBD_FUNCTION_HANDLE) buffer;

End:
    if (!NT_SUCCESS(status))
    {
        if (buffer != NULL)
        {
            ExFreePoolWithTag (buffer, POOL_TAG);
            buffer = NULL;
        }
    }

    if (irp != NULL)
    {
        IoFreeIrp(irp);
        irp = NULL;
    }

    return;
}

Cancelar o registro de um dispositivo composto

  1. Aloque um IRP (pacote de solicitação de E/S) chamando IoAllocateIrp e obtenha um ponteiro para o primeiro local de pilha (IO_STACK_LOCATION) chamando IoGetNextIrpStackLocation.
  2. Crie a solicitação definindo o membro Parameters.DeviceIoControl.IoControlCode de IO_STACK_LOCATION como IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE.
  3. Chame IoCallDriver para enviar a solicitação passando o IRP para o próximo local da pilha.

A solicitação IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE é enviada uma vez pelo driver composto no contexto da rotina remove-device. A finalidade da solicitação é remover a associação entre a pilha de driver USB e o driver composto e sua função enumerada. A solicitação também limpa todos os recursos que foram criados para manter essa associação e todos os identificadores de função que foram retornados na solicitação de registro anterior.

O exemplo de código a seguir mostra como criar e enviar uma solicitação para cancelar o registro do dispositivo composto. O exemplo pressupõe que o driver composto foi registrado anteriormente por meio de uma solicitação de registro, conforme descrito anteriormente neste artigo.

VOID  UnregisterCompositeDriver(
    PPARENT_FDO_EXT parentFdoExt )
{
    PIRP                irp;
    PIO_STACK_LOCATION  nextSp;
    NTSTATUS            status;

    PAGED_CODE();

    irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);

    if (irp == NULL)
    {
        //IoAllocateIrp failed.
        status = STATUS_INSUFFICIENT_RESOURCES;
        return;
    }

    nextSp = IoGetNextIrpStackLocation(irp);

    nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DRIVER;

    // Pass the IRP down to the next device object in the stack. Not shown.
    status = CallNextDriverSync(parentFdoExt, irp, FALSE);

    if (NT_SUCCESS(status))
    {
        parentFdoExt->compositeDriverRegistered = FALSE;
    }

    IoFreeIrp(irp);

    return;
}