reactos/drivers/usb/usbuhci/usb_queue.cpp
2013-06-16 22:01:41 +00:00

554 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"
#include "hardware.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);
}
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;
}