feat: wait up to 10s for a late MONTAUKROOT, retry USB enumeration after a port reset, recover halted EP0/bulk endpoints, MSC card reader settle
This commit is contained in:
1 parent
ffee9002a6
commit
93d8becce3
14 files changed
+317
-34
No files matched your search
@@ -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
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 80
|
||||
#define MONTAUK_BUILD_NUMBER 86
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
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,
|
||||
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;
|
||||
}
|
||||
return ok;
|
||||
if (!ScsiCommand(dev, cdb, 6, dev.ScratchBuf, dev.ScratchPhys, 18, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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++) {
|
||||
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;
|
||||
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) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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,9 +132,10 @@ namespace Drivers::USB::UsbDevice {
|
||||
<< ", root port " << (uint64_t)location.RootPort << ") speed=" << SpeedToString(speed);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
for (int attempt = 1; ; attempt++) {
|
||||
// -------------------------------------------------------------
|
||||
// Step 1: Enable Slot
|
||||
// -----------------------------------------------------------------
|
||||
// -------------------------------------------------------------
|
||||
Xhci::TRB enableSlotTrb = {};
|
||||
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";
|
||||
|
||||
bool isHub = false;
|
||||
bool retryable = false;
|
||||
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
|
||||
// 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) = {};
|
||||
|
||||
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
|
||||
|
||||
// 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;
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,9 +134,22 @@ namespace Fs {
|
||||
Fat32::RegisterProbe();
|
||||
Ext2::RegisterProbe();
|
||||
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")
|
||||
<< "No ramdisk and no " << RootLabel << " partition; using the first one found";
|
||||
}
|
||||
}
|
||||
FsProbe::MountPartitions(hasRamdisk ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user