Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ] adapter: Set Audio service class bit for A2DP sink
@ 2026-09-18 13:27 Mathias Grelck Johnsen
  2026-09-18 15:03 ` [BlueZ] " bluez.test.bot
  2026-10-03 14:53 ` bluez.test.bot
  0 siblings, 2 replies; 3+ messages in thread
From: Mathias Grelck Johnsen @ 2026-09-18 13:27 UTC (permalink / raw)
  To: linux-bluetooth@vger.kernel.org; +Cc: luiz.von.dentz@intel.com

From: Mathias Johnsen <majo@bang-olufsen.dk>

Problem: The Nintendo Switch refuses to connect to a device that does
not advertise the CoD 'Audio' (0x20) service class bit. It completes
pairing, takes the link key, then terminates the ACL with reason 0x13
without ever authenticating, encrypting, or connecting AVDTP on PSM 25.

Cause: get_uuid_mask() currently only grants the Audio service class
bit to HEADSET and HANDSFREE, so a device implementing A2DP sink
without HFP or HSP advertises 'Rendering' with no Audio bit set.

Fix: Report 'Audio' in addition to 'Rendering' (0x04) for
AUDIO_SINK_SVCLASS_ID in get_uuid_mask().

Tested on a Yocto-based speaker running bluez 5.65 with this change
backported and compile-tested on current master. Note get_uuid_mask()
is unchanged between 5.65 and current master. The speaker under test is
purely an A2DP sink; the patch changes the CoD from 0x040414 to
0x240414, after which the Switch connects and streams normally.

Note that A2DP v1.4 section 5.5.1 mandates only the 'Rendering' bit for
a sink, so the current behavior is conformant as it stands. Conversely,
adding the bit is not a violation either: Assigned Numbers section
2.8.1 defines bit 21 as 'Audio (Speaker, Microphone, Headset service,
...)', which a loudspeaker A2DP sink fits.

This is the same kind of device-specific workaround as the
HANDSFREE_AGW telephony bit for Nokia 616 carkit also present in the
get_uuid_mask() switch-statement.

Assisted-by: Claude:claude-opus-5
---
Switch system version 22.5.0. The speaker under test implements A2DP
sink and AVRCP only, no HFP or HSP, so get_uuid_mask() never grants it
the Audio bit.

CoD before: 0x040414  (Rendering | Audio/Video | Loudspeaker)
CoD after:  0x240414  (Audio | Rendering | Audio/Video | Loudspeaker)

btmon excerpt of a failing connection, before the change. BD_ADDRs and
the link key are removed; nested SDP data elements are elided and
summarised in parentheses.

> HCI Event: Connect Complete (0x03) plen 11           #21308 302.177551
        Status: Success (0x00)
        Link type: ACL (0x01)
        Encryption: Disabled (0x00)

> ACL Data RX: Handle 2 flags 0x02 dlen 28             #21350 302.288211
      Channel: 64 len 24 [PSM 1 mode Basic (0x00)] {chan 0}
      SDP: Service Search Attribute Request (0x06) tid 0 len 19
        [...]
      (search pattern: Audio Sink 0x110b; attribute IDs 0x0001, 0x0004,
       0x0009)

< ACL Data TX: Handle 2 flags 0x00 dlen 58             #21351 302.288706
      Channel: 82 len 54 [PSM 1 mode Basic (0x00)] {chan 0}
      SDP: Service Search Attribute Response (0x07) tid 0 len 49
        [...]
      (Service Class ID List: Audio Sink 0x110b; Protocol Descriptor
       List: L2CAP PSM 25, AVDTP 0x0019; Bluetooth Profile Descriptor
       List: A2DP 0x0103)

> ACL Data RX: Handle 2 flags 0x02 dlen 12             #21353 302.293176
      L2CAP: Disconnection Request (0x06) ident 6 len 4

> HCI Event: IO Capability Response (0x32) plen 9      #21357 302.298185
        IO capability: NoInputNoOutput (0x03)
        Authentication: General Bonding - MITM not required (0x04)

> HCI Event: Simple Pairing Complete (0x36) plen 7     #21398 302.649188
        Status: Success (0x00)

> HCI Event: Link Key Notification (0x18) plen 23      #21402 302.672037
        Link key: <redacted>
        Key type: Unauthenticated Combination key from P-192 (0x04)

> HCI Event: Disconnect Complete (0x05) plen 4         #21403 302.675468
        Status: Success (0x00)
        Reason: Remote User Terminated Connection (0x13)

The Switch tears down the ACL 3 ms after receiving the link key.
There is no Authentication Complete, no Encryption Change, and no
L2CAP connect to PSM 25 anywhere in the capture. The following
attempt repeats the full SSP exchange rather than authenticating
with the stored key, so the bond is not retained on the Switch
side.

 src/adapter.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/src/adapter.c b/src/adapter.c
index fc734424b..98d758cdc 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -1303,9 +1303,17 @@ static uint8_t get_uuid_mask(uuid_t *uuid)
 	case AUDIO_SOURCE_SVCLASS_ID:
 	case VIDEO_SOURCE_SVCLASS_ID:
 		return 0x08;	/* Capturing */
-	case AUDIO_SINK_SVCLASS_ID:
 	case VIDEO_SINK_SVCLASS_ID:
 		return 0x04;	/* Rendering */
+	/*
+	 * A2DP v1.4 section 5.5.1 mandates only the 'Rendering'
+	 * bit for a sink, but the Nintendo Switch will not connect
+	 * to an audio device whose Class of Device is missing the
+	 * 'Audio' bit, which is otherwise only set for HEADSET and
+	 * HANDSFREE.
+	 */
+	case AUDIO_SINK_SVCLASS_ID:
+		return 0x24;	/* Audio & Rendering */
 	case PANU_SVCLASS_ID:
 	case NAP_SVCLASS_ID:
 	case GN_SVCLASS_ID:
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-03 14:53 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-18 13:27 [PATCH BlueZ] adapter: Set Audio service class bit for A2DP sink Mathias Grelck Johnsen
2026-09-18 15:03 ` [BlueZ] " bluez.test.bot
2026-10-03 14:53 ` bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox