diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 39cdee0..0a5fc31 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 25 +#define MONTAUK_BUILD_NUMBER 29 diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp index cb1762f..1a69f9e 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp @@ -34,6 +34,18 @@ namespace Drivers::USB::Bluetooth::Hci { static volatile uint32_t g_eventLen = 0; static volatile bool g_eventReady = false; + // Continuation bytes still expected for the event in g_eventBuf. An HCI + // event larger than the interrupt endpoint's max packet (64) arrives as + // several USB packets; only the first starts with the event header. The + // mailbox is marked ready ONLY once the full declared length has arrived. + // This matters beyond correctness of the data: sending the NEXT command + // while the previous response is still mid-transmission wedges the AX211 + // bootloader into permanently ignoring commands. (For months the boot + // console's slow flanterm rendering accidentally paced commands past this; + // any log-suppressed/deferred bring-up hit it deterministically at the + // 96-byte FC05 TLV response.) + static volatile uint32_t g_eventRemaining = 0; + // 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 @@ -355,12 +367,33 @@ namespace Drivers::USB::Bluetooth::Hci { // consumed by WaitCommandComplete / WaitCommandStatus. uint8_t evtCode = data[0]; - if (evtCode == EVT_COMMAND_COMPLETE || evtCode == EVT_COMMAND_STATUS) { + if (g_eventRemaining > 0) { + // Continuation packet of a buffered multi-packet event: + // append; ready only once the declared length is in. + uint32_t take = length; + if (take > g_eventRemaining) take = g_eventRemaining; + uint32_t room = sizeof(g_eventBuf) - g_eventLen; + uint32_t copyLen = (take < room) ? take : room; + if (copyLen > 0) { + memcpy(g_eventBuf + g_eventLen, data, copyLen); + g_eventLen = g_eventLen + copyLen; + } + g_eventRemaining = g_eventRemaining - take; + if (g_eventRemaining == 0) g_eventReady = true; + } else 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; + + // Total event size = header (2) + declared parameter len. + // Larger than this packet -> more packets follow. + uint32_t total = (length >= 2) ? (2u + data[1]) : length; + if (total > length) { + g_eventRemaining = total - length; + } else { + g_eventReady = true; + } } else { // Process immediately (inquiry results, connection events, etc.) ProcessEvent(data, length); @@ -496,6 +529,11 @@ namespace Drivers::USB::Bluetooth::Hci { // wValue = 0, wIndex = 0 // wLength = sizeof(CommandHeader) + paramLen + // A new command abandons any half-assembled response from the previous + // one (only possible if the controller died mid-event) -- otherwise a + // stale g_eventRemaining would swallow this command's reply. + g_eventRemaining = 0; + // Use DMA-allocated buffer (not stack) for the command data. // xHCI reads from this buffer via DMA for OUT transfers. memset(g_cmdDmaBuf, 0, 512); diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index 5e6569e..892baa9 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -94,9 +94,16 @@ namespace Drivers::USB::Xhci { static volatile uint32_t g_cmdCompletionCode = 0; volatile uint32_t g_cmdCompletionSlotId = 0; // not static: accessed by UsbDevice.cpp - // Transfer completion tracking (for EP0 control transfers during init) - static volatile bool g_xferCompleted = false; - static volatile uint32_t g_xferCompletionCode = 0; + // EP0 control-transfer completion tracking -- PER SLOT. This was a single + // global, which aliased completions across devices: once userspace is up, + // another device's EP0 completion (HID idle/LED traffic, a hotplug rescan) + // released a waiting ControlTransfer EARLY, before its own status stage + // finished; the next command then rewrote TRBs over a still-executing TD + // and garbled the wire. Observed as the AX211 BT bootloader going + // permanently mute after the first FC05 whenever its bring-up ran + // post-boot (deferred) instead of during single-user early boot. + static volatile bool g_xferCompleted[MAX_SLOTS + 1] = {}; + static volatile uint32_t g_xferCompletionCode[MAX_SLOTS + 1] = {}; // Synchronous bulk transfer tracking. Only one storage-style blocking bulk // transfer is active at a time. @@ -514,9 +521,13 @@ namespace Drivers::USB::Xhci { lastSlot = slotId; lastEp = epDci; lastCC = completionCode; if (epDci == 1) { - // EP0 (DCI 1) - control transfer completion - g_xferCompletionCode = completionCode; - g_xferCompleted = true; + // EP0 (DCI 1) - control transfer completion, attributed + // to the slot it belongs to (see the per-slot note at + // the declaration). + if (slotId <= MAX_SLOTS) { + g_xferCompletionCode[slotId] = completionCode; + g_xferCompleted[slotId] = true; + } } else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) { UsbDeviceInfo& dev = g_devices[slotId]; @@ -814,7 +825,7 @@ namespace Drivers::USB::Xhci { AdvanceEP0Ring(dev); // Ring doorbell for this slot, target EP0 (DCI 1) - g_xferCompleted = false; + g_xferCompleted[slotId] = false; WriteDoorbell(slotId, 1); // If we are nested inside THIS core's PollEvents (e.g. an HCI reply sent @@ -835,9 +846,9 @@ namespace Drivers::USB::Xhci { uint64_t xferStart = Timekeeping::GetMilliseconds(); while (Timekeeping::GetMilliseconds() - xferStart < 2000) { PollEvents(); - if (g_xferCompleted) { + if (g_xferCompleted[slotId]) { g_controlXferLock.Release(); - return g_xferCompletionCode; + return g_xferCompletionCode[slotId]; } for (int j = 0; j < 100; j++) { asm volatile("" ::: "memory"); diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 2810861..f46b804 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -186,17 +186,12 @@ extern "C" void kmain() { Fs::InitializeBootFilesystems(boot.modules); - // 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(); + // EXPERIMENT (wip/bt-deferred-init): the Bluetooth firmware bring-up is + // NOT run here -- the idle loop picks it up via ServiceDeferredInit() + // after boot, with CPU reservation, the IRQ-safe trace ring, and the + // bulk-pipelined download. Earlier deferral attempts (2026-07-05) made + // the AX211 stop answering after the first FC05; this run captures a + // ring trace of exactly what the event pipe delivers in deferred mode. Hal::LoadTSS(); montauk::abi::InitializeSyscalls(); @@ -207,6 +202,13 @@ extern "C" void kmain() { // Boot Application Processors (all subsystems ready, APs can schedule) Smp::BootAPs(boot.smp); + // The Bluetooth firmware bring-up is deferred to the idle loop + // (ServiceDeferredInit from IdleOnce) so its download never stalls boot. + // Requires the HCI multi-packet event reassembly in Hci.cpp: without it, + // the next command races the tail of a multi-packet response and wedges + // the AX211 bootloader -- which only ever worked before because boot-time + // flanterm rendering accidentally paced the commands. + // Flush any stale PS/2 mouse bytes that accumulated during boot // (edge-triggered IRQs can be lost while spinlocks disable interrupts) Drivers::PS2::Mouse::FlushState();