Guide pratique pour énumérer des canaux USB

Cet article fournit une vue d’ensemble des canaux USB et décrit les étapes requises par un pilote client USB pour obtenir des poignées de canal à partir de la pile de pilotes USB.

Un point de terminaison USB est une mémoire tampon dans l’appareil à laquelle le pilote client envoie des données ou à partir duquel il reçoit des données. Pour envoyer ou recevoir des données, le pilote client envoie une demande de transfert d’E/S à la pile de pilotes USB, qui présente les données au contrôleur hôte. Le contrôleur hôte suit ensuite certains protocoles (selon le type de point de terminaison : en bloc, interruption ou isochronisme) pour générer des demandes qui transfèrent des données vers ou à partir de l’appareil. Tous les détails du transfert de données sont extraits du pilote client. Tant que le pilote client envoie une demande bien formée, la pile de pilotes USB traite la demande et transfère les données vers l’appareil.

Pendant la configuration de l’appareil, la pile de pilotes USB crée un canal USB (côté hôte) pour chacun des points de terminaison de l’appareil définis dans l’interface USB et son autre paramètre actif. Un canal USB est un canal de communication entre le contrôleur hôte et le point de terminaison. Pour le pilote client, un canal est une abstraction logique du point de terminaison. Pour envoyer des transferts de données, le pilote doit obtenir le handle de canal associé au point de terminaison qui est la cible du transfert. Des poignées de canal sont également requises lorsque le pilote souhaite abandonner les transferts ou réinitialiser le canal, en cas de conditions d’erreur.

Tous les attributs d’un canal sont dérivés du descripteur de point de terminaison associé. Par instance, en fonction du type du point de terminaison, la pile de pilotes USB attribue un type pour le canal. Pour un point de terminaison en bloc, la pile de pilotes USB crée un canal en bloc ; pour un point de terminaison isochronieux, un canal isochronieux est créé, et ainsi de suite. Un autre attribut important est la quantité de données que le contrôleur hôte peut envoyer au point de terminaison dans une requête. En fonction de cette valeur, le pilote client doit déterminer la disposition de la mémoire tampon de transfert.

Windows Driver Foundation (WDF) fournit des objets cibles d’E/S spécialisés dans KMDF (Kernel-Mode Driver Framework) et User-Mode Driver Framework (UMDF) qui simplifient la plupart des tâches de configuration pour le pilote client. En utilisant ces objets, le pilote client peut récupérer des informations sur la configuration actuelle, telles que le nombre d’interfaces, un autre paramètre au sein de chaque interface et leurs points de terminaison. L’un de ces objets, appelé objet de canal cible, effectue des tâches liées au point de terminaison. Cet article explique comment obtenir des informations sur le canal à l’aide de l’objet de canal cible.

Pour les pilotes clients WDM (Windows Driver Model), la pile de pilotes USB retourne un tableau de structures USBD_PIPE_INFORMATION . Le nombre d’éléments dans le tableau dépend du nombre de points de terminaison définis pour le paramètre alternatif actif d’une interface dans la configuration sélectionnée. Chaque élément contient des informations sur le canal créé pour un point de terminaison particulier. Pour plus d’informations sur la sélection d’une configuration et l’obtention du tableau d’informations sur le canal, consultez Comment sélectionner une configuration pour un périphérique USB.

Bon à savoir

Avant que le pilote client ne puisse énumérer les canaux, assurez-vous que ces conditions sont remplies :

Obtention de poignées de canal USB dans un pilote client KMDF

