mirror of https://gitlab.com/nakst/essence
1287 lines
49 KiB
C++
1287 lines
49 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#include <module.h>
|
|
|
|
// TODO Babble error recovery.
|
|
|
|
#define SPEED_LOW (1)
|
|
#define SPEED_FULL (2)
|
|
#define SPEED_HIGH (3)
|
|
#define SPEED_SUPER (4)
|
|
|
|
#define COMMAND_RING_ENTRIES (255)
|
|
#define EVENT_RING_ENTRIES (252)
|
|
#define TRANSFER_RING_ENTRIES (255)
|
|
#define INPUT_CONTEXT_BYTES ((contextSize64 ? 64 : 32) * 33)
|
|
#define OUTPUT_CONTEXT_BYTES ((contextSize64 ? 64 : 32) * 32)
|
|
#define CONTEXT_INDEX(i) ((i) * (contextSize64 ? 16 : 8))
|
|
|
|
// Capability registers:
|
|
|
|
#define RD_REGISTER_CAPLENGTH() pci-> ReadBAR32(0, 0x00) // Capability register length and interface version number.
|
|
#define WR_REGISTER_CAPLENGTH(x) pci->WriteBAR32(0, 0x00, x)
|
|
#define RD_REGISTER_HCSPARAMS1() pci-> ReadBAR32(0, 0x04) // Structural parameters 1.
|
|
#define WR_REGISTER_HCSPARAMS1(x) pci->WriteBAR32(0, 0x04, x)
|
|
#define RD_REGISTER_HCSPARAMS2() pci-> ReadBAR32(0, 0x08) // Structural parameters 2.
|
|
#define WR_REGISTER_HCSPARAMS2(x) pci->WriteBAR32(0, 0x08, x)
|
|
#define RD_REGISTER_HCSPARAMS3() pci-> ReadBAR32(0, 0x0C) // Structural parameters 3.
|
|
#define WR_REGISTER_HCSPARAMS3(x) pci->WriteBAR32(0, 0x0C, x)
|
|
#define RD_REGISTER_HCCPARAMS1() pci-> ReadBAR32(0, 0x10) // Capability parameters 1.
|
|
#define WR_REGISTER_HCCPARAMS1(x) pci->WriteBAR32(0, 0x10, x)
|
|
#define RD_REGISTER_DBOFF() pci-> ReadBAR32(0, 0x14) // Doorbell offset.
|
|
#define WR_REGISTER_DBOFF(x) pci->WriteBAR32(0, 0x14, x)
|
|
#define RD_REGISTER_RTSOFF() pci-> ReadBAR32(0, 0x18) // Runtime register space offset.
|
|
#define WR_REGISTER_RTSOFF(x) pci->WriteBAR32(0, 0x18, x)
|
|
#define RD_REGISTER_HCCPARAMS2() pci-> ReadBAR32(0, 0x1C) // Capability parameters 2.
|
|
#define WR_REGISTER_HCCPARAMS2(x) pci->WriteBAR32(0, 0x1C, x)
|
|
|
|
// Host controller operational registers:
|
|
|
|
#define RD_REGISTER_USBCMD() pci-> ReadBAR32(0, operationalRegistersOffset + 0x00) // USB command.
|
|
#define WR_REGISTER_USBCMD(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x00, x)
|
|
#define RD_REGISTER_USBSTS() pci-> ReadBAR32(0, operationalRegistersOffset + 0x04) // USB status.
|
|
#define WR_REGISTER_USBSTS(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x04, x)
|
|
#define RD_REGISTER_PAGESIZE() pci-> ReadBAR32(0, operationalRegistersOffset + 0x08) // Page size.
|
|
#define WR_REGISTER_PAGESIZE(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x08, x)
|
|
#define RD_REGISTER_DNCTRL() pci-> ReadBAR32(0, operationalRegistersOffset + 0x14) // Device notification control.
|
|
#define WR_REGISTER_DNCTRL(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x14, x)
|
|
#define RD_REGISTER_CRCR() pci-> ReadBAR64(0, operationalRegistersOffset + 0x18) // Command ring control.
|
|
#define WR_REGISTER_CRCR(x) pci->WriteBAR64(0, operationalRegistersOffset + 0x18, x)
|
|
#define RD_REGISTER_DCBAAP() pci-> ReadBAR64(0, operationalRegistersOffset + 0x30) // Device context base address array pointer.
|
|
#define WR_REGISTER_DCBAAP(x) pci->WriteBAR64(0, operationalRegistersOffset + 0x30, x)
|
|
#define RD_REGISTER_CONFIG() pci-> ReadBAR32(0, operationalRegistersOffset + 0x38) // Configure.
|
|
#define WR_REGISTER_CONFIG(x) pci->WriteBAR32(0, operationalRegistersOffset + 0x38, x)
|
|
|
|
// Port register sets:
|
|
|
|
#define RD_REGISTER_PORTSC(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x400 + (n) * 0x10) // Port status and control.
|
|
#define WR_REGISTER_PORTSC(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x400 + (n) * 0x10, x)
|
|
#define RD_REGISTER_PORTPMSC(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x404 + (n) * 0x10) // Port port management status and control.
|
|
#define WR_REGISTER_PORTPMSC(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x404 + (n) * 0x10, x)
|
|
#define RD_REGISTER_PORTLI(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x408 + (n) * 0x10) // Port link info.
|
|
#define WR_REGISTER_PORTLI(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x408 + (n) * 0x10, x)
|
|
#define RD_REGISTER_PORTHLPMC(n) pci-> ReadBAR32(0, operationalRegistersOffset + 0x40C + (n) * 0x10) // Port hardware LPM control.
|
|
#define WR_REGISTER_PORTHLPMC(n, x) pci->WriteBAR32(0, operationalRegistersOffset + 0x40C + (n) * 0x10, x)
|
|
|
|
// Host controller runtime registers:
|
|
|
|
#define RD_REGISTER_MFINDEX() pci-> ReadBAR32(0, runtimeRegistersOffset + 0x00) // Microframe index.
|
|
#define WR_REGISTER_MFINDEX(x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x00, x)
|
|
|
|
// Interrupter register sets:
|
|
|
|
#define RD_REGISTER_IMAN(n) pci-> ReadBAR32(0, runtimeRegistersOffset + 0x20 + (n) * 0x20) // Interrupter management.
|
|
#define WR_REGISTER_IMAN(n, x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x20 + (n) * 0x20, x)
|
|
#define RD_REGISTER_IMOD(n) pci-> ReadBAR32(0, runtimeRegistersOffset + 0x24 + (n) * 0x20) // Interrupter moderation.
|
|
#define WR_REGISTER_IMOD(n, x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x24 + (n) * 0x20, x)
|
|
#define RD_REGISTER_ERSTSZ(n) pci-> ReadBAR32(0, runtimeRegistersOffset + 0x28 + (n) * 0x20) // Event ring segment table size.
|
|
#define WR_REGISTER_ERSTSZ(n, x) pci->WriteBAR32(0, runtimeRegistersOffset + 0x28 + (n) * 0x20, x)
|
|
#define RD_REGISTER_ERSTBA(n) pci-> ReadBAR64(0, runtimeRegistersOffset + 0x30 + (n) * 0x20) // Event ring segment table base address.
|
|
#define WR_REGISTER_ERSTBA(n, x) pci->WriteBAR64(0, runtimeRegistersOffset + 0x30 + (n) * 0x20, x)
|
|
#define RD_REGISTER_ERDP(n) pci-> ReadBAR64(0, runtimeRegistersOffset + 0x38 + (n) * 0x20) // Event ring dequeue pointer.
|
|
#define WR_REGISTER_ERDP(n, x) pci->WriteBAR64(0, runtimeRegistersOffset + 0x38 + (n) * 0x20, x)
|
|
|
|
// Doorbell registers:
|
|
|
|
#define RD_REGISTER_DOORBELL(n) pci-> ReadBAR32(0, doorbellsOffset + (n) * 0x04) // Doorbell.
|
|
#define WR_REGISTER_DOORBELL(n, x) pci->WriteBAR32(0, doorbellsOffset + (n) * 0x04, x)
|
|
|
|
struct XHCIDevice : KUSBDevice {
|
|
uintptr_t port;
|
|
};
|
|
|
|
struct XHCIEndpoint {
|
|
uint32_t *data;
|
|
uintptr_t physical;
|
|
|
|
uint32_t lastStatus;
|
|
uint16_t maximumPacketSize;
|
|
uint16_t index : 15, phase : 1;
|
|
|
|
KUSBTransferCallback callback;
|
|
EsGeneric context;
|
|
KAsyncTask callbackAsyncTask;
|
|
|
|
bool CreateTransferRing();
|
|
|
|
// TODO Detecting when the transfer ring is full.
|
|
void AdvanceTransferRingIndex();
|
|
};
|
|
|
|
struct XHCIPort {
|
|
bool usb2, statusChangeEvent, enabled;
|
|
uint8_t slotType, slotID, speed;
|
|
// uintptr_t maximumPacketSize;
|
|
|
|
XHCIEndpoint controlEndpoint;
|
|
volatile uint32_t *controlTransferResult;
|
|
volatile uintptr_t controlTransferLastTRBAddress;
|
|
KEvent controlTransferCompleteEvent;
|
|
|
|
XHCIEndpoint ioEndpoints[30];
|
|
|
|
uint32_t *outputContext;
|
|
uintptr_t outputContextPhysical;
|
|
|
|
XHCIDevice *device;
|
|
|
|
KMutex mutex;
|
|
};
|
|
|
|
struct XHCIController : KDevice {
|
|
KPCIDevice *pci;
|
|
|
|
uintptr_t operationalRegistersOffset,
|
|
extendedCapabilitiesOffset,
|
|
doorbellsOffset,
|
|
runtimeRegistersOffset;
|
|
size_t maximumDeviceSlots,
|
|
maximumInterrupters,
|
|
maximumPorts,
|
|
maximumEventRingSegments;
|
|
bool contextSize64;
|
|
|
|
uint64_t *deviceContextBaseAddressArray;
|
|
|
|
uint32_t *commandRing;
|
|
uintptr_t commandRingIndex;
|
|
bool commandRingPhase;
|
|
volatile uint32_t *commandResult;
|
|
KEvent commandCompleteEvent;
|
|
|
|
uint32_t *eventRing;
|
|
uintptr_t eventRingPhysical;
|
|
uintptr_t eventRingIndex;
|
|
bool eventRingPhase;
|
|
|
|
uint32_t *inputContext;
|
|
uintptr_t inputContextPhysical;
|
|
|
|
KEvent portStatusChangeEvent;
|
|
KSpinlock portResetSpinlock;
|
|
|
|
XHCIPort *ports;
|
|
|
|
void Initialise();
|
|
void DumpState();
|
|
bool HandleIRQ();
|
|
|
|
void OnPortEnable(uintptr_t port);
|
|
void OnPortDisable(uintptr_t port);
|
|
|
|
bool RunCommand(uint32_t *dw);
|
|
bool AddTransferDescriptors(uintptr_t buffer, size_t length, int operation, XHCIEndpoint *endpoint,
|
|
uint32_t trbType, bool interruptOnLast);
|
|
|
|
bool ControlTransfer(uintptr_t port, uint8_t flags, uint8_t request, uint16_t value, uint16_t index,
|
|
void *buffer, uint16_t length, int operation, uint16_t *transferred, bool alreadyLocked);
|
|
bool SelectConfigurationAndInterface(uintptr_t port, KUSBDevice *device);
|
|
bool QueueInterruptTransfer(uintptr_t portIndex, uint8_t endpointAddress, KUSBTransferCallback callback,
|
|
void *buffer, size_t bufferBytes, EsGeneric context);
|
|
};
|
|
|
|
const char *commandCompletionCodes[] = {
|
|
"Invalid",
|
|
"Success",
|
|
"Data buffer error",
|
|
"Babble detected error",
|
|
"USB transaction error",
|
|
"TRB error",
|
|
"Stall error",
|
|
"Resource error",
|
|
"Bandwidth error",
|
|
"No slot available error",
|
|
"Invalid stream type error",
|
|
"Slot not enabled error",
|
|
"Endpoint not enabled error",
|
|
"Short packet",
|
|
"Ring underrun",
|
|
"Ring overrun",
|
|
"VF event ring full error",
|
|
"Parameter error",
|
|
"Bandwidth overrun error",
|
|
"Context state error",
|
|
"No ping response error",
|
|
"Event ring full error",
|
|
"Incompatible device error",
|
|
"Missed service error",
|
|
"Command ring stopped",
|
|
"Command aborted",
|
|
"Stopped",
|
|
"Stopped - length invalid",
|
|
"Stopped - short packet",
|
|
"Max exit latency too large error",
|
|
"Reserved",
|
|
"Isochronous buffer overrun",
|
|
"Event lost error",
|
|
"Undefined error",
|
|
"Invalid stream ID error",
|
|
"Secondary bandwidth error",
|
|
"Split transcation error",
|
|
};
|
|
|
|
void XHCIController::DumpState() {
|
|
EsPrint("xHCI controller state:\n");
|
|
|
|
EsPrint("\t--- Registers ---\n");
|
|
|
|
EsPrint("\t\tCapability register length: %x.\n", RD_REGISTER_CAPLENGTH());
|
|
EsPrint("\t\tStructural parameters 1: %x.\n", RD_REGISTER_HCSPARAMS1());
|
|
EsPrint("\t\tStructural parameters 2: %x.\n", RD_REGISTER_HCSPARAMS2());
|
|
EsPrint("\t\tStructural parameters 3: %x.\n", RD_REGISTER_HCSPARAMS3());
|
|
EsPrint("\t\tCapability parameters 1: %x.\n", RD_REGISTER_HCCPARAMS1());
|
|
EsPrint("\t\tDoorbell offset: %x.\n", RD_REGISTER_DBOFF());
|
|
EsPrint("\t\tRuntime register space offset: %x.\n", RD_REGISTER_RTSOFF());
|
|
EsPrint("\t\tCapability parameters 2: %x.\n", RD_REGISTER_HCCPARAMS2());
|
|
|
|
EsPrint("\t\tUSB command: %x.\n", RD_REGISTER_USBCMD());
|
|
EsPrint("\t\tUSB status: %x.\n", RD_REGISTER_USBSTS());
|
|
EsPrint("\t\tPage size: %x.\n", RD_REGISTER_PAGESIZE());
|
|
EsPrint("\t\tDevice notification control: %x.\n", RD_REGISTER_DNCTRL());
|
|
EsPrint("\t\tCommand ring control: %x.\n", RD_REGISTER_CRCR());
|
|
EsPrint("\t\tDevice context base address array pointer (64-bit): %x.\n", RD_REGISTER_DCBAAP());
|
|
EsPrint("\t\tConfigure: %x.\n", RD_REGISTER_CONFIG());
|
|
|
|
for (uintptr_t i = 0; i < maximumPorts; i++) {
|
|
EsPrint("\t\tPort %d:\n", i);
|
|
EsPrint("\t\t\tPort status and control: %x.\n", RD_REGISTER_PORTSC(i));
|
|
EsPrint("\t\t\tPort port management status and control: %x.\n", RD_REGISTER_PORTPMSC(i));
|
|
EsPrint("\t\t\tPort link info: %x.\n", RD_REGISTER_PORTLI(i));
|
|
EsPrint("\t\t\tPort hardware LPM control: %x.\n", RD_REGISTER_PORTHLPMC(i));
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < maximumInterrupters; i++) {
|
|
EsPrint("\t\tInterrupter %d:\n", i);
|
|
EsPrint("\t\t\tInterrupter management: %x.\n", RD_REGISTER_IMAN(i));
|
|
EsPrint("\t\t\tInterrupter moderation: %x.\n", RD_REGISTER_IMOD(i));
|
|
EsPrint("\t\t\tEvent ring segment table size: %x.\n", RD_REGISTER_ERSTSZ(i));
|
|
EsPrint("\t\t\tEvent ring segment table base address: %x.\n", RD_REGISTER_ERSTBA(i));
|
|
EsPrint("\t\t\tEvent ring dequeue pointer: %x.\n", RD_REGISTER_ERDP(i));
|
|
}
|
|
}
|
|
|
|
bool XHCIEndpoint::CreateTransferRing() {
|
|
if (!MMPhysicalAllocateAndMap(16 * (TRANSFER_RING_ENTRIES + 1), 16, 0, true,
|
|
0, (uint8_t **) &data, &physical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the transfer ring.\n");
|
|
return false;
|
|
}
|
|
|
|
index = 0;
|
|
phase = 1;
|
|
|
|
data[TRANSFER_RING_ENTRIES * 4 + 0] = physical & 0xFFFFFFFF;
|
|
data[TRANSFER_RING_ENTRIES * 4 + 1] = (physical >> 32) & 0xFFFFFFFF;
|
|
data[TRANSFER_RING_ENTRIES * 4 + 3] = (1 << 1 /* toggle cycle */) | (6 /* link TRB */ << 10);
|
|
|
|
return true;
|
|
}
|
|
|
|
void XHCIEndpoint::AdvanceTransferRingIndex() {
|
|
if (phase) {
|
|
data[index * 4 + 3] |= 1 << 0;
|
|
}
|
|
|
|
index++;
|
|
|
|
if (index == TRANSFER_RING_ENTRIES) {
|
|
// Toggle cycle bit on link TRB.
|
|
data[TRANSFER_RING_ENTRIES * 4 + 3] ^= 1 << 0;
|
|
index = 0;
|
|
phase = phase ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
bool XHCIController::AddTransferDescriptors(uintptr_t buffer, size_t length, int operation, XHCIEndpoint *endpoint, uint32_t trbType, bool interruptOnLast) {
|
|
if (this->flags & K_DEVICE_REMOVED) {
|
|
return false;
|
|
}
|
|
|
|
uintptr_t position = 0;
|
|
|
|
while (position != length) {
|
|
uintptr_t count = K_PAGE_SIZE - ((buffer + position) & (K_PAGE_SIZE - 1));
|
|
bool last = count >= length - position;
|
|
if (last) count = length - position;
|
|
uintptr_t physical = MMArchTranslateAddress(MMGetKernelSpace(), buffer + position);
|
|
physical += (buffer + position) & (K_PAGE_SIZE - 1);
|
|
size_t remainingPackets = (length - position + endpoint->maximumPacketSize - 1) / endpoint->maximumPacketSize;
|
|
|
|
uint32_t *dw = endpoint->data + endpoint->index * 4;
|
|
|
|
dw[0] = (physical >> 0) & 0xFFFFFFFF;
|
|
dw[1] = (physical >> 32) & 0xFFFFFFFF;
|
|
dw[2] = count | ((last ? 0 : remainingPackets < 31 ? remainingPackets : 31) << 17);
|
|
|
|
dw[3] = trbType << 10;
|
|
if (operation == K_ACCESS_READ) dw[3] |= 1 << 16 /* input */;
|
|
if (last && interruptOnLast) dw[3] |= 1 << 5 /* interrupt on completion */;
|
|
if (!last) dw[3] |= 1 << 4 /* chain */;
|
|
|
|
endpoint->AdvanceTransferRingIndex();
|
|
position += count;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool XHCIController::ControlTransfer(uintptr_t portIndex, uint8_t flags, uint8_t request, uint16_t value, uint16_t index,
|
|
void *buffer, uint16_t length, int operation, uint16_t *transferred, bool alreadyLocked) {
|
|
XHCIPort *port = ports + portIndex;
|
|
|
|
if (!alreadyLocked) KMutexAcquire(&port->mutex);
|
|
EsDefer(if (!alreadyLocked) KMutexRelease(&port->mutex));
|
|
if (alreadyLocked) KMutexAssertLocked(&port->mutex);
|
|
|
|
if (this->flags & K_DEVICE_REMOVED) {
|
|
return false;
|
|
}
|
|
|
|
KEventReset(&port->controlTransferCompleteEvent);
|
|
|
|
uint32_t transferResult[4];
|
|
port->controlTransferResult = transferResult;
|
|
|
|
{
|
|
uint32_t *dw = port->controlEndpoint.data + port->controlEndpoint.index * 4;
|
|
dw[0] = (uint32_t) flags | ((uint32_t) request << 8) | ((uint32_t) value << 16);
|
|
dw[1] = (uint32_t) index | ((uint32_t) length << 16);
|
|
dw[2] = 8 /* transfer length */;
|
|
dw[3] = (1 << 6 /* immediate data */) | (2 /* setup TRB */ << 10);
|
|
|
|
if (length) {
|
|
dw[3] |= (operation == K_ACCESS_READ ? 3 : 2) << 16;
|
|
}
|
|
|
|
port->controlEndpoint.AdvanceTransferRingIndex();
|
|
}
|
|
|
|
if (!AddTransferDescriptors((uintptr_t) buffer, length, operation, &port->controlEndpoint, 3 /* data TRB */, false)) {
|
|
return false;
|
|
}
|
|
|
|
{
|
|
uint32_t *dw = port->controlEndpoint.data + port->controlEndpoint.index * 4;
|
|
port->controlTransferLastTRBAddress = port->controlEndpoint.physical + port->controlEndpoint.index * 16;
|
|
dw[0] = dw[1] = dw[2] = 0;
|
|
dw[3] = (1 << 5 /* interrupt on completion */) | (operation == K_ACCESS_WRITE ? (1 << 16) : 0) | (4 /* status TRB */ << 10);
|
|
port->controlEndpoint.AdvanceTransferRingIndex();
|
|
}
|
|
|
|
WR_REGISTER_DOORBELL(port->slotID, 1 /* control endpoint */);
|
|
|
|
if (!KEventWait(&port->controlTransferCompleteEvent, 1000)) {
|
|
// Timeout.
|
|
return false;
|
|
}
|
|
|
|
uint32_t status = transferResult[2];
|
|
uint8_t completionCode = (status >> 24) & 0xFF;
|
|
|
|
if (completionCode != 1) {
|
|
if (completionCode < sizeof(commandCompletionCodes) / sizeof(commandCompletionCodes[0])) {
|
|
KernelLog(LOG_ERROR, "xHCI", "failed control transfer", "Control transfer failed with completion code '%z'.\n", commandCompletionCodes[completionCode]);
|
|
} else {
|
|
KernelLog(LOG_ERROR, "xHCI", "failed control transfer", "Control transfer failed with unrecognised completion code %d.\n", completionCode);
|
|
}
|
|
|
|
if ((port->outputContext[CONTEXT_INDEX(1) + 0] & 7) == 2 /* halted */) {
|
|
// Reset the endpoint.
|
|
|
|
uint32_t dw[4] = {};
|
|
dw[3] = (port->slotID << 24) | (1 /* control endpoint */ << 16)
|
|
| (14 /* reset endpoint command */ << 10) | (0 /* reset transfer state */ << 9);
|
|
|
|
if (!RunCommand(dw)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "reset control endpoint failure", "Could not reset the control endpoint (1).\n");
|
|
// TODO Force detach the device.
|
|
return false;
|
|
}
|
|
|
|
// Reset the dequeue pointer.
|
|
|
|
port->controlEndpoint.index = 0;
|
|
port->controlEndpoint.phase = 1;
|
|
|
|
EsMemoryZero(port->controlEndpoint.data, 16 * (TRANSFER_RING_ENTRIES + 1));
|
|
|
|
dw[0] = (port->controlEndpoint.physical & 0xFFFFFFFF) | (1 << 0 /* phase bit */);
|
|
dw[1] = (port->controlEndpoint.physical >> 32) & 0xFFFFFFFF;
|
|
dw[2] = 0;
|
|
dw[3] = (port->slotID << 24) | (1 /* control endpoint */ << 16)
|
|
| (16 /* set TR dequeue pointer command */ << 10);
|
|
|
|
if (!RunCommand(dw)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "reset control endpoint failure", "Could not reset the control endpoint (2).\n");
|
|
// TODO Force detach the device.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (transferred) *transferred = length - (status & 0xFFFFFF);
|
|
return true;
|
|
}
|
|
|
|
bool XHCIController::SelectConfigurationAndInterface(uintptr_t portIndex, KUSBDevice *device) {
|
|
XHCIPort *port = ports + portIndex;
|
|
|
|
KMutexAcquire(&port->mutex);
|
|
EsDefer(KMutexRelease(&port->mutex));
|
|
|
|
if (this->flags & K_DEVICE_REMOVED) {
|
|
return false;
|
|
}
|
|
|
|
// Setup the input context.
|
|
|
|
EsMemoryZero(inputContext, INPUT_CONTEXT_BYTES);
|
|
|
|
uintptr_t lastIndex = 0;
|
|
|
|
for (uintptr_t i = 0; i < device->interfaceDescriptor.endpointCount; i++) {
|
|
KUSBEndpointDescriptor *descriptor = (KUSBEndpointDescriptor *) device->GetCommonDescriptor(5 /* endpoint */, i);
|
|
|
|
if (!descriptor) {
|
|
KernelLog(LOG_ERROR, "xHCI", "endpoint descriptor missing", "Could not find endpoint descriptor %d (%d total).\n",
|
|
i, device->interfaceDescriptor.endpointCount);
|
|
return false;
|
|
}
|
|
|
|
KUSBEndpointCompanionDescriptor *companion = nullptr;
|
|
KUSBEndpointIsochronousCompanionDescriptor *isochronousCompanion = nullptr;
|
|
|
|
if (port->speed == SPEED_SUPER) {
|
|
companion = (KUSBEndpointCompanionDescriptor *) ((uint8_t *) descriptor + descriptor->length);
|
|
|
|
if (companion->length < sizeof(KUSBEndpointCompanionDescriptor)
|
|
|| (size_t) ((uint8_t *) companion + companion->length - device->configurationDescriptors) > device->configurationDescriptorsBytes
|
|
|| companion->descriptorType != 48 /* superspeed endpoint companion */) {
|
|
companion = nullptr;
|
|
} else if (companion->HasISOCompanion()) {
|
|
isochronousCompanion = (KUSBEndpointIsochronousCompanionDescriptor *) ((uint8_t *) companion + companion->length);
|
|
|
|
if (isochronousCompanion->length < sizeof(KUSBEndpointCompanionDescriptor)
|
|
|| (size_t) ((uint8_t *) isochronousCompanion + isochronousCompanion->length - device->configurationDescriptors)
|
|
> device->configurationDescriptorsBytes
|
|
|| isochronousCompanion->descriptorType != 49 /* superspeed isochronous endpoint companion */) {
|
|
isochronousCompanion = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (descriptor->IsControl()) {
|
|
// Already enabled.
|
|
continue;
|
|
}
|
|
|
|
uint32_t maximumBurst = companion ? companion->maxBurst : descriptor->IsBulk() ? 0 : ((descriptor->maximumPacketSize & 0x1800) >> 11);
|
|
uint32_t maximumPacketSize = descriptor->GetMaximumPacketSize();
|
|
uint32_t maximumESITPayload = descriptor->IsBulk() ? 0 : (companion
|
|
? (isochronousCompanion ? isochronousCompanion->bytesPerInterval : companion->bytesPerInterval)
|
|
: (maximumPacketSize * (maximumBurst + 1)));
|
|
|
|
uintptr_t index = (descriptor->IsInput() ? 2 : 1) + descriptor->GetAddress() * 2;
|
|
inputContext[CONTEXT_INDEX(0) + 1] |= 1 << (index - 1);
|
|
if (lastIndex < index - 1) lastIndex = index - 1;
|
|
|
|
XHCIEndpoint *endpoint = port->ioEndpoints + index - 3;
|
|
|
|
endpoint->maximumPacketSize = maximumPacketSize;
|
|
|
|
if (!endpoint->CreateTransferRing()) {
|
|
return false;
|
|
}
|
|
|
|
// See "4.8.2 Endpoint Context Initialization".
|
|
inputContext[CONTEXT_INDEX(index) + 0] = 0;
|
|
inputContext[CONTEXT_INDEX(index) + 1] = (maximumBurst << 8) | (maximumPacketSize << 16) | (((maximumESITPayload >> 16) & 0xFF) << 24);
|
|
inputContext[CONTEXT_INDEX(index) + 2] = (1 << 0 /* phase */) | (endpoint->physical & 0xFFFFFFFF);
|
|
inputContext[CONTEXT_INDEX(index) + 3] = ((endpoint->physical >> 32) & 0xFFFFFFFF);
|
|
inputContext[CONTEXT_INDEX(index) + 4] = ((maximumESITPayload & 0xFFFF)) << 16;
|
|
|
|
if (descriptor->IsInterrupt()) {
|
|
inputContext[CONTEXT_INDEX(index) + 1] |= (3 /* error count */ << 1) | ((descriptor->IsInput() ? 7 : 3) << 3 /* endpoint type */);
|
|
} else if (descriptor->IsBulk() && (!companion || !companion->GetMaximumStreams())) {
|
|
inputContext[CONTEXT_INDEX(index) + 1] |= (3 /* error count */ << 1) | ((descriptor->IsInput() ? 6 : 2) << 3 /* endpoint type */);
|
|
} else {
|
|
// TODO Isochronous and stream bulk endpoints.
|
|
KernelLog(LOG_ERROR, "xHCI", "unsupported endpoint type", "Isochronous and bulk endpoints are currently unsupported.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inputContext[CONTEXT_INDEX(0) + 1] |= 1 << 0 /* slot context */;
|
|
|
|
// See "4.5.2 Slot Context Initialization".
|
|
inputContext[CONTEXT_INDEX(1) + 0] = lastIndex << 27;
|
|
|
|
// Send the configure endpoint command.
|
|
|
|
{
|
|
uint32_t dw[4];
|
|
|
|
dw[0] = (inputContextPhysical >> 0) & 0xFFFFFFFF;
|
|
dw[1] = (inputContextPhysical >> 32) & 0xFFFFFFFF;
|
|
dw[2] = 0;
|
|
dw[3] = (port->slotID << 24) | (12 /* configure endpoint command */ << 10);
|
|
|
|
if (!RunCommand(dw)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "configure endpoint failure", "The configure endpoint command failed.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set the configuration.
|
|
|
|
return ControlTransfer(portIndex, 0, 0x09 /* set configuration */,
|
|
device->configurationDescriptor.configurationIndex, 0,
|
|
nullptr, 0, K_ACCESS_WRITE, nullptr, true /* already locked */);
|
|
}
|
|
|
|
bool XHCIController::QueueInterruptTransfer(uintptr_t portIndex, uint8_t endpointAddress, KUSBTransferCallback callback,
|
|
void *buffer, size_t bufferBytes, EsGeneric context) {
|
|
XHCIPort *port = ports + portIndex;
|
|
|
|
KMutexAcquire(&port->mutex);
|
|
EsDefer(KMutexRelease(&port->mutex));
|
|
|
|
if (this->flags & K_DEVICE_REMOVED) {
|
|
return false;
|
|
}
|
|
|
|
XHCIEndpoint *endpoint = port->ioEndpoints + endpointAddress - 2;
|
|
endpoint->callback = callback;
|
|
endpoint->context = context;
|
|
|
|
if (!AddTransferDescriptors((uintptr_t) buffer, bufferBytes, (endpointAddress & 1) ? K_ACCESS_READ : K_ACCESS_WRITE,
|
|
endpoint, 1 /* normal TRB */, true)) {
|
|
return false;
|
|
}
|
|
|
|
WR_REGISTER_DOORBELL(port->slotID, endpointAddress);
|
|
return true;
|
|
}
|
|
|
|
bool XHCIController::HandleIRQ() {
|
|
// Clear interrupt status.
|
|
|
|
uint32_t usbStatus = RD_REGISTER_USBSTS();
|
|
WR_REGISTER_USBSTS((1 << 3) | (1 << 4));
|
|
|
|
// Check for an interrupt.
|
|
|
|
uint32_t status = RD_REGISTER_IMAN(0);
|
|
|
|
KernelLog(LOG_VERBOSE, "xHCI", "IRQ", "Received IRQ. USB status: %x. Interrupter status: %x.\n", usbStatus, status);
|
|
|
|
// Acknowledge the interrupt.
|
|
|
|
WR_REGISTER_IMAN(0, status);
|
|
RD_REGISTER_IMAN(0); // Read the value back.
|
|
|
|
// Consume events.
|
|
|
|
while (true) {
|
|
uint32_t dw0 = eventRing[0x10 + 0x04 * eventRingIndex + 0x00];
|
|
uint32_t dw1 = eventRing[0x10 + 0x04 * eventRingIndex + 0x01];
|
|
uint32_t dw2 = eventRing[0x10 + 0x04 * eventRingIndex + 0x02];
|
|
uint32_t dw3 = eventRing[0x10 + 0x04 * eventRingIndex + 0x03];
|
|
|
|
if ((dw3 & (1 << 0)) != eventRingPhase) {
|
|
break;
|
|
}
|
|
|
|
uint32_t type = (dw3 >> 10) & 0x3F;
|
|
uint8_t completionCode = (dw2 >> 24) & 0xFF;
|
|
|
|
KernelLog(LOG_VERBOSE, "xHCI", "got event", "Received event of type %d with code %d from %x.\n",
|
|
type, completionCode, (uint64_t) dw0 | ((uint64_t) dw1 << 32));
|
|
|
|
if (type == 32 /* transfer completion event */) {
|
|
uint8_t slotID = (dw3 >> 24) & 0xFF;
|
|
uint8_t endpointID = (dw3 >> 16) & 0x1F;
|
|
|
|
if (endpointID == 1) {
|
|
for (uintptr_t i = 0; i < maximumPorts; i++) {
|
|
XHCIPort *port = ports + i;
|
|
if (port->slotID != slotID) continue;
|
|
if (!port->enabled) break;
|
|
|
|
if (!port->controlTransferResult) {
|
|
KernelLog(LOG_ERROR, "xHCI", "spurious transfer completion event",
|
|
"Received an unexpected transfer command completion event on port %d. "
|
|
"Contents: %x, %x, %x, %x.\n", i, dw0, dw1, dw2, dw3);
|
|
} else {
|
|
bool lastTransfer = dw0 == (port->controlTransferLastTRBAddress & 0xFFFFFFFF)
|
|
&& dw1 == ((port->controlTransferLastTRBAddress >> 32) & 0xFFFFFFFF);
|
|
|
|
if (completionCode != 1 || lastTransfer) {
|
|
port->controlTransferResult[0] = dw0;
|
|
port->controlTransferResult[1] = dw1;
|
|
port->controlTransferResult[2] = dw2;
|
|
port->controlTransferResult[3] = dw3;
|
|
KEventSet(&port->controlTransferCompleteEvent);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
} else if (endpointID >= 2) {
|
|
for (uintptr_t i = 0; i < maximumPorts; i++) {
|
|
XHCIPort *port = ports + i;
|
|
if (port->slotID != slotID) continue;
|
|
if (!port->enabled) break;
|
|
XHCIEndpoint *endpoint = port->ioEndpoints + endpointID - 2;
|
|
endpoint->lastStatus = dw2;
|
|
|
|
if (completionCode != 1) {
|
|
if (completionCode < sizeof(commandCompletionCodes) / sizeof(commandCompletionCodes[0])) {
|
|
KernelLog(LOG_ERROR, "xHCI", "failed transfer", "Transfer failed with completion code '%z' (%x).\n",
|
|
commandCompletionCodes[completionCode], dw2);
|
|
} else {
|
|
KernelLog(LOG_ERROR, "xHCI", "failed transfer", "Transfer failed with unrecognised completion code %d.\n", completionCode);
|
|
}
|
|
}
|
|
|
|
if (endpoint->callback) {
|
|
KRegisterAsyncTask(&endpoint->callbackAsyncTask, [] (KAsyncTask *task) {
|
|
XHCIEndpoint *endpoint = EsContainerOf(XHCIEndpoint, callbackAsyncTask, task);
|
|
KUSBTransferCallback callback = endpoint->callback;
|
|
endpoint->callback = nullptr;
|
|
callback((endpoint->lastStatus >> 24) != 1 ? -1 : endpoint->lastStatus & 0xFFFFFF, endpoint->context);
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
} else if (type == 33 /* command completion event */) {
|
|
if (!commandResult) {
|
|
KernelLog(LOG_ERROR, "xHCI", "spurious command completion event",
|
|
"Received a spurious command completion event (no commands issued since last expected received completion event). "
|
|
"Contents: %x, %x, %x, %x.\n", dw0, dw1, dw2, dw3);
|
|
} else {
|
|
commandResult[0] = dw0;
|
|
commandResult[1] = dw1;
|
|
commandResult[2] = dw2;
|
|
commandResult[3] = dw3;
|
|
KEventSet(&commandCompleteEvent);
|
|
}
|
|
} else if (type == 34 /* port status change event */) {
|
|
uintptr_t port = (dw0 >> 24) - 1;
|
|
|
|
KSpinlockAcquire(&portResetSpinlock);
|
|
|
|
// Acknowledge the status change.
|
|
|
|
uint32_t status = RD_REGISTER_PORTSC(port);
|
|
uint32_t linkState = (status >> 5) & 0x0F;
|
|
WR_REGISTER_PORTSC(port, ((1 << 9) | (1 << 17) | (1 << 18) | (1 << 19) | (1 << 20) | (1 << 21) | (1 << 22) | (1 << 23)) & status);
|
|
KernelLog(LOG_INFO, "xHCI", "port status change", "Port %d has new status: %x.\n", port, status);
|
|
|
|
if (!ports[port].enabled && (status & (1 << 1 /* port enabled */))) {
|
|
KernelLog(LOG_INFO, "xHCI", "port enabled", "Port %d has been enabled.\n", port);
|
|
ports[port].statusChangeEvent = true;
|
|
ports[port].enabled = true;
|
|
KEventSet(&portStatusChangeEvent, true);
|
|
} else if (ports[port].usb2 && (linkState == 7 || linkState == 4) && (~status & (1 << 4))) {
|
|
KernelLog(LOG_INFO, "xHCI", "port reset", "Attempting to reset USB 2 port %d... (1)\n", port);
|
|
WR_REGISTER_PORTSC(port, (status & (1 << 9)) | (1 << 4));
|
|
} else if (ports[port].enabled && linkState == 5 && (~status & (1 << 0)) && (status & (1 << 17))) {
|
|
KernelLog(LOG_INFO, "xHCI", "port detach", "Device detached from port %d.\n", port);
|
|
ports[port].statusChangeEvent = true;
|
|
ports[port].enabled = false;
|
|
KEventSet(&portStatusChangeEvent, true);
|
|
}
|
|
|
|
KSpinlockRelease(&portResetSpinlock);
|
|
}
|
|
|
|
eventRingIndex++;
|
|
|
|
if (eventRingIndex == EVENT_RING_ENTRIES) {
|
|
eventRingIndex = 0;
|
|
eventRingPhase = !eventRingPhase;
|
|
}
|
|
}
|
|
|
|
WR_REGISTER_ERDP(0, (eventRingPhysical + 0x40 + (eventRingIndex << 4)) | (1 << 3));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool XHCIController::RunCommand(uint32_t *dw) {
|
|
KEventReset(&commandCompleteEvent);
|
|
commandResult = dw;
|
|
|
|
commandRing[commandRingIndex * 4 + 0] = dw[0];
|
|
commandRing[commandRingIndex * 4 + 1] = dw[1];
|
|
commandRing[commandRingIndex * 4 + 2] = dw[2];
|
|
commandRing[commandRingIndex * 4 + 3] = dw[3] | (commandRingPhase ? (1 << 0) : 0);
|
|
|
|
commandRingIndex++;
|
|
|
|
if (commandRingIndex == COMMAND_RING_ENTRIES) {
|
|
commandRingIndex = 0;
|
|
commandRingPhase = !commandRingPhase;
|
|
commandRing[COMMAND_RING_ENTRIES * 4 + 3] ^= 1 << 0; // Toggle phase of link TRB.
|
|
}
|
|
|
|
// TODO Timeout, and command abortion.
|
|
WR_REGISTER_DOORBELL(0, 0);
|
|
KEventWait(&commandCompleteEvent);
|
|
|
|
commandResult = nullptr;
|
|
|
|
uint8_t completionCode = dw[2] >> 24;
|
|
|
|
if (completionCode != 1) {
|
|
if (completionCode < sizeof(commandCompletionCodes) / sizeof(commandCompletionCodes[0])) {
|
|
KernelLog(LOG_ERROR, "xHCI", "failed command", "Command failed with completion code '%z'.\n", commandCompletionCodes[completionCode]);
|
|
} else {
|
|
KernelLog(LOG_ERROR, "xHCI", "failed command", "Command failed with unrecognised completion code %d.\n", completionCode);
|
|
}
|
|
}
|
|
|
|
return completionCode == 1;
|
|
}
|
|
|
|
static bool SelectConfigurationAndInterfaceWrapper(KUSBDevice *_device) {
|
|
XHCIDevice *device = (XHCIDevice *) _device;
|
|
XHCIController *controller = (XHCIController *) device->parent;
|
|
return controller->SelectConfigurationAndInterface(device->port, device);
|
|
}
|
|
|
|
static bool QueueTransferWrapper(KUSBDevice *_device, KUSBEndpointDescriptor *endpoint, KUSBTransferCallback callback,
|
|
void *buffer, size_t bufferBytes, EsGeneric context) {
|
|
XHCIDevice *device = (XHCIDevice *) _device;
|
|
XHCIController *controller = (XHCIController *) device->parent;
|
|
return controller->QueueInterruptTransfer(device->port, endpoint->GetAddress() * 2 + (endpoint->IsInput() ? 1 : 0),
|
|
callback, buffer, bufferBytes, context);
|
|
}
|
|
|
|
static bool ControlTransferWrapper(KUSBDevice *_device, uint8_t flags, uint8_t request, uint16_t value, uint16_t index,
|
|
void *buffer, uint16_t length, int operation, uint16_t *transferred) {
|
|
XHCIDevice *device = (XHCIDevice *) _device;
|
|
XHCIController *controller = (XHCIController *) device->parent;
|
|
KernelLog(LOG_VERBOSE, "xHCI", "control transfer", "Control transfer: %X, %X, %d, %d, %D.\n", flags, request, value, index, length);
|
|
bool success = controller->ControlTransfer(device->port, flags, request, value, index, buffer, length, operation, transferred, false);
|
|
KernelLog(LOG_VERBOSE, "xHCI", "control transfer complete", "Control transfer complete. Success = %d.\n", success);
|
|
return success;
|
|
}
|
|
|
|
void XHCIController::OnPortEnable(uintptr_t port) {
|
|
uint32_t speed = (RD_REGISTER_PORTSC(port) >> 10) & 0xF;
|
|
uint32_t maximumPacketSize = speed == SPEED_LOW ? 8 : speed == SPEED_FULL ? 8 : speed == SPEED_HIGH ? 64 : speed == SPEED_SUPER ? 512 : 0;
|
|
ports[port].speed = speed;
|
|
ports[port].controlEndpoint.maximumPacketSize = maximumPacketSize;
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "initialising port", "Port %d is enabled, initialising. Speed: %z.\n",
|
|
port, speed == SPEED_LOW ? "low" : speed == SPEED_FULL ? "full" : speed == SPEED_HIGH ? "high" : speed == SPEED_SUPER ? "super" : "unknown");
|
|
|
|
if (!maximumPacketSize) {
|
|
KernelLog(LOG_ERROR, "xHCI", "unrecognised device speed", "Unrecognised device speed %d on port %d.\n", speed, port);
|
|
return;
|
|
}
|
|
|
|
// Assign a device slot.
|
|
|
|
uint32_t dw[4] = {};
|
|
dw[3] = (9 /* enable slot */ << 10) | (ports[port].slotType << 16);
|
|
|
|
if (!RunCommand(dw) || !(dw[3] >> 24)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "enable slot error", "Could not enable a slot for the device on port %d.\n", port);
|
|
return;
|
|
}
|
|
|
|
ports[port].slotID = dw[3] >> 24;
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "assign device slot", "Port %d assigned device slot %d.\n", port, ports[port].slotID);
|
|
|
|
// Allocate the transfer ring.
|
|
|
|
if (!ports[port].controlEndpoint.CreateTransferRing()) {
|
|
return;
|
|
}
|
|
|
|
// Setup the input context.
|
|
|
|
EsMemoryZero(inputContext, INPUT_CONTEXT_BYTES);
|
|
|
|
inputContext[CONTEXT_INDEX(0) + 1] = (1 << 0 /* slot context valid */) | (1 << 1 /* control endpoint context valid */);
|
|
|
|
inputContext[CONTEXT_INDEX(1) + 0] = (1 /* 1 context entry */ << 27) | (speed << 20);
|
|
inputContext[CONTEXT_INDEX(1) + 1] = (port + 1) << 16;
|
|
|
|
// TODO Update this with the maximum packet size from the device descriptor.
|
|
// This field is interpreted differently on USB 2/3.
|
|
|
|
inputContext[CONTEXT_INDEX(2) + 1] = (3 /* error count */ << 1) | (4 /* control endpoint */ << 3) | (maximumPacketSize << 16);
|
|
inputContext[CONTEXT_INDEX(2) + 2] = (ports[port].controlEndpoint.physical & 0xFFFFFFFF) | (1 << 0 /* phase bit */);
|
|
inputContext[CONTEXT_INDEX(2) + 3] = (ports[port].controlEndpoint.physical >> 32) & 0xFFFFFFFF;
|
|
inputContext[CONTEXT_INDEX(2) + 4] = 8 /* average TRB length */;
|
|
|
|
// Allocate the output context.
|
|
|
|
if (!MMPhysicalAllocateAndMap(OUTPUT_CONTEXT_BYTES, K_PAGE_SIZE, 0, true,
|
|
0, (uint8_t **) &ports[port].outputContext, &ports[port].outputContextPhysical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the output context for port %d.\n", port);
|
|
return;
|
|
}
|
|
|
|
deviceContextBaseAddressArray[ports[port].slotID] = ports[port].outputContextPhysical;
|
|
|
|
// Address the device.
|
|
// TODO Full-speed devices need their maximum packet size queried before addressing.
|
|
|
|
dw[0] = (inputContextPhysical >> 0) & 0xFFFFFFFF;
|
|
dw[1] = (inputContextPhysical >> 32) & 0xFFFFFFFF;
|
|
dw[2] = 0;
|
|
dw[3] = (ports[port].slotID << 24) | (0 /* send SET_ADDRESS */ << 9) | (11 /* address device command */ << 10);
|
|
|
|
if (!RunCommand(dw)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "address device error", "Could not address the device on port %d.\n", port);
|
|
return;
|
|
}
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "address device", "Port %d successfully addressed.\n", port);
|
|
|
|
// Register the device with USB subsystem.
|
|
|
|
XHCIDevice *device = (XHCIDevice *) KDeviceCreate("XHCI device", this, sizeof(XHCIDevice));
|
|
ports[port].device = device;
|
|
device->port = port;
|
|
device->controlTransfer = ControlTransferWrapper;
|
|
device->selectConfigurationAndInterface = SelectConfigurationAndInterfaceWrapper;
|
|
device->queueTransfer = QueueTransferWrapper;
|
|
KRegisterUSBDevice(device);
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "register device", "Port %d registered with USB subsystem.\n", port);
|
|
}
|
|
|
|
void XHCIController::OnPortDisable(uintptr_t port) {
|
|
KMutexAcquire(&ports[port].mutex);
|
|
EsDefer(KMutexRelease(&ports[port].mutex));
|
|
|
|
if (ports[port].device) {
|
|
KDeviceRemoved(ports[port].device);
|
|
ports[port].device = nullptr;
|
|
}
|
|
|
|
uint32_t dw[4];
|
|
|
|
// Stop endpoints.
|
|
|
|
for (uintptr_t i = 0; i < sizeof(ports[port].ioEndpoints) / sizeof(ports[port].ioEndpoints[0]); i++) {
|
|
XHCIEndpoint *endpoint = ports[port].ioEndpoints + i;
|
|
|
|
if (!endpoint->data) {
|
|
continue;
|
|
}
|
|
|
|
dw[0] = dw[1] = dw[2] = 0;
|
|
dw[3] = (15 /* stop endpoint */ << 10) | ((i + 2) << 16) | (ports[port].slotID << 24);
|
|
RunCommand(dw);
|
|
|
|
MMPhysicalFreeAndUnmap(endpoint->data, endpoint->physical);
|
|
endpoint->data = nullptr;
|
|
endpoint->physical = 0;
|
|
|
|
KUSBTransferCallback callback = endpoint->callback;
|
|
endpoint->callback = nullptr;
|
|
|
|
if (callback) {
|
|
callback(-1, endpoint->context);
|
|
}
|
|
}
|
|
|
|
// Disable slot.
|
|
|
|
dw[0] = dw[1] = dw[2] = 0;
|
|
dw[3] = (10 /* disable slot */ << 10) | (ports[port].slotID << 24);
|
|
RunCommand(dw);
|
|
|
|
// Free the output context.
|
|
|
|
MMPhysicalFreeAndUnmap(ports[port].outputContext, ports[port].outputContextPhysical);
|
|
deviceContextBaseAddressArray[ports[port].slotID] = ports[port].outputContextPhysical;
|
|
ports[port].outputContext = nullptr;
|
|
ports[port].outputContextPhysical = 0;
|
|
|
|
// Free the control endpoint transfer ring.
|
|
|
|
MMPhysicalFreeAndUnmap(ports[port].controlEndpoint.data, ports[port].controlEndpoint.physical);
|
|
ports[port].controlEndpoint.data = nullptr;
|
|
ports[port].controlEndpoint.physical = 0;
|
|
|
|
// Zero out remaining fields in the port structure.
|
|
|
|
ports[port].slotID = 0;
|
|
ports[port].speed = 0;
|
|
ports[port].controlTransferResult = nullptr;
|
|
ports[port].controlTransferLastTRBAddress = 0;
|
|
}
|
|
|
|
void XHCIController::Initialise() {
|
|
portStatusChangeEvent.autoReset = true;
|
|
|
|
// Read capabilities.
|
|
|
|
operationalRegistersOffset = RD_REGISTER_CAPLENGTH() & 0xFF;
|
|
|
|
if ((RD_REGISTER_CAPLENGTH() & 0xFF000000) == 0) {
|
|
KernelLog(LOG_ERROR, "xHCI", "unsupported version", "xHCI controller reports unsupported version number %x.\n",
|
|
RD_REGISTER_CAPLENGTH() >> 16);
|
|
return;
|
|
}
|
|
|
|
uint32_t hcsp1 = RD_REGISTER_HCSPARAMS1();
|
|
maximumDeviceSlots = (hcsp1 >> 0) & 0xFF;
|
|
maximumInterrupters = (hcsp1 >> 8) & 0x7FF;
|
|
maximumPorts = (hcsp1 >> 24) & 0xFF;
|
|
|
|
ports = (XHCIPort *) EsHeapAllocate(sizeof(XHCIPort) * maximumPorts, true, K_FIXED);
|
|
|
|
if (!ports) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate ports array.\n");
|
|
return;
|
|
}
|
|
|
|
uint32_t hcsp2 = RD_REGISTER_HCSPARAMS2();
|
|
maximumEventRingSegments = 1 << ((hcsp2 >> 4) & 0xF);
|
|
|
|
uint32_t scratchpadBufferCount = ((hcsp2 >> 27) & 0x1F) | (((hcsp2 >> 21) & 0x1F) << 5);
|
|
KernelLog(LOG_INFO, "xHCI", "scratchpad buffers", "Controller requests %d scratchpad buffers (HCSP2 = %x).\n", scratchpadBufferCount, hcsp2);
|
|
|
|
uint32_t hccp1 = RD_REGISTER_HCCPARAMS1();
|
|
contextSize64 = (hccp1 & (1 << 2)) ? true : false;
|
|
extendedCapabilitiesOffset = ((hccp1 >> 16) & 0xFFFF) << 2;
|
|
|
|
#ifdef ES_BITS_64
|
|
if (~hccp1 & (1 << 0)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "missing feature", "xHCI controller does not support 64-bit addresses.\n");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
doorbellsOffset = RD_REGISTER_DBOFF();
|
|
runtimeRegistersOffset = RD_REGISTER_RTSOFF();
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "capabilities", "xHC reports capabilities: maximum ports - %d, maximum interrupts - %d, "
|
|
"maximum device slots - %d, maximum event ring segments - %d.\n",
|
|
maximumPorts, maximumInterrupters, maximumDeviceSlots, maximumEventRingSegments);
|
|
|
|
// Enumerate extended capabilities.
|
|
|
|
if (extendedCapabilitiesOffset) {
|
|
uintptr_t offset = extendedCapabilitiesOffset;
|
|
|
|
for (uintptr_t i = 0; i < 256; i++) {
|
|
uint32_t extendedCapability = pci->ReadBAR32(0, offset);
|
|
uint8_t id = (extendedCapability >> 0) & 0xFF;
|
|
uint8_t next = (extendedCapability >> 8) & 0xFF;
|
|
|
|
if (id == 1) {
|
|
// Perform BIOS handoff, if necessary.
|
|
|
|
if (extendedCapability & (1 << 16)) {
|
|
pci->WriteBAR32(0, offset, extendedCapability & (1 << 24));
|
|
|
|
KTimeout timeout(1000);
|
|
while (!timeout.Hit() && (pci->ReadBAR32(0, offset) & (1 << 16)));
|
|
|
|
if (pci->ReadBAR32(0, offset) & (1 << 16)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "BIOS handoff failure", "Could not take control of the xHC from the BIOS.\n");
|
|
return;
|
|
}
|
|
|
|
uint32_t control = pci->ReadBAR32(0, offset + 4);
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "legacy control", "USB legacy control/status register is %x.\n", control);
|
|
|
|
// Disable SMIs.
|
|
control &= ~((1 << 0) | (1 << 4) | (1 << 13) | (1 << 14) | (1 << 15));
|
|
|
|
// Clear RsvdZ bits.
|
|
control &= ~((1 << 21) | (1 << 22) | (1 << 23) | (1 << 24) | (1 << 25) | (1 << 26) | (1 << 27) | (1 << 28));
|
|
|
|
// Acknowledge any status bits.
|
|
control |= (1 << 29) | (1 << 30) | (1 << 31);
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "legacy control", "Writing legacy control/status %x.\n", control);
|
|
|
|
pci->WriteBAR32(0, offset + 4, control);
|
|
}
|
|
} else if (id == 2) {
|
|
uint32_t nameString = pci->ReadBAR32(0, offset + 4);
|
|
uint32_t portRange = pci->ReadBAR32(0, offset + 8);
|
|
uintptr_t portOffset = (portRange & 0xFF) - 1;
|
|
size_t portCount = (portRange >> 8) & 0xFF;
|
|
uint32_t slotType = pci->ReadBAR32(0, offset + 12) & 0x1F;
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "protocol enumerated",
|
|
"Controller supports protocol on ports %d->%d with version %d.%d (%s) and slot type %d.\n",
|
|
portOffset, portOffset + portCount - 1,
|
|
extendedCapability >> 24, (extendedCapability >> 16) & 0xFF, 4, &nameString, slotType);
|
|
|
|
for (uintptr_t i = portOffset; i < portOffset + portCount; i++) {
|
|
ports[i].usb2 = (extendedCapability >> 24) == 2;
|
|
ports[i].slotType = slotType;
|
|
}
|
|
}
|
|
|
|
if (next) {
|
|
offset += next << 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check page size is supported.
|
|
|
|
if (~RD_REGISTER_PAGESIZE() & (1 << (K_PAGE_BITS - 12))) {
|
|
KernelLog(LOG_ERROR, "xHCI", "page size not supported", "xHC does not support native page size of %x; supported mask is %x.\n",
|
|
K_PAGE_SIZE, RD_REGISTER_PAGESIZE());
|
|
return;
|
|
}
|
|
|
|
// Stop the controller.
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "stop controller", "Stopping the controller...\n");
|
|
|
|
WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() & ~(1 << 0));
|
|
|
|
{
|
|
KTimeout timeout(20);
|
|
while (!timeout.Hit() && (~RD_REGISTER_USBSTS() & (1 << 0)));
|
|
|
|
if ((~RD_REGISTER_USBSTS() & (1 << 0))) {
|
|
KernelLog(LOG_ERROR, "xHCI", "stop failure", "Could not stop the xHC to perform a reset.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Reset the controller.
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "reset controller", "Resetting the controller...\n");
|
|
|
|
WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() | (1 << 1));
|
|
|
|
{
|
|
KTimeout timeout(100);
|
|
while (!timeout.Hit() && (RD_REGISTER_USBCMD() & (1 << 1)));
|
|
|
|
if (RD_REGISTER_USBCMD() & (1 << 1)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "reset failure", "Could not reset the xHC within 100ms.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "reset controller", "Controller successfully reset.\n");
|
|
|
|
#if defined(ES_ARCH_X86_32) || defined(ES_ARCH_X86_64)
|
|
// Any PS/2 emulation should have been disabled, so its controller is safe to initialise.
|
|
KPS2SafeToInitialise();
|
|
#endif
|
|
|
|
// Allocate the input context.
|
|
|
|
if (!MMPhysicalAllocateAndMap(INPUT_CONTEXT_BYTES, K_PAGE_SIZE, 0, true,
|
|
0, (uint8_t **) &inputContext, &inputContextPhysical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the input context.\n");
|
|
return;
|
|
}
|
|
|
|
// Allocate the device context base address array.
|
|
|
|
uintptr_t deviceContextBaseAddressArrayPhysical;
|
|
|
|
if (!MMPhysicalAllocateAndMap(256 * 8, 64, 0, true,
|
|
0, (uint8_t **) &deviceContextBaseAddressArray, &deviceContextBaseAddressArrayPhysical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the device context base address array.\n");
|
|
return;
|
|
}
|
|
|
|
WR_REGISTER_DCBAAP(deviceContextBaseAddressArrayPhysical);
|
|
|
|
// Allocate the command ring.
|
|
|
|
uintptr_t commandRingPhysical;
|
|
|
|
if (!MMPhysicalAllocateAndMap(16 * COMMAND_RING_ENTRIES, 64, 0, true,
|
|
0, (uint8_t **) &commandRing, &commandRingPhysical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the command ring.\n");
|
|
return;
|
|
}
|
|
|
|
if (RD_REGISTER_CRCR() & (1 << 3)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "command ring running", "Command ring running while controller stopped.\n");
|
|
return;
|
|
}
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "command ring allocation", "Allocated command ring at physical address %x.\n", commandRingPhysical);
|
|
|
|
WR_REGISTER_CRCR(commandRingPhysical | 1);
|
|
|
|
commandRing[COMMAND_RING_ENTRIES * 4 + 0] = commandRingPhysical & 0xFFFFFFFF;
|
|
commandRing[COMMAND_RING_ENTRIES * 4 + 1] = (commandRingPhysical >> 32) & 0xFFFFFFFF;
|
|
commandRing[COMMAND_RING_ENTRIES * 4 + 3] = (1 << 1 /* toggle cycle */) | (6 /* link TRB */ << 10);
|
|
|
|
commandRingPhase = true;
|
|
|
|
// Allocate the event ring.
|
|
|
|
if (!MMPhysicalAllocateAndMap(64 + 16 * EVENT_RING_ENTRIES, 64, 0, true,
|
|
0, (uint8_t **) &eventRing, &eventRingPhysical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the command ring.\n");
|
|
return;
|
|
}
|
|
|
|
eventRing[0] = ((eventRingPhysical + 0x40) >> 0) & 0xFFFFFFFF;
|
|
eventRing[1] = ((eventRingPhysical + 0x40) >> 32) & 0xFFFFFFFF;
|
|
eventRing[2] = EVENT_RING_ENTRIES;
|
|
eventRing[3] = 0;
|
|
eventRingPhase = true;
|
|
|
|
WR_REGISTER_ERDP(0, (eventRingPhysical + 0x40) | (1 << 3));
|
|
WR_REGISTER_ERSTSZ(0, (RD_REGISTER_ERSTSZ(0) & 0xFFFF0000) | 1);
|
|
WR_REGISTER_ERSTBA(0, (RD_REGISTER_ERSTBA(0) & 0x3F) | eventRingPhysical);
|
|
|
|
// Setup the CONFIG register.
|
|
|
|
WR_REGISTER_CONFIG((RD_REGISTER_CONFIG() & 0xFFFFFC00) | maximumDeviceSlots);
|
|
|
|
// Register the interrupt handler and enable interrupts.
|
|
|
|
KIRQHandler handler = [] (uintptr_t, void *context) { return ((XHCIController *) context)->HandleIRQ(); };
|
|
|
|
if (!pci->EnableSingleInterrupt(handler, this, "xHCI")) {
|
|
KernelLog(LOG_ERROR, "xHCI", "IRQ registration failure", "Could not register interrupt handler.\n");
|
|
return;
|
|
}
|
|
|
|
WR_REGISTER_IMOD(0, 4000 /* in 250ns increments */); // Interrupts should be sent at most every millisecond.
|
|
WR_REGISTER_IMAN(0, RD_REGISTER_IMAN(0) | (1 << 1));
|
|
WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() | (1 << 2));
|
|
|
|
// Allocate scratchpad buffers.
|
|
|
|
if (scratchpadBufferCount) {
|
|
uint64_t *scratchpadArray;
|
|
uintptr_t scratchpadArrayPhysical;
|
|
|
|
if (!MMPhysicalAllocateAndMap(scratchpadBufferCount * sizeof(uint64_t), 64, 0, true,
|
|
0, (uint8_t **) &scratchpadArray, &scratchpadArrayPhysical)) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate the scratchpad buffer array.\n");
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < scratchpadBufferCount; i++) {
|
|
scratchpadArray[i] = MMPhysicalAllocate(MM_PHYSICAL_ALLOCATE_CAN_FAIL | MM_PHYSICAL_ALLOCATE_COMMIT_NOW | MM_PHYSICAL_ALLOCATE_ZEROED);
|
|
|
|
if (!scratchpadArray[i]) {
|
|
KernelLog(LOG_ERROR, "xHCI", "allocation failure", "Could not allocate scratchpad buffer %d.\n", i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
deviceContextBaseAddressArray[0] = scratchpadArrayPhysical;
|
|
}
|
|
|
|
// Start the controller!
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "start controller", "Starting controller...\n");
|
|
|
|
WR_REGISTER_USBCMD(RD_REGISTER_USBCMD() | (1 << 0));
|
|
|
|
{
|
|
KTimeout timeout(20);
|
|
while (!timeout.Hit() && (RD_REGISTER_USBSTS() & (1 << 0)));
|
|
|
|
if ((RD_REGISTER_USBSTS() & (1 << 0))) {
|
|
KernelLog(LOG_ERROR, "xHCI", "start failure", "Could not start the xHC after reset.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "start controller", "Controller successfully started.\n");
|
|
|
|
// Reset USB2 ports.
|
|
// TODO Will we receive events for connected USB3 ports automatically, or do we need to do something?
|
|
|
|
KSpinlockAcquire(&portResetSpinlock);
|
|
|
|
for (uintptr_t i = 0; i < maximumPorts; i++) {
|
|
uint32_t status = RD_REGISTER_PORTSC(i);
|
|
uint32_t linkState = (status >> 5) & 0x0F;
|
|
KernelLog(LOG_INFO, "xHCI", "port status", "Port %d (USB %d) has status %x (link state = %d).\n", i, ports[i].usb2 ? 2 : 3, status, linkState);
|
|
|
|
if (ports[i].usb2 && (linkState == 7 || linkState == 4) && (~status & (1 << 4))) {
|
|
KernelLog(LOG_INFO, "xHCI", "port reset", "Attempting to reset USB 2 port %d... (2)\n", i);
|
|
WR_REGISTER_PORTSC(i, (status & (1 << 9)) | (1 << 4));
|
|
}
|
|
}
|
|
|
|
KSpinlockRelease(&portResetSpinlock);
|
|
|
|
// Wait for events to process.
|
|
|
|
while (true) {
|
|
uintptr_t port = 0;
|
|
|
|
for (uintptr_t i = 0; i < maximumPorts; i++) {
|
|
if (ports[i].statusChangeEvent) {
|
|
port = i;
|
|
goto found;
|
|
}
|
|
}
|
|
|
|
KEventWait(&portStatusChangeEvent);
|
|
continue;
|
|
|
|
found:;
|
|
ports[port].statusChangeEvent = false;
|
|
|
|
if (ports[port].enabled) {
|
|
OnPortEnable(port);
|
|
} else {
|
|
OnPortDisable(port);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DeviceAttach(KDevice *_parent) {
|
|
KPCIDevice *parent = (KPCIDevice *) _parent;
|
|
|
|
XHCIController *device = (XHCIController *) KDeviceCreate("XHCI controller", parent, sizeof(XHCIController));
|
|
if (!device) return;
|
|
device->pci = parent;
|
|
|
|
device->dumpState = [] (KDevice *device) {
|
|
((XHCIController *) device)->DumpState();
|
|
};
|
|
|
|
KernelLog(LOG_INFO, "xHCI", "found controller", "Found XHCI controller at PCI function %d/%d/%d.\n", parent->bus, parent->slot, parent->function);
|
|
|
|
// Enable PCI features.
|
|
parent->EnableFeatures(K_PCI_FEATURE_INTERRUPTS
|
|
| K_PCI_FEATURE_BUSMASTERING_DMA
|
|
| K_PCI_FEATURE_MEMORY_SPACE_ACCESS
|
|
| K_PCI_FEATURE_BAR_0);
|
|
|
|
// Initialise the controller on a new thread.
|
|
KThreadCreate("XHCIInitialisation", [] (uintptr_t context) {
|
|
((XHCIController *) context)->Initialise();
|
|
}, (uintptr_t) device);
|
|
}
|
|
|
|
KDriver driverxHCI = {
|
|
.attach = DeviceAttach,
|
|
};
|