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