Le framework représente chaque canal ouvert par la pile de pilotes USB en tant qu’objet de canal cible USB. Un pilote client KMDF peut accéder aux méthodes de l’objet de canal cible pour obtenir des informations sur le canal. Pour effectuer des transferts de données, le pilote client doit disposer de handles de canal WDFUSBPIPE. Pour obtenir les handles de canal, le pilote doit énumérer les interfaces et les autres paramètres de la configuration active, puis énumérer les points de terminaison définis dans chaque paramètre. L’exécution d’opérations d’énumération, pour chaque transfert de données, peut s’avérer coûteuse. Par conséquent, une approche consiste à obtenir des poignées de canal une fois l’appareil configuré et à les stocker dans le contexte de l’appareil défini par le pilote. Lorsque le pilote reçoit des demandes de transfert de données, il peut récupérer les handles de canal nécessaires à partir du contexte de l’appareil et les utiliser pour envoyer la demande. Si le pilote client modifie la configuration de l’appareil, par exemple, sélectionne un autre paramètre, celui-ci doit également actualiser le contexte de l’appareil avec les nouvelles poignées de canal. Dans le cas contraire, le pilote peut envoyer par erreur des demandes de transfert sur des poignées de canal obsolètes.

Les handles de canal ne sont pas nécessaires pour les transferts de contrôle. Pour envoyer des demandes de transfert de contrôle, un pilote client WDF appelle les méthodes WdfUsbDevicexxxxxx exposées par l’objet d’appareil framework. Ces méthodes nécessitent un handle WDFUSBDEVICE pour lancer des transferts de contrôle qui ciblent le point de terminaison par défaut. Pour ces transferts, la cible d’E/S de la requête est le point de terminaison par défaut et est représentée par le handle WDFIOTARGET, qui est extrait par le handle WDFUSBPIPE. Au niveau de l’appareil, le handle WDFUSBDEVICE est une abstraction du handle WDFUSBPIPE sur le point de terminaison par défaut.

