public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH BlueZ v3 0/6] BLE-HID/Nintendo Switch 2 support
@ 2026-04-03  8:55 Martin BTS
  2026-04-03  8:55 ` [PATCH BlueZ v3 1/6] shared/gatt: Add skip_secondary option for GATT client Martin BTS
                   ` (5 more replies)
  0 siblings, 6 replies; 9+ messages in thread
From: Martin BTS @ 2026-04-03  8:55 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: hadess, luiz.dentz, vi, Martin BTS

Changes v3:
* Simplified config setting approach.
* Added a 2-second timeout for secondary discovery.
* Separated BLE/GATT-to-HID code from Procon 2 code.
* Removed all Switch/Procon 2 overlap with the HID kernel driver.
* Link to v2: https://lore.kernel.org/all/20260308124745.19248-1-martinbts@gmx.net/

v3 blurb:

While the overall motivation for my work is the Procon 2 controller, there
are two things very BlueZ specific:

A) Devices (e.g. the Procon 2) may deviate from the spec and not respond
   to service discovery, which currently must result in a failed
   connection attempt.
B) (I think) There currently isn't a facility that allows feeding HID
   devices BLE/GATT traffic.

* Patches 1 and 2 are two alternatives to resolve problem A.
* Patch 5 attempts to resolve problem B.
* Patches 3-4 are minor cleanups (rename set_alias, add Gaming
  appearance class), unchanged from v2.
* Patch 6 is minimalistic support for the Switch 2 controllers so that
  HID drivers can take over

Patch 1 uses a configuration setting (v1 feedback), while
Patch 2 uses a new 2-second timeout (v2 feedback).

Both work, but connecting the controller using only Patch 2 is a lot
less reliable. The Procon 2 does only try/wait for a connection for
a few seconds. BLE discovery tends to take so long that together with
the 2 second timeout the controller gives up before the timeout fires.
Patch 1 always worked, when the device showed up in bluetoothctl.

Patch 1 and Patch 2 are both individually suited to solve my
immediate problem. From my point of view it is not necessary to apply
both. I am submitting both for side-by-side comparison and discussion.

Patch 5 is a GATT-UHID bridge that forwards notifications from the
controller to the hid device and hid-device-writes to the
controller. This was previously burried in all the other code that
supported the Procon 2. It can now be used for other input devices that
use only GATT. As far as I can tell, this does not overlap at all with
other Switch 2 support efforts.

Patche 6 is specific to the Switch 2 controllers so that they can be
interacted with through uhid. In contrast to v1 and v2 this is "minimal"
code that makes bluetoothd create a HID device and does the plumbing for
a GATT-HID bridge. There is no overlap to any published Switch 2 driver (that
I am aware of). The result is not usable. One still needs a driver that
"does the right thing". I am keeping my "works on my machine" code here:
https://github.com/martin-bts/hid-switch2-dkms. I will keep using it
until we get proper driver support in the kernel.

Disclaimer (as in v1): I only have a Nintendo Switch 2 Pro Controller
and cannot attest to anything related to any other controller.

Martin BTS (6):
  shared/gatt: Add skip_secondary option for GATT client
  shared/gatt: Add timeout for secondary service discovery
  device: Rename set_alias to  btd_device_set_alias()
  dbus-common: Add Gaming appearance class (0x2a)
  plugins/gatt-uhid: Add generic GATT-to-UHID bridge
  plugins/switch2: Add Nintendo Switch 2 Controller plugin

 Makefile.plugins         |   5 +
 peripheral/gatt.c        |   3 +-
 plugins/gatt-uhid.c      | 350 +++++++++++++++++++++++++++++++++++++++
 plugins/gatt-uhid.h      |  56 +++++++
 plugins/switch2.c        | 255 ++++++++++++++++++++++++++++
 src/dbus-common.c        |   2 +
 src/device.c             |  24 +--
 src/device.h             |   2 +
 src/shared/gatt-client.c |  70 +++++++-
 src/shared/gatt-client.h |   3 +-
 tools/btgatt-client.c    |   2 +-
 unit/test-bap.c          |   2 +-
 unit/test-gatt.c         |   2 +-
 unit/test-gmap.c         |   2 +-
 unit/test-mcp.c          |   2 +-
 unit/test-micp.c         |   3 +-
 unit/test-tmap.c         |   2 +-
 17 files changed, 762 insertions(+), 23 deletions(-)
 create mode 100644 plugins/gatt-uhid.c
 create mode 100644 plugins/gatt-uhid.h
 create mode 100644 plugins/switch2.c

-- 
2.47.3


