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 :
Le pilote client doit avoir créé l’objet de périphérique cible USB du framework.
Si vous utilisez les modèles USB fournis avec Microsoft Visual Studio Professional 2012, le code du modèle effectue ces tâches. Le code de modèle obtient le handle de l’objet d’appareil cible et les stocke dans le contexte de l’appareil.
Pilote client KMDF :
Un pilote client KMDF doit obtenir un handle WDFUSBDEVICE en appelant la méthode WdfUsbTargetDeviceCreateWithParameters . Pour plus d’informations, consultez « Code source de l’appareil » dans Présentation de la structure de code du pilote client USB (KMDF).
Pilote client UMDF :
Un pilote client UMDF doit obtenir un pointeur IWDFUsbTargetDevice en interrogeant l’objet d’appareil cible du framework. Pour plus d’informations, consultez « Implémentation IPnpCallbackHardware et tâches spécifiques à USB » dans Présentation de la structure de code du pilote client USB (UMDF).
L’appareil doit avoir une configuration active.
Si vous utilisez des modèles USB, le code sélectionne la première configuration et l’autre paramètre par défaut dans chaque interface. Pour plus d’informations sur la modification de ce paramètre par défaut, consultez Comment sélectionner un autre paramètre dans une interface USB.
Pilote client KMDF :
Un pilote client KMDF doit appeler la méthode WdfUsbTargetDeviceSelectConfig .
Pilote client UMDF :
Pour un pilote client UMDF, l’infrastructure sélectionne la première configuration et l’autre paramètre par défaut pour chaque interface de cette configuration.
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.
É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;
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).
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.