Pour plus d’informations sur l’envoi de transferts de contrôle et les méthodes KMDF, consultez Comment envoyer un transfert de contrôle USB.

  1. Étendez la structure de contexte de votre appareil pour stocker les poignées de canal.

    Si vous connaissez les points de terminaison de votre appareil, étendez la structure du contexte de votre appareil en ajoutant des membres WDFUSBPIPE pour stocker les poignées de canal USB associées. Par exemple, vous pouvez étendre la structure du contexte de l’appareil, comme illustré ici :

    typedef struct _DEVICE_CONTEXT {
        WDFUSBDEVICE    UsbDevice;
        WDFUSBINTERFACE UsbInterface;
        WDFUSBPIPE      BulkReadPipe;   // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      BulkWritePipe;  // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      InterruptPipe;  // Pipe opened for the interrupt IN endpoint.
        WDFUSBPIPE      StreamInPipe;   // Pipe opened for stream IN endpoint.
        WDFUSBPIPE      StreamOutPipe;  // Pipe opened for stream OUT endpoint.
        UCHAR           NumberConfiguredPipes;  // Number of pipes opened.
        ...
        ...                                     // Other members. Not shown.
    
    } DEVICE_CONTEXT, *PDEVICE_CONTEXT;
    
  2. Déclarez une structure de contexte de canal.

    Chaque canal peut stocker les caractéristiques liées au point de terminaison dans une autre structure appelée contexte de canal. Comme pour un contexte d’appareil, un contexte de canal est une structure de données (définie par le pilote client) permettant de stocker des informations sur les canaux associés aux points de terminaison. Pendant la configuration de l’appareil, le pilote client transmet un pointeur vers son contexte de canal vers l’infrastructure. L’infrastructure alloue un bloc de mémoire en fonction de la taille de la structure et stocke un pointeur vers cet emplacement de mémoire avec l’objet de canal cible USB du framework. Le pilote client peut utiliser le pointeur pour accéder aux informations de canal et les stocker dans les membres du contexte du canal.

    typedef struct _PIPE_CONTEXT {
    
        ULONG MaxPacketSize;
        ULONG MaxStreamsSupported;
        PUSBD_STREAM_INFORMATION StreamInfo;
    } PIPE_CONTEXT, *PPIPE_CONTEXT;
    
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PIPE_CONTEXT, GetPipeContext)
    
    

    Dans cet exemple, le contexte de canal stocke le nombre maximal d’octets pouvant être envoyés en un seul transfert. Le pilote client peut utiliser cette valeur pour déterminer la taille de la mémoire tampon de transfert. La déclaration inclut également la macro WDF_DECLARE_CONTEXT_TYPE_WITH_NAME , qui génère une fonction inline, GetPipeContext. Le pilote client peut appeler cette fonction pour récupérer un pointeur vers le bloc de mémoire qui stocke le contexte du canal.

    Pour plus d’informations sur les contextes, consultez Espace de contexte d’objet framework.

    Pour passer un pointeur vers l’infrastructure, le pilote client initialise d’abord son contexte de canal en appelant WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE. Ensuite, passe un pointeur vers le contexte de canal lors de l’appel de WdfUsbTargetDeviceSelectConfig (pour sélectionner une configuration) ou WdfUsbInterfaceSelectSetting (pour sélectionner un autre paramètre).

  3. Une fois la demande de configuration de l’appareil terminée, énumérez l’interface et obtenez les poignées de canal pour les canaux configurés. Vous aurez besoin de cet ensemble d’informations :

    • Handle WDFUSBINTERFACE à l’interface qui contient le paramètre actuel. Vous pouvez obtenir ce handle en énumérant les interfaces dans la configuration active. Si vous avez également fourni un pointeur vers une structure WDF_USB_DEVICE_SELECT_CONFIG_PARAMS dans WdfUsbTargetDeviceSelectConfig, vous pouvez obtenir le handle à partir du membre Type.SingleInterface.ConfigureUsbInterface (pour les appareils à interface unique) ou du membre Type.MultiInterface.Pairs.UsbInterface (pour les appareils multi-interface).
    • Nombre de canaux ouverts pour les points de terminaison dans le paramètre actuel. Vous pouvez obtenir ce numéro sur une interface particulière en appelant la méthode WdfUsbInterfaceGetNumConfiguredPipes .
    • Les handles WDFUSBPIPE pour tous les canaux configurés. Vous pouvez obtenir le handle en appelant la méthode WdfUsbInterfaceGetConfiguredPipe .

    Après avoir obtenu la poignée de canal, le pilote client peut appeler des méthodes pour déterminer le type et la direction du canal. Le pilote peut obtenir des informations sur le point de terminaison, dans une structure WDF_USB_PIPE_INFORMATION . Le pilote peut obtenir la structure renseignée en appelant la méthode WdfUsbTargetPipeGetInformation . Le pilote peut également fournir un pointeur vers la structure dans l’appel WdfUsbInterfaceGetConfiguredPipe .

L’exemple de code suivant énumère les canaux dans le paramètre actuel. Il obtient des handles de canal pour les points de terminaison en bloc et d’interruption de l’appareil, et les stocke dans la structure du contexte d’appareil du pilote. Il stocke la taille de paquet maximale de chaque point de terminaison dans le contexte de canal associé. Si le point de terminaison prend en charge les flux, il ouvre des flux statiques en appelant la routine OpenStreams. L’implémentation d’OpenStreams est illustrée dans Comment ouvrir et fermer des flux statiques dans un point de terminaison en bloc USB.

Pour déterminer si un point de terminaison en bloc particulier prend en charge les flux statiques, le pilote client examine le descripteur de point de terminaison. Ce code est implémenté dans une routine d’assistance nommée RetrieveStreamInfoFromEndpointDesc, indiquée dans le bloc de code suivant.

NTSTATUS
    FX3EnumeratePipes(
    _In_ WDFDEVICE Device)

