Écrire un pilote client UDE
Cet article décrit le comportement de l’extension de classe UDE (emulation de périphérique USB) et les tâches qu’un pilote client doit effectuer pour un contrôleur hôte émulé et les appareils qui y sont attachés. Il fournit des informations sur la façon dont le pilote de classe et l’extension de classe communiquent avec chacun par le biais d’un ensemble de routines et de fonctions de rappel. Il décrit également les fonctionnalités que le pilote client est censé implémenter.
Résumé
- Objets et handles UDE utilisés par l’extension de classe et le pilote client.
- Création d’un contrôleur hôte émulé avec des fonctionnalités permettant d’interroger les fonctionnalités du contrôleur et de réinitialiser le contrôleur.
- Création d’un périphérique USB virtuel, configuration pour la gestion de l’alimentation et les transferts de données via des points de terminaison.
API importantes
Avant de commencer
- Installez la dernière version du Kit de pilotes Windows (WDK) sur votre ordinateur de développement. Le kit contient les fichiers d’en-tête et les bibliothèques requis pour l’écriture d’un pilote client UDE. En particulier, vous aurez besoin des éléments suivants :
- Bibliothèque stub, (Udecxstub.lib). La bibliothèque traduit les appels effectués par le pilote client et les transmet à UdeCx.
- Fichier d’en-tête, Udecx.h.
- Installez Windows 10 sur votre ordinateur cible.
- Familiarisez-vous avec l’UDE. Consultez Architecture : Émulation de périphérique USB (UDE).
- Familiarisez-vous avec Windows Driver Foundation (WDF). Lecture recommandée : Développement de pilotes avec Windows Driver Foundation, écrit par Penny Orwick et Guy Smith.
Objets et handles UDE
L’extension de classe UDE et le pilote client utilisent des objets WDF particuliers qui représentent le contrôleur hôte émulé et l’appareil virtuel, y compris ses points de terminaison et ses URB utilisés pour transférer des données entre l’appareil et l’hôte. Le pilote client demande la création des objets et la durée de vie de l’objet est gérée par l’extension de classe.
Objet de contrôleur d’hôte émulé (WDFDEVICE)
Représente le contrôleur d’hôte émulé et est le main handle entre l’extension de classe UDE et le pilote client.
Objet d’appareil UDE (UDECXUSBDEVICE)
Représente un périphérique USB virtuel connecté à un port sur le contrôleur hôte émulé.
Objet de point de terminaison UDE (UDECXUSBENDPOINT)
Représente les canaux de données séquentiels des périphériques USB. Utilisé pour recevoir des demandes logicielles d’envoi ou de réception de données sur un point de terminaison.
Initialiser le contrôleur hôte émulé
Voici le résumé de la séquence dans laquelle le pilote client récupère un handle WDFDEVICE pour le contrôleur hôte émulé. Nous recommandons au pilote d’effectuer ces tâches dans sa fonction de rappel EvtDriverDeviceAdd .
Appelez UdecxInitializeWdfDeviceInit en transmettant la référence à WDFDEVICE_INIT passée par l’infrastructure.
Initialisez la structure WDFDEVICE_INIT avec des informations d’installation de sorte que cet appareil semble similaire à d’autres contrôleurs hôtes USB. Par exemple, attribuez un nom FDO et un lien symbolique, inscrivez une interface d’appareil avec le GUID d’GUID_DEVINTERFACE_USB_HOST_CONTROLLER fourni par Microsoft en tant que GUID d’interface de l’appareil afin que les applications puissent ouvrir un handle à l’appareil.
Appelez WdfDeviceCreate pour créer l’objet d’appareil framework.
Appelez UdecxWdfDeviceAddUsbDeviceEmulation et inscrivez les fonctions de rappel du pilote client.
Voici les fonctions de rappel associées à l’objet contrôleur hôte, qui sont appelées par l’extension de classe UDE. Ces fonctions doivent être implémentées par le pilote client.
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Détermine les fonctionnalités prises en charge par le contrôleur hôte que le pilote client doit signaler à l’extension de classe.
-
facultatif. Réinitialise le contrôleur hôte et/ou les appareils connectés.
EVT_WDF_DRIVER_DEVICE_ADD Controller_WdfEvtDeviceAdd; #define BASE_DEVICE_NAME L"\\Device\\USBFDO-" #define BASE_SYMBOLIC_LINK_NAME L"\\DosDevices\\HCD" #define DeviceNameSize sizeof(BASE_DEVICE_NAME)+MAX_SUFFIX_SIZE #define SymLinkNameSize sizeof(BASE_SYMBOLIC_LINK_NAME)+MAX_SUFFIX_SIZE NTSTATUS Controller_WdfEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT WdfDeviceInit ) { NTSTATUS status; WDFDEVICE wdfDevice; WDF_PNPPOWER_EVENT_CALLBACKS wdfPnpPowerCallbacks; WDF_OBJECT_ATTRIBUTES wdfDeviceAttributes; WDF_OBJECT_ATTRIBUTES wdfRequestAttributes; UDECX_WDF_DEVICE_CONFIG controllerConfig; WDF_FILEOBJECT_CONFIG fileConfig; PWDFDEVICE_CONTEXT pControllerContext; WDF_IO_QUEUE_CONFIG defaultQueueConfig; WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; UNICODE_STRING refString; ULONG instanceNumber; BOOLEAN isCreated; DECLARE_UNICODE_STRING_SIZE(uniDeviceName, DeviceNameSize); DECLARE_UNICODE_STRING_SIZE(uniSymLinkName, SymLinkNameSize); UNREFERENCED_PARAMETER(Driver); ... WdfDeviceInitSetPnpPowerEventCallbacks(WdfDeviceInit, &wdfPnpPowerCallbacks); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfRequestAttributes, REQUEST_CONTEXT); WdfDeviceInitSetRequestAttributes(WdfDeviceInit, &wdfRequestAttributes); // To distinguish I/O sent to GUID_DEVINTERFACE_USB_HOST_CONTROLLER, we will enable // enable interface reference strings by calling WdfDeviceInitSetFileObjectConfig // with FileObjectClass WdfFileObjectWdfXxx. WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK // No cleanup callback function ); ... WdfDeviceInitSetFileObjectConfig(WdfDeviceInit, &fileConfig, WDF_NO_OBJECT_ATTRIBUTES); ... // Do additional setup required for USB controllers. status = UdecxInitializeWdfDeviceInit(WdfDeviceInit); ... WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfDeviceAttributes, WDFDEVICE_CONTEXT); wdfDeviceAttributes.EvtCleanupCallback = _ControllerWdfEvtCleanupCallback; // Call WdfDeviceCreate with a few extra compatibility steps to ensure this device looks // exactly like other USB host controllers. isCreated = FALSE; for (instanceNumber = 0; instanceNumber < ULONG_MAX; instanceNumber++) { status = RtlUnicodeStringPrintf(&uniDeviceName, L"%ws%d", BASE_DEVICE_NAME, instanceNumber); ... status = WdfDeviceInitAssignName(*WdfDeviceInit, &uniDeviceName); ... status = WdfDeviceCreate(WdfDeviceInit, WdfDeviceAttributes, WdfDevice); if (status == STATUS_OBJECT_NAME_COLLISION) { // This is expected to happen at least once when another USB host controller // already exists on the system. ... } else if (!NT_SUCCESS(status)) { ... } else { isCreated = TRUE; break; } } if (!isCreated) { ... } // Create the symbolic link (also for compatibility). status = RtlUnicodeStringPrintf(&uniSymLinkName, L"%ws%d", BASE_SYMBOLIC_LINK_NAME, instanceNumber); ... status = WdfDeviceCreateSymbolicLink(*WdfDevice, &uniSymLinkName); ... // Create the device interface. RtlInitUnicodeString(&refString, USB_HOST_DEVINTERFACE_REF_STRING); status = WdfDeviceCreateDeviceInterface(wdfDevice, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, &refString); ... UDECX_WDF_DEVICE_CONFIG_INIT(&controllerConfig, Controller_EvtUdecxWdfDeviceQueryUsbCapability); status = UdecxWdfDeviceAddUsbDeviceEmulation(wdfDevice, &controllerConfig); // Create default queue. It only supports USB controller IOCTLs. (USB I/O will come through // in separate USB device queues.) // Shown later in this topic. WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultQueueConfig, WdfIoQueueDispatchSequential); defaultQueueConfig.EvtIoDeviceControl = ControllerEvtIoDeviceControl; defaultQueueConfig.PowerManaged = WdfFalse; status = WdfIoQueueCreate(wdfDevice, &defaultQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &pControllerContext->DefaultQueue); ... // Initialize virtual USB device software objects. // Shown later in this topic. status = Usb_Initialize(wdfDevice); ... exit: return status; } ```1.
Gérer les requêtes IOCTL en mode utilisateur envoyées au contrôleur hôte
Pendant l’initialisation, le pilote client UDE expose le GUID d’interface de périphérique GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Cela permet au pilote de recevoir des requêtes IOCTL d’une application qui ouvre un handle d’appareil à l’aide de ce GUID. Pour obtenir la liste des codes de contrôle IOCTL, consultez IOCTL USB avec GUID d’interface d’appareil : GUID_DEVINTERFACE_USB_HOST_CONTROLLER.
Pour gérer ces demandes, le pilote client inscrit le rappel d’événement EvtIoDeviceControl . Dans l’implémentation, au lieu de gérer la demande, le pilote peut choisir de transférer la demande à l’extension de classe UDE pour traitement. Pour transférer la demande, le pilote doit appeler UdecxWdfDeviceTryHandleUserIoctl. Si le code de contrôle IOCTL reçu correspond à une requête standard, telle que la récupération des descripteurs d’appareil, l’extension de classe traite et termine la demande avec succès. Dans ce cas, UdecxWdfDeviceTryHandleUserIoctl se termine par TRUE comme valeur de retour. Dans le cas contraire, l’appel retourne FALSE et le pilote doit déterminer comment effectuer la demande. Dans une implémentation la plus simple, le pilote peut terminer la demande avec un code d’échec approprié en appelant WdfRequestComplete.
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Controller_EvtIoDeviceControl;
VOID
Controller_EvtIoDeviceControl(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
)
{
BOOLEAN handled;
NTSTATUS status;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
handled = UdecxWdfDeviceTryHandleUserIoctl(WdfIoQueueGetDevice(Queue),
Request);
if (handled) {
goto exit;
}
// Unexpected control code.
// Fail the request.
status = STATUS_INVALID_DEVICE_REQUEST;
WdfRequestComplete(Request, status);
exit:
return;
}
Signaler les fonctionnalités du contrôleur hôte
Avant que les pilotes de couche supérieure puissent utiliser les fonctionnalités d’un contrôleur hôte USB, les pilotes doivent déterminer si ces fonctionnalités sont prises en charge par le contrôleur. Les pilotes effectuent de telles requêtes en appelant WdfUsbTargetDeviceQueryUsbCapability et USBD_QueryUsbCapability. Ces appels sont transférés vers l’extension de classe UDE (USB Device Emulation). Lors de l’obtention de la demande, l’extension de classe appelle l’implémentation EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY du pilote client. Cet appel est effectué uniquement une fois EvtDriverDeviceAdd terminé, généralement dans EvtDevicePrepareHardware et non après EvtDeviceReleaseHardware. Il s’agit d’une fonction de rappel obligatoire.
Dans l’implémentation, le pilote client doit indiquer s’il prend en charge la fonctionnalité demandée. Certaines fonctionnalités ne sont pas prises en charge par l’UDE, telles que les flux statiques.
NTSTATUS
Controller_EvtControllerQueryUsbCapability(
WDFDEVICE UdeWdfDevice,
PGUID CapabilityType,
ULONG OutputBufferLength,
PVOID OutputBuffer,
PULONG ResultLength
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(UdeWdfDevice);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(OutputBuffer);
*ResultLength = 0;
if (RtlCompareMemory(CapabilityType,
&GUID_USB_CAPABILITY_CHAINED_MDLS,
sizeof(GUID)) == sizeof(GUID)) {
//
// TODO: Is GUID_USB_CAPABILITY_CHAINED_MDLS supported?
// If supported, status = STATUS_SUCCESS
// Otherwise, status = STATUS_NOT_SUPPORTED
}
else {
status = STATUS_NOT_IMPLEMENTED;
}
return status;
}
Créer un périphérique USB virtuel
Un périphérique USB virtuel se comporte comme un périphérique USB. Il prend en charge une configuration avec plusieurs interfaces et chaque interface prend en charge d’autres paramètres. Chaque paramètre peut avoir un point de terminaison supplémentaire utilisé pour les transferts de données. Tous les descripteurs (appareil, configuration, interface, point de terminaison) sont définis par le pilote client UDE afin que l’appareil puisse signaler des informations semblable à un appareil USB réel.
Notes
Le pilote client UDE ne prend pas en charge les hubs externes
Voici le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBDEVICE pour un objet d’appareil UDE. Le pilote doit effectuer ces étapes après avoir récupéré le handle WDFDEVICE pour le contrôleur hôte émulé. Nous recommandons au pilote d’effectuer ces tâches dans sa fonction de rappel EvtDriverDeviceAdd .
Appelez UdecxUsbDeviceInitAllocate pour obtenir un pointeur vers les paramètres d’initialisation requis pour créer l’appareil. Cette structure est allouée par l’extension de classe UDE.
Inscrivez les fonctions de rappel d’événements en définissant les membres de UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS , puis en appelant UdecxUsbDeviceInitSetStateChangeCallbacks. Voici les fonctions de rappel associées à l’objet d’appareil UDE, qui sont appelées par l’extension de classe UDE.
Ces fonctions sont implémentées par le pilote client pour créer ou configurer des points de terminaison.
Appelez UdecxUsbDeviceInitSetSpeed pour définir la vitesse du périphérique USB et le type d’appareil, USB 2.0 ou un appareil SuperSpeed.
Appelez UdecxUsbDeviceInitSetEndpointsType pour spécifier le type de points de terminaison pris en charge par l’appareil : simple ou dynamique. Si le pilote client choisit de créer des points de terminaison simples, il doit créer tous les objets de point de terminaison avant de brancher l’appareil. L’appareil ne doit avoir qu’une seule configuration et un seul paramètre d’interface par interface. Dans le cas de points de terminaison dynamiques, le pilote peut créer des points de terminaison à tout moment après avoir branché l’appareil lorsqu’il reçoit un rappel d’événement EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE . Consultez Créer des points de terminaison dynamiques.
Appelez l’une de ces méthodes pour ajouter les descripteurs nécessaires à l’appareil.
UdecxUsbDeviceInitAddStringDescriptorRaw
Si l’extension de classe UDE reçoit une demande pour un descripteur standard fourni par le pilote client lors de l’initialisation à l’aide de l’une des méthodes précédentes, l’extension de classe termine automatiquement la demande. L’extension de classe ne transfère pas cette demande au pilote client. Cette conception réduit le nombre de requêtes que le pilote doit traiter pour les demandes de contrôle. En outre, il élimine également la nécessité pour le pilote d’implémenter une logique de descripteur qui nécessite une analyse approfondie du paquet d’installation et la gestion correcte de wLength et TransferBufferLength . Cette liste inclut les demandes standard. Le pilote client n’a pas besoin de case activée pour ces requêtes (uniquement si les méthodes précédentes ont été appelées pour ajouter un descripteur) :
USB_REQUEST_GET_DESCRIPTOR
USB_REQUEST_SET_CONFIGURATION
USB_REQUEST_SET_INTERFACE
USB_REQUEST_SET_ADDRESS
USB_REQUEST_SET_FEATURE
USB_FEATURE_FUNCTION_SUSPEND
USB_FEATURE_REMOTE_WAKEUP
USB_REQUEST_CLEAR_FEATURE
USB_FEATURE_ENDPOINT_STALL
USB_REQUEST_SET_SEL
USB_REQUEST_ISOCH_DELAY
Toutefois, les demandes pour l’interface, le descripteur spécifique à la classe ou défini par le fournisseur, l’extension de classe UDE les transfère au pilote client. Le pilote doit gérer ces requêtes GET_DESCRIPTOR.
Appelez UdecxUsbDeviceCreate pour créer l’objet d’appareil UDE et récupérer le handle UDECXUSBDEVICE.
Créez des points de terminaison statiques en appelant UdecxUsbEndpointCreate. Consultez Créer des points de terminaison simples.
Appelez UdecxUsbDevicePlugIn pour indiquer à l’extension de classe UDE que l’appareil est attaché et peut recevoir des demandes d’E/S sur les points de terminaison. Après cet appel, l’extension de classe peut également appeler des fonctions de rappel sur les points de terminaison et le périphérique USB. Note Si le périphérique USB doit être supprimé au moment de l’exécution, le pilote client peut appeler UdecxUsbDevicePlugOutAndDelete. Si le pilote souhaite utiliser l’appareil, il doit le créer en appelant UdecxUsbDeviceCreate.
Dans cet exemple, les déclarations de descripteur sont supposées être des variables globales, déclarées comme indiqué ici pour un appareil HID à titre d’exemple :
const UCHAR g_UsbDeviceDescriptor[] = {
// Device Descriptor
0x12, // Descriptor Size
0x01, // Device Descriptor Type
0x00, 0x03, // USB 3.0
0x00, // Device class
0x00, // Device sub-class
0x00, // Device protocol
0x09, // Maxpacket size for EP0 : 2^9
0x5E, 0x04, // Vendor ID
0x39, 0x00, // Product ID
0x00, // LSB of firmware version
0x03, // MSB of firmware version
0x01, // Manufacture string index
0x03, // Product string index
0x00, // Serial number string index
0x01 // Number of configurations
};
Voici un exemple dans lequel le pilote client spécifie des paramètres d’initialisation en inscrivant des fonctions de rappel, en définissant la vitesse de l’appareil, en indiquant le type de points de terminaison et enfin en définissant certains descripteurs d’appareil.
NTSTATUS
Usb_Initialize(
_In_
WDFDEVICE WdfDevice
)
{
NTSTATUS status;
PUSB_CONTEXT usbContext; //Client driver declared context for the host controller object
PUDECX_USBDEVICE_CONTEXT deviceContext; //Client driver declared context for the UDE device object
UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
WDF_OBJECT_ATTRIBUTES attributes;
UDECX_USB_DEVICE_PLUG_IN_OPTIONS pluginOptions;
usbContext = WdfDeviceGetUsbContext(WdfDevice);
usbContext->UdecxUsbDeviceInit = UdecxUsbDeviceInitAllocate(WdfDevice);
if (usbContext->UdecxUsbDeviceInit == NULL) {
...
goto exit;
}
// State changed callbacks
UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
#ifndef SIMPLEENDPOINTS
callbacks.EvtUsbDeviceDefaultEndpointAdd = UsbDevice_EvtUsbDeviceDefaultEndpointAdd;
callbacks.EvtUsbDeviceEndpointAdd = UsbDevice_EvtUsbDeviceEndpointAdd;
callbacks.EvtUsbDeviceEndpointsConfigure = UsbDevice_EvtUsbDeviceEndpointsConfigure;
#endif
callbacks.EvtUsbDeviceLinkPowerEntry = UsbDevice_EvtUsbDeviceLinkPowerEntry;
callbacks.EvtUsbDeviceLinkPowerExit = UsbDevice_EvtUsbDeviceLinkPowerExit;
callbacks.EvtUsbDeviceSetFunctionSuspendAndWake = UsbDevice_EvtUsbDeviceSetFunctionSuspendAndWake;
UdecxUsbDeviceInitSetStateChangeCallbacks(usbContext->UdecxUsbDeviceInit, &callbacks);
// Set required attributes.
UdecxUsbDeviceInitSetSpeed(usbContext->UdecxUsbDeviceInit, UdecxUsbLowSpeed);
#ifdef SIMPLEENDPOINTS
UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeSimple);
#else
UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeDynamic);
#endif
// Add device descriptor
//
status = UdecxUsbDeviceInitAddDescriptor(usbContext->UdecxUsbDeviceInit,
(PUCHAR)g_UsbDeviceDescriptor,
sizeof(g_UsbDeviceDescriptor));
if (!NT_SUCCESS(status)) {
goto exit;
}
#ifdef USB30
// Add BOS descriptor for a SuperSpeed device
status = UdecxUsbDeviceInitAddDescriptor(pUsbContext->UdecxUsbDeviceInit,
(PUCHAR)g_UsbBOSDescriptor,
sizeof(g_UsbBOSDescriptor));
if (!NT_SUCCESS(status)) {
goto exit;
}
#endif
// String descriptors
status = UdecxUsbDeviceInitAddDescriptorWithIndex(usbContext->UdecxUsbDeviceInit,
(PUCHAR)g_LanguageDescriptor,
sizeof(g_LanguageDescriptor),
0);
if (!NT_SUCCESS(status)) {
goto exit;
}
status = UdecxUsbDeviceInitAddStringDescriptor(usbContext->UdecxUsbDeviceInit,
&g_ManufacturerStringEnUs,
g_ManufacturerIndex,
US_ENGLISH);
if (!NT_SUCCESS(status)) {
goto exit;
}
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, UDECX_USBDEVICE_CONTEXT);
status = UdecxUsbDeviceCreate(&usbContext->UdecxUsbDeviceInit,
&attributes,
&usbContext->UdecxUsbDevice);
if (!NT_SUCCESS(status)) {
goto exit;
}
#ifdef SIMPLEENDPOINTS
// Create the default control endpoint
// Shown later in this topic.
status = UsbCreateControlEndpoint(WdfDevice);
if (!NT_SUCCESS(status)) {
goto exit;
}
#endif
UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&pluginOptions);
#ifdef USB30
pluginOptions.Usb30PortNumber = 2;
#else
pluginOptions.Usb20PortNumber = 1;
#endif
status = UdecxUsbDevicePlugIn(usbContext->UdecxUsbDevice, &pluginOptions);
exit:
if (!NT_SUCCESS(status)) {
UdecxUsbDeviceInitFree(usbContext->UdecxUsbDeviceInit);
usbContext->UdecxUsbDeviceInit = NULL;
}
return status;
}
Gestion de l’alimentation du périphérique USB
L’extension de classe UDE appelle les fonctions de rappel du pilote client lorsqu’elle reçoit une demande d’envoi de l’appareil à un état d’alimentation faible ou de retour à l’état opérationnel. Ces fonctions de rappel sont requises pour les périphériques USB qui prennent en charge la veille. Le pilote client a enregistré son implémentation par lors de l’appel précédent à UdecxUsbDeviceInitSetStateChangeCallbacks.
Pour plus d’informations, consultez États d’alimentation des périphériques USB.
EVT_UDECX_USB_DEVICE_D0_ENTRY : le pilote client fait passer l’appareil d’un état Dx à l’état D0.
EVT_UDECX_USB_DEVICE_D0_EXIT : le pilote client fait passer l’appareil de l’état D0 à un état Dx.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE : le pilote client modifie l’état de la fonction de l’interface spécifiée du périphérique USB 3.0 virtuel.
Un appareil USB 3.0 permet à des fonctions individuelles d’entrer dans un état d’alimentation inférieur. Chaque fonction est également capable d’envoyer un signal de veille. L’extension de classe UDE avertit le pilote client en appelant EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Cet événement indique un changement d’état d’alimentation de la fonction et indique au pilote client si la fonction peut sortir du nouvel état. Dans la fonction, l’extension de classe transmet le numéro d’interface de la fonction qui se réveille.
Le pilote client peut simuler l’action d’un périphérique USB virtuel qui lance son propre réveil à partir d’un état d’alimentation faible, d’une interruption de fonction ou des deux. Pour un périphérique USB 2.0, le pilote doit appeler UdecxUsbDeviceSignalWake, si le pilote a activé la veille sur l’appareil dans la EVT_UDECX_USB_DEVICE_D0_EXIT la plus récente. Pour un périphérique USB 3.0, le pilote doit appeler UdecxUsbDeviceSignalFunctionWake , car la fonctionnalité de veille USB 3.0 est par fonction. Si l’ensemble de l’appareil est dans un état de faible consommation ou en entrant un tel état, UdecxUsbDeviceSignalFunctionWake réveille l’appareil.
Créer des points de terminaison simples
Le pilote client crée des objets de point de terminaison UDE pour gérer les transferts de données vers et depuis le périphérique USB. Le pilote crée des points de terminaison simples après avoir créé l’appareil UDE et avant de signaler l’appareil comme connecté.
Voici le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBENDPOINT pour un objet de point de terminaison UDE. Le pilote doit effectuer ces étapes après avoir récupéré le handle UDECXUSBDEVICE pour le périphérique USB virtuel. Nous recommandons au pilote d’effectuer ces tâches dans sa fonction de rappel EvtDriverDeviceAdd .
Appelez UdecxUsbSimpleEndpointInitAllocate pour obtenir un pointeur vers les paramètres d’initialisation alloués par l’extension de classe.
Appelez UdecxUsbEndpointInitSetEndpointAddress pour définir l’adresse du point de terminaison dans les paramètres d’initialisation.
Appelez UdecxUsbEndpointInitSetCallbacks pour inscrire les fonctions de rappel implémentées par le pilote client.
Ces fonctions sont implémentées par le pilote client pour gérer les files d’attente et les requêtes sur un point de terminaison.
EVT_UDECX_USB_ENDPOINT_RESET : réinitialise un point de terminaison du périphérique USB virtuel.
EVT_UDECX_USB_ENDPOINT_START : facultatif. Démarre le traitement des demandes d’E/S
EVT_UDECX_USB_ENDPOINT_PURGE : facultatif. Arrêtez la mise en file d’attente des demandes d’E/S dans la file d’attente du point de terminaison et annulez les demandes non traitées.
Appelez UdecxUsbEndpointCreate pour créer l’objet de point de terminaison et récupérer le handle UDECXUSBENDPOINT.
Appelez UdecxUsbEndpointSetWdfIoQueue pour associer un objet de file d’attente d’infrastructure au point de terminaison. Le cas échéant, il peut définir l’objet point de terminaison sur l’objet parent WDF de la file d’attente en définissant les attributs appropriés.
Chaque objet de point de terminaison a un objet de file d’attente framework pour gérer les demandes de transfert. Pour chaque demande de transfert que l’extension de classe reçoit, elle met en file d’attente un objet de demande d’infrastructure. L’état de la file d’attente (démarré, vidé) est géré par l’extension de classe UDE et le pilote client ne doit pas modifier cet état. Chaque objet de requête contient un bloc de requête USB (URB) qui contient les détails du transfert.
Dans cet exemple, le pilote client crée le point de terminaison de contrôle par défaut.
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL IoEvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET UsbEndpointReset;
EVT_UDECX_USB_ENDPOINT_PURGE UsEndpointEvtPurge;
EVT_UDECX_USB_ENDPOINT_START UsbEndpointEvtStart;
NTSTATUS
UsbCreateControlEndpoint(
_In_
WDFDEVICE WdfDevice
)
{
NTSTATUS status;
PUSB_CONTEXT pUsbContext;
WDF_IO_QUEUE_CONFIG queueConfig;
WDFQUEUE controlQueue;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
PUDECXUSBENDPOINT_INIT endpointInit;
pUsbContext = WdfDeviceGetUsbContext(WdfDevice);
endpointInit = NULL;
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;
status = WdfIoQueueCreate (Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&controlQueue);
if (!NT_SUCCESS(status)) {
goto exit;
}
endpointInit = UdecxUsbSimpleEndpointInitAllocate(pUsbContext->UdecxUsbDevice);
if (endpointInit == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
UdecxUsbEndpointInitSetEndpointAddress(endpointInit, USB_DEFAULT_ENDPOINT_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);
callbacks.EvtUsbEndpointStart = UsbEndpointEvtStart;
callbacks.EvtUsbEndpointPurge = UsEndpointEvtPurge;
status = UdecxUsbEndpointCreate(&endpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&pUsbContext->UdecxUsbControlEndpoint);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointSetWdfIoQueue(pUsbContext->UdecxUsbControlEndpoint,
controlQueue);
exit:
if (endpointInit != NULL) {
NT_ASSERT(!NT_SUCCESS(status));
UdecxUsbEndpointInitFree(endpointInit);
endpointInit = NULL;
}
return status;
}
Créer des points de terminaison dynamiques
Le pilote client peut créer des points de terminaison dynamiques à la demande de l’extension de classe UDE (pour le compte du pilote hub et des pilotes clients). L’extension de classe effectue la requête en appelant l’une des fonctions de rappel suivantes :
*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Le pilote client crée le point de terminaison de contrôle par défaut (Point de terminaison 0)
*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD Le pilote client crée un point de terminaison dynamique.
*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Le pilote client modifie la configuration en sélectionnant un autre paramètre, en désactivant les points de terminaison actuels ou en ajoutant des points de terminaison dynamiques.
Le pilote client a enregistré le rappel précédent lors de son appel à UdecxUsbDeviceInitSetStateChangeCallbacks. Consultez Créer un périphérique USB virtuel. Ce mécanisme permet au pilote client de modifier dynamiquement la configuration USB et les paramètres d’interface sur l’appareil. Par exemple, lorsqu’un objet de point de terminaison est nécessaire ou qu’un objet de point de terminaison existant doit être libéré, l’extension de classe appelle le EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.
Voici le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBENDPOINT pour un objet de point de terminaison dans son implémentation de la fonction de rappel.
Appelez UdecxUsbEndpointInitSetEndpointAddress pour définir l’adresse du point de terminaison dans les paramètres d’initialisation.
Appelez UdecxUsbEndpointInitSetCallbacks pour inscrire les fonctions de rappel implémentées par le pilote client. Comme pour les points de terminaison simples, le pilote peut inscrire ces fonctions de rappel :
Appelez UdecxUsbEndpointCreate pour créer l’objet de point de terminaison et récupérer le handle UDECXUSBENDPOINT.
Appelez UdecxUsbEndpointSetWdfIoQueue pour associer un objet de file d’attente d’infrastructure au point de terminaison.
Dans cet exemple d’implémentation, le pilote client crée un point de terminaison de contrôle par défaut dynamique.
NTSTATUS
UsbDevice_EvtUsbDeviceDefaultEndpointAdd(
_In_
UDECXUSBDEVICE UdecxUsbDevice,
_In_
PUDECXUSBENDPOINT_INIT UdecxUsbEndpointInit
)
{
NTSTATUS status;
PUDECX_USBDEVICE_CONTEXT deviceContext;
WDFQUEUE controlQueue;
WDF_IO_QUEUE_CONFIG queueConfig;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
deviceContext = UdecxDeviceGetContext(UdecxUsbDevice);
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;
status = WdfIoQueueCreate (deviceContext->WdfDevice,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&controlQueue);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointInitSetEndpointAddress(UdecxUsbEndpointInit, USB_DEFAULT_DEVICE_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
UdecxUsbEndpointInitSetCallbacks(UdecxUsbEndpointInit, &callbacks);
status = UdecxUsbEndpointCreate(UdecxUsbEndpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&deviceContext->UdecxUsbControlEndpoint);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointSetWdfIoQueue(deviceContext->UdecxUsbControlEndpoint,
controlQueue);
exit:
return status;
}
Effectuer une récupération d’erreur en réinitialisant un point de terminaison
Parfois, les transferts de données peuvent échouer pour diverses raisons, telles qu’une condition de blocage dans le point de terminaison. En cas d’échec des transferts, le point de terminaison ne peut pas traiter les demandes tant que la condition d’erreur n’a pas été effacée. Lorsque l’extension de classe UDE rencontre des échecs de transfert de données, elle appelle la fonction de rappel EVT_UDECX_USB_ENDPOINT_RESET du pilote client, que le pilote a inscrite lors de l’appel précédent à UdecxUsbEndpointInitSetCallbacks. Dans l’implémentation, le pilote peut choisir d’effacer l’état HALT du canal et de prendre d’autres mesures nécessaires pour effacer la condition d’erreur.
Cet appel est asynchrone. Une fois que le client a terminé l’opération de réinitialisation, le pilote doit terminer la demande avec un code d’échec approprié en appelant WdfRequestComplete. Cet appel avertit l’extension cliente UDE de la fin de l’opération de réinitialisation avec status.
Note Si une solution complexe est requise pour la récupération d’erreurs, le pilote client a la possibilité de réinitialiser le contrôleur hôte. Cette logique peut être implémentée dans la fonction de rappel EVT_UDECX_WDF_DEVICE_RESET que le pilote a inscrite dans son appel UdecxWdfDeviceAddUsbDeviceEmulation . Le cas échéant, le pilote peut réinitialiser le contrôleur hôte et tous les appareils en aval. Si le pilote client n’a pas besoin de réinitialiser le contrôleur, mais de réinitialiser tous les périphériques en aval, le pilote doit spécifier UdeWdfDeviceResetActionResetEachUsbDevice dans les paramètres de configuration lors de l’inscription. Dans ce cas, l’extension de classe appelle EVT_UDECX_WDF_DEVICE_RESET pour chaque appareil connecté.
Implémenter la gestion de l’état de la file d’attente
L’état de l’objet de file d’attente de framework associé à un objet de point de terminaison UDE est géré par l’extension de classe UDE. Toutefois, si le pilote client transfère les requêtes des files d’attente de point de terminaison vers d’autres files d’attente internes, le client doit implémenter la logique pour gérer les modifications apportées au flux d’E/S du point de terminaison. Ces fonctions de rappel sont inscrites auprès de UdecxUsbEndpointInitSetCallbacks.
Opération de purge du point de terminaison
Un pilote client UDE avec une file d’attente par point de terminaison peut implémenter EVT_UDECX_USB_ENDPOINT_PURGE comme illustré dans cet exemple :
Dans l’implémentation EVT_UDECX_USB_ENDPOINT_PURGE , le pilote client est requis pour s’assurer que toutes les E/S transférées à partir de la file d’attente du point de terminaison ont été terminées et que les E/S nouvellement transférées échouent également jusqu’à ce que la EVT_UDECX_USB_ENDPOINT_START du pilote client soit appelée. Ces exigences sont remplies en appelant UdecxUsbEndpointPurgeComplete, ce qui garantit que toutes les E/S transférées sont terminées et que les E/S transférées ultérieures échouent.
Opération de démarrage du point de terminaison
Dans l’implémentation EVT_UDECX_USB_ENDPOINT_START , le pilote client doit commencer à traiter les E/S sur la file d’attente du point de terminaison et sur toutes les files d’attente qui reçoivent des E/S transférées pour le point de terminaison. Une fois qu’un point de terminaison est créé, il ne reçoit aucune E/S tant que cette fonction de rappel n’est pas retournée. Ce rappel retourne le point de terminaison à un état d’E/S de traitement une fois EVT_UDECX_USB_ENDPOINT_PURGE terminé.
Gestion des demandes de transfert de données (URB)
Pour traiter les demandes d’E/S USB envoyées aux points de terminaison de l’appareil client, interceptez le rappel EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL sur l’objet file d’attente utilisé avec UdecxUsbEndpointInitSetCallbacks lors de l’association de la file d’attente au point de terminaison. Dans ce rappel, traitez les E/S pour le IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (consultez l’exemple de code sous Méthodes de gestion URB).
Méthodes de gestion URB
Dans le cadre du traitement des URB via IOCTL_INTERNAL_USB_SUBMIT_URB d’une file d’attente associée à un point de terminaison sur un appareil virtuel, un pilote client UDE peut obtenir un pointeur vers la mémoire tampon de transfert d’une demande d’E/S à l’aide des méthodes suivantes :
Ces fonctions sont implémentées par le pilote client pour gérer les files d’attente et les requêtes sur un point de terminaison.
UdecxUrbRetrieveControlSetupPacket Récupère un paquet d’installation de contrôle USB à partir d’un objet de demande d’infrastructure spécifié.
UdecxUrbRetrieveBuffer Récupère la mémoire tampon de transfert d’un URB à partir de l’objet de demande d’infrastructure spécifié envoyé à la file d’attente du point de terminaison.
UdecxUrbSetBytesCompleted Définit le nombre d’octets transférés pour l’URB contenu dans un objet de demande d’infrastructure.
UdecxUrbComplete Termine la requête URB avec un code d’achèvement spécifique status à USB.
UdecxUrbCompleteWithNtStatus Termine la requête URB avec un code NTSTATUS.
Vous trouverez ci-dessous le flux de traitement d’E/S classique pour l’URB d’un transfert USB OUT.
static VOID
IoEvtSampleOutUrb(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
PENDPOINTQUEUE_CONTEXT pEpQContext;
NTSTATUS status = STATUS_SUCCESS;
PUCHAR transferBuffer;
ULONG transferBufferLength = 0;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
// one possible way to get context info
pEpQContext = GetEndpointQueueContext(Queue);
if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB)
{
LogError(TRACE_DEVICE, "WdfRequest %p Incorrect IOCTL %x, %!STATUS!",
Request, IoControlCode, status);
status = STATUS_INVALID_PARAMETER;
goto exit;
}
status = UdecxUrbRetrieveBuffer(Request, &transferBuffer, &transferBufferLength);
if (!NT_SUCCESS(status))
{
LogError(TRACE_DEVICE, "WdfRequest %p unable to retrieve buffer %!STATUS!",
Request, status);
goto exit;
}
if (transferBufferLength >= 1)
{
//consume one byte of output data
pEpQContext->global_storage = transferBuffer[0];
}
exit:
// writes never pended, always completed
UdecxUrbSetBytesCompleted(Request, transferBufferLength);
UdecxUrbCompleteWithNtStatus(Request, status);
return;
}
Le pilote client peut effectuer une demande d’E/S sur un autre avec un DPC. Suivez ces bonnes pratiques :
- Pour garantir la compatibilité avec les pilotes USB existants, le client UDE doit appeler WdfRequestComplete à DISPATCH_LEVEL.
- Si l’URB a été ajouté à la file d’attente d’un point de terminaison et que le pilote commence à le traiter de manière synchrone sur le thread ou la DPC du pilote appelant, la demande ne doit pas être effectuée de manière synchrone. Un DPC distinct est requis à cet effet, que le pilote met en file d’attente en appelant WdfDpcEnqueue.
- Lorsque l’extension de classe UDE appelle EvtIoCanceledOnQueue ou EvtRequestCancel, le pilote client doit effectuer l’URB reçue sur un DPC distinct du thread ou du DPC de l’appelant. Pour ce faire, le pilote doit fournir un rappel EvtIoCanceledOnQueue pour ses files d’attente URB .
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour