/*++ Copyright (c) 2014 Minoca Corp. All Rights Reserved Module Name: dwhci.c Abstract: This module implements support for the DesignWare High-Speed USB 2.0 On-The-Go (HS OTG) host controller. Author: Chris Stevens 28-Mar-2014 Environment: Kernel --*/ // // ------------------------------------------------------------------- Includes // #include #include #include "dwhci.h" // // --------------------------------------------------------------------- Macros // // // ---------------------------------------------------------------- Definitions // // // ------------------------------------------------------ Data Type Definitions // /*++ Structure Description: This structure stores context about a DWHCI Host Controller. Members: InterruptLine - Stores the interrupt line that this controller's interrupt comes in on. InterruptVector - Stores the interrupt vector that this controller's interrupt comes in on. InterruptResourcesFound - Stores a boolean indicating whether or not the interrupt line and interrupt vector fields are valid. InterruptHandle - Stores a pointer to the handle received when the interrupt was connected. Controller - Stores a pointer to the OTG controller. RegisterBasePhysical - Stores the physical memory address where the DWHCI registers are located. RegisterBase - Stores a pointer to the virtual address where the DWHCI registers are located. ChannelCount - Stores the number of channels for this host controller. Speed - Stores the speed of the DWHCI host controller. MaxTransferSize - Stores the maximum tranfer size for the DWHCI host controller. MaxPacketCount - Store the maximum packet count for the DWHCI host controller. Revision - Stores the revision of the DWHCI host controller. --*/ typedef struct _DWHCI_CONTROLLER_CONTEXT { ULONGLONG InterruptLine; ULONGLONG InterruptVector; BOOL InterruptResourcesFound; HANDLE InterruptHandle; PDWHCI_CONTROLLER Controller; PHYSICAL_ADDRESS RegisterBasePhysical; PVOID RegisterBase; ULONG ChannelCount; USB_DEVICE_SPEED Speed; ULONG MaxTransferSize; ULONG MaxPacketCount; ULONG Revision; } DWHCI_CONTROLLER_CONTEXT, *PDWHCI_CONTROLLER_CONTEXT; // // ----------------------------------------------- Internal Function Prototypes // KSTATUS DwhciAddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ); VOID DwhciDispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID DwhciDispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID DwhciDispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID DwhciDispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID DwhciDispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); KSTATUS DwhcipProcessResourceRequirements ( PIRP Irp, PDWHCI_CONTROLLER_CONTEXT Device ); KSTATUS DwhcipStartDevice ( PIRP Irp, PDWHCI_CONTROLLER_CONTEXT Device ); VOID DwhcipEnumerateChildren ( PIRP Irp, PDWHCI_CONTROLLER_CONTEXT Device ); KSTATUS DwhcipGatherControllerParameters ( PDWHCI_CONTROLLER_CONTEXT ControllerContext, PRESOURCE_ALLOCATION ControllerBase ); // // -------------------------------------------------------------------- Globals // PDRIVER DwhciDriver = NULL; // // ------------------------------------------------------------------ Functions // KSTATUS DriverEntry ( PDRIVER Driver ) /*++ Routine Description: This routine is the entry point for the DWHCI driver. It registers its 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; DwhciDriver = Driver; RtlZeroMemory(&FunctionTable, sizeof(DRIVER_FUNCTION_TABLE)); FunctionTable.Version = DRIVER_FUNCTION_TABLE_VERSION; FunctionTable.AddDevice = DwhciAddDevice; FunctionTable.DispatchStateChange = DwhciDispatchStateChange; FunctionTable.DispatchOpen = DwhciDispatchOpen; FunctionTable.DispatchClose = DwhciDispatchClose; FunctionTable.DispatchIo = DwhciDispatchIo; FunctionTable.DispatchSystemControl = DwhciDispatchSystemControl; Status = IoRegisterDriverFunctions(Driver, &FunctionTable); return Status; } // // --------------------------------------------------------- Internal Functions // KSTATUS DwhciAddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ) /*++ Routine Description: This routine is called when a device is detected for which the DWHCI 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. --*/ { PDWHCI_CONTROLLER_CONTEXT NewDevice; KSTATUS Status; // // Create the device context and attach to the device. // NewDevice = MmAllocateNonPagedPool(sizeof(DWHCI_CONTROLLER_CONTEXT), DWHCI_ALLOCATION_TAG); if (NewDevice == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(NewDevice, sizeof(DWHCI_CONTROLLER_CONTEXT)); NewDevice->InterruptHandle = INVALID_HANDLE; Status = IoAttachDriverToDevice(Driver, DeviceToken, NewDevice); return Status; } VOID DwhciDispatchStateChange ( 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. --*/ { PDWHCI_CONTROLLER_CONTEXT Device; KSTATUS Status; ASSERT(Irp->MajorCode == IrpMajorStateChange); Device = (PDWHCI_CONTROLLER_CONTEXT)DeviceContext; // // If there is no controller context, then DWHCI is acting as the bus // driver for the root hub. Simply complete standard IRPs. // if (Device == NULL) { switch (Irp->MinorCode) { case IrpMinorQueryResources: case IrpMinorStartDevice: case IrpMinorQueryChildren: IoCompleteIrp(DwhciDriver, Irp, STATUS_SUCCESS); break; default: break; } return; } switch (Irp->MinorCode) { case IrpMinorQueryResources: // // On the way up, filter the resource requirements to add interrupt // vectors to any lines. // if (Irp->Direction == IrpUp) { Status = DwhcipProcessResourceRequirements(Irp, Device); if (!KSUCCESS(Status)) { IoCompleteIrp(DwhciDriver, Irp, Status); } } break; case IrpMinorStartDevice: // // Attempt to fire the thing up if the bus has already started it. // if (Irp->Direction == IrpUp) { Status = DwhcipStartDevice(Irp, Device); if (!KSUCCESS(Status)) { IoCompleteIrp(DwhciDriver, Irp, Status); } } break; case IrpMinorQueryChildren: if (Irp->Direction == IrpUp) { DwhcipEnumerateChildren(Irp, Device); } break; case IrpMinorRemoveDevice: ASSERT(FALSE); break; // // For all other IRPs, do nothing. // default: break; } return; } VOID DwhciDispatchOpen ( 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 DwhciDispatchClose ( 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 DwhciDispatchIo ( 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 DwhciDispatchSystemControl ( 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 DwhcipProcessResourceRequirements ( PIRP Irp, PDWHCI_CONTROLLER_CONTEXT Device ) /*++ Routine Description: This routine filters through the resource requirements presented by the bus for a DWHCI Host controller. It adds an interrupt vector requirement for any interrupt line requested. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this DWHCI device. Return Value: Status code. --*/ { PRESOURCE_CONFIGURATION_LIST Requirements; KSTATUS Status; RESOURCE_REQUIREMENT VectorRequirement; ASSERT((Irp->MajorCode == IrpMajorStateChange) && (Irp->MinorCode == IrpMinorQueryResources)); // // Initialize a nice interrupt vector requirement in preparation. // RtlZeroMemory(&VectorRequirement, sizeof(RESOURCE_REQUIREMENT)); VectorRequirement.Type = ResourceTypeInterruptVector; VectorRequirement.Minimum = 0; VectorRequirement.Maximum = -1; VectorRequirement.Length = 1; // // Loop through all configuration lists, creating a vector for each line. // Requirements = Irp->U.QueryResources.ResourceRequirements; Status = IoCreateAndAddInterruptVectorsForLines(Requirements, &VectorRequirement); if (!KSUCCESS(Status)) { goto ProcessResourceRequirementsEnd; } ProcessResourceRequirementsEnd: return Status; } KSTATUS DwhcipStartDevice ( PIRP Irp, PDWHCI_CONTROLLER_CONTEXT Device ) /*++ Routine Description: This routine starts up the DWHCI controller. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this DWHCI device. Return Value: Status code. --*/ { PRESOURCE_ALLOCATION Allocation; PRESOURCE_ALLOCATION_LIST AllocationList; IO_CONNECT_INTERRUPT_PARAMETERS Connect; PDWHCI_CONTROLLER Controller; PRESOURCE_ALLOCATION ControllerBase; PRESOURCE_ALLOCATION LineAllocation; KSTATUS Status; Controller = NULL; ControllerBase = NULL; // // Loop through the allocated resources to get the controller base and the // interrupt. // AllocationList = Irp->U.StartDevice.ProcessorLocalResources; Allocation = IoGetNextResourceAllocation(AllocationList, NULL); while (Allocation != NULL) { // // If the resource is an interrupt vector, then it should have an // owning interrupt line allocation. // if (Allocation->Type == ResourceTypeInterruptVector) { // // Currently only one interrupt resource is expected. // ASSERT(Device->InterruptResourcesFound == FALSE); ASSERT(Allocation->OwningAllocation != NULL); // // Save the line and vector number. // LineAllocation = Allocation->OwningAllocation; Device->InterruptLine = LineAllocation->Allocation; Device->InterruptVector = Allocation->Allocation; Device->InterruptResourcesFound = TRUE; } else if (Allocation->Type == ResourceTypePhysicalAddressSpace) { ASSERT(ControllerBase == NULL); ControllerBase = Allocation; } // // Get the next allocation in the list. // Allocation = IoGetNextResourceAllocation(AllocationList, Allocation); } // // Fail to start if the controller base was not found. // if (ControllerBase == NULL) { Status = STATUS_INVALID_CONFIGURATION; goto StartDeviceEnd; } // // Get DWHCI register parameters, including the register base and port // count. // Status = DwhcipGatherControllerParameters(Device, ControllerBase); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } ASSERT(ControllerBase->Allocation == Device->RegisterBasePhysical); // // Allocate the controller structures. // Controller = DwhcipInitializeControllerState(Device->RegisterBase, Device->ChannelCount, Device->Speed, Device->MaxTransferSize, Device->MaxPacketCount, Device->Revision); if (Controller == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto StartDeviceEnd; } Device->Controller = Controller; // // Start up the controller. // Status = DwhcipInitializeController(Controller); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } // // Register the device with the USB core. This is required before enabling // the interrupt. // Status = DwhcipRegisterController(Controller, Irp->Device); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } // // Attempt to connect the interrupt. // ASSERT(Device->InterruptHandle == INVALID_HANDLE); RtlZeroMemory(&Connect, sizeof(IO_CONNECT_INTERRUPT_PARAMETERS)); Connect.Version = IO_CONNECT_INTERRUPT_PARAMETERS_VERSION; Connect.Device = Irp->Device; Connect.LineNumber = Device->InterruptLine; Connect.Vector = Device->InterruptVector; Connect.InterruptServiceRoutine = DwhcipInterruptService; Connect.Context = Device->Controller; Connect.Interrupt = &(Device->InterruptHandle); Status = IoConnectInterrupt(&Connect); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } DwhcipSetInterruptHandle(Controller, Device->InterruptHandle); StartDeviceEnd: if (!KSUCCESS(Status)) { if (Device->InterruptHandle != INVALID_HANDLE) { IoDisconnectInterrupt(Device->InterruptHandle); Device->InterruptHandle = INVALID_HANDLE; } if (Controller != NULL) { DwhcipDestroyControllerState(Controller); } } return Status; } VOID DwhcipEnumerateChildren ( PIRP Irp, PDWHCI_CONTROLLER_CONTEXT Device ) /*++ Routine Description: This routine enumerates the root hub of a DWHCI controller. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this DWHCI device. Return Value: None. --*/ { KSTATUS Status; // // Forward this on to the USB core to figure out. // Status = UsbHostQueryChildren(Irp, Device->Controller->UsbCoreHandle); IoCompleteIrp(DwhciDriver, Irp, Status); return; } KSTATUS DwhcipGatherControllerParameters ( PDWHCI_CONTROLLER_CONTEXT ControllerContext, PRESOURCE_ALLOCATION ControllerBase ) /*++ Routine Description: This routine pokes around and collects various pieces of needed information for the controller, such as the register base and port count. Arguments: ControllerContext - Supplies a pointer to the DWHCI controller context. ControllerBase - Supplies a pointer to the resource allocation defining the location of the controller's registers. Return Value: Status code. --*/ { ULONG AlignmentOffset; ULONG ChannelCount; PHYSICAL_ADDRESS EndAddress; ULONG PageSize; PHYSICAL_ADDRESS PhysicalAddress; PVOID RegisterAddress; ULONG RegisterValue; ULONG Size; KSTATUS Status; PVOID VirtualAddress; ULONG Width; PageSize = MmPageSize(); ControllerContext->RegisterBasePhysical = ControllerBase->Allocation; // // Initialize and map the DWHCI registers. // if (ControllerContext->RegisterBase == NULL) { PhysicalAddress = ControllerContext->RegisterBasePhysical; EndAddress = PhysicalAddress + ControllerBase->Length; PhysicalAddress = ALIGN_RANGE_DOWN(PhysicalAddress, PageSize); AlignmentOffset = ControllerContext->RegisterBasePhysical - PhysicalAddress; EndAddress = ALIGN_RANGE_UP(EndAddress, PageSize); Size = (ULONG)(EndAddress - PhysicalAddress); VirtualAddress = MmMapPhysicalAddress(PhysicalAddress, Size, TRUE, FALSE, TRUE); if (VirtualAddress == NULL) { Status = STATUS_NO_MEMORY; goto GatherControllerParametersEnd; } ControllerContext->RegisterBase = VirtualAddress + AlignmentOffset; } // // Read the host channel count. The stored value is one less than the // actual number of channels. // if (ControllerContext->ChannelCount == 0) { RegisterAddress = ControllerContext->RegisterBase + DwhciRegisterHardware2; RegisterValue = HlReadRegister32(RegisterAddress); ChannelCount = 1 + ((RegisterValue & DWHCI_HARDWARE2_HOST_CHANNEL_COUNT_MASK) >> DWHCI_HARDWARE2_HOST_CHANNEL_COUNT_SHIFT); ControllerContext->ChannelCount = ChannelCount; } if (ControllerContext->ChannelCount == 0) { ASSERT(FALSE); Status = STATUS_NO_SUCH_DEVICE; goto GatherControllerParametersEnd; } // // Determine the speed of the DWHCI host controller. // if (ControllerContext->Speed == UsbDeviceSpeedInvalid) { RegisterAddress = ControllerContext->RegisterBase + DwhciRegisterHardware2; RegisterValue = HlReadRegister32(RegisterAddress); if ((RegisterValue & DWHCI_HARDWARE2_HIGH_SPEED_MASK) == DWHCI_HARDWARE2_HIGH_SPEED_NOT_SUPPORTED) { ControllerContext->Speed = UsbDeviceSpeedFull; } else { ControllerContext->Speed = UsbDeviceSpeedHigh; } } // // Determine the maximum transfer size and the maximum packet count for the // DWHCI host controller. // if ((ControllerContext->MaxTransferSize == 0) || (ControllerContext->MaxPacketCount == 0)) { RegisterAddress = ControllerContext->RegisterBase + DwhciRegisterHardware3; RegisterValue = HlReadRegister32(RegisterAddress); Width = (RegisterValue & DWHCI_HARDWARE3_TRANSFER_SIZE_WIDTH_MASK) >> DWHCI_HARDWARE3_TRANSFER_SIZE_WIDTH_SHIFT; Width += DWHCI_HARDWARE3_TRANSFER_SIZE_WIDTH_OFFSET; ControllerContext->MaxTransferSize = (1 << Width) - 1; Width = (RegisterValue & DWHCI_HARDWARE3_PACKET_COUNT_WIDTH_MASK) >> DWHCI_HARDWARE3_PACKET_COUNT_WIDTH_SHIFT; Width += DWHCI_HARDWARE3_PACKET_COUNT_WIDTH_OFFSET; ControllerContext->MaxPacketCount = (1 << Width) - 1; } if ((ControllerContext->MaxTransferSize == 0) || (ControllerContext->MaxPacketCount == 0)) { ASSERT(FALSE); Status = STATUS_NO_SUCH_DEVICE; goto GatherControllerParametersEnd; } // // Query the revision. // RegisterAddress = ControllerContext->RegisterBase + DwhciRegisterCoreId; ControllerContext->Revision = HlReadRegister32(RegisterAddress); Status = STATUS_SUCCESS; GatherControllerParametersEnd: return Status; }