From b44b3ffe43660c4764de1f9231c7b3593f073705 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sun, 25 Oct 2026 17:55:49 +0100 Subject: [PATCH] feat: USB root boot on Pi 4/400 - wait up to 10s for a late MONTAUKROOT, retry USB enumeration after a port reset, recover halted EP0/bulk endpoints, MSC card reader settle (TUR/sense, CSW stall) --- docs/raspberry-pi.md | 5 ++ kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Boot/Main.cpp | 6 ++ kernel/src/Drivers/Init.cpp | 4 + kernel/src/Drivers/Init.hpp | 5 ++ kernel/src/Drivers/USB/Hub.cpp | 5 ++ kernel/src/Drivers/USB/Hub.hpp | 4 + kernel/src/Drivers/USB/MassStorage.cpp | 113 ++++++++++++++++++++----- kernel/src/Drivers/USB/UsbDevice.cpp | 92 ++++++++++++++------ kernel/src/Drivers/USB/Xhci.cpp | 108 ++++++++++++++++++++++- kernel/src/Drivers/USB/Xhci.hpp | 12 +++ kernel/src/Fs/Boot.cpp | 25 +++++- kernel/src/Fs/FsProbe.cpp | 11 ++- kernel/src/Fs/FsProbe.hpp | 5 +- 14 files changed, 340 insertions(+), 57 deletions(-) diff --git a/docs/raspberry-pi.md b/docs/raspberry-pi.md index f9e99bd..064d69e 100644 --- a/docs/raspberry-pi.md +++ b/docs/raspberry-pi.md @@ -25,6 +25,11 @@ module. Finding no ramdisk, the kernel mounts the partition labelled `MONTAUKROOT` as `0:/` (`Fs/Boot.cpp`), read through the SD driver (`Drivers/Storage/Sdhci`), and the ESP after it. Files load as they are used instead of the firmware reading the whole 80 MB tree before boot. +The card can also boot from a USB card reader (or the image from a USB +stick): the root then comes through `Drivers/USB/MassStorage.cpp`. A USB +root can register after the boot scan (a SuperSpeed link still training, a +reader still bringing its card up), so a boot that finds no `MONTAUKROOT` +waits up to 10 s for one, servicing USB hot-plug meanwhile. Unlike the ramdisk, the root is persistent: changes to `0:/` survive a reboot, so shut down from the desktop rather than pulling power. The image is sized to its contents (`ROOT_FREE_MIB`, default 64, sets the diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 555b528..8f90946 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 80 +#define MONTAUK_BUILD_NUMBER 86 diff --git a/kernel/src/Boot/Main.cpp b/kernel/src/Boot/Main.cpp index 53bbebc..67ff226 100644 --- a/kernel/src/Boot/Main.cpp +++ b/kernel/src/Boot/Main.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -178,6 +179,11 @@ extern "C" void kmain() { Arch::LateInitialize(); + // Without a root nothing can start; say so instead of idling silently. + if (!Fs::Vfs::IsDriveRegistered(0)) { + Panic("No root filesystem (0:/): cannot start 0:/os/init.elf", nullptr); + } + Kt::SuppressKernelLog(); Sched::Spawn("0:/os/init.elf"); diff --git a/kernel/src/Drivers/Init.cpp b/kernel/src/Drivers/Init.cpp index 561de27..020d03d 100644 --- a/kernel/src/Drivers/Init.cpp +++ b/kernel/src/Drivers/Init.cpp @@ -373,6 +373,10 @@ namespace Drivers { Storage::Gpt::ProbeAll(); } + void ServiceStorageHotplug() { + if (USB::Xhci::HasDeferredWork()) USB::Xhci::ProcessDeferredWork(); + } + void InitializeAudio() { // HDA driver initializes during ProbeNormal(). // Bluetooth audio (A2DP) initializes with its adapter: during USB diff --git a/kernel/src/Drivers/Init.hpp b/kernel/src/Drivers/Init.hpp index b35ec67..ca0e637 100644 --- a/kernel/src/Drivers/Init.hpp +++ b/kernel/src/Drivers/Init.hpp @@ -29,6 +29,11 @@ namespace Drivers { // Post-probe: register SATA drives with VFS. void InitializeStorage(); + // Before the scheduler runs: enumerate storage that arrived after the boot + // scan (a USB device still training, a hub port), which normally waits for + // the idle loop. Its partitions are mounted as it registers. + void ServiceStorageHotplug(); + // Post-probe: initialize audio subsystem. void InitializeAudio(); diff --git a/kernel/src/Drivers/USB/Hub.cpp b/kernel/src/Drivers/USB/Hub.cpp index ca77e78..ec4d270 100644 --- a/kernel/src/Drivers/USB/Hub.cpp +++ b/kernel/src/Drivers/USB/Hub.cpp @@ -335,6 +335,11 @@ namespace Drivers::USB::Hub { g_pending.store(true, std::memory_order_release); } + bool ResetChildPort(uint8_t hubSlot, uint8_t port, uint32_t& speed) { + if (hubSlot == 0 || hubSlot > Xhci::MAX_SLOTS || !g_hubs[hubSlot].Active) return false; + return ResetPort(hubSlot, port, g_hubs[hubSlot].Info.SuperSpeed, speed); + } + bool HasPendingWork() { return g_pending.load(std::memory_order_acquire); } diff --git a/kernel/src/Drivers/USB/Hub.hpp b/kernel/src/Drivers/USB/Hub.hpp index ca9009b..422b4d7 100644 --- a/kernel/src/Drivers/USB/Hub.hpp +++ b/kernel/src/Drivers/USB/Hub.hpp @@ -47,6 +47,10 @@ namespace Drivers::USB::Hub { // afresh, as if replugged. void RequestPortCheck(uint8_t hubSlot, uint8_t port); + // Reset a hub port again (an enumeration retry) and report the speed the + // device came up at. False if the hub is gone or the reset failed. + bool ResetChildPort(uint8_t hubSlot, uint8_t port, uint32_t& speed); + // Port changes are waiting for ProcessPendingWork. bool HasPendingWork(); diff --git a/kernel/src/Drivers/USB/MassStorage.cpp b/kernel/src/Drivers/USB/MassStorage.cpp index ed97158..c641ff1 100644 --- a/kernel/src/Drivers/USB/MassStorage.cpp +++ b/kernel/src/Drivers/USB/MassStorage.cpp @@ -51,6 +51,16 @@ namespace Drivers::USB::MassStorage { constexpr uint8_t SERVICE_READ_CAPACITY16 = 0x10; + constexpr uint8_t SENSE_NOT_READY = 0x02; + constexpr uint8_t SENSE_UNIT_ATTENTION = 0x06; + constexpr uint8_t ASC_MEDIUM_NOT_PRESENT = 0x3A; + + // TEST UNIT READY attempts, 100 ms apart: any failure gets a few, a unit + // that says it is coming up (see TestUnitReady) gets about five seconds. + constexpr int TUR_TRIES = 5; + constexpr int TUR_SETTLE_TRIES = 50; + constexpr int TUR_NO_MEDIUM_TRIES = 3; + constexpr uint32_t MAX_BULK_TRANSFER_BYTES = 0x10000; constexpr int MAX_MSC_DEVICES = 16; @@ -275,6 +285,15 @@ namespace Drivers::USB::MassStorage { return maxLun; } + // A failed stage leaves its endpoint halted, or after a timeout its TRB + // still queued; either way the next transfer would fail too. + static uint32_t BulkStage(Device& dev, bool dirIn, void* data, uint64_t dataPhys, + uint32_t length, uint32_t* actual) { + uint32_t cc = Xhci::BulkTransfer(dev.SlotId, dirIn, data, dataPhys, length, actual); + if (!IsGoodCompletion(cc)) Xhci::RecoverBulkEndpoint(dev.SlotId, dirIn); + return cc; + } + static bool ScsiCommand(Device& dev, const uint8_t* cdb, uint8_t cdbLen, void* data, uint64_t dataPhys, uint32_t dataLen, bool dataIn) { @@ -295,24 +314,34 @@ namespace Drivers::USB::MassStorage { memcpy(cbw->CommandBlock, cdb, cdbLen); uint32_t actual = 0; - uint32_t cc = Xhci::BulkTransfer(dev.SlotId, false, dev.CbwBuf, dev.CbwPhys, - sizeof(CommandBlockWrapper), &actual); + uint32_t cc = BulkStage(dev, false, dev.CbwBuf, dev.CbwPhys, + sizeof(CommandBlockWrapper), &actual); if (!IsGoodCompletion(cc) || actual != sizeof(CommandBlockWrapper)) { ReleaseTransport(); return false; } + // A device that fails the command, or has less data than asked for, + // may STALL the data stage; the status still follows in the CSW once + // the halt is cleared (BOT 6.7.2, 6.7.3). Card readers do this for + // commands on a slot that is not ready. if (dataLen > 0) { - cc = Xhci::BulkTransfer(dev.SlotId, dataIn, data, dataPhys, dataLen, &actual); - if (!IsGoodCompletion(cc)) { + cc = BulkStage(dev, dataIn, data, dataPhys, dataLen, &actual); + if (!IsGoodCompletion(cc) && cc != Xhci::CC_STALL_ERROR) { ReleaseTransport(); return false; } } + // A STALL on the CSW itself is cleared and the CSW read again once + // (BOT 5.3.3, figure 2). memset(dev.CswBuf, 0, 0x1000); - cc = Xhci::BulkTransfer(dev.SlotId, true, dev.CswBuf, dev.CswPhys, - sizeof(CommandStatusWrapper), &actual); + cc = BulkStage(dev, true, dev.CswBuf, dev.CswPhys, + sizeof(CommandStatusWrapper), &actual); + if (cc == Xhci::CC_STALL_ERROR) { + cc = BulkStage(dev, true, dev.CswBuf, dev.CswPhys, + sizeof(CommandStatusWrapper), &actual); + } if (!IsGoodCompletion(cc) || actual < sizeof(CommandStatusWrapper)) { ReleaseTransport(); return false; @@ -335,35 +364,66 @@ namespace Drivers::USB::MassStorage { // SCSI commands // ------------------------------------------------------------------------- - static bool RequestSense(Device& dev) { + struct Sense { + uint8_t Key; + uint8_t Asc; + uint8_t Ascq; + }; + + static bool RequestSense(Device& dev, Sense& out) { uint8_t cdb[6] = {}; cdb[0] = SCSI_REQUEST_SENSE; cdb[4] = 18; memset(dev.ScratchBuf, 0, 18); - bool ok = ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 18, true); - if (ok) { - uint8_t senseKey = dev.ScratchBuf[2] & 0x0F; - uint8_t asc = dev.ScratchBuf[12]; - uint8_t ascq = dev.ScratchBuf[13]; - KernelLogStream(INFO, "USB-MSC") << "REQUEST SENSE: key=" - << (uint64_t)senseKey << " asc=" << base::hex << (uint64_t)asc - << " ascq=" << (uint64_t)ascq << base::dec; + if (!ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 18, true)) { + return false; } - return ok; + + out.Key = dev.ScratchBuf[2] & 0x0F; + out.Asc = dev.ScratchBuf[12]; + out.Ascq = dev.ScratchBuf[13]; + return true; } + // Right after a reset a unit reports UNIT ATTENTION (power on, medium + // changed), and a card reader then reports NOT READY while it brings the + // card up, which can take well over a second. Keep asking while the sense + // says the unit is on its way; an empty slot (MEDIUM NOT PRESENT) is final. static bool TestUnitReady(Device& dev) { uint8_t cdb[6] = {}; cdb[0] = SCSI_TEST_UNIT_READY; - for (int i = 0; i < 5; i++) { - if (ScsiCommand(dev, cdb, 6, nullptr, 0, 0, false)) return true; - RequestSense(dev); - PollWaitMs(50); - } + int limit = TUR_TRIES; + int noMedium = 0; + Sense last = {0xFF, 0xFF, 0xFF}; - return false; + for (int i = 0; ; i++) { + if (ScsiCommand(dev, cdb, 6, nullptr, 0, 0, false)) return true; + + Sense sense = {}; + if (RequestSense(dev, sense)) { + if (sense.Key != last.Key || sense.Asc != last.Asc || sense.Ascq != last.Ascq) { + KernelLogStream(INFO, "USB-MSC") << "LUN " << (uint64_t)dev.Lun + << " REQUEST SENSE: key=" << (uint64_t)sense.Key + << " asc=" << base::hex << (uint64_t)sense.Asc + << " ascq=" << (uint64_t)sense.Ascq << base::dec; + last = sense; + } + + if (sense.Key == SENSE_NOT_READY && sense.Asc == ASC_MEDIUM_NOT_PRESENT) { + if (++noMedium >= TUR_NO_MEDIUM_TRIES) return false; + } else { + noMedium = 0; + if (sense.Key == SENSE_NOT_READY || sense.Key == SENSE_UNIT_ATTENTION) { + limit = TUR_SETTLE_TRIES; + } + } + } + + if (i + 1 >= limit) return false; + PollWaitMs(100); + } } static bool Inquiry(Device& dev) { @@ -621,7 +681,14 @@ namespace Drivers::USB::MassStorage { continue; } - if (!ReadCapacity(dev)) { + // Some readers fail the first READ CAPACITY after the unit has + // just become ready. + bool haveCapacity = false; + for (int attempt = 0; attempt < 3 && !haveCapacity; attempt++) { + if (attempt > 0) PollWaitMs(100); + haveCapacity = ReadCapacity(dev); + } + if (!haveCapacity) { KernelLogStream(WARNING, "USB-MSC") << "READ CAPACITY failed for LUN " << (uint64_t)lun; dev.Active = false; diff --git a/kernel/src/Drivers/USB/UsbDevice.cpp b/kernel/src/Drivers/USB/UsbDevice.cpp index 3cf5291..c0f2bb3 100644 --- a/kernel/src/Drivers/USB/UsbDevice.cpp +++ b/kernel/src/Drivers/USB/UsbDevice.cpp @@ -107,7 +107,13 @@ namespace Drivers::USB::UsbDevice { // --------------------------------------------------------------------------- static bool EnumerateInSlot(uint8_t slotId, const DeviceLocation& location, uint32_t speed, - bool& isHub, Hub::HubDescriptorInfo& hubInfo); + bool& isHub, Hub::HubDescriptorInfo& hubInfo, bool& retryable); + + // Attempts at bringing a device up, each after a fresh port reset (as + // Linux's hub_port_init): a device can fail its first requests with a + // transaction error while it is still settling after the reset. Card + // readers do, and USB devices the firmware used to boot from. + constexpr int ENUM_TRIES = 3; uint8_t EnumerateDevice(uint8_t portId, uint32_t speed) { DeviceLocation location = {}; @@ -126,29 +132,37 @@ namespace Drivers::USB::UsbDevice { << ", root port " << (uint64_t)location.RootPort << ") speed=" << SpeedToString(speed); } - // ----------------------------------------------------------------- - // Step 1: Enable Slot - // ----------------------------------------------------------------- - Xhci::TRB enableSlotTrb = {}; - enableSlotTrb.Control = (Xhci::TRB_ENABLE_SLOT << Xhci::TRB_TYPE_SHIFT); + for (int attempt = 1; ; attempt++) { + // ------------------------------------------------------------- + // Step 1: Enable Slot + // ------------------------------------------------------------- + Xhci::TRB enableSlotTrb = {}; + enableSlotTrb.Control = (Xhci::TRB_ENABLE_SLOT << Xhci::TRB_TYPE_SHIFT); - uint32_t cc = Xhci::SendCommand(enableSlotTrb); - if (cc != Xhci::CC_SUCCESS) { - KernelLogStream(ERROR, "USB") << "Enable Slot failed, cc=" << (uint64_t)cc; - return 0; - } + uint32_t cc = Xhci::SendCommand(enableSlotTrb); + if (cc != Xhci::CC_SUCCESS) { + KernelLogStream(ERROR, "USB") << "Enable Slot failed, cc=" << (uint64_t)cc; + return 0; + } - uint8_t slotId = (uint8_t)Xhci::g_cmdCompletionSlotId; - if (slotId == 0 || slotId > Xhci::MAX_SLOTS) { - KernelLogStream(ERROR, "USB") << "Invalid slot ID: " << (uint64_t)slotId; - return 0; - } + uint8_t slotId = (uint8_t)Xhci::g_cmdCompletionSlotId; + if (slotId == 0 || slotId > Xhci::MAX_SLOTS) { + KernelLogStream(ERROR, "USB") << "Invalid slot ID: " << (uint64_t)slotId; + return 0; + } - KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << " enabled"; + KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << " enabled"; + + bool isHub = false; + bool retryable = false; + Hub::HubDescriptorInfo hubInfo = {}; + if (EnumerateInSlot(slotId, location, speed, isHub, hubInfo, retryable)) { + // A hub enumerates what is behind it now that its own slot is + // published (its children name it as their parent). + if (isHub) Hub::RegisterHub(slotId, hubInfo); + return slotId; + } - bool isHub = false; - Hub::HubDescriptorInfo hubInfo = {}; - if (!EnumerateInSlot(slotId, location, speed, isHub, hubInfo)) { // Give the slot back, or every failed attempt (a flaky device // re-reported by its hub) would leak one until none are left. Xhci::TRB disableTrb = {}; @@ -157,20 +171,36 @@ namespace Drivers::USB::UsbDevice { Xhci::SendCommand(disableTrb); Xhci::g_dcbaa[slotId] = 0; *Xhci::GetDevice(slotId) = {}; - return 0; - } - // A hub enumerates what is behind it now that its own slot is - // published (its children name it as their parent). - if (isHub) Hub::RegisterHub(slotId, hubInfo); - return slotId; + if (!retryable || attempt >= ENUM_TRIES) return 0; + + // Back the device off to its default state, giving it longer to + // settle each time. + BusyWaitMs(100 * (uint64_t)attempt); + bool reset = location.ParentSlot == 0 + ? Xhci::ResetRootPort(location.RootPort, speed) + : Hub::ResetChildPort(location.ParentSlot, location.ParentPort, speed); + if (!reset) { + KernelLogStream(WARNING, "USB") << "Port reset for retry failed, giving up"; + return 0; + } + BusyWaitMs(10 * (uint64_t)(attempt + 1)); // reset recovery, and then some + + KernelLogStream(INFO, "USB") << "Retrying enumeration (attempt " + << (uint64_t)(attempt + 1) << " of " << (uint64_t)ENUM_TRIES + << ") speed=" << SpeedToString(speed); + } } static bool EnumerateInSlot(uint8_t slotId, const DeviceLocation& location, uint32_t speed, - bool& isHub, Hub::HubDescriptorInfo& hubInfo) { + bool& isHub, Hub::HubDescriptorInfo& hubInfo, bool& retryable) { uint8_t portId = location.RootPort; uint32_t cc = 0; + // Until the device is configured nothing has claimed it, so a failure + // up to then can be retried from a port reset. + retryable = true; + // ----------------------------------------------------------------- // Step 2: Allocate device output context and set DCBAA entry // ----------------------------------------------------------------- @@ -274,7 +304,12 @@ namespace Drivers::USB::UsbDevice { return false; } - uint8_t bMaxPacketSize0 = partialDesc[7]; + // USB 3 devices give bMaxPacketSize0 as an exponent (9: 512 bytes). + uint16_t bMaxPacketSize0 = partialDesc[7]; + if (speed >= Xhci::SPEED_SUPER) { + bMaxPacketSize0 = (bMaxPacketSize0 >= 5 && bMaxPacketSize0 <= 15) + ? (uint16_t)(1u << bMaxPacketSize0) : maxPacket; + } if (bMaxPacketSize0 == 0) bMaxPacketSize0 = maxPacket; // fallback KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId @@ -572,6 +607,7 @@ namespace Drivers::USB::UsbDevice { KernelLogStream(ERROR, "USB") << "SET_CONFIGURATION failed, cc=" << (uint64_t)cc; return false; } + retryable = false; // ----------------------------------------------------------------- // Step 8b: Hub descriptor (the slot context must describe the hub) diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index 71f00d5..47a1d7f 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -926,6 +926,30 @@ namespace Drivers::USB::Xhci { // ControlTransfer - perform a control transfer on EP0 // ------------------------------------------------------------------------- + // A STALL or a transaction error halts EP0 (xHCI 4.10.2.1), with the + // rest of the TD still queued; until it is reset every later request on + // the slot times out. Reset Endpoint, then Set TR Dequeue past the TD. + static void RecoverEP0(uint8_t slotId) { + UsbDeviceInfo& dev = g_devices[slotId]; + + TRB resetTrb = {}; + resetTrb.Control = (TRB_RESET_ENDPOINT << TRB_TYPE_SHIFT) + | ((uint32_t)slotId << 24) + | (1 << 16); // DCI=1 (EP0) + SendCommand(resetTrb); + + uint64_t newDeq = dev.EP0RingPhys + (uint64_t)dev.EP0RingEnqueue * sizeof(TRB); + if (dev.EP0RingCCS) 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) + | (1 << 16); // DCI=1 (EP0) + SendCommand(deqTrb); + } + 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); @@ -1068,7 +1092,9 @@ namespace Drivers::USB::Xhci { while (Timekeeping::GetMilliseconds() - xferStart < 2000) { PollEvents(); if (g_xferCompleted[slotId]) { - return g_xferCompletionCode[slotId]; + uint32_t cc = g_xferCompletionCode[slotId]; + if (cc != CC_SUCCESS && cc != CC_SHORT_PACKET) RecoverEP0(slotId); + return cc; } for (int j = 0; j < 100; j++) { Arch::CompilerBarrier(); @@ -1239,6 +1265,86 @@ namespace Drivers::USB::Xhci { SendCommand(deqTrb); } + // ------------------------------------------------------------------------- + // ResetRootPort - reset a connected root hub port again + // ------------------------------------------------------------------------- + + bool ResetRootPort(uint8_t portId, uint32_t& speed) { + if (!g_initialized || portId == 0 || portId > g_maxPorts) return false; + uint32_t reg = OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE; + + uint32_t portsc = ReadOp(reg); + if (!(portsc & PORTSC_CCS)) return false; + WriteOp(reg, (portsc & PORTSC_PRESERVE) | PORTSC_PR | PORTSC_CHANGE_BITS); + + bool resetDone = false; + for (uint32_t i = 0; i < 100000; i++) { + PollEvents(); + if (PortResetComplete(ReadOp(reg))) { resetDone = true; break; } + for (int j = 0; j < 100; j++) Arch::CompilerBarrier(); + } + if (!resetDone) return false; + + portsc = ReadOp(reg); + WriteOp(reg, (portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS); + if (!(portsc & PORTSC_PED)) return false; + speed = (portsc >> 10) & 0xF; + return true; + } + + // ------------------------------------------------------------------------- + // RecoverBulkEndpoint - clear a halt or a timed-out TRB on a bulk endpoint + // ------------------------------------------------------------------------- + + void RecoverBulkEndpoint(uint8_t slotId, bool dirIn) { + if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return; + UsbDeviceInfo& dev = g_devices[slotId]; + uint8_t epNum = dirIn ? dev.BulkInEpNum : dev.BulkOutEpNum; + TRB* ring = dirIn ? dev.BulkInRing : dev.BulkOutRing; + if (epNum == 0 || ring == nullptr) return; + + uint8_t dci = dirIn ? (uint8_t)(epNum * 2 + 1) : (uint8_t)(epNum * 2); + + // Reset Endpoint succeeds only on a halted endpoint; one that is still + // running (the transfer timed out) is stopped instead. Either way it + // ends up Stopped, which Set TR Dequeue requires. + TRB resetTrb = {}; + resetTrb.Control = (TRB_RESET_ENDPOINT << TRB_TYPE_SHIFT) + | ((uint32_t)slotId << 24) + | ((uint32_t)dci << 16); + bool wasHalted = SendCommand(resetTrb) == CC_SUCCESS; + + if (!wasHalted) { + TRB stopTrb = {}; + stopTrb.Control = (TRB_STOP_ENDPOINT << TRB_TYPE_SHIFT) + | ((uint32_t)slotId << 24) + | ((uint32_t)dci << 16); + SendCommand(stopTrb); + } + + uint64_t newDeq = dirIn + ? dev.BulkInRingPhys + (uint64_t)dev.BulkInRingEnqueue * sizeof(TRB) + : dev.BulkOutRingPhys + (uint64_t)dev.BulkOutRingEnqueue * sizeof(TRB); + if (dirIn ? dev.BulkInRingCCS : dev.BulkOutRingCCS) 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); + + // Reset Endpoint put the host's data toggle back to DATA0; CLEAR_FEATURE + // (ENDPOINT_HALT) clears the device's stall and does the same on its + // side (USB 2.0 9.4.5). Without a reset the toggles still agree. + if (wasHalted) { + ControlTransfer(slotId, 0x02 /* host-to-dev, standard, endpoint */, + 0x01 /* CLEAR_FEATURE */, 0 /* ENDPOINT_HALT */, + (uint16_t)((dirIn ? 0x80 : 0x00) | epNum), 0, nullptr, false); + } + } + // ------------------------------------------------------------------------- // QueueBulkInTransfer // ------------------------------------------------------------------------- diff --git a/kernel/src/Drivers/USB/Xhci.hpp b/kernel/src/Drivers/USB/Xhci.hpp index 43a8289..9b32781 100644 --- a/kernel/src/Drivers/USB/Xhci.hpp +++ b/kernel/src/Drivers/USB/Xhci.hpp @@ -161,6 +161,7 @@ namespace Drivers::USB::Xhci { // Completion codes (from Status field bits 31:24) constexpr uint32_t CC_SUCCESS = 1; + constexpr uint32_t CC_STALL_ERROR = 6; constexpr uint32_t CC_SHORT_PACKET = 13; // --------------------------------------------------------------------------- @@ -367,6 +368,17 @@ namespace Drivers::USB::Xhci { // outstanding-transfer accounting before submitting fresh work. void ResetBulkOutEndpoint(uint8_t slotId); + // Reset a connected root port again (an enumeration retry) and report the + // speed the device came up at. False if it is gone or did not enable. + bool ResetRootPort(uint8_t portId, uint32_t& speed); + + // Bring a bulk endpoint back after a failed BulkTransfer(): clear a halt + // (a STALL or a transaction error) on both the host and the device, or, + // after a timeout, stop the endpoint and drop its stale TRB, so the next + // transfer neither fails nor completes against the old one. Process or + // boot context only (it waits on commands and EP0). + void RecoverBulkEndpoint(uint8_t slotId, bool dirIn); + // True while PollEvents() is draining the event ring. A command submitter // reached from inside an event callback must fire-and-forget (not wait), // since a nested PollEvents() is a no-op. diff --git a/kernel/src/Fs/Boot.cpp b/kernel/src/Fs/Boot.cpp index 40f2d72..46b0e27 100644 --- a/kernel/src/Fs/Boot.cpp +++ b/kernel/src/Fs/Boot.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include namespace Fs { @@ -18,6 +20,12 @@ namespace Fs { constexpr const char* RootLabel = "MONTAUKROOT"; + // How long a boot without a ramdisk waits for the root partition to + // appear. A USB root (a stick, or a card reader still bringing its + // card up) can register after the boot scan; until then nothing would + // provide 0:/os/init.elf. + constexpr uint64_t RootWaitMs = 10000; + bool StringsEqual(const char* lhs, const char* rhs) { if (lhs == nullptr || rhs == nullptr) return false; @@ -126,8 +134,21 @@ namespace Fs { Fat32::RegisterProbe(); Ext2::RegisterProbe(); if (!hasRamdisk && FsProbe::MountRootByLabel(RootLabel) < 0) { - Kt::KernelLogStream(Kt::WARNING, "Fs") - << "No ramdisk and no " << RootLabel << " partition; using the first one found"; + Kt::KernelLogStream(Kt::INFO, "Fs") + << "Waiting for the " << RootLabel << " partition"; + + // A device that registers now gets its root partition mounted by + // FsProbe::MountPartitionsForBlockDevice, which knows the label. + // Counted in BusyWaitMs steps, which are bounded even without a tick. + for (uint64_t waited = 0; !Vfs::IsDriveRegistered(0) && waited < RootWaitMs; waited += 10) { + Drivers::ServiceStorageHotplug(); + Timekeeping::BusyWaitMs(10); + } + + if (!Vfs::IsDriveRegistered(0)) { + Kt::KernelLogStream(Kt::WARNING, "Fs") + << "No ramdisk and no " << RootLabel << " partition; using the first one found"; + } } FsProbe::MountPartitions(hasRamdisk ? 1 : 0); } diff --git a/kernel/src/Fs/FsProbe.cpp b/kernel/src/Fs/FsProbe.cpp index 5a1ad4e..a8300c4 100644 --- a/kernel/src/Fs/FsProbe.cpp +++ b/kernel/src/Fs/FsProbe.cpp @@ -16,6 +16,7 @@ namespace Fs::FsProbe { static int g_probeCount = 0; static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {}; static int g_driveForPart[Drivers::Storage::Gpt::MaxPartitions] = {}; + static const char* g_rootLabel = nullptr; // A successful probe returns an owned mount. Registration transfers that // ownership to the VFS; if registration loses a drive-slot race, release @@ -86,6 +87,7 @@ namespace Fs::FsProbe { int MountRootByLabel(const char* label) { if (!Vfs::IsInitialized() || g_probeCount == 0 || label == nullptr) return -1; + g_rootLabel = label; if (Vfs::IsDriveRegistered(0)) return -1; int partCount = Drivers::Storage::Gpt::GetPartitionCount(); @@ -136,9 +138,16 @@ namespace Fs::FsProbe { int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive) { if (!Vfs::IsInitialized() || g_probeCount == 0) return 0; - int partCount = Drivers::Storage::Gpt::GetPartitionCount(); int mounted = 0; + // Drive 0 free means the boot is still waiting for its root (Fs/Boot.cpp): + // this device may be the one carrying it, and nothing else may take 0. + if (firstDrive == 0 && !Vfs::IsDriveRegistered(0)) { + if (g_rootLabel != nullptr && MountRootByLabel(g_rootLabel) >= 0) mounted++; + firstDrive = 1; + } + + int partCount = Drivers::Storage::Gpt::GetPartitionCount(); for (int i = 0; i < partCount; i++) { auto* part = Drivers::Storage::Gpt::GetPartition(i); if (!part || part->BlockDevIndex != blockDevIndex || IsEfiPartition(part)) continue; diff --git a/kernel/src/Fs/FsProbe.hpp b/kernel/src/Fs/FsProbe.hpp index 09922a3..848e5ed 100644 --- a/kernel/src/Fs/FsProbe.hpp +++ b/kernel/src/Fs/FsProbe.hpp @@ -21,6 +21,8 @@ namespace Fs::FsProbe { // Mount the non-EFI partition whose filesystem label is `label` as drive 0 // (the system root when booting without a ramdisk). Returns the global // partition index, or -1 if none matched or drive 0 is already taken. + // The label is remembered: while drive 0 stays free, a block device that + // registers later (USB) is checked for it before anything else is mounted. int MountRootByLabel(const char* label); // Probe all discovered storage partitions and auto-mount recognized filesystems. @@ -28,7 +30,8 @@ namespace Fs::FsProbe { void MountPartitions(int firstDrive = 1); // Probe discovered partitions for a single block device and mount recognized - // filesystems at the lowest currently available VFS drive numbers. + // filesystems at the lowest currently available VFS drive numbers. A free + // drive 0 is only ever given to the root partition MountRootByLabel names. int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive = 0); // Try to mount a single partition (by global partition index) as the given VFS drive.