feat: fully async Intel BT firmware bring-up + HCI multi-packet event reassembly
The BT firmware download now runs from the idle loop after boot (zero boot stall), completing the async goal. What made every earlier deferral attempt fail was a months-latent HCI-layer bug, not the deferred environment: WaitCommandComplete returned after the FIRST USB packet of an event, but events larger than the 64-byte interrupt max-packet (like the AX211's 96-byte FC05 TLV version response) span several packets. Sending the next command while the tail of the previous response was still in flight wedges the AX211 bootloader into permanently ignoring commands. Boot-time flanterm rendering added milliseconds between commands and accidentally paced the protocol past the race -- which is why the synchronous bring-up always worked and every log-suppressed (deferred) bring-up went mute at FC05 #2, regardless of scheduling/MSI/xHCI fixes. Fix: reassemble multi-packet Command Complete/Status events in the transfer callback; the mailbox is marked ready only when the declared event length has fully arrived. This inherently paces command flow and, as a bonus, the TLV version read now sees the full response (sbe_type present -> ECDSA/RSA selection is no longer a guess). Also: per-slot EP0 completion tracking in the xHCI (a waiting ControlTransfer can no longer be released early by another device's EP0 completion). Verified on the AX211: instant boot, background download, real BD_ADDR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 25
|
#define MONTAUK_BUILD_NUMBER 29
|
||||||
|
|||||||
@@ -34,6 +34,18 @@ namespace Drivers::USB::Bluetooth::Hci {
|
|||||||
static volatile uint32_t g_eventLen = 0;
|
static volatile uint32_t g_eventLen = 0;
|
||||||
static volatile bool g_eventReady = false;
|
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
|
// Firmware-phase diagnostics. g_fwTrace turns on a bounded per-completion
|
||||||
// trace of the interrupt IN pipe (SetFwTrace, driven by the bring-up);
|
// trace of the interrupt IN pipe (SetFwTrace, driven by the bring-up);
|
||||||
// g_intInCompletions counts interrupt-IN completions since power-up so
|
// g_intInCompletions counts interrupt-IN completions since power-up so
|
||||||
@@ -355,12 +367,33 @@ namespace Drivers::USB::Bluetooth::Hci {
|
|||||||
// consumed by WaitCommandComplete / WaitCommandStatus.
|
// consumed by WaitCommandComplete / WaitCommandStatus.
|
||||||
uint8_t evtCode = data[0];
|
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;
|
uint32_t copyLen = length;
|
||||||
if (copyLen > sizeof(g_eventBuf)) copyLen = sizeof(g_eventBuf);
|
if (copyLen > sizeof(g_eventBuf)) copyLen = sizeof(g_eventBuf);
|
||||||
memcpy(g_eventBuf, data, copyLen);
|
memcpy(g_eventBuf, data, copyLen);
|
||||||
g_eventLen = 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 {
|
} else {
|
||||||
// Process immediately (inquiry results, connection events, etc.)
|
// Process immediately (inquiry results, connection events, etc.)
|
||||||
ProcessEvent(data, length);
|
ProcessEvent(data, length);
|
||||||
@@ -496,6 +529,11 @@ namespace Drivers::USB::Bluetooth::Hci {
|
|||||||
// wValue = 0, wIndex = 0
|
// wValue = 0, wIndex = 0
|
||||||
// wLength = sizeof(CommandHeader) + paramLen
|
// 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.
|
// Use DMA-allocated buffer (not stack) for the command data.
|
||||||
// xHCI reads from this buffer via DMA for OUT transfers.
|
// xHCI reads from this buffer via DMA for OUT transfers.
|
||||||
memset(g_cmdDmaBuf, 0, 512);
|
memset(g_cmdDmaBuf, 0, 512);
|
||||||
|
|||||||
@@ -94,9 +94,16 @@ namespace Drivers::USB::Xhci {
|
|||||||
static volatile uint32_t g_cmdCompletionCode = 0;
|
static volatile uint32_t g_cmdCompletionCode = 0;
|
||||||
volatile uint32_t g_cmdCompletionSlotId = 0; // not static: accessed by UsbDevice.cpp
|
volatile uint32_t g_cmdCompletionSlotId = 0; // not static: accessed by UsbDevice.cpp
|
||||||
|
|
||||||
// Transfer completion tracking (for EP0 control transfers during init)
|
// EP0 control-transfer completion tracking -- PER SLOT. This was a single
|
||||||
static volatile bool g_xferCompleted = false;
|
// global, which aliased completions across devices: once userspace is up,
|
||||||
static volatile uint32_t g_xferCompletionCode = 0;
|
// 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
|
// Synchronous bulk transfer tracking. Only one storage-style blocking bulk
|
||||||
// transfer is active at a time.
|
// transfer is active at a time.
|
||||||
@@ -514,9 +521,13 @@ namespace Drivers::USB::Xhci {
|
|||||||
lastSlot = slotId; lastEp = epDci; lastCC = completionCode;
|
lastSlot = slotId; lastEp = epDci; lastCC = completionCode;
|
||||||
|
|
||||||
if (epDci == 1) {
|
if (epDci == 1) {
|
||||||
// EP0 (DCI 1) - control transfer completion
|
// EP0 (DCI 1) - control transfer completion, attributed
|
||||||
g_xferCompletionCode = completionCode;
|
// to the slot it belongs to (see the per-slot note at
|
||||||
g_xferCompleted = true;
|
// 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) {
|
} else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) {
|
||||||
UsbDeviceInfo& dev = g_devices[slotId];
|
UsbDeviceInfo& dev = g_devices[slotId];
|
||||||
|
|
||||||
@@ -814,7 +825,7 @@ namespace Drivers::USB::Xhci {
|
|||||||
AdvanceEP0Ring(dev);
|
AdvanceEP0Ring(dev);
|
||||||
|
|
||||||
// Ring doorbell for this slot, target EP0 (DCI 1)
|
// Ring doorbell for this slot, target EP0 (DCI 1)
|
||||||
g_xferCompleted = false;
|
g_xferCompleted[slotId] = false;
|
||||||
WriteDoorbell(slotId, 1);
|
WriteDoorbell(slotId, 1);
|
||||||
|
|
||||||
// If we are nested inside THIS core's PollEvents (e.g. an HCI reply sent
|
// 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();
|
uint64_t xferStart = Timekeeping::GetMilliseconds();
|
||||||
while (Timekeeping::GetMilliseconds() - xferStart < 2000) {
|
while (Timekeeping::GetMilliseconds() - xferStart < 2000) {
|
||||||
PollEvents();
|
PollEvents();
|
||||||
if (g_xferCompleted) {
|
if (g_xferCompleted[slotId]) {
|
||||||
g_controlXferLock.Release();
|
g_controlXferLock.Release();
|
||||||
return g_xferCompletionCode;
|
return g_xferCompletionCode[slotId];
|
||||||
}
|
}
|
||||||
for (int j = 0; j < 100; j++) {
|
for (int j = 0; j < 100; j++) {
|
||||||
asm volatile("" ::: "memory");
|
asm volatile("" ::: "memory");
|
||||||
|
|||||||
+13
-11
@@ -186,17 +186,12 @@ extern "C" void kmain() {
|
|||||||
|
|
||||||
Fs::InitializeBootFilesystems(boot.modules);
|
Fs::InitializeBootFilesystems(boot.modules);
|
||||||
|
|
||||||
// A Bluetooth adapter present at boot enumerates during the xHCI port scan,
|
// EXPERIMENT (wip/bt-deferred-init): the Bluetooth firmware bring-up is
|
||||||
// before the ramdisk is mounted. Now that drive 0 is up, finish any
|
// NOT run here -- the idle loop picks it up via ServiceDeferredInit()
|
||||||
// firmware-dependent bring-up that was deferred (loads ibt-*.sfi/.ddc).
|
// after boot, with CPU reservation, the IRQ-safe trace ring, and the
|
||||||
//
|
// bulk-pipelined download. Earlier deferral attempts (2026-07-05) made
|
||||||
// Deliberately synchronous, HERE, pre-Sched/pre-SMP/pre-MSI: deferring
|
// the AX211 stop answering after the first FC05; this run captures a
|
||||||
// this to the post-boot idle loop made the AX211 bootloader stop
|
// ring trace of exactly what the event pipe delivers in deferred mode.
|
||||||
// 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();
|
Hal::LoadTSS();
|
||||||
montauk::abi::InitializeSyscalls();
|
montauk::abi::InitializeSyscalls();
|
||||||
@@ -207,6 +202,13 @@ extern "C" void kmain() {
|
|||||||
// Boot Application Processors (all subsystems ready, APs can schedule)
|
// Boot Application Processors (all subsystems ready, APs can schedule)
|
||||||
Smp::BootAPs(boot.smp);
|
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
|
// Flush any stale PS/2 mouse bytes that accumulated during boot
|
||||||
// (edge-triggered IRQs can be lost while spinlocks disable interrupts)
|
// (edge-triggered IRQs can be lost while spinlocks disable interrupts)
|
||||||
Drivers::PS2::Mouse::FlushState();
|
Drivers::PS2::Mouse::FlushState();
|
||||||
|
|||||||
Reference in New Issue
Block a user