Écrire un pilote client UDE
Cet article décrit le comportement de l’extension de classe d’émulation d’un appareil USB (UDE) 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 manière dont le pilote de classe et l’extension de classe communiquent entre eux à travers 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 pour interroger les capacités du contrôleur et le réinitialiser.
- Création d’un appareil USB virtuel, configuration de ce dernier pour la gestion de l’alimentation et les transferts de données via des points de terminaison.
API importantes
Avant de commencer
- Installez le dernier kit de pilotes Windows (WDK) sur votre ordinateur de développement. Le kit contient les fichiers et bibliothèques d’en-tête requis pour écrire un pilote client UDE, en particulier :
- La bibliothèque de stubs, (Udecxstub.lib). La bibliothèque convertit les appels effectués par le pilote client et les transmet à UdeCx.
- Le fichier d’en-tête, Udecx.h.
- Installez Windows 10 sur votre ordinateur cible.
- Se familiariser avec l’UDE. Consultez Architecture : émulation d’appareils USB (UDE).
- Familiarisez-vous avec Windows Driver Foundation (WDF). Lecture conseillée : Developing Drivers with Windows Driver Foundation écrit par Penny Orwick et Guy Smith.
Handles et objets 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 hôte émulé (WDFDEVICE)
Représente le contrôleur hôte émulé et est le handle principal entre l’extension de classe UDE et le pilote client.
Objet d’appareil UDE (UDECXUSBDEVICE)
Représente un appareil USB virtuel connecté à un port sur le contrôleur hôte émulé.
Objet de point de terminaison UDE (UDECXUSBENDPOINT)
Représente des canaux de données séquentiels d’appareils USB. Permet de recevoir des demandes logicielles pour envoyer ou recevoir des données sur un point de terminaison.
Initialiser le contrôleur hôte émulé
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é est présenté ci-dessous. Nous recommandons que le pilote effectue ces tâches dans sa fonction de rappel EvtDriverDeviceAdd.
Appelez UdecxInitializeWdfDeviceInit en transmettant la référence à WDFDEVICE_INIT transmise par l’infrastructure.
Initialisez la structure WDFDEVICE_INIT avec des informations de configuration de sorte que cet appareil soit similaire à d’autres contrôleurs hôtes USB. Par exemple, affectez un nom FDO et un lien symbolique, enregistrez une interface d’appareil avec le GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER fourni par Microsoft comme GUID d’interface d’appareil, afin que les applications puissent ouvrir un handle sur l’appareil.
Appelez WdfDeviceCreate pour créer l’objet d’appareil d’infrastructure.
Appelez UdecxWdfDeviceAddUsbDeviceEmulation et enregistrez les fonctions de rappel du pilote client.
Voici les fonctions de rappel associées à l’objet du 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
Lors de l’initialisation, le pilote client UDE expose le GUID de l’interface de l’appareil GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Cela permet au pilote de recevoir des demandes 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 USB IOCTLs avec GUID de l’interface d’appareil : GUID_DEVINTERFACE_USB_HOST_CONTROLLER.
Pour gérer ces demandes, le pilote client enregistre le rappel d’événement EvtIoDeviceControl. Lors de l’implémentation, au lieu de gérer la demande, le pilote peut choisir de la transférer à l’extension de classe UDE pour son traitement. Pour transférer la demande, le pilote doit appeler UdecxWdfDeviceTryHandleUserIoctl. Si le code de contrôle IOCTL reçu correspond à une demande standard, telle que la récupération des descripteurs d’appareil, l’extension de classe traite et achève la demande avec succès. Dans ce cas, UdecxWdfDeviceTryHandleUserIoctl se termine par TRUE comme valeur de retour. Dans le cas contraire, l’appel renvoie FALSE et le pilote doit déterminer comment faire aboutir la demande. Dans l’implémentation la plus simple, le pilote peut faire aboutir 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;
}
Informer des capacités du contrôleur hôte
Avant que les pilotes de la couche supérieure puissent utiliser les capacités d’un contrôleur hôte USB, ils doivent déterminer si ces dernières sont prises en charge par le contrôleur. Les pilotes effectuent de telles demandes en appelant WdfUsbTargetDeviceQueryUsbCapability a USBD_QueryUsbCapability. Ces appels sont transférés à l’extension de classe d’émulation d’appareil USB (UDE). Lorsque la demande des obtenue, 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. Cette fonction de rappel est requise.
Lors de l’implémentation, le pilote client doit indiquer s’il prend en charge la capacité demandée. Certaines capacités ne sont pas prises en charge par l’UDE, comme 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 appareil USB virtuel
Un appareil USB virtuel se comporte comme un appareil USB. Il prend en charge une configuration avec plusieurs interfaces et chaque interface prend en charge plusieurs configurations. Chaque configuration peut avoir un autre point de terminaison 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 fournir des informations comme un appareil USB réel.
Remarque
Le pilote client UDE ne prend pas en charge les hubs externes
Le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBDEVICE pour un objet d’appareil UDE est présenté ci-dessous. Le pilote doit effectuer ces étapes après avoir récupéré le handle WDFDEVICE pour le contrôleur hôte émulé. Nous recommandons que le pilote effectue 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.
Enregistrez les fonctions de rappel d’événement 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 de l’appareil USB ainsi que 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 décide de créer des points de terminaison simples, il doit créer tous les objets de point de terminaison avant de connecter l’appareil. L’appareil ne doit avoir qu’une seule configuration et une seule configuration 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 la connexion de 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 de descripteur standard que le pilote client a fourni lors de l’initialisation à l’aide de l’une des méthodes précédentes, l’extension de classe complète automatiquement la demande. L’extension de classe ne transfère pas cette demande au pilote client. Cette conception réduit le nombre de demande que le pilote doit traiter pour les demandes de contrôle. En outre, elle évite au pilote d’implémenter une logique de descripteur qui nécessite une analyse approfondie du paquet d’initialisation et une gestion correcte de wLength et TransferBufferLength. Cette liste inclut les demandes standard. Le pilote client n’a pas besoin de vérifier 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, la classe UDE transmet au pilote client les demandes de descripteurs d’interface, de descripteurs spécifiques à une classe ou de descripteurs définis par le fournisseur. Le pilote doit gérer ces demandes 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 l’appareil USB. Remarque Si l’appareil 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 enregistrant 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 de l’appareil USB
L’extension de classe UDE invoque les fonctions de rappel du pilote client lorsqu’elle reçoit une demande visant à mettre l’appareil en état de faible consommation ou à le remettre en état de fonctionnement. Ces fonctions de rappel sont requises pour les appareils USB qui prennent en charge la sortie de veille. Le pilote client a enregistré son implémentation dans l’appel précédent à UdecxUsbDeviceInitSetStateChangeCallbacks.
Pour en savoir plus, consultez États d’alimentation des appareils USB.
EVT_UDECX_USB_DEVICE_D0_ENTRY : le pilote client passe l’appareil d’un état Dx à l’état D0.
EVT_UDECX_USB_DEVICE_D0_EXIT : le pilote client passe l’appareil d’un état D0 à l’état Dx.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE : le pilote client modifie l’état de fonction de l’interface spécifiée de l’appareil USB 3.0 virtuel.
Un appareil USB 3.0 permet aux fonctions individuelles d’entrer dans un état de faible alimentation. Chaque fonction est également capable d’envoyer un signal de sortie 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 du 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 sort de veille.
Le pilote client peut simuler l’action d’un appareil USB virtuel qui lance sa propre sortie de veille à partir d’un faible état d’alimentation de lien, d’une suspension de fonction ou des deux. Pour un appareil USB 2.0, le pilote doit appeler UdecxUsbDeviceSignalWake, si le pilote a activé la sortie de veille de l’appareil dans la EVT_UDECX_USB_DEVICE_D0_EXIT la plus récente. Pour un appareils USB 3.0, le pilote doit appeler UdecxUsbDeviceSignalFunctionWake , car la fonctionnalité de sortie de veille USB 3.0 est par fonction. Si l’ensemble de l’appareil est dans un état de faible alimentation ou en train d’entrer dans un tel état, UdecxUsbDeviceSignalFunctionWake sort l’appareil de veille.
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 l’appareil USB. Le pilote crée des points de terminaison simples après avoir créé l’appareil UDE et avant d’indiquer que l’appareil est connecté.
Le résumé de la séquence dans laquelle le pilote client crée un handle UDECXUSBDENDPOINT pour un objet de point de terminaison UDE est présenté ci-dessous. Le pilote doit effectuer ces étapes après avoir récupéré le handle UDECXUSBDEVICE pour l’appareil USB virtuel. Nous recommandons que le pilote effectue 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 enregistrer 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 demandes sur un point de terminaison.
EVT_UDECX_USB_ENDPOINT_RESET : réinitialise un point de terminaison de l’appareil 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 de point de terminaison comme 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 d’infrastructure pour gérer les demandes de transfert. Pour chaque demande de transfert reçue par l’extension de classe, 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 demande contient un bloc de demandes 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 de hub et des pilotes clients). L’extension de classe effectue la demande en appelant l’une de ces fonctions de rappel :
*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 pendant son appel à UdecxUsbDeviceInitSetStateChangeCallbacks. Consultez Créer l’appareil virtuel USB. Ce mécanisme permet au pilote client de modifier dynamiquement les paramètres de configuration et d’interface USB 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 enregistrer les fonctions de rappel implémentées par le pilote client. Comme pour les points de terminaison simples, le pilote peut enregistrer les fonctions de rappel suivantes :
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 la récupération d’erreurs en réinitialisant un point de terminaison
Parfois, les transferts de données peuvent échouer pour plusieurs raisons, telles qu’une condition de blocage dans le point de terminaison. En cas d’échec de transferts, le point de terminaison ne peut pas traiter les demandes tant que la condition d’erreur n’est pas supprimée. Lorsque l’extension de classe UDE connaît des échecs de transfert de données, elle appelle la fonction de rappel EVT_UDECX_USB_ENDPOINT_RESET pilote client, que le pilote a enregistré dans l’appel précédent à UdecxUsbEndpointInitSetCallbacks. Dans l’implémentation, le pilote peut choisir d’effacer l’état HALT du canal et d’effectuer d’autres étapes nécessaires pour effacer la condition d’erreur.
Ces appel est asynchrone. Une fois que le client a terminé l’opération de réinitialisation, le pilote doit achever la demande avec un code d’échec approprié en appelant WdfRequestComplete. Cet appel informe l’extension de client UDE de l’achèvement de l’opération de réinitialisation avec l’état.
Remarque 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 enregistrée 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 appareils en aval, il doit spécifier UdeWdfDeviceResetActionResetEachUsbDevice dans les paramètres de configuration pendant l’enregistrement. 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 d’infrastructure 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 demandes des files d’attente de points de terminaison vers d’autres files d’attente internes, le client doit implémenter la logique pour gérer les modifications dans le flux d’E/S du point de terminaison. Ces fonctions de rappel sont enregistrées auprès d’UdecxUsbEndpointInitSetCallbacks.
Opération de vidage 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 abouties, et que les E/S nouvellement transférées échouent également jusqu’à ce que le EVT_UDECX_USB_ENDPOINT_START du pilote client est appelé. 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 ultérieures transférées échouent.
Opération de démarrage du point de terminaison
Dans l’implémentation EVT_UDECX_USB_ENDPOINT_START , le pilote client est requis pour 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 ce dernier. Après la création d’un point d’extrémité, celui-ci ne reçoit aucune entrée/sortie avant le renvoi de cette fonction de rappel. Ce rappel renvoie le point de terminaison à un état de traitement des E/S 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 de 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 CioControlCode IOCTL_INTERNAL_USB_SUBMIT_URB (consultez l’exemple de code sous méthodes de gestion des URB).
Méthodes de gestion des URB
Dans le cadre du traitement des URB via l’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 demandes sur un point de terminaison.
UdecxUrbRetrieveControlSetupPacket Récupère un paquet de configuration 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 demande URB avec un code d’état d’achèvement spécifique pour l’USB.
UdecxUrbCompleteWithNtStatus Termine la demande URB avec un code NTSTATUS.
Vous trouverez ci-dessous le flux de traitement d’E/S standard 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 séparément avec un DPC. Suivez ces meilleures pratiques :
- Pour garantir la compatibilité avec les pilotes USB existants, le client UDE doit appeler WdfRequestComplete dans 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 DPC du pilote appelant, la demande ne doit pas être effectuée de manière synchrone. Un DPC distinct est nécessaire à cette fin, 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 terminer 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.