Partage de technologie

Développement de pilotes de périphériques USB Windows-traitement des flux statiques de transferts par lots

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Dans les appareils USB 2.0 et versions antérieures, un point de terminaison en masse peut envoyer ou recevoir un seul flux de données via le point de terminaison. Dans les appareils USB 3.0, le point de terminaison en masse est capable d'envoyer et de recevoir plusieurs flux de données via le point de terminaison.

La pile de pilotes USB fournie par Microsoft dans Windows prend en charge plusieurs flux. Cela permet aux pilotes clients d'envoyer des requêtes d'E/S indépendantes à chaque flux associé à un point de terminaison en masse dans un périphérique USB 3.0, sans sérialiser les requêtes vers différents flux.

Pour un pilote client, un flux représente plusieurs points de terminaison logiques avec le même ensemble de caractéristiques. Pour envoyer une requête à un flux spécifique, le pilote client a besoin d'un handle vers ce flux (similaire au handle de canal d'un point de terminaison). L'URB pour les requêtes d'E/S en streaming est similaire à l'URB pour les requêtes d'E/S pour les points de terminaison en masse. La seule différence est la poignée du tuyau. Pour envoyer une requête d'E/S à un flux, le pilote spécifie un handle de canal dans le flux.

Lors de la configuration du dispositif, le pilote client envoie une demande de configuration de sélection et éventuellement une demande d'interface de sélection. Ces requêtes récupèrent un ensemble de handles de canal pour les points de terminaison définis dans les paramètres d’activité de l’interface. Pour les points de terminaison prenant en charge les flux, le handle de canal du point de terminaison peut être utilisé pour envoyer des demandes d’E/S au flux par défaut (le premier flux) jusqu’à ce que le pilote ouvre le flux.

Si un pilote client souhaite envoyer des requêtes à des flux autres que le flux par défaut, le pilote doit ouvrir et obtenir des handles pour tous les flux. Pour ce faire, le pilote client envoie une requête d'ouverture de flux en précisant le nombre de flux à ouvrir. Une fois qu'un pilote client a fini d'utiliser les flux, il peut choisir de les fermer en envoyant une demande de fermeture du flux.

Kernel Mode Driver Framework (KMDF) ne prend pas en charge de manière native les flux statiques. Les pilotes clients doivent utiliser le modèle de pilote Windows (WDM) pour ouvrir et fermer les flux. Les pilotes clients UMDF (User-mode Driver Framework) ne peuvent pas utiliser la fonctionnalité de streaming statique.

Ce qui suit peut contenir des commentaires intitulés Pilotes WDM. Ces instructions décrivent les routines d'un pilote client USB basé sur WDM qui souhaite envoyer des requêtes de streaming.

conditions préalables

Avant qu'un pilote client puisse ouvrir ou fermer un flux, il doit avoir :

1. Appelez la méthode WdfUsbTargetDeviceCreateWithParameters. La méthode nécessite la version du protocole client USBD_CLIENT_CONTRACT_VERSION_602. En spécifiant cette version, le pilote client doit respecter un ensemble de règles.

Appelez pour récupérer le handle WDFUSBDEVICE de l’objet périphérique cible USB du framework. Le handle est requis pour les appels ultérieurs au flux ouvert. En règle générale, un pilote client s'enregistre dans la routine de rappel d'événement EVT_WDF_DEVICE_PREPARE_HARDWARE du pilote.

Pilote WDM : appelez la routine USBD_CreateHandle et obtenez le handle USBD enregistré par le pilote dans la pile de pilotes USB.

2. Configurez le périphérique et obtenez le handle de canal WDFUSBPIPE pour le point de terminaison en bloc qui prend en charge le streaming. Pour obtenir un handle de canal, appelez la méthode WdfUsbInterfaceGetConfiguredPipe sur les paramètres alternatifs actuels de l’interface dans la configuration sélectionnée.

Pilote WDM : obtient le descripteur de canal USBD en envoyant une requête de sélection de configuration ou d'interface de sélection.

Comment ouvrir un flux statique

1. Déterminez si la pile de pilotes USB sous-jacente et le contrôleur hôte prennent en charge la fonctionnalité de streaming statique en appelant la méthode WdfUsbTargetDeviceQueryUsbCapability. En règle générale, les pilotes clients appellent des routines dans la routine de rappel d'événement EVT_WDF_DEVICE_PREPARE_HARDWARE du pilote.

Pilote WDM : appelle la routine USBD_QueryUsbCapability. En règle générale, un pilote interroge la fonction à utiliser dans la routine de démarrage du pilote (IRP_MN_START_DEVICE).

