feat: support for USB mass storage devices, hotplugging

This commit is contained in:
2026-05-18 17:47:12 +02:00
parent d340e01e56
commit c20579afd3
36 changed files with 1537 additions and 130 deletions
+149 -9
View File
@@ -8,6 +8,7 @@
#include "UsbDevice.hpp"
#include "HidKeyboard.hpp"
#include "HidMouse.hpp"
#include "MassStorage.hpp"
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
@@ -17,6 +18,7 @@
#include <Libraries/Memory.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <atomic>
using namespace Kt;
@@ -50,7 +52,7 @@ namespace Drivers::USB::Xhci {
// Hot-plug deferred work
static volatile bool g_hotplugPending[MAX_PORTS] = {};
static volatile bool g_deferredWorkPending = false;
static bool g_hotplugProcessing = false;
static std::atomic<bool> g_hotplugProcessing{false};
// MMIO region pointers
static volatile uint8_t* g_mmioBase = nullptr;
@@ -92,6 +94,16 @@ namespace Drivers::USB::Xhci {
static volatile bool g_xferCompleted = false;
static volatile uint32_t g_xferCompletionCode = 0;
// Synchronous bulk transfer tracking. Only one storage-style blocking bulk
// transfer is active at a time.
static volatile bool g_syncBulkActive = false;
static volatile bool g_syncBulkCompleted = false;
static volatile uint8_t g_syncBulkSlotId = 0;
static volatile uint8_t g_syncBulkEpDci = 0;
static volatile uint32_t g_syncBulkLength = 0;
static volatile uint32_t g_syncBulkResidual = 0;
static volatile uint32_t g_syncBulkCompletionCode = 0;
// Per-device info
static UsbDeviceInfo g_devices[MAX_SLOTS + 1] = {};
@@ -300,7 +312,7 @@ namespace Drivers::USB::Xhci {
WriteOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE,
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
// Defer enumeration to ProcessDeferredWork (called from timer tick)
// Defer enumeration to ProcessDeferredWork outside interrupt context.
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
g_hotplugPending[portId - 1] = true;
g_deferredWorkPending = true;
@@ -312,6 +324,7 @@ namespace Drivers::USB::Xhci {
uint32_t completionCode = (evt.Status >> 24) & 0xFF;
uint32_t slotId = (evt.Control >> 24) & 0xFF;
uint32_t epDci = (evt.Control >> 16) & 0x1F;
uint32_t residual = evt.Status & 0x00FFFFFF;
if (epDci == 1) {
// EP0 (DCI 1) - control transfer completion
@@ -320,10 +333,17 @@ namespace Drivers::USB::Xhci {
} else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) {
UsbDeviceInfo& dev = g_devices[slotId];
if (g_syncBulkActive &&
slotId == g_syncBulkSlotId &&
epDci == g_syncBulkEpDci) {
g_syncBulkResidual = residual;
g_syncBulkCompletionCode = completionCode;
g_syncBulkCompleted = true;
break;
}
if (completionCode == CC_SUCCESS || completionCode == CC_SHORT_PACKET) {
// Compute actual transfer length from residual
uint32_t residual = evt.Status & 0x00FFFFFF;
// Check if this is a bulk IN endpoint completion
uint8_t bulkInDci = dev.BulkInEpNum ? (dev.BulkInEpNum * 2 + 1) : 0;
uint8_t bulkOutDci = dev.BulkOutEpNum ? (dev.BulkOutEpNum * 2) : 0;
@@ -688,6 +708,91 @@ namespace Drivers::USB::Xhci {
WriteDoorbell(slotId, target);
}
// -------------------------------------------------------------------------
// BulkTransfer - blocking bulk IN/OUT transfer
// -------------------------------------------------------------------------
uint32_t BulkTransfer(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;
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return 0xFF;
if (data == nullptr) return 0xFF;
if (g_syncBulkActive) return 0xFF;
UsbDeviceInfo& dev = g_devices[slotId];
if (dataPhys == 0) dataPhys = Memory::SubHHDM(data);
uint8_t target = 0;
if (dirIn) {
if (!dev.BulkInRing || dev.BulkInEpNum == 0) return 0xFF;
TRB& trb = dev.BulkInRing[dev.BulkInRingEnqueue];
trb.Parameter0 = (uint32_t)(dataPhys & 0xFFFFFFFF);
trb.Parameter1 = (uint32_t)(dataPhys >> 32);
trb.Status = length;
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC | TRB_ISP;
if (dev.BulkInRingCCS) control |= TRB_CYCLE_BIT;
trb.Control = control;
AdvanceBulkInRing(dev);
target = dev.BulkInEpNum * 2 + 1;
} else {
if (!dev.BulkOutRing || dev.BulkOutEpNum == 0) return 0xFF;
TRB& trb = dev.BulkOutRing[dev.BulkOutRingEnqueue];
trb.Parameter0 = (uint32_t)(dataPhys & 0xFFFFFFFF);
trb.Parameter1 = (uint32_t)(dataPhys >> 32);
trb.Status = length;
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC;
if (dev.BulkOutRingCCS) control |= TRB_CYCLE_BIT;
trb.Control = control;
AdvanceBulkOutRing(dev);
target = dev.BulkOutEpNum * 2;
}
g_syncBulkSlotId = slotId;
g_syncBulkEpDci = target;
g_syncBulkLength = length;
g_syncBulkResidual = length;
g_syncBulkCompletionCode = 0;
g_syncBulkCompleted = false;
g_syncBulkActive = true;
WriteDoorbell(slotId, target);
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < 5000) {
PollEvents();
if (g_syncBulkCompleted) {
uint32_t cc = g_syncBulkCompletionCode;
uint32_t residual = g_syncBulkResidual;
g_syncBulkActive = false;
if (actualLength) {
*actualLength = (residual < g_syncBulkLength)
? (g_syncBulkLength - residual)
: 0;
}
return cc;
}
for (int j = 0; j < 100; j++) {
asm volatile("" ::: "memory");
}
}
g_syncBulkActive = false;
KernelLogStream(WARNING, "xHCI") << "Bulk transfer timeout on slot "
<< base::dec << (uint64_t)slotId << " ep " << (uint64_t)target;
return 0xFF;
}
// -------------------------------------------------------------------------
// RegisterTransferCallback
// -------------------------------------------------------------------------
@@ -729,15 +834,45 @@ namespace Drivers::USB::Xhci {
return g_initialized && g_deferredWorkPending;
}
static void UnregisterClassDriver(uint8_t slotId, const UsbDeviceInfo& dev) {
if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) {
MassStorage::UnregisterDevice(slotId);
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
HidKeyboard::UnregisterDevice(slotId);
} else if (dev.InterfaceClass == UsbDevice::CLASS_HID &&
dev.InterfaceProtocol == UsbDevice::PROTOCOL_MOUSE) {
HidMouse::UnregisterDevice(slotId);
}
}
static void DisableSlot(uint8_t slotId) {
TRB trb = {};
trb.Control = (TRB_DISABLE_SLOT << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24);
uint32_t cc = SendCommand(trb);
if (cc != CC_SUCCESS) {
KernelLogStream(WARNING, "xHCI") << "Disable Slot failed for slot "
<< base::dec << (uint64_t)slotId << " cc=" << (uint64_t)cc;
}
}
// -------------------------------------------------------------------------
// ProcessDeferredWork - handle hot-plug outside interrupt context
// Called from timer tick (same pattern as E1000E::Poll)
// ProcessDeferredWork - handle hot-plug outside interrupt context.
// Called from the BSP idle path so class-driver probing can wait on timers.
// -------------------------------------------------------------------------
void ProcessDeferredWork() {
if (!g_initialized || !g_bootScanComplete || !g_deferredWorkPending) return;
if (g_hotplugProcessing) return;
g_hotplugProcessing = true;
// CAS claim: any idle core may call this, but only one runs at a time.
// AcquireTransport in MassStorage handles the SCSI-in-flight case itself.
bool expected = false;
if (!g_hotplugProcessing.compare_exchange_strong(expected, true,
std::memory_order_acquire, std::memory_order_relaxed)) {
return;
}
g_deferredWorkPending = false;
for (uint32_t port = 0; port < g_maxPorts; port++) {
@@ -802,7 +937,12 @@ namespace Drivers::USB::Xhci {
// Device disconnected — deactivate its slot
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
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";
@@ -812,7 +952,7 @@ namespace Drivers::USB::Xhci {
}
}
g_hotplugProcessing = false;
g_hotplugProcessing.store(false, std::memory_order_release);
}
// -------------------------------------------------------------------------