Files
haiku-beta6/src/add-ons/kernel/busses/usb/ehci.cpp
T
Michael Lotz 00f6fab931 * Implemented a notification method for BusManagers so that they can be notified of pipe changes (creation, destruction, changed settings).
This is necessary in OHCI and will probably be used in EHCI also to keep one endpoint construct for each pipe open instead on creating and deleting it for each transfer.
* Pseudo implemented set_pipe_policy for isochronous pipes that makes use of the new notification system.

git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@18945 a95241bf-73f2-0310-859d-f6bbb57e9c96
2006-09-26 20:51:31 +00:00

1657 lines
42 KiB
C++

/*
* Copyright 2006, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Michael Lotz <[email protected]>
*/
#include <module.h>
#include <PCI.h>
#include <USB3.h>
#include <KernelExport.h>
#include "ehci.h"
pci_module_info *EHCI::sPCIModule = NULL;
static int32
ehci_std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
TRACE(("usb_ehci_module: init module\n"));
return B_OK;
case B_MODULE_UNINIT:
TRACE(("usb_ehci_module: uninit module\n"));
return B_OK;
}
return EINVAL;
}
host_controller_info ehci_module = {
{
"busses/usb/ehci",
0,
ehci_std_ops
},
NULL,
EHCI::AddTo
};
module_info *modules[] = {
(module_info *)&ehci_module,
NULL
};
//
// #pragma mark -
//
#ifdef TRACE_USB
void
print_descriptor_chain(ehci_qtd *descriptor)
{
while (descriptor) {
dprintf(" %08lx n%08lx a%08lx t%08lx %08lx %08lx %08lx %08lx %08lx s%ld\n",
descriptor->this_phy, descriptor->next_phy,
descriptor->alt_next_phy, descriptor->token,
descriptor->buffer_phy[0], descriptor->buffer_phy[1],
descriptor->buffer_phy[2], descriptor->buffer_phy[3],
descriptor->buffer_phy[4], descriptor->buffer_size);
if (descriptor->next_phy & EHCI_QTD_TERMINATE)
break;
descriptor = (ehci_qtd *)descriptor->next_log;
}
}
void
print_queue(ehci_qh *queueHead)
{
dprintf("queue: t%08lx n%08lx ch%08lx ca%08lx cu%08lx\n",
queueHead->this_phy, queueHead->next_phy, queueHead->endpoint_chars,
queueHead->endpoint_caps, queueHead->current_qtd_phy);
dprintf("overlay: n%08lx a%08lx t%08lx %08lx %08lx %08lx %08lx %08lx\n",
queueHead->overlay.next_phy, queueHead->overlay.alt_next_phy,
queueHead->overlay.token, queueHead->overlay.buffer_phy[0],
queueHead->overlay.buffer_phy[1], queueHead->overlay.buffer_phy[2],
queueHead->overlay.buffer_phy[3], queueHead->overlay.buffer_phy[4]);
print_descriptor_chain((ehci_qtd *)queueHead->element_log);
}
#endif // TRACE_USB
//
// #pragma mark -
//
EHCI::EHCI(pci_info *info, Stack *stack)
: BusManager(stack),
fPCIInfo(info),
fStack(stack),
fPeriodicFrameListArea(-1),
fPeriodicFrameList(NULL),
fFirstTransfer(NULL),
fLastTransfer(NULL),
fFinishThread(-1),
fCleanupThread(-1),
fStopThreads(false),
fFreeListHead(NULL),
fRootHub(NULL),
fRootHubAddress(0),
fPortCount(0),
fPortResetChange(0),
fPortSuspendChange(0)
{
if (BusManager::InitCheck() < B_OK) {
TRACE_ERROR(("usb_ehci: bus manager failed to init\n"));
return;
}
TRACE(("usb_ehci: constructing new EHCI Host Controller Driver\n"));
fInitOK = false;
// enable busmaster and memory mapped access
uint16 command = sPCIModule->read_pci_config(fPCIInfo->bus,
fPCIInfo->device, fPCIInfo->function, PCI_command, 2);
command &= ~PCI_command_io;
command |= PCI_command_master | PCI_command_memory;
sPCIModule->write_pci_config(fPCIInfo->bus, fPCIInfo->device,
fPCIInfo->function, PCI_command, 2, command);
// map the registers
uint32 offset = fPCIInfo->u.h0.base_registers[0] & (B_PAGE_SIZE - 1);
addr_t physicalAddress = fPCIInfo->u.h0.base_registers[0] - offset;
size_t mapSize = (fPCIInfo->u.h0.base_register_sizes[0] + offset
+ B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
TRACE(("usb_ehci: map physical memory 0x%08lx (base: 0x%08lx; offset: %lx); size: %ld\n", fPCIInfo->u.h0.base_registers[0], physicalAddress, offset, fPCIInfo->u.h0.base_register_sizes[0]));
fRegisterArea = map_physical_memory("EHCI memory mapped registers",
(void *)physicalAddress, mapSize, B_ANY_KERNEL_BLOCK_ADDRESS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_READ_AREA | B_WRITE_AREA,
(void **)&fCapabilityRegisters);
if (fRegisterArea < B_OK) {
TRACE(("usb_ehci: failed to map register memory\n"));
return;
}
fCapabilityRegisters += offset;
fOperationalRegisters = fCapabilityRegisters + ReadCapReg8(EHCI_CAPLENGTH);
TRACE(("usb_ehci: mapped capability registers: 0x%08lx\n", (uint32)fCapabilityRegisters));
TRACE(("usb_ehci: mapped operational registers: 0x%08lx\n", (uint32)fOperationalRegisters));
TRACE(("usb_ehci: structural parameters: 0x%08lx\n", ReadCapReg32(EHCI_HCSPARAMS)));
TRACE(("usb_ehci: capability parameters: 0x%08lx\n", ReadCapReg32(EHCI_HCCPARAMS)));
// read port count from capability register
fPortCount = ReadCapReg32(EHCI_HCSPARAMS) & 0x0f;
uint32 extendedCapPointer = ReadCapReg32(EHCI_HCCPARAMS) >> EHCI_ECP_SHIFT;
extendedCapPointer &= EHCI_ECP_MASK;
if (extendedCapPointer > 0) {
TRACE(("usb_ehci: extended capabilities register at %ld\n", extendedCapPointer));
uint32 legacySupport = sPCIModule->read_pci_config(fPCIInfo->bus,
fPCIInfo->device, fPCIInfo->function, extendedCapPointer, 4);
if ((legacySupport & EHCI_LEGSUP_CAPID_MASK) == EHCI_LEGSUP_CAPID) {
if (legacySupport & EHCI_LEGSUP_BIOSOWNED) {
TRACE(("usb_ehci: the host controller is bios owned\n"));
}
TRACE(("usb_ehci: claiming ownership of the host controller\n"));
sPCIModule->write_pci_config(fPCIInfo->bus, fPCIInfo->device,
fPCIInfo->function, extendedCapPointer, 4, EHCI_LEGSUP_OSOWNED);
for (int32 i = 0; i < 10; i++) {
legacySupport = sPCIModule->read_pci_config(fPCIInfo->bus,
fPCIInfo->device, fPCIInfo->function, extendedCapPointer, 4);
if (legacySupport & EHCI_LEGSUP_BIOSOWNED) {
TRACE(("usb_ehci: controller is still bios owned, waiting\n"));
snooze(50000);
} else
break;
}
if (legacySupport & EHCI_LEGSUP_BIOSOWNED) {
TRACE_ERROR(("usb_ehci: bios won't give up control over the host controller\n"));
return;
} else if (legacySupport & EHCI_LEGSUP_OSOWNED) {
TRACE(("usb_ehci: successfully took ownership of the host controller\n"));
}
} else {
TRACE(("usb_ehci: extended capability is not a legacy support register\n"));
}
} else {
TRACE(("usb_ehci: no extended capabilities register\n"));
}
// disable interrupts
WriteOpReg(EHCI_USBINTR, 0);
// reset the segment register
WriteOpReg(EHCI_CTRDSSEGMENT, 0);
// reset the host controller
if (ControllerReset() < B_OK) {
TRACE_ERROR(("usb_ehci: host controller failed to reset\n"));
return;
}
// create semaphores the finisher thread will wait for
fAsyncAdvanceSem = create_sem(0, "EHCI Async Advance");
fFinishTransfersSem = create_sem(0, "EHCI Finish Transfers");
fCleanupSem = create_sem(0, "EHCI Cleanup");
if (fFinishTransfersSem < B_OK || fAsyncAdvanceSem < B_OK
|| fCleanupSem < B_OK) {
TRACE_ERROR(("usb_ehci: failed to create semaphores\n"));
return;
}
// create finisher service thread
fFinishThread = spawn_kernel_thread(FinishThread, "ehci finish thread",
B_NORMAL_PRIORITY, (void *)this);
resume_thread(fFinishThread);
// create cleanup service thread
fCleanupThread = spawn_kernel_thread(CleanupThread, "ehci cleanup thread",
B_NORMAL_PRIORITY, (void *)this);
resume_thread(fCleanupThread);
// install the interrupt handler and enable interrupts
install_io_interrupt_handler(fPCIInfo->u.h0.interrupt_line,
InterruptHandler, (void *)this, 0);
WriteOpReg(EHCI_USBINTR, EHCI_USBINTR_HOSTSYSERR
| EHCI_USBINTR_USBERRINT | EHCI_USBINTR_USBINT | EHCI_USBINTR_INTONAA);
// allocate the periodic frame list
fPeriodicFrameListArea = fStack->AllocateArea((void **)&fPeriodicFrameList,
(void **)&physicalAddress, B_PAGE_SIZE, "USB EHCI Periodic Framelist");
if (fPeriodicFrameListArea < B_OK) {
TRACE_ERROR(("usb_ehci: unable to allocate periodic framelist\n"));
return;
}
// terminate all elements
for (int32 i = 0; i < 1024; i++)
fPeriodicFrameList[i] = EHCI_PFRAMELIST_TERM;
WriteOpReg(EHCI_PERIODICLISTBASE, (uint32)physicalAddress);
// allocate a queue head that will always stay in the async frame list
fAsyncQueueHead = CreateQueueHead();
if (!fAsyncQueueHead) {
TRACE_ERROR(("usb_ehci: unable to allocate stray async queue head\n"));
return;
}
fAsyncQueueHead->next_phy = fAsyncQueueHead->this_phy | EHCI_QH_TYPE_QH;
fAsyncQueueHead->next_log = fAsyncQueueHead;
fAsyncQueueHead->prev_log = fAsyncQueueHead;
fAsyncQueueHead->endpoint_chars = EHCI_QH_CHARS_EPS_HIGH | EHCI_QH_CHARS_RECHEAD;
fAsyncQueueHead->endpoint_caps = 1 << EHCI_QH_CAPS_MULT_SHIFT;
fAsyncQueueHead->current_qtd_phy = EHCI_QTD_TERMINATE;
fAsyncQueueHead->overlay.next_phy = EHCI_QTD_TERMINATE;
WriteOpReg(EHCI_ASYNCLISTADDR, (uint32)fAsyncQueueHead->this_phy
| EHCI_QH_TYPE_QH);
TRACE(("usb_ehci: set the async list addr to 0x%08lx\n", ReadOpReg(EHCI_ASYNCLISTADDR)));
fInitOK = true;
TRACE(("usb_ehci: EHCI Host Controller Driver constructed\n"));
}
EHCI::~EHCI()
{
TRACE(("usb_ehci: tear down EHCI Host Controller Driver\n"));
WriteOpReg(EHCI_USBCMD, 0);
WriteOpReg(EHCI_CONFIGFLAG, 0);
CancelAllPendingTransfers();
int32 result = 0;
fStopThreads = true;
delete_sem(fAsyncAdvanceSem);
delete_sem(fFinishTransfersSem);
wait_for_thread(fFinishThread, &result);
wait_for_thread(fCleanupThread, &result);
delete fRootHub;
delete_area(fPeriodicFrameListArea);
delete_area(fRegisterArea);
put_module(B_PCI_MODULE_NAME);
}
status_t
EHCI::Start()
{
TRACE(("usb_ehci: starting EHCI Host Controller\n"));
TRACE(("usb_ehci: usbcmd: 0x%08lx; usbsts: 0x%08lx\n", ReadOpReg(EHCI_USBCMD), ReadOpReg(EHCI_USBSTS)));
uint32 frameListSize = (ReadOpReg(EHCI_USBCMD) >> EHCI_USBCMD_FLS_SHIFT)
& EHCI_USBCMD_FLS_MASK;
WriteOpReg(EHCI_USBCMD, ReadOpReg(EHCI_USBCMD) | EHCI_USBCMD_RUNSTOP
| EHCI_USBCMD_ASENABLE /*| EHCI_USBCMD_PSENABLE*/
| (frameListSize << EHCI_USBCMD_FLS_SHIFT)
| (2 << EHCI_USBCMD_ITC_SHIFT));
// route all ports to us
WriteOpReg(EHCI_CONFIGFLAG, EHCI_CONFIGFLAG_FLAG);
bool running = false;
for (int32 i = 0; i < 10; i++) {
uint32 status = ReadOpReg(EHCI_USBSTS);
TRACE(("usb_ehci: try %ld: status 0x%08lx\n", i, status));
if (status & EHCI_USBSTS_HCHALTED) {
snooze(10000);
} else {
running = true;
break;
}
}
// set the interrupt threshold
WriteOpReg(EHCI_USBCMD, ReadOpReg(EHCI_USBCMD)
| (1 << EHCI_USBCMD_ITC_SHIFT));
if (!running) {
TRACE(("usb_ehci: Host Controller didn't start\n"));
return B_ERROR;
}
fRootHubAddress = AllocateAddress();
fRootHub = new(std::nothrow) EHCIRootHub(RootObject(), fRootHubAddress);
if (!fRootHub) {
TRACE_ERROR(("usb_ehci: no memory to allocate root hub\n"));
return B_NO_MEMORY;
}
if (fRootHub->InitCheck() < B_OK) {
TRACE_ERROR(("usb_ehci: root hub failed init check\n"));
return fRootHub->InitCheck();
}
SetRootHub(fRootHub);
TRACE(("usb_ehci: Host Controller started\n"));
return BusManager::Start();
}
status_t
EHCI::SubmitTransfer(Transfer *transfer)
{
// short circuit the root hub
if (transfer->TransferPipe()->DeviceAddress() == fRootHubAddress)
return fRootHub->ProcessTransfer(this, transfer);
uint32 type = transfer->TransferPipe()->Type();
if ((type & USB_OBJECT_CONTROL_PIPE) > 0
|| (type & USB_OBJECT_BULK_PIPE) > 0) {
TRACE(("usb_ehci: submitting async transfer\n"));
return SubmitAsyncTransfer(transfer);
}
if ((type & USB_OBJECT_INTERRUPT_PIPE) > 0
|| (type & USB_OBJECT_ISO_PIPE) > 0) {
TRACE(("usb_ehci: submitting periodic transfer\n"));
return SubmitPeriodicTransfer(transfer);
}
TRACE_ERROR(("usb_ehci: tried to submit transfer for unknown pipe type %lu\n", type));
return B_ERROR;
}
status_t
EHCI::SubmitAsyncTransfer(Transfer *transfer)
{
ehci_qh *queueHead = CreateQueueHead();
if (!queueHead) {
TRACE_ERROR(("usb_ehci: failed to allocate async queue head\n"));
return B_NO_MEMORY;
}
Pipe *pipe = transfer->TransferPipe();
switch (pipe->Speed()) {
case USB_SPEED_LOWSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_LOW;
break;
case USB_SPEED_FULLSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_FULL;
break;
case USB_SPEED_HIGHSPEED:
queueHead->endpoint_chars = EHCI_QH_CHARS_EPS_HIGH;
break;
default:
TRACE_ERROR(("usb_ehci: unknown pipe speed\n"));
FreeQueueHead(queueHead);
return B_ERROR;
}
if (pipe->Type() & USB_OBJECT_CONTROL_PIPE) {
queueHead->endpoint_chars |=
(pipe->Speed() != USB_SPEED_HIGHSPEED ? EHCI_QH_CHARS_CONTROL : 0);
}
queueHead->endpoint_chars |= (3 << EHCI_QH_CHARS_RL_SHIFT)
| (pipe->MaxPacketSize() << EHCI_QH_CHARS_MPL_SHIFT)
| (pipe->EndpointAddress() << EHCI_QH_CHARS_EPT_SHIFT)
| (pipe->DeviceAddress() << EHCI_QH_CHARS_DEV_SHIFT)
| EHCI_QH_CHARS_TOGGLE;
queueHead->endpoint_caps = (1 << EHCI_QH_CAPS_MULT_SHIFT)
| (0x1c << EHCI_QH_CAPS_SCM_SHIFT);
status_t result;
bool directionIn;
ehci_qtd *dataDescriptor;
if (pipe->Type() & USB_OBJECT_CONTROL_PIPE)
result = FillQueueWithRequest(transfer, queueHead, &dataDescriptor,
&directionIn);
else
result = FillQueueWithData(transfer, queueHead, &dataDescriptor,
&directionIn);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to fill transfer queue with data\n"));
FreeQueueHead(queueHead);
return result;
}
result = AddPendingTransfer(transfer, queueHead, dataDescriptor, directionIn);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to add pending transfer\n"));
FreeQueueHead(queueHead);
return result;
}
#ifdef TRACE_USB
TRACE(("usb_ehci: linking queue\n"));
print_queue(queueHead);
#endif
result = LinkQueueHead(queueHead);
if (result < B_OK) {
TRACE_ERROR(("usb_ehci: failed to link queue head to the async list\n"));
FreeQueueHead(queueHead);
return result;
}
return B_OK;
}
status_t
EHCI::SubmitPeriodicTransfer(Transfer *transfer)
{
return B_ERROR;
}
status_t
EHCI::NotifyPipeChange(Pipe *pipe, usb_change change)
{
TRACE_ERROR(("usb_ehci: pipe change %d for pipe 0x%08lx\n", change, (uint32)pipe));
switch (change) {
case USB_CHANGE_CREATED:
case USB_CHANGE_DESTROYED: {
// ToDo: we should create and keep a single queue head
// for all transfers to/from this pipe
break;
}
case USB_CHANGE_PIPE_POLICY_CHANGED: {
// ToDo: for isochronous pipes we might need to adapt to new
// pipe policy settings here
break;
}
}
return B_OK;
}
status_t
EHCI::AddTo(Stack *stack)
{
#ifdef TRACE_USB
set_dprintf_enabled(true);
load_driver_symbols("ehci");
#endif
if (!sPCIModule) {
status_t status = get_module(B_PCI_MODULE_NAME, (module_info **)&sPCIModule);
if (status < B_OK) {
TRACE_ERROR(("usb_ehci: getting pci module failed! 0x%08lx\n", status));
return status;
}
}
TRACE(("usb_ehci: searching devices\n"));
bool found = false;
pci_info *item = new(std::nothrow) pci_info;
if (!item) {
sPCIModule = NULL;
put_module(B_PCI_MODULE_NAME);
return B_NO_MEMORY;
}
for (int32 i = 0; sPCIModule->get_nth_pci_info(i, item) >= B_OK; i++) {
if (item->class_base == PCI_serial_bus && item->class_sub == PCI_usb
&& item->class_api == PCI_usb_ehci) {
if (item->u.h0.interrupt_line == 0
|| item->u.h0.interrupt_line == 0xFF) {
TRACE_ERROR(("usb_ehci: found device with invalid IRQ - check IRQ assignement\n"));
continue;
}
TRACE(("usb_ehci: found device at IRQ %u\n", item->u.h0.interrupt_line));
EHCI *bus = new(std::nothrow) EHCI(item, stack);
if (!bus) {
delete item;
sPCIModule = NULL;
put_module(B_PCI_MODULE_NAME);
return B_NO_MEMORY;
}
if (bus->InitCheck() < B_OK) {
TRACE_ERROR(("usb_ehci: bus failed init check\n"));
delete bus;
continue;
}
// the bus took it away
item = new(std::nothrow) pci_info;
bus->Start();
stack->AddBusManager(bus);
found = true;
}
}
if (!found) {
TRACE_ERROR(("usb_ehci: no devices found\n"));
delete item;
sPCIModule = NULL;
put_module(B_PCI_MODULE_NAME);
return ENODEV;
}
delete item;
return B_OK;
}
status_t
EHCI::GetPortStatus(uint8 index, usb_port_status *status)
{
if (index >= fPortCount)
return B_BAD_INDEX;
status->status = status->change = 0;
uint32 portStatus = ReadOpReg(EHCI_PORTSC + index * sizeof(uint32));
// build the status
if (portStatus & EHCI_PORTSC_CONNSTATUS)
status->status |= PORT_STATUS_CONNECTION;
if (portStatus & EHCI_PORTSC_ENABLE)
status->status |= PORT_STATUS_ENABLE;
if (portStatus & EHCI_PORTSC_ENABLE)
status->status |= PORT_STATUS_HIGH_SPEED;
if (portStatus & EHCI_PORTSC_OCACTIVE)
status->status |= PORT_STATUS_OVER_CURRENT;
if (portStatus & EHCI_PORTSC_PORTRESET)
status->status |= PORT_STATUS_RESET;
if (portStatus & EHCI_PORTSC_PORTPOWER)
status->status |= PORT_STATUS_POWER;
if (portStatus & EHCI_PORTSC_SUSPEND)
status->status |= PORT_STATUS_SUSPEND;
if (portStatus & EHCI_PORTSC_DMINUS)
status->status |= PORT_STATUS_LOW_SPEED;
// build the change
if (portStatus & EHCI_PORTSC_CONNCHANGE)
status->change |= PORT_STATUS_CONNECTION;
if (portStatus & EHCI_PORTSC_ENABLECHANGE)
status->change |= PORT_STATUS_ENABLE;
if (portStatus & EHCI_PORTSC_OCCHANGE)
status->change |= PORT_STATUS_OVER_CURRENT;
// there are no bits to indicate suspend and reset change
if (fPortResetChange & (1 << index))
status->change |= PORT_STATUS_RESET;
if (fPortSuspendChange & (1 << index))
status->change |= PORT_STATUS_SUSPEND;
return B_OK;
}
status_t
EHCI::SetPortFeature(uint8 index, uint16 feature)
{
if (index >= fPortCount)
return B_BAD_INDEX;
uint32 portRegister = EHCI_PORTSC + index * sizeof(uint32);
uint32 portStatus = ReadOpReg(portRegister) & EHCI_PORTSC_DATAMASK;
switch (feature) {
case PORT_SUSPEND:
return SuspendPort(index);
case PORT_RESET:
return ResetPort(index);
case PORT_POWER:
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_PORTPOWER);
return B_OK;
}
return B_BAD_VALUE;
}
status_t
EHCI::ClearPortFeature(uint8 index, uint16 feature)
{
if (index >= fPortCount)
return B_BAD_INDEX;
uint32 portRegister = EHCI_PORTSC + index * sizeof(uint32);
uint32 portStatus = ReadOpReg(portRegister) & EHCI_PORTSC_DATAMASK;
switch (feature) {
case PORT_ENABLE:
WriteOpReg(portRegister, portStatus & ~EHCI_PORTSC_ENABLE);
return B_OK;
case PORT_POWER:
WriteOpReg(portRegister, portStatus & ~EHCI_PORTSC_PORTPOWER);
return B_OK;
case C_PORT_CONNECTION:
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_CONNCHANGE);
return B_OK;
case C_PORT_ENABLE:
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_ENABLECHANGE);
return B_OK;
case C_PORT_OVER_CURRENT:
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_OCCHANGE);
return B_OK;
case C_PORT_RESET:
fPortResetChange &= ~(1 << index);
return B_OK;
case C_PORT_SUSPEND:
fPortSuspendChange &= ~(1 << index);
return B_OK;
}
return B_BAD_VALUE;
}
status_t
EHCI::ResetPort(uint8 index)
{
TRACE(("usb_ehci: reset port %d\n", index));
uint32 portRegister = EHCI_PORTSC + index * sizeof(uint32);
uint32 portStatus = ReadOpReg(portRegister) & EHCI_PORTSC_DATAMASK;
if (portStatus & EHCI_PORTSC_DMINUS) {
TRACE(("usb_ehci: lowspeed device connected, giving up port ownership\n"));
// there is a lowspeed device connected.
// we give the ownership to a companion controller.
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_PORTOWNER);
fPortResetChange |= (1 << index);
return B_OK;
}
// enable reset signaling
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_PORTRESET);
snooze(250000);
// disable reset signaling
portStatus = ReadOpReg(portRegister) & EHCI_PORTSC_DATAMASK;
WriteOpReg(portRegister, portStatus & ~EHCI_PORTSC_PORTRESET);
snooze(2000);
portStatus = ReadOpReg(portRegister) & EHCI_PORTSC_DATAMASK;
if (portStatus & EHCI_PORTSC_PORTRESET) {
TRACE(("usb_ehci: port reset won't complete\n"));
return B_ERROR;
}
if ((portStatus & EHCI_PORTSC_ENABLE) == 0) {
TRACE(("usb_ehci: fullspeed device connected, giving up port ownership\n"));
// the port was not enabled, this means that no high speed device is
// attached to this port. we give up ownership to a companion controler
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_PORTOWNER);
}
fPortResetChange |= (1 << index);
return B_OK;
}
status_t
EHCI::SuspendPort(uint8 index)
{
uint32 portRegister = EHCI_PORTSC + index * sizeof(uint32);
uint32 portStatus = ReadOpReg(portRegister) & EHCI_PORTSC_DATAMASK;
WriteOpReg(portRegister, portStatus | EHCI_PORTSC_SUSPEND);
fPortSuspendChange |= (1 << index);
return B_OK;
}
status_t
EHCI::ControllerReset()
{
// halt the controller first
WriteOpReg(EHCI_USBCMD, 0);
snooze(10000);
// then reset it
WriteOpReg(EHCI_USBCMD, EHCI_USBCMD_HCRESET);
int32 tries = 5;
while (ReadOpReg(EHCI_USBCMD) & EHCI_USBCMD_HCRESET) {
snooze(10000);
if (tries-- < 0)
return B_ERROR;
}
return B_OK;
}
status_t
EHCI::LightReset()
{
return B_ERROR;
}
int32
EHCI::InterruptHandler(void *data)
{
return ((EHCI *)data)->Interrupt();
}
int32
EHCI::Interrupt()
{
spinlock lock = 0;
acquire_spinlock(&lock);
// check if any interrupt was generated
uint32 status = ReadOpReg(EHCI_USBSTS);
if ((status & EHCI_USBSTS_INTMASK) == 0) {
release_spinlock(&lock);
return B_UNHANDLED_INTERRUPT;
}
uint32 acknowledge = 0;
bool asyncAdvance = false;
bool finishTransfers = false;
int32 result = B_HANDLED_INTERRUPT;
if (status & EHCI_USBSTS_USBINT) {
TRACE(("usb_ehci: transfer finished\n"));
acknowledge |= EHCI_USBSTS_USBINT;
result = B_INVOKE_SCHEDULER;
finishTransfers = true;
}
if (status & EHCI_USBSTS_USBERRINT) {
TRACE(("usb_ehci: transfer error\n"));
acknowledge |= EHCI_USBSTS_USBERRINT;
result = B_INVOKE_SCHEDULER;
finishTransfers = true;
}
if (status & EHCI_USBSTS_PORTCHANGE) {
TRACE(("usb_ehci: port change detected\n"));
acknowledge |= EHCI_USBSTS_PORTCHANGE;
}
if (status & EHCI_USBSTS_FLROLLOVER) {
TRACE(("usb_ehci: frame list rolled over\n"));
acknowledge |= EHCI_USBSTS_FLROLLOVER;
}
if (status & EHCI_USBSTS_INTONAA) {
TRACE(("usb_ehci: interrupt on async advance\n"));
acknowledge |= EHCI_USBSTS_INTONAA;
asyncAdvance = true;
result = B_INVOKE_SCHEDULER;
}
if (status & EHCI_USBSTS_HOSTSYSERR) {
TRACE_ERROR(("usb_ehci: host system error!\n"));
acknowledge |= EHCI_USBSTS_HOSTSYSERR;
}
if (acknowledge)
WriteOpReg(EHCI_USBSTS, acknowledge);
release_spinlock(&lock);
if (asyncAdvance)
release_sem_etc(fAsyncAdvanceSem, 1, B_DO_NOT_RESCHEDULE);
if (finishTransfers)
release_sem_etc(fFinishTransfersSem, 1, B_DO_NOT_RESCHEDULE);
return result;
}
status_t
EHCI::AddPendingTransfer(Transfer *transfer, ehci_qh *queueHead,
ehci_qtd *dataDescriptor, bool directionIn)
{
transfer_data *data = new(std::nothrow) transfer_data();
if (!data)
return B_NO_MEMORY;
data->transfer = transfer;
data->queue_head = queueHead;
data->data_descriptor = dataDescriptor;
data->user_area = -1;
data->incoming = directionIn;
data->link = NULL;
#ifndef HAIKU_TARGET_PLATFORM_HAIKU
if (directionIn) {
// we might need to access a buffer in userspace. this will not
// be possible in the kernel space finisher thread unless we
// get the proper area id for the space we need and then clone it
// before writing to it. this is of course terribly inefficient...
iovec *vector = transfer->Vector();
size_t vectorCount = transfer->VectorCount();
for (size_t i = 0; i < vectorCount; i++) {
if (IS_USER_ADDRESS(vector[i].iov_base)) {
data->user_area = area_for(vector[i].iov_base);
if (data->user_area < B_OK) {
TRACE_ERROR(("usb_ehci: failed to get area of userspace buffer\n"));
delete data;
return B_BAD_ADDRESS;
}
break;
}
}
if (data->user_area >= B_OK) {
area_info areaInfo;
if (get_area_info(data->user_area, &areaInfo) < B_OK) {
TRACE_ERROR(("usb_ehci: failed to get info about user area\n"));
delete data;
return B_BAD_ADDRESS;
}
for (size_t i = 0; i < vectorCount; i++) {
(uint8 *)vector[i].iov_base -= (uint8 *)areaInfo.address;
if ((size_t)vector[i].iov_base > areaInfo.size
|| (size_t)vector[i].iov_base + vector[i].iov_len > areaInfo.size) {
TRACE_ERROR(("usb_ehci: output data buffer spans across multiple areas!\n"));
delete data;
return B_BAD_ADDRESS;
}
}
}
}
#endif // !HAIKU_TARGET_PLATFORM_HAIKU
if (!Lock()) {
delete data;
return B_ERROR;
}
if (fLastTransfer)
fLastTransfer->link = data;
else
fFirstTransfer = data;
fLastTransfer = data;
Unlock();
return B_OK;
}
status_t
EHCI::CancelPendingTransfer(Transfer *transfer)
{
if (!Lock())
return B_ERROR;
transfer_data *last = NULL;
transfer_data *current = fFirstTransfer;
while (current) {
if (current->transfer == transfer) {
current->transfer->Finished(B_CANCELED, 0);
delete current->transfer;
if (last)
last->link = current->link;
else
fFirstTransfer = current->link;
if (fLastTransfer == current)
fLastTransfer = last;
delete current;
Unlock();
return B_OK;
}
last = current;
current = current->link;
}
Unlock();
return B_BAD_VALUE;
}
status_t
EHCI::CancelAllPendingTransfers()
{
if (!Lock())
return B_ERROR;
transfer_data *transfer = fFirstTransfer;
while (transfer) {
transfer->transfer->Finished(B_CANCELED, 0);
delete transfer->transfer;
transfer_data *next = transfer->link;
delete transfer;
transfer = next;
}
fFirstTransfer = NULL;
fLastTransfer = NULL;
Unlock();
return B_OK;
}
int32
EHCI::FinishThread(void *data)
{
((EHCI *)data)->FinishTransfers();
return B_OK;
}
void
EHCI::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(("usb_ehci: finishing transfers\n"));
transfer_data *lastTransfer = NULL;
transfer_data *transfer = fFirstTransfer;
Unlock();
while (transfer) {
bool transferDone = false;
ehci_qtd *descriptor = (ehci_qtd *)transfer->queue_head->element_log;
#ifdef TRACE_USB
print_queue(transfer->queue_head);
#endif
while (descriptor) {
uint32 status = descriptor->token;
if (status & EHCI_QTD_STATUS_ACTIVE) {
// still in progress
TRACE(("usb_ehci: qtd (0x%08lx) still active\n", descriptor->this_phy));
break;
}
if (status & EHCI_QTD_STATUS_ERRMASK) {
// a transfer error occured
TRACE_ERROR(("usb_ehci: qtd (0x%08lx) error: 0x%08lx\n", descriptor->this_phy, status));
status_t callbackStatus = B_ERROR;
uint8 errorCount = status >> EHCI_QTD_ERRCOUNT_SHIFT;
errorCount &= EHCI_QTD_ERRCOUNT_MASK;
if (errorCount == 0) {
// the error counter counted down to zero, report why
int32 reasons = 0;
if (status & EHCI_QTD_STATUS_BUFFER) {
callbackStatus = transfer->incoming ? B_DEV_DATA_OVERRUN : B_DEV_DATA_UNDERRUN;
reasons++;
}
if (status & EHCI_QTD_STATUS_TERROR) {
callbackStatus = B_DEV_CRC_ERROR;
reasons++;
}
if (reasons > 1)
callbackStatus = B_DEV_MULTIPLE_ERRORS;
} else if (status & EHCI_QTD_STATUS_BABBLE) {
// there is a babble condition
callbackStatus = transfer->incoming ? B_DEV_FIFO_OVERRUN : B_DEV_FIFO_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;
}
UnlinkQueueHead(transfer->queue_head, &fFreeListHead);
transfer->transfer->Finished(callbackStatus, 0);
transferDone = true;
break;
}
if (descriptor->next_phy & EHCI_QTD_TERMINATE) {
// we arrived at the last (stray) descriptor, we're done
TRACE(("usb_ehci: qtd (0x%08lx) done\n", descriptor->this_phy));
size_t actualLength = 0;
bool nextDataToggle = false;
if (transfer->data_descriptor && transfer->incoming) {
// data to read out
iovec *vector = transfer->transfer->Vector();
size_t vectorCount = transfer->transfer->VectorCount();
#ifndef HAIKU_TARGET_PLATFORM_HAIKU
area_id clonedArea = -1;
if (transfer->user_area >= B_OK) {
// we got a userspace output buffer, need to clone
// the area for that space first and map the iovecs
// to this cloned area.
void *clonedMemory = NULL;
clonedArea = clone_area("userspace accessor",
&clonedMemory, B_ANY_ADDRESS,
B_WRITE_AREA | B_KERNEL_WRITE_AREA,
transfer->user_area);
for (size_t i = 0; i < vectorCount; i++)
(uint8 *)vector[i].iov_base += (addr_t)clonedMemory;
}
#endif // !HAIKU_TARGET_PLATFORM_HAIKU
actualLength = ReadDescriptorChain(
transfer->data_descriptor,
vector, vectorCount,
&nextDataToggle);
#ifndef HAIKU_TARGET_PLATFORM_HAIKU
if (clonedArea >= B_OK)
delete_area(clonedArea);
#endif // !HAIKU_TARGET_PLATFORM_HAIKU
} else {
// calculate transfered length
actualLength = ReadActualLength(
(ehci_qtd *)transfer->queue_head->element_log,
&nextDataToggle);
}
UnlinkQueueHead(transfer->queue_head, &fFreeListHead);
transfer->transfer->TransferPipe()->SetDataToggle(nextDataToggle);
transfer->transfer->Finished(B_OK, actualLength);
transferDone = true;
break;
}
descriptor = (ehci_qtd *)descriptor->next_log;
}
if (transferDone) {
if (Lock()) {
if (lastTransfer)
lastTransfer->link = transfer->link;
if (transfer == fFirstTransfer)
fFirstTransfer = transfer->link;
if (transfer == fLastTransfer)
fLastTransfer = lastTransfer;
transfer_data *next = transfer->link;
delete transfer->transfer;
delete transfer;
transfer = next;
Unlock();
}
} else {
if (Lock()) {
lastTransfer = transfer;
transfer = transfer->link;
Unlock();
}
}
release_sem(fCleanupSem);
}
}
}
int32
EHCI::CleanupThread(void *data)
{
((EHCI *)data)->Cleanup();
return B_OK;
}
void
EHCI::Cleanup()
{
ehci_qh *lastFreeListHead = NULL;
while (!fStopThreads) {
if (acquire_sem(fCleanupSem) < B_OK)
continue;
ehci_qh *freeListHead = fFreeListHead;
if (freeListHead == lastFreeListHead)
continue;
// set the doorbell and wait for the host controller to notify us
WriteOpReg(EHCI_USBCMD, ReadOpReg(EHCI_USBCMD) | EHCI_USBCMD_INTONAAD);
if (acquire_sem(fAsyncAdvanceSem) < B_OK)
continue;
ehci_qh *current = freeListHead;
while (current != lastFreeListHead) {
ehci_qh *next = (ehci_qh *)current->next_log;
FreeQueueHead(current);
current = next;
}
lastFreeListHead = freeListHead;
}
}
ehci_qh *
EHCI::CreateQueueHead()
{
ehci_qh *result;
void *physicalAddress;
if (fStack->AllocateChunk((void **)&result, &physicalAddress,
sizeof(ehci_qh)) < B_OK) {
TRACE_ERROR(("usb_ehci: failed to allocate queue head\n"));
return NULL;
}
result->this_phy = (addr_t)physicalAddress;
result->next_phy = EHCI_QH_TERMINATE;
result->next_log = NULL;
result->prev_log = NULL;
ehci_qtd *descriptor = CreateDescriptor(0, 0);
if (!descriptor) {
TRACE_ERROR(("usb_ehci: failed to allocate initial qtd for queue head\n"));
fStack->FreeChunk(result, (void *)result->this_phy, sizeof(ehci_qh));
return NULL;
}
descriptor->token &= ~EHCI_QTD_STATUS_ACTIVE;
result->stray_log = descriptor;
result->element_log = descriptor;
result->current_qtd_phy = EHCI_QTD_TERMINATE;
result->overlay.next_phy = descriptor->this_phy;
result->overlay.alt_next_phy = EHCI_QTD_TERMINATE;
result->overlay.token = 0;
for (int32 i = 0; i < 5; i++) {
result->overlay.buffer_phy[i] = 0;
result->overlay.ext_buffer_phy[i] = 0;
}
return result;
}
void
EHCI::FreeQueueHead(ehci_qh *queueHead)
{
if (!queueHead)
return;
FreeDescriptorChain((ehci_qtd *)queueHead->element_log);
FreeDescriptor((ehci_qtd *)queueHead->stray_log);
fStack->FreeChunk(queueHead, (void *)queueHead->this_phy, sizeof(ehci_qh));
}
status_t
EHCI::LinkQueueHead(ehci_qh *queueHead)
{
if (!Lock())
return B_ERROR;
ehci_qh *prevHead = (ehci_qh *)fAsyncQueueHead->prev_log;
queueHead->next_phy = fAsyncQueueHead->this_phy | EHCI_QH_TYPE_QH;
queueHead->next_log = fAsyncQueueHead;
queueHead->prev_log = prevHead;
fAsyncQueueHead->prev_log = queueHead;
prevHead->next_log = queueHead;
prevHead->next_phy = queueHead->this_phy | EHCI_QH_TYPE_QH;
Unlock();
return B_OK;
}
status_t
EHCI::UnlinkQueueHead(ehci_qh *queueHead, ehci_qh **freeListHead)
{
if (!Lock())
return B_ERROR;
ehci_qh *prevHead = (ehci_qh *)queueHead->prev_log;
ehci_qh *nextHead = (ehci_qh *)queueHead->next_log;
prevHead->next_phy = queueHead->next_phy | EHCI_QH_TYPE_QH;
prevHead->next_log = queueHead->next_log;
nextHead->prev_log = queueHead->prev_log;
queueHead->next_phy = fAsyncQueueHead->this_phy | EHCI_QH_TYPE_QH;
queueHead->next_log = NULL;
queueHead->prev_log = NULL;
queueHead->next_log = *freeListHead;
*freeListHead = queueHead;
Unlock();
return B_OK;
}
status_t
EHCI::FillQueueWithRequest(Transfer *transfer, ehci_qh *queueHead,
ehci_qtd **_dataDescriptor, bool *_directionIn)
{
Pipe *pipe = transfer->TransferPipe();
usb_request_data *requestData = transfer->RequestData();
bool directionIn = (requestData->RequestType & USB_REQTYPE_DEVICE_IN) > 0;
ehci_qtd *setupDescriptor = CreateDescriptor(sizeof(usb_request_data),
EHCI_QTD_PID_SETUP);
ehci_qtd *statusDescriptor = CreateDescriptor(0,
directionIn ? EHCI_QTD_PID_OUT : EHCI_QTD_PID_IN);
if (!setupDescriptor || !statusDescriptor) {
TRACE_ERROR(("usb_ehci: failed to allocate descriptors\n"));
FreeDescriptor(setupDescriptor);
FreeDescriptor(statusDescriptor);
return B_NO_MEMORY;
}
iovec vector;
vector.iov_base = requestData;
vector.iov_len = sizeof(usb_request_data);
WriteDescriptorChain(setupDescriptor, &vector, 1);
ehci_qtd *strayDescriptor = (ehci_qtd *)queueHead->stray_log;
statusDescriptor->token |= EHCI_QTD_IOC | EHCI_QTD_DATA_TOGGLE;
ehci_qtd *dataDescriptor = NULL;
if (transfer->VectorCount() > 0) {
ehci_qtd *lastDescriptor = NULL;
status_t result = CreateDescriptorChain(pipe, &dataDescriptor,
&lastDescriptor, strayDescriptor, transfer->VectorLength(),
directionIn ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT);
if (result < B_OK) {
FreeDescriptor(setupDescriptor);
FreeDescriptor(statusDescriptor);
return result;
}
if (!directionIn) {
WriteDescriptorChain(dataDescriptor, transfer->Vector(),
transfer->VectorCount());
}
LinkDescriptors(setupDescriptor, dataDescriptor, strayDescriptor);
LinkDescriptors(lastDescriptor, statusDescriptor, strayDescriptor);
} else {
// no data: link setup and status descriptors directly
LinkDescriptors(setupDescriptor, statusDescriptor, strayDescriptor);
}
queueHead->element_log = setupDescriptor;
queueHead->overlay.next_phy = setupDescriptor->this_phy;
queueHead->overlay.alt_next_phy = EHCI_QTD_TERMINATE;
*_dataDescriptor = dataDescriptor;
*_directionIn = directionIn;
return B_OK;
}
status_t
EHCI::FillQueueWithData(Transfer *transfer, ehci_qh *queueHead,
ehci_qtd **_dataDescriptor, bool *_directionIn)
{
Pipe *pipe = transfer->TransferPipe();
bool directionIn = (pipe->Direction() == Pipe::In);
ehci_qtd *firstDescriptor = NULL;
ehci_qtd *lastDescriptor = NULL;
ehci_qtd *strayDescriptor = (ehci_qtd *)queueHead->stray_log;
status_t result = CreateDescriptorChain(pipe, &firstDescriptor,
&lastDescriptor, strayDescriptor, transfer->VectorLength(),
directionIn ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT);
if (result < B_OK)
return result;
lastDescriptor->token |= EHCI_QTD_IOC;
if (!directionIn) {
WriteDescriptorChain(firstDescriptor, transfer->Vector(),
transfer->VectorCount());
}
queueHead->element_log = firstDescriptor;
queueHead->overlay.next_phy = firstDescriptor->this_phy;
queueHead->overlay.alt_next_phy = EHCI_QTD_TERMINATE;
*_dataDescriptor = firstDescriptor;
*_directionIn = directionIn;
return B_OK;
}
ehci_qtd *
EHCI::CreateDescriptor(size_t bufferSize, uint8 pid)
{
ehci_qtd *result;
void *physicalAddress;
if (fStack->AllocateChunk((void **)&result, &physicalAddress,
sizeof(ehci_qtd)) < B_OK) {
TRACE_ERROR(("usb_ehci: failed to allocate a qtd\n"));
return NULL;
}
result->this_phy = (addr_t)physicalAddress;
result->next_phy = EHCI_QTD_TERMINATE;
result->next_log = NULL;
result->alt_next_phy = EHCI_QTD_TERMINATE;
result->alt_next_log = NULL;
result->buffer_size = bufferSize;
result->token = bufferSize << EHCI_QTD_BYTES_SHIFT;
result->token |= 3 << EHCI_QTD_ERRCOUNT_SHIFT;
result->token |= pid << EHCI_QTD_PID_SHIFT;
result->token |= EHCI_QTD_STATUS_ACTIVE;
if (bufferSize == 0) {
result->buffer_log = NULL;
for (int32 i = 0; i < 5; i++) {
result->buffer_phy[i] = 0;
result->ext_buffer_phy[i] = 0;
}
return result;
}
if (fStack->AllocateChunk(&result->buffer_log, &physicalAddress,
bufferSize) < B_OK) {
TRACE_ERROR(("usb_ehci: unable to allocate qtd buffer\n"));
fStack->FreeChunk(result, (void *)result->this_phy, sizeof(ehci_qtd));
return NULL;
}
addr_t physicalBase = (addr_t)physicalAddress;
result->buffer_phy[0] = physicalBase;
result->ext_buffer_phy[0] = 0;
for (int32 i = 1; i < 5; i++) {
physicalBase += B_PAGE_SIZE;
result->buffer_phy[i] = physicalBase & EHCI_QTD_PAGE_MASK;
result->ext_buffer_phy[i] = 0;
}
return result;
}
status_t
EHCI::CreateDescriptorChain(Pipe *pipe, ehci_qtd **_firstDescriptor,
ehci_qtd **_lastDescriptor, ehci_qtd *strayDescriptor, size_t bufferSize,
uint8 pid)
{
size_t packetSize = B_PAGE_SIZE * 4;
int32 descriptorCount = (bufferSize + packetSize - 1) / packetSize;
bool dataToggle = pipe->DataToggle();
ehci_qtd *firstDescriptor = NULL;
ehci_qtd *lastDescriptor = *_firstDescriptor;
for (int32 i = 0; i < descriptorCount; i++) {
ehci_qtd *descriptor = CreateDescriptor(min_c(packetSize, bufferSize),
pid);
if (!descriptor) {
FreeDescriptorChain(firstDescriptor);
return B_NO_MEMORY;
}
if (dataToggle)
descriptor->token |= EHCI_QTD_DATA_TOGGLE;
if (lastDescriptor)
LinkDescriptors(lastDescriptor, descriptor, strayDescriptor);
bufferSize -= packetSize;
lastDescriptor = descriptor;
if (!firstDescriptor)
firstDescriptor = descriptor;
}
*_firstDescriptor = firstDescriptor;
*_lastDescriptor = lastDescriptor;
return B_OK;
}
void
EHCI::FreeDescriptor(ehci_qtd *descriptor)
{
if (!descriptor)
return;
if (descriptor->buffer_log) {
fStack->FreeChunk(descriptor->buffer_log,
(void *)descriptor->buffer_phy[0], descriptor->buffer_size);
}
fStack->FreeChunk(descriptor, (void *)descriptor->this_phy, sizeof(ehci_qtd));
}
void
EHCI::FreeDescriptorChain(ehci_qtd *topDescriptor)
{
ehci_qtd *current = topDescriptor;
ehci_qtd *next = NULL;
while (current) {
next = (ehci_qtd *)current->next_log;
FreeDescriptor(current);
current = next;
}
}
void
EHCI::LinkDescriptors(ehci_qtd *first, ehci_qtd *last, ehci_qtd *alt)
{
first->next_phy = last->this_phy;
first->next_log = last;
if (alt) {
first->alt_next_phy = alt->this_phy;
first->alt_next_log = alt;
} else {
first->alt_next_phy = EHCI_QTD_TERMINATE;
first->alt_next_log = NULL;
}
}
size_t
EHCI::WriteDescriptorChain(ehci_qtd *topDescriptor, iovec *vector,
size_t vectorCount)
{
ehci_qtd *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].iov_len - vectorOffset);
memcpy((uint8 *)current->buffer_log + bufferOffset,
(uint8 *)vector[vectorIndex].iov_base + vectorOffset, length);
actualLength += length;
vectorOffset += length;
bufferOffset += length;
if (vectorOffset >= vector[vectorIndex].iov_len) {
if (++vectorIndex >= vectorCount) {
TRACE(("usb_ehci: wrote descriptor chain (%ld bytes, no more vectors)\n", actualLength));
return actualLength;
}
vectorOffset = 0;
}
if (bufferOffset >= current->buffer_size) {
bufferOffset = 0;
break;
}
}
if (current->next_phy & EHCI_QTD_TERMINATE)
break;
current = (ehci_qtd *)current->next_log;
}
TRACE(("usb_ehci: wrote descriptor chain (%ld bytes)\n", actualLength));
return actualLength;
}
size_t
EHCI::ReadDescriptorChain(ehci_qtd *topDescriptor, iovec *vector,
size_t vectorCount, bool *nextDataToggle)
{
uint32 dataToggle = 0;
ehci_qtd *current = topDescriptor;
size_t actualLength = 0;
size_t vectorIndex = 0;
size_t vectorOffset = 0;
size_t bufferOffset = 0;
while (current && (current->token & EHCI_QTD_STATUS_ACTIVE) == 0) {
if (!current->buffer_log)
break;
dataToggle = current->token & EHCI_QTD_DATA_TOGGLE;
size_t bufferSize = current->buffer_size;
bufferSize -= (current->token >> EHCI_QTD_BYTES_SHIFT) & EHCI_QTD_BYTES_MASK;
while (true) {
size_t length = min_c(bufferSize - bufferOffset,
vector[vectorIndex].iov_len - vectorOffset);
memcpy((uint8 *)vector[vectorIndex].iov_base + vectorOffset,
(uint8 *)current->buffer_log + bufferOffset, length);
actualLength += length;
vectorOffset += length;
bufferOffset += length;
if (vectorOffset >= vector[vectorIndex].iov_len) {
if (++vectorIndex >= vectorCount) {
TRACE(("usb_ehci: read descriptor chain (%ld bytes, no more vectors)\n", actualLength));
*nextDataToggle = dataToggle > 0 ? true : false;
return actualLength;
}
vectorOffset = 0;
}
if (bufferOffset >= bufferSize) {
bufferOffset = 0;
break;
}
}
if (current->next_phy & EHCI_QTD_TERMINATE)
break;
current = (ehci_qtd *)current->next_log;
}
TRACE(("usb_ehci: read descriptor chain (%ld bytes)\n", actualLength));
*nextDataToggle = dataToggle > 0 ? true : false;
return actualLength;
}
size_t
EHCI::ReadActualLength(ehci_qtd *topDescriptor, bool *nextDataToggle)
{
size_t actualLength = 0;
ehci_qtd *current = topDescriptor;
uint32 dataToggle = 0;
while (current && (current->token & EHCI_QTD_STATUS_ACTIVE) == 0) {
dataToggle = current->token & EHCI_QTD_DATA_TOGGLE;
size_t length = current->buffer_size;
length -= (current->token >> EHCI_QTD_BYTES_SHIFT) & EHCI_QTD_BYTES_MASK;
actualLength += length;
if (current->next_phy & EHCI_QTD_TERMINATE)
break;
current = (ehci_qtd *)current->next_log;
}
TRACE(("usb_ehci: read actual length (%ld bytes)\n", actualLength));
*nextDataToggle = dataToggle > 0 ? true : false;
return actualLength;
}
inline void
EHCI::WriteOpReg(uint32 reg, uint32 value)
{
*(volatile uint32 *)(fOperationalRegisters + reg) = value;
}
inline uint32
EHCI::ReadOpReg(uint32 reg)
{
return *(volatile uint32 *)(fOperationalRegisters + reg);
}
inline uint8
EHCI::ReadCapReg8(uint32 reg)
{
return *(volatile uint8 *)(fCapabilityRegisters + reg);
}
inline uint16
EHCI::ReadCapReg16(uint32 reg)
{
return *(volatile uint16 *)(fCapabilityRegisters + reg);
}
inline uint32
EHCI::ReadCapReg32(uint32 reg)
{
return *(volatile uint32 *)(fCapabilityRegisters + reg);
}