fournissez les informations suivantes:

  • Handle de l’objet périphérique USB récupéré lors d’un appel précédent à WdfUsbTargetDeviceCreateWithParameters, utilisé pour enregistrer le pilote client.

Pilote WDM : transmettez le handle USBD récupéré lors de l’appel précédent à USBD_CreateHandle.

Si un pilote client souhaite utiliser une fonctionnalité spécifique, il doit d'abord interroger la pile de pilotes USB sous-jacente pour déterminer si la pile de pilotes et le contrôleur hôte prennent en charge la fonctionnalité. Si la fonctionnalité est prise en charge, alors seulement le pilote doit envoyer une demande pour utiliser la fonctionnalité. Certaines requêtes nécessitent des URB, comme la fonctionnalité de streaming abordée à l'étape 5. Pour ces requêtes, assurez-vous d’utiliser le même handle pour interroger la fonction et allouer l’URB. En effet, la pile de pilotes utilise des poignées pour suivre les fonctionnalités prises en charge que le pilote peut utiliser.

Par exemple, si USBD_HANDLE a été obtenu en appelant USBD_CreateHandle, la pile de pilotes est interrogée en appelant USBD_QueryUsbCapability et l'URB est alloué en appelant USBD_UrbAllocate. Transmettez le même USBD_HANDLE dans les deux appels.

Si vous appelez les méthodes KMDF, WdfUsbTargetDeviceQueryUsbCapability et WdfUsbTargetDeviceCreateUrb, spécifiez le même handle WDFUSBDEVICE pour l'objet cible du framework dans ces appels de méthode.

  • GUID attribué à GUID_USB_CAPABILITY_STATIC_STREAMS ;
  • Pointeur vers le tampon de sortie (pointant vers USHORT). Une fois terminé, le tampon sera rempli avec (nombre maximum de flux pour) chaque point de terminaison pris en charge par le contrôleur hôte ;
  • La longueur du tampon de sortie, en octets. Pour les flux, la longueur est sizeof (USORT) ;

2. Évaluez la valeur NTSTATUS renvoyée. Si la routine se termine avec succès, STATUS_SUCCESS est renvoyé et la fonctionnalité de streaming statique est prise en charge. Sinon, la méthode renvoie le code d'erreur approprié.

3. Déterminez le nombre de flux à ouvrir. Le nombre maximum de flux pouvant être ouverts est limité par :

  • Le nombre maximum de flux pris en charge par le contrôleur hôte. WdfUsbTargetDeviceQueryUsbCapability (reçoit WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) dans le tampon de sortie fourni par l’appelant. La pile de pilotes USB fournie par Microsoft prend en charge jusqu'à 255 flux. WdfUsbTargetDeviceQueryUsbCapability prend en compte cette limite lors du calcul du nombre de flux. La méthode ne renvoie jamais une valeur supérieure à 255.
  • Nombre maximum de flux pris en charge par le point de terminaison dans l'appareil. Pour obtenir ce numéro, vérifiez le descripteur compagnon du point de terminaison (dans Usbspec.h) pour USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR. Pour obtenir le descripteur compagnon du point de terminaison, le descripteur de configuration doit être analysé. Pour obtenir le descripteur de configuration, le pilote client doit appeler la méthode WdfUsbTargetDeviceRetrieveConfigDescriptor. Les routines d'assistance, USBD_ParseConfigurationDescriptorEx et USBD_ParseDescriptor, doivent être utilisées.

Pour déterminer le nombre maximum de flux, choisissez la plus petite des deux valeurs prises en charge par le contrôleur hôte et le point de terminaison.

4. Allouez un tableau de structures USBD_STREAM_INFORMATION à n éléments, où n est le nombre de flux à ouvrir. Le pilote client est responsable de la libération de ce tableau une fois que le pilote a fini d'utiliser le flux.

5. Allouez un URB pour la demande de flux ouvert en appelant la méthode WdfUsbTargetDeviceCreateUrb. Si l'appel se termine avec succès, la méthode récupère l'objet mémoire WDF et l'adresse de la structure URB allouée par la pile de pilotes USB.

Pilote WDM : appelle la routine USBD_UrbAllocate.

6. Définissez le format URB de la demande de flux ouvert. URB utilise la structure _URB_OPEN_STATIC_STREAMS pour définir les requêtes. Pour formater un URB, il vous faut :

  • Poignée de tuyau USBD pointant vers le point de terminaison. S’il existe un objet canal WDF, vous pouvez obtenir le handle du canal USBD en appelant la méthode WdfUsbTargetPipeWdmGetPipeHandle.
  • Créé à l'étape 4 (tableau de flux)
  • Pointeur vers la structure URB créée à l’étape 5.