{
    NTSTATUS                    status;
    PDEVICE_CONTEXT             pDeviceContext;
    UCHAR                       i;
    PPIPE_CONTEXT               pipeContext;
    WDFUSBPIPE                  pipe;
    WDF_USB_PIPE_INFORMATION    pipeInfo;

    PAGED_CODE();

    pDeviceContext = GetDeviceContext(Device);

    // Get the number of pipes in the current alternate setting.
    pDeviceContext->NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(
        pDeviceContext->UsbInterface);

    if (pDeviceContext->NumberConfiguredPipes == 0)
    {
        status = USBD_STATUS_BAD_NUMBER_OF_ENDPOINTS;
        goto Exit;
    }
    else
    {
        status = STATUS_SUCCESS;
    }

    // Enumerate the pipes and get pipe information for each pipe.
    for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++)
    {
        WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

        pipe =  WdfUsbInterfaceGetConfiguredPipe(
            pDeviceContext->UsbInterface,
            i,
            &pipeInfo);

        if (pipe == NULL)
        {
            continue;
        }

        pipeContext = GetPipeContext (pipe);

        // If the pipe is a bulk endpoint that supports streams,
        // If the host controller supports streams, open streams.
        // Use the endpoint as an IN bulk endpoint.
        // Store the maximum packet size.

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {

            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamInPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamInPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkReadPipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsOutEndpoint (pipe))
        {
            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamOutPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamOutPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkWritePipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {
            pDeviceContext->InterruptPipe = pipe;
            pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            continue;
        }

    }

Exit:
    return status;
}

L’exemple de code suivant montre une routine d’assistance nommée RetrieveStreamInfoFromEndpointDesc, que le pilote client appelle lors de l’énumération des canaux.

Dans l’exemple de code suivant, le pilote client appelle la routine d’assistance précédente, RetrieveStreamInfoFromEndpointDesc, lors de l’énumération des canaux. La routine examine d’abord le descripteur de configuration et l’analyse pour récupérer les descripteurs de point de terminaison. Si le descripteur de point de terminaison du canal contient un USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE descripteur, le pilote récupère le nombre maximal de flux pris en charge par le point de terminaison.

/*++
Routine Description:

This routine parses the configuration descriptor and finds the endpoint
with which the specified pipe is associated.
It then retrieves the maximum number of streams supported by the endpoint.
It stores maximum number of streams in the pipe context.

Arguments:

Device - WDFUSBDEVICE handle to the target device object.
The driver obtained that handle in a previous call to
WdfUsbTargetDeviceCreateWithParameters.

Pipe - WDFUSBPIPE handle to the target pipe object.

Return Value:

NTSTATUS
++*/

