Книга: Writing Windows WDM Device Drivers
USBDI IOCTLs
Разделы на этой странице:
USBDI IOCTLs
The USB class drivers are primarily used through the USB Device Interface (USBDI) Internal IOCTLs shown in Table 21.2. As these are Internal IOCTLs, they are only available to other parts of the kernel, such as device drivers, and are not available to user mode applications.
A few ordinary IOCTLs are also available to Win32 programs. These are intended for use by diagnostic utilities, so I shall not cover them here. The DDK UsbView utility source shows how to use these IOCTLs.
Table 21.2 USBDI internal IOCTLs
IOCTL_INTERNAL_USB_SUBMIT_URB |
Submit URBBlock awaiting result |
IOCTL_INTERNAL_USB_RESET_PORT |
Reset and reenable a port |
IOCTL_INTERNAL_USB_GET_PORT_STATUS |
Get port status bits: USBD_PORT_ENABLED USBD_PORT_CONNECTED |
IOCTL_INTERNAL_USB_ENABLE_PORT |
Reenable a disabled port |
IOCTL_INTERNAL_USB_GET_HUB_COUNT |
Used internally by hub driver |
IOCTL_INTERNAL_USB_CYCLE_PORT |
Simulates a device unplug and replug |
IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO |
Used internally by hub driver |
IOCTL_INTERNAL_USB_GET_HUB_NAME |
Get the device name of the USB hub |
IOCTL_INTERNAL_USB_GET_BUS_INFO |
Fills in a USB_BUS_NOTIFICATION structure (W2000 only) |
IOCTL_INTERNAL_USB_GET_CONTROLLER_NAME |
Get the host controller device name (W2000 only) |
URBs
The most important Internal IOCTL is IOCTL_INTERNAL_USB_SUBMIT_URB, which lets you submit a USB Request Block (URB) for processing by the USB class drivers. There are thirty-odd different URB function codes. USB clients use URBs to do most of their hard work.
The URB structure itself is a union of some 16 different _URB_* structures, as shown in Listing 21.1. Each function code uses one of these other URB structures to detail its input or output parameters. All URB structures begin with a common header _URB_HEADER structure. The header Length and Function fields must be filled in before calling the USB Device Interface. The result of processing the URB is returned in the Status field.
Listing 21.1 URB structures
typedef struct _URB {
union {
struct _URB_HEADER UrbHeader;
struct _URB_SELECT_INTERFACE UrbSelectInterface;
struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
// …
};
} URB, *PURB;
struct _URB_HEADER {
USHORT Length;
USHORT Function;
USBD_STATUS Status;
// …
};
The Status field uses its top two bits as a State code to indicate how the request completed. Table 21.3 shows the possible values along with the names of macros that can be used to detect these conditions. The rest of the Status field is filled with more detailed error codes. Some error codes are internal system USB errors (e.g., no memory).
Table 21.3 URB Status field State code bits
State code bits | Interpretation | Macro |
---|---|---|
00 | Completed successfully | USBD_SUCCESS |
01 | Request is pending | USBD_PENDING |
10 | Error, endpoint not stalled | USBD_ERROR |
11 | Error, endpoint stalled | USBD_ERROR or USBD_HALTED |
To make it easier to construct suitable URBs, various build macros are provided, such as UsbBuildGetDescriptorRequest, which fill in a preallocated URB. Other useful routines both allocate the memory for a URB and fill it in.
The reference section towards the end of this chapter lists the URB function codes. The reference section also details the other crucial USB structures. However, this chapter first illustrates how to perform most common USB actions by describing how these jobs are done in the UsbKbd driver.
Several URB structures have a UrbLink field. If non-NULL, this specifies a pointer to a URB that is processed if the current one completes successfully.
Calling USBDI
Listing 21.2 shows the Call USBDI routine in Usb.cpp. This is used to issue all the Internal lOCTLs to the USB system class drivers. It has default parameters that make it easy to ask for a URB to be processed.
CallUSBDI has to create a new IRP for the Internal IOCTL, fill in the IRP, and send off the IRP down the device stack to the USB system drivers. Further, it then waits until the IRP has been processed. CallUSBDI can only be called at PASSIVE_LEVEL
The USB Internal IOCTLs do not use the standard input and output IRP stack locations. Instead, the stack Parameters.Others.Argument1 field is set to the URB pointer, etc. The Parameters.Others.Argument2 field is used for one of the USB IOCTLs.
Allocating IRPs
If you want to call a lower driver, it is usually simplest to reuse an existing IRP. You simply fill in the next IRP stack location with the correct function codes and parameters and call the next driver down the stack.
In some cases however, you will need to build an IRP from scratch. For example, you may wish to generate an IRP in your DriverEntry routine. Alternatively, you may process a large incoming request by splitting it into several different IRPs.
UsbKbd could reuse an existing IRP. However, it is straightforward to allocate a new IOCTL IRP. The CallUSBDI routine keeps the entire IRP allocation process wrapped up in one neat location using this technique.
Building and issuing an IOCTL IRP is made particularly easy with the IoBuildDeviceIoControlRequest call. If you pass an initialized event, you can wait for the IRP to complete simply by waiting for the event to become signalled. You do not need to set up a completion routine.
IoBuilDeviceIoControlRequest can be used to make both IOCTL and Internal IOCTL IRPs, depending on the InternalDeviceIoControl parameter. In a similar way to the Win32 DeviceIoControl call, both input and output buffers can be used. The IOCTL control code must use buffered I/O.
Internally, it seems as though IoBuildDeviceIoControlRequest allocates some nonpaged memory to hold the combined input and output buffer, to make it work exactly like a standard IOCTL call. IoBuildDeviceIoControlRequest copies the input buffer there initially. It must set its own completion routine that copies the data back into your given output buffer (as well as setting the event and freeing the IRP memory).
The USB Internal IOCTLs do not use the standard input and output buffers[51]. Instead, you have to set up the next stack location Parameters.Others.Argument1 field. This is more complicated than you might think.
Previous examples have used IoSkipCurrentIrpStackLocation to reuse the current IRP stack location, and IoCopyCurrentIrpStackLocationToNext to copy the current stack location to the next when setting a completion routine. We cannot use these routines, as the current stack location is not set up yet.
The IoCallDriver call moves onto the next IRP stack location. CallUSBDI wants to change the stack location that the next lower driver sees. The IoGetNextIrpStackLocation call returns the required IRP stack location pointer[52]. The IoBuildDeviceIoControlRequest call has already set up most of the correct values for this stack location. The required values are set into the Parameters.Others.Argument1 field, etc.
In summary, CallUSBDI does the following jobs.
1. Initializes an IRP completion event
2. Builds an Internal IOCTL
3. Stores the URB pointer, etc., in the IRP's next stack location
4. Calls the next driver
5. If the request is still pending, wait for the completion event to become signalled. The KeWaitForSingleObjectWaitReason parameter must be set to Suspended.
Listing 21.2 Calling USBDI
NTSTATUS CallUSBDI(IN PUSBKBD_DEVICE_EXTENSION dx, IN PVOID UrbEtc,
IN ULONG IoControlCode/*=IOCTL_INTERNAL_USB_SUBMIT_URB*/,
IN ULONG Arg2/*=0*/) {
IO_STATUS_BLOCK IoStatus;
KEVENT event;
// Initialise IRP completion event
KeInitializeEvent(&event, NotificationEvent, FALSE);
// Build Internal IOCTL IRP
PIRP Irp = IoBuildDeviceIoControlRequest(
IoControlCode, dx->NextStackDevice,
NULL, 0, // Input buffer
NULL, 0, // Output buffer
TRUE, &event, &IoStatus);
// Get IRP stack location for next driver down (already set up)
PIO_STACK_LOCATION NextIrpStack = IoGetNextIrpStackLocation(Irp);
// Store pointer to the URB etc
NextIrpStack->Parameters.Others.Argument1 = UrbEtc;
NextIrpStack->Parameters.Others.Argument2 = (PVOID)Arg2;
// Call the driver and wait for completion if necessary
NTSTATUS status = IoCallDriver(dx->NextStackDevice, Irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL);
status = IoStatus.Status;
}
// return IRP completion status
return status;
}
Other IRP Allocations
Other kernel calls can be used to allocate IRPs. IoBuildSynchronousFsdRequest builds a Read, Write, Flush, or Shutdown IRP that uses an event to signal completion in the same way as IoBuildDeviceIoControlRequest. IoBuildSynchronousFsdRequest must be called at PASSIVE_LEVEL IRQL.
IoBuildAsynchronousFsdRequest works asynchronously, as it does not use an event to signal its completion. Consequently, it can be called at or below DISPATCH_LEVEL. Note that the IRP must be freed using IoFreeIrp. A common way to do this is to attach a completion routine. It is OK to call IoFreeIrp here and then return STATUS_MORE_PROCESSING_REQUIRED.
There are two final macho ways of building IRPs. IoAllocateIrp allocates an IRP, while IoInitializeIrp makes an IRP out of some driver allocated memory. Be very careful to set up all IRP and IRP stack locations correctly. Both of these methods allow you specify the size of the IRP stack size. Use IoGetNextIrpStackLocation to get the first IRP stack location if you need to set up a completion routine. Call IoFreeIrp to free an IRP created by IoAllocateIrp.
The old DDK documentation wrongly says that you can call IoInitializeIrp to reuse an IRP allocated using IoAllocateIrp. You can use this function as long as you preserve the IRP AllocationFlags field, as shown in Chapter 23. In W2000, you can reuse an IRP created with IoAllocateIrp using IoReuseIrp.
Multiple USBDI Calls
If you were reading carefully, you will have noticed that the Call USBDI routine can only be called at PASSIVE_LEVEL. This means that it cannot be called from a StartIo routine, which runs at DISPATCH_LEVEL. UsbKbd makes its USB calls direct from its dispatch routines that run at PASSIVE_LEVEL.
In UsbKbd, it is possible for a user program to issue two overlapped read requests "simultaneously". This might easily result in the USB class drivers being sent two IRPs for processing at the same time. There is nothing in the documentation that says this is a problem. I suspect that it is not, as the USB class driver will almost certainly serialize requests.
If you feel that you ought to serialize your USBDI calls, you will have to use a StartIo routine. A single prebuilt IRP can be reused for each call. Chapter 23 shows how to build, use, and free such an IRP.
In many cases, it might be useful to send off a series of IRPs to the USB class drivers. As there will usually be a spare IRP queued up for processing, no incoming data will be lost.