^ permalink raw reply	[flat|nested] 9+ messages in thread
* [PATCH BlueZ v3 1/6] shared/gatt: Add skip_secondary option for GATT client
@ 2026-03-17 20:26 Martin BTS
  2026-03-17 21:31 ` BLE-HID/Nintendo Switch 2 support bluez.test.bot
  0 siblings, 1 reply; 9+ messages in thread
From: Martin BTS @ 2026-03-17 20:26 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: hadess, luiz.dentz, vi, Martin BTS, Claude Opus 4.6 (1M context)

Some BLE devices reject or ignore secondary service discovery requests,
causing ATT timeouts that terminate the connection. Add a skip_secondary
parameter to bt_gatt_client_new() that skips the secondary service
discovery step during GATT client initialization. Add
btd_device_set_skip_secondary() so device-specific plugins can enable
this. All existing callers pass false (no behavior change).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 peripheral/gatt.c        | 3 ++-
 src/device.c             | 9 ++++++++-
 src/device.h             | 1 +
 src/shared/gatt-client.c | 8 ++++++--
 src/shared/gatt-client.h | 3 ++-
 tools/btgatt-client.c    | 2 +-
 unit/test-bap.c          | 2 +-
 unit/test-gatt.c         | 2 +-
 unit/test-gmap.c         | 2 +-
 unit/test-mcp.c          | 2 +-
 unit/test-micp.c         | 3 ++-
 unit/test-tmap.c         | 2 +-
 12 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/peripheral/gatt.c b/peripheral/gatt.c
index d1ddf0c97..2af67f24a 100644
--- a/peripheral/gatt.c
+++ b/peripheral/gatt.c
@@ -121,7 +121,8 @@ static struct gatt_conn *gatt_conn_new(int fd)
 		return NULL;
 	}
 
-	conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu, 0);
+	conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu, 0,
+								false);
 	if (!conn->gatt) {
 		fprintf(stderr, "Failed to create GATT client\n");
 		bt_gatt_server_unref(conn->gatt);
diff --git a/src/device.c b/src/device.c
index 3ea683667..fbe137db7 100644
--- a/src/device.c
+++ b/src/device.c
@@ -213,6 +213,7 @@ struct btd_device {
 	bool		pending_paired;		/* "Paired" waiting for SDP */
 	bool		svc_refreshed;
 	bool		refresh_discovery;
+	bool		skip_secondary;
 
 	/* Manage whether this device can wake the system from suspend.
 	 * - wake_support: Requires a profile that supports wake (i.e. HID)
@@ -6302,7 +6303,8 @@ static void gatt_client_init(struct btd_device *device)
 	}
 
 	device->client = bt_gatt_client_new(device->db, device->att,
-						device->att_mtu, features);
+						device->att_mtu, features,
+						device->skip_secondary);
 	if (!device->client) {
 		DBG("Failed to initialize");
 		return;
@@ -8254,6 +8256,11 @@ void btd_device_set_conn_param(struct btd_device *device, uint16_t min_interval,
 					timeout);
 }
 
+void btd_device_set_skip_secondary(struct btd_device *device, bool skip)
+{
+	device->skip_secondary = skip;
+}
+
 void btd_device_foreach_service_data(struct btd_device *dev, bt_ad_func_t func,
 							void *data)
 {
diff --git a/src/device.h b/src/device.h
index c7b8b2a16..fe988652d 100644
--- a/src/device.h
+++ b/src/device.h
@@ -231,6 +231,7 @@ void btd_device_foreach_ad(struct btd_device *dev, bt_device_ad_func_t func,
 void btd_device_set_conn_param(struct btd_device *device, uint16_t min_interval,
 					uint16_t max_interval, uint16_t latency,
 					uint16_t timeout);
+void btd_device_set_skip_secondary(struct btd_device *device, bool skip);
 void btd_device_foreach_service_data(struct btd_device *dev,
 					bt_device_ad_func_t func,
 					void *data);
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index df1541b88..e1685809f 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -93,6 +93,7 @@ struct bt_gatt_client {
 	struct queue *notify_chrcs;
 	int next_reg_id;
 	unsigned int disc_id, nfy_id, nfy_mult_id, ind_id;
+	bool skip_secondary;
 
 	/*
 	 * Handles of the GATT Service and the Service Changed characteristic
@@ -1344,7 +1345,7 @@ secondary:
 	 * functionality of a device and is referenced from at least one
 	 * primary service on the device.
 	 */
-	if (queue_isempty(op->pending_svcs))
+	if (queue_isempty(op->pending_svcs) || client->skip_secondary)
 		goto done;
 
 	/* Discover secondary services */
@@ -2550,7 +2551,8 @@ fail:
 struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db,
 							struct bt_att *att,
 							uint16_t mtu,
-							uint8_t features)
+							uint8_t features,
+							bool skip_secondary)
 {
 	struct bt_gatt_client *client;
 
@@ -2561,6 +2563,8 @@ struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db,
 	if (!client)
 		return NULL;
 
+	client->skip_secondary = skip_secondary;
+
 	if (!gatt_client_init(client, mtu)) {
 		bt_gatt_client_free(client);
 		return NULL;
diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
index 63cf99500..d9655e6f0 100644
--- a/src/shared/gatt-client.h
+++ b/src/shared/gatt-client.h
@@ -19,7 +19,8 @@ struct bt_gatt_client;
 struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db,
 							struct bt_att *att,
 							uint16_t mtu,
-							uint8_t features);
+							uint8_t features,
+							bool skip_secondary);
 struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client);
 
 struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client);
diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c
index 667b3d651..58999011a 100644
--- a/tools/btgatt-client.c
+++ b/tools/btgatt-client.c
@@ -206,7 +206,7 @@ static struct client *client_create(int fd, uint16_t mtu)
 		return NULL;
 	}
 
-	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu, 0);
+	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu, 0, false);
 	if (!cli->gatt) {
 		fprintf(stderr, "Failed to create GATT client\n");
 		gatt_db_unref(cli->db);
diff --git a/unit/test-bap.c b/unit/test-bap.c
index 3a67e7016..221bbedfb 100644
--- a/unit/test-bap.c
+++ b/unit/test-bap.c
@@ -582,7 +582,7 @@ static void test_setup(const void *user_data)
 	db = gatt_db_new();
 	g_assert(db);
 
-	data->client = bt_gatt_client_new(db, att, 64, 0);
+	data->client = bt_gatt_client_new(db, att, 64, 0, false);
 	g_assert(data->client);
 
 	bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 535baafc6..8780cc4f5 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -684,7 +684,7 @@ static struct context *create_context(uint16_t mtu, gconstpointer data)
 		g_assert(context->client_db);
 
 		context->client = bt_gatt_client_new(context->client_db,
-							context->att, mtu, 0);
+						context->att, mtu, 0, false);
 		g_assert(context->client);
 
 		bt_gatt_client_set_debug(context->client, print_debug,
diff --git a/unit/test-gmap.c b/unit/test-gmap.c
index 8b37efd18..fbf1529a6 100644
--- a/unit/test-gmap.c
+++ b/unit/test-gmap.c
@@ -323,7 +323,7 @@ static void test_setup(const void *user_data)
 	db = gatt_db_new();
 	g_assert(db);
 
-	data->client = bt_gatt_client_new(db, att, 64, 0);
+	data->client = bt_gatt_client_new(db, att, 64, 0, false);
 	g_assert(data->client);
 
 	bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
diff --git a/unit/test-mcp.c b/unit/test-mcp.c
index 7d922bb83..3c5cdaad3 100644
--- a/unit/test-mcp.c
+++ b/unit/test-mcp.c
@@ -509,7 +509,7 @@ static void test_setup(const void *user_data)
 	db = gatt_db_new();
 	g_assert(db);
 
-	data->client = bt_gatt_client_new(db, att, 64, 0);
+	data->client = bt_gatt_client_new(db, att, 64, 0, false);
 	g_assert(data->client);
 
 	bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
diff --git a/unit/test-micp.c b/unit/test-micp.c
index ff17300d5..64a248a40 100644
--- a/unit/test-micp.c
+++ b/unit/test-micp.c
@@ -500,7 +500,8 @@ static void test_setup(const void *user_data)
 	db = gatt_db_new();
 	g_assert(db);
 
-	data->client = bt_gatt_client_new(db, att, MICP_GATT_CLIENT_MTU, 0);
+	data->client = bt_gatt_client_new(db, att, MICP_GATT_CLIENT_MTU, 0,
+								false);
 	g_assert(data->client);
 
 	bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
diff --git a/unit/test-tmap.c b/unit/test-tmap.c
index e75d62119..44465abf5 100644
--- a/unit/test-tmap.c
+++ b/unit/test-tmap.c
@@ -288,7 +288,7 @@ static void test_setup(const void *user_data)
 	db = gatt_db_new();
 	g_assert(db);
 
-	data->client = bt_gatt_client_new(db, att, 64, 0);
+	data->client = bt_gatt_client_new(db, att, 64, 0, false);
 	g_assert(data->client);
 
 	bt_gatt_client_set_debug(data->client, print_debug, "bt_gatt_client:",
-- 
2.47.3


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

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

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03  8:55 [PATCH BlueZ v3 0/6] BLE-HID/Nintendo Switch 2 support Martin BTS
2026-04-03  8:55 ` [PATCH BlueZ v3 1/6] shared/gatt: Add skip_secondary option for GATT client Martin BTS
2026-04-03 10:18   ` BLE-HID/Nintendo Switch 2 support bluez.test.bot
2026-04-03  8:55 ` [PATCH BlueZ v3 2/6] shared/gatt: Add timeout for secondary service discovery Martin BTS
2026-04-03  8:55 ` [PATCH BlueZ v3 3/6] device: Rename set_alias to btd_device_set_alias() Martin BTS
2026-04-03  8:55 ` [PATCH BlueZ v3 4/6] dbus-common: Add Gaming appearance class (0x2a) Martin BTS
2026-04-03  8:55 ` [PATCH BlueZ v3 5/6] plugins/gatt-uhid: Add generic GATT-to-UHID bridge Martin BTS
2026-04-03  8:55 ` [PATCH BlueZ v3 6/6] plugins/switch2: Add Nintendo Switch 2 Controller plugin Martin BTS
  -- strict thread matches above, loose matches on Subject: below --
2026-03-17 20:26 [PATCH BlueZ v3 1/6] shared/gatt: Add skip_secondary option for GATT client Martin BTS
2026-03-17 21:31 ` BLE-HID/Nintendo Switch 2 support 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