mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 18:15:11 +00:00
548 lines
13 KiB
C++
548 lines
13 KiB
C++
/*
|
|
* PROJECT: ReactOS Universal Serial Bus Host Controller Interface
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: drivers/usb/usbuhci/usb_queue.cpp
|
|
* PURPOSE: USB UHCI device driver.
|
|
* PROGRAMMERS:
|
|
* Michael Martin (michael.martin@reactos.org)
|
|
* Johannes Anderwald (johannes.anderwald@reactos.org)
|
|
*/
|
|
|
|
#include "usbuhci.h"
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
class CUSBQueue : public IUHCIQueue
|
|
{
|
|
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_IUSBQUEUE
|
|
IMP_IUHCIQUEUE
|
|
|
|
// local
|
|
VOID LinkQueueHead(PUHCI_QUEUE_HEAD QueueHead, PUHCI_QUEUE_HEAD NextQueueHead);
|
|
VOID UnLinkQueueHead(PUHCI_QUEUE_HEAD PreviousQueueHead, PUHCI_QUEUE_HEAD NextQueueHead);
|
|
BOOLEAN IsQueueHeadComplete(PUHCI_QUEUE_HEAD QueueHead);
|
|
NTSTATUS AddQueueHead(PUHCI_QUEUE_HEAD NewQueueHead);
|
|
VOID QueueHeadCleanup(IN PUHCI_QUEUE_HEAD QueueHead, IN PUHCI_QUEUE_HEAD PreviousQueueHead, OUT PUHCI_QUEUE_HEAD *NextQueueHead);
|
|
|
|
|
|
// constructor / destructor
|
|
CUSBQueue(IUnknown *OuterUnknown){}
|
|
virtual ~CUSBQueue(){}
|
|
|
|
protected:
|
|
LONG m_Ref; // reference count
|
|
KSPIN_LOCK m_Lock; // list lock
|
|
PUHCIHARDWAREDEVICE m_Hardware; // hardware
|
|
|
|
};
|
|
|
|
//=================================================================================================
|
|
// COM
|
|
//
|
|
NTSTATUS
|
|
STDMETHODCALLTYPE
|
|
CUSBQueue::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;
|
|
}
|
|
|
|
NTSTATUS
|
|
CUSBQueue::Initialize(
|
|
IN PUSBHARDWAREDEVICE Hardware,
|
|
IN PDMA_ADAPTER AdapterObject,
|
|
IN PDMAMEMORYMANAGER MemManager,
|
|
IN OPTIONAL PKSPIN_LOCK Lock)
|
|
{
|
|
|
|
//
|
|
// store hardware
|
|
//
|
|
m_Hardware = PUHCIHARDWAREDEVICE(Hardware);
|
|
|
|
//
|
|
// initialize spinlock
|
|
//
|
|
KeInitializeSpinLock(&m_Lock);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
CUSBQueue::AddQueueHead(
|
|
PUHCI_QUEUE_HEAD NewQueueHead)
|
|
{
|
|
PUHCIREQUEST Request;
|
|
PUHCI_QUEUE_HEAD QueueHead = NULL;
|
|
|
|
|
|
//
|
|
// get request
|
|
//
|
|
Request = (PUHCIREQUEST)NewQueueHead->Request;
|
|
if (!Request)
|
|
{
|
|
//
|
|
// no request
|
|
//
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Request->GetTransferType() == USB_ENDPOINT_TYPE_CONTROL)
|
|
{
|
|
//
|
|
// get device speed
|
|
//
|
|
if (Request->GetDeviceSpeed() == UsbLowSpeed)
|
|
{
|
|
//
|
|
// use low speed queue
|
|
//
|
|
m_Hardware->GetQueueHead(UHCI_LOW_SPEED_CONTROL_QUEUE, &QueueHead);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// use full speed queue
|
|
//
|
|
m_Hardware->GetQueueHead(UHCI_FULL_SPEED_CONTROL_QUEUE, &QueueHead);
|
|
}
|
|
}
|
|
else if (Request->GetTransferType() == USB_ENDPOINT_TYPE_BULK)
|
|
{
|
|
//
|
|
// use full speed queue
|
|
//
|
|
m_Hardware->GetQueueHead(UHCI_BULK_QUEUE, &QueueHead);
|
|
}
|
|
else if (Request->GetTransferType() == USB_ENDPOINT_TYPE_INTERRUPT)
|
|
{
|
|
//
|
|
// use full speed queue
|
|
//
|
|
m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
|
|
}
|
|
|
|
//
|
|
// FIXME support isochronous
|
|
//
|
|
ASSERT(QueueHead);
|
|
|
|
//
|
|
// add reference
|
|
//
|
|
Request->AddRef();
|
|
|
|
//
|
|
// now link the new queue head
|
|
//
|
|
LinkQueueHead(QueueHead, NewQueueHead);
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
CUSBQueue::AddUSBRequest(
|
|
IUSBRequest * Req)
|
|
{
|
|
PUHCI_QUEUE_HEAD NewQueueHead;
|
|
NTSTATUS Status;
|
|
PUHCIREQUEST Request;
|
|
|
|
// get request
|
|
Request = (PUHCIREQUEST)Req;
|
|
|
|
//
|
|
// get queue head
|
|
//
|
|
Status = Request->GetEndpointDescriptor(&NewQueueHead);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// failed to create queue head
|
|
//
|
|
DPRINT1("[USBUHCI] Failed to create queue head %x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// sanity check
|
|
//
|
|
ASSERT(PVOID(Request) == NewQueueHead->Request);
|
|
|
|
//
|
|
// add queue head
|
|
//
|
|
DPRINT("AddUSBRequest Request %p\n", Request);
|
|
DPRINT("NewQueueHead %p\n", NewQueueHead);
|
|
return AddQueueHead(NewQueueHead);
|
|
}
|
|
|
|
VOID
|
|
CUSBQueue::LinkQueueHead(
|
|
IN PUHCI_QUEUE_HEAD QueueHead,
|
|
IN PUHCI_QUEUE_HEAD NextQueueHead)
|
|
{
|
|
NextQueueHead->LinkPhysical = QueueHead->LinkPhysical;
|
|
NextQueueHead->NextLogicalDescriptor = QueueHead->NextLogicalDescriptor;
|
|
|
|
QueueHead->LinkPhysical = NextQueueHead->PhysicalAddress | QH_NEXT_IS_QH;
|
|
QueueHead->NextLogicalDescriptor = (PVOID)NextQueueHead;
|
|
}
|
|
|
|
|
|
VOID
|
|
CUSBQueue::UnLinkQueueHead(
|
|
PUHCI_QUEUE_HEAD QueueHeadToRemove,
|
|
PUHCI_QUEUE_HEAD PreviousQueueHead)
|
|
{
|
|
PreviousQueueHead->LinkPhysical = QueueHeadToRemove->LinkPhysical;
|
|
PreviousQueueHead->NextLogicalDescriptor = QueueHeadToRemove->NextLogicalDescriptor;
|
|
}
|
|
|
|
NTSTATUS
|
|
CUSBQueue::AbortDevicePipe(
|
|
IN UCHAR DeviceAddress,
|
|
IN PUSB_ENDPOINT_DESCRIPTOR EndDescriptor)
|
|
{
|
|
KIRQL OldLevel;
|
|
PUHCI_TRANSFER_DESCRIPTOR Descriptor;
|
|
PUHCI_QUEUE_HEAD QueueHead, PreviousQueueHead = NULL;
|
|
UCHAR EndpointAddress, EndpointDeviceAddress;
|
|
PUSB_ENDPOINT EndpointDescriptor;
|
|
|
|
// get descriptor
|
|
EndpointDescriptor = (PUSB_ENDPOINT)EndDescriptor;
|
|
|
|
// acquire lock
|
|
KeAcquireSpinLock(&m_Lock, &OldLevel);
|
|
|
|
// get queue head
|
|
m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
|
|
|
|
while(QueueHead)
|
|
{
|
|
// get descriptor
|
|
Descriptor = (PUHCI_TRANSFER_DESCRIPTOR)QueueHead->NextElementDescriptor;
|
|
|
|
if (Descriptor)
|
|
{
|
|
// extract endpoint address
|
|
EndpointAddress = (Descriptor->Token >> TD_TOKEN_ENDPTADDR_SHIFT) & 0x0F;
|
|
|
|
// extract device address
|
|
EndpointDeviceAddress = (Descriptor->Token >> TD_TOKEN_DEVADDR_SHIFT) & 0x7F;
|
|
|
|
// check if they match
|
|
if (EndpointAddress == (EndpointDescriptor->EndPointDescriptor.bEndpointAddress & 0x0F) &&
|
|
DeviceAddress == EndpointDeviceAddress)
|
|
{
|
|
// cleanup queue head
|
|
QueueHeadCleanup(QueueHead, PreviousQueueHead, &QueueHead);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// move to next queue head
|
|
PreviousQueueHead = QueueHead;
|
|
QueueHead = (PUHCI_QUEUE_HEAD)QueueHead->NextLogicalDescriptor;
|
|
}
|
|
|
|
// release lock
|
|
KeReleaseSpinLock(&m_Lock, OldLevel);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
CUSBQueue::CreateUSBRequest(
|
|
IUSBRequest **OutRequest)
|
|
{
|
|
PUSBREQUEST UsbRequest;
|
|
NTSTATUS Status;
|
|
|
|
*OutRequest = NULL;
|
|
Status = InternalCreateUSBRequest(&UsbRequest);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
*OutRequest = UsbRequest;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
BOOLEAN
|
|
CUSBQueue::IsQueueHeadComplete(
|
|
IN PUHCI_QUEUE_HEAD QueueHead)
|
|
{
|
|
PUHCI_TRANSFER_DESCRIPTOR Descriptor;
|
|
ULONG ErrorCount;
|
|
|
|
if (QueueHead->NextElementDescriptor == NULL)
|
|
{
|
|
//
|
|
// empty queue head
|
|
//
|
|
DPRINT("QueueHead %p empty element physical\n", QueueHead);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// check all descriptors
|
|
//
|
|
Descriptor = (PUHCI_TRANSFER_DESCRIPTOR)QueueHead->NextElementDescriptor;
|
|
while(Descriptor)
|
|
{
|
|
if (Descriptor->Status & TD_STATUS_ACTIVE)
|
|
{
|
|
//
|
|
// descriptor is still active
|
|
//
|
|
DPRINT("Descriptor %p is active Status %x BufferSize %lu\n", Descriptor, Descriptor->Status, Descriptor->BufferSize);
|
|
return FALSE;
|
|
}
|
|
|
|
if (Descriptor->Status & TD_ERROR_MASK)
|
|
{
|
|
//
|
|
// error happened
|
|
//
|
|
DPRINT1("[USBUHCI] Error detected at descriptor %p Physical %x\n", Descriptor, Descriptor->PhysicalAddress);
|
|
|
|
//
|
|
// get error count
|
|
//
|
|
ErrorCount = (Descriptor->Status >> TD_ERROR_COUNT_SHIFT) & TD_ERROR_COUNT_MASK;
|
|
if (ErrorCount == 0)
|
|
{
|
|
//
|
|
// error retry count elapsed
|
|
//
|
|
DPRINT1("[USBUHCI] ErrorBuffer %x TimeOut %x Nak %x BitStuff %x\n",
|
|
Descriptor->Status & TD_STATUS_ERROR_BUFFER,
|
|
Descriptor->Status & TD_STATUS_ERROR_TIMEOUT,
|
|
Descriptor->Status & TD_STATUS_ERROR_NAK,
|
|
Descriptor->Status & TD_STATUS_ERROR_BITSTUFF);
|
|
return TRUE;
|
|
}
|
|
else if (Descriptor->Status & TD_STATUS_ERROR_BABBLE)
|
|
{
|
|
//
|
|
// babble error
|
|
//
|
|
DPRINT1("[USBUHCI] Babble detected\n");
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// stall detected
|
|
//
|
|
DPRINT1("[USBUHCI] Stall detected\n");
|
|
}
|
|
}
|
|
|
|
//
|
|
// move to next descriptor
|
|
//
|
|
Descriptor = (PUHCI_TRANSFER_DESCRIPTOR)Descriptor->NextLogicalDescriptor;
|
|
}
|
|
|
|
//
|
|
// request is complete
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
CUSBQueue::QueueHeadCleanup(
|
|
IN PUHCI_QUEUE_HEAD QueueHead,
|
|
IN PUHCI_QUEUE_HEAD PreviousQueueHead,
|
|
OUT PUHCI_QUEUE_HEAD *NextQueueHead)
|
|
{
|
|
PUHCIREQUEST Request;
|
|
PUHCI_QUEUE_HEAD NewQueueHead;
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// unlink queue head
|
|
//
|
|
UnLinkQueueHead(QueueHead, PreviousQueueHead);
|
|
|
|
//
|
|
// get next queue head
|
|
//
|
|
*NextQueueHead = (PUHCI_QUEUE_HEAD)PreviousQueueHead->NextLogicalDescriptor;
|
|
ASSERT(*NextQueueHead != QueueHead);
|
|
|
|
//
|
|
// the queue head is complete, is the transfer now completed?
|
|
//
|
|
Request = (PUHCIREQUEST)QueueHead->Request;
|
|
ASSERT(Request);
|
|
|
|
//
|
|
// free queue head
|
|
//
|
|
DPRINT("Request %p\n", Request);
|
|
Request->FreeEndpointDescriptor(QueueHead);
|
|
|
|
//
|
|
// check if transfer is complete
|
|
//
|
|
if (Request->IsRequestComplete())
|
|
{
|
|
//
|
|
// the transfer is complete
|
|
//
|
|
Request->CompletionCallback();
|
|
Request->Release();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// grab new queue head
|
|
//
|
|
Status = Request->GetEndpointDescriptor(&NewQueueHead);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// failed to get new queue head
|
|
//
|
|
DPRINT1("[USBUHCI] Failed to get new queue head with %x\n", Status);
|
|
Request->CompletionCallback();
|
|
Request->Release();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Link queue head
|
|
//
|
|
Status = AddQueueHead(NewQueueHead);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// failed to get new queue head
|
|
//
|
|
DPRINT1("[USBUHCI] Failed to add queue head with %x\n", Status);
|
|
Request->CompletionCallback();
|
|
Request->Release();
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
VOID
|
|
CUSBQueue::TransferInterrupt(
|
|
UCHAR ErrorInterrupt)
|
|
{
|
|
KIRQL OldLevel;
|
|
PUHCI_QUEUE_HEAD QueueHead, PreviousQueueHead = NULL;
|
|
BOOLEAN IsComplete;
|
|
|
|
//
|
|
// acquire lock
|
|
//
|
|
KeAcquireSpinLock(&m_Lock, &OldLevel);
|
|
|
|
//
|
|
// get queue head
|
|
//
|
|
m_Hardware->GetQueueHead(UHCI_INTERRUPT_QUEUE, &QueueHead);
|
|
|
|
while(QueueHead)
|
|
{
|
|
//
|
|
// is queue head complete
|
|
//
|
|
DPRINT("QueueHead %p\n", QueueHead);
|
|
IsComplete = IsQueueHeadComplete(QueueHead);
|
|
if (IsComplete)
|
|
{
|
|
//
|
|
// cleanup queue head
|
|
//
|
|
QueueHeadCleanup(QueueHead, PreviousQueueHead, &QueueHead);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// backup previous queue head
|
|
//
|
|
PreviousQueueHead = QueueHead;
|
|
|
|
//
|
|
// get next queue head
|
|
//
|
|
QueueHead = (PUHCI_QUEUE_HEAD)QueueHead->NextLogicalDescriptor;
|
|
}
|
|
|
|
//
|
|
// release lock
|
|
//
|
|
KeReleaseSpinLock(&m_Lock, OldLevel);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CreateUSBQueue(
|
|
PUSBQUEUE *OutUsbQueue)
|
|
{
|
|
PUSBQUEUE This;
|
|
|
|
//
|
|
// allocate controller
|
|
//
|
|
This = new(NonPagedPool, TAG_USBUHCI) CUSBQueue(0);
|
|
if (!This)
|
|
{
|
|
//
|
|
// failed to allocate
|
|
//
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// add reference count
|
|
//
|
|
This->AddRef();
|
|
|
|
//
|
|
// return result
|
|
//
|
|
*OutUsbQueue = (PUSBQUEUE)This;
|
|
|
|
//
|
|
// done
|
|
//
|
|
return STATUS_SUCCESS;
|
|
}
|