Descripteurs de configuration USB
Un périphérique USB expose ses fonctionnalités sous la forme d’une série d’interfaces appelée configuration USB. Chaque interface se compose d’un ou de plusieurs autres paramètres, et chaque autre paramètre est composé d’un ensemble de points de terminaison. Cette rubrique décrit les différents descripteurs associés à une configuration USB.
Une configuration USB est décrite dans un descripteur de configuration (voir structure USB_CONFIGURATION_DESCRIPTOR ). Un descripteur de configuration contient des informations sur la configuration et ses interfaces, les autres paramètres et leurs points de terminaison. Chaque descripteur d’interface ou paramètre de remplacement est décrit dans une structure USB_INTERFACE_DESCRIPTOR . Dans une configuration, chaque descripteur d’interface est suivi en mémoire par tous les descripteurs de point de terminaison pour l’interface et un autre paramètre. Chaque descripteur de point de terminaison est stocké dans une structure USB_ENDPOINT_DESCRIPTOR .
Par exemple, considérez un périphérique de webcam USB décrit dans Disposition des périphériques USB. L’appareil prend en charge une configuration avec deux interfaces, et la première interface (index 0) prend en charge deux autres paramètres.
L’exemple suivant montre le descripteur de configuration pour le périphérique de webcam USB :
Configuration Descriptor:
wTotalLength: 0x02CA
bNumInterfaces: 0x02
bConfigurationValue: 0x01
iConfiguration: 0x00
bmAttributes: 0x80 (Bus Powered )
MaxPower: 0xFA (500 mA)
Le champ bConfigurationValue indique le numéro de la configuration définie dans le microprogramme de l’appareil. Le pilote client utilise cette valeur de nombre pour sélectionner une configuration active. Pour plus d’informations sur la configuration d’un périphérique USB, consultez Comment sélectionner une configuration pour un périphérique USB. Une configuration USB indique également certaines caractéristiques d’alimentation. Le bmAttributes contient un masque de bits qui indique si la configuration prend en charge la fonctionnalité de mise en éveil à distance et si l’appareil est alimenté par le bus ou auto-alimenté. Le champ MaxPower spécifie la puissance maximale (en milliampères) que l’appareil peut tirer de l’hôte, lorsque l’appareil est alimenté par le bus. Le descripteur de configuration indique également le nombre total d’interfaces (bNumInterfaces) prises en charge par l’appareil.
L’exemple suivant montre le descripteur d’interface pour l’autre paramètre 0 de l’interface 0 pour l’appareil webcam :
Interface Descriptor:
bInterfaceNumber: 0x00
bAlternateSetting: 0x00
bNumEndpoints: 0x01
bInterfaceClass: 0x0E
bInterfaceSubClass: 0x02
bInterfaceProtocol: 0x00
iInterface: 0x02
0x0409: "Microsoft LifeCam VX-5000"
0x0409: "Microsoft LifeCam VX-5000"
Dans l’exemple précédent, notez les valeurs de champ bInterfaceNumber et bAlternateSetting . Ces champs contiennent des valeurs d’index qu’un pilote client utilise pour activer l’interface et l’un de ses autres paramètres. Pour l’activation, le pilote envoie une demande d’interface de sélection à la pile de pilotes USB. La pile de pilotes génère ensuite une demande de contrôle standard (SET INTERFACE) et l’envoie à l’appareil. Notez le champ bInterfaceClass . Le descripteur d’interface ou le descripteur de l’un de ses autres paramètres spécifie un code de classe, une sous-classe et un protocole. La valeur de 0x0E indique que l’interface est pour la classe d’appareil vidéo. Notez également le champ iInterface . Cette valeur indique que deux descripteurs de chaîne sont ajoutés au descripteur d’interface. Les descripteurs de chaîne contiennent des descriptions Unicode utilisées lors de l’énumération de l’appareil pour identifier les fonctionnalités. Pour plus d’informations sur les descripteurs de chaîne, consultez Descripteurs de chaîne USB.
Chaque point de terminaison, dans une interface, décrit un flux unique d’entrée ou de sortie pour l’appareil. Un appareil qui prend en charge les flux pour différents types de fonctions a plusieurs interfaces. Un appareil qui prend en charge plusieurs flux qui se rapportent à une fonction peut prendre en charge plusieurs points de terminaison sur une seule interface.
Tous les types de points de terminaison (à l’exception du point de terminaison par défaut) doivent fournir des descripteurs de point de terminaison afin que l’hôte puisse obtenir des informations sur le point de terminaison. Un descripteur de point de terminaison inclut des informations, telles que son adresse, son type, sa direction et la quantité de données que le point de terminaison peut gérer. Les transferts de données vers le point de terminaison sont basés sur ces informations.
L’exemple suivant montre un descripteur de point de terminaison pour l’appareil webcam :
Endpoint Descriptor:
bEndpointAddress: 0x82 IN
bmAttributes: 0x01
wMaxPacketSize: 0x0080 (128)
bInterval: 0x01
Le champ bEndpointAddress spécifie l’adresse de point de terminaison unique qui contient le numéro de point de terminaison (Bits 3..0) et la direction du point de terminaison (Bit 7). En lisant ces valeurs dans l’exemple précédent, nous pouvons déterminer que le descripteur décrit un point de terminaison IN dont le numéro de point de terminaison est 2. L’attribut bmAttributes indique que le type de point de terminaison est isochronieux. WMaxPacketSizefield indique le nombre maximal d’octets que le point de terminaison peut envoyer ou recevoir dans une seule transaction. Les bits 12..11 indiquent le nombre total de transactions pouvant être envoyées par microframe. BInterval indique la fréquence à laquelle le point de terminaison peut envoyer ou recevoir des données.
Comment obtenir le descripteur de configuration
Le descripteur de configuration est obtenu à partir de l’appareil via une demande d’appareil standard (GET_DESCRIPTOR), qui est envoyée en tant que transfert de contrôle par la pile de pilotes USB. Un pilote client USB peut lancer la demande de l’une des manières suivantes :
Si l’appareil ne prend en charge qu’une seule configuration, le moyen le plus simple consiste à appeler la méthode WdfUsbTargetDeviceRetrieveConfigDescriptor fournie par l’infrastructure .
Pour un appareil qui prend en charge plusieurs configurations, si le pilote client souhaite obtenir le descripteur de la configuration autre que le premier, le pilote doit envoyer un URB. Pour envoyer un URB, le pilote doit allouer, mettre en forme, puis envoyer l’URB à la pile de pilotes USB.
Pour allouer l’URB, le pilote client doit appeler la méthode WdfUsbTargetDeviceCreateUrb . La méthode reçoit un pointeur vers un URB alloué par la pile de pilotes USB.
Pour mettre en forme l’URB, le pilote client peut utiliser la macro UsbBuildGetDescriptorRequest . La macro définit toutes les informations nécessaires dans l’URB, telles que le numéro de configuration défini par l’appareil pour lequel récupérer le descripteur. La fonction URB a la valeur URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (voir _URB_CONTROL_DESCRIPTOR_REQUEST) et le type de descripteur est défini sur USB_CONFIGURATION_DESCRIPTOR_TYPE. En utilisant les informations contenues dans l’URB, la pile de pilotes USB génère une demande de contrôle standard et l’envoie à l’appareil.
Pour envoyer l’URB, le pilote client doit utiliser un objet de requête WDF. Pour envoyer l’objet de requête à la pile de pilotes USB de manière asynchrone, le pilote doit appeler la méthode **WdfRequestSend**. Pour l’envoyer de manière synchrone, appelez la méthode WdfUsbTargetDeviceSendUrbSynchronously .
Pilotes WDM : Un pilote client WDM (Windows Driver Model) ne peut obtenir le descripteur de configuration qu’en envoyant un URB. Pour allouer l’URB, le pilote doit appeler la routine USBD_UrbAllocate . Pour mettre en forme l’URB, le pilote doit appeler la macro UsbBuildGetDescriptorRequest . Pour envoyer l’URB, le pilote doit associer l’URB à un IRP et envoyer l’IRP à la pile de pilotes USB. Pour plus d’informations, consultez Comment envoyer un URB.
Dans une configuration USB, le nombre d’interfaces et leurs autres paramètres sont variables. Par conséquent, il est difficile de prédire la taille de la mémoire tampon requise pour contenir le descripteur de configuration. Le pilote client doit collecter toutes ces informations en deux étapes. Tout d’abord, déterminez la taille de mémoire tampon requise pour contenir tout le descripteur de configuration, puis émettez une demande pour récupérer l’intégralité du descripteur. Un pilote client peut obtenir la taille de l’une des manières suivantes :
Pour obtenir le descripteur de configuration en appelant WdfUsbTargetDeviceRetrieveConfigDescriptor, procédez comme suit :
- Obtenez la taille de mémoire tampon requise pour contenir toutes les informations de configuration en appelant WdfUsbTargetDeviceRetrieveConfigDescriptor. Le pilote doit passer la valeur NULL dans la mémoire tampon et une variable pour contenir la taille de la mémoire tampon.
- Allouez une mémoire tampon plus grande en fonction de la taille reçue via l’appel WdfUsbTargetDeviceRetrieveConfigDescriptor précédent.
- Appelez à nouveau WdfUsbTargetDeviceRetrieveConfigDescriptor et spécifiez un pointeur vers la nouvelle mémoire tampon allouée à l’étape 2.
NTSTATUS RetrieveDefaultConfigurationDescriptor (
_In_ WDFUSBDEVICE UsbDevice,
_Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor
)
{
NTSTATUS ntStatus = -1;
USHORT sizeConfigDesc;
PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;
PAGED_CODE();
*ConfigDescriptor = NULL;
ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
UsbDevice,
NULL,
&sizeConfigDesc);
if (sizeConfigDesc == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor size.");
goto Exit;
}
else
{
fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
NonPagedPool,
sizeConfigDesc,
USBCLIENT_TAG);
if (!fullConfigDesc)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
}
RtlZeroMemory (fullConfigDesc, sizeConfigDesc);
ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
UsbDevice,
fullConfigDesc,
&sizeConfigDesc);
if (!NT_SUCCESS(ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor.");
goto Exit;
}
*ConfigDescriptor = fullConfigDesc;
Exit:
return ntStatus;
}
Pour obtenir le descripteur de configuration en soumettant un URB, procédez comme suit :
- Allouez un URB en appelant la méthode WdfUsbTargetDeviceCreateUrb .
- Mettez en forme l’URB en appelant la macro UsbBuildGetDescriptorRequest . La mémoire tampon de transfert de l’URB doit pointer vers une mémoire tampon suffisamment grande pour contenir une structure USB_CONFIGURATION_DESCRIPTOR .
- Envoyez l’URB en tant qu’objet de requête WDF en appelant WdfRequestSend ou WdfUsbTargetDeviceSendUrbSynchronously.
- Une fois la demande terminée, case activée le membre wTotalLength de USB_CONFIGURATION_DESCRIPTOR. Cette valeur indique la taille de la mémoire tampon requise pour contenir un descripteur de configuration complet.
- Allouez une mémoire tampon plus grande en fonction de la taille récupérée dans wTotalLength.
- Émettez la même demande avec la mémoire tampon plus grande.
L’exemple de code suivant montre l’appel UsbBuildGetDescriptorRequest pour une demande d’obtention d’informations de configuration pour la configuration i-th :
NTSTATUS FX3_RetrieveConfigurationDescriptor (
_In_ WDFUSBDEVICE UsbDevice,
_In_ PUCHAR ConfigurationIndex,
_Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor
)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
USB_CONFIGURATION_DESCRIPTOR configDesc;
PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;
PURB urb = NULL;
WDFMEMORY urbMemory = NULL;
PAGED_CODE();
RtlZeroMemory (&configDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
*ConfigDescriptor = NULL;
// Allocate an URB for the get-descriptor request.
// WdfUsbTargetDeviceCreateUrb returns the address of the
// newly allocated URB and the WDFMemory object that
// contains the URB.
ntStatus = WdfUsbTargetDeviceCreateUrb (
UsbDevice,
NULL,
&urbMemory,
&urb);
if (!NT_SUCCESS (ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate URB for an open-streams request.");
goto Exit;
}
// Format the URB.
UsbBuildGetDescriptorRequest (
urb, // Points to the URB to be formatted
(USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ), // Size of the URB.
USB_CONFIGURATION_DESCRIPTOR_TYPE, // Type of descriptor
*ConfigurationIndex, // Index of the configuration
0, // Not used for configuration descriptors
&configDesc, // Points to a USB_CONFIGURATION_DESCRIPTOR structure
NULL, // Not required because we are providing a buffer not MDL
sizeof(USB_CONFIGURATION_DESCRIPTOR), // Size of the USB_CONFIGURATION_DESCRIPTOR structure.
NULL // Reserved.
);
// Send the request synchronously.
ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
UsbDevice,
NULL,
NULL,
urb);
if (configDesc.wTotalLength == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor size.");
ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;
goto Exit;
}
// Allocate memory based on the retrieved size.
// The allocated memory is released by the caller.
fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
NonPagedPool,
configDesc.wTotalLength,
USBCLIENT_TAG);
RtlZeroMemory (fullConfigDesc, configDesc.wTotalLength);
if (!fullConfigDesc)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
// Format the URB.
UsbBuildGetDescriptorRequest (
urb,
(USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),
USB_CONFIGURATION_DESCRIPTOR_TYPE,
*ConfigurationIndex,
0,
fullConfigDesc,
NULL,
configDesc.wTotalLength,
NULL
);
// Send the request again.
ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
UsbDevice,
NULL,
NULL,
urb);
if ((fullConfigDesc->wTotalLength == 0) || !NT_SUCCESS (ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor.");
ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;
goto Exit;
}
// Return to the caller.
*ConfigDescriptor = fullConfigDesc;
Exit:
if (urbMemory)
{
WdfObjectDelete (urbMemory);
}
return ntStatus;
}
Lorsque l’appareil retourne le descripteur de configuration, la mémoire tampon de requête est remplie de descripteurs d’interface pour tous les autres paramètres et de descripteurs de point de terminaison pour tous les points de terminaison au sein d’un autre paramètre particulier. Pour l’appareil décrit dans Disposition des périphériques USB, le diagramme suivant illustre la façon dont les informations de configuration sont disposées en mémoire.
Le membre bInterfaceNumber de base zéro de USB_INTERFACE_DESCRIPTOR distingue les interfaces au sein d’une configuration. Pour une interface donnée, le membre bAlternateSetting de base zéro fait la distinction entre les autres paramètres de l’interface. L’appareil retourne des descripteurs d’interface dans l’ordre des valeurs bInterfaceNumber , puis dans l’ordre des valeurs bAlternateSetting .
Pour rechercher un descripteur d’interface donné dans la configuration, le pilote client peut appeler USBD_ParseConfigurationDescriptorEx. Dans l’appel, le pilote client fournit une position de départ dans la configuration. Si vous le souhaitez, le pilote peut spécifier un numéro d’interface, un autre paramètre, une classe, une sous-classe ou un protocole. La routine retourne un pointeur vers le descripteur d’interface correspondant suivant.
Pour examiner un descripteur de configuration pour un descripteur de point de terminaison ou de chaîne, utilisez la routine USBD_ParseDescriptors . L’appelant fournit une position de départ dans la configuration et un type de descripteur, tel que USB_STRING_DESCRIPTOR_TYPE ou USB_ENDPOINT_DESCRIPTOR_TYPE. La routine retourne un pointeur vers le descripteur correspondant suivant.