/* * Copyright 2004-2011, Haiku Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Michael Lotz * Niels S. Reedijk * Salvatore Benedetto */ #include #include #include #include #include #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; } if (!force) { // if the transfer is canceled by force, the one causing the // cancel is probably not the one who initiated the transfer // and the callback is likely not safe anymore 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; 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(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(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); }