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/usbohci/hcd_controller.cpp
5 * PURPOSE: USB OHCI device driver.
7 * Michael Martin (michael.martin@reactos.org)
8 * Johannes Anderwald (johannes.anderwald@reactos.org)
16 typedef VOID __stdcall
HD_INIT_CALLBACK(IN PVOID CallBackContext
);
20 InterruptServiceRoutine(
21 IN PKINTERRUPT Interrupt
,
22 IN PVOID ServiceContext
);
28 IN PVOID DeferredContext
,
29 IN PVOID SystemArgument1
,
30 IN PVOID SystemArgument2
);
34 StatusChangeWorkItemRoutine(PVOID Context
);
36 class CUSBHardwareDevice
: public IOHCIHardwareDevice
39 STDMETHODIMP
QueryInterface( REFIID InterfaceId
, PVOID
* Interface
);
41 STDMETHODIMP_(ULONG
) AddRef()
43 InterlockedIncrement(&m_Ref
);
46 STDMETHODIMP_(ULONG
) Release()
48 InterlockedDecrement(&m_Ref
);
58 IMP_IUSBHARDWAREDEVICE
59 IMP_IUSBOHCIHARDWAREDEVICE
63 NTSTATUS
StartController();
64 NTSTATUS
StopController();
65 BOOLEAN
InterruptService();
66 NTSTATUS
InitializeController();
67 NTSTATUS
AllocateEndpointDescriptor(OUT POHCI_ENDPOINT_DESCRIPTOR
*OutDescriptor
);
70 friend BOOLEAN NTAPI
InterruptServiceRoutine(IN PKINTERRUPT Interrupt
, IN PVOID ServiceContext
);
71 friend VOID NTAPI
OhciDefferedRoutine(IN PKDPC Dpc
, IN PVOID DeferredContext
, IN PVOID SystemArgument1
, IN PVOID SystemArgument2
);
72 friend VOID NTAPI
StatusChangeWorkItemRoutine(PVOID Context
);
73 // constructor / destructor
74 CUSBHardwareDevice(IUnknown
*OuterUnknown
){}
75 virtual ~CUSBHardwareDevice(){}
78 LONG m_Ref
; // reference count
79 PDRIVER_OBJECT m_DriverObject
; // driver object
80 PDEVICE_OBJECT m_PhysicalDeviceObject
; // pdo
81 PDEVICE_OBJECT m_FunctionalDeviceObject
; // fdo (hcd controller)
82 PDEVICE_OBJECT m_NextDeviceObject
; // lower device object
83 KSPIN_LOCK m_Lock
; // hardware lock
84 PKINTERRUPT m_Interrupt
; // interrupt object
85 KDPC m_IntDpcObject
; // dpc object for deferred isr processing
86 PVOID VirtualBase
; // virtual base for memory manager
87 PHYSICAL_ADDRESS PhysicalAddress
; // physical base for memory manager
88 PULONG m_Base
; // OHCI operational port base registers
89 PDMA_ADAPTER m_Adapter
; // dma adapter object
90 ULONG m_MapRegisters
; // map registers count
91 USHORT m_VendorID
; // vendor id
92 USHORT m_DeviceID
; // device id
93 POHCIQUEUE m_UsbQueue
; // usb request queue
94 POHCIHCCA m_HCCA
; // hcca virtual base
95 PHYSICAL_ADDRESS m_HCCAPhysicalAddress
; // hcca physical address
96 POHCI_ENDPOINT_DESCRIPTOR m_ControlEndpointDescriptor
; // dummy control endpoint descriptor
97 POHCI_ENDPOINT_DESCRIPTOR m_BulkEndpointDescriptor
; // dummy control endpoint descriptor
98 POHCI_ENDPOINT_DESCRIPTOR m_IsoEndpointDescriptor
; // iso endpoint descriptor
99 POHCI_ENDPOINT_DESCRIPTOR m_InterruptEndpoints
[OHCI_STATIC_ENDPOINT_COUNT
]; // endpoints for interrupt / iso transfers
100 ULONG m_NumberOfPorts
; // number of ports
101 PDMAMEMORYMANAGER m_MemoryManager
; // memory manager
102 HD_INIT_CALLBACK
* m_SCECallBack
; // status change callback routine
103 PVOID m_SCEContext
; // status change callback routine context
104 WORK_QUEUE_ITEM m_StatusChangeWorkItem
; // work item for status change callback
105 volatile LONG m_StatusChangeWorkItemStatus
; // work item active status
106 ULONG m_SyncFramePhysAddr
; // periodic frame list physical address
107 ULONG m_IntervalValue
; // periodic interval value
110 //=================================================================================================
115 CUSBHardwareDevice::QueryInterface(
119 if (IsEqualGUIDAligned(refiid
, IID_IUnknown
))
121 *Output
= PVOID(PUNKNOWN(this));
122 PUNKNOWN(*Output
)->AddRef();
123 return STATUS_SUCCESS
;
126 return STATUS_UNSUCCESSFUL
;
131 CUSBHardwareDevice::GetUSBType()
139 CUSBHardwareDevice::Initialize(
140 PDRIVER_OBJECT DriverObject
,
141 PDEVICE_OBJECT FunctionalDeviceObject
,
142 PDEVICE_OBJECT PhysicalDeviceObject
,
143 PDEVICE_OBJECT LowerDeviceObject
)
145 BUS_INTERFACE_STANDARD BusInterface
;
146 PCI_COMMON_CONFIG PciConfig
;
151 DPRINT("CUSBHardwareDevice::Initialize\n");
154 // Create DMAMemoryManager for use with QueueHeads and Transfer Descriptors.
156 Status
= CreateDMAMemoryManager(&m_MemoryManager
);
157 if (!NT_SUCCESS(Status
))
159 DPRINT1("Failed to create DMAMemoryManager Object\n");
164 // Create the UsbQueue class that will handle the Asynchronous and Periodic Schedules
166 Status
= CreateUSBQueue(&Queue
);
167 if (!NT_SUCCESS(Status
))
169 DPRINT1("Failed to create UsbQueue!\n");
174 m_UsbQueue
= POHCIQUEUE(Queue
);
180 // store device objects
182 m_DriverObject
= DriverObject
;
183 m_FunctionalDeviceObject
= FunctionalDeviceObject
;
184 m_PhysicalDeviceObject
= PhysicalDeviceObject
;
185 m_NextDeviceObject
= LowerDeviceObject
;
188 // initialize device lock
190 KeInitializeSpinLock(&m_Lock
);
193 // intialize status change work item
195 ExInitializeWorkItem(&m_StatusChangeWorkItem
, StatusChangeWorkItemRoutine
, PVOID(this));
200 Status
= GetBusInterface(PhysicalDeviceObject
, &BusInterface
);
201 if (!NT_SUCCESS(Status
))
203 DPRINT1("Failed to get BusInteface!\n");
207 BytesRead
= (*BusInterface
.GetBusData
)(BusInterface
.Context
,
208 PCI_WHICHSPACE_CONFIG
,
211 PCI_COMMON_HDR_LENGTH
);
213 if (BytesRead
!= PCI_COMMON_HDR_LENGTH
)
215 DPRINT1("Failed to get pci config information!\n");
216 return STATUS_SUCCESS
;
219 m_VendorID
= PciConfig
.VendorID
;
220 m_DeviceID
= PciConfig
.DeviceID
;
222 return STATUS_SUCCESS
;
227 CUSBHardwareDevice::PnpStart(
228 PCM_RESOURCE_LIST RawResources
,
229 PCM_RESOURCE_LIST TranslatedResources
)
232 PCM_PARTIAL_RESOURCE_DESCRIPTOR ResourceDescriptor
;
233 DEVICE_DESCRIPTION DeviceDescription
;
238 DPRINT("CUSBHardwareDevice::PnpStart\n");
239 for(Index
= 0; Index
< TranslatedResources
->List
[0].PartialResourceList
.Count
; Index
++)
242 // get resource descriptor
244 ResourceDescriptor
= &TranslatedResources
->List
[0].PartialResourceList
.PartialDescriptors
[Index
];
246 switch(ResourceDescriptor
->Type
)
248 case CmResourceTypeInterrupt
:
250 KeInitializeDpc(&m_IntDpcObject
,
254 Status
= IoConnectInterrupt(&m_Interrupt
,
255 InterruptServiceRoutine
,
258 ResourceDescriptor
->u
.Interrupt
.Vector
,
259 (KIRQL
)ResourceDescriptor
->u
.Interrupt
.Level
,
260 (KIRQL
)ResourceDescriptor
->u
.Interrupt
.Level
,
261 (KINTERRUPT_MODE
)(ResourceDescriptor
->Flags
& CM_RESOURCE_INTERRUPT_LATCHED
),
262 (ResourceDescriptor
->ShareDisposition
!= CmResourceShareDeviceExclusive
),
263 ResourceDescriptor
->u
.Interrupt
.Affinity
,
266 if (!NT_SUCCESS(Status
))
269 // failed to register interrupt
271 DPRINT1("IoConnect Interrupt failed with %x\n", Status
);
276 case CmResourceTypeMemory
:
281 ResourceBase
= MmMapIoSpace(ResourceDescriptor
->u
.Memory
.Start
, ResourceDescriptor
->u
.Memory
.Length
, MmNonCached
);
285 // failed to map registers
287 DPRINT1("MmMapIoSpace failed\n");
288 return STATUS_INSUFFICIENT_RESOURCES
;
292 // Get controllers capabilities
294 Version
= READ_REGISTER_ULONG((PULONG
)((ULONG_PTR
)ResourceBase
+ OHCI_REVISION_OFFSET
));
296 DPRINT("Version %x\n", Version
& 0xFFFF);
299 // Store Resource base
301 m_Base
= (PULONG
)ResourceBase
;
309 // zero device description
311 RtlZeroMemory(&DeviceDescription
, sizeof(DEVICE_DESCRIPTION
));
314 // initialize device description
316 DeviceDescription
.Version
= DEVICE_DESCRIPTION_VERSION
;
317 DeviceDescription
.Master
= TRUE
;
318 DeviceDescription
.ScatterGather
= TRUE
;
319 DeviceDescription
.Dma32BitAddresses
= TRUE
;
320 DeviceDescription
.DmaWidth
= Width32Bits
;
321 DeviceDescription
.InterfaceType
= PCIBus
;
322 DeviceDescription
.MaximumLength
= MAXULONG
;
327 m_Adapter
= IoGetDmaAdapter(m_PhysicalDeviceObject
, &DeviceDescription
, &m_MapRegisters
);
331 // failed to get dma adapter
333 DPRINT1("Failed to acquire dma adapter\n");
334 return STATUS_INSUFFICIENT_RESOURCES
;
338 // Create Common Buffer
340 VirtualBase
= m_Adapter
->DmaOperations
->AllocateCommonBuffer(m_Adapter
,
346 DPRINT1("Failed to allocate a common buffer\n");
347 return STATUS_INSUFFICIENT_RESOURCES
;
351 // Initialize the DMAMemoryManager
353 Status
= m_MemoryManager
->Initialize(this, &m_Lock
, PAGE_SIZE
* 4, VirtualBase
, PhysicalAddress
, 32);
354 if (!NT_SUCCESS(Status
))
356 DPRINT1("Failed to initialize the DMAMemoryManager\n");
361 // initializes the controller
363 Status
= InitializeController();
364 if (!NT_SUCCESS(Status
))
366 DPRINT1("Failed to Initialize the controller \n");
371 // Initialize the UsbQueue now that we have an AdapterObject.
373 Status
= m_UsbQueue
->Initialize(this, m_Adapter
, m_MemoryManager
, NULL
);
374 if (!NT_SUCCESS(Status
))
376 DPRINT1("Failed to Initialize the UsbQueue\n");
381 // Start the controller
383 DPRINT1("Starting Controller\n");
384 Status
= StartController();
394 CUSBHardwareDevice::PnpStop(void)
397 return STATUS_NOT_IMPLEMENTED
;
402 CUSBHardwareDevice::GetDeviceDetails(
403 OUT OPTIONAL PUSHORT VendorId
,
404 OUT OPTIONAL PUSHORT DeviceId
,
405 OUT OPTIONAL PULONG NumberOfPorts
,
406 OUT OPTIONAL PULONG Speed
)
413 *VendorId
= m_VendorID
;
421 *DeviceId
= m_DeviceID
;
427 // get number of ports
429 *NumberOfPorts
= m_NumberOfPorts
;
440 return STATUS_SUCCESS
;
445 CUSBHardwareDevice::GetDMA(
446 OUT
struct IDMAMemoryManager
**OutDMAMemoryManager
)
448 if (!m_MemoryManager
)
449 return STATUS_UNSUCCESSFUL
;
450 *OutDMAMemoryManager
= m_MemoryManager
;
451 return STATUS_SUCCESS
;
456 CUSBHardwareDevice::GetUSBQueue(
457 OUT
struct IUSBQueue
**OutUsbQueue
)
460 return STATUS_UNSUCCESSFUL
;
461 *OutUsbQueue
= m_UsbQueue
;
462 return STATUS_SUCCESS
;
467 CUSBHardwareDevice::StartController(void)
469 ULONG Control
, Descriptor
, FrameInterval
, Periodic
, Port
, Reset
, Index
;
470 ULONG NewControl
, WaitInMs
;
471 LARGE_INTEGER Timeout
;
472 BOOLEAN Again
= FALSE
;
477 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
480 NewControl
= Control
& OHCI_REMOTE_WAKEUP_CONNECTED
;
482 if ((Control
& OHCI_INTERRUPT_ROUTING
))
487 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_OWNERSHIP_CHANGE_REQUEST
);
488 for(Index
= 0; Index
< 100; Index
++)
493 KeStallExecutionProcessor(100);
498 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
499 if (!(Control
& OHCI_INTERRUPT_ROUTING
))
502 // acquired ownership
509 // if the ownership is still not changed, perform reset
511 if (Control
& OHCI_INTERRUPT_ROUTING
)
513 DPRINT1("SMM not responding\n");
517 DPRINT1("SMM has given up ownership\n");
522 // read contents of control register
525 Control
= (READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
)) & OHCI_HC_FUNCTIONAL_STATE_MASK
);
526 DPRINT1("Controller State %x\n", Control
);
530 case OHCI_HC_FUNCTIONAL_STATE_RESET
:
531 NewControl
|= OHCI_HC_FUNCTIONAL_STATE_RESET
;
535 case OHCI_HC_FUNCTIONAL_STATE_SUSPEND
:
536 case OHCI_HC_FUNCTIONAL_STATE_RESUME
:
537 NewControl
|= OHCI_HC_FUNCTIONAL_STATE_RESUME
;
549 // Do the state transition
550 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
), NewControl
);
557 Timeout
.QuadPart
= WaitInMs
;
558 DPRINT1("Waiting %lu milliseconds for controller to transition state\n", Timeout
.LowPart
);
561 // convert to 100 ns units (absolute)
563 Timeout
.QuadPart
*= -10000;
568 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
573 // now reset controller
575 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_HOST_CONTROLLER_RESET
);
578 // reset time is 10ms
580 for(Index
= 0; Index
< 100; Index
++)
585 KeStallExecutionProcessor(10);
588 // read command status
590 Reset
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
));
593 // was reset bit cleared
595 if ((Reset
& OHCI_HOST_CONTROLLER_RESET
) == 0)
598 // controller completed reset
604 if ((Reset
& OHCI_HOST_CONTROLLER_RESET
))
607 // failed to reset controller
609 return STATUS_UNSUCCESSFUL
;
613 // get frame interval
615 FrameInterval
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_OFFSET
));
616 m_IntervalValue
= OHCI_GET_INTERVAL_VALUE(FrameInterval
);
618 FrameInterval
= ((FrameInterval
& OHCI_FRAME_INTERVAL_TOGGLE
) ^ OHCI_FRAME_INTERVAL_TOGGLE
);
620 DPRINT1("FrameInterval %x IntervalValue %x\n", FrameInterval
, m_IntervalValue
);
621 FrameInterval
|= OHCI_FSMPS(m_IntervalValue
) | m_IntervalValue
;
622 DPRINT1("Computed FrameInterval %x\n", FrameInterval
);
625 // write frame interval
627 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_OFFSET
), FrameInterval
);
629 FrameInterval
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_OFFSET
));
630 DPRINT1("Read FrameInterval %x\n", FrameInterval
);
635 Periodic
= OHCI_PERIODIC(m_IntervalValue
);
636 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_PERIODIC_START_OFFSET
), Periodic
);
637 DPRINT1("Computed Periodic Start %x\n", Periodic
);
639 Periodic
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_PERIODIC_START_OFFSET
));
640 DPRINT1("Read Periodic Start %x\n", Periodic
);
642 // Linux does this hack for some bad controllers
643 if (!(FrameInterval
& 0x3FFF0000) ||
648 DPRINT1("Trying reset again on faulty controller\n");
654 DPRINT1("Second reset didn't solve the problem, failing\n");
655 return STATUS_UNSUCCESSFUL
;
660 // lets write physical address of dummy control endpoint descriptor
662 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_HEAD_ED_OFFSET
), m_ControlEndpointDescriptor
->PhysicalAddress
.LowPart
);
665 // lets write physical address of dummy bulk endpoint descriptor
667 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_BULK_HEAD_ED_OFFSET
), m_BulkEndpointDescriptor
->PhysicalAddress
.LowPart
);
672 Descriptor
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_A_OFFSET
));
675 // get port count (in a loop due to AMD errata)
679 KeStallExecutionProcessor(20);
680 m_NumberOfPorts
= OHCI_RH_GET_PORT_COUNT(Descriptor
);
681 } while (m_NumberOfPorts
== 0);
683 DPRINT1("NumberOfPorts %lu\n", m_NumberOfPorts
);
684 ASSERT(m_NumberOfPorts
< OHCI_MAX_PORT_COUNT
);
687 // no over current protection
689 Descriptor
|= OHCI_RH_NO_OVER_CURRENT_PROTECTION
;
692 // power switching on
694 Descriptor
&= ~OHCI_RH_NO_POWER_SWITCHING
;
697 // control each port power independently
699 Descriptor
|= OHCI_RH_POWER_SWITCHING_MODE
;
702 // write the configuration back
704 DPRINT1("Descriptor A: %x\n", Descriptor
);
705 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_A_OFFSET
), Descriptor
);
710 Descriptor
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_B_OFFSET
));
713 // set power power control for each port to use PPS
715 for (Port
= 1; Port
<= m_NumberOfPorts
; Port
++)
717 Descriptor
|= (1 << (16 + Port
));
721 // write the configuration back
723 DPRINT1("Descriptor B: %x\n", Descriptor
);
724 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_B_OFFSET
), Descriptor
);
727 // HCCA alignment check
729 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_HCCA_OFFSET
), 0xFFFFFFFF);
730 KeStallExecutionProcessor(10);
731 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_HCCA_OFFSET
));
732 ASSERT((m_HCCAPhysicalAddress
.LowPart
& Control
) == m_HCCAPhysicalAddress
.LowPart
);
733 DPRINT1("HCCA: %x Alignment mask: %x\n", m_HCCAPhysicalAddress
.LowPart
, Control
);
736 // write address of HCCA
738 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_HCCA_OFFSET
), m_HCCAPhysicalAddress
.LowPart
);
741 // now enable the interrupts
743 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
), OHCI_NORMAL_INTERRUPTS
| OHCI_MASTER_INTERRUPT_ENABLE
);
748 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
), (NewControl
& OHCI_REMOTE_WAKEUP_CONNECTED
) | OHCI_ENABLE_LIST
);
751 // start the controller
753 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
), OHCI_ENABLE_LIST
|
754 (NewControl
& OHCI_REMOTE_WAKEUP_CONNECTED
) |
755 OHCI_CONTROL_BULK_RATIO_1_4
|
756 OHCI_HC_FUNCTIONAL_STATE_OPERATIONAL
);
761 KeStallExecutionProcessor(100);
764 // is the controller started
766 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
769 // assert that the controller has been started
771 ASSERT((Control
& OHCI_HC_FUNCTIONAL_STATE_MASK
) == OHCI_HC_FUNCTIONAL_STATE_OPERATIONAL
);
772 ASSERT((Control
& OHCI_ENABLE_LIST
) == OHCI_ENABLE_LIST
);
773 DPRINT1("Control %x\n", Control
);
778 DPRINT1("OHCI controller is operational\n");
779 return STATUS_SUCCESS
;
783 CUSBHardwareDevice::AllocateEndpointDescriptor(
784 OUT POHCI_ENDPOINT_DESCRIPTOR
*OutDescriptor
)
786 POHCI_ENDPOINT_DESCRIPTOR Descriptor
;
787 PHYSICAL_ADDRESS DescriptorAddress
;
791 // allocate descriptor
793 Status
= m_MemoryManager
->Allocate(sizeof(OHCI_ENDPOINT_DESCRIPTOR
), (PVOID
*)&Descriptor
, &DescriptorAddress
);
794 if (!NT_SUCCESS(Status
))
797 // failed to allocate descriptor
803 // intialize descriptor
805 Descriptor
->Flags
= OHCI_ENDPOINT_SKIP
;
806 Descriptor
->HeadPhysicalDescriptor
= 0;
807 Descriptor
->NextPhysicalEndpoint
= 0;
808 Descriptor
->TailPhysicalDescriptor
= 0;
809 Descriptor
->PhysicalAddress
.QuadPart
= DescriptorAddress
.QuadPart
;
814 *OutDescriptor
= Descriptor
;
819 return STATUS_SUCCESS
;
824 CUSBHardwareDevice::GetBulkHeadEndpointDescriptor(
825 struct _OHCI_ENDPOINT_DESCRIPTOR
** OutDescriptor
)
827 *OutDescriptor
= m_BulkEndpointDescriptor
;
832 CUSBHardwareDevice::GetInterruptEndpointDescriptors(
833 struct _OHCI_ENDPOINT_DESCRIPTOR
*** OutDescriptor
)
835 *OutDescriptor
= m_InterruptEndpoints
;
840 CUSBHardwareDevice::GetIsochronousHeadEndpointDescriptor(
841 struct _OHCI_ENDPOINT_DESCRIPTOR
** OutDescriptor
)
843 *OutDescriptor
= m_IsoEndpointDescriptor
;
848 CUSBHardwareDevice::HeadEndpointDescriptorModified(
851 if (Type
== USB_ENDPOINT_TYPE_CONTROL
)
856 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_CONTROL_LIST_FILLED
);
858 else if (Type
== USB_ENDPOINT_TYPE_BULK
)
863 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_BULK_LIST_FILLED
);
869 CUSBHardwareDevice::GetControlHeadEndpointDescriptor(
870 struct _OHCI_ENDPOINT_DESCRIPTOR
** OutDescriptor
)
872 *OutDescriptor
= m_ControlEndpointDescriptor
;
876 CUSBHardwareDevice::InitializeController()
879 ULONG Index
, Interval
, IntervalIndex
, InsertIndex
;
880 POHCI_ENDPOINT_DESCRIPTOR Descriptor
;
883 // first allocate the hcca area
885 Status
= m_MemoryManager
->Allocate(sizeof(OHCIHCCA
), (PVOID
*)&m_HCCA
, &m_HCCAPhysicalAddress
);
886 if (!NT_SUCCESS(Status
))
895 // now allocate an endpoint for control transfers
896 // this endpoint will never be removed
898 Status
= AllocateEndpointDescriptor(&m_ControlEndpointDescriptor
);
899 if (!NT_SUCCESS(Status
))
908 // now allocate an endpoint for bulk transfers
909 // this endpoint will never be removed
911 Status
= AllocateEndpointDescriptor(&m_BulkEndpointDescriptor
);
912 if (!NT_SUCCESS(Status
))
921 // now allocate an endpoint for iso transfers
922 // this endpoint will never be removed
924 Status
= AllocateEndpointDescriptor(&m_IsoEndpointDescriptor
);
925 if (!NT_SUCCESS(Status
))
934 // now allocate endpoint descriptors for iso / interrupt transfers interval is 1,2,4,8,16,32
936 for(Index
= 0; Index
< OHCI_STATIC_ENDPOINT_COUNT
; Index
++)
939 // allocate endpoint descriptor
941 Status
= AllocateEndpointDescriptor(&Descriptor
);
942 if (!NT_SUCCESS(Status
))
953 m_InterruptEndpoints
[Index
] = Descriptor
;
958 // now link the descriptors, taken from Haiku
960 Interval
= OHCI_BIGGEST_INTERVAL
;
961 IntervalIndex
= OHCI_STATIC_ENDPOINT_COUNT
- 1;
964 InsertIndex
= Interval
/ 2;
965 while (InsertIndex
< OHCI_BIGGEST_INTERVAL
)
968 // assign endpoint address
970 m_HCCA
->InterruptTable
[InsertIndex
] = m_InterruptEndpoints
[IntervalIndex
]->PhysicalAddress
.LowPart
;
971 InsertIndex
+= Interval
;
979 // link all endpoint descriptors to first descriptor in array
981 m_HCCA
->InterruptTable
[0] = m_InterruptEndpoints
[0]->PhysicalAddress
.LowPart
;
982 for (Index
= 1; Index
< OHCI_STATIC_ENDPOINT_COUNT
; Index
++)
987 m_InterruptEndpoints
[Index
]->NextPhysicalEndpoint
= m_InterruptEndpoints
[0]->PhysicalAddress
.LowPart
;
991 // Now link the first endpoint to the isochronous endpoint
993 m_InterruptEndpoints
[0]->NextPhysicalEndpoint
= m_IsoEndpointDescriptor
->PhysicalAddress
.LowPart
;
996 // set iso endpoint type
998 m_IsoEndpointDescriptor
->Flags
|= OHCI_ENDPOINT_ISOCHRONOUS_FORMAT
;
1003 return STATUS_SUCCESS
;
1007 CUSBHardwareDevice::StopController(void)
1011 return STATUS_UNSUCCESSFUL
;
1016 CUSBHardwareDevice::ResetPort(
1021 return STATUS_SUCCESS
;
1026 CUSBHardwareDevice::GetPortStatus(
1028 OUT USHORT
*PortStatus
,
1029 OUT USHORT
*PortChange
)
1033 if (PortId
> m_NumberOfPorts
)
1034 return STATUS_UNSUCCESSFUL
;
1036 // init result variables
1043 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)));
1044 DPRINT("GetPortStatus PortId %x Value %x\n", PortId
, Value
);
1047 if (Value
& OHCI_RH_PORTSTATUS_CCS
)
1049 *PortStatus
|= USB_PORT_STATUS_CONNECT
;
1052 if (Value
& OHCI_RH_PORTSTATUS_LSDA
)
1053 *PortStatus
|= USB_PORT_STATUS_LOW_SPEED
;
1056 // did a device connect?
1057 if (Value
& OHCI_RH_PORTSTATUS_CSC
)
1058 *PortChange
|= USB_PORT_STATUS_CONNECT
;
1061 if (Value
& OHCI_RH_PORTSTATUS_PES
)
1062 *PortStatus
|= USB_PORT_STATUS_ENABLE
;
1064 // port disconnect or hardware error
1065 if (Value
& OHCI_RH_PORTSTATUS_PESC
)
1066 *PortChange
|= USB_PORT_STATUS_CONNECT
;
1069 if (Value
& OHCI_RH_PORTSTATUS_PSS
)
1070 *PortStatus
|= USB_PORT_STATUS_SUSPEND
;
1073 if (Value
& OHCI_RH_PORTSTATUS_PSSC
)
1074 *PortChange
|= USB_PORT_STATUS_ENABLE
;
1076 // port reset started
1077 if (Value
& OHCI_RH_PORTSTATUS_PRS
)
1078 *PortStatus
|= USB_PORT_STATUS_RESET
;
1081 if (Value
& OHCI_RH_PORTSTATUS_PRSC
)
1082 *PortChange
|= USB_PORT_STATUS_RESET
;
1084 return STATUS_SUCCESS
;
1089 CUSBHardwareDevice::ClearPortStatus(
1095 DPRINT("CUSBHardwareDevice::ClearPortStatus PortId %x Feature %x\n", PortId
, Status
);
1097 if (PortId
> m_NumberOfPorts
)
1098 return STATUS_UNSUCCESSFUL
;
1100 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)));
1102 if (Status
== C_PORT_RESET
)
1107 ASSERT((Value
& OHCI_RH_PORTSTATUS_PRSC
));
1110 // clear reset bit complete
1112 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PRSC
);
1117 ASSERT((Value
& OHCI_RH_PORTSTATUS_PES
));
1120 if (Status
== C_PORT_CONNECTION
|| Status
== C_PORT_ENABLE
)
1123 // clear change bits
1125 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), Value
& (OHCI_RH_PORTSTATUS_CSC
| OHCI_RH_PORTSTATUS_PESC
));
1128 // wait for port to stabilize
1130 if (Status
== C_PORT_CONNECTION
&& (Value
& OHCI_RH_PORTSTATUS_CCS
))
1132 LARGE_INTEGER Timeout
;
1137 Timeout
.QuadPart
= 100;
1138 DPRINT1("Waiting %lu milliseconds for port to stabilize after connection\n", Timeout
.LowPart
);
1141 // convert to 100 ns units (absolute)
1143 Timeout
.QuadPart
*= -10000;
1148 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1153 // re-enable root hub change
1155 DPRINT1("Enabling status change\n");
1156 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
), OHCI_ROOT_HUB_STATUS_CHANGE
);
1158 return STATUS_SUCCESS
;
1164 CUSBHardwareDevice::SetPortFeature(
1170 DPRINT("CUSBHardwareDevice::SetPortFeature PortId %x Feature %x\n", PortId
, Feature
);
1175 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)));
1178 if (Feature
== PORT_ENABLE
)
1183 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PES
);
1184 return STATUS_SUCCESS
;
1186 else if (Feature
== PORT_POWER
)
1188 LARGE_INTEGER Timeout
;
1193 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PPS
);
1196 // read descriptor A for the delay data
1198 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_A_OFFSET
));
1201 // compute the delay
1203 Timeout
.QuadPart
= OHCI_RH_GET_POWER_ON_TO_POWER_GOOD_TIME(Value
);
1206 // delay is multiplied by 2 ms
1208 Timeout
.QuadPart
*= 2;
1209 DPRINT1("Waiting %lu milliseconds for port power up\n", Timeout
.LowPart
);
1212 // convert to 100 ns units (absolute)
1214 Timeout
.QuadPart
*= -10000;
1219 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1221 return STATUS_SUCCESS
;
1223 else if (Feature
== PORT_SUSPEND
)
1228 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PSS
);
1229 return STATUS_SUCCESS
;
1231 else if (Feature
== PORT_RESET
)
1236 ASSERT((Value
& OHCI_RH_PORTSTATUS_CCS
));
1241 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PRS
);
1244 // an interrupt signals the reset completion
1246 return STATUS_SUCCESS
;
1248 return STATUS_SUCCESS
;
1255 CUSBHardwareDevice::SetStatusChangeEndpointCallBack(
1259 m_SCECallBack
= (HD_INIT_CALLBACK
*)CallBack
;
1260 m_SCEContext
= Context
;
1265 CUSBHardwareDevice::GetCurrentFrameNumber(
1272 Number
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_NUMBER_OFFSET
));
1273 DPRINT("FrameNumberInterval %x Frame %x\n", Number
, m_HCCA
->CurrentFrameNumber
);
1276 // remove reserved bits
1281 // store frame number
1283 *FrameNumber
= Number
;
1286 // is the controller started
1288 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
1289 ASSERT((Control
& OHCI_ENABLE_LIST
) == OHCI_ENABLE_LIST
);
1297 InterruptServiceRoutine(
1298 IN PKINTERRUPT Interrupt
,
1299 IN PVOID ServiceContext
)
1301 CUSBHardwareDevice
*This
;
1302 ULONG DoneHead
, Status
, Acknowledge
= 0;
1307 This
= (CUSBHardwareDevice
*) ServiceContext
;
1309 DPRINT("InterruptServiceRoutine\n");
1314 DoneHead
= This
->m_HCCA
->DoneHead
;
1322 // the interrupt was not caused by DoneHead update
1323 // check if something important happened
1325 DPRINT("InterruptStatus %x InterruptEnable %x\n", READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_STATUS_OFFSET
)),
1326 READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
)));
1327 Status
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_STATUS_OFFSET
)) & READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
)) & (~OHCI_WRITEBACK_DONE_HEAD
);
1331 // nothing happened, appears to be shared interrupt
1339 // DoneHead update happened, check if there are other events too
1341 Status
= OHCI_WRITEBACK_DONE_HEAD
;
1344 // since ed descriptors are 16 byte aligned, the controller sets the lower bits if there were other interrupt requests
1346 if (DoneHead
& OHCI_DONE_INTERRUPTS
)
1351 Status
|= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_STATUS_OFFSET
)) & READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
));
1358 ASSERT(Status
!= 0);
1360 if (Status
& OHCI_WRITEBACK_DONE_HEAD
)
1365 Acknowledge
|= OHCI_WRITEBACK_DONE_HEAD
;
1366 This
->m_HCCA
->DoneHead
= 0;
1369 if (Status
& OHCI_RESUME_DETECTED
)
1374 DPRINT1("InterruptServiceRoutine> Resume\n");
1375 Acknowledge
|= OHCI_RESUME_DETECTED
;
1379 if (Status
& OHCI_UNRECOVERABLE_ERROR
)
1381 DPRINT1("InterruptServiceRoutine> Controller error\n");
1387 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_CONTROL_OFFSET
), OHCI_HC_FUNCTIONAL_STATE_RESET
);
1390 if (Status
& OHCI_ROOT_HUB_STATUS_CHANGE
)
1393 // disable interrupt as it will fire untill the port has been reset
1395 DPRINT1("Disabling status change interrupt\n");
1396 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_DISABLE_OFFSET
), OHCI_ROOT_HUB_STATUS_CHANGE
);
1397 Acknowledge
|= OHCI_ROOT_HUB_STATUS_CHANGE
;
1401 // is there something to acknowledge
1408 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_STATUS_OFFSET
), Acknowledge
);
1414 DPRINT("Status %x Acknowledge %x FrameNumber %x\n", Status
, Acknowledge
, This
->m_HCCA
->CurrentFrameNumber
);
1415 KeInsertQueueDpc(&This
->m_IntDpcObject
, (PVOID
)Status
, (PVOID
)(DoneHead
& ~1));
1418 // interrupt handled
1425 OhciDefferedRoutine(
1427 IN PVOID DeferredContext
,
1428 IN PVOID SystemArgument1
,
1429 IN PVOID SystemArgument2
)
1431 CUSBHardwareDevice
*This
;
1432 ULONG CStatus
, Index
, PortStatus
;
1433 ULONG DoneHead
, QueueSCEWorkItem
;
1438 This
= (CUSBHardwareDevice
*)DeferredContext
;
1439 CStatus
= (ULONG
) SystemArgument1
;
1440 DoneHead
= (ULONG
)SystemArgument2
;
1442 DPRINT("OhciDefferedRoutine Status %x DoneHead %x\n", CStatus
, DoneHead
);
1444 if (CStatus
& OHCI_WRITEBACK_DONE_HEAD
)
1447 // notify queue of event
1449 This
->m_UsbQueue
->TransferDescriptorCompletionCallback(DoneHead
);
1451 if (CStatus
& OHCI_ROOT_HUB_STATUS_CHANGE
)
1454 // device connected, lets check which port
1456 QueueSCEWorkItem
= FALSE
;
1457 for(Index
= 0; Index
< This
->m_NumberOfPorts
; Index
++)
1462 PortStatus
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_RH_PORT_STATUS(Index
)));
1465 // check if there is a status change
1467 if (PortStatus
& OHCI_RH_PORTSTATUS_CSC
)
1470 // did a device connect
1472 if (PortStatus
& OHCI_RH_PORTSTATUS_CCS
)
1477 DPRINT1("New device arrival at Port %lu LowSpeed %x\n", Index
, (PortStatus
& OHCI_RH_PORTSTATUS_LSDA
));
1482 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_RH_PORT_STATUS(Index
)), OHCI_RH_PORTSTATUS_PES
);
1487 // device disconnected
1489 DPRINT1("Device disconnected at Port %x\n", Index
);
1495 QueueSCEWorkItem
= TRUE
;
1497 else if (PortStatus
& OHCI_RH_PORTSTATUS_PESC
)
1500 // device disconnected or some error condition
1502 ASSERT(!(PortStatus
& OHCI_RH_PORTSTATUS_PES
));
1507 QueueSCEWorkItem
= TRUE
;
1509 else if (PortStatus
& OHCI_RH_PORTSTATUS_PRSC
)
1512 // This is a port reset complete interrupt
1514 DPRINT1("Port %lu completed reset\n", Index
);
1517 // Queue a work item
1519 QueueSCEWorkItem
= TRUE
;
1524 // is there a status change callback and a device connected / disconnected
1526 if (QueueSCEWorkItem
&& This
->m_SCECallBack
!= NULL
)
1528 if (InterlockedCompareExchange(&This
->m_StatusChangeWorkItemStatus
, 1, 0) == 0)
1531 // queue work item for processing
1533 ExQueueWorkItem(&This
->m_StatusChangeWorkItem
, DelayedWorkQueue
);
1541 StatusChangeWorkItemRoutine(
1545 // cast to hardware object
1547 CUSBHardwareDevice
* This
= (CUSBHardwareDevice
*)Context
;
1550 // is there a callback
1552 if (This
->m_SCECallBack
)
1557 This
->m_SCECallBack(This
->m_SCEContext
);
1561 // reset active status
1563 InterlockedDecrement(&This
->m_StatusChangeWorkItemStatus
);
1569 PUSBHARDWAREDEVICE
*OutHardware
)
1571 PUSBHARDWAREDEVICE This
;
1573 This
= new(NonPagedPool
, TAG_USBOHCI
) CUSBHardwareDevice(0);
1576 return STATUS_INSUFFICIENT_RESOURCES
;
1581 *OutHardware
= (PUSBHARDWAREDEVICE
)This
;
1583 return STATUS_SUCCESS
;