From ed1eff2fec7b126e8032ef19357cb7aceda2c388 Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Sun, 18 Nov 2007 01:05:39 +0000 Subject: [PATCH] * 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 --- src/add-ons/kernel/busses/usb/ehci.cpp | 215 ++++++++++++++---- src/add-ons/kernel/busses/usb/ehci.h | 9 +- src/add-ons/kernel/busses/usb/ehci_hardware.h | 6 + 3 files changed, 190 insertions(+), 40 deletions(-) diff --git a/src/add-ons/kernel/busses/usb/ehci.cpp b/src/add-ons/kernel/busses/usb/ehci.cpp index f24e7276f9..dada59a9fc 100644 --- a/src/add-ons/kernel/busses/usb/ehci.cpp +++ b/src/add-ons/kernel/busses/usb/ehci.cpp @@ -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; diff --git a/src/add-ons/kernel/busses/usb/ehci.h b/src/add-ons/kernel/busses/usb/ehci.h index a042fce72e..a46dece2f9 100644 --- a/src/add-ons/kernel/busses/usb/ehci.h +++ b/src/add-ons/kernel/busses/usb/ehci.h @@ -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; diff --git a/src/add-ons/kernel/busses/usb/ehci_hardware.h b/src/add-ons/kernel/busses/usb/ehci_hardware.h index b080e87c06..29731bace1 100644 --- a/src/add-ons/kernel/busses/usb/ehci_hardware.h +++ b/src/add-ons/kernel/busses/usb/ehci_hardware.h @@ -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)