/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: usbcomp.c Abstract: This module implements support for USB compound devices (devices with multiple interfaces). Author: Evan Green 20-Mar-2013 Environment: Kernel --*/ // // ------------------------------------------------------------------- Includes // #include #include // // --------------------------------------------------------------------- Macros // // // ---------------------------------------------------------------- Definitions // // // Define the allocation tag used throughout the USB compound device driver. // #define USB_COMPOUND_ALLOCATION_TAG 0x43627355 // 'CbsU' // // ------------------------------------------------------ Data Type Definitions // /*++ Structure Description: This structure stores context about a USB compound device. Members: UsbCoreHandle - Stores the handle to the device as identified by the USB core library. InterfaceCount - Stores the number of interfaces this device has. Children - Stores the array of pointers to child devices, one for each exposed interface. --*/ typedef struct _USB_COMPOUND_DEVICE { HANDLE UsbCoreHandle; ULONG InterfaceCount; PDEVICE *Children; } USB_COMPOUND_DEVICE, *PUSB_COMPOUND_DEVICE; // // ----------------------------------------------- Internal Function Prototypes // KSTATUS UsbCmpAddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ); VOID UsbCmpDispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID UsbCmpDispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID UsbCmpDispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID UsbCmpDispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID UsbCmpDispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); KSTATUS UsbCmppStartDevice ( PIRP Irp, PUSB_COMPOUND_DEVICE Device ); KSTATUS UsbCmppEnumerateChildren ( PIRP Irp, PUSB_COMPOUND_DEVICE Device ); VOID UsbCmppRemoveDevice ( PIRP Irp, PUSB_COMPOUND_DEVICE Device ); // // -------------------------------------------------------------------- Globals // PDRIVER UsbCmpDriver = NULL; // // ------------------------------------------------------------------ Functions // KSTATUS DriverEntry ( PDRIVER Driver ) /*++ Routine Description: This routine is the entry point for the USB compound device driver. It registers the other dispatch functions, and performs driver-wide initialization. Arguments: Driver - Supplies a pointer to the driver object. Return Value: STATUS_SUCCESS on success. Failure code on error. --*/ { DRIVER_FUNCTION_TABLE FunctionTable; KSTATUS Status; UsbCmpDriver = Driver; RtlZeroMemory(&FunctionTable, sizeof(DRIVER_FUNCTION_TABLE)); FunctionTable.Version = DRIVER_FUNCTION_TABLE_VERSION; FunctionTable.AddDevice = UsbCmpAddDevice; FunctionTable.DispatchStateChange = UsbCmpDispatchStateChange; FunctionTable.DispatchOpen = UsbCmpDispatchOpen; FunctionTable.DispatchClose = UsbCmpDispatchClose; FunctionTable.DispatchIo = UsbCmpDispatchIo; FunctionTable.DispatchSystemControl = UsbCmpDispatchSystemControl; Status = IoRegisterDriverFunctions(Driver, &FunctionTable); return Status; } // // --------------------------------------------------------- Internal Functions // KSTATUS UsbCmpAddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ) /*++ Routine Description: This routine is called when a device is detected for which the USB compound device driver acts as the function driver. The driver will attach itself to the stack. Arguments: Driver - Supplies a pointer to the driver being called. DeviceId - Supplies a pointer to a string with the device ID. ClassId - Supplies a pointer to a string containing the device's class ID. CompatibleIds - Supplies a pointer to a string containing device IDs that would be compatible with this device. DeviceToken - Supplies an opaque token that the driver can use to identify the device in the system. This token should be used when attaching to the stack. Return Value: STATUS_SUCCESS on success. Failure code if the driver was unsuccessful in attaching itself. --*/ { PUSB_COMPOUND_DEVICE NewDevice; KSTATUS Status; // // Create the device context and attach to the device. // NewDevice = MmAllocatePagedPool(sizeof(USB_COMPOUND_DEVICE), USB_COMPOUND_ALLOCATION_TAG); if (NewDevice == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(NewDevice, sizeof(USB_COMPOUND_DEVICE)); NewDevice->UsbCoreHandle = INVALID_HANDLE; // // Attempt to attach to the USB core. // Status = UsbDriverAttach(DeviceToken, UsbCmpDriver, &(NewDevice->UsbCoreHandle)); if (!KSUCCESS(Status)) { goto AddDeviceEnd; } ASSERT(NewDevice->UsbCoreHandle != INVALID_HANDLE); Status = IoAttachDriverToDevice(Driver, DeviceToken, NewDevice); AddDeviceEnd: if (!KSUCCESS(Status)) { if (NewDevice->UsbCoreHandle != INVALID_HANDLE) { UsbDeviceClose(NewDevice->UsbCoreHandle); } if (NewDevice != NULL) { MmFreePagedPool(NewDevice); } } return Status; } VOID UsbCmpDispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles State Change IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { PUSB_COMPOUND_DEVICE Device; KSTATUS Status; ASSERT(Irp->MajorCode == IrpMajorStateChange); Device = (PUSB_COMPOUND_DEVICE)DeviceContext; // // If this is the parent device, enumerate children. // if (Device != NULL) { switch (Irp->MinorCode) { case IrpMinorQueryResources: if (Irp->Direction == IrpUp) { IoCompleteIrp(UsbCmpDriver, Irp, STATUS_SUCCESS); } break; case IrpMinorStartDevice: // // Attempt to fire the thing up if the bus has already started it. // if (Irp->Direction == IrpUp) { Status = UsbCmppStartDevice(Irp, Device); IoCompleteIrp(UsbCmpDriver, Irp, Status); } break; case IrpMinorQueryChildren: if (Irp->Direction == IrpUp) { Status = UsbCmppEnumerateChildren(Irp, Device); IoCompleteIrp(UsbCmpDriver, Irp, Status); } break; case IrpMinorRemoveDevice: if (Irp->Direction == IrpUp) { UsbCmppRemoveDevice(Irp, Device); } break; // // For all other IRPs, do nothing. // default: break; } // // If this driver is acting as the bus driver for the child device, then // simply complete things as a happy bus. // } else { switch (Irp->MinorCode) { case IrpMinorRemoveDevice: case IrpMinorQueryResources: case IrpMinorStartDevice: case IrpMinorQueryChildren: IoCompleteIrp(UsbCmpDriver, Irp, STATUS_SUCCESS); break; default: break; } } return; } VOID UsbCmpDispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Open IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID UsbCmpDispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Close IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID UsbCmpDispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles I/O IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID UsbCmpDispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles System Control IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { ASSERT(Irp->MajorCode == IrpMajorSystemControl); // // Do no processing on any IRPs. Let them flow. // return; } KSTATUS UsbCmppStartDevice ( PIRP Irp, PUSB_COMPOUND_DEVICE Device ) /*++ Routine Description: This routine starts up the USB compound device. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this USB compound device. Return Value: Status code. --*/ { ULONG AllocationSize; PUSB_CONFIGURATION_DESCRIPTION Configuration; PLIST_ENTRY CurrentEntry; ULONG InterfaceCount; KSTATUS Status; // // If the configuration isn't yet set, set the first one. // Configuration = UsbGetActiveConfiguration(Device->UsbCoreHandle); if (Configuration == NULL) { Status = UsbSetConfiguration(Device->UsbCoreHandle, 0, TRUE); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } Configuration = UsbGetActiveConfiguration(Device->UsbCoreHandle); ASSERT(Configuration != NULL); } if (Device->InterfaceCount == 0) { // // Loop through once counting the number of interfaces. // InterfaceCount = 0; CurrentEntry = Configuration->InterfaceListHead.Next; while (CurrentEntry != &(Configuration->InterfaceListHead)) { CurrentEntry = CurrentEntry->Next; InterfaceCount += 1; } if (InterfaceCount == 0) { ASSERT(FALSE); Status = STATUS_NO_INTERFACE; goto StartDeviceEnd; } // // Allocate the device pointer list. // AllocationSize = InterfaceCount * sizeof(PDEVICE); Device->Children = MmAllocatePagedPool(AllocationSize, USB_COMPOUND_ALLOCATION_TAG); if (Device->Children == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto StartDeviceEnd; } RtlZeroMemory(Device->Children, AllocationSize); Device->InterfaceCount = InterfaceCount; } Status = STATUS_SUCCESS; StartDeviceEnd: return Status; } KSTATUS UsbCmppEnumerateChildren ( PIRP Irp, PUSB_COMPOUND_DEVICE Device ) /*++ Routine Description: This routine enumerates the children of the given USB compound device. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this USB compound device. Return Value: Status code. --*/ { PUSB_CONFIGURATION_DESCRIPTION Configuration; PLIST_ENTRY CurrentEntry; PUSB_INTERFACE_DESCRIPTION Interface; ULONG InterfaceIndex; KSTATUS Status; Configuration = UsbGetActiveConfiguration(Device->UsbCoreHandle); if (Configuration == NULL) { Status = STATUS_NOT_CONFIGURED; goto EnumerateChildrenEnd; } // // Loop through each child. // CurrentEntry = Configuration->InterfaceListHead.Next; for (InterfaceIndex = 0; InterfaceIndex < Device->InterfaceCount; InterfaceIndex += 1) { if (CurrentEntry == &(Configuration->InterfaceListHead)) { ASSERT(FALSE); Status = STATUS_DATA_LENGTH_MISMATCH; goto EnumerateChildrenEnd; } Interface = LIST_VALUE(CurrentEntry, USB_INTERFACE_DESCRIPTION, ListEntry); CurrentEntry = CurrentEntry->Next; // // Ask the USB core to enumerate a device for this interface. // Status = UsbEnumerateDeviceForInterface( Device->UsbCoreHandle, Interface, &(Device->Children[InterfaceIndex])); if (!KSUCCESS(Status)) { goto EnumerateChildrenEnd; } } Status = IoMergeChildArrays(Irp, Device->Children, Device->InterfaceCount, USB_COMPOUND_ALLOCATION_TAG); if (!KSUCCESS(Status)) { goto EnumerateChildrenEnd; } EnumerateChildrenEnd: return Status; } VOID UsbCmppRemoveDevice ( PIRP Irp, PUSB_COMPOUND_DEVICE Device ) /*++ Routine Description: This routine removes the USB compound device. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this USB compound device. Return Value: Status code. --*/ { // // Detach the device from USB core's grasp. This marks it as disconnected. // UsbDetachDevice(Device->UsbCoreHandle); // // Destroy the interface device list. By the time the removal IRP reaches // the compound device driver, all of the children have already been // released. Do not interate over the pointers in this array because they // are invalid. // if (Device->Children != NULL) { MmFreePagedPool(Device->Children); } // // Release the reference taken on the USB core handle. This will clean up // the cached configurations. // UsbDeviceClose(Device->UsbCoreHandle); // // Release the USB compound device. // MmFreePagedPool(Device); return; }