* Implement interrupt transfers in EHCI

* Uses a "collapsed binary tree" (for lack of a better name) to support the different intervals
* Remove a leftover variable declaration that was hiding error conditions away...
* Some cleanup

git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22942 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Michael Lotz 2007-11-18 01:05:39 +00:00
parent d4fe284960
commit ed1eff2fec
3 changed files with 190 additions and 40 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2006, Haiku Inc. All rights reserved.
* Copyright 2006-2007, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@ -238,18 +238,69 @@ EHCI::EHCI(pci_info *info, Stack *stack)
// allocate the periodic frame list
fPeriodicFrameListArea = fStack->AllocateArea((void **)&fPeriodicFrameList,
(void **)&physicalAddress, B_PAGE_SIZE, "USB EHCI Periodic Framelist");
(void **)&physicalAddress, B_PAGE_SIZE * 2, "USB EHCI Periodic Framelist");
if (fPeriodicFrameListArea < B_OK) {
TRACE_ERROR(("usb_ehci: unable to allocate periodic framelist\n"));
return;
}
// terminate all elements
for (int32 i = 0; i < 1024; i++)
fPeriodicFrameList[i] = EHCI_PFRAMELIST_TERM;
// set the periodic frame list base on the controller
WriteOpReg(EHCI_PERIODICLISTBASE, (uint32)physicalAddress);
// create the interrupt entries to support different polling intervals
TRACE(("usb_ehci: creating interrupt entries\n"));
addr_t physicalBase = physicalAddress + B_PAGE_SIZE;
uint8 *logicalBase = (uint8 *)fPeriodicFrameList + B_PAGE_SIZE;
memset(logicalBase, 0, B_PAGE_SIZE);
fInterruptEntries = (interrupt_entry *)logicalBase;
for (int32 i = 0; i < 10; i++) {
ehci_qh *queueHead = &fInterruptEntries[i].queue_head;
queueHead->this_phy = physicalBase;
queueHead->current_qtd_phy = EHCI_QTD_TERMINATE;
queueHead->overlay.next_phy = EHCI_QTD_TERMINATE;
queueHead->overlay.alt_next_phy = EHCI_QTD_TERMINATE;
queueHead->overlay.token = EHCI_QTD_STATUS_HALTED;
// set dummy endpoint information
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_HIGH
| (3 << EHCI_QH_CHARS_RL_SHIFT) | (64 << EHCI_QH_CHARS_MPL_SHIFT)
| EHCI_QH_CHARS_TOGGLE;
queueHead->endpoint_caps = (1 << EHCI_QH_CAPS_MULT_SHIFT)
| (0xff << EHCI_QH_CAPS_ISM_SHIFT);
physicalBase += sizeof(interrupt_entry);
}
// build flat interrupt tree
TRACE(("usb_ehci: build up interrupt links\n"));
uint32 interval = 1024;
uint32 intervalIndex = 9;
while (interval > 1) {
uint32 insertIndex = interval / 2;
while (insertIndex < 1024) {
uint32 entry = fInterruptEntries[intervalIndex].queue_head.this_phy;
fPeriodicFrameList[insertIndex] = entry | EHCI_PFRAMELIST_QH;
insertIndex += interval;
}
intervalIndex--;
interval /= 2;
}
// setup the empty slot in the list and linking of all -> first
ehci_qh *firstLogical = &fInterruptEntries[0].queue_head;
uint32 firstPhysical = firstLogical->this_phy | EHCI_QH_TYPE_QH;
fPeriodicFrameList[0] = firstPhysical;
for (int32 i = 1; i < 10; i++) {
fInterruptEntries[i].queue_head.next_phy = firstPhysical;
fInterruptEntries[i].queue_head.next_log = firstLogical;
}
// terminate the first entry
firstLogical->next_phy = EHCI_QH_TERMINATE;
firstLogical->next_log = NULL;
// allocate a queue head that will always stay in the async frame list
fAsyncQueueHead = CreateQueueHead();
if (!fAsyncQueueHead) {
@ -387,36 +438,13 @@ EHCI::SubmitAsyncTransfer(Transfer *transfer)
}
Pipe *pipe = transfer->TransferPipe();
switch (pipe->Speed()) {
case USB_SPEED_LOWSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_LOW;
break;
case USB_SPEED_FULLSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_FULL;
break;
case USB_SPEED_HIGHSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_HIGH;
break;
default:
TRACE_ERROR(("usb_ehci: unknown pipe speed\n"));
FreeQueueHead(queueHead);
return B_ERROR;
status_t result = InitQueueHead(queueHead, pipe);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to init queue head\n"));
FreeQueueHead(queueHead);
return result;
}
if (pipe->Type() & USB_OBJECT_CONTROL_PIPE) {
queueHead->endpoint_chars |=
(pipe->Speed() != USB_SPEED_HIGHSPEED ? EHCI_QH_CHARS_CONTROL : 0);
}
queueHead->endpoint_chars |= (3 << EHCI_QH_CHARS_RL_SHIFT)
| (pipe->MaxPacketSize() << EHCI_QH_CHARS_MPL_SHIFT)
| (pipe->EndpointAddress() << EHCI_QH_CHARS_EPT_SHIFT)
| (pipe->DeviceAddress() << EHCI_QH_CHARS_DEV_SHIFT)
| EHCI_QH_CHARS_TOGGLE;
queueHead->endpoint_caps = (1 << EHCI_QH_CAPS_MULT_SHIFT)
| (0x1c << EHCI_QH_CAPS_SCM_SHIFT);
status_t result;
bool directionIn;
ehci_qtd *dataDescriptor;
if (pipe->Type() & USB_OBJECT_CONTROL_PIPE)
@ -458,14 +486,62 @@ EHCI::SubmitAsyncTransfer(Transfer *transfer)
status_t
EHCI::SubmitPeriodicTransfer(Transfer *transfer)
{
return B_ERROR;
Pipe *pipe = transfer->TransferPipe();
if ((pipe->Type() & USB_OBJECT_INTERRUPT_PIPE) == 0)
return B_ERROR;
ehci_qh *queueHead = CreateQueueHead();
if (!queueHead) {
TRACE_ERROR(("usb_ehci: failed to allocate periodic queue head\n"));
return B_NO_MEMORY;
}
status_t result = InitQueueHead(queueHead, pipe);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to init queue head\n"));
FreeQueueHead(queueHead);
return result;
}
bool directionIn;
ehci_qtd *dataDescriptor;
result = FillQueueWithData(transfer, queueHead, &dataDescriptor,
&directionIn);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to fill transfer queue with data\n"));
FreeQueueHead(queueHead);
return result;
}
result = AddPendingTransfer(transfer, queueHead, dataDescriptor, directionIn);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to add pending transfer\n"));
FreeQueueHead(queueHead);
return result;
}
#ifdef TRACE_USB
TRACE(("usb_ehci: linking interrupt queue\n"));
print_queue(queueHead);
#endif
result = LinkInterruptQueueHead(queueHead,
((InterruptPipe *)pipe)->Interval());
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to link queue head to the async list\n"));
FreeQueueHead(queueHead);
return result;
}
return B_OK;
}
status_t
EHCI::NotifyPipeChange(Pipe *pipe, usb_change change)
{
TRACE_ERROR(("usb_ehci: pipe change %d for pipe 0x%08lx\n", change, (uint32)pipe));
TRACE(("usb_ehci: pipe change %d for pipe 0x%08lx\n", change, (uint32)pipe));
switch (change) {
case USB_CHANGE_CREATED:
case USB_CHANGE_DESTROYED: {
@ -966,7 +1042,6 @@ EHCI::FinishTransfers()
// a transfer error occured
TRACE_ERROR(("usb_ehci: qtd (0x%08lx) error: 0x%08lx\n", descriptor->this_phy, status));
status_t callbackStatus = B_ERROR;
uint8 errorCount = status >> EHCI_QTD_ERRCOUNT_SHIFT;
errorCount &= EHCI_QTD_ERRCOUNT_MASK;
if (errorCount == 0) {
@ -1177,6 +1252,43 @@ EHCI::CreateQueueHead()
}
status_t
EHCI::InitQueueHead(ehci_qh *queueHead, Pipe *pipe)
{
switch (pipe->Speed()) {
case USB_SPEED_LOWSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_LOW;
break;
case USB_SPEED_FULLSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_FULL;
break;
case USB_SPEED_HIGHSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_HIGH;
break;
default:
TRACE_ERROR(("usb_ehci: unknown pipe speed\n"));
return B_ERROR;
}
if (pipe->Type() & USB_OBJECT_CONTROL_PIPE) {
queueHead->endpoint_chars |=
(pipe->Speed() != USB_SPEED_HIGHSPEED ? EHCI_QH_CHARS_CONTROL : 0);
}
queueHead->endpoint_chars |= (3 << EHCI_QH_CHARS_RL_SHIFT)
| (pipe->MaxPacketSize() << EHCI_QH_CHARS_MPL_SHIFT)
| (pipe->EndpointAddress() << EHCI_QH_CHARS_EPT_SHIFT)
| (pipe->DeviceAddress() << EHCI_QH_CHARS_DEV_SHIFT)
| EHCI_QH_CHARS_TOGGLE;
queueHead->endpoint_caps = (1 << EHCI_QH_CAPS_MULT_SHIFT);
if (pipe->Type() & USB_OBJECT_INTERRUPT_PIPE)
queueHead->endpoint_caps |= (0xff << EHCI_QH_CAPS_ISM_SHIFT);
return B_OK;
}
void
EHCI::FreeQueueHead(ehci_qh *queueHead)
{
@ -1208,6 +1320,34 @@ EHCI::LinkQueueHead(ehci_qh *queueHead)
}
status_t
EHCI::LinkInterruptQueueHead(ehci_qh *queueHead, uint8 interval)
{
if (!Lock())
return B_ERROR;
// this should not happen
if (interval < 1)
interval = 1;
// this may happen as intervals can go up to 16; we limit the value to
// 10 as you cannot support intervals above that with a frame list of
// just 1024 entries...
if (interval > 10)
interval = 10;
ehci_qh *interruptQueue = &fInterruptEntries[interval - 1].queue_head;
queueHead->next_log = interruptQueue->next_log;
queueHead->next_phy = interruptQueue->next_phy;
queueHead->prev_log = interruptQueue;
interruptQueue->next_log = queueHead;
interruptQueue->next_phy = queueHead->this_phy | EHCI_QH_TYPE_QH;
Unlock();
return B_OK;
}
status_t
EHCI::UnlinkQueueHead(ehci_qh *queueHead, ehci_qh **freeListHead)
{
@ -1220,7 +1360,6 @@ EHCI::UnlinkQueueHead(ehci_qh *queueHead, ehci_qh **freeListHead)
prevHead->next_log = queueHead->next_log;
nextHead->prev_log = queueHead->prev_log;
queueHead->next_phy = fAsyncQueueHead->this_phy | EHCI_QH_TYPE_QH;
queueHead->next_log = NULL;
queueHead->prev_log = NULL;
queueHead->next_log = *freeListHead;

View File

@ -76,9 +76,13 @@ static int32 CleanupThread(void *data);
// Queue Head functions
ehci_qh *CreateQueueHead();
status_t InitQueueHead(ehci_qh *queueHead,
Pipe *pipe);
void FreeQueueHead(ehci_qh *queueHead);
status_t LinkQueueHead(ehci_qh *queueHead);
status_t LinkInterruptQueueHead(ehci_qh *queueHead,
uint8 interval);
status_t UnlinkQueueHead(ehci_qh *queueHead,
ehci_qh **freeList);
@ -133,11 +137,12 @@ static pci_module_info *sPCIModule;
pci_info *fPCIInfo;
Stack *fStack;
// Framelist memory
// Periodic transfer framelist and interrupt entries
area_id fPeriodicFrameListArea;
addr_t *fPeriodicFrameList;
interrupt_entry *fInterruptEntries;
// Async frame list management
// Async transfer queue management
ehci_qh *fAsyncQueueHead;
sem_id fAsyncAdvanceSem;

View File

@ -190,6 +190,12 @@ typedef struct {
} ehci_qh;
typedef struct {
ehci_qh queue_head;
uint32 padding[2];
} interrupt_entry;
// Applies to ehci_qh.link_phy
#define EHCI_QH_TYPE_ITD (0 << 1)
#define EHCI_QH_TYPE_QH (1 << 1)