VOID RetrieveStreamInfoFromEndpointDesc (
    WDFDEVICE Device,
    WDFUSBPIPE Pipe)
{
    PDEVICE_CONTEXT                                 deviceContext                = NULL;
    PUSB_CONFIGURATION_DESCRIPTOR                   configDescriptor             = NULL;
    WDF_USB_PIPE_INFORMATION                        pipeInfo;
    PUSB_COMMON_DESCRIPTOR                          pCommonDescriptorHeader      = NULL;
    PUSB_INTERFACE_DESCRIPTOR                       pInterfaceDescriptor         = NULL;
    PUSB_ENDPOINT_DESCRIPTOR                        pEndpointDescriptor          = NULL;
    PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR   pEndpointCompanionDescriptor = NULL;
    ULONG                                           maxStreams;
    ULONG                                           index;
    BOOLEAN                                         found                        = FALSE;
    UCHAR                                           interfaceNumber = 0;
    UCHAR                                           alternateSetting = 1;
    PPIPE_CONTEXT                                   pipeContext = NULL;
    NTSTATUS                                        status;

    PAGED_CODE();

    deviceContext = GetDeviceContext (Device);
    pipeContext = GetPipeContext (Pipe);

    // Get the configuration descriptor of the currently selected configuration
    status = FX3RetrieveConfigurationDescriptor (
        deviceContext->UsbDevice,
        &deviceContext->ConfigurationNumber,
        &configDescriptor);

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not retrieve the configuration descriptor.");

        status = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    if (deviceContext->ConfigurationNumber == 1)
    {
        alternateSetting = 1;
    }
    else
    {
        alternateSetting = 0;
    }

    // Get the Endpoint Address of the pipe
    WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
    WdfUsbTargetPipeGetInformation (Pipe, &pipeInfo);

    // Parse the ConfigurationDescriptor (including all Interface and
    // Endpoint Descriptors) and locate a Interface Descriptor which
    // matches the InterfaceNumber, AlternateSetting, InterfaceClass,
    // InterfaceSubClass, and InterfaceProtocol parameters.
    pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
        configDescriptor,
        configDescriptor,
        interfaceNumber,  //Interface number is 0.
        alternateSetting,  // Alternate Setting is 1
        -1, // InterfaceClass, ignore
        -1, // InterfaceSubClass, ignore
        -1  // InterfaceProtocol, ignore
        );

    if (pInterfaceDescriptor == NULL )
    {
        // USBD_ParseConfigurationDescriptorEx failed to retrieve Interface Descriptor.
        goto Exit;
    }

    pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR) pInterfaceDescriptor;

    for(index = 0; index < pInterfaceDescriptor->bNumEndpoints; index++)
    {

        pCommonDescriptorHeader = USBD_ParseDescriptors(
            configDescriptor,
            configDescriptor->wTotalLength,
            pCommonDescriptorHeader,
            USB_ENDPOINT_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader == NULL)
        {
            // USBD_ParseDescriptors failed to retrieve Endpoint Descriptor unexpectedly.
            goto Exit;
        }

        pEndpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) pCommonDescriptorHeader;

        // Search an Endpoint Descriptor that matches the EndpointAddress
        if (pEndpointDescriptor->bEndpointAddress == pipeInfo.EndpointAddress)
        {

            found = TRUE;
            break;
        }

        // Skip the current Endpoint Descriptor and search for the next.
        pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)pCommonDescriptorHeader)
            + pCommonDescriptorHeader->bLength);
    }

    if (found)
    {
        // Locate the SuperSpeed Endpoint Companion Descriptor
        // associated with the endpoint descriptor
        pCommonDescriptorHeader = USBD_ParseDescriptors (
            configDescriptor,
            configDescriptor->wTotalLength,
            pEndpointDescriptor,
            USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader != NULL)
        {
            pEndpointCompanionDescriptor =
                (PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR) pCommonDescriptorHeader;

            maxStreams = pEndpointCompanionDescriptor->bmAttributes.Bulk.MaxStreams;

            if (maxStreams == 0)
            {
                pipeContext->MaxStreamsSupported = 0;
                pipeContext->IsStreamsCapable = FALSE;
            }
            else
            {
                pipeContext->IsStreamsCapable = TRUE;
                pipeContext->MaxStreamsSupported = 1 << maxStreams;
            }
        }
        else
        {
            KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                "USBD_ParseDescriptors failed to retrieve SuperSpeed Endpoint Companion Descriptor unexpectedly.\n" ));
        }
    }
    else
    {
        pipeContext->MaxStreamsSupported = 0;
        pipeContext->IsStreamsCapable = FALSE;
    }

Exit:
    if (configDescriptor)
    {
        ExFreePoolWithTag (configDescriptor, USBCLIENT_TAG);
    }

    return;
}

Obtention de handles de canal dans un pilote client UMDF

Un pilote client UMDF utilise l’infrastructure COM et implémente des classes de rappel COM associées à des objets d’appareil framework. Comme pour un pilote KMDF, un pilote client UMDF ne peut obtenir des informations de canal qu’une fois l’appareil configuré. Pour obtenir des informations sur le canal, le pilote client doit obtenir un pointeur vers l’interface IWDFUsbTargetPipe de l’objet d’interface framework qui contient le paramètre actif. À l’aide du pointeur d’interface, le pilote peut énumérer les canaux dans ce paramètre pour obtenir des pointeurs d’interface IWDFUsbTargetPipe exposés par les objets de canal cible du framework.

