2 * PROJECT: ReactOS Universal Serial Bus Bulk Enhanced Host Controller Interface
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: drivers/usb/usbehci/hcd_controller.cpp
5 * PURPOSE: USB EHCI device driver.
7 * Michael Martin (michael.martin@reactos.org)
8 * Johannes Anderwald (johannes.anderwald@reactos.org)
15 typedef VOID __stdcall
HD_INIT_CALLBACK(IN PVOID CallBackContext
);
19 InterruptServiceRoutine(
20 IN PKINTERRUPT Interrupt
,
21 IN PVOID ServiceContext
);
27 IN PVOID DeferredContext
,
28 IN PVOID SystemArgument1
,
29 IN PVOID SystemArgument2
);
33 StatusChangeWorkItemRoutine(PVOID Context
);
35 class CUSBHardwareDevice
: public IEHCIHardwareDevice
38 STDMETHODIMP
QueryInterface( REFIID InterfaceId
, PVOID
* Interface
);
40 STDMETHODIMP_(ULONG
) AddRef()
42 InterlockedIncrement(&m_Ref
);
45 STDMETHODIMP_(ULONG
) Release()
47 InterlockedDecrement(&m_Ref
);
57 IMP_IUSBHARDWAREDEVICE
61 BOOLEAN
InterruptService();
62 VOID
PrintCapabilities();
63 NTSTATUS
StartController();
64 NTSTATUS
StopController();
65 NTSTATUS
ResetController();
68 friend BOOLEAN NTAPI
InterruptServiceRoutine(IN PKINTERRUPT Interrupt
, IN PVOID ServiceContext
);
69 friend VOID NTAPI
EhciDefferedRoutine(IN PKDPC Dpc
, IN PVOID DeferredContext
, IN PVOID SystemArgument1
, IN PVOID SystemArgument2
);
70 friend VOID NTAPI
StatusChangeWorkItemRoutine(PVOID Context
);
71 // constructor / destructor
72 CUSBHardwareDevice(IUnknown
*OuterUnknown
){}
73 virtual ~CUSBHardwareDevice(){}
76 LONG m_Ref
; // reference count
77 PDRIVER_OBJECT m_DriverObject
; // driver object
78 PDEVICE_OBJECT m_PhysicalDeviceObject
; // pdo
79 PDEVICE_OBJECT m_FunctionalDeviceObject
; // fdo (hcd controller)
80 PDEVICE_OBJECT m_NextDeviceObject
; // lower device object
81 KSPIN_LOCK m_Lock
; // hardware lock
82 PKINTERRUPT m_Interrupt
; // interrupt object
83 KDPC m_IntDpcObject
; // dpc object for deferred isr processing
84 PVOID VirtualBase
; // virtual base for memory manager
85 PHYSICAL_ADDRESS PhysicalAddress
; // physical base for memory manager
86 PULONG m_Base
; // EHCI operational port base registers
87 PDMA_ADAPTER m_Adapter
; // dma adapter object
88 ULONG m_MapRegisters
; // map registers count
89 EHCI_CAPS m_Capabilities
; // EHCI caps
90 USHORT m_VendorID
; // vendor id
91 USHORT m_DeviceID
; // device id
92 PQUEUE_HEAD AsyncQueueHead
; // async queue head terminator
93 PEHCIQUEUE m_UsbQueue
; // usb request queue
94 PDMAMEMORYMANAGER m_MemoryManager
; // memory manager
95 HD_INIT_CALLBACK
* m_SCECallBack
; // status change callback routine
96 PVOID m_SCEContext
; // status change callback routine context
97 BOOLEAN m_DoorBellRingInProgress
; // door bell ring in progress
98 WORK_QUEUE_ITEM m_StatusChangeWorkItem
; // work item for status change callback
99 volatile LONG m_StatusChangeWorkItemStatus
; // work item status
100 ULONG m_SyncFramePhysAddr
; // periodic frame list physical address
101 BUS_INTERFACE_STANDARD m_BusInterface
; // pci bus interface
102 BOOLEAN m_PortResetInProgress
[0xF]; // stores reset in progress (vbox hack)
105 ULONG
EHCI_READ_REGISTER_ULONG(ULONG Offset
);
108 VOID
EHCI_WRITE_REGISTER_ULONG(ULONG Offset
, ULONG Value
);
111 //=================================================================================================
116 CUSBHardwareDevice::QueryInterface(
120 if (IsEqualGUIDAligned(refiid
, IID_IUnknown
))
122 *Output
= PVOID(PUNKNOWN(this));
123 PUNKNOWN(*Output
)->AddRef();
124 return STATUS_SUCCESS
;
127 return STATUS_UNSUCCESSFUL
;
132 CUSBHardwareDevice::GetUSBType()
139 CUSBHardwareDevice::Initialize(
140 PDRIVER_OBJECT DriverObject
,
141 PDEVICE_OBJECT FunctionalDeviceObject
,
142 PDEVICE_OBJECT PhysicalDeviceObject
,
143 PDEVICE_OBJECT LowerDeviceObject
)
145 PCI_COMMON_CONFIG PciConfig
;
149 DPRINT("CUSBHardwareDevice::Initialize\n");
152 // Create DMAMemoryManager for use with QueueHeads and Transfer Descriptors.
154 Status
= CreateDMAMemoryManager(&m_MemoryManager
);
155 if (!NT_SUCCESS(Status
))
157 DPRINT1("Failed to create DMAMemoryManager Object\n");
162 // Create the UsbQueue class that will handle the Asynchronous and Periodic Schedules
164 Status
= CreateUSBQueue((PUSBQUEUE
*)&m_UsbQueue
);
165 if (!NT_SUCCESS(Status
))
167 DPRINT1("Failed to create UsbQueue!\n");
172 // store device objects
174 m_DriverObject
= DriverObject
;
175 m_FunctionalDeviceObject
= FunctionalDeviceObject
;
176 m_PhysicalDeviceObject
= PhysicalDeviceObject
;
177 m_NextDeviceObject
= LowerDeviceObject
;
180 // initialize device lock
182 KeInitializeSpinLock(&m_Lock
);
185 // intialize status change work item
187 ExInitializeWorkItem(&m_StatusChangeWorkItem
, StatusChangeWorkItemRoutine
, PVOID(this));
192 Status
= GetBusInterface(PhysicalDeviceObject
, &m_BusInterface
);
193 if (!NT_SUCCESS(Status
))
195 DPRINT1("Failed to get BusInteface!\n");
199 BytesRead
= (*m_BusInterface
.GetBusData
)(m_BusInterface
.Context
,
200 PCI_WHICHSPACE_CONFIG
,
203 PCI_COMMON_HDR_LENGTH
);
205 if (BytesRead
!= PCI_COMMON_HDR_LENGTH
)
207 DPRINT1("Failed to get pci config information!\n");
208 return STATUS_SUCCESS
;
211 m_VendorID
= PciConfig
.VendorID
;
212 m_DeviceID
= PciConfig
.DeviceID
;
214 return STATUS_SUCCESS
;
219 CUSBHardwareDevice::SetCommandRegister(PEHCI_USBCMD_CONTENT UsbCmd
)
222 Register
= (PULONG
)UsbCmd
;
223 WRITE_REGISTER_ULONG((PULONG
)((ULONG
)m_Base
+ EHCI_USBCMD
), *Register
);
228 CUSBHardwareDevice::GetCommandRegister(PEHCI_USBCMD_CONTENT UsbCmd
)
231 Register
= (PULONG
)UsbCmd
;
232 *Register
= READ_REGISTER_ULONG((PULONG
)((ULONG
)m_Base
+ EHCI_USBCMD
));
236 CUSBHardwareDevice::EHCI_READ_REGISTER_ULONG(ULONG Offset
)
238 return READ_REGISTER_ULONG((PULONG
)((ULONG
)m_Base
+ Offset
));
242 CUSBHardwareDevice::EHCI_WRITE_REGISTER_ULONG(ULONG Offset
, ULONG Value
)
244 WRITE_REGISTER_ULONG((PULONG
)((ULONG
)m_Base
+ Offset
), Value
);
248 CUSBHardwareDevice::PrintCapabilities()
250 if (m_Capabilities
.HCSParams
.PortPowerControl
)
252 DPRINT1("Controler EHCI has Port Power Control\n");
255 DPRINT1("Controller Port Routing Rules %lu\n", m_Capabilities
.HCSParams
.PortRouteRules
);
256 DPRINT1("Number of Ports per Companion Controller %lu\n", m_Capabilities
.HCSParams
.PortPerCHC
);
257 DPRINT1("Number of Companion Controller %lu\n", m_Capabilities
.HCSParams
.CHCCount
);
259 if (m_Capabilities
.HCSParams
.PortIndicator
)
261 DPRINT1("Controller has Port Indicators Support\n");
264 if (m_Capabilities
.HCSParams
.DbgPortNum
)
266 DPRINT1("Controller has Debug Port Support At Port %x\n", m_Capabilities
.HCSParams
.DbgPortNum
);
269 if (m_Capabilities
.HCCParams
.EECPCapable
)
271 DPRINT1("Controller has Extended Capabilities Support\n");
274 if (m_Capabilities
.HCCParams
.ParkMode
)
276 DPRINT1("Controller supports Asynchronous Schedule Park\n");
279 if (m_Capabilities
.HCCParams
.VarFrameList
)
281 DPRINT1("Controller supports Programmable Frame List Size\n");
284 if (m_Capabilities
.HCCParams
.CurAddrBits
)
286 DPRINT1("Controller uses 64-Bit Addressing\n");
292 CUSBHardwareDevice::PnpStart(
293 PCM_RESOURCE_LIST RawResources
,
294 PCM_RESOURCE_LIST TranslatedResources
)
297 PCM_PARTIAL_RESOURCE_DESCRIPTOR ResourceDescriptor
;
298 DEVICE_DESCRIPTION DeviceDescription
;
299 PHYSICAL_ADDRESS AsyncPhysicalAddress
;
305 DPRINT("CUSBHardwareDevice::PnpStart\n");
306 for(Index
= 0; Index
< TranslatedResources
->List
[0].PartialResourceList
.Count
; Index
++)
309 // get resource descriptor
311 ResourceDescriptor
= &TranslatedResources
->List
[0].PartialResourceList
.PartialDescriptors
[Index
];
313 switch(ResourceDescriptor
->Type
)
315 case CmResourceTypeInterrupt
:
317 KeInitializeDpc(&m_IntDpcObject
,
321 Status
= IoConnectInterrupt(&m_Interrupt
,
322 InterruptServiceRoutine
,
325 ResourceDescriptor
->u
.Interrupt
.Vector
,
326 (KIRQL
)ResourceDescriptor
->u
.Interrupt
.Level
,
327 (KIRQL
)ResourceDescriptor
->u
.Interrupt
.Level
,
328 (KINTERRUPT_MODE
)(ResourceDescriptor
->Flags
& CM_RESOURCE_INTERRUPT_LATCHED
),
329 (ResourceDescriptor
->ShareDisposition
!= CmResourceShareDeviceExclusive
),
330 ResourceDescriptor
->u
.Interrupt
.Affinity
,
333 if (!NT_SUCCESS(Status
))
336 // failed to register interrupt
338 DPRINT1("IoConnect Interrupt failed with %x\n", Status
);
343 case CmResourceTypeMemory
:
348 ResourceBase
= MmMapIoSpace(ResourceDescriptor
->u
.Memory
.Start
, ResourceDescriptor
->u
.Memory
.Length
, MmNonCached
);
352 // failed to map registers
354 DPRINT1("MmMapIoSpace failed\n");
355 return STATUS_INSUFFICIENT_RESOURCES
;
359 // Get controllers capabilities
361 m_Capabilities
.Length
= READ_REGISTER_UCHAR((PUCHAR
)ResourceBase
+ EHCI_CAPLENGTH
);
362 m_Capabilities
.HCIVersion
= READ_REGISTER_USHORT((PUSHORT
)((ULONG
)ResourceBase
+ EHCI_HCIVERSION
));
363 m_Capabilities
.HCSParamsLong
= READ_REGISTER_ULONG((PULONG
)((ULONG
)ResourceBase
+ EHCI_HCSPARAMS
));
364 m_Capabilities
.HCCParamsLong
= READ_REGISTER_ULONG((PULONG
)((ULONG
)ResourceBase
+ EHCI_HCCPARAMS
));
366 DPRINT1("Controller has %c Length\n", m_Capabilities
.Length
);
367 DPRINT1("Controller EHCI Version %x\n", m_Capabilities
.HCIVersion
);
368 DPRINT1("Controller EHCI Caps HCSParamsLong %x\n", m_Capabilities
.HCSParamsLong
);
369 DPRINT1("Controller EHCI Caps HCCParamsLong %x\n", m_Capabilities
.HCCParamsLong
);
370 DPRINT1("Controller has %lu Ports\n", m_Capabilities
.HCSParams
.PortCount
);
373 // print capabilities
377 if (m_Capabilities
.HCSParams
.PortRouteRules
)
380 PortCount
= max(m_Capabilities
.HCSParams
.PortCount
/2, (m_Capabilities
.HCSParams
.PortCount
+1)/2);
384 // each entry is a 4 bit field EHCI 2.2.5
386 Value
= READ_REGISTER_UCHAR((PUCHAR
)(ULONG
)ResourceBase
+ EHCI_HCSP_PORTROUTE
+ Count
);
387 m_Capabilities
.PortRoute
[Count
*2] = (Value
& 0xF0);
389 if ((Count
*2) + 1 < m_Capabilities
.HCSParams
.PortCount
)
390 m_Capabilities
.PortRoute
[(Count
*2)+1] = (Value
& 0x0F);
393 }while(Count
< PortCount
);
397 // Set m_Base to the address of Operational Register Space
399 m_Base
= (PULONG
)((ULONG
)ResourceBase
+ m_Capabilities
.Length
);
407 // zero device description
409 RtlZeroMemory(&DeviceDescription
, sizeof(DEVICE_DESCRIPTION
));
412 // initialize device description
414 DeviceDescription
.Version
= DEVICE_DESCRIPTION_VERSION
;
415 DeviceDescription
.Master
= TRUE
;
416 DeviceDescription
.ScatterGather
= TRUE
;
417 DeviceDescription
.Dma32BitAddresses
= TRUE
;
418 DeviceDescription
.DmaWidth
= Width32Bits
;
419 DeviceDescription
.InterfaceType
= PCIBus
;
420 DeviceDescription
.MaximumLength
= MAXULONG
;
425 m_Adapter
= IoGetDmaAdapter(m_PhysicalDeviceObject
, &DeviceDescription
, &m_MapRegisters
);
429 // failed to get dma adapter
431 DPRINT1("Failed to acquire dma adapter\n");
432 return STATUS_INSUFFICIENT_RESOURCES
;
436 // Create Common Buffer
438 VirtualBase
= m_Adapter
->DmaOperations
->AllocateCommonBuffer(m_Adapter
,
444 DPRINT1("Failed to allocate a common buffer\n");
445 return STATUS_INSUFFICIENT_RESOURCES
;
449 // Initialize the DMAMemoryManager
451 Status
= m_MemoryManager
->Initialize(this, &m_Lock
, PAGE_SIZE
* 4, VirtualBase
, PhysicalAddress
, 32);
452 if (!NT_SUCCESS(Status
))
454 DPRINT1("Failed to initialize the DMAMemoryManager\n");
459 // Create a queuehead for the Async Register
461 m_MemoryManager
->Allocate(sizeof(QUEUE_HEAD
), (PVOID
*)&AsyncQueueHead
, &AsyncPhysicalAddress
);
463 AsyncQueueHead
->PhysicalAddr
= AsyncPhysicalAddress
.LowPart
;
464 AsyncQueueHead
->HorizontalLinkPointer
= AsyncQueueHead
->PhysicalAddr
| QH_TYPE_QH
;
465 AsyncQueueHead
->EndPointCharacteristics
.HeadOfReclamation
= TRUE
;
466 AsyncQueueHead
->EndPointCharacteristics
.EndPointSpeed
= QH_ENDPOINT_HIGHSPEED
;
467 AsyncQueueHead
->Token
.Bits
.Halted
= TRUE
;
469 AsyncQueueHead
->EndPointCapabilities
.NumberOfTransactionPerFrame
= 0x01;
470 AsyncQueueHead
->NextPointer
= TERMINATE_POINTER
;
471 AsyncQueueHead
->CurrentLinkPointer
= TERMINATE_POINTER
;
473 InitializeListHead(&AsyncQueueHead
->LinkedQueueHeads
);
476 // Initialize the UsbQueue now that we have an AdapterObject.
478 Status
= m_UsbQueue
->Initialize(PUSBHARDWAREDEVICE(this), m_Adapter
, m_MemoryManager
, &m_Lock
);
479 if (!NT_SUCCESS(Status
))
481 DPRINT1("Failed to Initialize the UsbQueue\n");
486 // Start the controller
488 DPRINT1("Starting Controller\n");
489 Status
= StartController();
499 CUSBHardwareDevice::PnpStop(void)
502 return STATUS_NOT_IMPLEMENTED
;
507 CUSBHardwareDevice::GetDeviceDetails(
508 OUT OPTIONAL PUSHORT VendorId
,
509 OUT OPTIONAL PUSHORT DeviceId
,
510 OUT OPTIONAL PULONG NumberOfPorts
,
511 OUT OPTIONAL PULONG Speed
)
514 *VendorId
= m_VendorID
;
516 *DeviceId
= m_DeviceID
;
518 *NumberOfPorts
= m_Capabilities
.HCSParams
.PortCount
;
519 //FIXME: What to returned here?
522 return STATUS_SUCCESS
;
527 CUSBHardwareDevice::GetDMA(
528 OUT
struct IDMAMemoryManager
**OutDMAMemoryManager
)
530 if (!m_MemoryManager
)
531 return STATUS_UNSUCCESSFUL
;
532 *OutDMAMemoryManager
= m_MemoryManager
;
533 return STATUS_SUCCESS
;
538 CUSBHardwareDevice::GetUSBQueue(
539 OUT
struct IUSBQueue
**OutUsbQueue
)
542 return STATUS_UNSUCCESSFUL
;
543 *OutUsbQueue
= m_UsbQueue
;
544 return STATUS_SUCCESS
;
549 CUSBHardwareDevice::StartController(void)
551 EHCI_USBCMD_CONTENT UsbCmd
;
552 ULONG UsbSts
, FailSafe
, ExtendedCapsSupport
, Caps
, Index
;
554 LARGE_INTEGER Timeout
;
557 // are extended caps supported
559 ExtendedCapsSupport
= (m_Capabilities
.HCCParamsLong
>> EHCI_ECP_SHIFT
) & EHCI_ECP_MASK
;
560 if (ExtendedCapsSupport
)
562 DPRINT1("[EHCI] Extended Caps Support detected!\n");
567 ASSERT(ExtendedCapsSupport
>= PCI_COMMON_HDR_LENGTH
);
568 m_BusInterface
.GetBusData(m_BusInterface
.Context
, PCI_WHICHSPACE_CONFIG
, &Caps
, ExtendedCapsSupport
, sizeof(ULONG
));
571 // OS Handoff Synchronization support capability. EHCI 5.1
573 if ((Caps
& EHCI_LEGSUP_CAPID_MASK
) == EHCI_LEGSUP_CAPID
)
578 if ((Caps
& EHCI_LEGSUP_BIOSOWNED
))
580 DPRINT1("[EHCI] Controller is BIOS owned, acquring control\n");
586 m_BusInterface
.SetBusData(m_BusInterface
.Context
, PCI_WHICHSPACE_CONFIG
, &Value
, ExtendedCapsSupport
+3, sizeof(UCHAR
));
588 for(Index
= 0; Index
< 20; Index
++)
593 m_BusInterface
.GetBusData(m_BusInterface
.Context
, PCI_WHICHSPACE_CONFIG
, &Caps
, ExtendedCapsSupport
, sizeof(ULONG
));
594 if ((Caps
& EHCI_LEGSUP_BIOSOWNED
))
599 Timeout
.QuadPart
= 50;
600 DPRINT1("Waiting %lu milliseconds for port reset\n", Timeout
.LowPart
);
603 // convert to 100 ns units (absolute)
605 Timeout
.QuadPart
*= -10000;
610 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
613 if ((Caps
& EHCI_LEGSUP_BIOSOWNED
))
616 // failed to aquire ownership
618 DPRINT1("[EHCI] failed to acquire ownership\n");
620 else if ((Caps
& EHCI_LEGSUP_OSOWNED
))
623 // HC OS Owned Semaphore EHCI 2.1.7
625 DPRINT1("[EHCI] acquired ownership\n");
629 // explictly clear the bios owned flag 2.1.7
632 m_BusInterface
.SetBusData(m_BusInterface
.Context
, PCI_WHICHSPACE_CONFIG
, &Value
, ExtendedCapsSupport
+2, sizeof(UCHAR
));
635 // clear SMI interrupt EHCI 2.1.8
638 m_BusInterface
.SetBusData(m_BusInterface
.Context
, PCI_WHICHSPACE_CONFIG
, &Caps
, ExtendedCapsSupport
+4, sizeof(ULONG
));
645 // get command register
647 GetCommandRegister(&UsbCmd
);
650 // disable running schedules
652 UsbCmd
.PeriodicEnable
= FALSE
;
653 UsbCmd
.AsyncEnable
= FALSE
;
654 SetCommandRegister(&UsbCmd
);
657 // Wait for execution to start
659 for (FailSafe
= 100; FailSafe
> 1; FailSafe
--)
661 KeStallExecutionProcessor(100);
662 UsbSts
= EHCI_READ_REGISTER_ULONG(EHCI_USBSTS
);
664 if (!(UsbSts
& EHCI_STS_PSS
) && (UsbSts
& EHCI_STS_ASS
))
670 if ((UsbSts
& (EHCI_STS_PSS
| EHCI_STS_ASS
)))
672 DPRINT1("Failed to stop running schedules %x\n", UsbSts
);
678 // Stop the controller if its running
680 UsbSts
= EHCI_READ_REGISTER_ULONG(EHCI_USBSTS
);
681 if (!(UsbSts
& EHCI_STS_HALT
))
683 DPRINT1("Stopping Controller %x\n", UsbSts
);
688 // Reset the controller
695 if (m_Capabilities
.HCCParams
.CurAddrBits
)
698 // disable 64-bit addressing
700 EHCI_WRITE_REGISTER_ULONG(EHCI_CTRLDSSEGMENT
, 0x0);
704 // Enable Interrupts and start execution
706 ULONG Mask
= EHCI_USBINTR_INTE
| EHCI_USBINTR_ERR
| EHCI_USBINTR_ASYNC
| EHCI_USBINTR_HSERR
| EHCI_USBINTR_PC
;
707 EHCI_WRITE_REGISTER_ULONG(EHCI_USBINTR
, Mask
);
709 KeStallExecutionProcessor(10);
711 ULONG Status
= EHCI_READ_REGISTER_ULONG(EHCI_USBINTR
);
713 DPRINT1("Interrupt Mask %x\n", Status
);
714 ASSERT((Status
& Mask
) == Mask
);
717 // Assign the SyncList Register
719 EHCI_WRITE_REGISTER_ULONG(EHCI_PERIODICLISTBASE
, m_SyncFramePhysAddr
);
722 // Set Schedules to Enable and Interrupt Threshold to 1ms.
724 RtlZeroMemory(&UsbCmd
, sizeof(EHCI_USBCMD_CONTENT
));
726 UsbCmd
.PeriodicEnable
= TRUE
;
727 UsbCmd
.IntThreshold
= 0x8; //1ms
729 UsbCmd
.FrameListSize
= 0x0; //1024
731 if (m_Capabilities
.HCCParams
.ParkMode
)
734 // enable async park mode
736 UsbCmd
.AsyncParkEnable
= TRUE
;
737 UsbCmd
.AsyncParkCount
= 3;
740 SetCommandRegister(&UsbCmd
);
744 // Wait for execution to start
746 for (FailSafe
= 100; FailSafe
> 1; FailSafe
--)
748 KeStallExecutionProcessor(100);
749 UsbSts
= EHCI_READ_REGISTER_ULONG(EHCI_USBSTS
);
751 if (!(UsbSts
& EHCI_STS_HALT
) && (UsbSts
& EHCI_STS_PSS
))
757 if (UsbSts
& EHCI_STS_HALT
)
759 DPRINT1("Could not start execution on the controller\n");
761 return STATUS_UNSUCCESSFUL
;
764 if (!(UsbSts
& EHCI_STS_PSS
))
766 DPRINT1("Could not enable periodic scheduling\n");
768 return STATUS_UNSUCCESSFUL
;
772 // Assign the AsyncList Register
774 EHCI_WRITE_REGISTER_ULONG(EHCI_ASYNCLISTBASE
, AsyncQueueHead
->PhysicalAddr
);
777 // get command register
779 GetCommandRegister(&UsbCmd
);
784 UsbCmd
.AsyncEnable
= TRUE
;
789 SetCommandRegister(&UsbCmd
);
792 // Wait for execution to start
794 for (FailSafe
= 100; FailSafe
> 1; FailSafe
--)
796 KeStallExecutionProcessor(100);
797 UsbSts
= EHCI_READ_REGISTER_ULONG(EHCI_USBSTS
);
799 if ((UsbSts
& EHCI_STS_ASS
))
805 if (!(UsbSts
& EHCI_STS_ASS
))
807 DPRINT1("Failed to enable async schedule UsbSts %x\n", UsbSts
);
809 return STATUS_UNSUCCESSFUL
;
812 DPRINT1("UsbSts %x\n", UsbSts
);
813 GetCommandRegister(&UsbCmd
);
815 DPRINT1("UsbCmd.PeriodicEnable %x\n", UsbCmd
.PeriodicEnable
);
816 DPRINT1("UsbCmd.AsyncEnable %x\n", UsbCmd
.AsyncEnable
);
817 DPRINT1("UsbCmd.IntThreshold %x\n", UsbCmd
.IntThreshold
);
818 DPRINT1("UsbCmd.Run %x\n", UsbCmd
.Run
);
819 DPRINT1("UsbCmd.FrameListSize %x\n", UsbCmd
.FrameListSize
);
822 // Set port routing to EHCI controller
824 EHCI_WRITE_REGISTER_ULONG(EHCI_CONFIGFLAG
, 1);
826 DPRINT1("EHCI Started!\n");
827 return STATUS_SUCCESS
;
831 CUSBHardwareDevice::StopController(void)
833 EHCI_USBCMD_CONTENT UsbCmd
;
834 ULONG UsbSts
, FailSafe
;
837 // Disable Interrupts and stop execution
839 EHCI_WRITE_REGISTER_ULONG (EHCI_USBINTR
, 0);
841 GetCommandRegister(&UsbCmd
);
843 SetCommandRegister(&UsbCmd
);
845 for (FailSafe
= 100; FailSafe
> 1; FailSafe
--)
847 KeStallExecutionProcessor(10);
848 UsbSts
= EHCI_READ_REGISTER_ULONG(EHCI_USBSTS
);
849 if (UsbSts
& EHCI_STS_HALT
)
855 if (!(UsbSts
& EHCI_STS_HALT
))
857 DPRINT1("EHCI ERROR: Controller is not responding to Stop request!\n");
858 return STATUS_UNSUCCESSFUL
;
861 return STATUS_SUCCESS
;
865 CUSBHardwareDevice::ResetController(void)
867 EHCI_USBCMD_CONTENT UsbCmd
;
870 GetCommandRegister(&UsbCmd
);
871 UsbCmd
.HCReset
= TRUE
;
872 SetCommandRegister(&UsbCmd
);
874 for (FailSafe
= 100; FailSafe
> 1; FailSafe
--)
876 KeStallExecutionProcessor(100);
877 GetCommandRegister(&UsbCmd
);
884 DPRINT1("EHCI ERROR: Controller is not responding to reset request!\n");
885 return STATUS_UNSUCCESSFUL
;
888 return STATUS_SUCCESS
;
893 CUSBHardwareDevice::ResetPort(
897 LARGE_INTEGER Timeout
;
899 if (PortIndex
> m_Capabilities
.HCSParams
.PortCount
)
900 return STATUS_UNSUCCESSFUL
;
902 PortStatus
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortIndex
));
904 ASSERT(!EHCI_IS_LOW_SPEED(PortStatus
));
905 ASSERT(PortStatus
& EHCI_PRT_CONNECTED
);
908 // Reset and clean enable
910 PortStatus
|= EHCI_PRT_RESET
;
911 PortStatus
&= EHCI_PORTSC_DATAMASK
;
912 EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortIndex
), PortStatus
);
915 // delay is 50 ms for port reset as per USB 2.0 spec
917 Timeout
.QuadPart
= 50;
918 DPRINT1("Waiting %lu milliseconds for port reset\n", Timeout
.LowPart
);
921 // convert to 100 ns units (absolute)
923 Timeout
.QuadPart
*= -10000;
928 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
930 return STATUS_SUCCESS
;
935 CUSBHardwareDevice::GetPortStatus(
937 OUT USHORT
*PortStatus
,
938 OUT USHORT
*PortChange
)
941 USHORT Status
= 0, Change
= 0;
943 if (PortId
> m_Capabilities
.HCSParams
.PortCount
)
944 return STATUS_UNSUCCESSFUL
;
947 // Get the value of the Port Status and Control Register
949 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
));
952 // If the PowerPortControl is 0 then host controller does not have power control switches
953 if (!m_Capabilities
.HCSParams
.PortPowerControl
)
955 Status
|= USB_PORT_STATUS_POWER
;
959 // Check the value of PortPower
960 if (Value
& EHCI_PRT_POWER
)
962 Status
|= USB_PORT_STATUS_POWER
;
966 // Get Connected Status
967 if (Value
& EHCI_PRT_CONNECTED
)
969 Status
|= USB_PORT_STATUS_CONNECT
;
971 // EHCI only supports high speed
972 Status
|= USB_PORT_STATUS_HIGH_SPEED
;
975 // Get Enabled Status
976 if (Value
& EHCI_PRT_ENABLED
)
977 Status
|= USB_PORT_STATUS_ENABLE
;
980 if (Value
& EHCI_PRT_SUSPEND
)
981 Status
|= USB_PORT_STATUS_SUSPEND
;
983 // a overcurrent is active?
984 if (Value
& EHCI_PRT_OVERCURRENTACTIVE
)
985 Status
|= USB_PORT_STATUS_OVER_CURRENT
;
988 if ((Value
& EHCI_PRT_RESET
) || m_PortResetInProgress
[PortId
])
990 Status
|= USB_PORT_STATUS_RESET
;
991 Change
|= USB_PORT_STATUS_RESET
;
994 // This indicates a connect or disconnect
995 if (Value
& EHCI_PRT_CONNECTSTATUSCHANGE
)
996 Change
|= USB_PORT_STATUS_CONNECT
;
998 // This is set to indicate a critical port error
999 if (Value
& EHCI_PRT_ENABLEDSTATUSCHANGE
)
1000 Change
|= USB_PORT_STATUS_ENABLE
;
1002 *PortStatus
= Status
;
1003 *PortChange
= Change
;
1005 return STATUS_SUCCESS
;
1010 CUSBHardwareDevice::ClearPortStatus(
1015 LARGE_INTEGER Timeout
;
1017 DPRINT("CUSBHardwareDevice::ClearPortStatus PortId %x Feature %x\n", PortId
, Status
);
1019 if (PortId
> m_Capabilities
.HCSParams
.PortCount
)
1020 return STATUS_UNSUCCESSFUL
;
1022 if (Status
== C_PORT_RESET
)
1025 m_PortResetInProgress
[PortId
] = FALSE
;
1028 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
));
1029 Value
&= (EHCI_PORTSC_DATAMASK
| EHCI_PRT_ENABLED
);
1030 Value
&= ~EHCI_PRT_RESET
;
1031 EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
), Value
);
1034 // wait for reset bit to clear
1038 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
));
1040 if (!(Value
& EHCI_PRT_RESET
))
1043 KeStallExecutionProcessor(20);
1049 Timeout
.QuadPart
= 50;
1050 DPRINT1("Waiting %lu milliseconds for port to recover after reset\n", Timeout
.LowPart
);
1053 // convert to 100 ns units (absolute)
1055 Timeout
.QuadPart
*= -10000;
1060 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1063 // check the port status after reset
1065 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
));
1066 if (!(Value
& EHCI_PRT_CONNECTED
))
1068 DPRINT1("No device is here after reset. Bad controller/device?\n");
1069 return STATUS_UNSUCCESSFUL
;
1071 else if (EHCI_IS_LOW_SPEED(Value
))
1073 DPRINT1("Low speed device connected. Releasing ownership\n");
1074 EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
), Value
| EHCI_PRT_RELEASEOWNERSHIP
);
1075 return STATUS_DEVICE_NOT_CONNECTED
;
1077 else if (!(Value
& EHCI_PRT_ENABLED
))
1079 DPRINT1("Full speed device connected. Releasing ownership\n");
1080 EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
), Value
| EHCI_PRT_RELEASEOWNERSHIP
);
1081 return STATUS_DEVICE_NOT_CONNECTED
;
1085 DPRINT1("High speed device connected\n");
1086 return STATUS_SUCCESS
;
1089 else if (Status
== C_PORT_CONNECTION
)
1092 // reset status change bits
1094 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
));
1095 EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
), Value
);
1097 if (Value
& EHCI_PRT_CONNECTED
)
1102 Timeout
.QuadPart
= 100;
1103 DPRINT1("Waiting %lu milliseconds for port to stabilize after connection\n", Timeout
.LowPart
);
1106 // convert to 100 ns units (absolute)
1108 Timeout
.QuadPart
*= -10000;
1113 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1117 return STATUS_SUCCESS
;
1123 CUSBHardwareDevice::SetPortFeature(
1129 DPRINT("CUSBHardwareDevice::SetPortFeature\n");
1131 if (PortId
> m_Capabilities
.HCSParams
.PortCount
)
1132 return STATUS_UNSUCCESSFUL
;
1134 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
));
1136 if (Feature
== PORT_ENABLE
)
1139 // FIXME: EHCI Ports can only be disabled via reset
1141 DPRINT1("PORT_ENABLE not supported for EHCI\n");
1144 if (Feature
== PORT_RESET
)
1151 // reset in progress
1152 m_PortResetInProgress
[PortId
] = TRUE
;
1155 // is there a status change callback
1157 if (m_SCECallBack
!= NULL
)
1162 m_SCECallBack(m_SCEContext
);
1166 if (Feature
== PORT_POWER
)
1168 if (m_Capabilities
.HCSParams
.PortPowerControl
)
1171 LARGE_INTEGER Timeout
;
1174 // enable port power
1176 Value
= EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * PortId
)) | EHCI_PRT_POWER
;
1177 EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
, Value
);
1182 Timeout
.QuadPart
= 20;
1183 DPRINT1("Waiting %lu milliseconds for port power up\n", Timeout
.LowPart
);
1186 // convert to 100 ns units (absolute)
1188 Timeout
.QuadPart
*= -10000;
1193 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1196 return STATUS_SUCCESS
;
1201 CUSBHardwareDevice::SetAsyncListRegister(
1202 ULONG PhysicalAddress
)
1204 EHCI_WRITE_REGISTER_ULONG(EHCI_ASYNCLISTBASE
, PhysicalAddress
);
1209 CUSBHardwareDevice::SetPeriodicListRegister(
1210 ULONG PhysicalAddress
)
1213 // store physical address
1215 m_SyncFramePhysAddr
= PhysicalAddress
;
1218 struct _QUEUE_HEAD
*
1220 CUSBHardwareDevice::GetAsyncListQueueHead()
1222 return AsyncQueueHead
;
1227 CUSBHardwareDevice::GetPeriodicListRegister()
1235 CUSBHardwareDevice::SetStatusChangeEndpointCallBack(
1239 m_SCECallBack
= (HD_INIT_CALLBACK
*)CallBack
;
1240 m_SCEContext
= Context
;
1245 InterruptServiceRoutine(
1246 IN PKINTERRUPT Interrupt
,
1247 IN PVOID ServiceContext
)
1249 CUSBHardwareDevice
*This
;
1252 This
= (CUSBHardwareDevice
*) ServiceContext
;
1253 CStatus
= This
->EHCI_READ_REGISTER_ULONG(EHCI_USBSTS
);
1255 CStatus
&= (EHCI_ERROR_INT
| EHCI_STS_INT
| EHCI_STS_IAA
| EHCI_STS_PCD
| EHCI_STS_FLR
);
1256 DPRINT("CStatus %x\n", CStatus
);
1259 // Check that it belongs to EHCI
1267 This
->EHCI_WRITE_REGISTER_ULONG(EHCI_USBSTS
, CStatus
);
1269 if (CStatus
& EHCI_STS_FATAL
)
1271 This
->StopController();
1272 DPRINT1("EHCI: Host System Error!\n");
1276 if (CStatus
& EHCI_ERROR_INT
)
1278 DPRINT1("EHCI Status = 0x%x\n", CStatus
);
1281 if (CStatus
& EHCI_STS_HALT
)
1283 DPRINT1("Host Error Unexpected Halt\n");
1284 // FIXME: Reset controller\n");
1288 KeInsertQueueDpc(&This
->m_IntDpcObject
, This
, (PVOID
)CStatus
);
1293 EhciDefferedRoutine(
1295 IN PVOID DeferredContext
,
1296 IN PVOID SystemArgument1
,
1297 IN PVOID SystemArgument2
)
1299 CUSBHardwareDevice
*This
;
1300 ULONG CStatus
, PortStatus
, PortCount
, i
, ShouldRingDoorBell
, QueueSCEWorkItem
;
1301 NTSTATUS Status
= STATUS_SUCCESS
;
1302 EHCI_USBCMD_CONTENT UsbCmd
;
1304 This
= (CUSBHardwareDevice
*) SystemArgument1
;
1305 CStatus
= (ULONG
) SystemArgument2
;
1307 DPRINT("CStatus %x\n", CStatus
);
1310 // check for completion of async schedule
1312 if (CStatus
& (EHCI_STS_RECL
| EHCI_STS_INT
| EHCI_ERROR_INT
))
1315 // check if there is a door bell ring in progress
1317 if (This
->m_DoorBellRingInProgress
== FALSE
)
1319 if (CStatus
& EHCI_ERROR_INT
)
1322 // controller reported error
1324 DPRINT1("CStatus %x\n", CStatus
);
1329 // inform IUSBQueue of a completed queue head
1331 This
->m_UsbQueue
->InterruptCallback(Status
, &ShouldRingDoorBell
);
1334 // was a queue head completed?
1336 if (ShouldRingDoorBell
)
1339 // set door ring bell in progress status flag
1341 This
->m_DoorBellRingInProgress
= TRUE
;
1344 // get command register
1346 This
->GetCommandRegister(&UsbCmd
);
1349 // set door rang bell bit
1351 UsbCmd
.DoorBell
= TRUE
;
1354 // update command status
1356 This
->SetCommandRegister(&UsbCmd
);
1362 // check if the controller has acknowledged the door bell
1364 if (CStatus
& EHCI_STS_IAA
)
1367 // controller has acknowledged, assert we rang the bell
1369 PC_ASSERT(This
->m_DoorBellRingInProgress
== TRUE
);
1372 // now notify IUSBQueue that it can free completed requests
1374 This
->m_UsbQueue
->CompleteAsyncRequests();
1377 // door ring bell completed
1379 This
->m_DoorBellRingInProgress
= FALSE
;
1382 This
->GetDeviceDetails(NULL
, NULL
, &PortCount
, NULL
);
1383 if (CStatus
& EHCI_STS_PCD
)
1385 QueueSCEWorkItem
= FALSE
;
1386 for (i
= 0; i
< PortCount
; i
++)
1388 PortStatus
= This
->EHCI_READ_REGISTER_ULONG(EHCI_PORTSC
+ (4 * i
));
1391 // Device connected or removed
1393 if (PortStatus
& EHCI_PRT_CONNECTSTATUSCHANGE
)
1395 if (PortStatus
& EHCI_PRT_CONNECTED
)
1397 DPRINT1("Device connected on port %lu\n", i
);
1399 if (This
->m_Capabilities
.HCSParams
.CHCCount
)
1401 if (PortStatus
& EHCI_PRT_ENABLED
)
1403 DPRINT1("Misbeaving controller. Port should be disabled at this point\n");
1406 if (EHCI_IS_LOW_SPEED(PortStatus
))
1408 DPRINT1("Low speed device connected. Releasing ownership\n");
1409 This
->EHCI_WRITE_REGISTER_ULONG(EHCI_PORTSC
+ (4 * i
), PortStatus
| EHCI_PRT_RELEASEOWNERSHIP
);
1417 QueueSCEWorkItem
= TRUE
;
1421 DPRINT1("Device disconnected on port %lu\n", i
);
1426 QueueSCEWorkItem
= TRUE
;
1432 // is there a status change callback and a high speed device connected / disconnected
1434 if (QueueSCEWorkItem
&& This
->m_SCECallBack
!= NULL
)
1436 if (InterlockedCompareExchange(&This
->m_StatusChangeWorkItemStatus
, 1, 0) == 0)
1439 // queue work item for processing
1441 ExQueueWorkItem(&This
->m_StatusChangeWorkItem
, DelayedWorkQueue
);
1450 StatusChangeWorkItemRoutine(
1454 // cast to hardware object
1456 CUSBHardwareDevice
* This
= (CUSBHardwareDevice
*)Context
;
1459 // is there a callback
1461 if (This
->m_SCECallBack
)
1466 This
->m_SCECallBack(This
->m_SCEContext
);
1470 // reset active status
1472 InterlockedDecrement(&This
->m_StatusChangeWorkItemStatus
);
1478 PUSBHARDWAREDEVICE
*OutHardware
)
1480 PUSBHARDWAREDEVICE This
;
1482 This
= new(NonPagedPool
, TAG_USBEHCI
) CUSBHardwareDevice(0);
1485 return STATUS_INSUFFICIENT_RESOURCES
;
1490 *OutHardware
= (PUSBHARDWAREDEVICE
)This
;
1492 return STATUS_SUCCESS
;