wip: async BT firmware download attempt — DO NOT MERGE, breaks AX211 bring-up
Deferring the Intel BT firmware download off the boot path made the AX211 bootloader stop answering after the first FC05; even the final synchronous revert freezes boot, so one of the 'neutral' fixes kept in this diff breaks the bring-up on its own (candidates: BT-TRACE logging inside TransferCallback, unconditional interrupt-IN re-queue after error completions on a halted EP, xHCI interrupt-IN ZLP len fix interacting with HID, InPollContext owner check). Full history + next experiments in memory notes, 2026-07-05/06. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 17
|
||||
#define MONTAUK_BUILD_NUMBER 22
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <atomic>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -31,8 +33,10 @@ namespace Drivers::USB::Bluetooth {
|
||||
// True when the USB transport is up but the firmware-dependent HCI init is
|
||||
// still waiting for the ramdisk (drive 0) to be mounted. Set when an
|
||||
// adapter enumerates during the boot port scan, which runs before the boot
|
||||
// filesystems are mounted; cleared by ServiceDeferredInit() once VFS is up.
|
||||
static bool g_initPending = false;
|
||||
// filesystems are mounted; claimed (atomically -- the pickup runs from the
|
||||
// idle loop, concurrently with the rest of the system) by
|
||||
// ServiceDeferredInit() once VFS is up.
|
||||
static std::atomic<bool> g_initPending{false};
|
||||
|
||||
// Forward declaration: firmware-dependent HCI bring-up, run once VFS is up.
|
||||
static void CompleteInit();
|
||||
@@ -236,15 +240,19 @@ namespace Drivers::USB::Bluetooth {
|
||||
|
||||
// Start the event pipe BEFORE sending any HCI commands.
|
||||
// HCI command responses arrive as events on the interrupt IN endpoint,
|
||||
// so it must be queued to receive them.
|
||||
// so it must be queued to receive them. Deliberately done HERE, at
|
||||
// enumeration time, not in the deferred bring-up: this preserves the
|
||||
// exact transport timing of the original synchronous boot path.
|
||||
Hci::StartEventPipe();
|
||||
|
||||
// The firmware download path reads the .sfi/.ddc images from the
|
||||
// ramdisk (drive 0). Adapters present at boot enumerate during the
|
||||
// xHCI port scan, which runs before the boot filesystems are mounted,
|
||||
// so defer the firmware-dependent bring-up until VFS is available.
|
||||
// The idle loop (ServiceDeferredInit) picks it up after boot, keeping
|
||||
// the multi-second firmware download off the boot-critical path.
|
||||
if (!Fs::Vfs::IsDriveRegistered(0)) {
|
||||
g_initPending = true;
|
||||
g_initPending.store(true, std::memory_order_release);
|
||||
KernelLogStream(INFO, "BT") << "Transport up; deferring init until ramdisk is mounted";
|
||||
return;
|
||||
}
|
||||
@@ -362,12 +370,33 @@ namespace Drivers::USB::Bluetooth {
|
||||
// =========================================================================
|
||||
|
||||
void ServiceDeferredInit() {
|
||||
if (!g_initPending || g_initialized) return;
|
||||
if (!g_initPending.load(std::memory_order_relaxed) || g_initialized) return;
|
||||
if (!Fs::Vfs::IsDriveRegistered(0)) return; // ramdisk still not mounted
|
||||
if (Xhci::InPollContext()) return; // never nest under PollEvents
|
||||
|
||||
g_initPending = false;
|
||||
KernelLogStream(INFO, "BT") << "Ramdisk mounted; completing Bluetooth init";
|
||||
// Claim the pending init (this runs from the idle loop; make sure only
|
||||
// one pass performs the bring-up). The firmware download inside takes
|
||||
// seconds -- running it here instead of on the boot path is what keeps
|
||||
// boot fast.
|
||||
bool expected = true;
|
||||
if (!g_initPending.compare_exchange_strong(expected, false,
|
||||
std::memory_order_acquire)) return;
|
||||
|
||||
KernelLogStream(INFO, "BT") << "Completing deferred Bluetooth init in background";
|
||||
|
||||
// Reserve this CPU for the duration. The bring-up overlaps desktop
|
||||
// startup, and if the scheduler tick pulls this idle context away
|
||||
// whenever a process is ready, the HCI waits' wall-clock timeouts
|
||||
// expire with almost no polling done. Reserved, the bring-up runs
|
||||
// uninterrupted here while processes use other CPUs; on a single-CPU
|
||||
// system this briefly pauses userspace, matching the old synchronous
|
||||
// behavior minus the boot-path stall.
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
if (cpu) cpu->reservedForKernelWork = true;
|
||||
Hci::SetFwTrace(true); // bounded per-completion event-pipe trace
|
||||
CompleteInit();
|
||||
Hci::SetFwTrace(false);
|
||||
if (cpu) cpu->reservedForKernelWork = false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
||||
@@ -34,6 +34,19 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
static volatile uint32_t g_eventLen = 0;
|
||||
static volatile bool g_eventReady = false;
|
||||
|
||||
// Firmware-phase diagnostics. g_fwTrace turns on a bounded per-completion
|
||||
// trace of the interrupt IN pipe (SetFwTrace, driven by the bring-up);
|
||||
// g_intInCompletions counts interrupt-IN completions since power-up so
|
||||
// wait-loop timeouts can report whether the pipe was delivering at all.
|
||||
static std::atomic<bool> g_fwTrace{false};
|
||||
static std::atomic<uint32_t> g_fwTraceCount{0};
|
||||
static std::atomic<uint32_t> g_intInCompletions{0};
|
||||
|
||||
void SetFwTrace(bool on) {
|
||||
g_fwTrace.store(on, std::memory_order_relaxed);
|
||||
if (on) g_fwTraceCount.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// ACL receive ring buffer. The bulk-IN callback (nested under PollEvents)
|
||||
// only copies an incoming packet into a slot; DrainEvents() processes them
|
||||
// at top level. A ring (not a single buffer) is required because the headset
|
||||
@@ -276,26 +289,53 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
uint8_t intDci = dev->InterruptEpNum ? (dev->InterruptEpNum * 2 + 1) : 0;
|
||||
uint8_t bulkInDci = dev->BulkInEpNum ? (dev->BulkInEpNum * 2 + 1) : 0;
|
||||
|
||||
if (epDci == intDci && data && length > 0) {
|
||||
// HCI Event received on interrupt IN.
|
||||
// Dispatch asynchronous events (inquiry results, connection events,
|
||||
// etc.) immediately so they are never lost. Only buffer
|
||||
// Command Complete / Command Status events — those are consumed
|
||||
// by WaitCommandComplete / WaitCommandStatus.
|
||||
uint8_t evtCode = (length >= 1) ? data[0] : 0;
|
||||
if (epDci == intDci) {
|
||||
g_intInCompletions.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
if (evtCode == EVT_COMMAND_COMPLETE || evtCode == EVT_COMMAND_STATUS) {
|
||||
uint32_t copyLen = length;
|
||||
if (copyLen > sizeof(g_eventBuf)) copyLen = sizeof(g_eventBuf);
|
||||
memcpy(g_eventBuf, data, copyLen);
|
||||
g_eventLen = copyLen;
|
||||
g_eventReady = true;
|
||||
} else {
|
||||
// Process immediately (inquiry results, connection events, etc.)
|
||||
ProcessEvent(data, length);
|
||||
// Bounded bring-up trace: one line per interrupt-IN completion
|
||||
// while the firmware phase runs, so a hardware boot log shows
|
||||
// exactly what the event pipe delivered (or didn't).
|
||||
if (g_fwTrace.load(std::memory_order_relaxed)) {
|
||||
uint32_t n = g_fwTraceCount.fetch_add(1, std::memory_order_relaxed);
|
||||
if (n < 48) {
|
||||
KernelLogStream(INFO, "BT-TRACE") << "int-in cc=" << (uint64_t)completionCode
|
||||
<< " len=" << length
|
||||
<< " b0=" << base::hex << (uint64_t)(data && length ? data[0] : 0xEE)
|
||||
<< " b3b4=" << (uint64_t)(data && length >= 5
|
||||
? ((uint32_t)data[3] | ((uint32_t)data[4] << 8)) : 0xEEEE)
|
||||
// ncmd = HCI command-flow-control window in a Command
|
||||
// Complete; 0 here would explain a controller ignoring
|
||||
// all subsequent commands.
|
||||
<< " ncmd=" << (uint64_t)(data && length >= 3 ? data[2] : 0xEE)
|
||||
<< base::dec << (g_eventReady ? " [mailbox-full]" : "");
|
||||
}
|
||||
}
|
||||
|
||||
// Re-queue interrupt transfer for next event
|
||||
if (data && length > 0) {
|
||||
// HCI Event received on interrupt IN.
|
||||
// Dispatch asynchronous events (inquiry results, connection
|
||||
// events, etc.) immediately so they are never lost. Only
|
||||
// buffer Command Complete / Command Status events — those are
|
||||
// consumed by WaitCommandComplete / WaitCommandStatus.
|
||||
uint8_t evtCode = data[0];
|
||||
|
||||
if (evtCode == EVT_COMMAND_COMPLETE || evtCode == EVT_COMMAND_STATUS) {
|
||||
uint32_t copyLen = length;
|
||||
if (copyLen > sizeof(g_eventBuf)) copyLen = sizeof(g_eventBuf);
|
||||
memcpy(g_eventBuf, data, copyLen);
|
||||
g_eventLen = copyLen;
|
||||
g_eventReady = true;
|
||||
} else {
|
||||
// Process immediately (inquiry results, connection events, etc.)
|
||||
ProcessEvent(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
// ALWAYS re-queue, including error (data == nullptr) and 0-length
|
||||
// (ZLP) completions. Re-arming only on data>0 meant a single such
|
||||
// completion silently killed the event pipe for good -- every
|
||||
// later command then "timed out" with no xHCI error in sight.
|
||||
// Mirrors the bulk-IN rule below ("must keep cycling").
|
||||
Xhci::QueueInterruptTransfer(slotId);
|
||||
} else if (epDci == bulkInDci) {
|
||||
// ACL data received on bulk IN -> copy into the ring; processed by
|
||||
@@ -1035,6 +1075,17 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
if (!outBuf || maxLen <= 0) return -1;
|
||||
|
||||
uint8_t param = 0xFF;
|
||||
uint32_t completionsBefore = g_intInCompletions.load(std::memory_order_relaxed);
|
||||
|
||||
// Up to 3 attempts, 150ms apart: a bootloader that dropped one FC05
|
||||
// (busy, or still flushing a previous multi-packet response) gets a
|
||||
// fresh chance instead of failing the whole bring-up.
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
if (attempt > 0) {
|
||||
KernelLogStream(WARNING, "BT-HCI") << "Retrying Intel TLV version read (attempt "
|
||||
<< (uint64_t)(attempt + 1) << ")";
|
||||
PollWait(150);
|
||||
}
|
||||
if (!SendCommand(OP_INTEL_READ_VERSION, ¶m, 1)) return -1;
|
||||
|
||||
// Wait for the matching Command Complete and copy the *actually
|
||||
@@ -1064,7 +1115,12 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
for (int j = 0; j < 100; j++) asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "BT-HCI") << "ReadIntelVersionTlv timeout";
|
||||
// Distinguish "pipe dead" (0 completions) from "responses arriving but
|
||||
// never matching" (mailbox scrambling) -- the two need different fixes.
|
||||
KernelLogStream(WARNING, "BT-HCI") << "ReadIntelVersionTlv timeout ("
|
||||
<< (uint64_t)(g_intInCompletions.load(std::memory_order_relaxed) - completionsBefore)
|
||||
<< " int-in completions during wait)";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -265,6 +265,12 @@ namespace Drivers::USB::Bluetooth::Hci {
|
||||
// Returns true if a matching bond was found and forgotten.
|
||||
bool ForgetBond(const uint8_t* addr);
|
||||
|
||||
// Toggle the bounded firmware-phase interrupt-IN trace (one log line per
|
||||
// completion, capped) plus mailbox-overwrite flags. Enabled by the
|
||||
// deferred bring-up so a failing hardware boot log shows exactly what the
|
||||
// event pipe delivered.
|
||||
void SetFwTrace(bool on);
|
||||
|
||||
// Non-blocking peek at the most recent 0xFF/0x06 secure-send result without
|
||||
// consuming it. Returns true if one has arrived since the last
|
||||
// ClearSecureSendResult(). The payload loop uses this to catch a mid-stream
|
||||
|
||||
@@ -428,7 +428,13 @@ namespace Drivers::USB::Xhci {
|
||||
// sent from a Bluetooth event handler). Such callers must fire-and-forget,
|
||||
// not wait, since a nested PollEvents is a no-op.
|
||||
bool InPollContext() {
|
||||
return g_pollActive.load(std::memory_order_relaxed);
|
||||
// Same-core only: "nested under PollEvents" means an event callback on
|
||||
// THIS core issued the query. A different core merely polling must
|
||||
// not make callers (WaitCommandComplete & co.) skip their wait -- that
|
||||
// reported success before the reply arrived (same cross-core bug
|
||||
// ControlTransfer's trueNested check already guards against).
|
||||
return g_pollActive.load(std::memory_order_relaxed) &&
|
||||
g_pollOwnerCpu.load(std::memory_order_relaxed) == CurrentCpuIndex();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -568,9 +574,17 @@ namespace Drivers::USB::Xhci {
|
||||
g_transferCallbacks[slotId](slotId, epDci,
|
||||
nullptr, 0, completionCode);
|
||||
} else if (epDci == intDci) {
|
||||
// Interrupt IN — HID or callback dispatch
|
||||
uint16_t len = dev.InterruptMaxPacket;
|
||||
if (residual < len) len = dev.InterruptMaxPacket - (uint16_t)residual;
|
||||
// Interrupt IN — HID or callback dispatch.
|
||||
// len = actually-transferred bytes. A ZLP has
|
||||
// residual == requested, so len MUST be 0 (same
|
||||
// fix as bulk IN above): the old code left len at
|
||||
// the full max-packet and handed the callback a
|
||||
// slice of STALE DMA buffer -- for Bluetooth that
|
||||
// can replay the PREVIOUS HCI event into the
|
||||
// command-complete mailbox.
|
||||
uint16_t reqLen = dev.InterruptMaxPacket;
|
||||
uint16_t len = (residual < reqLen)
|
||||
? (uint16_t)(reqLen - residual) : 0;
|
||||
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HID) {
|
||||
if (dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
|
||||
|
||||
@@ -40,6 +40,14 @@ namespace Smp {
|
||||
Hal::TSS64* tss; // pointer to this CPU's TSS
|
||||
bool hasMwait; // CPU supports MONITOR/MWAIT
|
||||
|
||||
// When set, the scheduler tick must NOT pull this (idle) CPU away to
|
||||
// run a ready process: its idle context is busy with long-running
|
||||
// kernel work whose wall-clock-bounded waits would spuriously expire
|
||||
// if the context only ran when nothing else is ready (observed: the
|
||||
// deferred Bluetooth firmware download racing login-screen startup).
|
||||
// Other CPUs schedule normally while this is held.
|
||||
volatile bool reservedForKernelWork = false;
|
||||
|
||||
// Per-CPU GDT and TSS (APs use these; BSP uses globals)
|
||||
Hal::BasicGDT cpuGdt __attribute__((aligned(16)));
|
||||
Hal::TSS64 cpuTss __attribute__((aligned(16)));
|
||||
|
||||
@@ -189,6 +189,13 @@ extern "C" void kmain() {
|
||||
// A Bluetooth adapter present at boot enumerates during the xHCI port scan,
|
||||
// before the ramdisk is mounted. Now that drive 0 is up, finish any
|
||||
// firmware-dependent bring-up that was deferred (loads ibt-*.sfi/.ddc).
|
||||
//
|
||||
// Deliberately synchronous, HERE, pre-Sched/pre-SMP/pre-MSI: deferring
|
||||
// this to the post-boot idle loop made the AX211 bootloader stop
|
||||
// answering after the first FC05 (2026-07-05, three attempts: CPU
|
||||
// reservation, restored pipe timing, event-pipe fixes -- identical
|
||||
// failure each time; suspected xHCI-MSI-context event processing, see
|
||||
// memory notes). Boot pays the download cost until that is understood.
|
||||
Drivers::USB::Bluetooth::ServiceDeferredInit();
|
||||
|
||||
Hal::LoadTSS();
|
||||
|
||||
@@ -945,7 +945,12 @@ namespace Sched {
|
||||
// scanning 256 process slots on every tick. On a 32-core
|
||||
// system with 27 idle CPUs, this avoids ~7M cache-line
|
||||
// reads/sec from the process table.
|
||||
if (readyCount > 0) {
|
||||
//
|
||||
// A CPU whose idle context is reserved for long-running kernel
|
||||
// work (e.g. the deferred Bluetooth firmware download) is left
|
||||
// alone: stealing it starves that work of CPU time while its
|
||||
// wall-clock timeouts keep running.
|
||||
if (readyCount > 0 && !cpu->reservedForKernelWork) {
|
||||
Schedule();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -212,6 +212,13 @@ namespace Timekeeping {
|
||||
Drivers::USB::Xhci::ProcessDeferredWork();
|
||||
}
|
||||
|
||||
// Complete any boot-deferred Bluetooth bring-up (Intel firmware
|
||||
// download) here instead of on the boot path: the download takes
|
||||
// seconds and used to stall kmain before the first process spawned.
|
||||
// Cheap no-op unless an adapter is waiting; self-claiming, and safe
|
||||
// to preempt (the scheduler saves/resumes the idle context).
|
||||
Drivers::USB::Bluetooth::ServiceDeferredInit();
|
||||
|
||||
// Service Bluetooth inbound traffic (the headset's SDP/AVRCP queries,
|
||||
// AVDTP commands, ACL flow-control credits) whenever a core idles.
|
||||
// Without this, BT events are only processed while some syscall happens
|
||||
|
||||
Reference in New Issue
Block a user