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.

Talking USB

Initializing a USB Device

There are several jobs that a USB client driver must do to initialize its connection to its device. The UsbKbd driver does these jobs in its Create IRP handler. However, most USB drivers will want to initialize their device when processing the Start Device Plug and Play IRP.

1. Check device enabled. Reset and enable the device, if necessary.

2. Select one interface in one of the configurations.

3. Possibly read other descriptors, such as the string-, class-, or vendor-specific descriptors.

4. Talk to your device to issue whatever commands are relevant, get device status, and initialize pipes.

Device Reset

Listing 21.3 shows how UsbKbd resets its device in routine UsbResetDevice when a Win32 program opens a handle to it. UsbGetPortStatus issues IOCTL_INTERNAL_USB_GET_PORT_STATUS to retrieve the port status bits. UsbResetDevice checks the USBD_PORT_CONNECTED and USBD_PORT_ENABLED bits, and calls UsbResetPort, if necessary. UsbResetPort simply issues IOCTL_INTERNAL_USB_RESET_PORT to the USB class drivers.

You may need to reset the port if some drastic communication problem has arisen with your device (e.g., if control transfers to the default pipe keep failing). However, if another pipe stalls, do not reset the port.

If a pipe stalls or USBDI detects a timeout, the pipe becomes halted. The USBD_HALTED macro detects this condition. All URBs linked to the current URB are cancelled. A halted pipe cannot accept any more transfers until a Reset Pipe URB is issued.

However, the default pipe can never be halted. If a timeout or stall occurs here, an error is reported, which is detectable using the USBD_ERROR macro, but not USBD_HALTED. The halt condition is cleared automatically to give transfers a chance of succeeding on this pipe. If they keep failing, you will have to reset the port.

Listing 21.3 Device reset routines

NTSTATUS UsbGetPortStatus(IN PUSBKBD_DEVICE_EXTENSION dx, OUT ULONG& PortStatus) {

 DebugPrintMsg('Getting port status');

 PortStatus = 0;

 NTSTATUS status = CallUSBDI(dx, &PortStatus, IOCTL_INTERNAL_USB_GET_PORT_STATUS);

 DebugPrint('Got port status %x', PortStatus);

 return status;

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×