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)
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 IOHCIHardwareDevice
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
58 IMP_IUSBOHCIHARDWAREDEVICE
62 NTSTATUS
StartController();
63 NTSTATUS
StopController();
64 BOOLEAN
InterruptService();
65 NTSTATUS
InitializeController();
66 NTSTATUS
AllocateEndpointDescriptor(OUT POHCI_ENDPOINT_DESCRIPTOR
*OutDescriptor
);
69 friend BOOLEAN NTAPI
InterruptServiceRoutine(IN PKINTERRUPT Interrupt
, IN PVOID ServiceContext
);
70 friend VOID NTAPI
OhciDefferedRoutine(IN PKDPC Dpc
, IN PVOID DeferredContext
, IN PVOID SystemArgument1
, IN PVOID SystemArgument2
);
71 friend VOID NTAPI
StatusChangeWorkItemRoutine(PVOID Context
);
72 // constructor / destructor
73 CUSBHardwareDevice(IUnknown
*OuterUnknown
){}
74 virtual ~CUSBHardwareDevice(){}
77 LONG m_Ref
; // reference count
78 PDRIVER_OBJECT m_DriverObject
; // driver object
79 PDEVICE_OBJECT m_PhysicalDeviceObject
; // pdo
80 PDEVICE_OBJECT m_FunctionalDeviceObject
; // fdo (hcd controller)
81 PDEVICE_OBJECT m_NextDeviceObject
; // lower device object
82 KSPIN_LOCK m_Lock
; // hardware lock
83 PKINTERRUPT m_Interrupt
; // interrupt object
84 KDPC m_IntDpcObject
; // dpc object for deferred isr processing
85 PVOID VirtualBase
; // virtual base for memory manager
86 PHYSICAL_ADDRESS PhysicalAddress
; // physical base for memory manager
87 PULONG m_Base
; // OHCI operational port base registers
88 PDMA_ADAPTER m_Adapter
; // dma adapter object
89 ULONG m_MapRegisters
; // map registers count
90 USHORT m_VendorID
; // vendor id
91 USHORT m_DeviceID
; // device id
92 POHCIQUEUE m_UsbQueue
; // usb request queue
93 POHCIHCCA m_HCCA
; // hcca virtual base
94 PHYSICAL_ADDRESS m_HCCAPhysicalAddress
; // hcca physical address
95 POHCI_ENDPOINT_DESCRIPTOR m_ControlEndpointDescriptor
; // dummy control endpoint descriptor
96 POHCI_ENDPOINT_DESCRIPTOR m_BulkEndpointDescriptor
; // dummy control endpoint descriptor
97 POHCI_ENDPOINT_DESCRIPTOR m_IsoEndpointDescriptor
; // iso endpoint descriptor
98 POHCI_ENDPOINT_DESCRIPTOR m_InterruptEndpoints
[OHCI_STATIC_ENDPOINT_COUNT
]; // endpoints for interrupt / iso transfers
99 ULONG m_NumberOfPorts
; // number of ports
100 PDMAMEMORYMANAGER m_MemoryManager
; // memory manager
101 HD_INIT_CALLBACK
* m_SCECallBack
; // status change callback routine
102 PVOID m_SCEContext
; // status change callback routine context
103 WORK_QUEUE_ITEM m_StatusChangeWorkItem
; // work item for status change callback
104 ULONG m_SyncFramePhysAddr
; // periodic frame list physical address
105 ULONG m_IntervalValue
; // periodic interval value
108 //=================================================================================================
113 CUSBHardwareDevice::QueryInterface(
117 if (IsEqualGUIDAligned(refiid
, IID_IUnknown
))
119 *Output
= PVOID(PUNKNOWN(this));
120 PUNKNOWN(*Output
)->AddRef();
121 return STATUS_SUCCESS
;
124 return STATUS_UNSUCCESSFUL
;
129 CUSBHardwareDevice::GetUSBType()
137 CUSBHardwareDevice::Initialize(
138 PDRIVER_OBJECT DriverObject
,
139 PDEVICE_OBJECT FunctionalDeviceObject
,
140 PDEVICE_OBJECT PhysicalDeviceObject
,
141 PDEVICE_OBJECT LowerDeviceObject
)
143 BUS_INTERFACE_STANDARD BusInterface
;
144 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(&Queue
);
165 if (!NT_SUCCESS(Status
))
167 DPRINT1("Failed to create UsbQueue!\n");
172 m_UsbQueue
= POHCIQUEUE(Queue
);
178 // store device objects
180 m_DriverObject
= DriverObject
;
181 m_FunctionalDeviceObject
= FunctionalDeviceObject
;
182 m_PhysicalDeviceObject
= PhysicalDeviceObject
;
183 m_NextDeviceObject
= LowerDeviceObject
;
186 // initialize device lock
188 KeInitializeSpinLock(&m_Lock
);
191 // intialize status change work item
193 ExInitializeWorkItem(&m_StatusChangeWorkItem
, StatusChangeWorkItemRoutine
, PVOID(this));
198 Status
= GetBusInterface(PhysicalDeviceObject
, &BusInterface
);
199 if (!NT_SUCCESS(Status
))
201 DPRINT1("Failed to get BusInteface!\n");
205 BytesRead
= (*BusInterface
.GetBusData
)(BusInterface
.Context
,
206 PCI_WHICHSPACE_CONFIG
,
209 PCI_COMMON_HDR_LENGTH
);
211 if (BytesRead
!= PCI_COMMON_HDR_LENGTH
)
213 DPRINT1("Failed to get pci config information!\n");
214 return STATUS_SUCCESS
;
217 m_VendorID
= PciConfig
.VendorID
;
218 m_DeviceID
= PciConfig
.DeviceID
;
220 return STATUS_SUCCESS
;
225 CUSBHardwareDevice::PnpStart(
226 PCM_RESOURCE_LIST RawResources
,
227 PCM_RESOURCE_LIST TranslatedResources
)
230 PCM_PARTIAL_RESOURCE_DESCRIPTOR ResourceDescriptor
;
231 DEVICE_DESCRIPTION DeviceDescription
;
236 DPRINT("CUSBHardwareDevice::PnpStart\n");
237 for(Index
= 0; Index
< TranslatedResources
->List
[0].PartialResourceList
.Count
; Index
++)
240 // get resource descriptor
242 ResourceDescriptor
= &TranslatedResources
->List
[0].PartialResourceList
.PartialDescriptors
[Index
];
244 switch(ResourceDescriptor
->Type
)
246 case CmResourceTypeInterrupt
:
248 KeInitializeDpc(&m_IntDpcObject
,
252 Status
= IoConnectInterrupt(&m_Interrupt
,
253 InterruptServiceRoutine
,
256 ResourceDescriptor
->u
.Interrupt
.Vector
,
257 (KIRQL
)ResourceDescriptor
->u
.Interrupt
.Level
,
258 (KIRQL
)ResourceDescriptor
->u
.Interrupt
.Level
,
259 (KINTERRUPT_MODE
)(ResourceDescriptor
->Flags
& CM_RESOURCE_INTERRUPT_LATCHED
),
260 (ResourceDescriptor
->ShareDisposition
!= CmResourceShareDeviceExclusive
),
261 ResourceDescriptor
->u
.Interrupt
.Affinity
,
264 if (!NT_SUCCESS(Status
))
267 // failed to register interrupt
269 DPRINT1("IoConnect Interrupt failed with %x\n", Status
);
274 case CmResourceTypeMemory
:
279 ResourceBase
= MmMapIoSpace(ResourceDescriptor
->u
.Memory
.Start
, ResourceDescriptor
->u
.Memory
.Length
, MmNonCached
);
283 // failed to map registers
285 DPRINT1("MmMapIoSpace failed\n");
286 return STATUS_INSUFFICIENT_RESOURCES
;
290 // Get controllers capabilities
292 Version
= READ_REGISTER_ULONG((PULONG
)((ULONG_PTR
)ResourceBase
+ OHCI_REVISION_OFFSET
));
294 DPRINT("Version %x\n", Version
& 0xFFFF);
297 // Store Resource base
299 m_Base
= (PULONG
)ResourceBase
;
307 // zero device description
309 RtlZeroMemory(&DeviceDescription
, sizeof(DEVICE_DESCRIPTION
));
312 // initialize device description
314 DeviceDescription
.Version
= DEVICE_DESCRIPTION_VERSION
;
315 DeviceDescription
.Master
= TRUE
;
316 DeviceDescription
.ScatterGather
= TRUE
;
317 DeviceDescription
.Dma32BitAddresses
= TRUE
;
318 DeviceDescription
.DmaWidth
= Width32Bits
;
319 DeviceDescription
.InterfaceType
= PCIBus
;
320 DeviceDescription
.MaximumLength
= MAXULONG
;
325 m_Adapter
= IoGetDmaAdapter(m_PhysicalDeviceObject
, &DeviceDescription
, &m_MapRegisters
);
329 // failed to get dma adapter
331 DPRINT1("Failed to acquire dma adapter\n");
332 return STATUS_INSUFFICIENT_RESOURCES
;
336 // Create Common Buffer
338 VirtualBase
= m_Adapter
->DmaOperations
->AllocateCommonBuffer(m_Adapter
,
344 DPRINT1("Failed to allocate a common buffer\n");
345 return STATUS_INSUFFICIENT_RESOURCES
;
349 // Initialize the DMAMemoryManager
351 Status
= m_MemoryManager
->Initialize(this, &m_Lock
, PAGE_SIZE
* 4, VirtualBase
, PhysicalAddress
, 32);
352 if (!NT_SUCCESS(Status
))
354 DPRINT1("Failed to initialize the DMAMemoryManager\n");
359 // initializes the controller
361 Status
= InitializeController();
362 if (!NT_SUCCESS(Status
))
364 DPRINT1("Failed to Initialize the controller \n");
369 // Initialize the UsbQueue now that we have an AdapterObject.
371 Status
= m_UsbQueue
->Initialize(this, m_Adapter
, m_MemoryManager
, NULL
);
372 if (!NT_SUCCESS(Status
))
374 DPRINT1("Failed to Initialize the UsbQueue\n");
379 // Start the controller
381 DPRINT1("Starting Controller\n");
382 Status
= StartController();
392 CUSBHardwareDevice::PnpStop(void)
395 return STATUS_NOT_IMPLEMENTED
;
400 CUSBHardwareDevice::GetDeviceDetails(
401 OUT OPTIONAL PUSHORT VendorId
,
402 OUT OPTIONAL PUSHORT DeviceId
,
403 OUT OPTIONAL PULONG NumberOfPorts
,
404 OUT OPTIONAL PULONG Speed
)
411 *VendorId
= m_VendorID
;
419 *DeviceId
= m_DeviceID
;
425 // get number of ports
427 *NumberOfPorts
= m_NumberOfPorts
;
438 return STATUS_SUCCESS
;
443 CUSBHardwareDevice::GetDMA(
444 OUT
struct IDMAMemoryManager
**OutDMAMemoryManager
)
446 if (!m_MemoryManager
)
447 return STATUS_UNSUCCESSFUL
;
448 *OutDMAMemoryManager
= m_MemoryManager
;
449 return STATUS_SUCCESS
;
454 CUSBHardwareDevice::GetUSBQueue(
455 OUT
struct IUSBQueue
**OutUsbQueue
)
458 return STATUS_UNSUCCESSFUL
;
459 *OutUsbQueue
= m_UsbQueue
;
460 return STATUS_SUCCESS
;
465 CUSBHardwareDevice::StartController(void)
467 ULONG Control
, Descriptor
, FrameInterval
, Periodic
, Port
, Reset
, Index
;
468 ULONG NewControl
, WaitInMs
;
469 LARGE_INTEGER Timeout
;
470 BOOLEAN Again
= FALSE
;
475 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
478 NewControl
= Control
& OHCI_REMOTE_WAKEUP_CONNECTED
;
480 if ((Control
& OHCI_INTERRUPT_ROUTING
))
485 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_OWNERSHIP_CHANGE_REQUEST
);
486 for(Index
= 0; Index
< 100; Index
++)
491 KeStallExecutionProcessor(100);
496 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
497 if (!(Control
& OHCI_INTERRUPT_ROUTING
))
500 // acquired ownership
507 // if the ownership is still not changed, perform reset
509 if (Control
& OHCI_INTERRUPT_ROUTING
)
511 DPRINT1("SMM not responding\n");
515 DPRINT1("SMM has given up ownership\n");
520 // read contents of control register
523 Control
= (READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
)) & OHCI_HC_FUNCTIONAL_STATE_MASK
);
524 DPRINT1("Controller State %x\n", Control
);
528 case OHCI_HC_FUNCTIONAL_STATE_RESET
:
529 NewControl
|= OHCI_HC_FUNCTIONAL_STATE_RESET
;
533 case OHCI_HC_FUNCTIONAL_STATE_SUSPEND
:
534 case OHCI_HC_FUNCTIONAL_STATE_RESUME
:
535 NewControl
|= OHCI_HC_FUNCTIONAL_STATE_RESUME
;
547 // Do the state transition
548 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
), NewControl
);
555 Timeout
.QuadPart
= WaitInMs
;
556 DPRINT1("Waiting %d milliseconds for controller to transition state\n", Timeout
.LowPart
);
559 // convert to 100 ns units (absolute)
561 Timeout
.QuadPart
*= -10000;
566 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
571 // now reset controller
573 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_HOST_CONTROLLER_RESET
);
576 // reset time is 10ms
578 for(Index
= 0; Index
< 100; Index
++)
583 KeStallExecutionProcessor(10);
586 // read command status
588 Reset
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
));
591 // was reset bit cleared
593 if ((Reset
& OHCI_HOST_CONTROLLER_RESET
) == 0)
596 // controller completed reset
602 if ((Reset
& OHCI_HOST_CONTROLLER_RESET
))
605 // failed to reset controller
607 return STATUS_UNSUCCESSFUL
;
611 // get frame interval
613 FrameInterval
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_OFFSET
));
614 m_IntervalValue
= OHCI_GET_INTERVAL_VALUE(FrameInterval
);
616 FrameInterval
= ((FrameInterval
& OHCI_FRAME_INTERVAL_TOGGLE
) ^ OHCI_FRAME_INTERVAL_TOGGLE
);
618 DPRINT1("FrameInterval %x IntervalValue %x\n", FrameInterval
, m_IntervalValue
);
619 FrameInterval
|= OHCI_FSMPS(m_IntervalValue
) | m_IntervalValue
;
620 DPRINT1("Computed FrameInterval %x\n", FrameInterval
);
623 // write frame interval
625 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_OFFSET
), FrameInterval
);
627 FrameInterval
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_OFFSET
));
628 DPRINT1("Read FrameInterval %x\n", FrameInterval
);
633 Periodic
= OHCI_PERIODIC(m_IntervalValue
);
634 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_PERIODIC_START_OFFSET
), Periodic
);
635 DPRINT1("Computed Periodic Start %x\n", Periodic
);
637 Periodic
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_PERIODIC_START_OFFSET
));
638 DPRINT1("Read Periodic Start %x\n", Periodic
);
640 // Linux does this hack for some bad controllers
641 if (!(FrameInterval
& 0x3FFF0000) ||
646 DPRINT1("Trying reset again on faulty controller\n");
652 DPRINT1("Second reset didn't solve the problem, failing\n");
653 return STATUS_UNSUCCESSFUL
;
658 // lets write physical address of dummy control endpoint descriptor
660 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_HEAD_ED_OFFSET
), m_ControlEndpointDescriptor
->PhysicalAddress
.LowPart
);
663 // lets write physical address of dummy bulk endpoint descriptor
665 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_BULK_HEAD_ED_OFFSET
), m_BulkEndpointDescriptor
->PhysicalAddress
.LowPart
);
670 Descriptor
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_A_OFFSET
));
673 // get port count (in a loop due to AMD errata)
677 KeStallExecutionProcessor(20);
678 m_NumberOfPorts
= OHCI_RH_GET_PORT_COUNT(Descriptor
);
679 } while (m_NumberOfPorts
== 0);
681 DPRINT1("NumberOfPorts %lu\n", m_NumberOfPorts
);
682 ASSERT(m_NumberOfPorts
< OHCI_MAX_PORT_COUNT
);
685 // no over current protection
687 Descriptor
|= OHCI_RH_NO_OVER_CURRENT_PROTECTION
;
690 // power switching on
692 Descriptor
&= ~OHCI_RH_NO_POWER_SWITCHING
;
695 // control each port power independently
697 Descriptor
|= OHCI_RH_POWER_SWITCHING_MODE
;
700 // write the configuration back
702 DPRINT1("Descriptor A: %x\n", Descriptor
);
703 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_A_OFFSET
), Descriptor
);
708 Descriptor
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_B_OFFSET
));
711 // set power power control for each port to use PPS
713 for (Port
= 1; Port
<= m_NumberOfPorts
; Port
++)
715 Descriptor
|= (1 << (16 + Port
));
719 // write the configuration back
721 DPRINT1("Descriptor B: %x\n", Descriptor
);
722 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_B_OFFSET
), Descriptor
);
725 // HCCA alignment check
727 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_HCCA_OFFSET
), 0xFFFFFFFF);
728 KeStallExecutionProcessor(10);
729 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_HCCA_OFFSET
));
730 ASSERT((m_HCCAPhysicalAddress
.LowPart
& Control
) == m_HCCAPhysicalAddress
.LowPart
);
731 DPRINT1("HCCA: %x Alignment mask: %x\n", m_HCCAPhysicalAddress
.LowPart
, Control
);
734 // write address of HCCA
736 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_HCCA_OFFSET
), m_HCCAPhysicalAddress
.LowPart
);
739 // now enable the interrupts
741 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
), OHCI_NORMAL_INTERRUPTS
| OHCI_MASTER_INTERRUPT_ENABLE
);
746 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
), (NewControl
& OHCI_REMOTE_WAKEUP_CONNECTED
) | OHCI_ENABLE_LIST
);
749 // start the controller
751 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
), OHCI_ENABLE_LIST
|
752 (NewControl
& OHCI_REMOTE_WAKEUP_CONNECTED
) |
753 OHCI_CONTROL_BULK_RATIO_1_4
|
754 OHCI_HC_FUNCTIONAL_STATE_OPERATIONAL
);
759 KeStallExecutionProcessor(100);
762 // is the controller started
764 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
767 // assert that the controller has been started
769 ASSERT((Control
& OHCI_HC_FUNCTIONAL_STATE_MASK
) == OHCI_HC_FUNCTIONAL_STATE_OPERATIONAL
);
770 ASSERT((Control
& OHCI_ENABLE_LIST
) == OHCI_ENABLE_LIST
);
771 DPRINT1("Control %x\n", Control
);
776 DPRINT1("OHCI controller is operational\n");
777 return STATUS_SUCCESS
;
781 CUSBHardwareDevice::AllocateEndpointDescriptor(
782 OUT POHCI_ENDPOINT_DESCRIPTOR
*OutDescriptor
)
784 POHCI_ENDPOINT_DESCRIPTOR Descriptor
;
785 PHYSICAL_ADDRESS DescriptorAddress
;
789 // allocate descriptor
791 Status
= m_MemoryManager
->Allocate(sizeof(OHCI_ENDPOINT_DESCRIPTOR
), (PVOID
*)&Descriptor
, &DescriptorAddress
);
792 if (!NT_SUCCESS(Status
))
795 // failed to allocate descriptor
801 // intialize descriptor
803 Descriptor
->Flags
= OHCI_ENDPOINT_SKIP
;
804 Descriptor
->HeadPhysicalDescriptor
= 0;
805 Descriptor
->NextPhysicalEndpoint
= 0;
806 Descriptor
->TailPhysicalDescriptor
= 0;
807 Descriptor
->PhysicalAddress
.QuadPart
= DescriptorAddress
.QuadPart
;
812 *OutDescriptor
= Descriptor
;
817 return STATUS_SUCCESS
;
822 CUSBHardwareDevice::GetBulkHeadEndpointDescriptor(
823 struct _OHCI_ENDPOINT_DESCRIPTOR
** OutDescriptor
)
825 *OutDescriptor
= m_BulkEndpointDescriptor
;
830 CUSBHardwareDevice::GetInterruptEndpointDescriptors(
831 struct _OHCI_ENDPOINT_DESCRIPTOR
*** OutDescriptor
)
833 *OutDescriptor
= m_InterruptEndpoints
;
838 CUSBHardwareDevice::GetIsochronousHeadEndpointDescriptor(
839 struct _OHCI_ENDPOINT_DESCRIPTOR
** OutDescriptor
)
841 *OutDescriptor
= m_IsoEndpointDescriptor
;
846 CUSBHardwareDevice::HeadEndpointDescriptorModified(
849 if (Type
== USB_ENDPOINT_TYPE_CONTROL
)
854 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_CONTROL_LIST_FILLED
);
856 else if (Type
== USB_ENDPOINT_TYPE_BULK
)
861 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_COMMAND_STATUS_OFFSET
), OHCI_BULK_LIST_FILLED
);
867 CUSBHardwareDevice::GetControlHeadEndpointDescriptor(
868 struct _OHCI_ENDPOINT_DESCRIPTOR
** OutDescriptor
)
870 *OutDescriptor
= m_ControlEndpointDescriptor
;
874 CUSBHardwareDevice::InitializeController()
877 ULONG Index
, Interval
, IntervalIndex
, InsertIndex
;
878 POHCI_ENDPOINT_DESCRIPTOR Descriptor
;
881 // first allocate the hcca area
883 Status
= m_MemoryManager
->Allocate(sizeof(OHCIHCCA
), (PVOID
*)&m_HCCA
, &m_HCCAPhysicalAddress
);
884 if (!NT_SUCCESS(Status
))
893 // now allocate an endpoint for control transfers
894 // this endpoint will never be removed
896 Status
= AllocateEndpointDescriptor(&m_ControlEndpointDescriptor
);
897 if (!NT_SUCCESS(Status
))
906 // now allocate an endpoint for bulk transfers
907 // this endpoint will never be removed
909 Status
= AllocateEndpointDescriptor(&m_BulkEndpointDescriptor
);
910 if (!NT_SUCCESS(Status
))
919 // now allocate an endpoint for iso transfers
920 // this endpoint will never be removed
922 Status
= AllocateEndpointDescriptor(&m_IsoEndpointDescriptor
);
923 if (!NT_SUCCESS(Status
))
932 // now allocate endpoint descriptors for iso / interrupt transfers interval is 1,2,4,8,16,32
934 for(Index
= 0; Index
< OHCI_STATIC_ENDPOINT_COUNT
; Index
++)
937 // allocate endpoint descriptor
939 Status
= AllocateEndpointDescriptor(&Descriptor
);
940 if (!NT_SUCCESS(Status
))
951 m_InterruptEndpoints
[Index
] = Descriptor
;
956 // now link the descriptors, taken from Haiku
958 Interval
= OHCI_BIGGEST_INTERVAL
;
959 IntervalIndex
= OHCI_STATIC_ENDPOINT_COUNT
- 1;
962 InsertIndex
= Interval
/ 2;
963 while (InsertIndex
< OHCI_BIGGEST_INTERVAL
)
966 // assign endpoint address
968 m_HCCA
->InterruptTable
[InsertIndex
] = m_InterruptEndpoints
[IntervalIndex
]->PhysicalAddress
.LowPart
;
969 InsertIndex
+= Interval
;
977 // link all endpoint descriptors to first descriptor in array
979 m_HCCA
->InterruptTable
[0] = m_InterruptEndpoints
[0]->PhysicalAddress
.LowPart
;
980 for (Index
= 1; Index
< OHCI_STATIC_ENDPOINT_COUNT
; Index
++)
985 m_InterruptEndpoints
[Index
]->NextPhysicalEndpoint
= m_InterruptEndpoints
[0]->PhysicalAddress
.LowPart
;
989 // Now link the first endpoint to the isochronous endpoint
991 m_InterruptEndpoints
[0]->NextPhysicalEndpoint
= m_IsoEndpointDescriptor
->PhysicalAddress
.LowPart
;
994 // set iso endpoint type
996 m_IsoEndpointDescriptor
->Flags
|= OHCI_ENDPOINT_ISOCHRONOUS_FORMAT
;
1001 return STATUS_SUCCESS
;
1005 CUSBHardwareDevice::StopController(void)
1009 return STATUS_UNSUCCESSFUL
;
1014 CUSBHardwareDevice::ResetPort(
1019 return STATUS_SUCCESS
;
1024 CUSBHardwareDevice::GetPortStatus(
1026 OUT USHORT
*PortStatus
,
1027 OUT USHORT
*PortChange
)
1031 if (PortId
> m_NumberOfPorts
)
1032 return STATUS_UNSUCCESSFUL
;
1034 // init result variables
1041 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)));
1042 DPRINT("GetPortStatus PortId %x Value %x\n", PortId
, Value
);
1045 if (Value
& OHCI_RH_PORTSTATUS_CCS
)
1047 *PortStatus
|= USB_PORT_STATUS_CONNECT
;
1050 if (Value
& OHCI_RH_PORTSTATUS_LSDA
)
1051 *PortStatus
|= USB_PORT_STATUS_LOW_SPEED
;
1054 // did a device connect?
1055 if (Value
& OHCI_RH_PORTSTATUS_CSC
)
1056 *PortChange
|= USB_PORT_STATUS_CONNECT
;
1059 if (Value
& OHCI_RH_PORTSTATUS_PES
)
1060 *PortStatus
|= USB_PORT_STATUS_ENABLE
;
1062 // port disconnect or hardware error
1063 if (Value
& OHCI_RH_PORTSTATUS_PESC
)
1064 *PortChange
|= USB_PORT_STATUS_CONNECT
;
1067 if (Value
& OHCI_RH_PORTSTATUS_PSS
)
1068 *PortStatus
|= USB_PORT_STATUS_SUSPEND
;
1071 if (Value
& OHCI_RH_PORTSTATUS_PSSC
)
1072 *PortChange
|= USB_PORT_STATUS_ENABLE
;
1074 // port reset started
1075 if (Value
& OHCI_RH_PORTSTATUS_PRS
)
1076 *PortStatus
|= USB_PORT_STATUS_RESET
;
1079 if (Value
& OHCI_RH_PORTSTATUS_PRSC
)
1080 *PortChange
|= USB_PORT_STATUS_RESET
;
1082 return STATUS_SUCCESS
;
1087 CUSBHardwareDevice::ClearPortStatus(
1093 DPRINT("CUSBHardwareDevice::ClearPortStatus PortId %x Feature %x\n", PortId
, Status
);
1095 if (PortId
> m_NumberOfPorts
)
1096 return STATUS_UNSUCCESSFUL
;
1098 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)));
1100 if (Status
== C_PORT_RESET
)
1105 ASSERT((Value
& OHCI_RH_PORTSTATUS_PRSC
));
1108 // clear reset bit complete
1110 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PRSC
);
1115 ASSERT((Value
& OHCI_RH_PORTSTATUS_PES
));
1118 if (Status
== C_PORT_CONNECTION
|| Status
== C_PORT_ENABLE
)
1121 // clear change bits
1123 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), Value
& (OHCI_RH_PORTSTATUS_CSC
| OHCI_RH_PORTSTATUS_PESC
));
1126 // wait for port to stabilize
1128 if (Status
== C_PORT_CONNECTION
&& (Value
& OHCI_RH_PORTSTATUS_CCS
))
1130 LARGE_INTEGER Timeout
;
1135 Timeout
.QuadPart
= 100;
1136 DPRINT1("Waiting %d milliseconds for port to stabilize after connection\n", Timeout
.LowPart
);
1139 // convert to 100 ns units (absolute)
1141 Timeout
.QuadPart
*= -10000;
1146 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1151 // re-enable root hub change
1153 DPRINT1("Enabling status change\n");
1154 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
), OHCI_ROOT_HUB_STATUS_CHANGE
);
1156 return STATUS_SUCCESS
;
1162 CUSBHardwareDevice::SetPortFeature(
1168 DPRINT("CUSBHardwareDevice::SetPortFeature PortId %x Feature %x\n", PortId
, Feature
);
1173 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)));
1176 if (Feature
== PORT_ENABLE
)
1181 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PES
);
1182 return STATUS_SUCCESS
;
1184 else if (Feature
== PORT_POWER
)
1186 LARGE_INTEGER Timeout
;
1191 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PPS
);
1194 // read descriptor A for the delay data
1196 Value
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_DESCRIPTOR_A_OFFSET
));
1199 // compute the delay
1201 Timeout
.QuadPart
= OHCI_RH_GET_POWER_ON_TO_POWER_GOOD_TIME(Value
);
1204 // delay is multiplied by 2 ms
1206 Timeout
.QuadPart
*= 2;
1207 DPRINT1("Waiting %d milliseconds for port power up\n", Timeout
.LowPart
);
1210 // convert to 100 ns units (absolute)
1212 Timeout
.QuadPart
*= -10000;
1217 KeDelayExecutionThread(KernelMode
, FALSE
, &Timeout
);
1219 return STATUS_SUCCESS
;
1221 else if (Feature
== PORT_SUSPEND
)
1226 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PSS
);
1227 return STATUS_SUCCESS
;
1229 else if (Feature
== PORT_RESET
)
1234 ASSERT((Value
& OHCI_RH_PORTSTATUS_CCS
));
1239 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_RH_PORT_STATUS(PortId
)), OHCI_RH_PORTSTATUS_PRS
);
1242 // an interrupt signals the reset completion
1244 return STATUS_SUCCESS
;
1246 return STATUS_SUCCESS
;
1253 CUSBHardwareDevice::SetStatusChangeEndpointCallBack(
1257 m_SCECallBack
= (HD_INIT_CALLBACK
*)CallBack
;
1258 m_SCEContext
= Context
;
1263 CUSBHardwareDevice::GetCurrentFrameNumber(
1270 Number
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_FRAME_INTERVAL_NUMBER_OFFSET
));
1271 DPRINT("FrameNumberInterval %x Frame %x\n", Number
, m_HCCA
->CurrentFrameNumber
);
1274 // remove reserved bits
1279 // store frame number
1281 *FrameNumber
= Number
;
1284 // is the controller started
1286 Control
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)m_Base
+ OHCI_CONTROL_OFFSET
));
1287 ASSERT((Control
& OHCI_ENABLE_LIST
) == OHCI_ENABLE_LIST
);
1295 InterruptServiceRoutine(
1296 IN PKINTERRUPT Interrupt
,
1297 IN PVOID ServiceContext
)
1299 CUSBHardwareDevice
*This
;
1300 ULONG DoneHead
, Status
, Acknowledge
= 0;
1305 This
= (CUSBHardwareDevice
*) ServiceContext
;
1307 DPRINT("InterruptServiceRoutine\n");
1312 DoneHead
= This
->m_HCCA
->DoneHead
;
1320 // the interrupt was not caused by DoneHead update
1321 // check if something important happened
1323 DPRINT("InterruptStatus %x InterruptEnable %x\n", READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_STATUS_OFFSET
)),
1324 READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_ENABLE_OFFSET
)));
1325 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
);
1329 // nothing happened, appears to be shared interrupt
1337 // DoneHead update happened, check if there are other events too
1339 Status
= OHCI_WRITEBACK_DONE_HEAD
;
1342 // since ed descriptors are 16 byte aligned, the controller sets the lower bits if there were other interrupt requests
1344 if (DoneHead
& OHCI_DONE_INTERRUPTS
)
1349 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
));
1356 ASSERT(Status
!= 0);
1358 if (Status
& OHCI_WRITEBACK_DONE_HEAD
)
1363 Acknowledge
|= OHCI_WRITEBACK_DONE_HEAD
;
1364 This
->m_HCCA
->DoneHead
= 0;
1367 if (Status
& OHCI_RESUME_DETECTED
)
1372 DPRINT1("InterruptServiceRoutine> Resume\n");
1373 Acknowledge
|= OHCI_RESUME_DETECTED
;
1377 if (Status
& OHCI_UNRECOVERABLE_ERROR
)
1379 DPRINT1("InterruptServiceRoutine> Controller error\n");
1385 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_CONTROL_OFFSET
), OHCI_HC_FUNCTIONAL_STATE_RESET
);
1388 if (Status
& OHCI_ROOT_HUB_STATUS_CHANGE
)
1391 // disable interrupt as it will fire untill the port has been reset
1393 DPRINT1("Disabling status change interrupt\n");
1394 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_DISABLE_OFFSET
), OHCI_ROOT_HUB_STATUS_CHANGE
);
1395 Acknowledge
|= OHCI_ROOT_HUB_STATUS_CHANGE
;
1399 // is there something to acknowledge
1406 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_INTERRUPT_STATUS_OFFSET
), Acknowledge
);
1412 DPRINT("Status %x Acknowledge %x FrameNumber %x\n", Status
, Acknowledge
, This
->m_HCCA
->CurrentFrameNumber
);
1413 KeInsertQueueDpc(&This
->m_IntDpcObject
, (PVOID
)Status
, (PVOID
)(DoneHead
& ~1));
1416 // interrupt handled
1423 OhciDefferedRoutine(
1425 IN PVOID DeferredContext
,
1426 IN PVOID SystemArgument1
,
1427 IN PVOID SystemArgument2
)
1429 CUSBHardwareDevice
*This
;
1430 ULONG CStatus
, Index
, PortStatus
;
1431 ULONG DoneHead
, QueueSCEWorkItem
;
1436 This
= (CUSBHardwareDevice
*)DeferredContext
;
1437 CStatus
= (ULONG
) SystemArgument1
;
1438 DoneHead
= (ULONG
)SystemArgument2
;
1440 DPRINT("OhciDefferedRoutine Status %x DoneHead %x\n", CStatus
, DoneHead
);
1442 if (CStatus
& OHCI_WRITEBACK_DONE_HEAD
)
1445 // notify queue of event
1447 This
->m_UsbQueue
->TransferDescriptorCompletionCallback(DoneHead
);
1449 if (CStatus
& OHCI_ROOT_HUB_STATUS_CHANGE
)
1452 // device connected, lets check which port
1454 QueueSCEWorkItem
= FALSE
;
1455 for(Index
= 0; Index
< This
->m_NumberOfPorts
; Index
++)
1460 PortStatus
= READ_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_RH_PORT_STATUS(Index
)));
1463 // check if there is a status change
1465 if (PortStatus
& OHCI_RH_PORTSTATUS_CSC
)
1468 // did a device connect
1470 if (PortStatus
& OHCI_RH_PORTSTATUS_CCS
)
1475 DPRINT1("New device arrival at Port %d LowSpeed %x\n", Index
, (PortStatus
& OHCI_RH_PORTSTATUS_LSDA
));
1480 WRITE_REGISTER_ULONG((PULONG
)((PUCHAR
)This
->m_Base
+ OHCI_RH_PORT_STATUS(Index
)), OHCI_RH_PORTSTATUS_PES
);
1485 // device disconnected
1487 DPRINT1("Device disconnected at Port %x\n", Index
);
1493 QueueSCEWorkItem
= TRUE
;
1495 else if (PortStatus
& OHCI_RH_PORTSTATUS_PESC
)
1498 // device disconnected or some error condition
1500 ASSERT(!(PortStatus
& OHCI_RH_PORTSTATUS_PES
));
1505 QueueSCEWorkItem
= TRUE
;
1507 else if (PortStatus
& OHCI_RH_PORTSTATUS_PRSC
)
1510 // This is a port reset complete interrupt
1512 DPRINT1("Port %d completed reset\n", Index
);
1515 // Queue a work item
1517 QueueSCEWorkItem
= TRUE
;
1522 // is there a status change callback and a device connected / disconnected
1524 if (QueueSCEWorkItem
&& This
->m_SCECallBack
!= NULL
)
1527 // queue work item for processing
1529 ExQueueWorkItem(&This
->m_StatusChangeWorkItem
, DelayedWorkQueue
);
1536 StatusChangeWorkItemRoutine(
1540 // cast to hardware object
1542 CUSBHardwareDevice
* This
= (CUSBHardwareDevice
*)Context
;
1545 // is there a callback
1547 if (This
->m_SCECallBack
)
1552 This
->m_SCECallBack(This
->m_SCEContext
);
1560 PUSBHARDWAREDEVICE
*OutHardware
)
1562 PUSBHARDWAREDEVICE This
;
1564 This
= new(NonPagedPool
, TAG_USBOHCI
) CUSBHardwareDevice(0);
1567 return STATUS_INSUFFICIENT_RESOURCES
;
1572 *OutHardware
= (PUSBHARDWAREDEVICE
)This
;
1574 return STATUS_SUCCESS
;