root/src/add-ons/kernel/busses/usb/uhci.cpp
/*
 * Copyright 2004-2011, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 *              Niels S. Reedijk
 *              Salvatore Benedetto <salvatore.benedetto@gmail.com>
 */


#include <stdio.h>

#include <module.h>
#include <bus/PCI.h>
#include <USB3.h>
#include <KernelExport.h>

#include "uhci.h"


#define CALLED(x...)    TRACE_MODULE("CALLED %s\n", __PRETTY_FUNCTION__)

#define USB_MODULE_NAME "uhci"

device_manager_info* gDeviceManager;
static usb_for_controller_interface* gUSB;


#define UHCI_PCI_DEVICE_MODULE_NAME "busses/usb/uhci/pci/driver_v1"
#define UHCI_PCI_USB_BUS_MODULE_NAME "busses/usb/uhci/device_v1"


typedef struct {
        UHCI* uhci;
        pci_device_module_info* pci;
        pci_device* device;

        pci_info pciinfo;

        device_node* node;
        device_node* driver_node;
} uhci_pci_sim_info;


//      #pragma mark -


static status_t
init_bus(device_node* node, void** bus_cookie)
{
        CALLED();

        driver_module_info* driver;
        uhci_pci_sim_info* bus;
        device_node* parent = gDeviceManager->get_parent_node(node);
        gDeviceManager->get_driver(parent, &driver, (void**)&bus);
        gDeviceManager->put_node(parent);

        Stack *stack;
        if (gUSB->get_stack((void**)&stack) != B_OK)
                return B_ERROR;

        UHCI *uhci = new(std::nothrow) UHCI(&bus->pciinfo, bus->pci, bus->device, stack, node);
        if (uhci == NULL) {
                return B_NO_MEMORY;
        }

        if (uhci->InitCheck() < B_OK) {
                TRACE_MODULE_ERROR("bus failed init check\n");
                delete uhci;
                return B_ERROR;
        }

        if (uhci->Start() != B_OK) {
                delete uhci;
                return B_ERROR;
        }

        *bus_cookie = uhci;

        return B_OK;
}


static void
uninit_bus(void* bus_cookie)
{
        CALLED();
        UHCI* uhci = (UHCI*)bus_cookie;
        delete uhci;
}


static status_t
register_child_devices(void* cookie)
{
        CALLED();
        uhci_pci_sim_info* bus = (uhci_pci_sim_info*)cookie;
        device_node* node = bus->driver_node;

        char prettyName[25];
        sprintf(prettyName, "UHCI Controller %" B_PRIu16, 0);

        device_attr attrs[] = {
                // properties of this controller for the usb bus manager
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
                        { .string = prettyName }},
                { B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
                        { .string = USB_FOR_CONTROLLER_MODULE_NAME }},

                // private data to identify the device
                { NULL }
        };

        return gDeviceManager->register_node(node, UHCI_PCI_USB_BUS_MODULE_NAME,
                attrs, NULL, NULL);
}


static status_t
init_device(device_node* node, void** device_cookie)
{
        CALLED();
        uhci_pci_sim_info* bus = (uhci_pci_sim_info*)calloc(1,
                sizeof(uhci_pci_sim_info));
        if (bus == NULL)
                return B_NO_MEMORY;

        pci_device_module_info* pci;
        pci_device* device;
        {
                device_node* pciParent = gDeviceManager->get_parent_node(node);
                gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
                        (void**)&device);
                gDeviceManager->put_node(pciParent);
        }

        bus->pci = pci;
        bus->device = device;
        bus->driver_node = node;

        pci_info *pciInfo = &bus->pciinfo;
        pci->get_pci_info(device, pciInfo);

        *device_cookie = bus;
        return B_OK;
}


static void
uninit_device(void* device_cookie)
{
        CALLED();
        uhci_pci_sim_info* bus = (uhci_pci_sim_info*)device_cookie;
        free(bus);
}


static status_t
register_device(device_node* parent)
{
        CALLED();
        device_attr attrs[] = {
                {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "UHCI PCI"}},
                {}
        };

        return gDeviceManager->register_node(parent,
                UHCI_PCI_DEVICE_MODULE_NAME, attrs, NULL, NULL);
}


static float
supports_device(device_node* parent)
{
        CALLED();
        const char* bus;
        uint16 type, subType, api;

        // make sure parent is a UHCI PCI device node
        if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)
                < B_OK) {
                return -1;
        }

        if (strcmp(bus, "pci") != 0)
                return 0.0f;

        if (gDeviceManager->get_attr_uint16(parent, B_DEVICE_SUB_TYPE, &subType,
                        false) < B_OK
                || gDeviceManager->get_attr_uint16(parent, B_DEVICE_TYPE, &type,
                        false) < B_OK
                || gDeviceManager->get_attr_uint16(parent, B_DEVICE_INTERFACE, &api,
                        false) < B_OK) {
                TRACE_MODULE("Could not find type/subtype/interface attributes\n");
                return -1;
        }

        if (type == PCI_serial_bus && subType == PCI_usb && api == PCI_usb_uhci) {
                pci_device_module_info* pci;
                pci_device* device;
                gDeviceManager->get_driver(parent, (driver_module_info**)&pci,
                        (void**)&device);
                TRACE_MODULE("UHCI Device found!\n");

                return 0.8f;
        }

        return 0.0f;
}


