feat: support for USB mass storage devices, hotplugging
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user