fix: improve Bluetooth reliability

This commit is contained in:
2026-07-31 18:42:48 +02:00
parent 4627ac92fd
commit 18122136dd
20 changed files with 1870 additions and 587 deletions
+4 -1
View File
@@ -56,7 +56,10 @@ namespace montauk::abi {
(Drivers::Audio::Mixer::Output)value);
if (cmd == AUDIO_CTL_BT_STATUS) {
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0;
return (int64_t)Drivers::USB::Bluetooth::A2dp::GetState();
if (Drivers::USB::Bluetooth::A2dp::IsReady()) return 2;
return Drivers::USB::Bluetooth::A2dp::GetState()
== Drivers::USB::Bluetooth::A2dp::State::Idle
? 0 : 1;
}
return (int64_t)Drivers::Audio::Mixer::Control(handle, cmd, value);
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 30
#define MONTAUK_BUILD_NUMBER 33
+1 -1
View File
@@ -167,7 +167,7 @@ namespace montauk::abi {
static constexpr int AUDIO_CTL_PAUSE = 3;
static constexpr int AUDIO_CTL_GET_OUTPUT = 4; // 0=HDA, 1=Bluetooth
static constexpr int AUDIO_CTL_SET_OUTPUT = 5; // Switch audio output
static constexpr int AUDIO_CTL_BT_STATUS = 6; // Get Bluetooth status
static constexpr int AUDIO_CTL_BT_STATUS = 6; // 0=unavailable, 1=setup, 2=ready
static constexpr int AUDIO_CTL_SET_MASTER_VOLUME = 7; // 0-100
static constexpr int AUDIO_CTL_GET_MASTER_VOLUME = 8;
static constexpr int AUDIO_CTL_SET_MUTE = 9; // 0/1, per-stream
+4 -4
View File
@@ -9,6 +9,7 @@
#include "IntelHda.hpp"
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
#include <Drivers/USB/Bluetooth/A2dp.hpp>
#include <Drivers/USB/Bluetooth/Avrcp.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/HHDM.hpp>
#include <Sched/Scheduler.hpp>
@@ -142,10 +143,7 @@ namespace Drivers::Audio::Mixer {
static bool BluetoothReady() {
if (!Drivers::USB::Bluetooth::IsInitialized()) return false;
auto state = Drivers::USB::Bluetooth::A2dp::GetState();
return state == Drivers::USB::Bluetooth::A2dp::State::Configured ||
state == Drivers::USB::Bluetooth::A2dp::State::Open ||
state == Drivers::USB::Bluetooth::A2dp::State::Streaming;
return Drivers::USB::Bluetooth::A2dp::IsReady();
}
static void SyncHdaMasterMute() {
@@ -664,6 +662,8 @@ namespace Drivers::Audio::Mixer {
if (changed) BumpSerialLocked();
g_lock.Release();
if (changed)
Drivers::USB::Bluetooth::Avrcp::NotifyVolumeChanged(percent);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
}
File diff suppressed because it is too large Load Diff
+16 -1
View File
@@ -32,11 +32,22 @@ namespace Drivers::USB::Bluetooth::A2dp {
// handshakes), NOT from an event/interrupt path. Leaves the stream Open.
bool StartSource(uint32_t timeoutMs = 5000);
// Reset all per-link AVDTP state for a newly established ACL connection.
// This is deliberately separate from StartSource(): the peer may open its
// AVDTP channels during authentication/the post-encryption settle window,
// and StartSource must preserve those already-live inbound channels.
void OnConnected(uint16_t aclHandle);
// Called by L2CAP when an AVDTP channel becomes ready
void OnChannelReady(uint16_t l2capCid);
// Process an AVDTP signaling packet
void ProcessAvdtp(const uint8_t* data, uint16_t len);
void ProcessAvdtp(uint16_t localCid, const uint8_t* data, uint16_t len);
// L2CAP channel teardown can happen while the ACL link remains alive.
// Clear stale signaling/media CIDs so subsequent writes do not target a
// dead channel and a manual reconnect can rebuild the A2DP path.
void OnChannelClosed(uint16_t localCid);
// Process an SDP packet (on any SDP L2CAP channel). Dispatches by PDU:
// requests (the headset querying OUR services) are answered by the minimal
@@ -80,6 +91,10 @@ namespace Drivers::USB::Bluetooth::A2dp {
// Get current state
State GetState();
// True only when signaling and media transports are both live and the SEP
// has reached a state from which audio can be started.
bool IsReady();
// Check if currently streaming
bool IsStreaming();
+57 -29
View File
@@ -10,6 +10,7 @@
#include <Drivers/Audio/Mixer.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Libraries/Memory.hpp>
using namespace Kt;
@@ -28,6 +29,7 @@ namespace Drivers::USB::Bluetooth::Avrcp {
constexpr uint8_t AVC_RSP_ACCEPTED = 0x9;
constexpr uint8_t AVC_RSP_REJECTED = 0xA;
constexpr uint8_t AVC_RSP_STABLE = 0xC;
constexpr uint8_t AVC_RSP_CHANGED = 0xD;
constexpr uint8_t AVC_RSP_INTERIM = 0xF;
// AV/C opcodes
@@ -43,33 +45,35 @@ namespace Drivers::USB::Bluetooth::Avrcp {
constexpr uint8_t PDU_SET_ABS_VOLUME = 0x50;
// AVRCP notification events
constexpr uint8_t EVT_PLAYBACK_STATUS = 0x01;
constexpr uint8_t EVT_TRACK_CHANGED = 0x02;
constexpr uint8_t EVT_VOLUME_CHANGED = 0x0D;
static kcp::Spinlock g_notifyLock;
static uint16_t g_volumeNotifyCid = 0;
static uint8_t g_volumeNotifyTransaction = 0;
// =========================================================================
// Send helpers
// =========================================================================
// AVCTP single-packet response: byte0 = transaction<<4 | pktType<<2 |
// C/R(1=response)<<1 | IPID; bytes 1-2 = PID big-endian.
static void SendAvctp(uint16_t cid, uint8_t transaction, uint16_t pid,
static bool SendAvctp(uint16_t cid, uint8_t transaction, uint16_t pid,
bool invalidPid, const uint8_t* avc, uint16_t avcLen) {
uint8_t buf[96] = {};
if (3u + avcLen > sizeof(buf)) return;
if (3u + avcLen > sizeof(buf)) return false;
buf[0] = (uint8_t)((transaction << 4) | 0x02 | (invalidPid ? 0x01 : 0));
buf[1] = (uint8_t)(pid >> 8);
buf[2] = (uint8_t)(pid & 0xFF);
if (avc && avcLen) memcpy(&buf[3], avc, avcLen);
L2cap::SendData(cid, buf, (uint16_t)(3 + avcLen));
return L2cap::SendData(cid, buf, (uint16_t)(3 + avcLen));
}
// AVRCP vendor-dependent response frame:
// [rsp][subunit 0x48 panel][opcode 0x00][company 00 19 58][pdu][pkt 0][len BE][params]
static void SendVendorRsp(uint16_t cid, uint8_t transaction, uint8_t rspCode,
static bool SendVendorRsp(uint16_t cid, uint8_t transaction, uint8_t rspCode,
uint8_t pdu, const uint8_t* p, uint16_t n) {
uint8_t avc[64] = {};
if (10u + n > sizeof(avc)) return;
if (10u + n > sizeof(avc)) return false;
avc[0] = rspCode;
avc[1] = 0x48; // PANEL subunit
avc[2] = AVC_OP_VENDOR;
@@ -79,7 +83,8 @@ namespace Drivers::USB::Bluetooth::Avrcp {
avc[8] = (uint8_t)(n >> 8);
avc[9] = (uint8_t)(n & 0xFF);
if (p && n) memcpy(&avc[10], p, n);
SendAvctp(cid, transaction, 0x110E, false, avc, (uint16_t)(10 + n));
return SendAvctp(cid, transaction, 0x110E, false,
avc, (uint16_t)(10 + n));
}
// =========================================================================
@@ -126,14 +131,13 @@ namespace Drivers::USB::Bluetooth::Avrcp {
}
case AVC_OP_PASSTHROUGH: {
// Accept every pass-through (play/pause/etc.). Echo the frame
// with the response code swapped in. Wiring the operation ids
// to the Music app is a later feature; ACCEPTED keeps the
// controller-side state machine happy.
// No media-session control bridge exists yet. Claiming these
// commands were ACCEPTED made headset buttons appear broken and
// left the controller believing an action had happened.
uint8_t rsp[16] = {};
uint16_t cp = (alen > sizeof(rsp)) ? (uint16_t)sizeof(rsp) : alen;
memcpy(rsp, avc, cp);
rsp[0] = AVC_RSP_ACCEPTED;
rsp[0] = AVC_RSP_NOT_IMPL;
SendAvctp(localCid, transaction, pid, false, rsp, cp);
if (alen >= 4) {
KernelLogStream(INFO, "BT-AVRCP") << "pass-through op="
@@ -156,7 +160,12 @@ namespace Drivers::USB::Bluetooth::Avrcp {
uint8_t pdu = avc[6];
uint16_t plen = ((uint16_t)avc[8] << 8) | avc[9];
const uint8_t* p = &avc[10];
if (10u + plen > alen) plen = (uint16_t)(alen - 10);
if (10u + plen > alen) {
uint8_t err = 0x01; // invalid parameter
SendVendorRsp(localCid, transaction, AVC_RSP_REJECTED,
pdu, &err, 1);
break;
}
if (pdu == PDU_GET_CAPABILITIES && ctype == AVC_CTYPE_STATUS && plen >= 1) {
if (p[0] == 0x02) { // CompanyID list
@@ -164,8 +173,7 @@ namespace Drivers::USB::Bluetooth::Avrcp {
SendVendorRsp(localCid, transaction, AVC_RSP_STABLE,
pdu, rp, sizeof(rp));
} else if (p[0] == 0x03) { // EventsSupported
uint8_t rp[5] = {0x03, 0x03, EVT_PLAYBACK_STATUS,
EVT_TRACK_CHANGED, EVT_VOLUME_CHANGED};
uint8_t rp[3] = {0x03, 0x01, EVT_VOLUME_CHANGED};
SendVendorRsp(localCid, transaction, AVC_RSP_STABLE,
pdu, rp, sizeof(rp));
} else {
@@ -181,22 +189,18 @@ namespace Drivers::USB::Bluetooth::Avrcp {
pdu, rp, sizeof(rp));
} else if (pdu == PDU_REGISTER_NOTIFY && ctype == AVC_CTYPE_NOTIFY && plen >= 1) {
// INTERIM response with the current value. (A CHANGED
// follow-up on actual change is a later feature.)
// response is sent by NotifyVolumeChanged; registrations
// are one-shot as required by AVRCP.)
if (p[0] == EVT_VOLUME_CHANGED) {
uint8_t rp[2] = {EVT_VOLUME_CHANGED,
(uint8_t)((Drivers::Audio::Mixer::GetMasterVolume() * 127) / 100)};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
} else if (p[0] == EVT_PLAYBACK_STATUS) {
uint8_t rp[2] = {EVT_PLAYBACK_STATUS,
(uint8_t)(A2dp::IsStreaming() ? 0x01 : 0x00)};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
} else if (p[0] == EVT_TRACK_CHANGED) {
uint8_t rp[9] = {EVT_TRACK_CHANGED,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp));
g_notifyLock.Acquire();
if (SendVendorRsp(localCid, transaction, AVC_RSP_INTERIM,
pdu, rp, sizeof(rp))) {
g_volumeNotifyCid = localCid;
g_volumeNotifyTransaction = transaction;
}
g_notifyLock.Release();
} else {
uint8_t err = 0x01;
SendVendorRsp(localCid, transaction, AVC_RSP_REJECTED,
@@ -228,4 +232,28 @@ namespace Drivers::USB::Bluetooth::Avrcp {
}
}
void NotifyVolumeChanged(int percent) {
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
g_notifyLock.Acquire();
uint16_t cid = g_volumeNotifyCid;
uint8_t transaction = g_volumeNotifyTransaction;
g_volumeNotifyCid = 0; // one-shot, regardless of transport outcome
if (cid != 0) {
uint8_t rp[2] = {
EVT_VOLUME_CHANGED, (uint8_t)((percent * 127) / 100)
};
SendVendorRsp(cid, transaction, AVC_RSP_CHANGED,
PDU_REGISTER_NOTIFY, rp, sizeof(rp));
}
g_notifyLock.Release();
}
void OnChannelClosed(uint16_t localCid) {
g_notifyLock.Acquire();
if (g_volumeNotifyCid == localCid) g_volumeNotifyCid = 0;
g_notifyLock.Release();
}
}
+10 -2
View File
@@ -10,10 +10,18 @@
namespace Drivers::USB::Bluetooth::Avrcp {
// Process an AVCTP packet arriving on an AVRCP control channel (PSM 0x0017).
// Implements a minimal AVRCP Target: unit/subunit info, pass-through accept,
// GetCapabilities, RegisterNotification (interim), absolute volume. Runs
// Implements a minimal AVRCP Target: unit/subunit info, honest rejection of
// unimplemented pass-through controls, capabilities, one-shot absolute-
// volume notification, and absolute-volume control. Runs
// from the ACL receive path (DrainEvents, top-level) -- sending is safe
// there, blocking waits are not (none are used).
void ProcessAvctp(uint16_t localCid, const uint8_t* data, uint16_t len);
// Complete an outstanding EVENT_VOLUME_CHANGED registration. AVRCP
// notifications are one-shot: after CHANGED the controller registers again.
void NotifyVolumeChanged(int percent);
// Drop registrations tied to a channel that L2CAP has torn down.
void OnChannelClosed(uint16_t localCid);
}
+146 -129
View File
@@ -157,58 +157,45 @@ namespace Drivers::USB::Bluetooth {
// Intel Bluetooth firmware detection
// =========================================================================
static bool InitIntelBluetooth(uint8_t slotId) {
static bool InitIntelBluetooth() {
KernelLogStream(INFO, "BT") << "Intel Bluetooth adapter detected";
// Intel BT controllers require HCI Reset before they respond to
// vendor-specific commands. This mirrors the Linux btintel driver
// sequence: Reset → Read Version → (firmware load) → Reset.
if (!Hci::Reset()) {
KernelLogStream(ERROR, "BT") << "Initial HCI Reset failed";
return false;
}
// Read standard HCI version -- if this fails, the controller is likely
// in bootloader mode where only vendor commands are accepted.
Hci::LocalVersion lver = {};
bool hciVersionOk = Hci::ReadLocalVersion(&lver);
if (hciVersionOk) {
KernelLogStream(INFO, "BT") << "HCI version=" << (uint64_t)lver.HciVersion
<< " rev=" << base::hex << (uint64_t)lver.HciRevision
<< " LMP=" << (uint64_t)lver.LmpVersion
<< " manufacturer=" << (uint64_t)lver.Manufacturer
<< " subver=" << (uint64_t)lver.LmpSubversion << base::dec;
}
// Read legacy Intel version for diagnostics (TLV parts return this in
// a different layout; the authoritative state check happens inside the
// firmware download path below via the TLV version).
Hci::IntelVersion ver = {};
if (!Hci::ReadIntelVersion(&ver)) {
KernelLogStream(WARNING, "BT") << "Failed to read Intel BT version";
} else {
KernelLogStream(INFO, "BT") << "Intel BT: HW variant=" << (uint64_t)ver.HwVariant
<< " FW variant=" << base::hex << (uint64_t)ver.FwVariant
<< " FW rev=" << (uint64_t)ver.FwRevision << "."
<< (uint64_t)ver.FwBuildNum << base::dec;
}
// Run the firmware download path. This reads the TLV version, and if
// the controller is in bootloader mode, loads the matching .sfi image
// from the ramdisk, secure-sends it, boots the operational firmware
// Read the Intel TLV version first. It is the mode probe accepted by
// both Intel's bootloader and operational firmware. A standard HCI
// Reset must not precede it: the bootloader answers Reset with status
// 0x01 (Unknown HCI Command), which is normal rather than a fatal
// transport failure.
//
// If the controller is in bootloader mode, this loads the matching SFI
// image from the ramdisk, secure-sends it, boots operational firmware,
// and applies DDC parameters. Returns true if the controller ends up
// operational (either already loaded, or freshly downloaded).
if (!DownloadIntelFirmware()) {
KernelLogStream(WARNING, "BT")
<< "Intel BT firmware not loaded; limited functionality";
// Standard init already issued an HCI Reset above.
return true;
// Older Intel parts may lack the TLV command while already running
// usable operational firmware. Preserve that compatibility only
// when standard HCI proves it is genuinely operational.
if (Hci::Reset()) {
KernelLogStream(WARNING, "BT")
<< "Intel firmware query failed; continuing with operational HCI";
return true;
}
KernelLogStream(ERROR, "BT")
<< "Intel controller remains in bootloader mode";
return false;
}
// The operational firmware just (re)booted. Give it a clean reset and
// re-enable the Intel vendor event mask before the generic HCI setup.
Hci::Reset();
Hci::IntelSetEventMask();
// Whether it was already present or was just booted, operational
// firmware must now accept standard HCI. Do not mark the adapter ready
// if this transition did not actually happen.
if (!Hci::Reset()) {
KernelLogStream(ERROR, "BT")
<< "Operational firmware did not accept HCI Reset";
return false;
}
if (!Hci::IntelSetEventMask()) {
KernelLogStream(WARNING, "BT")
<< "Intel vendor event mask was not accepted";
}
return true;
}
@@ -272,10 +259,15 @@ namespace Drivers::USB::Bluetooth {
// Intel-specific initialization (firmware download + HCI Reset)
bool didReset = false;
if (IsIntelBt(dev->VendorId, dev->ProductId)) {
if (InitIntelBluetooth(g_slotId)) {
if (InitIntelBluetooth()) {
didReset = true; // InitIntelBluetooth already sent HCI Reset
} else {
KernelLogStream(WARNING, "BT") << "Intel BT init failed, continuing with basic HCI";
// A recognized Intel part that failed its vendor initialization
// is normally still a bootloader, where a second standard Reset
// only repeats status 0x01. Stop with an accurate failure
// instead of pretending a basic-HCI fallback exists.
KernelLogStream(ERROR, "BT") << "Intel BT initialization failed";
return;
}
}
@@ -315,6 +307,10 @@ namespace Drivers::USB::Bluetooth {
KernelLogStream(INFO, "BT") << "ACL buffer: " << (uint64_t)aclLen
<< " bytes x " << (uint64_t)aclNum;
}
// Bulk IN was armed before Intel firmware loading, but bootloader runts
// are not HCI ACL traffic and an absorbed firmware-phase USB error may
// have stopped the endpoint. Start framed ACL reception only now.
Hci::EnableAclDataReception();
// Set local name
Hci::WriteLocalName("MontaukOS");
@@ -363,10 +359,15 @@ namespace Drivers::USB::Bluetooth {
// Request (0x33), Simple Pairing Complete (0x36) all live there. It was
// 0x00 -> the controller started SSP but the IO-Capability Request event
// never reached us, so pairing always timed out with auth failure 0x05.
uint8_t eventMask[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0x20};
uint8_t eventMask[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20};
Hci::SendCommand(Hci::OP_SET_EVENT_MASK, eventMask, 8);
Hci::WaitCommandComplete(Hci::OP_SET_EVENT_MASK);
// Request Extended Inquiry Results so scan entries carry EIR names and
// RSSI. Older controllers may support only mode 1 (RSSI); the parser
// handles both result layouts and the fallback preserves discovery.
if (!Hci::WriteInquiryMode(2)) Hci::WriteInquiryMode(1);
// Enable inquiry + page scan (discoverable and connectable)
Hci::WriteScanEnable(0x03);
@@ -420,8 +421,11 @@ namespace Drivers::USB::Bluetooth {
void ServiceEvents() {
if (!g_initialized) return;
if (Xhci::InPollContext()) return; // never nest under PollEvents
Hci::ProcessPendingCommands();
A2dp::ServiceMedia(); // reap events and feed queued media
// DrainEvents may just have queued an accept/auth/encryption reply.
// Send it in this same service pass instead of adding a scheduler-turn
// delay to the controller's security timeout.
Hci::ProcessPendingCommands();
Drivers::Audio::Mixer::OnBluetoothWritable();
int requestedVolume;
if (A2dp::ConsumeVolumeRequest(&requestedVolume))
@@ -472,13 +476,22 @@ namespace Drivers::USB::Bluetooth {
}
}
// Let the disconnection(s) complete before reprogramming the address.
// A fixed 300 ms pause was merely hopeful and could change the identity
// under a still-live encrypted link.
if (droppedLink) {
uint64_t t0 = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - t0 < 300) {
bool anyActive = true;
while (anyActive && Timekeeping::GetMilliseconds() - t0 < 2000) {
Xhci::PollEvents();
Hci::DrainEvents();
anyActive = false;
for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
auto* conn = Hci::GetConnectionByIndex(i);
if (conn && conn->Active) { anyActive = true; break; }
}
for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
}
if (anyActive) return false;
}
// Program the new address. Do NOT issue an HCI Reset afterwards: the
@@ -515,9 +528,10 @@ namespace Drivers::USB::Bluetooth {
Hci::ClearInquiryResults();
// Convert timeout to 1.28s units (min 1, max 30)
uint8_t duration = (uint8_t)(timeoutMs / 1280);
if (duration < 1) duration = 1;
if (duration > 30) duration = 30;
uint32_t durationUnits = timeoutMs / 1280;
if (durationUnits < 1) durationUnits = 1;
if (durationUnits > 30) durationUnits = 30;
uint8_t duration = (uint8_t)durationUnits;
if (!Hci::StartInquiry(duration)) return -1;
@@ -534,7 +548,21 @@ namespace Drivers::USB::Bluetooth {
// Cancel if still running
if (Hci::IsInquiryActive()) {
Hci::CancelInquiry();
// A failed cancel must not be hidden: Create Connection while the
// controller is still in Inquiry is commonly rejected as Command
// Disallowed. Give a command that briefly lost HCI ownership one
// retry, continuing to service the completion event in between.
if (!Hci::CancelInquiry()) {
uint64_t cancelStart = Timekeeping::GetMilliseconds();
while (Hci::IsInquiryActive()
&& Timekeeping::GetMilliseconds() - cancelStart < 250) {
Xhci::PollEvents();
Hci::DrainEvents();
for (int j = 0; j < 100; j++)
asm volatile("pause" ::: "memory");
}
if (Hci::IsInquiryActive() && !Hci::CancelInquiry()) return -1;
}
}
return Hci::GetInquiryResults(buf, maxCount);
@@ -544,89 +572,82 @@ namespace Drivers::USB::Bluetooth {
// Connect — initiate ACL connection
// =========================================================================
static bool SameAddress(const uint8_t* a, const uint8_t* b) {
if (!a || !b) return false;
for (int i = 0; i < 6; i++) if (a[i] != b[i]) return false;
return true;
}
static Hci::ConnectionInfo* FindAclConnection(const uint8_t* bdAddr) {
for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
auto* conn = Hci::GetConnectionByIndex(i);
if (conn && conn->Active && conn->LinkType == 0x01
&& SameAddress(conn->BdAddr, bdAddr)) return conn;
}
return nullptr;
}
int Connect(const uint8_t* bdAddr, uint32_t timeoutMs) {
if (!g_initialized || !bdAddr) return -1;
if (!Hci::CreateConnection(bdAddr)) return -1;
// A previous attempt can leave a healthy encrypted ACL link with A2DP
// incomplete. Treat another click as an A2DP repair attempt on that
// link; issuing HCI Create Connection again just returns "connection
// already exists" and made manual recovery impossible.
Hci::ConnectionInfo* target = FindAclConnection(bdAddr);
bool reusedAcl = target != nullptr;
if (!target && !Hci::CreateConnection(bdAddr)) return -1;
// Wait for Connection Complete event
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
while (!target && Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents();
Hci::DrainEvents();
// Check connection table for matching BD_ADDR
for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
auto* conn = Hci::GetConnectionByIndex(i);
if (conn && conn->Active) {
bool match = true;
for (int j = 0; j < 6; j++) {
if (conn->BdAddr[j] != bdAddr[j]) { match = false; break; }
}
if (match) {
// The headset (in pairing mode) drives Secure Simple
// Pairing itself right after the ACL link comes up. Let
// authentication + encryption finish BEFORE opening any
// L2CAP/AVDTP channels: doing A2DP on a not-yet-
// authenticated link races with the pairing handshake and
// the headset drops us (reason 0x05). Drain events here
// so the IO-capability / user-confirm / link-key / encrypt
// events all get serviced.
//
// We are the initiator: request authentication so the
// controller starts Secure Simple Pairing (Link Key
// Request -> our negative reply -> IO Capability Request
// -> ... ). The headset does not start this on its own.
// NB this only works now that octet 6 of the event mask
// is enabled so the IO-Capability Request event actually
// reaches us; before that this produced 03 17 06 05.
Hci::AuthenticateLink(conn->Handle);
uint64_t t0 = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - t0 < 5000) {
Xhci::PollEvents();
Hci::DrainEvents();
// Send queued pairing replies reliably (top-level,
// not nested under PollEvents).
Hci::ProcessPendingCommands();
if (!conn->Active) break; // link dropped during pairing
if (conn->Encrypted) break; // authenticated + encrypted -> ready
for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
}
// Bring up the A2DP source stream (signaling + media
// channels, SBC negotiation) only once the link is
// secured. Without a media stream the headset also drops
// the link (reason 0x13), so this keeps it engaged too.
if (conn->Active) {
// Let the link settle after Encryption Change before
// dialing L2CAP: some sinks ignore a CONN_REQ that
// arrives the instant encryption completes. Drain
// (don't blind-sleep) so the ACL RX ring stays live.
uint64_t st = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - st < 300) {
Xhci::PollEvents();
Hci::DrainEvents();
for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
}
A2dp::StartSource();
Drivers::Audio::Mixer::OnBluetoothStateChanged();
}
// Persist any new link key now (process context), even if
// the link later dropped, so the disk write never stalls
// the nested pairing event handler.
Hci::FlushLinkKeys();
return 0;
}
}
}
target = FindAclConnection(bdAddr);
for (int j = 0; j < 200; j++) {
asm volatile("pause" ::: "memory");
}
}
if (!target) return -1;
return -1; // Timeout
// Connection Complete queues authentication for both incoming and
// outgoing ACLs. Deliver that common request and wait for encryption;
// a manual A2DP repair on an encrypted ACL skips this entire wait.
if (!target->Encrypted) {
// A link that predates this syscall may have exhausted or missed its
// earlier security attempt; explicitly restart it. A freshly-created
// link already has the request queued by Connection Complete.
if (reusedAcl) Hci::AuthenticateLink(target->Handle);
uint64_t t0 = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - t0 < 5000) {
Xhci::PollEvents();
Hci::DrainEvents();
Hci::ProcessPendingCommands();
if (!target->Active || !SameAddress(target->BdAddr, bdAddr)) break;
if (target->Encrypted) break;
for (int k = 0; k < 200; k++) asm volatile("pause" ::: "memory");
}
}
bool targetAlive = target->Active
&& SameAddress(target->BdAddr, bdAddr);
bool a2dpReady = false;
if (targetAlive) {
a2dpReady = A2dp::StartSource();
Drivers::Audio::Mixer::OnBluetoothStateChanged();
}
// Persist any new link key now (process context), even if the link later
// dropped, so disk I/O never stalls the nested pairing event handler.
Hci::FlushLinkKeys();
if (!target->Active || !SameAddress(target->BdAddr, bdAddr)) return -1;
if (!a2dpReady) {
KernelLogStream(WARNING, "BT")
<< "ACL connected but A2DP source setup failed";
return -2;
}
return 0;
}
// =========================================================================
@@ -640,11 +661,7 @@ namespace Drivers::USB::Bluetooth {
for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
auto* conn = Hci::GetConnectionByIndex(i);
if (conn && conn->Active) {
bool match = true;
for (int j = 0; j < 6; j++) {
if (conn->BdAddr[j] != bdAddr[j]) { match = false; break; }
}
if (match) {
if (SameAddress(conn->BdAddr, bdAddr)) {
Hci::Disconnect(conn->Handle, 0x13); // 0x13 = Remote User Terminated
return 0;
}
@@ -49,7 +49,8 @@ namespace Drivers::USB::Bluetooth {
int Scan(Hci::InquiryDevice* buf, int maxCount, uint32_t timeoutMs);
// Initiate connection to a remote device by BD_ADDR
// Returns 0 on success (connection established), -1 on failure
// Returns 0 when ACL + A2DP are ready, -1 when the ACL connection failed,
// or -2 when ACL connected but A2DP setup did not complete.
int Connect(const uint8_t* bdAddr, uint32_t timeoutMs = 10000);
// Disconnect a device by BD_ADDR
File diff suppressed because it is too large Load Diff
+8
View File
@@ -181,6 +181,11 @@ namespace Drivers::USB::Bluetooth::Hci {
// and keeps the event pipe alive -- see the definition).
void StartEventPipe();
// Enable operational ACL receive framing after firmware/HCI setup. Bulk IN
// is armed earlier to protect Intel firmware loading; pre-operational runts
// are intentionally ignored and any endpoint error is recovered here.
void EnableAclDataReception();
// Send an HCI command via USB control transfer (EP0)
bool SendCommand(uint16_t opcode, const uint8_t* params, uint8_t paramLen);
@@ -308,6 +313,9 @@ namespace Drivers::USB::Bluetooth::Hci {
// Write Simple Secure Pairing mode
bool WriteSSPMode(uint8_t mode);
// Select inquiry result format: 0=standard, 1=RSSI, 2=extended/EIR.
bool WriteInquiryMode(uint8_t mode);
// Accept an incoming connection
bool AcceptConnection(const uint8_t* bdAddr, uint8_t role);
+142 -22
View File
@@ -26,11 +26,6 @@ namespace Drivers::USB::Bluetooth::L2cap {
static bool g_initialized = false;
static uint8_t g_sigIdentifier = 1;
// Diagnostic: result of the most recent L2CAP Connection Response we got for
// an outgoing connection (0=success, 1=pending, 2=PSM-unsupported,
// 3=security-block, 4=no-resources). 0xFFFF = none received yet.
static volatile uint16_t g_lastConnRspResult = 0xFFFF;
// Channel table
static ChannelInfo g_channels[MAX_CHANNELS] = {};
static uint16_t g_nextCid = CID_DYNAMIC_START;
@@ -39,8 +34,29 @@ namespace Drivers::USB::Bluetooth::L2cap {
// Helpers
// =========================================================================
static uint8_t NextIdentifier() {
uint8_t id = g_sigIdentifier++;
if (id == 0) id = g_sigIdentifier++;
if (g_sigIdentifier == 0) g_sigIdentifier = 1;
return id;
}
static uint16_t AllocCid() {
return g_nextCid++;
// Dynamic CIDs are 0x0040..0xFFFF and must not collide with an active
// channel after wraparound.
for (uint32_t tries = 0; tries <= 0xFFC0; tries++) {
if (g_nextCid < CID_DYNAMIC_START) g_nextCid = CID_DYNAMIC_START;
uint16_t candidate = g_nextCid++;
bool used = false;
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active && g_channels[i].LocalCid == candidate) {
used = true;
break;
}
}
if (!used) return candidate;
}
return 0;
}
static ChannelInfo* AllocChannel(uint16_t psm) {
@@ -48,12 +64,18 @@ namespace Drivers::USB::Bluetooth::L2cap {
if (!g_channels[i].Active) {
g_channels[i].Active = true;
g_channels[i].LocalCid = AllocCid();
if (g_channels[i].LocalCid == 0) {
g_channels[i].Active = false;
return nullptr;
}
g_channels[i].RemoteCid = 0;
g_channels[i].Psm = psm;
g_channels[i].RemoteMtu = 672; // Default L2CAP MTU
g_channels[i].Configured = false;
g_channels[i].LocalConfigDone = false;
g_channels[i].RemoteConfigDone = false;
g_channels[i].ConnRspResult = 0xFFFF;
g_channels[i].ConnRspStatus = 0;
return &g_channels[i];
}
}
@@ -61,13 +83,14 @@ namespace Drivers::USB::Bluetooth::L2cap {
}
// Send L2CAP signaling command
static void SendSignal(uint8_t code, uint8_t identifier,
static bool SendSignal(uint8_t code, uint8_t identifier,
const uint8_t* payload, uint16_t payloadLen) {
// L2CAP header + Signal header + payload
uint16_t sigLen = sizeof(SignalHeader) + payloadLen;
uint16_t totalPayload = sizeof(L2capHeader) + sigLen;
uint8_t buf[128] = {};
if (totalPayload > sizeof(buf)) return false;
auto* l2hdr = (L2capHeader*)buf;
l2hdr->Length = sigLen;
l2hdr->ChannelId = CID_SIGNALING;
@@ -81,8 +104,8 @@ namespace Drivers::USB::Bluetooth::L2cap {
memcpy(buf + sizeof(L2capHeader) + sizeof(SignalHeader), payload, payloadLen);
}
Hci::SendAcl(g_aclHandle, Hci::ACL_PB_FIRST_FLUSH,
buf, totalPayload);
return Hci::SendAcl(g_aclHandle, Hci::ACL_PB_FIRST_NON_FLUSH,
buf, totalPayload);
}
// =========================================================================
@@ -96,17 +119,31 @@ namespace Drivers::USB::Bluetooth::L2cap {
g_nextCid = CID_DYNAMIC_START;
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active)
Avrcp::OnChannelClosed(g_channels[i].LocalCid);
g_channels[i].Active = false;
}
KernelLogStream(OK, "BT-L2CAP") << "Initialized for ACL handle " << (uint64_t)aclHandle;
}
void OnDisconnected(uint16_t aclHandle) {
if (!g_initialized || aclHandle != g_aclHandle) return;
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active)
Avrcp::OnChannelClosed(g_channels[i].LocalCid);
g_channels[i].Active = false;
}
g_aclHandle = 0;
g_initialized = false;
}
// =========================================================================
// ProcessPacket
// =========================================================================
void ProcessPacket(uint16_t aclHandle, const uint8_t* data, uint16_t len) {
if (!g_initialized || aclHandle != g_aclHandle) return;
if (len < sizeof(L2capHeader)) return;
auto* l2hdr = (const L2capHeader*)data;
@@ -145,8 +182,41 @@ namespace Drivers::USB::Bluetooth::L2cap {
// Accept connections for AVDTP, SDP, and AVRCP control
if (psm == PSM_AVDTP || psm == PSM_SDP || psm == PSM_AVCTP) {
auto* ch = AllocChannel(psm);
if (ch) {
// Detect a duplicate source CID instead of creating
// two local channels that address the same endpoint.
// Same-PSM duplicates are normal retransmissions;
// a different PSM reusing the CID is invalid.
ChannelInfo* existing = nullptr;
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active
&& g_channels[i].RemoteCid == srcCid) {
existing = &g_channels[i];
break;
}
}
auto* ch = existing ? nullptr : AllocChannel(psm);
if (existing && existing->Psm == psm) {
// Retransmission of a request whose response was
// lost: repeat SUCCESS for the original channel,
// and repeat our config request as well.
uint8_t rsp[8] = {
(uint8_t)existing->LocalCid,
(uint8_t)(existing->LocalCid >> 8),
(uint8_t)srcCid, (uint8_t)(srcCid >> 8),
0, 0, 0, 0
};
SendSignal(SIG_CONN_RSP, sig->Identifier, rsp, 8);
uint8_t cfgReq[4] = {
(uint8_t)srcCid, (uint8_t)(srcCid >> 8), 0, 0
};
SendSignal(SIG_CONFIG_REQ, NextIdentifier(), cfgReq, 4);
} else if (existing) {
uint8_t rsp[8] = {
0, 0, (uint8_t)srcCid, (uint8_t)(srcCid >> 8),
0x07, 0x00, 0, 0 // source CID already allocated
};
SendSignal(SIG_CONN_RSP, sig->Identifier, rsp, 8);
} else if (ch) {
ch->RemoteCid = srcCid;
// Send Connection Response (success)
@@ -168,7 +238,13 @@ namespace Drivers::USB::Bluetooth::L2cap {
cfgReq[0] = (uint8_t)(srcCid & 0xFF);
cfgReq[1] = (uint8_t)(srcCid >> 8);
cfgReq[2] = 0; cfgReq[3] = 0; // Flags
SendSignal(SIG_CONFIG_REQ, g_sigIdentifier++, cfgReq, 4);
SendSignal(SIG_CONFIG_REQ, NextIdentifier(), cfgReq, 4);
} else {
uint8_t rsp[8] = {
0, 0, (uint8_t)srcCid, (uint8_t)(srcCid >> 8),
0x04, 0x00, 0, 0 // no resources
};
SendSignal(SIG_CONN_RSP, sig->Identifier, rsp, 8);
}
} else {
// Reject: PSM not supported
@@ -193,8 +269,6 @@ namespace Drivers::USB::Bluetooth::L2cap {
// info, 1=authentication pending, 2=authorization pending.
uint16_t status = (sigPayloadLen >= 8)
? ((uint16_t)sigPayload[6] | ((uint16_t)sigPayload[7] << 8)) : 0;
g_lastConnRspResult = result;
KernelLogStream(INFO, "BT-L2CAP") << "Connection Response: dstCID="
<< base::hex << (uint64_t)dstCid << " result=" << (uint64_t)result
<< " status=" << (uint64_t)status << base::dec;
@@ -203,6 +277,8 @@ namespace Drivers::USB::Bluetooth::L2cap {
// Find our channel by srcCid (which is our local CID)
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active && g_channels[i].LocalCid == srcCid) {
g_channels[i].ConnRspResult = result;
g_channels[i].ConnRspStatus = status;
g_channels[i].RemoteCid = dstCid;
// Send Configuration Request
@@ -210,7 +286,7 @@ namespace Drivers::USB::Bluetooth::L2cap {
cfgReq[0] = (uint8_t)(dstCid & 0xFF);
cfgReq[1] = (uint8_t)(dstCid >> 8);
cfgReq[2] = 0; cfgReq[3] = 0; // Flags
SendSignal(SIG_CONFIG_REQ, g_sigIdentifier++, cfgReq, 4);
SendSignal(SIG_CONFIG_REQ, NextIdentifier(), cfgReq, 4);
break;
}
}
@@ -223,10 +299,21 @@ namespace Drivers::USB::Bluetooth::L2cap {
// a Disconnect for the half-open remote endpoint.
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active && g_channels[i].LocalCid == srcCid) {
g_channels[i].ConnRspResult = result;
g_channels[i].ConnRspStatus = status;
g_channels[i].RemoteCid = dstCid;
break;
}
}
} else {
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active
&& g_channels[i].LocalCid == srcCid) {
g_channels[i].ConnRspResult = result;
g_channels[i].ConnRspStatus = status;
break;
}
}
}
}
break;
@@ -333,7 +420,10 @@ namespace Drivers::USB::Bluetooth::L2cap {
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active && g_channels[i].LocalCid == dstCid) {
uint16_t closedCid = g_channels[i].LocalCid;
g_channels[i].Active = false;
A2dp::OnChannelClosed(closedCid);
Avrcp::OnChannelClosed(closedCid);
break;
}
}
@@ -378,6 +468,13 @@ namespace Drivers::USB::Bluetooth::L2cap {
// SDP client channel after a query); nothing left to do.
break;
case SIG_COMMAND_REJECT:
case SIG_ECHO_RSP:
case SIG_INFO_RSP:
// Terminal responses to requests sent by us. No response
// is required; in particular, never reject a response.
break;
case SIG_ECHO_REQ: {
// Echo Request must be answered (echo the payload back) or
// a peer probing the link times out on us.
@@ -392,6 +489,10 @@ namespace Drivers::USB::Bluetooth::L2cap {
KernelLogStream(INFO, "BT-L2CAP") << "Unhandled signaling code="
<< base::hex << (uint64_t)sig->Code << base::dec
<< " len=" << (uint64_t)sigPayloadLen;
// Command Reject, reason 0x0000 = command not understood.
// Silence leaves the peer waiting for its signaling timer.
uint8_t reject[2] = {0x00, 0x00};
SendSignal(SIG_COMMAND_REJECT, sig->Identifier, reject, 2);
break;
}
}
@@ -402,7 +503,7 @@ namespace Drivers::USB::Bluetooth::L2cap {
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active && g_channels[i].LocalCid == cid) {
if (g_channels[i].Psm == PSM_AVDTP) {
A2dp::ProcessAvdtp(payload, l2len);
A2dp::ProcessAvdtp(cid, payload, l2len);
} else if (g_channels[i].Psm == PSM_SDP) {
A2dp::ProcessSdp(cid, payload, l2len);
} else if (g_channels[i].Psm == PSM_AVCTP) {
@@ -421,8 +522,6 @@ namespace Drivers::USB::Bluetooth::L2cap {
uint16_t Connect(uint16_t psm) {
if (!g_initialized) return 0;
g_lastConnRspResult = 0xFFFF; // reset diagnostic for this attempt
auto* ch = AllocChannel(psm);
if (!ch) return 0;
@@ -432,7 +531,10 @@ namespace Drivers::USB::Bluetooth::L2cap {
req[1] = (uint8_t)(psm >> 8);
req[2] = (uint8_t)(ch->LocalCid & 0xFF);
req[3] = (uint8_t)(ch->LocalCid >> 8);
SendSignal(SIG_CONN_REQ, g_sigIdentifier++, req, 4);
if (!SendSignal(SIG_CONN_REQ, NextIdentifier(), req, 4)) {
ch->Active = false;
return 0;
}
return ch->LocalCid;
}
@@ -518,9 +620,11 @@ namespace Drivers::USB::Bluetooth::L2cap {
req[1] = (uint8_t)(g_channels[i].RemoteCid >> 8);
req[2] = (uint8_t)(g_channels[i].LocalCid & 0xFF);
req[3] = (uint8_t)(g_channels[i].LocalCid >> 8);
SendSignal(SIG_DISCONN_REQ, g_sigIdentifier++, req, 4);
SendSignal(SIG_DISCONN_REQ, NextIdentifier(), req, 4);
}
g_channels[i].Active = false;
A2dp::OnChannelClosed(localCid);
Avrcp::OnChannelClosed(localCid);
return true;
}
}
@@ -547,12 +651,28 @@ namespace Drivers::USB::Bluetooth::L2cap {
return 0;
}
uint16_t FindAvdtpChannelExcept(uint16_t exceptCid) {
for (int i = 0; i < MAX_CHANNELS; i++) {
if (g_channels[i].Active && g_channels[i].Psm == PSM_AVDTP
&& g_channels[i].LocalCid != exceptCid) {
return g_channels[i].LocalCid;
}
}
return 0;
}
uint16_t GetAclHandle() {
return g_aclHandle;
}
uint16_t LastConnRspResult() {
return g_lastConnRspResult;
uint16_t ConnRspResult(uint16_t localCid) {
auto* ch = GetChannel(localCid);
return ch ? ch->ConnRspResult : 0xFFFF;
}
uint16_t ConnRspStatus(uint16_t localCid) {
auto* ch = GetChannel(localCid);
return ch ? ch->ConnRspStatus : 0;
}
}
+16 -5
View File
@@ -78,6 +78,8 @@ namespace Drivers::USB::Bluetooth::L2cap {
bool Configured; // Both sides configured
bool LocalConfigDone;
bool RemoteConfigDone;
uint16_t ConnRspResult; // 0xFFFF until this outgoing dial is answered
uint16_t ConnRspStatus; // meaningful while result == CONN_PENDING
};
constexpr int MAX_CHANNELS = 8;
@@ -89,6 +91,11 @@ namespace Drivers::USB::Bluetooth::L2cap {
// Initialize L2CAP for a new HCI connection
void Initialize(uint16_t aclHandle);
// Invalidate every dynamic channel when its ACL link disappears. Without
// this, AVRCP or media code can send to stale CIDs until the next connection
// happens to reinitialize the table.
void OnDisconnected(uint16_t aclHandle);
// Process an L2CAP packet (called from HCI ACL processing)
void ProcessPacket(uint16_t aclHandle, const uint8_t* data, uint16_t len);
@@ -114,6 +121,11 @@ namespace Drivers::USB::Bluetooth::L2cap {
// it (we dial it after AVDTP_OPEN, but some sinks open it inbound instead).
uint16_t FindConfiguredAvdtpChannelExcept(uint16_t exceptCid);
// Find any active AVDTP channel except the signaling channel, including a
// still-pending outgoing media dial. Manual retries reuse that channel
// instead of leaking another slot/CID while authorization is in progress.
uint16_t FindAvdtpChannelExcept(uint16_t exceptCid);
// Reclaim a dialed channel during A2DP bring-up retries so a failed connect
// doesn't leak the fixed-size channel table. If the peer had acknowledged
// the channel (RemoteCid != 0) a Disconnect Request is sent first; an
@@ -125,10 +137,9 @@ namespace Drivers::USB::Bluetooth::L2cap {
// Get the ACL handle
uint16_t GetAclHandle();
// Result code of the most recent outgoing L2CAP Connection Response
// (0xFFFF if none yet). 0=success, 1=pending, 2=PSM-unsupported,
// 3=security-block, 4=no-resources. StartSource() retries only when the
// remote ignored the dial entirely (0xFFFF).
uint16_t LastConnRspResult();
// Per-channel connection response state. A global "last result" is unsafe:
// an unrelated SDP, AVDTP, or AVRCP response can arrive concurrently.
uint16_t ConnRspResult(uint16_t localCid);
uint16_t ConnRspStatus(uint16_t localCid);
}
+2
View File
@@ -115,6 +115,7 @@ namespace Drivers::USB::Bluetooth::Sbc {
headerBits += enc->Subbands; // join bits
}
uint32_t dataBits = enc->Blocks * enc->Bitpool;
if (enc->ChannelMode == MODE_DUAL_CHANNEL) dataBits *= enc->Channels;
enc->FrameSize = (headerBits + dataBits + 7) / 8;
}
@@ -143,6 +144,7 @@ namespace Drivers::USB::Bluetooth::Sbc {
uint32_t headerBits = 32 + (4 * enc->Subbands * enc->Channels);
if (enc->ChannelMode == MODE_JOINT_STEREO) headerBits += enc->Subbands;
uint32_t dataBits = enc->Blocks * enc->Bitpool;
if (enc->ChannelMode == MODE_DUAL_CHANNEL) dataBits *= enc->Channels;
enc->FrameSize = (headerBits + dataBits + 7) / 8;
}
+32
View File
@@ -988,6 +988,38 @@ namespace Drivers::USB::Xhci {
SendCommand(deqTrb);
}
// -------------------------------------------------------------------------
// ResetBulkOutEndpoint - clear a halted bulk OUT endpoint
// -------------------------------------------------------------------------
void ResetBulkOutEndpoint(uint8_t slotId) {
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
UsbDeviceInfo& dev = g_devices[slotId];
if (dev.BulkOutEpNum == 0 || !dev.BulkOutRing) return;
uint8_t dci = dev.BulkOutEpNum * 2;
TRB resetTrb = {};
resetTrb.Control = (TRB_RESET_ENDPOINT << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24)
| ((uint32_t)dci << 16);
SendCommand(resetTrb);
// Skip the errored TRB and everything queued behind it. The class
// driver releases the corresponding DMA slots before it sends again.
uint64_t newDeq = dev.BulkOutRingPhys
+ (uint64_t)dev.BulkOutRingEnqueue * sizeof(TRB);
if (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);
}
// -------------------------------------------------------------------------
// QueueBulkInTransfer
// -------------------------------------------------------------------------
+5
View File
@@ -326,6 +326,11 @@ namespace Drivers::USB::Xhci {
// Used for SDR stream stall recovery (RTL2832 bulk IN can STALL on start).
void ResetBulkInEndpoint(uint8_t slotId);
// Clear a halted bulk OUT endpoint and discard the errored/queued TRBs.
// Must be called from process context; the class driver reconciles its
// outstanding-transfer accounting before submitting fresh work.
void ResetBulkOutEndpoint(uint8_t slotId);
// 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.
+1 -1
View File
@@ -258,7 +258,7 @@ namespace montauk::abi {
static constexpr int AUDIO_CTL_PAUSE = 3;
static constexpr int AUDIO_CTL_GET_OUTPUT = 4; // 0=HDA, 1=Bluetooth
static constexpr int AUDIO_CTL_SET_OUTPUT = 5; // Switch audio output
static constexpr int AUDIO_CTL_BT_STATUS = 6; // Get Bluetooth status
static constexpr int AUDIO_CTL_BT_STATUS = 6; // 0=unavailable, 1=setup, 2=ready
static constexpr int AUDIO_CTL_SET_MASTER_VOLUME = 7; // 0-100
static constexpr int AUDIO_CTL_GET_MASTER_VOLUME = 8;
static constexpr int AUDIO_CTL_SET_MUTE = 9; // 0/1, per-stream
+1 -1
View File
@@ -550,7 +550,7 @@
audio_get_output AUDIO_CTL_GET_OUTPUT (4): 0=HDA, 1=Bluetooth
audio_set_output AUDIO_CTL_SET_OUTPUT (5): switch all streams
(SET_OUTPUT, 5) switch a stream's output route
audio_bt_status AUDIO_CTL_BT_STATUS (6)
audio_bt_status AUDIO_CTL_BT_STATUS (6): 0=unavailable, 1=setup, 2=ready
audio_set_master_volume, _get_ AUDIO_CTL_{SET,GET}_MASTER_VOLUME (7/8), 0-100
audio_set_mute, audio_get_mute AUDIO_CTL_{SET,GET}_MUTE (9/10), per-stream
audio_set_master_mute, _get_ AUDIO_CTL_{SET,GET}_MASTER_MUTE (11/12)
+37 -6
View File
@@ -325,6 +325,7 @@ static void finish_scan() {
static void do_connect(const uint8_t* addr) {
int r = montauk::bt_connect(addr);
if (r >= 0) set_status("Connected");
else if (r == -2) set_status("Connected; audio setup failed - use Retry");
else set_status("Connection failed");
refresh_devices();
clamp_scroll();
@@ -354,6 +355,10 @@ static bool is_connected(const uint8_t* addr) {
return false;
}
static bool is_audio_ready() {
return montauk::audio_bt_status(-1) >= 2;
}
// ============================================================================
// Format helpers
// ============================================================================
@@ -565,8 +570,14 @@ static bool handle_click(int mx, int my) {
if (!row.contains(mx, my)) continue;
Rect action = row_action_button_rect(row);
Rect retry = row_forget_button_rect(row);
bool connected = is_connected(g_scan[i].bdAddr);
if (connected && !is_audio_ready() && retry.contains(mx, my)) {
do_connect(g_scan[i].bdAddr);
return true;
}
if (action.contains(mx, my)) {
if (is_connected(g_scan[i].bdAddr))
if (connected)
do_disconnect(g_scan[i].bdAddr);
else
do_connect(g_scan[i].bdAddr);
@@ -580,6 +591,11 @@ static bool handle_click(int mx, int my) {
Rect action = row_action_button_rect(row);
if (g_devices[i].connected) {
Rect retry = row_forget_button_rect(row);
if (!is_audio_ready() && retry.contains(mx, my)) {
do_connect(g_devices[i].bdAddr);
return true;
}
if (action.contains(mx, my)) {
do_disconnect(g_devices[i].bdAddr);
return true;
@@ -672,6 +688,7 @@ static bool handle_adapter_key(const montauk::abi::KeyEvent& key) {
static void render_devices_tab(Canvas& canvas, const mtk::Theme& theme) {
Rect list = {0, devices_list_top(), g_win_w, gui_max(devices_list_bottom() - devices_list_top(), 0)};
int fh = system_font_height();
bool bt_audio_ready = is_audio_ready();
if (g_device_count == 0) {
draw_centered_text(canvas, list.y, list.h, "No paired devices", theme.text_subtle);
@@ -688,15 +705,17 @@ static void render_devices_tab(Canvas& canvas, const mtk::Theme& theme) {
mtk::draw_list_row(canvas, row, false, (i & 1) != 0, theme);
bool connected = g_devices[i].connected;
bool audio_ready = !connected || bt_audio_ready;
Color dot_color = connected ? GREEN : theme.text_subtle;
fill_circle(canvas, PAD + 6, row.y + ROW_H / 2, 5, dot_color);
Rect action = row_action_button_rect(row);
Rect forget = row_forget_button_rect(row);
int text_x = PAD + 20;
// Connected rows have one button (Disconnect); paired rows have two
// (Connect + Forget), so the text must stop before the leftmost button.
int text_w_max = (connected ? action.x : forget.x) - text_x - 10;
// A connected row whose media path failed exposes Retry + Disconnect;
// otherwise connected rows need only Disconnect.
int text_w_max = (connected && audio_ready ? action.x : forget.x)
- text_x - 10;
draw_text_fit(canvas, text_x, row.y + 8, g_device_names[i], text_w_max, theme.text);
char addr_str[24];
@@ -704,6 +723,10 @@ static void render_devices_tab(Canvas& canvas, const mtk::Theme& theme) {
draw_text_fit(canvas, text_x, row.y + 8 + fh + 2, addr_str, text_w_max, theme.text_subtle);
if (connected) {
if (!audio_ready) {
mtk::draw_button(canvas, forget, "Retry", mtk::BUTTON_PRIMARY,
button_state(forget, true), theme);
}
mtk::draw_button(canvas, action, "Disconnect", mtk::BUTTON_DANGER,
button_state(action, true), theme);
} else {
@@ -721,6 +744,7 @@ static void render_scan_tab(Canvas& canvas, const mtk::Theme& theme) {
Rect scan_btn = scan_button_rect();
Rect list = {0, scan_list_top(), g_win_w, gui_max(scan_list_bottom() - scan_list_top(), 0)};
int fh = system_font_height();
bool bt_audio_ready = is_audio_ready();
if (g_scanning) {
mtk::draw_button(canvas, scan_btn, "Scanning...", mtk::BUTTON_SECONDARY,
@@ -747,8 +771,12 @@ static void render_scan_tab(Canvas& canvas, const mtk::Theme& theme) {
fill_circle(canvas, PAD + 6, row.y + ROW_H / 2, 5, theme.accent);
Rect action = row_action_button_rect(row);
Rect retry = row_forget_button_rect(row);
int text_x = PAD + 20;
int text_w_max = action.x - text_x - 10;
bool conn = is_connected(g_scan[i].bdAddr);
bool audio_ready = !conn || bt_audio_ready;
int text_w_max = (conn && !audio_ready ? retry.x : action.x)
- text_x - 10;
const char* display_name = g_scan[i].name[0] ? g_scan[i].name : "Unknown Device";
draw_text_fit(canvas, text_x, row.y + 4, display_name, text_w_max, theme.text);
@@ -760,7 +788,10 @@ static void render_scan_tab(Canvas& canvas, const mtk::Theme& theme) {
device_class_str(g_scan[i].classOfDevice), addr_str, rssi_bar(g_scan[i].rssi));
draw_text_fit(canvas, text_x, row.y + 4 + fh + 2, detail, text_w_max, theme.text_subtle);
bool conn = is_connected(g_scan[i].bdAddr);
if (conn && !audio_ready) {
mtk::draw_button(canvas, retry, "Retry", mtk::BUTTON_PRIMARY,
button_state(retry, true), theme);
}
mtk::draw_button(canvas, action, conn ? "Disconnect" : "Connect",
conn ? mtk::BUTTON_DANGER : mtk::BUTTON_PRIMARY,
button_state(action, true), theme);