Avant que le pilote ne commence à énumérer les canaux, il doit connaître la configuration de l’appareil et les points de terminaison pris en charge. Sur la base de ces informations, le pilote peut stocker des objets de canal en tant que variables membres de classe.

L’exemple de code suivant étend le modèle UMDF USB fourni avec Visual Studio Professional 2012. Pour obtenir une explication du code de démarrage, consultez « Implémentation IPnpCallbackHardware et tâches spécifiques à l’USB » dans Présentation de la structure de code du pilote client USB (UMDF).

Étendez la déclaration de classe CDevice comme illustré ici. Cet exemple de code suppose que l’appareil est la carte OSR FX2. Pour plus d’informations sur sa disposition de descripteur, consultez Disposition des périphériques USB.

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:
    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:
    IWDFDevice *            m_FxDevice;
    CMyIoQueue *            m_IoQueue;
    IWDFUsbTargetDevice *   m_FxUsbDevice;
    IWDFUsbInterface *      m_pIUsbInterface;  //Pointer to the target interface object.
    IWDFUsbTargetPipe *     m_pIUsbInputPipe;  // Pointer to the target pipe object for the bulk IN endpoint.
    IWDFUsbTargetPipe *     m_pIUsbOutputPipe; // Pointer to the target pipe object for the bulk OUT endpoint.
    IWDFUsbTargetPipe *     m_pIUsbInterruptPipe; // Pointer to the target pipe object for the interrupt endpoint.

private:
    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:
    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );

    HRESULT                     // Declare a helper function to enumerate pipes.
    ConfigureUsbPipes(
        );

public:
    // IPnpCallbackHardware methods
    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

Dans la définition de classe CDevice, implémentez une méthode d’assistance appelée CreateUsbIoTargets. Cette méthode est appelée à partir de l’implémentation IPnpCallbackHardware ::OnPrepareHardware après que le pilote a obtenu un pointeur vers l’objet d’appareil cible.

HRESULT  CMyDevice::CreateUsbIoTargets()
{
    HRESULT                 hr;
    UCHAR                   NumEndPoints = 0;
    IWDFUsbInterface *      pIUsbInterface = NULL;
    IWDFUsbTargetPipe *     pIUsbPipe = NULL;

    if (SUCCEEDED(hr))
    {
        UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces();

        WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces);

        hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface);
        if (FAILED(hr))
        {
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Unable to retrieve USB interface from USB Device I/O Target %!HRESULT!",
                        hr
                        );
        }
        else
        {
            m_pIUsbInterface = pIUsbInterface;

            DriverSafeRelease (pIUsbInterface); //release creation reference
        }
     }

    if (SUCCEEDED(hr))
    {
        NumEndPoints = pIUsbInterface->GetNumEndPoints();

        if (NumEndPoints != NUM_OSRUSB_ENDPOINTS)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Has %d endpoints, expected %d, returning %!HRESULT! ",
                        NumEndPoints,
                        NUM_OSRUSB_ENDPOINTS,
                        hr
                        );
        }
    }

    if (SUCCEEDED(hr))
    {
        for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++)
        {
            hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex,
                                                  &pIUsbPipe);

            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_ERROR,
                            TEST_TRACE_DEVICE,
                            "%!FUNC! Unable to retrieve USB Pipe for PipeIndex %d, %!HRESULT!",
                            PipeIndex,
                            hr
                            );
            }
            else
            {
                if ( pIUsbPipe->IsInEndPoint() )
                {
                    if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInterruptPipe = pIUsbPipe;
                    }
                    else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInputPipe = pIUsbPipe;
                    }
                    else
                    {
                        pIUsbPipe->DeleteWdfObject();
                    }
                }
                else if ( pIUsbPipe->IsOutEndPoint() && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) )
                {
                    m_pIUsbOutputPipe = pIUsbPipe;
                }
                else
                {
                    pIUsbPipe->DeleteWdfObject();
                }

                DriverSafeRelease(pIUsbPipe);  //release creation reference
            }
        }

        if (NULL == m_pIUsbInputPipe || NULL == m_pIUsbOutputPipe)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Input or output pipe not found, returning %!HRESULT!",
                        hr
                        );
        }
    }

    return hr;
}

