feat: aarch64 port - Raspberry Pi 4/400 SD boot (SDHCI, ext2 root + first-boot grow), USB hubs, PCI INTx routing, SMP, DMA fences
This commit is contained in:
1 parent
8c09ccef66
commit
808e1d66fd
66 files changed
+3690
-196
No files matched your search
+385
-62
@@ -11,11 +11,14 @@
|
||||
#include "HidKeyboard.hpp"
|
||||
#include "HidMouse.hpp"
|
||||
#include "MassStorage.hpp"
|
||||
#include "Hub.hpp"
|
||||
#include "UserUsb.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Dma.hpp>
|
||||
#include <ACPI/GsiRouting.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
@@ -59,6 +62,21 @@ namespace Drivers::USB::Xhci {
|
||||
static std::atomic<bool> g_hotplugProcessing{false};
|
||||
static std::atomic<bool> g_irqDrainPending{false};
|
||||
|
||||
// HID interrupt endpoints that completed with an error. The event drain
|
||||
// does not re-arm them (a halted endpoint takes commands to clear, which
|
||||
// cannot be waited on from there): it records the error, and the
|
||||
// deferred-work pass recovers the endpoint once g_hidRecoverDueMs is
|
||||
// reached. Low/full-speed devices behind a hub's TT (every mouse on a
|
||||
// Raspberry Pi 4) see transaction and split errors often enough that
|
||||
// without this the device goes dead until replugged.
|
||||
// g_hidErrorStreak counts errors since the last good report; it sets the
|
||||
// backoff, and at HID_REENUMERATE_STREAK the device is reset and
|
||||
// enumerated again, as a replug would.
|
||||
static constexpr uint32_t HID_REENUMERATE_STREAK = 6;
|
||||
static std::atomic<uint32_t> g_hidErrorCc[MAX_SLOTS + 1] = {}; // 0 = no recovery due
|
||||
static std::atomic<uint32_t> g_hidErrorStreak[MAX_SLOTS + 1] = {};
|
||||
static std::atomic<uint64_t> g_hidRecoverDueMs[MAX_SLOTS + 1] = {};
|
||||
|
||||
// PollEvents also runs directly from the MSI handler. KernelLogStream uses
|
||||
// a process-context mutex, so printing there can deadlock if the interrupt
|
||||
// preempts a CPU that already owns the terminal lock. Capture the small
|
||||
@@ -243,18 +261,44 @@ namespace Drivers::USB::Xhci {
|
||||
return *(volatile uint32_t*)(g_mmioBase + reg);
|
||||
}
|
||||
|
||||
// TRBs (and the buffers they point at) must be visible before the
|
||||
// controller is told to look.
|
||||
static void WriteDoorbell(uint32_t index, uint32_t value) {
|
||||
Arch::DmaWriteFence();
|
||||
*(volatile uint32_t*)(g_dbBase + index * 4) = value;
|
||||
}
|
||||
|
||||
// Hand a TRB to the controller: everything else in it first, then the
|
||||
// control word that carries its cycle bit. A running ring may fetch the
|
||||
// TRB the moment the cycle bit matches.
|
||||
static inline void PublishTrb(TRB& trb, uint32_t control) {
|
||||
Arch::DmaWriteFence();
|
||||
*(volatile uint32_t*)&trb.Control = control;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DMA buffer allocation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Whether the controller's DMA snoops the CPU caches. PCI controllers
|
||||
// are taken to be coherent; a platform controller says so in _CCA.
|
||||
static bool g_dmaCoherent = true;
|
||||
|
||||
uint8_t* AllocateDmaPage(uint64_t& outPhysAddr) {
|
||||
return (uint8_t*)Memory::Dma::AllocatePage(outPhysAddr, g_dmaCoherent);
|
||||
}
|
||||
|
||||
void* AllocateDmaPage() {
|
||||
uint64_t phys;
|
||||
return AllocateDmaPage(phys);
|
||||
}
|
||||
|
||||
void FreeDmaPage(void* page) {
|
||||
Memory::Dma::FreePage(page, g_dmaCoherent);
|
||||
}
|
||||
|
||||
static uint8_t* AllocateDmaBuffer(uint64_t& outPhysAddr) {
|
||||
void* virt = Memory::g_pfa->AllocateZeroed();
|
||||
outPhysAddr = Memory::SubHHDM(virt);
|
||||
return (uint8_t*)virt;
|
||||
return AllocateDmaPage(outPhysAddr);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -419,6 +463,24 @@ namespace Drivers::USB::Xhci {
|
||||
// MSI setup (same pattern as E1000E)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Legacy INTx, for controllers without MSI (or platforms that cannot
|
||||
// receive it): the line the firmware or Pci::AssignInterruptLines put
|
||||
// in the Interrupt Line register. HandleInterrupt suits a level line:
|
||||
// clearing IMAN.IP drops it.
|
||||
static bool SetupIntx(uint8_t bus, uint8_t dev, uint8_t func) {
|
||||
uint8_t line = Pci::LegacyRead8(bus, dev, func, (uint8_t)Pci::PCI_REG_INTERRUPT);
|
||||
if (line == 0 || line >= 24) return false;
|
||||
|
||||
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND);
|
||||
Pci::LegacyWrite16(bus, dev, func, (uint8_t)Pci::PCI_REG_COMMAND,
|
||||
pciCmd & ~Pci::PCI_CMD_INTX_DISABLE);
|
||||
Arch::RegisterIrqHandler(line, HandleInterrupt);
|
||||
Arch::EnableLegacyIrq(line);
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Using INTx: platform line " << base::dec << (uint64_t)line;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) {
|
||||
uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI);
|
||||
if (cap == 0) {
|
||||
@@ -523,6 +585,8 @@ namespace Drivers::USB::Xhci {
|
||||
if (evtCycle != g_evtRingCCS) {
|
||||
break; // No more events
|
||||
}
|
||||
// The rest of the event was written before its cycle bit.
|
||||
Arch::DmaReadFence();
|
||||
|
||||
uint32_t trbType = (evt.Control & TRB_TYPE_MASK) >> TRB_TYPE_SHIFT;
|
||||
lastType = trbType;
|
||||
@@ -545,8 +609,12 @@ namespace Drivers::USB::Xhci {
|
||||
WriteOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE,
|
||||
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
|
||||
|
||||
// Defer enumeration to ProcessDeferredWork outside interrupt context.
|
||||
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
|
||||
// Defer enumeration to ProcessDeferredWork outside interrupt
|
||||
// context. Changes during the boot scan are kept too (that
|
||||
// pass skips ports already enumerated): a device that
|
||||
// connects after the scan looked at its port, slower on
|
||||
// real hardware than in QEMU, would otherwise be lost.
|
||||
if (portId >= 1 && portId <= g_maxPorts) {
|
||||
g_hotplugPending[portId - 1].store(true, std::memory_order_release);
|
||||
g_deferredWorkPending.store(true, std::memory_order_release);
|
||||
}
|
||||
@@ -642,12 +710,17 @@ namespace Drivers::USB::Xhci {
|
||||
uint16_t len = (residual < reqLen)
|
||||
? (uint16_t)(reqLen - residual) : 0;
|
||||
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HID) {
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HUB) {
|
||||
// Re-armed once the change is handled.
|
||||
Hub::OnStatusChange((uint8_t)slotId, g_interruptDataBuf[slotId],
|
||||
len, completionCode);
|
||||
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID) {
|
||||
if (dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
|
||||
HidKeyboard::ProcessReport(g_interruptDataBuf[slotId], len);
|
||||
} else if (dev.InterfaceProtocol == UsbDevice::PROTOCOL_MOUSE) {
|
||||
HidMouse::ProcessReport(g_interruptDataBuf[slotId], len);
|
||||
}
|
||||
g_hidErrorStreak[slotId].store(0, std::memory_order_relaxed);
|
||||
// Re-queue for next HID report
|
||||
QueueInterruptTransfer(slotId);
|
||||
} else if (g_transferCallbacks[slotId]) {
|
||||
@@ -672,6 +745,23 @@ namespace Drivers::USB::Xhci {
|
||||
g_diagnosticWorkPending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HUB && dev.InterruptEpNum &&
|
||||
epDci == (uint32_t)(dev.InterruptEpNum * 2 + 1)) {
|
||||
Hub::OnStatusChange((uint8_t)slotId, nullptr, 0, completionCode);
|
||||
}
|
||||
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HID && dev.InterruptEpNum &&
|
||||
epDci == (uint32_t)(dev.InterruptEpNum * 2 + 1)) {
|
||||
// Retry at once, then back off 10 ms doubling.
|
||||
uint32_t streak = g_hidErrorStreak[slotId].fetch_add(
|
||||
1, std::memory_order_relaxed) + 1;
|
||||
uint64_t delay = streak <= 1 ? 0 : 10ull << (streak - 2);
|
||||
if (delay > 1000) delay = 1000;
|
||||
g_hidRecoverDueMs[slotId].store(
|
||||
Timekeeping::GetMilliseconds() + delay, std::memory_order_relaxed);
|
||||
g_hidErrorCc[slotId].store(completionCode, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Notify callback of errors too
|
||||
if (g_transferCallbacks[slotId]) {
|
||||
g_transferCallbacks[slotId](slotId, epDci,
|
||||
@@ -787,7 +877,7 @@ namespace Drivers::USB::Xhci {
|
||||
if (g_cmdRingCCS) {
|
||||
control |= TRB_CYCLE_BIT;
|
||||
}
|
||||
slot.Control = control;
|
||||
PublishTrb(slot, control);
|
||||
|
||||
// Advance enqueue pointer
|
||||
g_cmdRingEnqueue++;
|
||||
@@ -836,10 +926,21 @@ namespace Drivers::USB::Xhci {
|
||||
// ControlTransfer - perform a control transfer on EP0
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint32_t ControlTransferLocked(uint8_t slotId, uint8_t bmRequestType, uint8_t bRequest,
|
||||
uint16_t wValue, uint16_t wIndex, uint16_t wLength,
|
||||
void* data, uint64_t dataPhys, bool dirIn, bool trueNested);
|
||||
|
||||
// Data stages of a non-coherent controller go through this page (taken
|
||||
// under g_controlXferLock). Callers pass small stack buffers, and cache
|
||||
// maintenance cannot make those safe: a cache line the buffer shares with
|
||||
// live stack data that the CPU dirties while the transfer runs is written
|
||||
// back over what the device wrote.
|
||||
static uint8_t* g_ctrlBounce = nullptr;
|
||||
static uint64_t g_ctrlBouncePhys = 0;
|
||||
|
||||
uint32_t ControlTransfer(uint8_t slotId, uint8_t bmRequestType, uint8_t bRequest,
|
||||
uint16_t wValue, uint16_t wIndex, uint16_t wLength,
|
||||
void* data, bool dirIn) {
|
||||
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) {
|
||||
return 0xFF;
|
||||
}
|
||||
@@ -853,6 +954,32 @@ namespace Drivers::USB::Xhci {
|
||||
g_pollOwnerCpu.load(std::memory_order_relaxed) == CurrentCpuIndex();
|
||||
if (!trueNested) g_controlXferLock.Acquire();
|
||||
|
||||
bool hasData = data != nullptr && wLength != 0;
|
||||
bool bounce = hasData && !g_dmaCoherent && !trueNested &&
|
||||
g_ctrlBounce != nullptr && wLength <= 0x1000;
|
||||
uint32_t cc;
|
||||
if (bounce) {
|
||||
// Copied in both directions: bytes a short IN transfer leaves
|
||||
// alone keep the caller's contents, as with a direct transfer.
|
||||
memcpy(g_ctrlBounce, data, wLength);
|
||||
cc = ControlTransferLocked(slotId, bmRequestType, bRequest, wValue, wIndex,
|
||||
wLength, g_ctrlBounce, g_ctrlBouncePhys, dirIn, false);
|
||||
if (dirIn) memcpy(data, g_ctrlBounce, wLength);
|
||||
} else {
|
||||
// The data stage buffer is the caller's (cacheable) memory.
|
||||
if (hasData) Memory::Dma::SyncForDevice(data, wLength, g_dmaCoherent);
|
||||
cc = ControlTransferLocked(slotId, bmRequestType, bRequest, wValue, wIndex, wLength,
|
||||
data, hasData ? Memory::SubHHDM(data) : 0, dirIn, trueNested);
|
||||
if (hasData && dirIn) Memory::Dma::SyncForCpu(data, wLength, g_dmaCoherent);
|
||||
}
|
||||
|
||||
if (!trueNested) g_controlXferLock.Release();
|
||||
return cc;
|
||||
}
|
||||
|
||||
static uint32_t ControlTransferLocked(uint8_t slotId, uint8_t bmRequestType, uint8_t bRequest,
|
||||
uint16_t wValue, uint16_t wIndex, uint16_t wLength,
|
||||
void* data, uint64_t dataPhys, bool dirIn, bool trueNested) {
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
|
||||
// --- Setup Stage TRB ---
|
||||
@@ -870,15 +997,15 @@ namespace Drivers::USB::Xhci {
|
||||
if (dev.EP0RingCCS) {
|
||||
setupControl |= TRB_CYCLE_BIT;
|
||||
}
|
||||
setup.Control = setupControl;
|
||||
// The Setup TRB's cycle bit is published only after the Data and
|
||||
// Status TRBs are complete (below): if EP0 is still running, it
|
||||
// would otherwise run into a half-built TD.
|
||||
|
||||
// Advance EP0 enqueue (handles Link TRB wrap)
|
||||
AdvanceEP0Ring(dev);
|
||||
|
||||
// --- Data Stage TRB (if wLength > 0) ---
|
||||
if (wLength > 0 && data != nullptr) {
|
||||
uint64_t dataPhys = Memory::SubHHDM(data);
|
||||
|
||||
TRB& dataTrb = dev.EP0Ring[dev.EP0RingEnqueue];
|
||||
dataTrb.Parameter0 = (uint32_t)(dataPhys & 0xFFFFFFFF);
|
||||
dataTrb.Parameter1 = (uint32_t)(dataPhys >> 32);
|
||||
@@ -891,7 +1018,7 @@ namespace Drivers::USB::Xhci {
|
||||
if (dev.EP0RingCCS) {
|
||||
dataControl |= TRB_CYCLE_BIT;
|
||||
}
|
||||
dataTrb.Control = dataControl;
|
||||
PublishTrb(dataTrb, dataControl);
|
||||
|
||||
AdvanceEP0Ring(dev);
|
||||
}
|
||||
@@ -912,10 +1039,12 @@ namespace Drivers::USB::Xhci {
|
||||
if (dev.EP0RingCCS) {
|
||||
statusControl |= TRB_CYCLE_BIT;
|
||||
}
|
||||
status.Control = statusControl;
|
||||
PublishTrb(status, statusControl);
|
||||
|
||||
AdvanceEP0Ring(dev);
|
||||
|
||||
PublishTrb(setup, setupControl);
|
||||
|
||||
// Ring doorbell for this slot, target EP0 (DCI 1)
|
||||
g_xferCompleted[slotId] = false;
|
||||
WriteDoorbell(slotId, 1);
|
||||
@@ -939,7 +1068,6 @@ namespace Drivers::USB::Xhci {
|
||||
while (Timekeeping::GetMilliseconds() - xferStart < 2000) {
|
||||
PollEvents();
|
||||
if (g_xferCompleted[slotId]) {
|
||||
g_controlXferLock.Release();
|
||||
return g_xferCompletionCode[slotId];
|
||||
}
|
||||
for (int j = 0; j < 100; j++) {
|
||||
@@ -973,7 +1101,6 @@ namespace Drivers::USB::Xhci {
|
||||
| (1 << 16); // DCI=1 (EP0)
|
||||
SendCommand(deqTrb);
|
||||
|
||||
g_controlXferLock.Release();
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
@@ -1003,7 +1130,7 @@ namespace Drivers::USB::Xhci {
|
||||
if (dev.InterruptRingCCS) {
|
||||
control |= TRB_CYCLE_BIT;
|
||||
}
|
||||
trb.Control = control;
|
||||
PublishTrb(trb, control);
|
||||
|
||||
// Advance enqueue (handles Link TRB wrap)
|
||||
AdvanceInterruptRing(dev);
|
||||
@@ -1140,7 +1267,7 @@ namespace Drivers::USB::Xhci {
|
||||
if (dev.BulkInRingCCS) {
|
||||
control |= TRB_CYCLE_BIT;
|
||||
}
|
||||
trb.Control = control;
|
||||
PublishTrb(trb, control);
|
||||
|
||||
AdvanceBulkInRing(dev);
|
||||
|
||||
@@ -1267,6 +1394,9 @@ namespace Drivers::USB::Xhci {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void QueueBulkOutTransfer(uint8_t slotId, uint8_t* data, uint64_t dataPhys, uint32_t length) {
|
||||
if (data != nullptr && length != 0) {
|
||||
Memory::Dma::SyncForDevice(data, length, g_dmaCoherent);
|
||||
}
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
|
||||
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
@@ -1281,7 +1411,7 @@ namespace Drivers::USB::Xhci {
|
||||
if (dev.BulkOutRingCCS) {
|
||||
control |= TRB_CYCLE_BIT;
|
||||
}
|
||||
trb.Control = control;
|
||||
PublishTrb(trb, control);
|
||||
|
||||
AdvanceBulkOutRing(dev);
|
||||
|
||||
@@ -1294,8 +1424,23 @@ namespace Drivers::USB::Xhci {
|
||||
// BulkTransfer - blocking bulk IN/OUT transfer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint32_t BulkTransferUnsynced(uint8_t slotId, bool dirIn, void* data, uint64_t dataPhys,
|
||||
uint32_t length, uint32_t* actualLength);
|
||||
|
||||
uint32_t BulkTransfer(uint8_t slotId, bool dirIn, void* data, uint64_t dataPhys,
|
||||
uint32_t length, uint32_t* actualLength) {
|
||||
if (data != nullptr && length != 0) {
|
||||
Memory::Dma::SyncForDevice(data, length, g_dmaCoherent);
|
||||
}
|
||||
uint32_t cc = BulkTransferUnsynced(slotId, dirIn, data, dataPhys, length, actualLength);
|
||||
if (data != nullptr && length != 0 && dirIn) {
|
||||
Memory::Dma::SyncForCpu(data, length, g_dmaCoherent);
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
static uint32_t BulkTransferUnsynced(uint8_t slotId, bool dirIn, void* data, uint64_t dataPhys,
|
||||
uint32_t length, uint32_t* actualLength) {
|
||||
if (actualLength) *actualLength = 0;
|
||||
if (length == 0) return CC_SUCCESS;
|
||||
if (length > 0x10000) return 0xFF;
|
||||
@@ -1318,7 +1463,7 @@ namespace Drivers::USB::Xhci {
|
||||
|
||||
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC | TRB_ISP;
|
||||
if (dev.BulkInRingCCS) control |= TRB_CYCLE_BIT;
|
||||
trb.Control = control;
|
||||
PublishTrb(trb, control);
|
||||
|
||||
AdvanceBulkInRing(dev);
|
||||
target = dev.BulkInEpNum * 2 + 1;
|
||||
@@ -1332,7 +1477,7 @@ namespace Drivers::USB::Xhci {
|
||||
|
||||
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC;
|
||||
if (dev.BulkOutRingCCS) control |= TRB_CYCLE_BIT;
|
||||
trb.Control = control;
|
||||
PublishTrb(trb, control);
|
||||
|
||||
AdvanceBulkOutRing(dev);
|
||||
target = dev.BulkOutEpNum * 2;
|
||||
@@ -1412,6 +1557,17 @@ namespace Drivers::USB::Xhci {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
// A HID endpoint whose error backoff has run out.
|
||||
static bool HidRecoveryDue() {
|
||||
uint64_t now = 0;
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_hidErrorCc[s].load(std::memory_order_acquire) == 0) continue;
|
||||
if (now == 0) now = Timekeeping::GetMilliseconds();
|
||||
if (now >= g_hidRecoverDueMs[s].load(std::memory_order_relaxed)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasDeferredWork() {
|
||||
if (g_initialized && g_pollMode && EventRingHasWork()) {
|
||||
g_irqDrainPending.store(true, std::memory_order_release);
|
||||
@@ -1419,6 +1575,7 @@ namespace Drivers::USB::Xhci {
|
||||
return g_initialized &&
|
||||
(g_irqDrainPending.load(std::memory_order_acquire) ||
|
||||
g_deferredWorkPending.load(std::memory_order_acquire) ||
|
||||
(g_bootScanComplete && (Hub::HasPendingWork() || HidRecoveryDue())) ||
|
||||
g_diagnosticWorkPending.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
@@ -1468,7 +1625,9 @@ namespace Drivers::USB::Xhci {
|
||||
}
|
||||
|
||||
static void UnregisterClassDriver(uint8_t slotId, const UsbDeviceInfo& dev) {
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HUB) {
|
||||
Hub::UnregisterHub(slotId);
|
||||
} else if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
|
||||
MassStorage::UnregisterDevice(slotId);
|
||||
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
|
||||
dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
|
||||
@@ -1491,6 +1650,105 @@ namespace Drivers::USB::Xhci {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveDevice(uint8_t slotId) {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
|
||||
|
||||
// Children first: a hub's slot outlives the devices addressed through it.
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (s != slotId && g_devices[s].Active && g_devices[s].ParentSlot == slotId) {
|
||||
RemoveDevice(s);
|
||||
}
|
||||
}
|
||||
|
||||
UserUsb::DeviceDisconnected(slotId);
|
||||
UnregisterClassDriver(slotId, g_devices[slotId]);
|
||||
g_devices[slotId].Active = false;
|
||||
g_hidErrorCc[slotId].store(0, std::memory_order_relaxed);
|
||||
g_hidErrorStreak[slotId].store(0, std::memory_order_relaxed);
|
||||
g_transferCallbacks[slotId] = nullptr;
|
||||
DisableSlot(slotId);
|
||||
g_dcbaa[slotId] = 0;
|
||||
g_devices[slotId] = {};
|
||||
KernelLogStream(INFO, "xHCI") << "Slot " << base::dec << (uint64_t)slotId << " deactivated";
|
||||
}
|
||||
|
||||
// Bring a HID device's interrupt endpoint back after a failed transfer.
|
||||
// Reset Endpoint clears a halt (a Context State Error when the error did
|
||||
// not halt it), Set TR Dequeue skips the failed TRB, and CLEAR_FEATURE
|
||||
// (ENDPOINT_HALT) clears a stall on the device and puts its data toggle
|
||||
// back to DATA0 to match the reset host side (USB 2.0 9.4.5). If errors keep coming, the
|
||||
// device is removed and its port reset, as a replug would.
|
||||
static void RecoverHidEndpoint(uint8_t slotId, uint32_t cc) {
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
if (!dev.Active || dev.InterfaceClass != UsbDevice::CLASS_HID ||
|
||||
dev.InterruptEpNum == 0 || !dev.InterruptRing) return;
|
||||
|
||||
uint32_t streak = g_hidErrorStreak[slotId].load(std::memory_order_relaxed);
|
||||
if (streak >= HID_REENUMERATE_STREAK) {
|
||||
KernelLogStream(WARNING, "xHCI") << "Slot " << base::dec << (uint64_t)slotId
|
||||
<< ": HID endpoint keeps failing (cc=" << (uint64_t)cc
|
||||
<< "), resetting the device";
|
||||
uint8_t parentSlot = dev.ParentSlot, parentPort = dev.ParentPort;
|
||||
uint8_t rootPort = dev.PortId;
|
||||
RemoveDevice(slotId);
|
||||
if (parentSlot != 0) {
|
||||
Hub::RequestPortCheck(parentSlot, parentPort);
|
||||
} else if (rootPort >= 1 && rootPort <= g_maxPorts) {
|
||||
// Disable the port (PED is write-1-to-clear) so the hot-plug
|
||||
// pass resets it before enumerating.
|
||||
uint32_t reg = OP_PORTSC_BASE + (rootPort - 1) * OP_PORTSC_STRIDE;
|
||||
WriteOp(reg, (ReadOp(reg) & PORTSC_PRESERVE) | PORTSC_PED);
|
||||
g_hotplugPending[rootPort - 1].store(true, std::memory_order_release);
|
||||
g_deferredWorkPending.store(true, std::memory_order_release);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (streak <= 1) {
|
||||
KernelLogStream(WARNING, "xHCI") << "Slot " << base::dec << (uint64_t)slotId
|
||||
<< ": HID transfer failed (cc=" << (uint64_t)cc << "), recovering endpoint";
|
||||
}
|
||||
|
||||
uint8_t dci = dev.InterruptEpNum * 2 + 1;
|
||||
|
||||
TRB resetTrb = {};
|
||||
resetTrb.Control = (TRB_RESET_ENDPOINT << TRB_TYPE_SHIFT)
|
||||
| ((uint32_t)slotId << 24)
|
||||
| ((uint32_t)dci << 16);
|
||||
bool wasHalted = SendCommand(resetTrb) == CC_SUCCESS;
|
||||
|
||||
uint64_t newDeq = dev.InterruptRingPhys
|
||||
+ (uint64_t)dev.InterruptRingEnqueue * sizeof(TRB);
|
||||
if (dev.InterruptRingCCS) newDeq |= 1; // DCS bit
|
||||
|
||||
TRB deqTrb = {};
|
||||
deqTrb.Parameter0 = (uint32_t)(newDeq & 0xFFFFFFFF);
|
||||
deqTrb.Parameter1 = (uint32_t)(newDeq >> 32);
|
||||
deqTrb.Control = (TRB_SET_TR_DEQUEUE << TRB_TYPE_SHIFT)
|
||||
| ((uint32_t)slotId << 24)
|
||||
| ((uint32_t)dci << 16);
|
||||
SendCommand(deqTrb);
|
||||
|
||||
// Only after a real reset: the host toggle is untouched otherwise.
|
||||
if (wasHalted) {
|
||||
ControlTransfer(slotId, 0x02 /* host-to-dev, standard, endpoint */,
|
||||
0x01 /* CLEAR_FEATURE */, 0 /* ENDPOINT_HALT */,
|
||||
(uint16_t)(0x80 | dev.InterruptEpNum), 0, nullptr, false);
|
||||
}
|
||||
|
||||
if (dev.Active) QueueInterruptTransfer(slotId);
|
||||
}
|
||||
|
||||
static void ProcessHidRecovery() {
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_hidErrorCc[s].load(std::memory_order_acquire) == 0) continue;
|
||||
if (now < g_hidRecoverDueMs[s].load(std::memory_order_relaxed)) continue;
|
||||
uint32_t cc = g_hidErrorCc[s].exchange(0, std::memory_order_acq_rel);
|
||||
if (cc != 0) RecoverHidEndpoint(s, cc);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ProcessDeferredWork - handle hot-plug outside interrupt context.
|
||||
// Called from the BSP idle path so class-driver probing can wait on timers.
|
||||
@@ -1508,7 +1766,8 @@ namespace Drivers::USB::Xhci {
|
||||
|
||||
bool haveDiagnostics = g_diagnosticWorkPending.load(std::memory_order_acquire);
|
||||
bool haveHotplug = g_bootScanComplete &&
|
||||
g_deferredWorkPending.load(std::memory_order_acquire);
|
||||
(g_deferredWorkPending.load(std::memory_order_acquire) || Hub::HasPendingWork() ||
|
||||
HidRecoveryDue());
|
||||
if (!haveDiagnostics && !haveHotplug) return;
|
||||
|
||||
// CAS claim: any idle core may call this, but only one runs at a time.
|
||||
@@ -1525,6 +1784,9 @@ namespace Drivers::USB::Xhci {
|
||||
return;
|
||||
}
|
||||
|
||||
// First: a device it gives up on is queued for the hot-plug pass below.
|
||||
ProcessHidRecovery();
|
||||
|
||||
g_deferredWorkPending.store(false, std::memory_order_release);
|
||||
|
||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||
@@ -1585,25 +1847,23 @@ namespace Drivers::USB::Xhci {
|
||||
UsbDevice::EnumerateDevice(port + 1, speed);
|
||||
}
|
||||
} else {
|
||||
// Device disconnected — deactivate its slot
|
||||
// Device disconnected: deactivate its slot (and, for a hub,
|
||||
// the slots of everything behind it)
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
|
||||
UserUsb::DeviceDisconnected(s);
|
||||
UnregisterClassDriver(s, g_devices[s]);
|
||||
g_devices[s].Active = false;
|
||||
g_transferCallbacks[s] = nullptr;
|
||||
DisableSlot(s);
|
||||
g_dcbaa[s] = 0;
|
||||
g_devices[s] = {};
|
||||
KernelLogStream(INFO, "xHCI") << "Hot-unplug: slot "
|
||||
<< base::dec << (uint64_t)s << " (port "
|
||||
<< (uint64_t)(port + 1) << ") deactivated";
|
||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1 &&
|
||||
g_devices[s].ParentSlot == 0) {
|
||||
KernelLogStream(INFO, "xHCI") << "Hot-unplug: port "
|
||||
<< base::dec << (uint64_t)(port + 1);
|
||||
RemoveDevice(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Port changes behind hubs.
|
||||
Hub::ProcessPendingWork();
|
||||
|
||||
g_hotplugProcessing.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -1611,32 +1871,22 @@ namespace Drivers::USB::Xhci {
|
||||
// Probe (called by PCI driver matching framework)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
bool Probe(const Pci::PciDevice& dev) {
|
||||
if (g_initialized) return false;
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Found controller at PCI "
|
||||
<< base::hex << (uint64_t)dev.Bus << ":"
|
||||
<< (uint64_t)dev.Device << "." << (uint64_t)dev.Function;
|
||||
|
||||
// Read BAR0 and map MMIO region
|
||||
uint64_t mmioPhys = Pci::ReadBar0(dev.Bus, dev.Device, dev.Function);
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "BAR0 physical: " << base::hex << mmioPhys;
|
||||
|
||||
constexpr uint64_t MmioSize = 0x10000;
|
||||
for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) {
|
||||
// Map the register block, reset the controller and set up its
|
||||
// context and command/event rings. Interrupt delivery is the caller's.
|
||||
static void InitializeController(uint64_t mmioPhys, uint64_t mmioSize) {
|
||||
for (uint64_t offset = 0; offset < mmioSize; offset += 0x1000) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
|
||||
}
|
||||
|
||||
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
|
||||
|
||||
Pci::EnableBusMaster(dev.Bus, dev.Device, dev.Function);
|
||||
KernelLogStream(OK, "xHCI") << "Bus mastering enabled";
|
||||
// Parse capability registers. CAPLENGTH and HCIVERSION share the
|
||||
// first dword; read it whole, since narrower accesses are not safe
|
||||
// behind every PCIe bridge (Linux does the same).
|
||||
uint32_t capBase = ReadCap(CAP_CAPLENGTH);
|
||||
g_capLength = (uint8_t)(capBase & 0xFF);
|
||||
|
||||
// Parse capability registers
|
||||
g_capLength = *(volatile uint8_t*)(g_mmioBase + CAP_CAPLENGTH);
|
||||
|
||||
uint32_t hciVersion = *(volatile uint16_t*)(g_mmioBase + CAP_HCIVERSION);
|
||||
uint32_t hciVersion = capBase >> 16;
|
||||
KernelLogStream(INFO, "xHCI") << "Version: " << base::hex << (uint64_t)hciVersion
|
||||
<< ", CapLength: " << (uint64_t)g_capLength;
|
||||
|
||||
@@ -1656,6 +1906,16 @@ namespace Drivers::USB::Xhci {
|
||||
g_rtBase = g_mmioBase + rtsOff;
|
||||
g_dbBase = g_mmioBase + dbOff;
|
||||
|
||||
// A firmware-described register block may be smaller than what the
|
||||
// capability registers point into (port registers, interrupter 0,
|
||||
// the doorbells): map those too.
|
||||
uint64_t needed = g_capLength + OP_PORTSC_BASE + (uint64_t)((hcsParams1 >> 24) & 0xFF) * OP_PORTSC_STRIDE;
|
||||
if (rtsOff + 0x40 > needed) needed = rtsOff + 0x40;
|
||||
if (dbOff + 4 * ((hcsParams1 & 0xFF) + 1) > needed) needed = dbOff + 4 * ((hcsParams1 & 0xFF) + 1);
|
||||
for (uint64_t offset = mmioSize; offset < needed; offset += 0x1000) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "MaxSlots: " << base::dec << (uint64_t)g_maxSlots
|
||||
<< ", MaxPorts: " << (uint64_t)g_maxPorts
|
||||
<< ", ScratchpadBufs: " << (uint64_t)maxScratchpadBufs;
|
||||
@@ -1701,6 +1961,8 @@ namespace Drivers::USB::Xhci {
|
||||
// Program CONFIG
|
||||
WriteOp(OP_CONFIG, g_maxSlots);
|
||||
|
||||
if (!g_dmaCoherent) g_ctrlBounce = AllocateDmaBuffer(g_ctrlBouncePhys);
|
||||
|
||||
// Allocate DCBAA
|
||||
g_dcbaa = (uint64_t*)AllocateDmaBuffer(g_dcbaaPhys);
|
||||
WriteOp(OP_DCBAAP, (uint32_t)(g_dcbaaPhys & 0xFFFFFFFF));
|
||||
@@ -1748,13 +2010,10 @@ namespace Drivers::USB::Xhci {
|
||||
g_evtRingCCS = true;
|
||||
g_evtRingDequeue = 0;
|
||||
KernelLogStream(OK, "xHCI") << "Event ring at phys " << base::hex << g_evtRingPhys;
|
||||
}
|
||||
|
||||
// MSI setup
|
||||
if (!SetupMsi(dev.Bus, dev.Device, dev.Function)) {
|
||||
KernelLogStream(WARNING, "xHCI") << "MSI not available, using poll mode";
|
||||
g_pollMode = true;
|
||||
}
|
||||
|
||||
// Enable the interrupter, run the controller, power and enumerate ports.
|
||||
static bool StartController() {
|
||||
// Enable interrupter 0
|
||||
WriteRt(IR0_IMAN, IMAN_IE);
|
||||
WriteRt(IR0_IMOD, IMOD_INTERVAL_100US);
|
||||
@@ -1822,11 +2081,75 @@ namespace Drivers::USB::Xhci {
|
||||
UsbDevice::EnumerateDevice(port + 1, speed);
|
||||
}
|
||||
|
||||
// Connected ports the scan found nothing on (still training, or the
|
||||
// enumeration failed) get another look from the hot-plug path.
|
||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||
if (!(ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE) & PORTSC_CCS)) continue;
|
||||
bool active = false;
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1) active = true;
|
||||
}
|
||||
if (!active) {
|
||||
g_hotplugPending[port].store(true, std::memory_order_release);
|
||||
g_deferredWorkPending.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
g_bootScanComplete = true;
|
||||
KernelLogStream(OK, "xHCI") << "Initialization complete";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProbePlatform(uint64_t mmioPhys, uint64_t mmioSize, uint32_t gsi, bool levelTriggered,
|
||||
bool activeLow, bool dmaCoherent) {
|
||||
if (g_initialized) return false;
|
||||
|
||||
g_dmaCoherent = dmaCoherent;
|
||||
InitializeController(mmioPhys, mmioSize < 0x1000 ? 0x1000 : (mmioSize + 0xFFF) & ~0xFFFULL);
|
||||
|
||||
uint8_t line = gsi != 0 ? Hal::GsiRouting::LineForGsi(gsi, levelTriggered, activeLow) : 0;
|
||||
if (line != 0) {
|
||||
Arch::RegisterIrqHandler(line, HandleInterrupt);
|
||||
Arch::EnableLegacyIrq(line);
|
||||
KernelLogStream(OK, "xHCI") << "Interrupt: GSI " << base::dec << (uint64_t)gsi
|
||||
<< " on platform line " << (uint64_t)line;
|
||||
} else {
|
||||
KernelLogStream(WARNING, "xHCI") << "No interrupt, using poll mode";
|
||||
}
|
||||
// Watch the event ring from the timer as well. The interrupt is a
|
||||
// PCI INTx routed by firmware through a bridge the kernel does not
|
||||
// drive; if it never arrives, input still works (a ring read per
|
||||
// tick costs next to nothing).
|
||||
g_pollMode = true;
|
||||
|
||||
return StartController();
|
||||
}
|
||||
|
||||
bool Probe(const Pci::PciDevice& dev) {
|
||||
if (g_initialized) return false;
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Found controller at PCI "
|
||||
<< base::hex << (uint64_t)dev.Bus << ":"
|
||||
<< (uint64_t)dev.Device << "." << (uint64_t)dev.Function;
|
||||
|
||||
uint64_t mmioPhys = Pci::ReadBar0(dev.Bus, dev.Device, dev.Function);
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "BAR0 physical: " << base::hex << mmioPhys;
|
||||
|
||||
Pci::EnableBusMaster(dev.Bus, dev.Device, dev.Function);
|
||||
KernelLogStream(OK, "xHCI") << "Bus mastering enabled";
|
||||
|
||||
InitializeController(mmioPhys, 0x10000);
|
||||
|
||||
// MSI setup
|
||||
if (!SetupMsi(dev.Bus, dev.Device, dev.Function) && !SetupIntx(dev.Bus, dev.Device, dev.Function)) {
|
||||
KernelLogStream(WARNING, "xHCI") << "No MSI or INTx, using poll mode";
|
||||
g_pollMode = true;
|
||||
}
|
||||
|
||||
return StartController();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Initialize (standalone fallback path)
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -2064,8 +2387,8 @@ namespace Drivers::USB::Xhci {
|
||||
// -----------------------------------------------------------------
|
||||
// Step 12: MSI setup
|
||||
// -----------------------------------------------------------------
|
||||
if (!SetupMsi(foundDev->Bus, foundDev->Device, foundDev->Function)) {
|
||||
KernelLogStream(WARNING, "xHCI") << "MSI not available, using poll mode";
|
||||
if (!SetupMsi(foundDev->Bus, foundDev->Device, foundDev->Function) && !SetupIntx(foundDev->Bus, foundDev->Device, foundDev->Function)) {
|
||||
KernelLogStream(WARNING, "xHCI") << "No MSI or INTx, using poll mode";
|
||||
g_pollMode = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user