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
+340 -57

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
(`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
+1 -1
View File
@@ -12,4 +12,4 @@
#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/Platform.hpp>
#include <Fs/Boot.hpp>
#include <Fs/Vfs.hpp>
#include <Sched/Scheduler.hpp>
#include <Ipc/Ipc.hpp>
#include <Api/Syscall.hpp>
@@ -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");
+4
View File
@@ -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
+5
View File
@@ -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();
+5
View File
@@ -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);
}
+4
View File
@@ -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();
+90 -23
View File
@@ -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;
+64 -28
View File
@@ -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)
+107 -1
View File
@@ -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
// -------------------------------------------------------------------------
+12
View File
@@ -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.
+23 -2
View File
@@ -11,6 +11,8 @@
#include <Fs/Fat32.hpp>
#include <Fs/Ext2.hpp>
#include <Fs/FsProbe.hpp>
#include <Drivers/Init.hpp>
#include <Timekeeping/Timer.hpp>
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);
}
+10 -1
View File
@@ -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;
+4 -1
View File
@@ -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.