Dans UMDF, le pilote client utilise un index de canal pour envoyer des demandes de transfert de données. Un index de canal est un nombre attribué par la pile de pilotes USB lorsqu’elle ouvre des canaux pour les points de terminaison dans un paramètre. Pour obtenir l’index de canal, appelez la méthode**IWDFUsbTargetPipe ::GetInformation**. La méthode remplit une structure WINUSB_PIPE_INFORMATION . La valeur PipeId indique l’index de canal.

Une façon d’effectuer des opérations de lecture et d’écriture sur le canal cible consiste à appeler IWDFUsbInterface ::GetWinUsbHandle pour obtenir un handle WinUSB, puis à appeler des fonctions WinUSB. Par exemple, le pilote peut appeler la fonction WinUsb_ReadPipe ou WinUsb_WritePipe . Dans ces appels de fonction, le pilote doit spécifier l’index de canal. Pour plus d’informations, consultez Comment accéder à un périphérique USB à l’aide de fonctions WinUSB.

Handles de canal pour les pilotes clients WDM

Une fois la configuration sélectionnée, la pile de pilotes USB configure un canal vers chacun des points de terminaison de l’appareil. La pile de pilotes USB retourne un tableau de structures USBD_PIPE_INFORMATION . Le nombre d’éléments dans le tableau dépend du nombre de points de terminaison définis pour le paramètre alternatif actif d’une interface dans la configuration sélectionnée. Chaque élément contient des informations sur le canal créé pour un point de terminaison particulier. Pour plus d’informations sur l’obtention de poignées de canal, consultez Comment sélectionner une configuration pour un périphérique USB.

Pour générer une demande de transfert d’E/S, le pilote client doit disposer d’un handle vers le canal associé à ce point de terminaison. Le pilote client peut obtenir le handle de canal à partir du membre PipeHandle de USBD_PIPE_INFORMATION dans le tableau.

En plus de la poignée de canal, le pilote client nécessite également le type de canal. Le pilote client peut déterminer le type de canal en examinant le membre PipeType .

En fonction du type de point de terminaison, la pile de pilotes USB prend en charge différents types de canaux. Le pilote client peut déterminer le type de canal en examinant le membre PipeType de USBD_PIPE_INFORMATION. Les différents types de canaux nécessitent différents types de blocs de requête USB (URB) pour effectuer des transactions d’E/S.

Le pilote client envoie ensuite l’URB à la pile de pilotes USB. La pile de pilotes USB traite la demande et envoie les données spécifiées au canal cible demandé.

L’URB contient des informations sur la requête, telles que le handle de canal cible, la mémoire tampon de transfert et sa longueur. Chaque structure au sein de l’union URB partage certains membres : TransferFlags, TransferBuffer, TransferBufferLength et TransferBufferMDL. Il existe des indicateurs spécifiques au type dans le membre TransferFlags qui correspondent à chaque type URB. Pour tous les URI de transfert de données, l’indicateur USBD_TRANSFER_DIRECTION_IN dans TransferFlags spécifie la direction du transfert. Les pilotes clients définissent l’indicateur USBD_TRANSFER_DIRECTION_IN pour lire les données de l’appareil. Les pilotes effacent cet indicateur pour envoyer des données à l’appareil. Les données peuvent être lues à partir d’un tampon résident en mémoire ou dans un MDL ou dans une mémoire tampon. Dans les deux cas, le pilote spécifie la taille de la mémoire tampon dans le membre TransferBufferLength . Le pilote fournit une mémoire tampon résidente dans le membre TransferBuffer et une MDL dans le membre TransferBufferMDL . Quel que soit le pilote fourni, l’autre doit avoir la valeur NULL.