Pour formater un URB, appelez UsbBuildOpenStaticStreamsRequest et transmettez les informations requises sous forme de valeurs de paramètres. Assurez-vous que le nombre de flux spécifié dans UsbBuildOpenStaticStreamsRequest ne dépasse pas le nombre maximum de flux pris en charge.

7. Envoyez l'URB en tant qu'objet de requête WDF en appelant la méthode WdfRequestSend. Pour envoyer la demande de manière synchrone, appelez plutôt la méthode WdfUsbTargetDeviceSendUrbSynchronously.

Pilote WDM : associe l'URB à l'IRP et soumet l'IRP à la pile de pilotes USB.

8. Une fois la demande terminée, vérifiez l'état de la demande.Si la demande de pile de pilote USB échoue, l'état URB contient le code d'erreur correspondant.

Si l'état de la requête (objet de requête IRP ou WDF) indique USBD_STATUS_SUCCESS, la requête s'est terminée avec succès. Tableau de structures USBD_STREAM_INFORMATION reçues une fois la vérification terminée. Le tableau est renseigné avec des informations sur le flux demandé. La pile de pilotes USB remplit chaque structure du tableau avec des informations de flux, telles que le handle reçu USBD_PIPE_HANDLE, l'identifiant de flux et la taille de transfert numérique maximale. Le flux peut désormais transférer des données.

Pour les requêtes de flux ouvert, les URB et les tableaux doivent être alloués. Une fois la demande d'ouverture de flux terminée, le pilote client doit libérer l'URB en appelant la méthode WdfObjectDelete sur l'objet mémoire WDF associé. Si le pilote envoie la demande de manière synchrone en appelant WdfUsbTargetDeviceSendUrbSynchronously, l'objet mémoire WDF doit être libéré après le retour de la méthode. Si un pilote client envoie une demande de manière asynchrone en appelant WdfRequestSend, le pilote doit libérer l'objet mémoire WDF dans la routine d'achèvement implémentée par le pilote associée à la demande.

Le tableau de flux peut être libéré une fois que le pilote client a fini d'utiliser le flux, ou il peut être stocké pour les requêtes d'E/S. Dans l'exemple de code inclus ci-dessous, le pilote stocke un tableau de flux dans le contexte du périphérique. Le pilote libère le contexte du périphérique avant de libérer l'objet périphérique.

Comment transférer des données vers un flux spécifique
Pour envoyer une demande de transfert de données à un flux spécifique, un objet de requête WDF est requis. En règle générale, les pilotes clients n'ont pas besoin d'attribuer des objets de requête WDF. Lorsque le gestionnaire d'E/S reçoit une demande d'une application, il crée un IRP pour la demande. L'IRP a été intercepté par le framework. Le framework alloue ensuite un objet de requête WDF pour représenter l'IRP. Ensuite, le framework transmet l'objet de requête WDF au pilote client. Le pilote client peut ensuite associer l'objet de requête à l'URB de transfert de données et l'envoyer à la pile de pilotes USB.

Si un pilote client ne reçoit pas d'objet de requête WDF du framework et souhaite envoyer la requête de manière asynchrone, le pilote doit allouer un objet de requête WDF en appelant la méthode WdfRequestCreate. Formatez le nouvel objet en appelant WdfUsbTargetPipeFormatRequestForUrb et envoyez la demande en appelant WdfRequestSend.

Dans le cas synchrone, la transmission de l'objet de requête WDF est facultative.

Pour transférer des données vers un flux, URB doit être utilisé. L'URB doit être formaté en appelant WdfUsbTargetPipeFormatRequestForUrb.

Les flux ne prennent pas en charge les méthodes WDF suivantes :

  • Demande de formatage WdfUsbTargetPipeForRead
  • Demande d'écriture de format de tuyau cible WdfUsb
  • WdfUsbTargetPipeReadSynchronously
  • WdfUsbTargetPipeWriteSynchronously

