diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index b148ae7..694c0f8 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 17 +#define MONTAUK_BUILD_NUMBER 22 diff --git a/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp b/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp index c0cb3ff..7b40193 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Bluetooth.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include 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 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; } // ========================================================================= diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp index 9ff5697..bef4301 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp @@ -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 g_fwTrace{false}; + static std::atomic g_fwTraceCount{0}; + static std::atomic 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; } diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.hpp b/kernel/src/Drivers/USB/Bluetooth/Hci.hpp index 928443f..b1c1f9d 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.hpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.hpp @@ -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 diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index e247401..5e6569e 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -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) { diff --git a/kernel/src/Hal/SmpBoot.hpp b/kernel/src/Hal/SmpBoot.hpp index abf4723..223184f 100644 --- a/kernel/src/Hal/SmpBoot.hpp +++ b/kernel/src/Hal/SmpBoot.hpp @@ -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))); diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index c1267a8..2810861 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -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(); diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index ffc4f73..8dfad62 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -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; diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index ce31515..c712d2d 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -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