/* * PROJECT: ReactOS Universal Serial Bus Bulk Enhanced Host Controller Interface * LICENSE: GPL - See COPYING in the top level directory * FILE: drivers/usb/usbehci/hcd_controller.cpp * PURPOSE: USB EHCI device driver. * PROGRAMMERS: * Michael Martin (michael.martin@reactos.org) * Johannes Anderwald (johannes.anderwald@reactos.org) */ #include "usbehci.h" #define NDEBUG #include typedef VOID __stdcall HD_INIT_CALLBACK(IN PVOID CallBackContext); BOOLEAN NTAPI InterruptServiceRoutine( IN PKINTERRUPT Interrupt, IN PVOID ServiceContext); VOID NTAPI EhciDeferredRoutine( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2); VOID NTAPI StatusChangeWorkItemRoutine(PVOID Context); class CUSBHardwareDevice : public IEHCIHardwareDevice { public: STDMETHODIMP QueryInterface( REFIID InterfaceId, PVOID* Interface); STDMETHODIMP_(ULONG) AddRef() { InterlockedIncrement(&m_Ref); return m_Ref; } STDMETHODIMP_(ULONG) Release() { InterlockedDecrement(&m_Ref); if (!m_Ref) { delete this; return 0; } return m_Ref; } // com IMP_IUSBHARDWAREDEVICE IMP_IUSBEHCIHARDWARE // local BOOLEAN InterruptService(); VOID PrintCapabilities(); NTSTATUS StartController(); NTSTATUS StopController(); NTSTATUS ResetController(); // friend function friend BOOLEAN NTAPI InterruptServiceRoutine(IN PKINTERRUPT Interrupt, IN PVOID ServiceContext); friend VOID NTAPI EhciDeferredRoutine(IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2); friend VOID NTAPI StatusChangeWorkItemRoutine(PVOID Context); // constructor / destructor CUSBHardwareDevice(IUnknown *OuterUnknown){} virtual ~CUSBHardwareDevice(){} protected: LONG m_Ref; // reference count PDRIVER_OBJECT m_DriverObject; // driver object PDEVICE_OBJECT m_PhysicalDeviceObject; // pdo PDEVICE_OBJECT m_FunctionalDeviceObject; // fdo (hcd controller) PDEVICE_OBJECT m_NextDeviceObject; // lower device object KSPIN_LOCK m_Lock; // hardware lock PKINTERRUPT m_Interrupt; // interrupt object KDPC m_IntDpcObject; // dpc object for deferred isr processing PVOID VirtualBase; // virtual base for memory manager PHYSICAL_ADDRESS PhysicalAddress; // physical base for memory manager PULONG m_Base; // EHCI operational port base registers PDMA_ADAPTER m_Adapter; // dma adapter object ULONG m_MapRegisters; // map registers count EHCI_CAPS m_Capabilities; // EHCI caps USHORT m_VendorID; // vendor id USHORT m_DeviceID; // device id PQUEUE_HEAD AsyncQueueHead; // async queue head terminator PEHCIQUEUE m_UsbQueue; // usb request queue PDMAMEMORYMANAGER m_MemoryManager; // memory manager HD_INIT_CALLBACK* m_SCECallBack; // status change callback routine PVOID m_SCEContext; // status change callback routine context BOOLEAN m_DoorBellRingInProgress; // door bell ring in progress WORK_QUEUE_ITEM m_StatusChangeWorkItem; // work item for status change callback volatile LONG m_StatusChangeWorkItemStatus; // work item status ULONG m_SyncFramePhysAddr; // periodic frame list physical address BUS_INTERFACE_STANDARD m_BusInterface; // pci bus interface BOOLEAN m_PortResetInProgress[0xF]; // stores reset in progress (vbox hack) // read register ULONG EHCI_READ_REGISTER_ULONG(ULONG Offset); // write register VOID EHCI_WRITE_REGISTER_ULONG(ULONG Offset, ULONG Value); }; //================================================================================================= // COM // NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::QueryInterface( IN REFIID refiid, OUT PVOID* Output) { if (IsEqualGUIDAligned(refiid, IID_IUnknown)) { *Output = PVOID(PUNKNOWN(this)); PUNKNOWN(*Output)->AddRef(); return STATUS_SUCCESS; } return STATUS_UNSUCCESSFUL; } LPCSTR STDMETHODCALLTYPE CUSBHardwareDevice::GetUSBType() { return "USBEHCI"; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::Initialize( PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT FunctionalDeviceObject, PDEVICE_OBJECT PhysicalDeviceObject, PDEVICE_OBJECT LowerDeviceObject) { PCI_COMMON_CONFIG PciConfig; NTSTATUS Status; ULONG BytesRead; DPRINT("CUSBHardwareDevice::Initialize\n"); // // Create DMAMemoryManager for use with QueueHeads and Transfer Descriptors. // Status = CreateDMAMemoryManager(&m_MemoryManager); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to create DMAMemoryManager Object\n"); return Status; } // // Create the UsbQueue class that will handle the Asynchronous and Periodic Schedules // Status = CreateUSBQueue((PUSBQUEUE*)&m_UsbQueue); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to create UsbQueue!\n"); return Status; } // // store device objects // m_DriverObject = DriverObject; m_FunctionalDeviceObject = FunctionalDeviceObject; m_PhysicalDeviceObject = PhysicalDeviceObject; m_NextDeviceObject = LowerDeviceObject; // // initialize device lock // KeInitializeSpinLock(&m_Lock); // // initialize status change work item // ExInitializeWorkItem(&m_StatusChangeWorkItem, StatusChangeWorkItemRoutine, PVOID(this)); m_VendorID = 0; m_DeviceID = 0; Status = GetBusInterface(PhysicalDeviceObject, &m_BusInterface); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to get BusInterface!\n"); return Status; } BytesRead = (*m_BusInterface.GetBusData)(m_BusInterface.Context, PCI_WHICHSPACE_CONFIG, &PciConfig, 0, PCI_COMMON_HDR_LENGTH); if (BytesRead != PCI_COMMON_HDR_LENGTH) { DPRINT1("Failed to get pci config information!\n"); return STATUS_SUCCESS; } m_VendorID = PciConfig.VendorID; m_DeviceID = PciConfig.DeviceID; return STATUS_SUCCESS; } VOID STDMETHODCALLTYPE CUSBHardwareDevice::SetCommandRegister(PEHCI_USBCMD_CONTENT UsbCmd) { PULONG Register; Register = (PULONG)UsbCmd; WRITE_REGISTER_ULONG((PULONG)((PUCHAR)m_Base + EHCI_USBCMD), *Register); } VOID STDMETHODCALLTYPE CUSBHardwareDevice::GetCommandRegister(PEHCI_USBCMD_CONTENT UsbCmd) { PULONG Register; Register = (PULONG)UsbCmd; *Register = READ_REGISTER_ULONG((PULONG)((PUCHAR)m_Base + EHCI_USBCMD)); } ULONG CUSBHardwareDevice::EHCI_READ_REGISTER_ULONG(ULONG Offset) { return READ_REGISTER_ULONG((PULONG)((PUCHAR)m_Base + Offset)); } VOID CUSBHardwareDevice::EHCI_WRITE_REGISTER_ULONG(ULONG Offset, ULONG Value) { WRITE_REGISTER_ULONG((PULONG)((PUCHAR)m_Base + Offset), Value); } VOID CUSBHardwareDevice::PrintCapabilities() { if (m_Capabilities.HCSParams.PortPowerControl) { DPRINT1("Controller EHCI has Port Power Control\n"); } DPRINT1("Controller Port Routing Rules %lu\n", m_Capabilities.HCSParams.PortRouteRules); DPRINT1("Number of Ports per Companion Controller %lu\n", m_Capabilities.HCSParams.PortPerCHC); DPRINT1("Number of Companion Controller %lu\n", m_Capabilities.HCSParams.CHCCount); if (m_Capabilities.HCSParams.PortIndicator) { DPRINT1("Controller has Port Indicators Support\n"); } if (m_Capabilities.HCSParams.DbgPortNum) { DPRINT1("Controller has Debug Port Support At Port %x\n", m_Capabilities.HCSParams.DbgPortNum); } if (m_Capabilities.HCCParams.EECPCapable) { DPRINT1("Controller has Extended Capabilities Support\n"); } if (m_Capabilities.HCCParams.ParkMode) { DPRINT1("Controller supports Asynchronous Schedule Park\n"); } if (m_Capabilities.HCCParams.VarFrameList) { DPRINT1("Controller supports Programmable Frame List Size\n"); } if (m_Capabilities.HCCParams.CurAddrBits) { DPRINT1("Controller uses 64-Bit Addressing\n"); } } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::PnpStart( PCM_RESOURCE_LIST RawResources, PCM_RESOURCE_LIST TranslatedResources) { ULONG Index, Count; PCM_PARTIAL_RESOURCE_DESCRIPTOR ResourceDescriptor; DEVICE_DESCRIPTION DeviceDescription; PHYSICAL_ADDRESS AsyncPhysicalAddress; PVOID ResourceBase; NTSTATUS Status; UCHAR Value; UCHAR PortCount; DPRINT("CUSBHardwareDevice::PnpStart\n"); for(Index = 0; Index < TranslatedResources->List[0].PartialResourceList.Count; Index++) { // // get resource descriptor // ResourceDescriptor = &TranslatedResources->List[0].PartialResourceList.PartialDescriptors[Index]; switch(ResourceDescriptor->Type) { case CmResourceTypeInterrupt: { KeInitializeDpc(&m_IntDpcObject, EhciDeferredRoutine, this); Status = IoConnectInterrupt(&m_Interrupt, InterruptServiceRoutine, (PVOID)this, NULL, ResourceDescriptor->u.Interrupt.Vector, (KIRQL)ResourceDescriptor->u.Interrupt.Level, (KIRQL)ResourceDescriptor->u.Interrupt.Level, (KINTERRUPT_MODE)(ResourceDescriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED), (ResourceDescriptor->ShareDisposition != CmResourceShareDeviceExclusive), ResourceDescriptor->u.Interrupt.Affinity, FALSE); if (!NT_SUCCESS(Status)) { // // failed to register interrupt // DPRINT1("IoConnect Interrupt failed with %x\n", Status); return Status; } break; } case CmResourceTypeMemory: { // // get resource base // ResourceBase = MmMapIoSpace(ResourceDescriptor->u.Memory.Start, ResourceDescriptor->u.Memory.Length, MmNonCached); if (!ResourceBase) { // // failed to map registers // DPRINT1("MmMapIoSpace failed\n"); return STATUS_INSUFFICIENT_RESOURCES; } // // Get controllers capabilities // m_Capabilities.Length = READ_REGISTER_UCHAR((PUCHAR)ResourceBase + EHCI_CAPLENGTH); m_Capabilities.HCIVersion = READ_REGISTER_USHORT((PUSHORT)((ULONG_PTR)ResourceBase + EHCI_HCIVERSION)); m_Capabilities.HCSParamsLong = READ_REGISTER_ULONG((PULONG)((ULONG_PTR)ResourceBase + EHCI_HCSPARAMS)); m_Capabilities.HCCParamsLong = READ_REGISTER_ULONG((PULONG)((ULONG_PTR)ResourceBase + EHCI_HCCPARAMS)); DPRINT1("Controller Capabilities Length 0x%x\n", m_Capabilities.Length); DPRINT1("Controller EHCI Version 0x%x\n", m_Capabilities.HCIVersion); DPRINT1("Controller EHCI Caps HCSParamsLong 0x%lx\n", m_Capabilities.HCSParamsLong); DPRINT1("Controller EHCI Caps HCCParamsLong 0x%lx\n", m_Capabilities.HCCParamsLong); DPRINT1("Controller has %lu Ports\n", m_Capabilities.HCSParams.PortCount); // // print capabilities // PrintCapabilities(); if (m_Capabilities.HCSParams.PortRouteRules) { Count = 0; PortCount = max(m_Capabilities.HCSParams.PortCount/2, (m_Capabilities.HCSParams.PortCount+1)/2); do { // // each entry is a 4 bit field EHCI 2.2.5 // Value = READ_REGISTER_UCHAR((PUCHAR)(ULONG_PTR)ResourceBase + EHCI_HCSP_PORTROUTE + Count); m_Capabilities.PortRoute[Count*2] = (Value & 0xF0); if ((Count*2) + 1 < m_Capabilities.HCSParams.PortCount) m_Capabilities.PortRoute[(Count*2)+1] = (Value & 0x0F); Count++; } while(Count < PortCount); } // // Set m_Base to the address of Operational Register Space // m_Base = (PULONG)((ULONG_PTR)ResourceBase + m_Capabilities.Length); break; } } } // // zero device description // RtlZeroMemory(&DeviceDescription, sizeof(DEVICE_DESCRIPTION)); // // initialize device description // DeviceDescription.Version = DEVICE_DESCRIPTION_VERSION; DeviceDescription.Master = TRUE; DeviceDescription.ScatterGather = TRUE; DeviceDescription.Dma32BitAddresses = TRUE; DeviceDescription.DmaWidth = Width32Bits; DeviceDescription.InterfaceType = PCIBus; DeviceDescription.MaximumLength = MAXULONG; // // get dma adapter // m_Adapter = IoGetDmaAdapter(m_PhysicalDeviceObject, &DeviceDescription, &m_MapRegisters); if (!m_Adapter) { // // failed to get dma adapter // DPRINT1("Failed to acquire dma adapter\n"); return STATUS_INSUFFICIENT_RESOURCES; } // // Create Common Buffer // VirtualBase = m_Adapter->DmaOperations->AllocateCommonBuffer(m_Adapter, PAGE_SIZE * 4, &PhysicalAddress, FALSE); if (!VirtualBase) { DPRINT1("Failed to allocate a common buffer\n"); return STATUS_INSUFFICIENT_RESOURCES; } // // Initialize the DMAMemoryManager // Status = m_MemoryManager->Initialize(this, &m_Lock, PAGE_SIZE * 4, VirtualBase, PhysicalAddress, 32); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to initialize the DMAMemoryManager\n"); return Status; } // // Create a queuehead for the Async Register // m_MemoryManager->Allocate(sizeof(QUEUE_HEAD), (PVOID*)&AsyncQueueHead, &AsyncPhysicalAddress); AsyncQueueHead->PhysicalAddr = AsyncPhysicalAddress.LowPart; AsyncQueueHead->HorizontalLinkPointer = AsyncQueueHead->PhysicalAddr | QH_TYPE_QH; AsyncQueueHead->EndPointCharacteristics.HeadOfReclamation = TRUE; AsyncQueueHead->EndPointCharacteristics.EndPointSpeed = QH_ENDPOINT_HIGHSPEED; AsyncQueueHead->Token.Bits.Halted = TRUE; AsyncQueueHead->EndPointCapabilities.NumberOfTransactionPerFrame = 0x01; AsyncQueueHead->NextPointer = TERMINATE_POINTER; AsyncQueueHead->CurrentLinkPointer = TERMINATE_POINTER; InitializeListHead(&AsyncQueueHead->LinkedQueueHeads); // // Initialize the UsbQueue now that we have an AdapterObject. // Status = m_UsbQueue->Initialize(PUSBHARDWAREDEVICE(this), m_Adapter, m_MemoryManager, &m_Lock); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to Initialize the UsbQueue\n"); return Status; } // // Start the controller // DPRINT1("Starting Controller\n"); Status = StartController(); // // done // return Status; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::PnpStop(void) { UNIMPLEMENTED; return STATUS_NOT_IMPLEMENTED; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::GetDeviceDetails( OUT OPTIONAL PUSHORT VendorId, OUT OPTIONAL PUSHORT DeviceId, OUT OPTIONAL PULONG NumberOfPorts, OUT OPTIONAL PULONG Speed) { if (VendorId) *VendorId = m_VendorID; if (DeviceId) *DeviceId = m_DeviceID; if (NumberOfPorts) *NumberOfPorts = m_Capabilities.HCSParams.PortCount; //FIXME: What to returned here? if (Speed) *Speed = 0x200; return STATUS_SUCCESS; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::GetDMA( OUT struct IDMAMemoryManager **OutDMAMemoryManager) { if (!m_MemoryManager) return STATUS_UNSUCCESSFUL; *OutDMAMemoryManager = m_MemoryManager; return STATUS_SUCCESS; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::GetUSBQueue( OUT struct IUSBQueue **OutUsbQueue) { if (!m_UsbQueue) return STATUS_UNSUCCESSFUL; *OutUsbQueue = m_UsbQueue; return STATUS_SUCCESS; } NTSTATUS CUSBHardwareDevice::StartController(void) { EHCI_USBCMD_CONTENT UsbCmd; ULONG UsbSts, FailSafe, ExtendedCapsSupport, Caps, Index; UCHAR Value; LARGE_INTEGER Timeout; // // are extended caps supported // ExtendedCapsSupport = (m_Capabilities.HCCParamsLong >> EHCI_ECP_SHIFT) & EHCI_ECP_MASK; if (ExtendedCapsSupport) { DPRINT1("[EHCI] Extended Caps Support detected!\n"); // // sanity check // ASSERT(ExtendedCapsSupport >= PCI_COMMON_HDR_LENGTH); m_BusInterface.GetBusData(m_BusInterface.Context, PCI_WHICHSPACE_CONFIG, &Caps, ExtendedCapsSupport, sizeof(ULONG)); // // OS Handoff Synchronization support capability. EHCI 5.1 // if ((Caps & EHCI_LEGSUP_CAPID_MASK) == EHCI_LEGSUP_CAPID) { // // is it bios owned // if ((Caps & EHCI_LEGSUP_BIOSOWNED)) { DPRINT1("[EHCI] Controller is BIOS owned, acquiring control\n"); // // acquire ownership // Value = 1; m_BusInterface.SetBusData(m_BusInterface.Context, PCI_WHICHSPACE_CONFIG, &Value, ExtendedCapsSupport+3, sizeof(UCHAR)); for(Index = 0; Index < 20; Index++) { // // get status // m_BusInterface.GetBusData(m_BusInterface.Context, PCI_WHICHSPACE_CONFIG, &Caps, ExtendedCapsSupport, sizeof(ULONG)); if ((Caps & EHCI_LEGSUP_BIOSOWNED)) { // // lets wait a bit // Timeout.QuadPart = 50; DPRINT1("Waiting %lu milliseconds for port reset\n", Timeout.LowPart); // // convert to 100 ns units (absolute) // Timeout.QuadPart *= -10000; // // perform the wait // KeDelayExecutionThread(KernelMode, FALSE, &Timeout); } } if ((Caps & EHCI_LEGSUP_BIOSOWNED)) { // // failed to acquire ownership // DPRINT1("[EHCI] failed to acquire ownership\n"); } else if ((Caps & EHCI_LEGSUP_OSOWNED)) { // // HC OS Owned Semaphore EHCI 2.1.7 // DPRINT1("[EHCI] acquired ownership\n"); } #if 0 // // explicitly clear the bios owned flag 2.1.7 // Value = 0; m_BusInterface.SetBusData(m_BusInterface.Context, PCI_WHICHSPACE_CONFIG, &Value, ExtendedCapsSupport+2, sizeof(UCHAR)); // // clear SMI interrupt EHCI 2.1.8 // Caps = 4; m_BusInterface.SetBusData(m_BusInterface.Context, PCI_WHICHSPACE_CONFIG, &Caps, ExtendedCapsSupport+4, sizeof(ULONG)); #endif } } } // // get command register // GetCommandRegister(&UsbCmd); // // disable running schedules // UsbCmd.PeriodicEnable = FALSE; UsbCmd.AsyncEnable = FALSE; SetCommandRegister(&UsbCmd); // // Wait for execution to start // for (FailSafe = 100; FailSafe > 1; FailSafe--) { KeStallExecutionProcessor(100); UsbSts = EHCI_READ_REGISTER_ULONG(EHCI_USBSTS); if (!(UsbSts & EHCI_STS_PSS) && (UsbSts & EHCI_STS_ASS)) { break; } } if ((UsbSts & (EHCI_STS_PSS | EHCI_STS_ASS))) { DPRINT1("Failed to stop running schedules %x\n", UsbSts); //ASSERT(FALSE); } // // Stop the controller if its running // UsbSts = EHCI_READ_REGISTER_ULONG(EHCI_USBSTS); if (!(UsbSts & EHCI_STS_HALT)) { DPRINT1("Stopping Controller %x\n", UsbSts); StopController(); } // // Reset the controller // ResetController(); // // check caps // if (m_Capabilities.HCCParams.CurAddrBits) { // // disable 64-bit addressing // EHCI_WRITE_REGISTER_ULONG(EHCI_CTRLDSSEGMENT, 0x0); } // // Enable Interrupts and start execution // ULONG Mask = EHCI_USBINTR_INTE | EHCI_USBINTR_ERR | EHCI_USBINTR_ASYNC | EHCI_USBINTR_HSERR | EHCI_USBINTR_PC; EHCI_WRITE_REGISTER_ULONG(EHCI_USBINTR, Mask); KeStallExecutionProcessor(10); ULONG Status = EHCI_READ_REGISTER_ULONG(EHCI_USBINTR); DPRINT1("Interrupt Mask %x\n", Status); ASSERT((Status & Mask) == Mask); // // Assign the SyncList Register // EHCI_WRITE_REGISTER_ULONG(EHCI_PERIODICLISTBASE, m_SyncFramePhysAddr); // // Set Schedules to Enable and Interrupt Threshold to 1ms. // RtlZeroMemory(&UsbCmd, sizeof(EHCI_USBCMD_CONTENT)); UsbCmd.PeriodicEnable = TRUE; UsbCmd.IntThreshold = 0x8; //1ms UsbCmd.Run = TRUE; UsbCmd.FrameListSize = 0x0; //1024 if (m_Capabilities.HCCParams.ParkMode) { // // enable async park mode // UsbCmd.AsyncParkEnable = TRUE; UsbCmd.AsyncParkCount = 3; } SetCommandRegister(&UsbCmd); // // Wait for execution to start // for (FailSafe = 100; FailSafe > 1; FailSafe--) { KeStallExecutionProcessor(100); UsbSts = EHCI_READ_REGISTER_ULONG(EHCI_USBSTS); if (!(UsbSts & EHCI_STS_HALT) && (UsbSts & EHCI_STS_PSS)) { break; } } if (UsbSts & EHCI_STS_HALT) { DPRINT1("Could not start execution on the controller\n"); //ASSERT(FALSE); return STATUS_UNSUCCESSFUL; } if (!(UsbSts & EHCI_STS_PSS)) { DPRINT1("Could not enable periodic scheduling\n"); //ASSERT(FALSE); return STATUS_UNSUCCESSFUL; } // // Assign the AsyncList Register // EHCI_WRITE_REGISTER_ULONG(EHCI_ASYNCLISTBASE, AsyncQueueHead->PhysicalAddr); // // get command register // GetCommandRegister(&UsbCmd); // // preserve bits // UsbCmd.AsyncEnable = TRUE; // // enable async // SetCommandRegister(&UsbCmd); // // Wait for execution to start // for (FailSafe = 100; FailSafe > 1; FailSafe--) { KeStallExecutionProcessor(100); UsbSts = EHCI_READ_REGISTER_ULONG(EHCI_USBSTS); if ((UsbSts & EHCI_STS_ASS)) { break; } } if (!(UsbSts & EHCI_STS_ASS)) { DPRINT1("Failed to enable async schedule UsbSts %x\n", UsbSts); //ASSERT(FALSE); return STATUS_UNSUCCESSFUL; } DPRINT1("UsbSts %x\n", UsbSts); GetCommandRegister(&UsbCmd); DPRINT1("UsbCmd.PeriodicEnable %x\n", UsbCmd.PeriodicEnable); DPRINT1("UsbCmd.AsyncEnable %x\n", UsbCmd.AsyncEnable); DPRINT1("UsbCmd.IntThreshold %x\n", UsbCmd.IntThreshold); DPRINT1("UsbCmd.Run %x\n", UsbCmd.Run); DPRINT1("UsbCmd.FrameListSize %x\n", UsbCmd.FrameListSize); // // Set port routing to EHCI controller // EHCI_WRITE_REGISTER_ULONG(EHCI_CONFIGFLAG, 1); DPRINT1("EHCI Started!\n"); return STATUS_SUCCESS; } NTSTATUS CUSBHardwareDevice::StopController(void) { EHCI_USBCMD_CONTENT UsbCmd; ULONG UsbSts, FailSafe; // // Disable Interrupts and stop execution // EHCI_WRITE_REGISTER_ULONG (EHCI_USBINTR, 0); GetCommandRegister(&UsbCmd); UsbCmd.Run = FALSE; SetCommandRegister(&UsbCmd); for (FailSafe = 100; FailSafe > 1; FailSafe--) { KeStallExecutionProcessor(10); UsbSts = EHCI_READ_REGISTER_ULONG(EHCI_USBSTS); if (UsbSts & EHCI_STS_HALT) { break; } } if (!(UsbSts & EHCI_STS_HALT)) { DPRINT1("EHCI ERROR: Controller is not responding to Stop request!\n"); return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; } NTSTATUS CUSBHardwareDevice::ResetController(void) { EHCI_USBCMD_CONTENT UsbCmd; ULONG FailSafe; GetCommandRegister(&UsbCmd); UsbCmd.HCReset = TRUE; SetCommandRegister(&UsbCmd); for (FailSafe = 100; FailSafe > 1; FailSafe--) { KeStallExecutionProcessor(100); GetCommandRegister(&UsbCmd); if (!UsbCmd.HCReset) break; } if (UsbCmd.HCReset) { DPRINT1("EHCI ERROR: Controller is not responding to reset request!\n"); return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::ResetPort( IN ULONG PortIndex) { ULONG PortStatus; LARGE_INTEGER Timeout; if (PortIndex > m_Capabilities.HCSParams.PortCount) return STATUS_UNSUCCESSFUL; PortStatus = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortIndex)); ASSERT(!EHCI_IS_LOW_SPEED(PortStatus)); ASSERT(PortStatus & EHCI_PRT_CONNECTED); // // Reset and clean enable // PortStatus |= EHCI_PRT_RESET; PortStatus &= EHCI_PORTSC_DATAMASK; EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * PortIndex), PortStatus); // // delay is 50 ms for port reset as per USB 2.0 spec // Timeout.QuadPart = 50; DPRINT1("Waiting %lu milliseconds for port reset\n", Timeout.LowPart); // // convert to 100 ns units (absolute) // Timeout.QuadPart *= -10000; // // perform the wait // KeDelayExecutionThread(KernelMode, FALSE, &Timeout); return STATUS_SUCCESS; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::GetPortStatus( ULONG PortId, OUT USHORT *PortStatus, OUT USHORT *PortChange) { ULONG Value; USHORT Status = 0, Change = 0; if (PortId > m_Capabilities.HCSParams.PortCount) return STATUS_UNSUCCESSFUL; // // Get the value of the Port Status and Control Register // Value = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId)); // // If the PowerPortControl is 0 then host controller does not have power control switches if (!m_Capabilities.HCSParams.PortPowerControl) { Status |= USB_PORT_STATUS_POWER; } else { // Check the value of PortPower if (Value & EHCI_PRT_POWER) { Status |= USB_PORT_STATUS_POWER; } } // Get Connected Status if (Value & EHCI_PRT_CONNECTED) { Status |= USB_PORT_STATUS_CONNECT; // EHCI only supports high speed Status |= USB_PORT_STATUS_HIGH_SPEED; } // Get Enabled Status if (Value & EHCI_PRT_ENABLED) Status |= USB_PORT_STATUS_ENABLE; // Is it suspended? if (Value & EHCI_PRT_SUSPEND) Status |= USB_PORT_STATUS_SUSPEND; // a overcurrent is active? if (Value & EHCI_PRT_OVERCURRENTACTIVE) Status |= USB_PORT_STATUS_OVER_CURRENT; // In a reset state? if ((Value & EHCI_PRT_RESET) || m_PortResetInProgress[PortId]) { Status |= USB_PORT_STATUS_RESET; Change |= USB_PORT_STATUS_RESET; } // This indicates a connect or disconnect if (Value & EHCI_PRT_CONNECTSTATUSCHANGE) Change |= USB_PORT_STATUS_CONNECT; // This is set to indicate a critical port error if (Value & EHCI_PRT_ENABLEDSTATUSCHANGE) Change |= USB_PORT_STATUS_ENABLE; *PortStatus = Status; *PortChange = Change; return STATUS_SUCCESS; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::ClearPortStatus( ULONG PortId, ULONG Status) { ULONG Value; LARGE_INTEGER Timeout; DPRINT("CUSBHardwareDevice::ClearPortStatus PortId %x Feature %x\n", PortId, Status); if (PortId > m_Capabilities.HCSParams.PortCount) return STATUS_UNSUCCESSFUL; if (Status == C_PORT_RESET) { // reset done m_PortResetInProgress[PortId] = FALSE; // Clear reset Value = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId)); Value &= (EHCI_PORTSC_DATAMASK | EHCI_PRT_ENABLED); Value &= ~EHCI_PRT_RESET; EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId), Value); // // wait for reset bit to clear // do { Value = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId)); if (!(Value & EHCI_PRT_RESET)) break; KeStallExecutionProcessor(20); } while (TRUE); // // delay is 50 ms // Timeout.QuadPart = 50; DPRINT1("Waiting %lu milliseconds for port to recover after reset\n", Timeout.LowPart); // // convert to 100 ns units (absolute) // Timeout.QuadPart *= -10000; // // perform the wait // KeDelayExecutionThread(KernelMode, FALSE, &Timeout); // // check the port status after reset // Value = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId)); if (!(Value & EHCI_PRT_CONNECTED)) { DPRINT1("No device is here after reset. Bad controller/device?\n"); return STATUS_UNSUCCESSFUL; } else if (EHCI_IS_LOW_SPEED(Value)) { DPRINT1("Low speed device connected. Releasing ownership\n"); EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId), Value | EHCI_PRT_RELEASEOWNERSHIP); return STATUS_DEVICE_NOT_CONNECTED; } else if (!(Value & EHCI_PRT_ENABLED)) { DPRINT1("Full speed device connected. Releasing ownership\n"); EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId), Value | EHCI_PRT_RELEASEOWNERSHIP); return STATUS_DEVICE_NOT_CONNECTED; } else { DPRINT1("High speed device connected\n"); return STATUS_SUCCESS; } } else if (Status == C_PORT_CONNECTION) { // // reset status change bits // Value = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId)); EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId), Value); if (Value & EHCI_PRT_CONNECTED) { // // delay is 100 ms // Timeout.QuadPart = 100; DPRINT1("Waiting %lu milliseconds for port to stabilize after connection\n", Timeout.LowPart); // // convert to 100 ns units (absolute) // Timeout.QuadPart *= -10000; // // perform the wait // KeDelayExecutionThread(KernelMode, FALSE, &Timeout); } } return STATUS_SUCCESS; } NTSTATUS STDMETHODCALLTYPE CUSBHardwareDevice::SetPortFeature( ULONG PortId, ULONG Feature) { DPRINT("CUSBHardwareDevice::SetPortFeature\n"); if (PortId > m_Capabilities.HCSParams.PortCount) return STATUS_UNSUCCESSFUL; if (Feature == PORT_ENABLE) { // // FIXME: EHCI Ports can only be disabled via reset // DPRINT1("PORT_ENABLE not supported for EHCI\n"); } if (Feature == PORT_RESET) { // // call the helper // ResetPort(PortId); // reset in progress m_PortResetInProgress[PortId] = TRUE; // // is there a status change callback // if (m_SCECallBack != NULL) { // // issue callback // m_SCECallBack(m_SCEContext); } } if (Feature == PORT_POWER) { if (m_Capabilities.HCSParams.PortPowerControl) { ULONG Value; LARGE_INTEGER Timeout; // // enable port power // Value = EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId)) | EHCI_PRT_POWER; EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * PortId), Value); // // delay is 20 ms // Timeout.QuadPart = 20; DPRINT1("Waiting %lu milliseconds for port power up\n", Timeout.LowPart); // // convert to 100 ns units (absolute) // Timeout.QuadPart *= -10000; // // perform the wait // KeDelayExecutionThread(KernelMode, FALSE, &Timeout); } } return STATUS_SUCCESS; } VOID STDMETHODCALLTYPE CUSBHardwareDevice::SetAsyncListRegister( ULONG PhysicalAddress) { EHCI_WRITE_REGISTER_ULONG(EHCI_ASYNCLISTBASE, PhysicalAddress); } VOID STDMETHODCALLTYPE CUSBHardwareDevice::SetPeriodicListRegister( ULONG PhysicalAddress) { // // store physical address // m_SyncFramePhysAddr = PhysicalAddress; } struct _QUEUE_HEAD * STDMETHODCALLTYPE CUSBHardwareDevice::GetAsyncListQueueHead() { return AsyncQueueHead; } ULONG STDMETHODCALLTYPE CUSBHardwareDevice::GetPeriodicListRegister() { UNIMPLEMENTED; return NULL; } VOID STDMETHODCALLTYPE CUSBHardwareDevice::SetStatusChangeEndpointCallBack( PVOID CallBack, PVOID Context) { m_SCECallBack = (HD_INIT_CALLBACK*)CallBack; m_SCEContext = Context; } BOOLEAN NTAPI InterruptServiceRoutine( IN PKINTERRUPT Interrupt, IN PVOID ServiceContext) { CUSBHardwareDevice *This; ULONG CStatus; This = (CUSBHardwareDevice*) ServiceContext; CStatus = This->EHCI_READ_REGISTER_ULONG(EHCI_USBSTS); CStatus &= (EHCI_ERROR_INT | EHCI_STS_INT | EHCI_STS_IAA | EHCI_STS_PCD | EHCI_STS_FLR); DPRINT("InterruptServiceRoutine CStatus %lx\n", CStatus); // // Check that it belongs to EHCI // if (!CStatus) return FALSE; // // Clear the Status // This->EHCI_WRITE_REGISTER_ULONG(EHCI_USBSTS, CStatus); if (CStatus & EHCI_STS_FATAL) { This->StopController(); DPRINT1("EHCI: Host System Error!\n"); return TRUE; } if (CStatus & EHCI_ERROR_INT) { DPRINT1("EHCI Status = 0x%x\n", CStatus); } if (CStatus & EHCI_STS_HALT) { DPRINT1("Host Error Unexpected Halt\n"); // FIXME: Reset controller\n"); return TRUE; } KeInsertQueueDpc(&This->m_IntDpcObject, This, UlongToPtr(CStatus)); return TRUE; } VOID NTAPI EhciDeferredRoutine( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2) { CUSBHardwareDevice *This; ULONG CStatus, PortStatus, PortCount, i, ShouldRingDoorBell, QueueSCEWorkItem; NTSTATUS Status = STATUS_SUCCESS; EHCI_USBCMD_CONTENT UsbCmd; This = (CUSBHardwareDevice*) SystemArgument1; CStatus = PtrToUlong(SystemArgument2); DPRINT("EhciDeferredRoutine CStatus %lx\n", CStatus); // // check for completion of async schedule // if (CStatus & (EHCI_STS_RECL| EHCI_STS_INT | EHCI_ERROR_INT)) { // // check if there is a door bell ring in progress // if (This->m_DoorBellRingInProgress == FALSE) { if (CStatus & EHCI_ERROR_INT) { // // controller reported error // DPRINT1("CStatus %lx\n", CStatus); //ASSERT(FALSE); } // // inform IUSBQueue of a completed queue head // This->m_UsbQueue->InterruptCallback(Status, &ShouldRingDoorBell); // // was a queue head completed? // if (ShouldRingDoorBell) { // // set door ring bell in progress status flag // This->m_DoorBellRingInProgress = TRUE; // // get command register // This->GetCommandRegister(&UsbCmd); // // set door rang bell bit // UsbCmd.DoorBell = TRUE; // // update command status // This->SetCommandRegister(&UsbCmd); } } } // // check if the controller has acknowledged the door bell // if (CStatus & EHCI_STS_IAA) { // // controller has acknowledged, assert we rang the bell // PC_ASSERT(This->m_DoorBellRingInProgress == TRUE); // // now notify IUSBQueue that it can free completed requests // This->m_UsbQueue->CompleteAsyncRequests(); // // door ring bell completed // This->m_DoorBellRingInProgress = FALSE; } This->GetDeviceDetails(NULL, NULL, &PortCount, NULL); if (CStatus & EHCI_STS_PCD) { QueueSCEWorkItem = FALSE; for (i = 0; i < PortCount; i++) { PortStatus = This->EHCI_READ_REGISTER_ULONG(EHCI_PORTSC + (4 * i)); // // Device connected or removed // if (PortStatus & EHCI_PRT_CONNECTSTATUSCHANGE) { if (PortStatus & EHCI_PRT_CONNECTED) { DPRINT1("Device connected on port %lu\n", i); if (This->m_Capabilities.HCSParams.CHCCount) { if (PortStatus & EHCI_PRT_ENABLED) { DPRINT1("Misbehaving controller. Port should be disabled at this point\n"); } if (EHCI_IS_LOW_SPEED(PortStatus)) { DPRINT1("Low speed device connected. Releasing ownership\n"); This->EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC + (4 * i), PortStatus | EHCI_PRT_RELEASEOWNERSHIP); continue; } } // // work to do // QueueSCEWorkItem = TRUE; } else { DPRINT1("Device disconnected on port %lu\n", i); // // work to do // QueueSCEWorkItem = TRUE; } } } // // is there a status change callback and a high speed device connected / disconnected // if (QueueSCEWorkItem && This->m_SCECallBack != NULL) { if (InterlockedCompareExchange(&This->m_StatusChangeWorkItemStatus, 1, 0) == 0) { // // queue work item for processing // ExQueueWorkItem(&This->m_StatusChangeWorkItem, DelayedWorkQueue); } } } return; } VOID NTAPI StatusChangeWorkItemRoutine( PVOID Context) { // // cast to hardware object // CUSBHardwareDevice * This = (CUSBHardwareDevice*)Context; // // is there a callback // if (This->m_SCECallBack) { // // issue callback // This->m_SCECallBack(This->m_SCEContext); } // // reset active status // InterlockedDecrement(&This->m_StatusChangeWorkItemStatus); } NTSTATUS NTAPI CreateUSBHardware( PUSBHARDWAREDEVICE *OutHardware) { PUSBHARDWAREDEVICE This; This = new(NonPagedPool, TAG_USBEHCI) CUSBHardwareDevice(0); if (!This) return STATUS_INSUFFICIENT_RESOURCES; This->AddRef(); // return result *OutHardware = (PUSBHARDWAREDEVICE)This; return STATUS_SUCCESS; }