La procédure suivante suppose que le pilote client reçoit l'objet de requête du framework.

  1. Allouez l’URB en appelant WdfUsbTargetDeviceCreateUrb. Cette méthode alloue un objet mémoire WDF contenant l'URB nouvellement alloué. Les pilotes clients peuvent choisir d'attribuer un URB pour chaque requête d'E/S, ou d'attribuer un URB et de l'utiliser pour des requêtes du même type.
  2. Formatez l'URB pour le transfert en masse en appelant UsbBuildInterruptOrBulkTransferRequest. Dans le paramètre PipeHandle, spécifiez le handle du flux. Le handle de flux est obtenu dans la requête précédente, comme décrit dans la section Comment ouvrir un flux statique.
  3. Formatez l'objet de requête WDF en appelant la méthode WdfUsbTargetPipeFormatRequestForUrb. Dans l'appel, spécifiez l'objet mémoire WDF contenant l'URB de transfert de données. L'objet mémoire a été alloué à l'étape 1.
  4. Envoyez l'URB sous forme de requête WDF en appelant WdfRequestSend ou WdfUsbTargetPipeSendUrbSynchronously. Si vous appelez WdfRequestSend, vous devez spécifier une routine d'achèvement en appelant WdfRequestSetCompletionRoutine afin que le pilote client puisse être averti lorsque l'opération asynchrone se termine. L'URB de transfert de données doit être libéré dans la routine d'achèvement.

Pilote WDM : alloue un URB en appelant USBD_UrbAllocate et le formate pour le transfert en masse (voir _URB_BULK_OR_INTERRUPT_TRANSFER). Pour formater un URB, vous pouvez appeler UsbBuildInterruptOrBulkTransferRequest ou formater manuellement la structure URB. Spécifiez le handle du flux dans le membre UrbBulkOrInterruptTransfer.PipeHandle de l’URB.

Comment fermer un flux statique

Le pilote client peut fermer le flux une fois qu'il a fini de l'utiliser. Cependant, la fermeture de la demande de streaming est facultative. La pile de pilotes USB ferme tous les flux lorsque le point de terminaison associé au flux n'est pas configuré. Les points de terminaison ne sont pas configurés lors de la sélection d'une autre configuration ou interface, de la suppression d'un appareil, etc. Si le pilote client souhaite ouvrir un nombre différent de flux, il doit fermer les flux. Envoyez une demande de fermeture de flux :

1. Allouez la structure URB en appelant WdfUsbTargetDeviceCreateUrb.

2. Définissez le format URB pour fermer la demande de flux. Le membre UrbPipeRequest de la structure URB est la structure _URB_PIPE_REQUEST. Remplissez ses membres comme suit :

  • Membre Hdr requis de URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST
  • Le membre PipeHandle doit être un handle qui contient le point de terminaison utilisé pour ouvrir le flux.

3. Envoyez l'URB sous forme de requête WDF en appelant WdfRequestSend ou WdfUsbTargetDeviceSendUrbSynchronously.

Le handle de fermeture demande la fermeture de tous les flux précédemment ouverts par le pilote client. Le pilote client ne peut pas utiliser de requêtes pour fermer un flux spécifique dans le point de terminaison.

Bonnes pratiques pour l'envoi de requêtes de streaming statiques

La pile de pilotes USB effectue une vérification sur l'URB reçu. Pour éviter les erreurs de validation, procédez comme suit :

  • N'envoyez pas de requêtes d'ouverture ou de fermeture de flux à des points de terminaison qui ne prennent pas en charge les flux. Appelez WdfUsbTargetDeviceQueryUsbCapability (, USBD_QueryUsbCapability) du pilote WDM pour déterminer la prise en charge du streaming statique et envoyez des requêtes de streaming uniquement si le point de terminaison le prend en charge.
  • Ne demandez pas plus de flux (ouverts) que le nombre maximum de flux pris en charge, et n'envoyez pas de demande sans spécifier le nombre de flux. Le nombre de flux est déterminé en fonction du nombre de flux pris en charge par la pile de pilotes USB et le point de terminaison du périphérique.
  • N'envoyez pas de demande de flux ouvert à un point de terminaison qui dispose déjà d'un flux ouvert.
  • N'envoyez pas de demande de fermeture de flux à un point de terminaison qui n'a pas de flux ouvert.
  • Après avoir ouvert un flux statique pour un point de terminaison, n’envoyez pas de requêtes d’E/S à l’aide du handle de canal de point de terminaison obtenu via une configuration de sélection ou une demande d’interface de sélection. Cela est vrai même si le flux statique est fermé.
Réinitialiser et abandonner les opérations de pipeline

Parfois, les transferts vers ou depuis un point de terminaison peuvent échouer. De tels échecs peuvent être provoqués par des conditions d'erreur sur le point final ou le contrôleur hôte, telles qu'une condition d'arrêt ou d'arrêt. Pour effacer la condition d'erreur, le pilote client annule d'abord le transfert en attente, puis réinitialise le canal associé au point de terminaison. Pour annuler un transfert en attente, le pilote client peut envoyer une demande d'abandon du canal. Pour réinitialiser un canal, le pilote client doit envoyer une demande de réinitialisation du canal.

