* [PATCH BlueZ v2 0/2] input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn
@ 2026-09-03 11:48 Frédéric Danis
2026-09-03 11:48 ` [PATCH BlueZ v2 1/2] btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets Frédéric Danis
2026-09-03 11:48 ` [PATCH BlueZ v2 2/2] profiles/input: disable force-active for Nintendo Switch Pro v1 Frédéric Danis
0 siblings, 2 replies; 5+ messages in thread
From: Frédéric Danis @ 2026-09-03 11:48 UTC (permalink / raw)
To: linux-bluetooth
We received reports of Bluetooth instability with Nintendo Switch Pro v1
(NSP1), including unexpected disconnects during use. HCI traces indicate
the controller repeatedly requests Sniff mode while BlueZ keeps forcing
Active mode, creating frequent Sniff/Exit Sniff transitions and increasing
the chance of LMP Sniff Response failures.
This series adds generic btio support to control force-active behavior for
L2CAP sockets, then uses it in the input profile for NSP1 by disabling
force-active on the HID interrupt channel. With that, BlueZ stops
repeatedly issuing Exit Sniff requests for this device, improving
connection stability.
v1->v2: Add missing Co-developed-by tag
Frédéric Danis (2):
btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets
profiles/input: disable force-active for Nintendo Switch Pro v1
btio/btio.c | 35 +++++++++++++++++++++++++++++++----
btio/btio.h | 1 +
profiles/input/device.c | 16 ++++++++++++++++
3 files changed, 48 insertions(+), 4 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH BlueZ v2 1/2] btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets
2026-09-03 11:48 [PATCH BlueZ v2 0/2] input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn Frédéric Danis
@ 2026-09-03 11:48 ` Frédéric Danis
2026-09-03 15:48 ` input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn bluez.test.bot
2026-09-03 11:48 ` [PATCH BlueZ v2 2/2] profiles/input: disable force-active for Nintendo Switch Pro v1 Frédéric Danis
1 sibling, 1 reply; 5+ messages in thread
From: Frédéric Danis @ 2026-09-03 11:48 UTC (permalink / raw)
To: linux-bluetooth
Introduce BT_IO_OPT_FORCE_ACTIVE in btio and plumb it through option
parsing and L2CAP setup.
When set, btio now programs BT_POWER on the underlying socket, allowing
callers to explicitly control force-active behavior.
Co-developed-by: Zhongjun.Yu@quectel.com
Signed-off-by: Zhongjun.Yu@quectel.com
---
v1->v2: Add missing Co-developed-by tag
btio/btio.c | 35 +++++++++++++++++++++++++++++++----
btio/btio.h | 1 +
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/btio/btio.c b/btio/btio.c
index 4c69d6035..016035dff 100644
--- a/btio/btio.c
+++ b/btio/btio.c
@@ -68,6 +68,7 @@ struct set_opts {
int central;
uint8_t mode;
int flushable;
+ int force_active;
uint32_t priority;
uint16_t voice;
struct bt_iso_qos qos;
@@ -601,6 +602,17 @@ static int l2cap_set_flushable(int sock, gboolean flushable)
return 0;
}
+static int l2cap_set_bt_power(int sock, int force_active)
+{
+ struct bt_power pwr = {0};
+
+ pwr.force_active = force_active;
+ if (setsockopt(sock, SOL_BLUETOOTH, BT_POWER, &pwr, sizeof(pwr)) < 0)
+ return -errno;
+
+ return 0;
+}
+
static int set_priority(int sock, uint32_t prio)
{
if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0)
@@ -696,8 +708,8 @@ static gboolean set_le_mode(int sock, uint8_t mode, GError **err)
static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level,
int imtu, uint16_t omtu, uint8_t mode,
- int central, int flushable, uint32_t priority,
- GError **err)
+ int central, int flushable, int force_active,
+ uint32_t priority, GError **err)
{
if (imtu != -1 || omtu || mode) {
gboolean ret = FALSE;
@@ -736,6 +748,11 @@ static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level,
return FALSE;
}
+ if (force_active >= 0 && l2cap_set_bt_power(sock, force_active) < 0) {
+ ERROR_FAILED(err, "l2cap_set_bt_power", errno);
+ return FALSE;
+ }
+
if (priority > 0 && set_priority(sock, priority) < 0) {
ERROR_FAILED(err, "set_priority", errno);
return FALSE;
@@ -968,6 +985,7 @@ static gboolean parse_set_opts(struct set_opts *opts, GError **err,
opts->central = -1;
opts->mode = L2CAP_MODE_BASIC;
opts->flushable = -1;
+ opts->force_active = -1;
opts->priority = 0;
opts->src_type = BDADDR_BREDR;
opts->dst_type = BDADDR_BREDR;
@@ -1043,6 +1061,9 @@ static gboolean parse_set_opts(struct set_opts *opts, GError **err,
case BT_IO_OPT_FLUSHABLE:
opts->flushable = va_arg(args, gboolean);
break;
+ case BT_IO_OPT_FORCE_ACTIVE:
+ opts->force_active = va_arg(args, int);
+ break;
case BT_IO_OPT_PRIORITY:
opts->priority = va_arg(args, int);
break;
@@ -1387,6 +1408,7 @@ parse_opts:
case BT_IO_OPT_SOURCE_CHANNEL:
case BT_IO_OPT_DEST_CHANNEL:
case BT_IO_OPT_MTU:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_QOS:
case BT_IO_OPT_BASE:
@@ -1544,6 +1566,7 @@ static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1,
case BT_IO_OPT_IMTU:
case BT_IO_OPT_MODE:
case BT_IO_OPT_FLUSHABLE:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_PRIORITY:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_QOS:
@@ -1658,6 +1681,7 @@ static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args)
case BT_IO_OPT_CENTRAL:
case BT_IO_OPT_MODE:
case BT_IO_OPT_FLUSHABLE:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_PRIORITY:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_QOS:
@@ -1795,6 +1819,7 @@ static gboolean iso_get(int sock, GError **err, BtIOOption opt1, va_list args)
case BT_IO_OPT_CENTRAL:
case BT_IO_OPT_MODE:
case BT_IO_OPT_FLUSHABLE:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_PRIORITY:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_ISO_BC_NUM_BIS:
@@ -1963,7 +1988,8 @@ gboolean bt_io_set(GIOChannel *io, GError **err, BtIOOption opt1, ...)
case BT_IO_L2CAP:
return l2cap_set(sock, opts.src_type, opts.sec_level, opts.imtu,
opts.omtu, opts.mode, opts.central,
- opts.flushable, opts.priority, err);
+ opts.flushable, opts.force_active,
+ opts.priority, err);
case BT_IO_RFCOMM:
return rfcomm_set(sock, opts.sec_level, opts.central, err);
case BT_IO_SCO:
@@ -2014,7 +2040,8 @@ static GIOChannel *create_io(gboolean server, struct set_opts *opts,
goto failed;
if (!l2cap_set(sock, opts->src_type, opts->sec_level,
opts->imtu, opts->omtu, opts->mode,
- opts->central, opts->flushable, opts->priority,
+ opts->central, opts->flushable,
+ opts->force_active, opts->priority,
err))
goto failed;
break;
diff --git a/btio/btio.h b/btio/btio.h
index 3e69092b1..f088c3cd4 100644
--- a/btio/btio.h
+++ b/btio/btio.h
@@ -42,6 +42,7 @@ typedef enum {
BT_IO_OPT_CLASS,
BT_IO_OPT_MODE,
BT_IO_OPT_FLUSHABLE,
+ BT_IO_OPT_FORCE_ACTIVE,
BT_IO_OPT_PRIORITY,
BT_IO_OPT_VOICE,
BT_IO_OPT_PHY,
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH BlueZ v2 2/2] profiles/input: disable force-active for Nintendo Switch Pro v1
2026-09-03 11:48 [PATCH BlueZ v2 0/2] input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn Frédéric Danis
2026-09-03 11:48 ` [PATCH BlueZ v2 1/2] btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets Frédéric Danis
@ 2026-09-03 11:48 ` Frédéric Danis
1 sibling, 0 replies; 5+ messages in thread
From: Frédéric Danis @ 2026-09-03 11:48 UTC (permalink / raw)
To: linux-bluetooth
For Nintendo Switch Pro v1 controllers (VID:PID 057e:2009), set
BT_IO_OPT_FORCE_ACTIVE to BT_POWER_FORCE_ACTIVE_OFF on the HID interrupt
channel during connection setup.
This avoids repeated host-triggered Exit Sniff requests and reduces
Sniff/Exit Sniff churn linked to unexpected disconnects.
Co-developed-by: Zhongjun.Yu@quectel.com
Signed-off-by: Zhongjun.Yu@quectel.com
---
v1->v2: Add missing Co-developed-by tag
profiles/input/device.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/profiles/input/device.c b/profiles/input/device.c
index 5b1be2b16..c2abe2451 100644
--- a/profiles/input/device.c
+++ b/profiles/input/device.c
@@ -1065,6 +1065,9 @@ static gboolean encrypt_notify(GIOChannel *io, GIOCondition condition,
return FALSE;
}
+#define NINTENDO_VENDOR_ID 0x057e
+#define NINTENDO_PRO_CONTROLLER_PID 0x2009
+
static int hidp_add_connection(struct input_device *idev)
{
struct hidp_connadd_req *req;
@@ -1101,6 +1104,19 @@ static int hidp_add_connection(struct input_device *idev)
goto cleanup;
}
+ if ((req->vendor == NINTENDO_VENDOR_ID) &&
+ (req->product == NINTENDO_PRO_CONTROLLER_PID)) {
+ if (!bt_io_set(idev->intr_io, &gerr,
+ BT_IO_OPT_FORCE_ACTIVE,
+ BT_POWER_FORCE_ACTIVE_OFF,
+ BT_IO_OPT_INVALID)) {
+ error("btio: %s", gerr->message);
+ g_error_free(gerr);
+ err = -EFAULT;
+ goto cleanup;
+ }
+ }
+
/* Encryption is mandatory for keyboards */
/* Some platforms may choose to require encryption for all devices */
/* Note that this only matters for pre 2.1 devices as otherwise the */
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH BlueZ 1/2] btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets
@ 2026-08-26 16:41 Frédéric Danis
2026-08-26 19:13 ` input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn bluez.test.bot
0 siblings, 1 reply; 5+ messages in thread
From: Frédéric Danis @ 2026-08-26 16:41 UTC (permalink / raw)
To: linux-bluetooth
Introduce BT_IO_OPT_FORCE_ACTIVE in btio and plumb it through option
parsing and L2CAP setup.
When set, btio now programs BT_POWER on the underlying socket, allowing
callers to explicitly control force-active behavior.
---
btio/btio.c | 35 +++++++++++++++++++++++++++++++----
btio/btio.h | 1 +
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/btio/btio.c b/btio/btio.c
index 4c69d6035..016035dff 100644
--- a/btio/btio.c
+++ b/btio/btio.c
@@ -68,6 +68,7 @@ struct set_opts {
int central;
uint8_t mode;
int flushable;
+ int force_active;
uint32_t priority;
uint16_t voice;
struct bt_iso_qos qos;
@@ -601,6 +602,17 @@ static int l2cap_set_flushable(int sock, gboolean flushable)
return 0;
}
+static int l2cap_set_bt_power(int sock, int force_active)
+{
+ struct bt_power pwr = {0};
+
+ pwr.force_active = force_active;
+ if (setsockopt(sock, SOL_BLUETOOTH, BT_POWER, &pwr, sizeof(pwr)) < 0)
+ return -errno;
+
+ return 0;
+}
+
static int set_priority(int sock, uint32_t prio)
{
if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0)
@@ -696,8 +708,8 @@ static gboolean set_le_mode(int sock, uint8_t mode, GError **err)
static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level,
int imtu, uint16_t omtu, uint8_t mode,
- int central, int flushable, uint32_t priority,
- GError **err)
+ int central, int flushable, int force_active,
+ uint32_t priority, GError **err)
{
if (imtu != -1 || omtu || mode) {
gboolean ret = FALSE;
@@ -736,6 +748,11 @@ static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level,
return FALSE;
}
+ if (force_active >= 0 && l2cap_set_bt_power(sock, force_active) < 0) {
+ ERROR_FAILED(err, "l2cap_set_bt_power", errno);
+ return FALSE;
+ }
+
if (priority > 0 && set_priority(sock, priority) < 0) {
ERROR_FAILED(err, "set_priority", errno);
return FALSE;
@@ -968,6 +985,7 @@ static gboolean parse_set_opts(struct set_opts *opts, GError **err,
opts->central = -1;
opts->mode = L2CAP_MODE_BASIC;
opts->flushable = -1;
+ opts->force_active = -1;
opts->priority = 0;
opts->src_type = BDADDR_BREDR;
opts->dst_type = BDADDR_BREDR;
@@ -1043,6 +1061,9 @@ static gboolean parse_set_opts(struct set_opts *opts, GError **err,
case BT_IO_OPT_FLUSHABLE:
opts->flushable = va_arg(args, gboolean);
break;
+ case BT_IO_OPT_FORCE_ACTIVE:
+ opts->force_active = va_arg(args, int);
+ break;
case BT_IO_OPT_PRIORITY:
opts->priority = va_arg(args, int);
break;
@@ -1387,6 +1408,7 @@ parse_opts:
case BT_IO_OPT_SOURCE_CHANNEL:
case BT_IO_OPT_DEST_CHANNEL:
case BT_IO_OPT_MTU:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_QOS:
case BT_IO_OPT_BASE:
@@ -1544,6 +1566,7 @@ static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1,
case BT_IO_OPT_IMTU:
case BT_IO_OPT_MODE:
case BT_IO_OPT_FLUSHABLE:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_PRIORITY:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_QOS:
@@ -1658,6 +1681,7 @@ static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args)
case BT_IO_OPT_CENTRAL:
case BT_IO_OPT_MODE:
case BT_IO_OPT_FLUSHABLE:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_PRIORITY:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_QOS:
@@ -1795,6 +1819,7 @@ static gboolean iso_get(int sock, GError **err, BtIOOption opt1, va_list args)
case BT_IO_OPT_CENTRAL:
case BT_IO_OPT_MODE:
case BT_IO_OPT_FLUSHABLE:
+ case BT_IO_OPT_FORCE_ACTIVE:
case BT_IO_OPT_PRIORITY:
case BT_IO_OPT_VOICE:
case BT_IO_OPT_ISO_BC_NUM_BIS:
@@ -1963,7 +1988,8 @@ gboolean bt_io_set(GIOChannel *io, GError **err, BtIOOption opt1, ...)
case BT_IO_L2CAP:
return l2cap_set(sock, opts.src_type, opts.sec_level, opts.imtu,
opts.omtu, opts.mode, opts.central,
- opts.flushable, opts.priority, err);
+ opts.flushable, opts.force_active,
+ opts.priority, err);
case BT_IO_RFCOMM:
return rfcomm_set(sock, opts.sec_level, opts.central, err);
case BT_IO_SCO:
@@ -2014,7 +2040,8 @@ static GIOChannel *create_io(gboolean server, struct set_opts *opts,
goto failed;
if (!l2cap_set(sock, opts->src_type, opts->sec_level,
opts->imtu, opts->omtu, opts->mode,
- opts->central, opts->flushable, opts->priority,
+ opts->central, opts->flushable,
+ opts->force_active, opts->priority,
err))
goto failed;
break;
diff --git a/btio/btio.h b/btio/btio.h
index 3e69092b1..f088c3cd4 100644
--- a/btio/btio.h
+++ b/btio/btio.h
@@ -42,6 +42,7 @@ typedef enum {
BT_IO_OPT_CLASS,
BT_IO_OPT_MODE,
BT_IO_OPT_FLUSHABLE,
+ BT_IO_OPT_FORCE_ACTIVE,
BT_IO_OPT_PRIORITY,
BT_IO_OPT_VOICE,
BT_IO_OPT_PHY,
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-03 15:48 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 11:48 [PATCH BlueZ v2 0/2] input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn Frédéric Danis
2026-09-03 11:48 ` [PATCH BlueZ v2 1/2] btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets Frédéric Danis
2026-09-03 15:48 ` input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn bluez.test.bot
2026-09-03 11:48 ` [PATCH BlueZ v2 2/2] profiles/input: disable force-active for Nintendo Switch Pro v1 Frédéric Danis
-- strict thread matches above, loose matches on Subject: below --
2026-08-26 16:41 [PATCH BlueZ 1/2] btio: add BT_IO_OPT_FORCE_ACTIVE support for L2CAP sockets Frédéric Danis
2026-08-26 19:13 ` input: avoid NSP1 disconnects caused by Sniff/Exit Sniff churn 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