Come inviare un trasferimento di controllo USB
Questo articolo illustra la struttura di un trasferimento di controllo e il modo in cui un driver client deve inviare una richiesta di controllo al dispositivo.
Informazioni sull'endpoint predefinito
Tutti i dispositivi USB devono supportare almeno un endpoint denominato endpoint predefinito. Qualsiasi trasferimento destinato all'endpoint predefinito è denominato trasferimento di controllo. Lo scopo di un trasferimento di controllo è consentire all'host di ottenere informazioni sul dispositivo, configurare il dispositivo o eseguire operazioni di controllo univoche per il dispositivo.
Per iniziare, studiare queste caratteristiche dell'endpoint predefinito.
- L'indirizzo dell'endpoint predefinito è 0.
- L'endpoint predefinito è bidirezionale, ovvero l'host può inviare dati all'endpoint e ricevere dati da esso entro un trasferimento.
- L'endpoint predefinito è disponibile a livello di dispositivo e non è definito in alcuna interfaccia del dispositivo.
- L'endpoint predefinito è attivo non appena viene stabilita una connessione tra l'host e il dispositivo. È attivo anche prima che venga selezionata una configurazione.
- La dimensione massima del pacchetto dell'endpoint predefinito dipende dalla velocità del bus del dispositivo. Bassa velocità, 8 byte; pieno e ad alta velocità, 64 byte; SuperSpeed, 512 byte.
Layout di un trasferimento di controllo
Poiché i trasferimenti di controllo sono trasferimenti con priorità elevata, una certa quantità di larghezza di banda viene riservata sul bus dall'host. Per i dispositivi a bassa e piena velocità, il 10% della larghezza di banda; 20% per i dispositivi ad alta velocità e SuperSpeed. Esaminiamo ora il layout di un trasferimento di controllo.
Un trasferimento di controllo è suddiviso in tre transazioni: transazione di configurazione, transazione di dati e transazione di stato. Ogni transazione contiene tre tipi di pacchetti: pacchetto di token, pacchetto di dati e pacchetto handshake.
Alcuni campi sono comuni a tutti i pacchetti. Questi campi sono:
- Campo di sincronizzazione che indica l'inizio del pacchetto.
- Identificatore di pacchetto (PID) che indica il tipo di pacchetto, la direzione della transazione e, nel caso di un pacchetto handshake, indica l'esito positivo o negativo della transazione.
- Il campo EOP indica la fine del pacchetto.
Altri campi dipendono dal tipo di pacchetto.
Pacchetto di token
Ogni transazione di installazione inizia con un pacchetto di token. Ecco la struttura del pacchetto. L'host invia sempre il pacchetto di token.
Il valore PID indica il tipo del pacchetto di token. Ecco i valori possibili:
- SETUP: indica l'inizio di una transazione di installazione in un trasferimento di controllo.
- IN: indica che l'host richiede dati dal dispositivo (caso di lettura).
- OUT: indica che l'host invia dati al dispositivo (caso di scrittura).
- SOF: indica l'inizio del frame. Questo tipo di pacchetto di token contiene un numero di frame a 11 bit. L'host invia il pacchetto sof. La frequenza con cui questo pacchetto viene inviato dipende dalla velocità del bus. Per tutta la velocità, l'host invia il pacchetto ogni 1millisecondo; ogni 125 microsecondi su un autobus ad alta velocità.
Pacchetto di dati
Immediatamente dopo il pacchetto di token è il pacchetto di dati che contiene il payload. Il numero di byte che ogni pacchetto di dati può contenere dipende dalle dimensioni massime del pacchetto dell'endpoint predefinito. Il pacchetto di dati può essere inviato dall'host o dal dispositivo, a seconda della direzione del trasferimento.
Pacchetto handshake
Subito dopo il pacchetto di dati è il pacchetto handshake. Il PID del pacchetto indica se il pacchetto è stato ricevuto dall'host o dal dispositivo. Il pacchetto handshake può essere inviato dall'host o dal dispositivo, a seconda della direzione del trasferimento.
È possibile visualizzare la struttura delle transazioni e dei pacchetti usando qualsiasi analizzatore USB, ad esempio Beagle, Ellisys, analizzatori di protocolli USB LeCroy. Un dispositivo analizzatore mostra come i dati vengono inviati o ricevuti da un dispositivo USB in rete. In questo esempio verranno esaminate alcune tracce acquisite da un analizzatore USB LeCroy. Questo esempio è solo per informazioni. Non si tratta di un'approvazione da parte di Microsoft.
Configurare la transazione
L'host avvia sempre un trasferimento di controllo. A tale scopo, inviare una transazione di installazione. Questa transazione contiene un pacchetto di token denominato token di installazione seguito da un pacchetto di dati a 8 byte. Questa schermata mostra una transazione di configurazione di esempio.
Nella traccia precedente, l'host avvia (indicato da H++) il trasferimento del controllo inviando il pacchetto di token di installazione #434. Si noti che il PID specifica SETUP che indica un token di installazione. Il PID è seguito dall'indirizzo del dispositivo e dall'indirizzo dell'endpoint. Per i trasferimenti di controllo, l'indirizzo dell'endpoint è sempre 0.
Successivamente, l'host invia il pacchetto di dati #435. Il PID è DATA0 e tale valore viene usato per la sequenziazione di pacchetti (da discutere). Il PID è seguito da 8 byte che contiene le informazioni principali su questa richiesta. Questi 8 byte indicano il tipo di richiesta e le dimensioni del buffer in cui il dispositivo scriverà la risposta.
Tutti i byte vengono ricevuti in ordine inverso. Come descritto nella sezione 9.3, vengono visualizzati i campi e i valori seguenti:
Campo | Dimensione | valore | Descrizione |
---|---|---|---|
bmRequestType (vedere 9.3.1 bmRequestType) | 1 | 0x80 | La direzione del trasferimento dei dati è dal dispositivo all'host (D7 è 1) La richiesta è una richiesta standard (D6... D5 è 0) Il destinatario della richiesta è il DISPOSITIVO (D4 è 0) |
bRequest (vedere la sezione Vedere 9.3.2 e tabella 9-4) | 1 | 0x06 | Il tipo di richiesta è GET_DESCRIPTOR. |
wValue (vedere la tabella 9-5) | 2 | 0x0100 | Il valore della richiesta indica che il tipo di descrittore è DEVICE. |
wIndex (vedere la sezione 9.3.4) | 2 | 0x0000 | La direzione è dall'host al dispositivo (D7 è 1) Il numero di endpoint è 0. |
wLength (vedere la sezione 9.3.5) | 2 | 0x0012 | La richiesta consiste nel recuperare 18 byte. |
È quindi possibile concludere che in questo trasferimento di controllo (lettura) l'host invia una richiesta per recuperare il descrittore del dispositivo e specifica 18 byte come lunghezza di trasferimento per contenere tale descrittore. Il modo in cui il dispositivo invia questi 18 byte dipende dalla quantità di dati che l'endpoint predefinito può inviare in una transazione. Tali informazioni sono incluse nel descrittore del dispositivo restituito dal dispositivo nella transazione di dati.
In risposta, il dispositivo invia un pacchetto handshake (#436 indicato da D++). Si noti che il valore PID è ACK (pacchetto ACK). Ciò indica che il dispositivo ha riconosciuto la transazione.
Transazione dati
A questo punto, vediamo cosa restituisce il dispositivo in risposta alla richiesta. I dati effettivi vengono trasferiti in una transazione di dati.
Ecco la traccia per la transazione di dati.
Dopo aver ricevuto il pacchetto ACK, l'host avvia la transazione di dati. Per avviare la transazione, invia un pacchetto di token (#450) con la direzione IN (denominata token IN).
In risposta, il dispositivo invia un pacchetto di dati (#451) che segue il token IN. Questo pacchetto di dati contiene il descrittore effettivo del dispositivo. Il primo byte indica la lunghezza del descrittore del dispositivo, 18 byte (0x12). L'ultimo byte in questo pacchetto di dati indica le dimensioni massime del pacchetto supportate dall'endpoint predefinito. In questo caso, si noterà che il dispositivo può inviare 8 byte alla volta tramite l'endpoint predefinito.
Nota
La dimensione massima del pacchetto dell'endpoint predefinito dipende dalla velocità del dispositivo. L'endpoint predefinito di un dispositivo ad alta velocità è di 64 byte; il dispositivo a bassa velocità è di 8 byte.
L'host riconosce la transazione di dati inviando un pacchetto ACK (#452) al dispositivo.
Calcoliamo ora la quantità di dati restituiti. Nel campo wLength del pacchetto di dati (#435) nella transazione di installazione l'host ha richiesto 18 byte. Nella transazione di dati si noterà che dal dispositivo sono stati ricevuti solo i primi 8 byte del descrittore del dispositivo. In che modo l'host riceve le informazioni archiviate nei 10 byte rimanenti? Il dispositivo esegue questa operazione in due transazioni: 8 byte e quindi ultimi 2 byte.
Ora che l'host conosce le dimensioni massime del pacchetto dell'endpoint predefinito, l'host avvia una nuova transazione di dati e richiede la parte successiva in base alle dimensioni del pacchetto.
Ecco la transazione dati successiva:
L'host avvia la transazione di dati precedente inviando un token IN (#463) e richiedendo i successivi 8 byte dal dispositivo. Il dispositivo risponde con un pacchetto di dati (#464) che contiene i successivi 8 byte del descrittore del dispositivo.
Dopo aver ricevuto i 8 byte, l'host invia un pacchetto ACK (#465) al dispositivo.
Successivamente, l'host richiede gli ultimi 2 byte in un'altra transazione di dati come indicato di seguito:
Si noterà quindi che per trasferire 18 byte dal dispositivo all'host, l'host tiene traccia del numero di byte trasferiti e avviato tre transazioni di dati (8+8+2).
Nota
Si noti il PID dei pacchetti di dati nelle transazioni di dati 19, 23, 26. Il PID si alterna tra DATA0 e DATA1. Questa sequenza viene chiamata attivazione/disattivazione dei dati. Nei casi in cui sono presenti più transazioni di dati, viene usato l'interruttore dei dati per verificare la sequenza di pacchetti. Questo metodo garantisce che i pacchetti di dati non vengano duplicati o persi.
Eseguendo il mapping dei pacchetti di dati consolidati alla struttura del descrittore di dispositivo (vedere la tabella 9-8), vengono visualizzati i campi e i valori seguenti:
Campo | Dimensione | valore | Descrizione |
---|---|---|---|
bLength | 1 | 0x12 | Lunghezza del descrittore del dispositivo, ovvero 18 byte. |
bDescriptorType | 1 | 0x01 | Il tipo di descrittore è device. |
bcdUSB | 2 | 0x0100 | Il numero di versione della specifica è 1.00. |
bDeviceClass | 1 | 0x00 | La classe device è 0. Ogni interfaccia nella configurazione contiene le informazioni sulla classe. |
bDeviceSubClass | 1 | 0x00 | La sottoclasse è 0 perché la classe del dispositivo è 0. |
bProtocol | 1 | 0x00 | Il protocollo è 0. Questo dispositivo non usa protocolli specifici della classe. |
bMaxPacketSize0 | 1 | 0x08 | La dimensione massima del pacchetto dell'endpoint è di 8 byte. |
idVendor | 2 | 0x0562 | Comunicazioni telex. |
idProduct | 2 | 0x0002 | Microfono USB. |
bcdDevice | 2 | 0x0100 | Indica il numero di versione del dispositivo. |
iManufacturer | 1 | 0x01 | Stringa del produttore. |
iProduct | 1 | 0x02 | Stringa del prodotto. |
iSerialNumber | 1 | 0x03 | Numero di serie. |
bNumConfigurations | 1 | 0x01 | Numero di configurazioni. |
Esaminando questi valori sono disponibili alcune informazioni preliminari sul dispositivo. Il dispositivo è un microfono USB a bassa velocità. La dimensione massima del pacchetto dell'endpoint predefinito è di 8 byte. Il dispositivo supporta una configurazione.
Transazione di stato
Infine, l'host completa il trasferimento del controllo avviando l'ultima transazione: transazione di stato.
L'host avvia la transazione con un pacchetto di token OUT (#481). Lo scopo di questo pacchetto è verificare che il dispositivo abbia inviato tutti i dati richiesti. Nessun pacchetto di dati inviato in questa transazione di stato. Il dispositivo risponde con un pacchetto ACK. Se si è verificato un errore, il PID potrebbe essere stato NAK o STALL.
Modelli di driver
- Framework driver in modalità kernel
- Framework driver in modalità utente
- Introduzione a WinUSB per sviluppatori
Prerequisiti
Prima che il driver client possa enumerare le pipe, assicurarsi che siano soddisfatti questi requisiti:
Il driver client deve aver creato l'oggetto dispositivo di destinazione USB del framework.
Se si usano i modelli USB forniti con Microsoft Visual Studio Professional 2012, il codice del modello esegue tali attività. Il codice del modello ottiene l'handle per l'oggetto dispositivo di destinazione e archivia nel contesto del dispositivo.
Driver client KMDF
Un driver client KMDF deve ottenere un handle WDFUSBDEVICE chiamando il metodo WdfUsbTargetDeviceCreateWithParameters . Per altre informazioni, vedere "Codice sorgente del dispositivo" in Informazioni sulla struttura del codice del driver client USB (KMDF).
Driver client UMDF
Un driver client UMDF deve ottenere un puntatore IWDFUsbTargetDevice eseguendo una query sull'oggetto dispositivo di destinazione del framework. Per altre informazioni, vedere "Implementazione IPnpCallbackHardware e attività specifiche dell'USB" in Informazioni sulla struttura del codice del driver client USB (UMDF).
L'aspetto più importante per un trasferimento di controllo consiste nel formattare il token di installazione in modo appropriato. Prima di inviare la richiesta, raccogliere questo set di informazioni:
- Direzione della richiesta: ospitare da host a dispositivo o dispositivo da ospitare.
- Destinatario della richiesta: dispositivo, interfaccia, endpoint o altro.
- Categoria di richiesta: standard, classe o fornitore.
- Tipo di richiesta, ad esempio una richiesta di GET_DESCRIPTPOR. Per altre informazioni, vedere la sezione 9.5 nella specifica USB.
- valori wValue e wIndex . Questi valori dipendono dal tipo di richiesta.
Puoi ottenere tutte queste informazioni dalla specifica USB ufficiale.
Se si scrive un driver UMDF, ottenere il file di intestazione, Usb_hw.h dal driver di esempio UMDF per OSR USB Fx2 Learning Kit. Questo file di intestazione contiene macro e struttura utili per la formattazione del pacchetto di installazione per il trasferimento del controllo.
Tutti i driver UMDF devono comunicare con un driver in modalità kernel per inviare e ricevere dati dai dispositivi. Per un driver USB UMDF, il driver in modalità kernel è sempre il driver WinUSB fornito da Microsoft (Winusb.sys).
Ogni volta che un driver UMDF effettua una richiesta per lo stack di driver USB, gestione I/O di Windows invia la richiesta a WinUSB. Dopo aver ricevuto la richiesta, WinUSB elabora la richiesta o la inoltra allo stack di driver USB.
Metodi definiti da Microsoft per l'invio di richieste di trasferimento dei controlli
Un driver client USB nell'host avvia la maggior parte delle richieste di controllo per ottenere informazioni sul dispositivo, configurare il dispositivo o inviare comandi di controllo fornitore. Tutte queste richieste possono essere classificate in:
Le richieste standard sono definite nella specifica USB. Lo scopo dell'invio di richieste standard è ottenere informazioni sul dispositivo, sulle relative configurazioni, interfacce ed endpoint. Il destinatario di ogni richiesta dipende dal tipo di richiesta. Il destinatario può essere il dispositivo, un'interfaccia o un endpoint.
Nota
La destinazione di qualsiasi trasferimento di controllo è sempre l'endpoint predefinito. Il destinatario è l'entità del dispositivo le cui informazioni (descrittore, stato e così via) l'host è interessato.
Le richieste possono essere ulteriormente classificate in: richieste di configurazione, richieste di funzionalità e richieste di stato.
- Le richieste di configurazione vengono inviate per ottenere informazioni dal dispositivo in modo che l'host possa configurarlo, ad esempio una richiesta di GET_DESCRIPTOR. Queste richieste possono anche essere richieste di scrittura inviate dall'host per impostare una configurazione specifica o un'impostazione alternativa nel dispositivo.
- Le richieste di funzionalità vengono inviate dal driver client per abilitare o disabilitare determinate impostazioni del dispositivo booleane supportate dal dispositivo, dall'interfaccia o da un endpoint.
- Le richieste di stato consentono all'host di ottenere o impostare i bit di stato definiti tramite USB di un dispositivo, un endpoint o un'interfaccia.
Per altre informazioni, vedere la sezione 9.4 nella specifica USB versione 2.0. I tipi di richiesta standard sono definiti nel file di intestazione Usbspec.h.
Le richieste di classe sono definite da una specifica della classe del dispositivo.
Le richieste fornitore vengono fornite dal fornitore e dipendono dalle richieste supportate dal dispositivo.
Lo stack USB fornito da Microsoft gestisce tutte le comunicazioni di protocollo con il dispositivo, come illustrato nelle tracce precedenti. Il driver espone le interfacce del driver di dispositivo (DDI) che consentono a un driver client di inviare trasferimenti di controllo in molti modi. Se il driver client è un driver Windows Driver Foundation (WDF), può chiamare routine direttamente per inviare i tipi comuni di richieste di controllo. WDF supporta i trasferimenti di controllo intrinsecamente per KMDF e UMDF.
Alcuni tipi di richieste di controllo non vengono esposti tramite WDF. Per queste richieste, il driver client può usare il modello WDF-hybrid. Questo modello consente al driver client di compilare e formattare richieste in stile WDM XSM e quindi inviare tali richieste usando oggetti framework WDF. Il modello ibrido si applica solo ai driver in modalità kernel.
Per i driver UMDF:
Usare le macro helper e la struttura definite in usb_hw.h. Questa intestazione è inclusa nel driver di esempio UMDF per OSR USB Fx2 Learning Kit.
Usare questa tabella per determinare il modo migliore per inviare richieste di controllo allo stack di driver USB. Se non è possibile visualizzare questa tabella, vedere la tabella in questo articolo.
Se si desidera inviare una richiesta di controllo a... | Per un driver KMDF... | Per un driver UMDF... | Per un driver WDM, creare una struttura ODBC (routine helper) |
---|---|---|---|
CLEAR_FEATURE: disabilitare determinate impostazioni di funzionalità nel dispositivo, le relative configurazioni, interfacce ed endpoint. Vedere la sezione 9.4.1 nella specifica USB. |
|
|
_URB_CONTROL_FEATURE_REQUEST (UsbBuildFeatureRequest) URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT URB_FUNCTION_CLEAR_FEATURE_TO_OTHER |
GET_CONFIGURATION: ottenere la configurazione USB corrente. Vedere la sezione 9.4.2 nella specifica USB. | KmDF seleziona la prima configurazione per impostazione predefinita. Per recuperare il numero di configurazione definito dal dispositivo:
|
UMDF seleziona la prima configurazione per impostazione predefinita. Per recuperare il numero di configurazione definito dal dispositivo:
|
_URB_CONTROL_GET_CONFIGURATION_REQUEST URB_FUNCTION_GET_CONFIGURATION |
GET_DESCRIPTOR: ottenere descrittori di dispositivo, configurazione, interfaccia e endpoint. Vedere la sezione 9.4.3 nella specifica USB. Per altre informazioni, vedere Descrittori USB. |
Chiamare questi metodi:
|
Chiamare questi metodi:
|
_URB_CONTROL_DESCRIPTOR_REQUEST (UsbBuildGetDescriptorRequest) URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE |
GET_INTERFACE: ottenere l'impostazione alternativa corrente per un'interfaccia. Vedere la sezione 9.4.4 nella specifica USB. |
|
_URB_CONTROL_GET_INTERFACE_REQUEST URB_FUNCTION_GET_INTERFACE |
|
GET_STATUS: ottenere i bit di stato da un dispositivo, un endpoint o un'interfaccia. Vedere la sezione 9.4.5. nella specifica USB. |
|
|
_URB_CONTROL_GET_STATUS_REQUEST (UsbBuildGetStatusRequest) URB_FUNCTION_GET_STATUS_FROM_DEVICE URB_FUNCTION_GET_STATUS_FROM_INTERFACE URB_FUNCTION_GET_STATUS_FROM_ENDPOINT URB_FUNCTION_GET_STATUS_FROM_OTHER. |
SET_ADDRESS: vedere la sezione 9.4.6 nella specifica USB. | Questa richiesta viene gestita dallo stack di driver USB; Il driver client non può eseguire questa operazione. | Questa richiesta viene gestita dallo stack di driver USB; Il driver client non può eseguire questa operazione. | Questa richiesta viene gestita dallo stack di driver USB; Il driver client non può eseguire questa operazione. |
SET_CONFIGURATION: impostare una configurazione. Vedere la sezione 9.4.7 nella specifica USB. Per altre informazioni, vedere Come selezionare una configurazione per un dispositivo USB. |
Per impostazione predefinita, KMDF seleziona la configurazione predefinita e la prima impostazione alternativa in ogni interfaccia. Il driver client può modificare la configurazione predefinita chiamando il metodo WdfUsbTargetDeviceSelectConfigType e specificando WdfUsbTargetDeviceSelectConfigTypeUrb come opzione di richiesta. È quindi necessario formattare un VALORE DI CLASSE per questa richiesta e inviarlo allo stack di driver USB. | Per impostazione predefinita, UMDF seleziona la configurazione predefinita e la prima impostazione alternativa in ogni interfaccia. Il driver client non può modificare la configurazione. | _URB_SELECT_CONFIGURATION (USBD_SelectConfigUrbAllocateAndBuild) URB_FUNCTION_SELECT_CONFIGURATION |
SET_DESCRIPTOR: aggiornare un descrittore di dispositivo, configurazione o stringa esistente. Vedere la sezione 9.4.8 nella specifica USB. Questa richiesta non viene comunemente usata. Tuttavia, lo stack di driver USB accetta tale richiesta dal driver client. |
|
|
_URB_CONTROL_DESCRIPTOR_REQUEST URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE |
SET_FEATURE: abilitare determinate impostazioni di funzionalità nel dispositivo, le relative configurazioni, interfacce ed endpoint. Vedere la sezione 9.4.9 nella specifica USB. |
|
|
_URB_CONTROL_FEATURE_REQUEST (UsbBuildFeatureRequest) URB_FUNCTION_SET_FEATURE_TO_DEVICE URB_FUNCTION_SET_FEATURE_TO_INTERFACE URB_FUNCTION_SET_FEATURE_TO_ENDPOINT URB_FUNCTION_SET_FEATURE_TO_OTHER |
SET_INTERFACE: modifica l'impostazione alternativa in un'interfaccia. Vedere la sezione 9.4.9 nella specifica USB. Per altre informazioni, vedere Come selezionare un'impostazione alternativa in un'interfaccia USB. |
WdfUsbTargetDeviceSelectConfig
|
|
_URB_SELECT_INTERFACE (USBD_SelectInterfaceUrbAllocateAndBuild) URB_FUNCTION_SELECT_INTERFACE |
SYNC_FRAME: impostare e ottenere il numero di frame di sincronizzazione dell'endpoint e . Vedere la sezione 9.4.10 nella specifica USB. | Questa richiesta viene gestita dallo stack di driver USB; Il driver client non può eseguire questa operazione. | Questa richiesta viene gestita dallo stack di driver USB; Il driver client non può eseguire questa operazione. | Questa richiesta viene gestita dallo stack di driver USB; Il driver client non può eseguire questa operazione. |
Per le richieste specifiche della classe del dispositivo e i comandi del fornitore. |
|
|
_URB_CONTROL_VENDOR_OR_CLASS_REQUEST (UsbBuildVendorRequest) URB_FUNCTION_VENDOR_DEVICE URB_FUNCTION_VENDOR_INTERFACE URB_FUNCTION_VENDOR_ENDPOINT URB_FUNCTION_VENDOR_OTHER URB_FUNCTION_CLASS_DEVICE URB_FUNCTION_CLASS_INTERFACE URB_FUNCTION_CLASS_ENDPOINT URB_FUNCTION_CLASS_OTHER |
Come inviare un trasferimento di controllo per i comandi del fornitore - KMDF
Questa procedura illustra come un driver client può inviare un trasferimento di controllo. In questo esempio, il driver client invia un comando fornitore che recupera la versione del firmware dal dispositivo.
Dichiarare una costante per il comando fornitore. Studiare la specifica hardware e determinare il comando fornitore che si vuole usare.
Dichiarare una struttura WDF_MEMORY_DESCRIPTOR e inizializzarla chiamando la macro WDF_MEMORY_DESCRIPTOR_INIT_BUFFER . Questa struttura riceverà la risposta dal dispositivo dopo che il driver USB completa la richiesta.
A seconda che si invii la richiesta in modo sincrono o asincrono, specificare le opzioni di invio:
Se si invia la richiesta in modo sincrono chiamando WdfUsbTargetDeviceSendControlTransferSynchronously, specificare un valore di timeout. Questo valore è importante perché senza un timeout, è possibile bloccare il thread per un periodo illimitato.
A tale scopo, dichiarare una struttura WDF_REQUEST_SEND_OPTIONS e inizializzarla chiamando la macro WDF_REQUEST_SEND_OPTIONS_INIT . Specificare l'opzione come WDF_REQUEST_SEND_OPTION_TIMEOUT.
Impostare quindi il valore di timeout chiamando la macro WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT .
Se si invia la richiesta in modo asincrono, implementare una routine di completamento. Liberare tutte le risorse allocate nella routine di completamento.
Dichiarare una struttura WDF_USB_CONTROL_SETUP_PACKET per contenere il token di installazione e formattare la struttura. A tale scopo, chiamare la macro WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR per formattare il pacchetto di installazione. Nella chiamata specificare la direzione della richiesta, il destinatario, le opzioni di richiesta inviata (inizializzate nel passaggio 3) e la costante per il comando fornitore.
Inviare la richiesta chiamando WdfUsbTargetDeviceSendControlTransferSynchronously o WdfUsbTargetDeviceFormatRequestForControlTransfer.
Controllare il valore NTSTATUS restituito dal framework ed esaminare il valore ricevuto.
Questo esempio di codice invia una richiesta di trasferimento del controllo a un dispositivo USB per recuperare la versione del firmware. La richiesta viene inviata in modo sincrono e il driver client specifica un valore di timeout relativo di 5 secondi (in unità da 100 nanosecondi). Il driver archivia la risposta ricevuta nel contesto di dispositivo definito dal driver.
enum {
USBFX2_GET_FIRMWARE_VERSION = 0x1,
....
} USBFX2_VENDOR_COMMANDS;
#define WDF_TIMEOUT_TO_SEC ((LONGLONG) 1 * 10 * 1000 * 1000) // defined in wdfcore.h
const __declspec(selectany) LONGLONG
DEFAULT_CONTROL_TRANSFER_TIMEOUT = 5 * -1 * WDF_TIMEOUT_TO_SEC;
typedef struct _DEVICE_CONTEXT
{
...
union {
USHORT VersionAsUshort;
struct {
BYTE Minor;
BYTE Major;
} Version;
} Firmware; // Firmware version.
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
__drv_requiresIRQL(PASSIVE_LEVEL)
VOID GetFirmwareVersion(
__in PDEVICE_CONTEXT DeviceContext
)
{
NTSTATUS status;
WDF_USB_CONTROL_SETUP_PACKET controlSetupPacket;
WDF_REQUEST_SEND_OPTIONS sendOptions;
USHORT firmwareVersion;
WDF_MEMORY_DESCRIPTOR memoryDescriptor;
PAGED_CODE();
firmwareVersion = 0;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, (PVOID) &firmwareVersion, sizeof(firmwareVersion));
WDF_REQUEST_SEND_OPTIONS_INIT(
&sendOptions,
WDF_REQUEST_SEND_OPTION_TIMEOUT
);
WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(
&sendOptions,
DEFAULT_CONTROL_TRANSFER_TIMEOUT
);
WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR(&controlSetupPacket,
BmRequestDeviceToHost, // Direction of the request
BmRequestToDevice, // Recipient
USBFX2_GET_FIRMWARE_VERSION, // Vendor command
0, // Value
0); // Index
status = WdfUsbTargetDeviceSendControlTransferSynchronously(
DeviceContext->UsbDevice,
WDF_NO_HANDLE, // Optional WDFREQUEST
&sendOptions,
&controlSetupPacket,
&memoryDescriptor, // MemoryDescriptor
NULL); // BytesTransferred
if (!NT_SUCCESS(status))
{
KdPrint(("Device %d: Failed to get device firmware version 0x%x\n", DeviceContext->DeviceNumber, status));
TraceEvents(DeviceContext->DebugLog,
TRACE_LEVEL_ERROR,
DBG_RUN,
"Device %d: Failed to get device firmware version 0x%x\n",
DeviceContext->DeviceNumber,
status);
}
else
{
DeviceContext->Firmware.VersionAsUshort = firmwareVersion;
TraceEvents(DeviceContext->DebugLog,
TRACE_LEVEL_INFORMATION,
DBG_RUN,
"Device %d: Get device firmware version : 0x%x\n",
DeviceContext->DeviceNumber,
firmwareVersion);
}
return;
}
Come inviare un trasferimento di controllo per GET_STATUS - UMDF
Questa procedura illustra come un driver client può inviare un trasferimento di controllo per un comando GET_STATUS. Il destinatario della richiesta è il dispositivo e la richiesta ottiene informazioni in bit D1-D0. Per altre informazioni, vedere la figura 9-4 nella specifica USB.
Includere il file di intestazione Usb_hw.h disponibile con il driver di esempio UMDF per OSR USB Fx2 Learning Kit.
Dichiarare una struttura WINUSB_CONTROL_SETUP_PACKET .
Inizializzare il pacchetto di installazione chiamando la macro helper WINUSB_CONTROL_SETUP_PACKET_INIT_GET_STATUS.
Specificare BmRequestToDevice come destinatario.
Specificare 0 nel valore index .
Chiamare il metodo helper SendControlTransferSynchronously per inviare la richiesta in modo sincrono.
Il metodo helper compila la richiesta associando il pacchetto di installazione inizializzato all'oggetto richiesta framework e al buffer di trasferimento chiamando il metodo IWDFUsbTargetDevice::FormatRequestForControlTransfer . Il metodo helper invia quindi la richiesta chiamando il metodo IWDFIoRequest::Send . Al termine del metodo, controllare il valore restituito.
Per determinare se lo stato indica la riattivazione remota e self-powered, usare questi valori definiti nell'enumerazione WINUSB_DEVICE_TRAITS :
Questo esempio di codice invia una richiesta di trasferimento del controllo a un oggetto per ottenere lo stato del dispositivo. L'esempio invia la richiesta in modo sincrono chiamando un metodo helper denominato SendControlTransferSynchronously.
HRESULT
CDevice::GetDeviceStatus ()
{
HRESULT hr = S_OK;
USHORT deviceStatus;
ULONG bytesTransferred;
TraceEvents(TRACE_LEVEL_INFORMATION,
DRIVER_ALL_INFO,
"%!FUNC!: entry");
// Setup the control packet.
WINUSB_CONTROL_SETUP_PACKET setupPacket;
WINUSB_CONTROL_SETUP_PACKET_INIT_GET_STATUS(
&setupPacket,
BmRequestToDevice,
0);
hr = SendControlTransferSynchronously(
&(setupPacket.WinUsb),
& deviceStatus,
sizeof(USHORT),
&bytesReturned
);
if (SUCCEEDED(hr))
{
if (deviceStatus & USB_GETSTATUS_SELF_POWERED)
{
m_Self_Powered = true;
}
if (deviceStatus & USB_GETSTATUS_REMOTE_WAKEUP_ENABLED)
{
m_remote_wake-enabled = true;
}
}
return hr;
}
L'esempio di codice seguente illustra l'implementazione del metodo helper denominato SendControlTransferSynchronously. Questo metodo invia una richiesta in modo sincrono.
HRESULT
CDevice::SendControlTransferSynchronously(
_In_ PWINUSB_SETUP_PACKET SetupPacket,
_Inout_ PBYTE Buffer,
_In_ ULONG BufferLength,
_Out_ PULONG LengthTransferred
)
{
HRESULT hr = S_OK;
IWDFIoRequest *pWdfRequest = NULL;
IWDFDriver * FxDriver = NULL;
IWDFMemory * FxMemory = NULL;
IWDFRequestCompletionParams * FxComplParams = NULL;
IWDFUsbRequestCompletionParams * FxUsbComplParams = NULL;
*LengthTransferred = 0;
hr = m_FxDevice->CreateRequest( NULL, //pCallbackInterface
NULL, //pParentObject
&pWdfRequest);
if (SUCCEEDED(hr))
{
m_FxDevice->GetDriver(&FxDriver);
hr = FxDriver->CreatePreallocatedWdfMemory( Buffer,
BufferLength,
NULL, //pCallbackInterface
pWdfRequest, //pParetObject
&FxMemory );
}
if (SUCCEEDED(hr))
{
hr = m_pIUsbTargetDevice->FormatRequestForControlTransfer( pWdfRequest,
SetupPacket,
FxMemory,
NULL); //TransferOffset
}
if (SUCCEEDED(hr))
{
hr = pWdfRequest->Send( m_pIUsbTargetDevice,
WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
0); //Timeout
}
if (SUCCEEDED(hr))
{
pWdfRequest->GetCompletionParams(&FxComplParams);
hr = FxComplParams->GetCompletionStatus();
}
if (SUCCEEDED(hr))
{
HRESULT hrQI = FxComplParams->QueryInterface(IID_PPV_ARGS(&FxUsbComplParams));
WUDF_TEST_DRIVER_ASSERT(SUCCEEDED(hrQI));
WUDF_TEST_DRIVER_ASSERT( WdfUsbRequestTypeDeviceControlTransfer ==
FxUsbComplParams->GetCompletedUsbRequestType() );
FxUsbComplParams->GetDeviceControlTransferParameters( NULL,
LengthTransferred,
NULL,
NULL );
}
SAFE_RELEASE(FxUsbComplParams);
SAFE_RELEASE(FxComplParams);
SAFE_RELEASE(FxMemory);
pWdfRequest->DeleteWdfObject();
SAFE_RELEASE(pWdfRequest);
SAFE_RELEASE(FxDriver);
return hr;
}
Se si usa Winusb.sys come driver di funzione per il dispositivo, è possibile inviare trasferimenti di controllo da un'applicazione. Per formattare il pacchetto di installazione in WinUSB, usare le macro e le strutture helper UMDF descritte nella tabella in questo articolo. Per inviare la richiesta, chiamare WinUsb_ControlTransfer funzione.