Pour le streaming, les requêtes d'abandon et de réinitialisation ne sont pas prises en charge pour les flux individuels associés au point de terminaison en masse. Si un transfert sur un canal de flux spécifique échoue, le contrôleur hôte arrêtera les transferts sur tous les autres canaux pour les autres flux. Pour récupérer de la condition d'erreur, le pilote client doit annuler manuellement le transfert vers chaque flux. un handle de canal pour envoyer une demande de réinitialisation de canal au point de terminaison en bloc. Pour cette demande, le pilote client doit spécifier le handle de canal du point de terminaison dans la structure _URB_PIPE_REQUEST et définir la fonction URB (Hdr.Function) sur URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.

Exemple complet

L'exemple de code suivant montre comment ouvrir un flux.

  1. NTSTATUS
  2. OpenStreams (
  3. _In_ WDFDEVICE Device,
  4. _In_ WDFUSBPIPE Pipe)
  5. {
  6. NTSTATUS status;
  7. PDEVICE_CONTEXT deviceContext;
  8. PPIPE_CONTEXT pipeContext;
  9. USHORT cStreams = 0;
  10. USBD_PIPE_HANDLE usbdPipeHandle;
  11. WDFMEMORY urbMemory = NULL;
  12. PURB urb = NULL;
  13. PAGED_CODE();
  14. deviceContext =GetDeviceContext(Device);
  15. pipeContext = GetPipeContext (Pipe);
  16. if (deviceContext->MaxStreamsController == 0)
  17. {
  18. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  19. "%!FUNC! Static streams are not supported.");
  20. status = STATUS_NOT_SUPPORTED;
  21. goto Exit;
  22. }
  23. // If static streams are not supported, number of streams supported is zero.
  24. if (pipeContext->MaxStreamsSupported == 0)
  25. {
  26. status = STATUS_DEVICE_CONFIGURATION_ERROR;
  27. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  28. "%!FUNC! Static streams are not supported by the endpoint.");
  29. goto Exit;
  30. }
  31. // Determine the number of streams to open.
  32. // Compare the number of streams supported by the endpoint with the
  33. // number of streams supported by the host controller, and choose the
  34. // lesser of the two values. The deviceContext->MaxStreams value was
  35. // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
  36. // that determined whether or not static streams is supported and
  37. // retrieved the maximum number of streams supported by the
  38. // host controller. The device context stores the values for IN and OUT
  39. // endpoints.
  40. // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
  41. // The number of elements in the array is the number of streams to open.
  42. // The code snippet stores the array in its device context.
  43. cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
  44. // Allocate an array of streams associated with the IN bulk endpoint
  45. // This array is released in CloseStreams.
  46. pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
  47. NonPagedPool,
  48. sizeof (USBD_STREAM_INFORMATION) * cStreams,
  49. USBCLIENT_TAG);
  50. if (pipeContext->StreamInfo == NULL)
  51. {
  52. status = STATUS_INSUFFICIENT_RESOURCES;
  53. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  54. "%!FUNC! Could not allocate stream information array.");
  55. goto Exit;
  56. }
  57. RtlZeroMemory (pipeContext->StreamInfo,
  58. sizeof (USBD_STREAM_INFORMATION) * cStreams);
  59. // Get USBD pipe handle from the WDF target pipe object. The client driver received the
  60. // endpoint pipe handles during device configuration.
  61. usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
  62. // Allocate an URB for the open streams request.
  63. // WdfUsbTargetDeviceCreateUrb returns the address of the
  64. // newly allocated URB and the WDFMemory object that
  65. // contains the URB.
  66. status = WdfUsbTargetDeviceCreateUrb (
  67. deviceContext->UsbDevice,
  68. NULL,
  69. &urbMemory,
  70. &urb);
  71. if (status != STATUS_SUCCESS)
  72. {
  73. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  74. "%!FUNC! Could not allocate URB for an open-streams request.");
  75. goto Exit;
  76. }
  77. // Format the URB for the open-streams request.
  78. // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
  79. // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
  80. UsbBuildOpenStaticStreamsRequest (
  81. urb,
  82. usbdPipeHandle,
  83. (USHORT)cStreams,
  84. pipeContext->StreamInfo);
  85. // Send the request synchronously.
  86. // Upon completion, the USB driver stack populates the array of with handles to streams.
  87. status = WdfUsbTargetPipeSendUrbSynchronously (
  88. Pipe,
  89. NULL,
  90. NULL,
  91. urb);
  92. if (status != STATUS_SUCCESS)
  93. {
  94. goto Exit;
  95. }
  96. Exit:
  97. if (urbMemory)
  98. {
  99. WdfObjectDelete (urbMemory);
  100. }
  101. return status;
  102. }