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)

This commit is contained in:
daniel committed 2026-10-25 17:55:49 +01:00
1 parent ffee9002a6
commit b44b3ffe43
14 files changed
+317 -34

No files matched your search

+5
View File
@@ -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 `MONTAUKROOT` as `0:/` (`Fs/Boot.cpp`), read through the SD driver
(`Drivers/Storage/Sdhci`), and the ESP after it. Files load as they are (`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. 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 Unlike the ramdisk, the root is persistent: changes to `0:/` survive a
reboot, so shut down from the desktop rather than pulling power. 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 The image is sized to its contents (`ROOT_FREE_MIB`, default 64, sets the
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 80 #define MONTAUK_BUILD_NUMBER 86
+6
View File
@@ -32,6 +32,7 @@
#include <Arch/Cpu.hpp> #include <Arch/Cpu.hpp>
#include <Arch/Platform.hpp> #include <Arch/Platform.hpp>
#include <Fs/Boot.hpp> #include <Fs/Boot.hpp>
#include <Fs/Vfs.hpp>
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
#include <Ipc/Ipc.hpp> #include <Ipc/Ipc.hpp>
#include <Api/Syscall.hpp> #include <Api/Syscall.hpp>
@@ -178,6 +179,11 @@ extern "C" void kmain() {
Arch::LateInitialize(); 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(); Kt::SuppressKernelLog();
Sched::Spawn("0:/os/init.elf"); Sched::Spawn("0:/os/init.elf");
+4
View File
@@ -373,6 +373,10 @@ namespace Drivers {
Storage::Gpt::ProbeAll(); Storage::Gpt::ProbeAll();
} }
void ServiceStorageHotplug() {
if (USB::Xhci::HasDeferredWork()) USB::Xhci::ProcessDeferredWork();
}
void InitializeAudio() { void InitializeAudio() {
// HDA driver initializes during ProbeNormal(). // HDA driver initializes during ProbeNormal().
// Bluetooth audio (A2DP) initializes with its adapter: during USB // Bluetooth audio (A2DP) initializes with its adapter: during USB
+5
View File
@@ -29,6 +29,11 @@ namespace Drivers {
// Post-probe: register SATA drives with VFS. // Post-probe: register SATA drives with VFS.
void InitializeStorage(); 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. // Post-probe: initialize audio subsystem.
void InitializeAudio(); void InitializeAudio();
+5
View File
@@ -335,6 +335,11 @@ namespace Drivers::USB::Hub {
g_pending.store(true, std::memory_order_release); 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() { bool HasPendingWork() {
return g_pending.load(std::memory_order_acquire); return g_pending.load(std::memory_order_acquire);
} }
+4
View File
@@ -47,6 +47,10 @@ namespace Drivers::USB::Hub {
// afresh, as if replugged. // afresh, as if replugged.
void RequestPortCheck(uint8_t hubSlot, uint8_t port); 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. // Port changes are waiting for ProcessPendingWork.
bool HasPendingWork(); bool HasPendingWork();
+87 -20
View File
@@ -51,6 +51,16 @@ namespace Drivers::USB::MassStorage {
constexpr uint8_t SERVICE_READ_CAPACITY16 = 0x10; 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 uint32_t MAX_BULK_TRANSFER_BYTES = 0x10000;
constexpr int MAX_MSC_DEVICES = 16; constexpr int MAX_MSC_DEVICES = 16;
@@ -275,6 +285,15 @@ namespace Drivers::USB::MassStorage {
return maxLun; 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, static bool ScsiCommand(Device& dev, const uint8_t* cdb, uint8_t cdbLen,
void* data, uint64_t dataPhys, uint32_t dataLen, void* data, uint64_t dataPhys, uint32_t dataLen,
bool dataIn) { bool dataIn) {
@@ -295,24 +314,34 @@ namespace Drivers::USB::MassStorage {
memcpy(cbw->CommandBlock, cdb, cdbLen); memcpy(cbw->CommandBlock, cdb, cdbLen);
uint32_t actual = 0; uint32_t actual = 0;
uint32_t cc = Xhci::BulkTransfer(dev.SlotId, false, dev.CbwBuf, dev.CbwPhys, uint32_t cc = BulkStage(dev, false, dev.CbwBuf, dev.CbwPhys,
sizeof(CommandBlockWrapper), &actual); sizeof(CommandBlockWrapper), &actual);
if (!IsGoodCompletion(cc) || actual != sizeof(CommandBlockWrapper)) { if (!IsGoodCompletion(cc) || actual != sizeof(CommandBlockWrapper)) {
ReleaseTransport(); ReleaseTransport();
return false; 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) { if (dataLen > 0) {
cc = Xhci::BulkTransfer(dev.SlotId, dataIn, data, dataPhys, dataLen, &actual); cc = BulkStage(dev, dataIn, data, dataPhys, dataLen, &actual);
if (!IsGoodCompletion(cc)) { if (!IsGoodCompletion(cc) && cc != Xhci::CC_STALL_ERROR) {
ReleaseTransport(); ReleaseTransport();
return false; 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); memset(dev.CswBuf, 0, 0x1000);
cc = Xhci::BulkTransfer(dev.SlotId, true, dev.CswBuf, dev.CswPhys, cc = BulkStage(dev, true, dev.CswBuf, dev.CswPhys,
sizeof(CommandStatusWrapper), &actual); 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)) { if (!IsGoodCompletion(cc) || actual < sizeof(CommandStatusWrapper)) {
ReleaseTransport(); ReleaseTransport();
return false; return false;
@@ -335,35 +364,66 @@ namespace Drivers::USB::MassStorage {
// SCSI commands // 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] = {}; uint8_t cdb[6] = {};
cdb[0] = SCSI_REQUEST_SENSE; cdb[0] = SCSI_REQUEST_SENSE;
cdb[4] = 18; cdb[4] = 18;
memset(dev.ScratchBuf, 0, 18); memset(dev.ScratchBuf, 0, 18);
bool ok = ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 18, true); if (!ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 18, true)) {
if (ok) { return false;
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;
}
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) { static bool TestUnitReady(Device& dev) {
uint8_t cdb[6] = {}; uint8_t cdb[6] = {};
cdb[0] = SCSI_TEST_UNIT_READY; cdb[0] = SCSI_TEST_UNIT_READY;
for (int i = 0; i < 5; i++) { int limit = TUR_TRIES;
int noMedium = 0;
Sense last = {0xFF, 0xFF, 0xFF};
for (int i = 0; ; i++) {
if (ScsiCommand(dev, cdb, 6, nullptr, 0, 0, false)) return true; if (ScsiCommand(dev, cdb, 6, nullptr, 0, 0, false)) return true;
RequestSense(dev);
PollWaitMs(50); 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;
} }
return false; 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) { static bool Inquiry(Device& dev) {
@@ -621,7 +681,14 @@ namespace Drivers::USB::MassStorage {
continue; 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 " KernelLogStream(WARNING, "USB-MSC") << "READ CAPACITY failed for LUN "
<< (uint64_t)lun; << (uint64_t)lun;
dev.Active = false; dev.Active = false;
+46 -10
View File
@@ -107,7 +107,13 @@ namespace Drivers::USB::UsbDevice {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static bool EnumerateInSlot(uint8_t slotId, const DeviceLocation& location, uint32_t 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);
// 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) { uint8_t EnumerateDevice(uint8_t portId, uint32_t speed) {
DeviceLocation location = {}; DeviceLocation location = {};
@@ -126,9 +132,10 @@ namespace Drivers::USB::UsbDevice {
<< ", root port " << (uint64_t)location.RootPort << ") speed=" << SpeedToString(speed); << ", root port " << (uint64_t)location.RootPort << ") speed=" << SpeedToString(speed);
} }
// ----------------------------------------------------------------- for (int attempt = 1; ; attempt++) {
// -------------------------------------------------------------
// Step 1: Enable Slot // Step 1: Enable Slot
// ----------------------------------------------------------------- // -------------------------------------------------------------
Xhci::TRB enableSlotTrb = {}; Xhci::TRB enableSlotTrb = {};
enableSlotTrb.Control = (Xhci::TRB_ENABLE_SLOT << Xhci::TRB_TYPE_SHIFT); enableSlotTrb.Control = (Xhci::TRB_ENABLE_SLOT << Xhci::TRB_TYPE_SHIFT);
@@ -147,8 +154,15 @@ namespace Drivers::USB::UsbDevice {
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << " enabled"; KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << " enabled";
bool isHub = false; bool isHub = false;
bool retryable = false;
Hub::HubDescriptorInfo hubInfo = {}; Hub::HubDescriptorInfo hubInfo = {};
if (!EnumerateInSlot(slotId, location, speed, isHub, 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;
}
// Give the slot back, or every failed attempt (a flaky device // Give the slot back, or every failed attempt (a flaky device
// re-reported by its hub) would leak one until none are left. // re-reported by its hub) would leak one until none are left.
Xhci::TRB disableTrb = {}; Xhci::TRB disableTrb = {};
@@ -157,20 +171,36 @@ namespace Drivers::USB::UsbDevice {
Xhci::SendCommand(disableTrb); Xhci::SendCommand(disableTrb);
Xhci::g_dcbaa[slotId] = 0; Xhci::g_dcbaa[slotId] = 0;
*Xhci::GetDevice(slotId) = {}; *Xhci::GetDevice(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; return 0;
} }
BusyWaitMs(10 * (uint64_t)(attempt + 1)); // reset recovery, and then some
// A hub enumerates what is behind it now that its own slot is KernelLogStream(INFO, "USB") << "Retrying enumeration (attempt "
// published (its children name it as their parent). << (uint64_t)(attempt + 1) << " of " << (uint64_t)ENUM_TRIES
if (isHub) Hub::RegisterHub(slotId, hubInfo); << ") speed=" << SpeedToString(speed);
return slotId; }
} }
static bool EnumerateInSlot(uint8_t slotId, const DeviceLocation& location, uint32_t 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; uint8_t portId = location.RootPort;
uint32_t cc = 0; 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 // Step 2: Allocate device output context and set DCBAA entry
// ----------------------------------------------------------------- // -----------------------------------------------------------------
@@ -274,7 +304,12 @@ namespace Drivers::USB::UsbDevice {
return false; 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 if (bMaxPacketSize0 == 0) bMaxPacketSize0 = maxPacket; // fallback
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId 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; KernelLogStream(ERROR, "USB") << "SET_CONFIGURATION failed, cc=" << (uint64_t)cc;
return false; return false;
} }
retryable = false;
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Step 8b: Hub descriptor (the slot context must describe the hub) // Step 8b: Hub descriptor (the slot context must describe the hub)
+107 -1
View File
@@ -926,6 +926,30 @@ namespace Drivers::USB::Xhci {
// ControlTransfer - perform a control transfer on EP0 // 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, static uint32_t ControlTransferLocked(uint8_t slotId, uint8_t bmRequestType, uint8_t bRequest,
uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint16_t wValue, uint16_t wIndex, uint16_t wLength,
void* data, uint64_t dataPhys, bool dirIn, bool trueNested); void* data, uint64_t dataPhys, bool dirIn, bool trueNested);
@@ -1068,7 +1092,9 @@ namespace Drivers::USB::Xhci {
while (Timekeeping::GetMilliseconds() - xferStart < 2000) { while (Timekeeping::GetMilliseconds() - xferStart < 2000) {
PollEvents(); PollEvents();
if (g_xferCompleted[slotId]) { 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++) { for (int j = 0; j < 100; j++) {
Arch::CompilerBarrier(); Arch::CompilerBarrier();
@@ -1239,6 +1265,86 @@ namespace Drivers::USB::Xhci {
SendCommand(deqTrb); 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 // QueueBulkInTransfer
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
+12
View File
@@ -161,6 +161,7 @@ namespace Drivers::USB::Xhci {
// Completion codes (from Status field bits 31:24) // Completion codes (from Status field bits 31:24)
constexpr uint32_t CC_SUCCESS = 1; constexpr uint32_t CC_SUCCESS = 1;
constexpr uint32_t CC_STALL_ERROR = 6;
constexpr uint32_t CC_SHORT_PACKET = 13; constexpr uint32_t CC_SHORT_PACKET = 13;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -367,6 +368,17 @@ namespace Drivers::USB::Xhci {
// outstanding-transfer accounting before submitting fresh work. // outstanding-transfer accounting before submitting fresh work.
void ResetBulkOutEndpoint(uint8_t slotId); 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 // True while PollEvents() is draining the event ring. A command submitter
// reached from inside an event callback must fire-and-forget (not wait), // reached from inside an event callback must fire-and-forget (not wait),
// since a nested PollEvents() is a no-op. // since a nested PollEvents() is a no-op.
+21
View File
@@ -11,6 +11,8 @@
#include <Fs/Fat32.hpp> #include <Fs/Fat32.hpp>
#include <Fs/Ext2.hpp> #include <Fs/Ext2.hpp>
#include <Fs/FsProbe.hpp> #include <Fs/FsProbe.hpp>
#include <Drivers/Init.hpp>
#include <Timekeeping/Timer.hpp>
namespace Fs { namespace Fs {
@@ -18,6 +20,12 @@ namespace Fs {
constexpr const char* RootLabel = "MONTAUKROOT"; 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) { bool StringsEqual(const char* lhs, const char* rhs) {
if (lhs == nullptr || rhs == nullptr) return false; if (lhs == nullptr || rhs == nullptr) return false;
@@ -126,9 +134,22 @@ namespace Fs {
Fat32::RegisterProbe(); Fat32::RegisterProbe();
Ext2::RegisterProbe(); Ext2::RegisterProbe();
if (!hasRamdisk && FsProbe::MountRootByLabel(RootLabel) < 0) { if (!hasRamdisk && FsProbe::MountRootByLabel(RootLabel) < 0) {
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") Kt::KernelLogStream(Kt::WARNING, "Fs")
<< "No ramdisk and no " << RootLabel << " partition; using the first one found"; << "No ramdisk and no " << RootLabel << " partition; using the first one found";
} }
}
FsProbe::MountPartitions(hasRamdisk ? 1 : 0); FsProbe::MountPartitions(hasRamdisk ? 1 : 0);
} }
+10 -1
View File
@@ -16,6 +16,7 @@ namespace Fs::FsProbe {
static int g_probeCount = 0; static int g_probeCount = 0;
static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {}; static bool g_mounted[Drivers::Storage::Gpt::MaxPartitions] = {};
static int g_driveForPart[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 // A successful probe returns an owned mount. Registration transfers that
// ownership to the VFS; if registration loses a drive-slot race, release // ownership to the VFS; if registration loses a drive-slot race, release
@@ -86,6 +87,7 @@ namespace Fs::FsProbe {
int MountRootByLabel(const char* label) { int MountRootByLabel(const char* label) {
if (!Vfs::IsInitialized() || g_probeCount == 0 || label == nullptr) return -1; if (!Vfs::IsInitialized() || g_probeCount == 0 || label == nullptr) return -1;
g_rootLabel = label;
if (Vfs::IsDriveRegistered(0)) return -1; if (Vfs::IsDriveRegistered(0)) return -1;
int partCount = Drivers::Storage::Gpt::GetPartitionCount(); int partCount = Drivers::Storage::Gpt::GetPartitionCount();
@@ -136,9 +138,16 @@ namespace Fs::FsProbe {
int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive) { int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive) {
if (!Vfs::IsInitialized() || g_probeCount == 0) return 0; if (!Vfs::IsInitialized() || g_probeCount == 0) return 0;
int partCount = Drivers::Storage::Gpt::GetPartitionCount();
int mounted = 0; 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++) { for (int i = 0; i < partCount; i++) {
auto* part = Drivers::Storage::Gpt::GetPartition(i); auto* part = Drivers::Storage::Gpt::GetPartition(i);
if (!part || part->BlockDevIndex != blockDevIndex || IsEfiPartition(part)) continue; if (!part || part->BlockDevIndex != blockDevIndex || IsEfiPartition(part)) continue;
+4 -1
View File
@@ -21,6 +21,8 @@ namespace Fs::FsProbe {
// Mount the non-EFI partition whose filesystem label is `label` as drive 0 // Mount the non-EFI partition whose filesystem label is `label` as drive 0
// (the system root when booting without a ramdisk). Returns the global // (the system root when booting without a ramdisk). Returns the global
// partition index, or -1 if none matched or drive 0 is already taken. // 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); int MountRootByLabel(const char* label);
// Probe all discovered storage partitions and auto-mount recognized filesystems. // Probe all discovered storage partitions and auto-mount recognized filesystems.
@@ -28,7 +30,8 @@ namespace Fs::FsProbe {
void MountPartitions(int firstDrive = 1); void MountPartitions(int firstDrive = 1);
// Probe discovered partitions for a single block device and mount recognized // 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); int MountPartitionsForBlockDevice(int blockDevIndex, int firstDrive = 0);
// Try to mount a single partition (by global partition index) as the given VFS drive. // Try to mount a single partition (by global partition index) as the given VFS drive.