module_dependency module_dependencies[] = {
        { USB_FOR_CONTROLLER_MODULE_NAME, (module_info**)&gUSB },
        { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
        {}
};


static usb_bus_interface gUHCIPCIDeviceModule = {
        {
                {
                        UHCI_PCI_USB_BUS_MODULE_NAME,
                        0,
                        NULL
                },
                NULL,  // supports device
                NULL,  // register device
                init_bus,
                uninit_bus,
                NULL,  // register child devices
                NULL,  // rescan
                NULL,  // device removed
        },
};

// Root device that binds to the PCI bus. It will register an usb_bus_interface
// node for each device.
static driver_module_info sUHCIDevice = {
        {
                UHCI_PCI_DEVICE_MODULE_NAME,
                0,
                NULL
        },
        supports_device,
        register_device,
        init_device,
        uninit_device,
        register_child_devices,
        NULL, // rescan
        NULL, // device removed
};

module_info* modules[] = {
        (module_info* )&sUHCIDevice,
        (module_info* )&gUHCIPCIDeviceModule,
        NULL
};


//
// #pragma mark -
//


#ifdef TRACE_USB

void
print_descriptor_chain(uhci_td *descriptor)
{
        while (descriptor) {
                dprintf("ph: 0x%08" B_PRIx32 "; lp: 0x%08" B_PRIx32 "; vf: %s; q: %s; "
                        "t: %s; st: 0x%08" B_PRIx32 "; to: 0x%08" B_PRIx32 "\n",
                        descriptor->this_phy & 0xffffffff, descriptor->link_phy & 0xfffffff0,
                        descriptor->link_phy & 0x4 ? "y" : "n",
                        descriptor->link_phy & 0x2 ? "qh" : "td",
                        descriptor->link_phy & 0x1 ? "y" : "n",
                        descriptor->status, descriptor->token);

                if (descriptor->link_phy & TD_TERMINATE)
                        break;

                descriptor = (uhci_td *)descriptor->link_log;
        }
}

#endif // TRACE_USB


//
// #pragma mark -
//


Queue::Queue(Stack *stack)
{
        fStack = stack;

        mutex_init(&fLock, "uhci queue lock");

        phys_addr_t physicalAddress;
        fStatus = fStack->AllocateChunk((void **)&fQueueHead, &physicalAddress,
                sizeof(uhci_qh));
        if (fStatus < B_OK)
                return;

        fQueueHead->this_phy = (uint32)physicalAddress;
        fQueueHead->element_phy = QH_TERMINATE;

        fStrayDescriptor = NULL;
        fQueueTop = NULL;
}


Queue::~Queue()
{
        Lock();
        mutex_destroy(&fLock);

        fStack->FreeChunk(fQueueHead, fQueueHead->this_phy, sizeof(uhci_qh));

        if (fStrayDescriptor)
                fStack->FreeChunk(fStrayDescriptor, fStrayDescriptor->this_phy,
                        sizeof(uhci_td));
}


status_t
Queue::InitCheck()
{
        return fStatus;
}


bool
Queue::Lock()
{
        return (mutex_lock(&fLock) == B_OK);
}


void
Queue::Unlock()
{
        mutex_unlock(&fLock);
}


status_t
Queue::LinkTo(Queue *other)
{
        if (!other)
                return B_BAD_VALUE;

        if (!Lock())
                return B_ERROR;

        fQueueHead->link_phy = other->fQueueHead->this_phy | QH_NEXT_IS_QH;
        fQueueHead->link_log = other->fQueueHead;
        Unlock();

        return B_OK;
}


status_t
Queue::TerminateByStrayDescriptor()
{
        // According to the *BSD USB sources, there needs to be a stray transfer
        // descriptor in order to get some chipset to work nicely (like the PIIX).
        phys_addr_t physicalAddress;
        status_t result = fStack->AllocateChunk((void **)&fStrayDescriptor,
                &physicalAddress, sizeof(uhci_td));
        if (result < B_OK) {
                TRACE_ERROR("failed to allocate a stray transfer descriptor\n");
                return result;
        }

        fStrayDescriptor->status = 0;
        fStrayDescriptor->this_phy = (uint32)physicalAddress;
        fStrayDescriptor->link_phy = TD_TERMINATE;
        fStrayDescriptor->link_log = NULL;
        fStrayDescriptor->buffer_phy = 0;
        fStrayDescriptor->buffer_log = NULL;
        fStrayDescriptor->buffer_size = 0;
        fStrayDescriptor->token = TD_TOKEN_NULL_DATA
                | (0x7f << TD_TOKEN_DEVADDR_SHIFT) | TD_TOKEN_IN;

        if (!Lock()) {
                fStack->FreeChunk(fStrayDescriptor, fStrayDescriptor->this_phy,
                        sizeof(uhci_td));
                return B_ERROR;
        }

        fQueueHead->link_phy = fStrayDescriptor->this_phy;
        fQueueHead->link_log = fStrayDescriptor;
        Unlock();

        return B_OK;
}


status_t
Queue::AppendTransfer(uhci_qh *transfer, bool lock)
{
        if (lock && !Lock())
                return B_ERROR;

        transfer->link_log = NULL;
        transfer->link_phy = fQueueHead->link_phy;

        if (!fQueueTop) {
                // the list is empty, make this the first element
                fQueueTop = transfer;
                fQueueHead->element_phy = transfer->this_phy | QH_NEXT_IS_QH;
        } else {
                // append the transfer queue to the list
                uhci_qh *element = fQueueTop;
                while (element->link_log != NULL)
                        element = (uhci_qh *)element->link_log;

                element->link_log = transfer;
                element->link_phy = transfer->this_phy | QH_NEXT_IS_QH;
        }

        if (lock)
                Unlock();
        return B_OK;
}


status_t
Queue::RemoveTransfer(uhci_qh *transfer, bool lock)
{
        if (lock && !Lock())
                return B_ERROR;

        if (fQueueTop == transfer) {
                // this was the top element
                fQueueTop = (uhci_qh *)transfer->link_log;
                if (!fQueueTop) {
                        // this was the only element, terminate this queue
                        fQueueHead->element_phy = QH_TERMINATE;
                } else {
                        // there are elements left, adjust the element pointer
                        fQueueHead->element_phy = transfer->link_phy;
                }

                if (lock)
                        Unlock();
                return B_OK;
        } else {
                uhci_qh *element = fQueueTop;
                while (element) {
                        if (element->link_log == transfer) {
                                element->link_log = transfer->link_log;
                                element->link_phy = transfer->link_phy;
                                if (lock)
                                        Unlock();
                                return B_OK;
                        }

                        element = (uhci_qh *)element->link_log;
                }
        }

        if (lock)
                Unlock();
        return B_BAD_VALUE;
}


uint32
Queue::PhysicalAddress()
{
        return fQueueHead->this_phy;
}


void
Queue::PrintToStream()
{
#ifdef TRACE_USB
        TRACE("queue:\n");
        dprintf("link phy: 0x%08" B_PRIx32 "; link type: %s; terminate: %s\n",
                fQueueHead->link_phy & 0xfff0,
                fQueueHead->link_phy & 0x0002 ? "QH" : "TD",
                fQueueHead->link_phy & 0x0001 ? "yes" : "no");
        dprintf("elem phy: 0x%08" B_PRIx32 "; elem type: %s; terminate: %s\n",
                fQueueHead->element_phy & 0xfff0,
                fQueueHead->element_phy & 0x0002 ? "QH" : "TD",
                fQueueHead->element_phy & 0x0001 ? "yes" : "no");
#endif
}


//
// #pragma mark -
//


UHCI::UHCI(pci_info *info, pci_device_module_info* pci, pci_device* device, Stack *stack,
        device_node* node)
        :       BusManager(stack, node),
                fPCIInfo(info),
                fPci(pci),
                fDevice(device),
                fStack(stack),
                fEnabledInterrupts(0),
                fFrameArea(-1),
                fFrameList(NULL),
                fFrameBandwidth(NULL),
                fFirstIsochronousDescriptor(NULL),
                fLastIsochronousDescriptor(NULL),
                fQueueCount(0),
                fQueues(NULL),
                fFirstTransfer(NULL),
                fLastTransfer(NULL),
                fFinishTransfersSem(-1),
                fFinishThread(-1),
                fStopThreads(false),
                fProcessingPipe(NULL),
                fFreeList(NULL),
                fCleanupThread(-1),
                fCleanupSem(-1),
                fCleanupCount(0),
                fFirstIsochronousTransfer(NULL),
                fLastIsochronousTransfer(NULL),
                fFinishIsochronousTransfersSem(-1),
                fFinishIsochronousThread(-1),
                fRootHub(NULL),
                fRootHubAddress(0),
                fPortResetChange(0),
                fIRQ(0),
                fUseMSI(false)
{
        // Create a lock for the isochronous transfer list
        mutex_init(&fIsochronousLock, "UHCI isochronous lock");

        if (!fInitOK) {
                TRACE_ERROR("bus manager failed to init\n");
                return;
        }

        TRACE("constructing new UHCI host controller driver\n");
        fInitOK = false;

        fRegisterBase = fPci->read_pci_config(fDevice, PCI_memory_base, 4);
        fRegisterBase &= PCI_address_io_mask;
        TRACE("iospace offset: 0x%08" B_PRIx32 "\n", fRegisterBase);

        if (fRegisterBase == 0) {
                fRegisterBase = fPCIInfo->u.h0.base_registers[0];
                TRACE_ALWAYS("register base: 0x%08" B_PRIx32 "\n", fRegisterBase);
        }

        // enable pci address access
        uint16 command = PCI_command_io | PCI_command_master | PCI_command_memory;
        command |= fPci->read_pci_config(fDevice, PCI_command, 2);

        fPci->write_pci_config(fDevice, PCI_command, 2, command);

        // disable interrupts
        WriteReg16(UHCI_USBINTR, 0);

        // make sure we gain control of the UHCI controller instead of the BIOS
        fPci->write_pci_config(fDevice, PCI_LEGSUP, 2, PCI_LEGSUP_USBPIRQDEN
                | PCI_LEGSUP_CLEAR_SMI);

        // do a global and host reset
        GlobalReset();
        if (ControllerReset() < B_OK) {
                TRACE_ERROR("host failed to reset\n");
                return;
        }

        // Setup the frame list
        phys_addr_t physicalAddress;
        fFrameArea = fStack->AllocateArea((void **)&fFrameList, &physicalAddress,
                4096, "USB UHCI framelist");

        if (fFrameArea < B_OK) {
                TRACE_ERROR("unable to create an area for the frame pointer list\n");
                return;
        }

        // Set base pointer and reset frame number
        WriteReg32(UHCI_FRBASEADD, (uint32)physicalAddress);
        WriteReg16(UHCI_FRNUM, 0);

        // Set the max packet size for bandwidth reclamation to 64 bytes
        WriteReg16(UHCI_USBCMD, ReadReg16(UHCI_USBCMD) | UHCI_USBCMD_MAXP);

        // we will create four queues:
        // 0: interrupt transfers
        // 1: low speed control transfers
        // 2: full speed control transfers
        // 3: bulk transfers
        // 4: debug queue
        // TODO: 4: bandwidth reclamation queue
        fQueueCount = 5;
        fQueues = new(std::nothrow) Queue *[fQueueCount];
        if (!fQueues) {
                delete_area(fFrameArea);
                return;
        }

        for (int32 i = 0; i < fQueueCount; i++) {
                fQueues[i] = new(std::nothrow) Queue(fStack);
                if (!fQueues[i] || fQueues[i]->InitCheck() < B_OK) {
                        TRACE_ERROR("cannot create queues\n");
                        delete_area(fFrameArea);
                        return;
                }

                if (i > 0)
                        fQueues[i - 1]->LinkTo(fQueues[i]);
        }

        // Make sure the last queue terminates
        fQueues[fQueueCount - 1]->TerminateByStrayDescriptor();

        // Create the array that will keep bandwidth information
        fFrameBandwidth = new(std::nothrow) uint16[NUMBER_OF_FRAMES];

        // Create lists for managing isochronous transfer descriptors
        fFirstIsochronousDescriptor = new(std::nothrow) uhci_td *[NUMBER_OF_FRAMES];
        if (!fFirstIsochronousDescriptor) {
                TRACE_ERROR("faild to allocate memory for first isochronous descriptor\n");
                return;
        }

        fLastIsochronousDescriptor = new(std::nothrow) uhci_td *[NUMBER_OF_FRAMES];
        if (!fLastIsochronousDescriptor) {
                TRACE_ERROR("failed to allocate memory for last isochronous descriptor\n");
                delete [] fFirstIsochronousDescriptor;
                return;
        }

        for (int32 i = 0; i < NUMBER_OF_FRAMES; i++) {
                fFrameList[i] = fQueues[UHCI_INTERRUPT_QUEUE]->PhysicalAddress()
                        | FRAMELIST_NEXT_IS_QH;
                fFrameBandwidth[i] = MAX_AVAILABLE_BANDWIDTH;
                fFirstIsochronousDescriptor[i] = NULL;
                fLastIsochronousDescriptor[i] = NULL;
        }

        // Create semaphore the finisher and cleanup threads will wait for
        fFinishTransfersSem = create_sem(0, "UHCI Finish Transfers");
        if (fFinishTransfersSem < B_OK) {
                TRACE_ERROR("failed to create finisher semaphore\n");
                return;
        }

        fCleanupSem = create_sem(0, "UHCI Cleanup");
        if (fCleanupSem < B_OK) {
                TRACE_ERROR("failed to create cleanup semaphore\n");
                return;
        }

        // Create the finisher service and cleanup threads
        fFinishThread = spawn_kernel_thread(FinishThread,
                "uhci finish thread", B_URGENT_DISPLAY_PRIORITY, (void *)this);
        resume_thread(fFinishThread);

        fCleanupThread = spawn_kernel_thread(CleanupThread,
                "uhci cleanup thread", B_NORMAL_PRIORITY, (void *)this);
        resume_thread(fCleanupThread);

        // Create semaphore the isochronous finisher thread will wait for
        fFinishIsochronousTransfersSem = create_sem(0,
                "UHCI Isochronous Finish Transfers");
        if (fFinishIsochronousTransfersSem < B_OK) {
                TRACE_ERROR("failed to create isochronous finisher semaphore\n");
                return;
        }

        // Create the isochronous finisher service thread
        fFinishIsochronousThread = spawn_kernel_thread(FinishIsochronousThread,
                "uhci isochronous finish thread", B_URGENT_DISPLAY_PRIORITY,
                (void *)this);
        resume_thread(fFinishIsochronousThread);

        // Find the right interrupt vector, using MSIs if available.
        fIRQ = fPCIInfo->u.h0.interrupt_line;
                if (fIRQ == 0xFF)
                        fIRQ = 0;

        if (fPci->get_msi_count(fDevice) >= 1) {
                uint32 msiVector = 0;
                if (fPci->configure_msi(fDevice, 1, &msiVector) == B_OK
                        && fPci->enable_msi(fDevice) == B_OK) {
                        TRACE_ALWAYS("using message signaled interrupts\n");
                        fIRQ = msiVector;
                        fUseMSI = true;
                }
        }

        if (fIRQ == 0) {
                TRACE_MODULE_ERROR("device PCI:%d:%d:%d was assigned an invalid IRQ\n",
                        fPCIInfo->bus, fPCIInfo->device, fPCIInfo->function);
                return;
        }

        // Install the interrupt handler
        TRACE("installing interrupt handler\n");
        install_io_interrupt_handler(fIRQ, InterruptHandler, (void *)this, 0);

        // Enable interrupts
        fEnabledInterrupts = UHCI_USBSTS_USBINT | UHCI_USBSTS_ERRINT
                | UHCI_USBSTS_HOSTERR | UHCI_USBSTS_HCPRERR | UHCI_USBSTS_HCHALT;
        WriteReg16(UHCI_USBINTR, UHCI_USBINTR_CRC | UHCI_USBINTR_IOC
                | UHCI_USBINTR_SHORT);

        TRACE("UHCI host controller driver constructed\n");
        fInitOK = true;
}


UHCI::~UHCI()
{
        int32 result = 0;
        fStopThreads = true;
        delete_sem(fFinishTransfersSem);
        delete_sem(fCleanupSem);
        delete_sem(fFinishIsochronousTransfersSem);
        wait_for_thread(fFinishThread, &result);
        wait_for_thread(fCleanupThread, &result);
        wait_for_thread(fFinishIsochronousThread, &result);

        remove_io_interrupt_handler(fIRQ, InterruptHandler, (void *)this);

        LockIsochronous();
        isochronous_transfer_data *isoTransfer = fFirstIsochronousTransfer;
        while (isoTransfer) {
                isochronous_transfer_data *next = isoTransfer->link;
                delete isoTransfer;
                isoTransfer = next;
        }
        mutex_destroy(&fIsochronousLock);

        Lock();
        transfer_data *transfer = fFirstTransfer;
        while (transfer) {
                transfer->transfer->Finished(B_CANCELED, 0);
                delete transfer->transfer;

                transfer_data *next = transfer->link;
                delete transfer;
                transfer = next;
        }

        for (int32 i = 0; i < fQueueCount; i++)
                delete fQueues[i];

        delete [] fQueues;
        delete [] fFrameBandwidth;
        delete [] fFirstIsochronousDescriptor;
        delete [] fLastIsochronousDescriptor;
        delete fRootHub;
        delete_area(fFrameArea);

        if (fUseMSI) {
                fPci->disable_msi(fDevice);
                fPci->unconfigure_msi(fDevice);
        }

        Unlock();
}


status_t
UHCI::Start()
{
        // Start the host controller, then start the Busmanager
        TRACE("starting UHCI BusManager\n");
        TRACE("usbcmd reg 0x%04x, usbsts reg 0x%04x\n",
                ReadReg16(UHCI_USBCMD), ReadReg16(UHCI_USBSTS));

        // Set the run bit in the command register
        WriteReg16(UHCI_USBCMD, ReadReg16(UHCI_USBCMD) | UHCI_USBCMD_RS);

        bool running = false;
        for (int32 i = 0; i < 10; i++) {
                uint16 status = ReadReg16(UHCI_USBSTS);
                TRACE("current loop %" B_PRId32 ", status 0x%04x\n", i, status);

                if (status & UHCI_USBSTS_HCHALT)
                        snooze(10000);
                else {
                        running = true;
                        break;
                }
        }

        if (!running) {
                TRACE_ERROR("controller won't start running\n");
                return B_ERROR;
        }

        fRootHubAddress = AllocateAddress();
        fRootHub = new(std::nothrow) UHCIRootHub(RootObject(), fRootHubAddress);
        if (!fRootHub) {
                TRACE_ERROR("no memory to allocate root hub\n");
                return B_NO_MEMORY;
        }

        if (fRootHub->InitCheck() < B_OK) {
                TRACE_ERROR("root hub failed init check\n");
                delete fRootHub;
                return B_ERROR;
        }

        SetRootHub(fRootHub);

        fRootHub->RegisterNode(Node());

        TRACE("controller is started. status: %u curframe: %u\n",
                ReadReg16(UHCI_USBSTS), ReadReg16(UHCI_FRNUM));
        TRACE_ALWAYS("successfully started the controller\n");
        return BusManager::Start();
}


status_t
UHCI::SubmitTransfer(Transfer *transfer)
{
        // Short circuit the root hub
        Pipe *pipe = transfer->TransferPipe();
        if (pipe->DeviceAddress() == fRootHubAddress)
                return fRootHub->ProcessTransfer(this, transfer);

        TRACE("submit transfer called for device %d\n", pipe->DeviceAddress());
        if (pipe->Type() & USB_OBJECT_CONTROL_PIPE)
                return SubmitRequest(transfer);

        // Process isochronous transfers
#if 0
        if (pipe->Type() & USB_OBJECT_ISO_PIPE)
                return SubmitIsochronous(transfer);
#else
        // At present, isochronous transfers cause busylooping, and do not seem to work.
        if (pipe->Type() & USB_OBJECT_ISO_PIPE)
                return B_NOT_SUPPORTED;
#endif

        uhci_td *firstDescriptor = NULL;
        uhci_qh *transferQueue = NULL;
        status_t result = CreateFilledTransfer(transfer, &firstDescriptor,
                &transferQueue);
        if (result < B_OK)
                return result;

        Queue *queue = NULL;
        if (pipe->Type() & USB_OBJECT_INTERRUPT_PIPE)
                queue = fQueues[UHCI_INTERRUPT_QUEUE];
        else
                queue = fQueues[UHCI_BULK_QUEUE];

        bool directionIn = (pipe->Direction() == Pipe::In);
        result = AddPendingTransfer(transfer, queue, transferQueue,
                firstDescriptor, firstDescriptor, directionIn);
        if (result < B_OK) {
                TRACE_ERROR("failed to add pending transfer\n");
                FreeDescriptorChain(firstDescriptor);
                FreeTransferQueue(transferQueue);
                return result;
        }

        queue->AppendTransfer(transferQueue);
        return B_OK;
}


status_t
UHCI::StartDebugTransfer(Transfer *transfer)
{
        if ((transfer->TransferPipe()->Type() & USB_OBJECT_CONTROL_PIPE) != 0)
                return B_UNSUPPORTED;

        static transfer_data transferData;
        transferData.first_descriptor = NULL;
        transferData.transfer_queue = NULL;
        status_t result = CreateFilledTransfer(transfer,
                &transferData.first_descriptor, &transferData.transfer_queue);
        if (result < B_OK)
                return result;

        fQueues[UHCI_DEBUG_QUEUE]->AppendTransfer(transferData.transfer_queue,
                false);

        // we abuse the callback cookie to hold our transfer data
        transfer->SetCallback(NULL, &transferData);
        return B_OK;
}


status_t
UHCI::CheckDebugTransfer(Transfer *transfer)
{
        bool transferOK = false;
        bool transferError = false;
        transfer_data *transferData = (transfer_data *)transfer->CallbackCookie();
        uhci_td *descriptor = transferData->first_descriptor;

        while (descriptor) {
                uint32 status = descriptor->status;
                if (status & TD_STATUS_ACTIVE)
                        break;

                if (status & TD_ERROR_MASK) {
                        transferError = true;
                        break;
                }

                if ((descriptor->link_phy & TD_TERMINATE)
                        || uhci_td_actual_length(descriptor)
                                < uhci_td_maximum_length(descriptor)) {
                        transferOK = true;
                        break;
                }

                descriptor = (uhci_td *)descriptor->link_log;
        }

        if (!transferOK && !transferError) {
                spin(200);
                return B_DEV_PENDING;
        }

        if (transferOK) {
                uint8 lastDataToggle = 0;
                if (transfer->TransferPipe()->Direction() == Pipe::In) {
                        // data to read out
                        generic_io_vec *vector = transfer->Vector();
                        size_t vectorCount = transfer->VectorCount();

                        ReadDescriptorChain(transferData->first_descriptor,
                                vector, vectorCount, transfer->IsPhysical(), &lastDataToggle);
                } else {
                        // read the actual length that was sent
                        ReadActualLength(transferData->first_descriptor, &lastDataToggle);
                }

                transfer->TransferPipe()->SetDataToggle(lastDataToggle == 0);
        }

        fQueues[UHCI_DEBUG_QUEUE]->RemoveTransfer(transferData->transfer_queue,
                false);
        FreeDescriptorChain(transferData->first_descriptor);
        FreeTransferQueue(transferData->transfer_queue);
        return transferOK ? B_OK : B_IO_ERROR;
}


void
UHCI::CancelDebugTransfer(Transfer *transfer)
{
        transfer_data *transferData = (transfer_data *)transfer->CallbackCookie();

        // clear the active bit so the descriptors are canceled
        uhci_td *descriptor = transferData->first_descriptor;
        while (descriptor) {
                descriptor->status &= ~TD_STATUS_ACTIVE;
                descriptor = (uhci_td *)descriptor->link_log;
        }

        transfer->Finished(B_CANCELED, 0);

        // dequeue and free resources
        fQueues[UHCI_DEBUG_QUEUE]->RemoveTransfer(transferData->transfer_queue,
                false);
        FreeDescriptorChain(transferData->first_descriptor);
        FreeTransferQueue(transferData->transfer_queue);
        // TODO: [bonefish] The Free*() calls cause "PMA: provided address resulted
        // in invalid index" to be printed, so apparently something is not right.
        // Though I have not clue what. This is the same cleanup code as in
        // CheckDebugTransfer() that should undo the CreateFilledTransfer() from
        // StartDebugTransfer().
}


status_t
UHCI::CancelQueuedTransfers(Pipe *pipe, bool force)
{
        if (pipe->Type() & USB_OBJECT_ISO_PIPE)
                return CancelQueuedIsochronousTransfers(pipe, force);

        if (!Lock())
                return B_ERROR;

        struct transfer_entry {
                Transfer *                      transfer;
                transfer_entry *        next;
        };

        transfer_entry *list = NULL;
        transfer_data *current = fFirstTransfer;
        while (current) {
                if (current->transfer && current->transfer->TransferPipe() == pipe) {
                        // clear the active bit so the descriptors are canceled
                        uhci_td *descriptor = current->first_descriptor;
                        while (descriptor) {
                                descriptor->status &= ~TD_STATUS_ACTIVE;
                                descriptor = (uhci_td *)descriptor->link_log;
                        }

                        transfer_entry *entry
                                = (transfer_entry *)malloc(sizeof(transfer_entry));
                        if (entry != NULL) {
                                entry->transfer = current->transfer;
                                current->transfer = NULL;
                                entry->next = list;
                                list = entry;
                        }

                        current->canceled = true;
                }
                current = current->link;
        }

        Unlock();

        while (list != NULL) {
                transfer_entry *next = list->next;

                // if the transfer is canceled by force, the one causing the
                // cancel is possibly not the one who initiated the transfer
                // and the callback is likely not safe anymore
                if (!force)
                        list->transfer->Finished(B_CANCELED, 0);

                delete list->transfer;
                free(list);
                list = next;
        }

        // wait for any transfers that might have made it before canceling
        while (fProcessingPipe == pipe)
                snooze(1000);

        // notify the finisher so it can clean up the canceled transfers
        release_sem_etc(fFinishTransfersSem, 1, B_DO_NOT_RESCHEDULE);
        return B_OK;
}


status_t
UHCI::CancelQueuedIsochronousTransfers(Pipe *pipe, bool force)
{
        isochronous_transfer_data *current = fFirstIsochronousTransfer;

        while (current) {
                if (current->transfer->TransferPipe() == pipe) {
                        int32 packetCount
                                = current->transfer->IsochronousData()->packet_count;
                        // Set the active bit off on every descriptor in order to prevent
                        // the controller from processing them. Then set off the is_active
                        // field of the transfer in order to make the finisher thread skip
                        // the transfer. The FinishIsochronousThread will do the rest.
                        for (int32 i = 0; i < packetCount; i++)
                                current->descriptors[i]->status &= ~TD_STATUS_ACTIVE;

                        // TODO: Use the force paramater in order to avoid calling
                        // invalid callbacks
                        current->is_active = false;
                }

                current = current->link;
        }

        TRACE_ERROR("no isochronous transfer found!\n");
        return B_ERROR;
}


status_t
UHCI::SubmitRequest(Transfer *transfer)
{
        Pipe *pipe = transfer->TransferPipe();
        usb_request_data *requestData = transfer->RequestData();
        bool directionIn = (requestData->RequestType & USB_REQTYPE_DEVICE_IN) > 0;

        uhci_td *setupDescriptor = CreateDescriptor(pipe, TD_TOKEN_SETUP,
                sizeof(usb_request_data));

        uhci_td *statusDescriptor = CreateDescriptor(pipe,
                directionIn ? TD_TOKEN_OUT : TD_TOKEN_IN, 0);

        if (!setupDescriptor || !statusDescriptor) {
                TRACE_ERROR("failed to allocate descriptors\n");
                FreeDescriptor(setupDescriptor);
                FreeDescriptor(statusDescriptor);
                return B_NO_MEMORY;
        }

        generic_io_vec vector;
        vector.base = (generic_addr_t)requestData;
        vector.length = sizeof(usb_request_data);
        WriteDescriptorChain(setupDescriptor, &vector, 1, false);

        statusDescriptor->status |= TD_CONTROL_IOC;
        statusDescriptor->token |= TD_TOKEN_DATA1;
        statusDescriptor->link_phy = TD_TERMINATE;
        statusDescriptor->link_log = NULL;

        uhci_td *dataDescriptor = NULL;
        if (transfer->VectorCount() > 0) {
                uhci_td *lastDescriptor = NULL;
                status_t result = CreateDescriptorChain(pipe, &dataDescriptor,
                        &lastDescriptor, directionIn ? TD_TOKEN_IN : TD_TOKEN_OUT,
                        transfer->FragmentLength());

                if (result < B_OK) {
                        FreeDescriptor(setupDescriptor);
                        FreeDescriptor(statusDescriptor);
                        return result;
                }

                if (!directionIn) {
                        WriteDescriptorChain(dataDescriptor, transfer->Vector(),
                                transfer->VectorCount(), transfer->IsPhysical());
                }

                LinkDescriptors(setupDescriptor, dataDescriptor);
                LinkDescriptors(lastDescriptor, statusDescriptor);
        } else {
                // Link transfer and status descriptors directly
                LinkDescriptors(setupDescriptor, statusDescriptor);
        }

        Queue *queue = NULL;
        if (pipe->Speed() == USB_SPEED_LOWSPEED)
                queue = fQueues[UHCI_LOW_SPEED_CONTROL_QUEUE];
        else
                queue = fQueues[UHCI_FULL_SPEED_CONTROL_QUEUE];

        uhci_qh *transferQueue = CreateTransferQueue(setupDescriptor);
        status_t result = AddPendingTransfer(transfer, queue, transferQueue,
                setupDescriptor, dataDescriptor, directionIn);
        if (result < B_OK) {
                TRACE_ERROR("failed to add pending transfer\n");
                FreeDescriptorChain(setupDescriptor);
                FreeTransferQueue(transferQueue);
                return result;
        }

        queue->AppendTransfer(transferQueue);
        return B_OK;
}


status_t
UHCI::AddPendingTransfer(Transfer *transfer, Queue *queue,
        uhci_qh *transferQueue, uhci_td *firstDescriptor, uhci_td *dataDescriptor,
        bool directionIn)
{
        if (!transfer || !queue || !transferQueue || !firstDescriptor)
                return B_BAD_VALUE;

        transfer_data *data = new(std::nothrow) transfer_data;
        if (!data)
                return B_NO_MEMORY;

        status_t result = transfer->InitKernelAccess();
        if (result < B_OK) {
                delete data;
                return result;
        }

        data->transfer = transfer;
        data->queue = queue;
        data->transfer_queue = transferQueue;
        data->first_descriptor = firstDescriptor;
        data->data_descriptor = dataDescriptor;
        data->incoming = directionIn;
        data->canceled = false;
        data->link = NULL;

        if (!Lock()) {
                delete data;
                return B_ERROR;
        }

        // We do not support queuing other transfers in tandem with a fragmented one.
        transfer_data *it = fFirstTransfer;
        while (it) {
                if (it->transfer && it->transfer->TransferPipe() == transfer->TransferPipe()
                                && it->transfer->IsFragmented()) {
                        TRACE_ERROR("cannot submit transfer: a fragmented transfer is queued\n");

                        Unlock();
                        delete data;
                        return B_DEV_RESOURCE_CONFLICT;
                }

                it = it->link;
        }

        if (fLastTransfer)
                fLastTransfer->link = data;
        if (!fFirstTransfer)
                fFirstTransfer = data;

        fLastTransfer = data;
        Unlock();
        return B_OK;
}


status_t
UHCI::AddPendingIsochronousTransfer(Transfer *transfer, uhci_td **isoRequest,
        bool directionIn)
{
        if (!transfer || !isoRequest)
                return B_BAD_VALUE;

        isochronous_transfer_data *data
                = new(std::nothrow) isochronous_transfer_data;
        if (!data)
                return B_NO_MEMORY;

        status_t result = transfer->InitKernelAccess();
        if (result < B_OK) {
                delete data;
                return result;
        }

        data->transfer = transfer;
        data->descriptors = isoRequest;
        data->last_to_process = transfer->IsochronousData()->packet_count - 1;
        data->incoming = directionIn;
        data->is_active = true;
        data->link = NULL;

        // Put in the isochronous transfer list
        if (!LockIsochronous()) {
                delete data;
                return B_ERROR;
        }

        if (fLastIsochronousTransfer)
                fLastIsochronousTransfer->link = data;
        if (!fFirstIsochronousTransfer)
                fFirstIsochronousTransfer = data;

        fLastIsochronousTransfer = data;
        UnlockIsochronous();
        return B_OK;
}


status_t
UHCI::SubmitIsochronous(Transfer *transfer)
{
        Pipe *pipe = transfer->TransferPipe();
        bool directionIn = (pipe->Direction() == Pipe::In);
        usb_isochronous_data *isochronousData = transfer->IsochronousData();
        size_t packetSize = transfer->DataLength();
        size_t restSize = packetSize % isochronousData->packet_count;
        packetSize /= isochronousData->packet_count;
        uint16 currentFrame;

        if (packetSize > pipe->MaxPacketSize()) {
                TRACE_ERROR("isochronous packetSize is bigger than pipe MaxPacketSize\n");
                return B_BAD_VALUE;
        }

        // Ignore the fact that the last descriptor might need less bandwidth.
        // The overhead is not worthy.
        uint16 bandwidth = transfer->Bandwidth() / isochronousData->packet_count;

        TRACE("isochronous transfer descriptor bandwidth %d\n", bandwidth);

        // The following holds the list of transfer descriptor of the
        // isochronous request. It is used to quickly remove all the isochronous
        // descriptors from the frame list, as descriptors are not link to each
        // other in a queue like for every other transfer.
        uhci_td **isoRequest
                = new(std::nothrow) uhci_td *[isochronousData->packet_count];
        if (isoRequest == NULL) {
                TRACE("failed to create isoRequest array!\n");
                return B_NO_MEMORY;
        }

        // Create the list of transfer descriptors
        for (uint32 i = 0; i < (isochronousData->packet_count - 1); i++) {
                isoRequest[i] = CreateDescriptor(pipe,
                        directionIn ? TD_TOKEN_IN : TD_TOKEN_OUT, packetSize);
                // If we ran out of memory, clean up and return
                if (isoRequest[i] == NULL) {
                        for (uint32 j = 0; j < i; j++)
                                FreeDescriptor(isoRequest[j]);
                        delete [] isoRequest;
                        return B_NO_MEMORY;
                }
                // Make sure data toggle is set to zero
                isoRequest[i]->token &= ~TD_TOKEN_DATA1;
        }

        // Create the last transfer descriptor which should be of smaller size
        // and set the IOC bit
        isoRequest[isochronousData->packet_count - 1] = CreateDescriptor(pipe,
                directionIn ? TD_TOKEN_IN : TD_TOKEN_OUT,
                (restSize) ? restSize : packetSize);
        // If we are that unlucky...
        if (!isoRequest[isochronousData->packet_count - 1]) {
                for (uint32 i = 0; i < (isochronousData->packet_count - 2); i++)
                        FreeDescriptor(isoRequest[i]);
                delete [] isoRequest;
                return B_NO_MEMORY;
        }
        isoRequest[isochronousData->packet_count - 1]->token &= ~TD_TOKEN_DATA1;

        // If direction is out set every descriptor data
        if (!directionIn) {
                generic_io_vec *vector = transfer->Vector();
                WriteIsochronousDescriptorChain(isoRequest,
                        isochronousData->packet_count, vector);
        }

        TRACE("isochronous submitted size=%ld bytes, TDs=%" B_PRId32 ", "
                "packetSize=%ld, restSize=%ld\n", transfer->DataLength(),
                isochronousData->packet_count, packetSize, restSize);

        // Find the entry where to start inserting the first Isochronous descriptor
        if (isochronousData->flags & USB_ISO_ASAP ||
                isochronousData->starting_frame_number == NULL) {
                // find the first available frame with enough bandwidth.
                // This should always be the case, as defining the starting frame
                // number in the driver makes no sense for many reason, one of which
                // is that frame numbers value are host controller specific, and the
                // driver does not know which host controller is running.
                currentFrame = ReadReg16(UHCI_FRNUM);

                // Make sure that:
                // 1. We are at least 5ms ahead the controller
                // 2. We stay in the range 0-1023
                // 3. There is enough bandwidth in the first entry
                currentFrame = (currentFrame + 5) % NUMBER_OF_FRAMES;
        } else {
                // Find out if the frame number specified has enough bandwidth,
                // otherwise find the first next available frame with enough bandwidth
                currentFrame = *isochronousData->starting_frame_number;
        }

        // Find the first entry with enough bandwidth
        // TODO: should we also check the bandwidth of the following packet_count frames?
        uint16 startSeekingFromFrame = currentFrame;
        while (fFrameBandwidth[currentFrame] < bandwidth) {
                currentFrame = (currentFrame + 1) % NUMBER_OF_FRAMES;
                if (currentFrame == startSeekingFromFrame) {
                        TRACE_ERROR("not enough bandwidth to queue the isochronous request");
                        for (uint32 i = 0; i < isochronousData->packet_count; i++)
                                FreeDescriptor(isoRequest[i]);
                        delete [] isoRequest;
                return B_ERROR;
                }
        }

        if (isochronousData->starting_frame_number)
                *isochronousData->starting_frame_number = currentFrame;

        // Add transfer to the list
        status_t result = AddPendingIsochronousTransfer(transfer, isoRequest,
                directionIn);
        if (result < B_OK) {
                TRACE_ERROR("failed to add pending isochronous transfer\n");
                for (uint32 i = 0; i < isochronousData->packet_count; i++)
                        FreeDescriptor(isoRequest[i]);
                delete [] isoRequest;
                return result;
        }

        TRACE("appended isochronous transfer by starting at frame number %d\n",
                currentFrame);

        // Insert the Transfer Descriptor by starting at
        // the starting_frame_number entry
        // TODO: We don't consider bInterval, and assume it's 1!
        for (uint32 i = 0; i < isochronousData->packet_count; i++) {
                result = LinkIsochronousDescriptor(isoRequest[i], currentFrame);
                if (result < B_OK) {
                        TRACE_ERROR("failed to add pending isochronous transfer\n");
                        for (uint32 i = 0; i < isochronousData->packet_count; i++)
                                FreeDescriptor(isoRequest[i]);
                        delete [] isoRequest;
                        return result;
                }

                fFrameBandwidth[currentFrame] -= bandwidth;
                currentFrame = (currentFrame + 1) % NUMBER_OF_FRAMES;
        }

        // Wake up the isochronous finisher thread
        release_sem_etc(fFinishIsochronousTransfersSem, 1, B_DO_NOT_RESCHEDULE);

        return B_OK;
}


isochronous_transfer_data *
UHCI::FindIsochronousTransfer(uhci_td *descriptor)
{
        // Simply check every last descriptor of the isochronous transfer list
        if (LockIsochronous()) {
                isochronous_transfer_data *transfer = fFirstIsochronousTransfer;
                if (transfer) {
                        while (transfer->descriptors[transfer->last_to_process]
                                != descriptor) {
                                transfer = transfer->link;
                                if (!transfer)
                                        break;
                        }
                }
                UnlockIsochronous();
                return transfer;
        }
        return NULL;
}


status_t
UHCI::LinkIsochronousDescriptor(uhci_td *descriptor, uint16 frame)
{
        // The transfer descriptor is appended to the last
        // existing isochronous transfer descriptor (if any)
        // in that frame.
        if (LockIsochronous()) {
                if (!fFirstIsochronousDescriptor[frame]) {
                        // Insert the transfer descriptor in the first position
                        fFrameList[frame] = descriptor->this_phy & ~FRAMELIST_NEXT_IS_QH;
                        fFirstIsochronousDescriptor[frame] = descriptor;
                        fLastIsochronousDescriptor[frame] = descriptor;
                } else {
                        // Append to the last transfer descriptor
                        fLastIsochronousDescriptor[frame]->link_log = descriptor;
                        fLastIsochronousDescriptor[frame]->link_phy
                                = descriptor->this_phy & ~TD_NEXT_IS_QH;
                        fLastIsochronousDescriptor[frame] = descriptor;
                }

                descriptor->link_phy
                        = fQueues[UHCI_INTERRUPT_QUEUE]->PhysicalAddress() | TD_NEXT_IS_QH;
                UnlockIsochronous();
                return B_OK;
        }
        return B_ERROR;
}


uhci_td *
UHCI::UnlinkIsochronousDescriptor(uint16 frame)
{
        // We always unlink from the top
        if (LockIsochronous()) {
                uhci_td *descriptor = fFirstIsochronousDescriptor[frame];
                if (descriptor) {
                        // The descriptor will be freed later.
                        fFrameList[frame] = descriptor->link_phy;
                        if (descriptor->link_log) {
                                fFirstIsochronousDescriptor[frame]
                                        = (uhci_td *)descriptor->link_log;
                        } else {
                                fFirstIsochronousDescriptor[frame] = NULL;
                                fLastIsochronousDescriptor[frame] = NULL;
                        }
                }
                UnlockIsochronous();
                return descriptor;
        }
        return NULL;
}


int32
UHCI::FinishThread(void *data)
{
        ((UHCI *)data)->FinishTransfers();
        return B_OK;
}


void
UHCI::FinishTransfers()
{
        while (!fStopThreads) {
                if (acquire_sem(fFinishTransfersSem) < B_OK)
                        continue;

                // eat up sems that have been released by multiple interrupts
                int32 semCount = 0;
                get_sem_count(fFinishTransfersSem, &semCount);
                if (semCount > 0)
                        acquire_sem_etc(fFinishTransfersSem, semCount, B_RELATIVE_TIMEOUT, 0);

                if (!Lock())
                        continue;

                TRACE("finishing transfers (first transfer: 0x%08lx; last"
                        " transfer: 0x%08lx)\n", (addr_t)fFirstTransfer,
                        (addr_t)fLastTransfer);
                transfer_data *lastTransfer = NULL;
                transfer_data *transfer = fFirstTransfer;
                Unlock();

                while (transfer) {
                        bool transferDone = false;
                        uhci_td *descriptor = transfer->first_descriptor;
                        status_t callbackStatus = B_OK;

                        while (descriptor) {
                                uint32 status = descriptor->status;
                                if (status & TD_STATUS_ACTIVE) {
                                        // still in progress
                                        TRACE("td (0x%08" B_PRIx32 ") still active\n",
                                                descriptor->this_phy);
                                        break;
                                }

                                if (status & TD_ERROR_MASK) {
                                        // an error occured
                                        TRACE_ERROR("td (0x%08" B_PRIx32 ") error: status: 0x%08"
                                                B_PRIx32 "; token: 0x%08" B_PRIx32 ";\n",
                                                descriptor->this_phy, status, descriptor->token);

                                        uint8 errorCount = status >> TD_ERROR_COUNT_SHIFT;
                                        errorCount &= TD_ERROR_COUNT_MASK;
                                        if (errorCount == 0) {
                                                // the error counter counted down to zero, report why
                                                int32 reasons = 0;
                                                if (status & TD_STATUS_ERROR_BUFFER) {
                                                        callbackStatus = transfer->incoming ? B_DEV_WRITE_ERROR : B_DEV_READ_ERROR;
                                                        reasons++;
                                                }
                                                if (status & TD_STATUS_ERROR_TIMEOUT) {
                                                        callbackStatus = transfer->incoming ? B_DEV_CRC_ERROR : B_TIMED_OUT;
                                                        reasons++;
                                                }
                                                if (status & TD_STATUS_ERROR_NAK) {
                                                        callbackStatus = B_DEV_UNEXPECTED_PID;
                                                        reasons++;
                                                }
                                                if (status & TD_STATUS_ERROR_BITSTUFF) {
                                                        callbackStatus = B_DEV_CRC_ERROR;
                                                        reasons++;
                                                }

                                                if (reasons > 1)
                                                        callbackStatus = B_DEV_MULTIPLE_ERRORS;
                                        } else if (status & TD_STATUS_ERROR_BABBLE) {
                                                // there is a babble condition
                                                callbackStatus = transfer->incoming ? B_DEV_DATA_OVERRUN : B_DEV_DATA_UNDERRUN;
                                        } else {
                                                // if the error counter didn't count down to zero
                                                // and there was no babble, then this halt was caused
                                                // by a stall handshake
                                                callbackStatus = B_DEV_STALLED;
                                        }

                                        transferDone = true;
                                        break;
                                }

                                if ((descriptor->link_phy & TD_TERMINATE)
                                        || ((descriptor->status & TD_CONTROL_SPD) != 0
                                                && uhci_td_actual_length(descriptor)
                                                        < uhci_td_maximum_length(descriptor))) {
                                        // all descriptors are done, or we have a short packet
                                        TRACE("td (0x%08" B_PRIx32 ") ok\n", descriptor->this_phy);
                                        callbackStatus = B_OK;
                                        transferDone = true;
                                        break;
                                }

                                descriptor = (uhci_td *)descriptor->link_log;
                        }

                        if (!transferDone) {
                                lastTransfer = transfer;
                                transfer = transfer->link;
                                continue;
                        }

                        // remove the transfer from the list first so we are sure
                        // it doesn't get canceled while we still process it
                        transfer_data *next = transfer->link;
                        if (Lock()) {
                                if (lastTransfer)
                                        lastTransfer->link = transfer->link;

                                if (transfer == fFirstTransfer)
                                        fFirstTransfer = transfer->link;
                                if (transfer == fLastTransfer)
                                        fLastTransfer = lastTransfer;

                                // store the currently processing pipe here so we can wait
                                // in cancel if we are processing something on the target pipe
                                if (!transfer->canceled)
                                        fProcessingPipe = transfer->transfer->TransferPipe();

                                transfer->link = NULL;
                                Unlock();
                        }

                        // if canceled the callback has already been called
                        if (!transfer->canceled) {
                                size_t actualLength = 0;
                                if (callbackStatus == B_OK) {
                                        uint8 lastDataToggle = 0;
                                        if (transfer->data_descriptor && transfer->incoming) {
                                                // data to read out
                                                generic_io_vec *vector = transfer->transfer->Vector();
                                                size_t vectorCount = transfer->transfer->VectorCount();

                                                transfer->transfer->PrepareKernelAccess();
                                                actualLength = ReadDescriptorChain(
                                                        transfer->data_descriptor,
                                                        vector, vectorCount, transfer->transfer->IsPhysical(),
                                                        &lastDataToggle);
                                        } else if (transfer->data_descriptor) {
                                                // read the actual length that was sent
                                                actualLength = ReadActualLength(
                                                        transfer->data_descriptor, &lastDataToggle);
                                        }

                                        transfer->transfer->TransferPipe()->SetDataToggle(lastDataToggle == 0);

                                        if (transfer->transfer->IsFragmented()) {
                                                // this transfer may still have data left
                                                TRACE("advancing fragmented transfer\n");
                                                transfer->transfer->AdvanceByFragment(actualLength);
                                                if (transfer->transfer->FragmentLength() > 0) {
                                                        TRACE("still %ld bytes left on transfer\n",
                                                                transfer->transfer->FragmentLength());

                                                        Transfer *resubmit = transfer->transfer;

                                                        // free the used descriptors
                                                        transfer->queue->RemoveTransfer(
                                                                transfer->transfer_queue);
                                                        AddToFreeList(transfer);

                                                        // resubmit the advanced transfer so the rest
                                                        // of the buffers are transmitted over the bus
                                                        resubmit->PrepareKernelAccess();
                                                        if (SubmitTransfer(resubmit) != B_OK)
                                                                resubmit->Finished(B_ERROR, 0);

                                                        transfer = next;
                                                        continue;
                                                }

                                                // the transfer is done, but we already set the
                                                // actualLength with AdvanceByFragment()
                                                actualLength = 0;
                                        }
                                }

                                transfer->transfer->Finished(callbackStatus, actualLength);
                                fProcessingPipe = NULL;
                        }

                        // remove and free the hardware queue and its descriptors
                        transfer->queue->RemoveTransfer(transfer->transfer_queue);
                        delete transfer->transfer;
                        AddToFreeList(transfer);
                        transfer = next;
                }
        }
}


void
UHCI::AddToFreeList(transfer_data *transfer)
{
        transfer->free_after_frame = ReadReg16(UHCI_FRNUM);
        if (!Lock())
                return;

        transfer->link = fFreeList;
        fFreeList = transfer;
        Unlock();

        if (atomic_add(&fCleanupCount, 1) == 0)
                release_sem(fCleanupSem);
}


int32
UHCI::CleanupThread(void *data)
{
        ((UHCI *)data)->Cleanup();
        return B_OK;
}


void
UHCI::Cleanup()
{
        while (!fStopThreads) {
                if (acquire_sem(fCleanupSem) != B_OK)
                        continue;

                bigtime_t nextTime = system_time() + 1000;
                while (atomic_get(&fCleanupCount) != 0) {
                        // wait for the frame to pass
                        snooze_until(nextTime, B_SYSTEM_TIMEBASE);
                        nextTime += 1000;

                        if (!Lock())
                                continue;

                        // find the first entry we may free
                        transfer_data **link = &fFreeList;
                        transfer_data *transfer = fFreeList;
                        uint16 frameNumber = ReadReg16(UHCI_FRNUM);
                        while (transfer) {
                                if (transfer->free_after_frame != frameNumber) {
                                        *link = NULL;
                                        break;
                                }

                                link = &transfer->link;
                                transfer = transfer->link;
                        }

                        Unlock();

                        // the transfers below this one are all freeable
                        while (transfer) {
                                transfer_data *next = transfer->link;
                                FreeDescriptorChain(transfer->first_descriptor);
                                FreeTransferQueue(transfer->transfer_queue);
                                delete transfer;
                                atomic_add(&fCleanupCount, -1);
                                transfer = next;
                        }
                }
        }
}


int32
UHCI::FinishIsochronousThread(void *data)
{
       ((UHCI *)data)->FinishIsochronousTransfers();
       return B_OK;
}


void
UHCI::FinishIsochronousTransfers()
{
        /* This thread stays one position behind the controller and processes every
         * isochronous descriptor. Once it finds the last isochronous descriptor
         * of a transfer, it processes the entire transfer.
         */

        while (!fStopThreads) {
                // Go to sleep if there are not isochronous transfer to process
                if (acquire_sem(fFinishIsochronousTransfersSem) < B_OK)
                        return;

                bool transferDone = false;
                uint16 currentFrame = ReadReg16(UHCI_FRNUM);

                // Process the frame list until one transfer is processed
                while (!transferDone) {
                        // wait 1ms in order to be sure to be one position behind
                        // the controller
                        if (currentFrame == ReadReg16(UHCI_FRNUM))
                                snooze(1000);

                        // Process the frame till it has isochronous descriptors in it.
                        while (!(fFrameList[currentFrame] & FRAMELIST_NEXT_IS_QH)) {
                                uhci_td *current = UnlinkIsochronousDescriptor(currentFrame);

                                // Process the transfer if we found the last descriptor
                                isochronous_transfer_data *transfer
                                        = FindIsochronousTransfer(current);
                                        // Process the descriptors only if it is still active and
                                        // belongs to an inbound transfer. If the transfer is not
                                        // active, it means the request has been removed, so simply
                                        // remove the descriptors.
                                if (transfer && transfer->is_active) {
                                        if (current->token & TD_TOKEN_IN) {
                                                generic_io_vec *vector = transfer->transfer->Vector();
                                                transfer->transfer->PrepareKernelAccess();
                                                ReadIsochronousDescriptorChain(transfer, vector);
                                        }

                                        // Remove the transfer
                                        if (LockIsochronous()) {
                                                if (transfer == fFirstIsochronousTransfer) {
                                                        fFirstIsochronousTransfer = transfer->link;
                                                        if (transfer == fLastIsochronousTransfer)
                                                                fLastIsochronousTransfer = NULL;
                                                } else {
                                                        isochronous_transfer_data *temp
                                                                = fFirstIsochronousTransfer;
                                                        while (transfer != temp->link)
                                                                temp = temp->link;

                                                        if (transfer == fLastIsochronousTransfer)
                                                                fLastIsochronousTransfer = temp;
                                                        temp->link = temp->link->link;
                                                }
                                                UnlockIsochronous();
                                        }

                                        transfer->transfer->Finished(B_OK, 0);

                                        uint32 packetCount =
                                                transfer->transfer->IsochronousData()->packet_count;
                                        for (uint32 i = 0; i < packetCount; i++)
                                                FreeDescriptor(transfer->descriptors[i]);

                                        delete [] transfer->descriptors;
                                        delete transfer->transfer;
                                        delete transfer;
                                        transferDone = true;
                                }
                        }

                        // Make sure to reset the frame bandwidth
                        fFrameBandwidth[currentFrame] = MAX_AVAILABLE_BANDWIDTH;
                        currentFrame = (currentFrame + 1) % NUMBER_OF_FRAMES;
                }
        }
}


void
UHCI::GlobalReset()
{
        uint8 sofValue = ReadReg8(UHCI_SOFMOD);

        WriteReg16(UHCI_USBCMD, UHCI_USBCMD_GRESET);
        snooze(100000);
        WriteReg16(UHCI_USBCMD, 0);
        snooze(10000);

        WriteReg8(UHCI_SOFMOD, sofValue);
}


status_t
UHCI::ControllerReset()
{
        WriteReg16(UHCI_USBCMD, UHCI_USBCMD_HCRESET);

        int32 tries = 5;
        while (ReadReg16(UHCI_USBCMD) & UHCI_USBCMD_HCRESET) {
                snooze(10000);
                if (tries-- < 0)
                        return B_ERROR;
        }

        return B_OK;
}


status_t
UHCI::GetPortStatus(uint8 index, usb_port_status *status)
{
        if (index > 1)
                return B_BAD_INDEX;

        status->status = status->change = 0;
        uint16 portStatus = ReadReg16(UHCI_PORTSC1 + index * 2);

        // build the status
        if (portStatus & UHCI_PORTSC_CURSTAT)
                status->status |= PORT_STATUS_CONNECTION;
        if (portStatus & UHCI_PORTSC_ENABLED)
                status->status |= PORT_STATUS_ENABLE;
        if (portStatus & UHCI_PORTSC_RESET)
                status->status |= PORT_STATUS_RESET;
        if (portStatus & UHCI_PORTSC_LOWSPEED)
                status->status |= PORT_STATUS_LOW_SPEED;

        // build the change
        if (portStatus & UHCI_PORTSC_STATCHA)
                status->change |= PORT_STATUS_CONNECTION;
        if (portStatus & UHCI_PORTSC_ENABCHA)
                status->change |= PORT_STATUS_ENABLE;

        // ToDo: work out suspended/resume

        // there are no bits to indicate reset change
        if (fPortResetChange & (1 << index))
                status->change |= PORT_STATUS_RESET;

        // the port is automagically powered on
        status->status |= PORT_STATUS_POWER;
        return B_OK;
}


status_t
UHCI::SetPortFeature(uint8 index, uint16 feature)
{
        if (index > 1)
                return B_BAD_INDEX;

        switch (feature) {
                case PORT_RESET:
                        return ResetPort(index);

                case PORT_POWER:
                        // the ports are automatically powered
                        return B_OK;
        }

        return B_BAD_VALUE;
}


status_t
UHCI::ClearPortFeature(uint8 index, uint16 feature)
{
        if (index > 1)
                return B_BAD_INDEX;

        uint32 portRegister = UHCI_PORTSC1 + index * 2;
        uint16 portStatus = ReadReg16(portRegister) & UHCI_PORTSC_DATAMASK;

        switch (feature) {
                case C_PORT_RESET:
                        fPortResetChange &= ~(1 << index);
                        return B_OK;

                case C_PORT_CONNECTION:
                        WriteReg16(portRegister, portStatus | UHCI_PORTSC_STATCHA);
                        return B_OK;

                case C_PORT_ENABLE:
                        WriteReg16(portRegister, portStatus | UHCI_PORTSC_ENABCHA);
                        return B_OK;
        }

        return B_BAD_VALUE;
}


status_t
UHCI::ResetPort(uint8 index)
{
        if (index > 1)
                return B_BAD_INDEX;

        TRACE("reset port %d\n", index);

        uint32 port = UHCI_PORTSC1 + index * 2;
        uint16 status = ReadReg16(port);
        status &= UHCI_PORTSC_DATAMASK;
        WriteReg16(port, status | UHCI_PORTSC_RESET);
        snooze(250000);

        status = ReadReg16(port);
        status &= UHCI_PORTSC_DATAMASK;
        WriteReg16(port, status & ~UHCI_PORTSC_RESET);
        snooze(1000);

        for (int32 i = 10; i > 0; i--) {
                // try to enable the port
                status = ReadReg16(port);
                status &= UHCI_PORTSC_DATAMASK;
                WriteReg16(port, status | UHCI_PORTSC_ENABLED);
                snooze(50000);

                status = ReadReg16(port);

                if ((status & UHCI_PORTSC_CURSTAT) == 0) {
                        // no device connected. since we waited long enough we can assume
                        // that the port was reset and no device is connected.
                        break;
                }

                if (status & (UHCI_PORTSC_STATCHA | UHCI_PORTSC_ENABCHA)) {
                        // port enabled changed or connection status were set.
                        // acknowledge either / both and wait again.
                        status &= UHCI_PORTSC_DATAMASK;
                        WriteReg16(port, status | UHCI_PORTSC_STATCHA | UHCI_PORTSC_ENABCHA);
                        continue;
                }

                if (status & UHCI_PORTSC_ENABLED) {
                        // the port is enabled
                        break;
                }
        }

        fPortResetChange |= (1 << index);
        TRACE("port was reset: 0x%04x\n", ReadReg16(port));
        return B_OK;
}


int32
UHCI::InterruptHandler(void *data)
{
        return ((UHCI *)data)->Interrupt();
}


int32
UHCI::Interrupt()
{
        static spinlock lock = B_SPINLOCK_INITIALIZER;
        acquire_spinlock(&lock);

        // Check if we really had an interrupt
        uint16 status = ReadReg16(UHCI_USBSTS);
        if ((status & fEnabledInterrupts) == 0) {
                if (status != 0) {
                        TRACE("discarding not enabled interrupts 0x%08x\n", status);
                        WriteReg16(UHCI_USBSTS, status);
                }

                release_spinlock(&lock);
                return B_UNHANDLED_INTERRUPT;
        }

        uint16 acknowledge = 0;
        bool finishTransfers = false;
        int32 result = B_HANDLED_INTERRUPT;

        if (status & UHCI_USBSTS_USBINT) {
                TRACE_MODULE("transfer finished\n");
                acknowledge |= UHCI_USBSTS_USBINT;
                result = B_INVOKE_SCHEDULER;
                finishTransfers = true;
        }

        if (status & UHCI_USBSTS_ERRINT) {
                TRACE_MODULE("transfer error\n");
                acknowledge |= UHCI_USBSTS_ERRINT;
                result = B_INVOKE_SCHEDULER;
                finishTransfers = true;
        }

        if (status & UHCI_USBSTS_RESDET) {
                TRACE_MODULE("resume detected\n");
                acknowledge |= UHCI_USBSTS_RESDET;
        }

        if (status & UHCI_USBSTS_HOSTERR) {
                TRACE_MODULE_ERROR("host system error\n");
                acknowledge |= UHCI_USBSTS_HOSTERR;
        }

        if (status & UHCI_USBSTS_HCPRERR) {
                TRACE_MODULE_ERROR("process error\n");
                acknowledge |= UHCI_USBSTS_HCPRERR;
        }

        if (status & UHCI_USBSTS_HCHALT) {
                TRACE_MODULE_ERROR("host controller halted\n");
                // at least disable interrupts so we do not flood the system
                WriteReg16(UHCI_USBINTR, 0);
                fEnabledInterrupts = 0;
                // ToDo: cancel all transfers and reset the host controller
                // acknowledge not needed
        }

        if (acknowledge)
                WriteReg16(UHCI_USBSTS, acknowledge);

        release_spinlock(&lock);

        if (finishTransfers)
                release_sem_etc(fFinishTransfersSem, 1, B_DO_NOT_RESCHEDULE);

        return result;
}


status_t
UHCI::CreateFilledTransfer(Transfer *transfer, uhci_td **_firstDescriptor,
        uhci_qh **_transferQueue)
{
        Pipe *pipe = transfer->TransferPipe();
        bool directionIn = (pipe->Direction() == Pipe::In);

        uhci_td *firstDescriptor = NULL;
        uhci_td *lastDescriptor = NULL;
        status_t result = CreateDescriptorChain(pipe, &firstDescriptor,
                &lastDescriptor, directionIn ? TD_TOKEN_IN : TD_TOKEN_OUT,
                transfer->FragmentLength());

        if (result < B_OK)
                return result;
        if (!firstDescriptor || !lastDescriptor)
                return B_NO_MEMORY;

        lastDescriptor->status |= TD_CONTROL_IOC;
        lastDescriptor->link_phy = TD_TERMINATE;
        lastDescriptor->link_log = NULL;

        if (!directionIn) {
                WriteDescriptorChain(firstDescriptor, transfer->Vector(),
                        transfer->VectorCount(), transfer->IsPhysical());
        }

        uhci_qh *transferQueue = CreateTransferQueue(firstDescriptor);
        if (!transferQueue) {
                FreeDescriptorChain(firstDescriptor);
                return B_NO_MEMORY;
        }

        *_firstDescriptor = firstDescriptor;
        *_transferQueue = transferQueue;
        return B_OK;
}


uhci_qh *
UHCI::CreateTransferQueue(uhci_td *descriptor)
{
        uhci_qh *queueHead;
        phys_addr_t physicalAddress;
        if (fStack->AllocateChunk((void **)&queueHead, &physicalAddress,
                sizeof(uhci_qh)) < B_OK)
                return NULL;

        queueHead->this_phy = (uint32)physicalAddress;
        queueHead->element_phy = descriptor->this_phy;
        return queueHead;
}


void
UHCI::FreeTransferQueue(uhci_qh *queueHead)
{
        if (!queueHead)
                return;

        fStack->FreeChunk(queueHead, queueHead->this_phy, sizeof(uhci_qh));
}


uhci_td *
UHCI::CreateDescriptor(Pipe *pipe, uint8 direction, size_t bufferSize)
{
        uhci_td *result;
        phys_addr_t physicalAddress;

        if (fStack->AllocateChunk((void **)&result, &physicalAddress,
                sizeof(uhci_td)) < B_OK) {
                TRACE_ERROR("failed to allocate a transfer descriptor\n");
                return NULL;
        }

        result->this_phy = (uint32)physicalAddress;
        result->status = TD_STATUS_ACTIVE;
        if (pipe->Type() & USB_OBJECT_ISO_PIPE)
                result->status |= TD_CONTROL_ISOCHRONOUS;
        else {
                result->status |= TD_CONTROL_3_ERRORS;
                if (direction == TD_TOKEN_IN)
                        result->status |= TD_CONTROL_SPD;
        }
        if (pipe->Speed() == USB_SPEED_LOWSPEED)
                result->status |= TD_CONTROL_LOWSPEED;

        result->buffer_size = bufferSize;
        if (bufferSize == 0)
                result->token = TD_TOKEN_NULL_DATA;
        else
                result->token = (bufferSize - 1) << TD_TOKEN_MAXLEN_SHIFT;

        result->token |= (pipe->EndpointAddress() << TD_TOKEN_ENDPTADDR_SHIFT)
                | (pipe->DeviceAddress() << 8) | direction;

        result->link_phy = 0;
        result->link_log = NULL;
        if (bufferSize <= 0) {
                result->buffer_log = NULL;
                result->buffer_phy = 0;
                return result;
        }

        if (fStack->AllocateChunk(&result->buffer_log, &physicalAddress,
                bufferSize) < B_OK) {
                TRACE_ERROR("unable to allocate space for the buffer\n");
                fStack->FreeChunk(result, result->this_phy, sizeof(uhci_td));
                return NULL;
        }
        result->buffer_phy = physicalAddress;

        return result;
}


status_t
UHCI::CreateDescriptorChain(Pipe *pipe, uhci_td **_firstDescriptor,
        uhci_td **_lastDescriptor, uint8 direction, size_t bufferSize)
{
        size_t packetSize = pipe->MaxPacketSize();
        int32 descriptorCount = (bufferSize + packetSize - 1) / packetSize;
        if (descriptorCount == 0)
                descriptorCount = 1;

        bool dataToggle = pipe->DataToggle();
        uhci_td *firstDescriptor = NULL;
        uhci_td *lastDescriptor = *_firstDescriptor;
        for (int32 i = 0; i < descriptorCount; i++) {
                uhci_td *descriptor = CreateDescriptor(pipe, direction,
                        min_c(packetSize, bufferSize));

                if (!descriptor) {
                        FreeDescriptorChain(firstDescriptor);
                        return B_NO_MEMORY;
                }

                if (dataToggle)
                        descriptor->token |= TD_TOKEN_DATA1;

                // link to previous
                if (lastDescriptor)
                        LinkDescriptors(lastDescriptor, descriptor);

                dataToggle = !dataToggle;
                bufferSize -= packetSize;
                lastDescriptor = descriptor;
                if (!firstDescriptor)
                        firstDescriptor = descriptor;
        }

        *_firstDescriptor = firstDescriptor;
        *_lastDescriptor = lastDescriptor;
        return B_OK;
}


void
UHCI::FreeDescriptor(uhci_td *descriptor)
{
        if (!descriptor)
                return;

        if (descriptor->buffer_log) {
                fStack->FreeChunk(descriptor->buffer_log,
                        descriptor->buffer_phy, descriptor->buffer_size);
        }

        fStack->FreeChunk(descriptor, descriptor->this_phy, sizeof(uhci_td));
}


void
UHCI::FreeDescriptorChain(uhci_td *topDescriptor)
{
        uhci_td *current = topDescriptor;
        uhci_td *next = NULL;

        while (current) {
                next = (uhci_td *)current->link_log;
                FreeDescriptor(current);
                current = next;
        }
}


void
UHCI::LinkDescriptors(uhci_td *first, uhci_td *second)
{
        first->link_phy = second->this_phy | TD_DEPTH_FIRST;
        first->link_log = second;
}


size_t
UHCI::WriteDescriptorChain(uhci_td *topDescriptor, generic_io_vec *vector,
        size_t vectorCount, bool physical)
{
        uhci_td *current = topDescriptor;
        size_t actualLength = 0;
        size_t vectorIndex = 0;
        size_t vectorOffset = 0;
        size_t bufferOffset = 0;

        while (current) {
                if (!current->buffer_log)
                        break;

                while (true) {
                        size_t length = min_c(current->buffer_size - bufferOffset,
                                vector[vectorIndex].length - vectorOffset);

                        TRACE("copying %ld bytes to bufferOffset %ld from"
                                " vectorOffset %ld at index %ld of %ld\n", length, bufferOffset,
                                vectorOffset, vectorIndex, vectorCount);
                        status_t status = generic_memcpy(
                                (generic_addr_t)current->buffer_log + bufferOffset, false,
                                vector[vectorIndex].base + vectorOffset, physical, length);
                        ASSERT_ALWAYS(status == B_OK);

                        actualLength += length;
                        vectorOffset += length;
                        bufferOffset += length;

                        if (vectorOffset >= vector[vectorIndex].length) {
                                if (++vectorIndex >= vectorCount) {
                                        TRACE("wrote descriptor chain (%ld bytes, no more vectors)\n",
                                                actualLength);
                                        return actualLength;
                                }

                                vectorOffset = 0;
                        }

                        if (bufferOffset >= current->buffer_size) {
                                bufferOffset = 0;
                                break;
                        }
                }

                if (current->link_phy & TD_TERMINATE)
                        break;

                current = (uhci_td *)current->link_log;
        }

        TRACE("wrote descriptor chain (%ld bytes)\n", actualLength);
        return actualLength;
}


size_t
UHCI::ReadDescriptorChain(uhci_td *topDescriptor, generic_io_vec *vector,
        size_t vectorCount, bool physical, uint8 *lastDataToggle)
{
        uint8 dataToggle = 0;
        uhci_td *current = topDescriptor;
        size_t actualLength = 0;
        size_t vectorIndex = 0;
        size_t vectorOffset = 0;
        size_t bufferOffset = 0;

        while (current && (current->status & TD_STATUS_ACTIVE) == 0) {
                if (!current->buffer_log)
                        break;

                dataToggle = (current->token >> TD_TOKEN_DATA_TOGGLE_SHIFT) & 0x01;
                size_t bufferSize = uhci_td_actual_length(current);

                while (true) {
                        size_t length = min_c(bufferSize - bufferOffset,
                                vector[vectorIndex].length - vectorOffset);

                        TRACE("copying %ld bytes to vectorOffset %ld from"
                                " bufferOffset %ld at index %ld of %ld\n", length, vectorOffset,
                                bufferOffset, vectorIndex, vectorCount);
                        status_t status = generic_memcpy(
                                vector[vectorIndex].base + vectorOffset, physical,
                                (generic_addr_t)current->buffer_log + bufferOffset, false, length);
                        ASSERT_ALWAYS(status == B_OK);

                        actualLength += length;
                        vectorOffset += length;
                        bufferOffset += length;

                        if (vectorOffset >= vector[vectorIndex].length) {
                                if (++vectorIndex >= vectorCount) {
                                        TRACE("read descriptor chain (%ld bytes, no more vectors)\n",
                                                actualLength);
                                        if (lastDataToggle)
                                                *lastDataToggle = dataToggle;
                                        return actualLength;
                                }

                                vectorOffset = 0;
                        }

                        if (bufferOffset >= bufferSize) {
                                bufferOffset = 0;
                                break;
                        }
                }

                if (current->link_phy & TD_TERMINATE)
                        break;

                current = (uhci_td *)current->link_log;
        }

        if (lastDataToggle)
                *lastDataToggle = dataToggle;

        TRACE("read descriptor chain (%ld bytes)\n", actualLength);
        return actualLength;
}


size_t
UHCI::ReadActualLength(uhci_td *topDescriptor, uint8 *lastDataToggle)
{
        size_t actualLength = 0;
        uhci_td *current = topDescriptor;
        uint8 dataToggle = 0;

        while (current && (current->status & TD_STATUS_ACTIVE) == 0) {
                actualLength += uhci_td_actual_length(current);
                dataToggle = (current->token >> TD_TOKEN_DATA_TOGGLE_SHIFT) & 0x01;

                if (current->link_phy & TD_TERMINATE)
                        break;

                current = (uhci_td *)current->link_log;
        }

        if (lastDataToggle)
                *lastDataToggle = dataToggle;

        TRACE("read actual length (%ld bytes)\n", actualLength);
        return actualLength;
}


void
UHCI::WriteIsochronousDescriptorChain(uhci_td **isoRequest, uint32 packetCount,
        generic_io_vec *vector)
{
        size_t vectorOffset = 0;
        for (uint32 i = 0; i < packetCount; i++) {
                size_t bufferSize = isoRequest[i]->buffer_size;
                memcpy((uint8 *)isoRequest[i]->buffer_log,
                        (uint8 *)vector->base + vectorOffset, bufferSize);
                vectorOffset += bufferSize;
        }
}


void
UHCI::ReadIsochronousDescriptorChain(isochronous_transfer_data *transfer,
        generic_io_vec *vector)
{
        size_t vectorOffset = 0;
        usb_isochronous_data *isochronousData
                = transfer->transfer->IsochronousData();

        for (uint32 i = 0; i < isochronousData->packet_count; i++) {
                uhci_td *current = transfer->descriptors[i];

                size_t bufferSize = current->buffer_size;
                size_t actualLength = uhci_td_actual_length(current);

                isochronousData->packet_descriptors[i].actual_length = actualLength;

                if (actualLength > 0)
                        isochronousData->packet_descriptors[i].status = B_OK;
                else {
                        isochronousData->packet_descriptors[i].status = B_ERROR;
                        vectorOffset += bufferSize;
                        continue;
                }
                memcpy((uint8 *)vector->base + vectorOffset,
                        (uint8 *)current->buffer_log, bufferSize);

                vectorOffset += bufferSize;
        }
}


bool
UHCI::LockIsochronous()
{
        return (mutex_lock(&fIsochronousLock) == B_OK);
}


void
UHCI::UnlockIsochronous()
{
        mutex_unlock(&fIsochronousLock);
}


inline void
UHCI::WriteReg8(uint32 reg, uint8 value)
{
        fPci->write_io_8(fDevice, fRegisterBase + reg, value);
}


inline void
UHCI::WriteReg16(uint32 reg, uint16 value)
{
        fPci->write_io_16(fDevice, fRegisterBase + reg, value);
}


inline void
UHCI::WriteReg32(uint32 reg, uint32 value)
{
        fPci->write_io_32(fDevice, fRegisterBase + reg, value);
}


inline uint8
UHCI::ReadReg8(uint32 reg)
{
        return fPci->read_io_8(fDevice, fRegisterBase + reg);
}


inline uint16
UHCI::ReadReg16(uint32 reg)
{
        return fPci->read_io_16(fDevice, fRegisterBase + reg);
}


inline uint32
UHCI::ReadReg32(uint32 reg)
{
        return fPci->read_io_32(fDevice, fRegisterBase + reg);
}