From: Valentin Kindschi <valentin.kindschi@fiveco.ch>
To: <linux-bluetooth@vger.kernel.org>
Cc: <marcel@holtmann.org>, <johan.hedberg@gmail.com>,
<luiz.dentz@gmail.com>, <linux-kernel@vger.kernel.org>,
Valentin Kindschi <valentin.kindschi@fiveco.ch>,
<stable@vger.kernel.org>
Subject: [PATCH v4 2/2] Bluetooth: hci_event: clear HCI_LE_ADV only on a created connection
Date: Tue, 18 Aug 2026 15:29:35 +0200 [thread overview]
Message-ID: <20260818132935.1083808-3-valentin.kindschi@fiveco.ch> (raw)
In-Reply-To: <20260818132935.1083808-1-valentin.kindschi@fiveco.ch>
le_conn_complete_evt() clears HCI_LE_ADV before looking at the event
status, on the premise stated in its comment that all controllers stop
advertising when a connection is created.
That premise only holds when a connection was actually created. On a
non-zero status none was, and the controller is still advertising: after
the host issues LE Create Connection Cancel the event arrives with
Unknown Connection Identifier (0x02), and a connection timeout behaves
the same way. Clearing the flag there leaves the host believing
advertising is off while the controller has it on.
It is also wrong for extended advertising, where several sets can be
advertising at once. hci_cc_le_set_ext_adv_enable() is careful about
this - on disabling one set it walks hdev->adv_instances and only clears
HCI_LE_ADV once no instance is still enabled. The unconditional clear
here discards that bookkeeping, so one set connecting drops the flag
while the others keep advertising.
The direction of the error matters. A flag left set is self-correcting:
hci_disable_advertising_sync() sends LE Set Advertising Enable(0) and
the command complete puts the state back. A flag left clear is not,
because that same function returns early without sending anything while
the flag is clear:
- LE Set Advertising Parameters is then sent to a controller that is
still advertising, and is correctly rejected with Command Disallowed
(0x0c);
- hci_enable_advertising_sync() returns at that point, before the
LE Set Advertising Enable that would set HCI_LE_ADV again.
On a controller without LE Extended Advertising that is reachable from
here: hci_schedule_adv_instance_sync() re-arms adv_instance_expire every
HCI_DEFAULT_ADV_DURATION (2 s) and its "already advertising" shortcut
tests HCI_LE_ADV, which can no longer become true, so the parameter
write is retried for as long as advertising is configured:
Bluetooth: hci0: Opcode 0x2006 failed: -16
Only clear the flag when a connection was established.
Note this is not on its own sufficient to stop that retry loop - the
redundant enable queued by hci_le_conn_failed() clears HCI_LE_ADV itself
and recreates the same mismatch, which patch 1 addresses. This patch
fixes the event handler reporting a state the controller is not in.
Verified on the affected device (BCM43455, legacy advertising only) with
this patch and patch 1 applied. A 221 s btmon capture with an out-of-range
peer at -90 dBm contains two outgoing connection attempts that the host
cancelled, each producing exactly the event this patch changes:
< LE Set Advertising Parameters 0x2006 Success
< LE Set Advertising Enable 0x200a Success
< LE Create Connection Cancel 0x200e Success
> LE Connection Complete Unknown Connection Identifier (0x02), central
Nothing follows either one; the next command is an unrelated scan restart
70 ms later. Over the whole capture: 7 LE Set Advertising Parameters sent,
all Success; 10 LE Set Advertising Enable, all Success; no Command
Disallowed of any opcode, and no 2 s cadence anywhere. Two central
connections to other peers completed normally afterwards, with feature
exchange and a connection parameter update, so advertising was still live
across the cancelled attempts.
The extended advertising case above is a code argument, not a measurement:
this controller has no LE Extended Advertising, so that path is not
exercised by the capture.
Fixes: fbd96c151cdc ("Bluetooth: Fix clearing HCI_LE_ADV for LE connections")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-5 btmon
Signed-off-by: Valentin Kindschi <valentin.kindschi@fiveco.ch>
---
Changes in v4:
- Test !status instead of status != HCI_ERROR_UNKNOWN_CONN_ID, per review:
a connection timeout should not clear the flag either. Built and tested
on the device in that form; the capture quoted above is from this
version, so the effect is now measured rather than inferred as in v2/v3.
- Note the extended-advertising case raised in review: several sets can be
advertising at once, and hci_cc_le_set_ext_adv_enable() already tracks
that per instance, which the unconditional clear here was discarding.
- On Advertising Timeout (0x3c), which v2/v3 used to justify a narrower
test: a controller can report it, since hci_le_directed_advertising_sync()
uses high duty cycle directed advertising. Not clearing the flag there is
harmless because directed advertising is always bracketed by
hci_pause_advertising_sync() / hci_resume_advertising_sync() in
hci_le_create_conn_sync(), and the resume re-schedules with force=true,
which bypasses the HCI_LE_ADV shortcut. le_conn_timeout() also disables
advertising itself on the peripheral path.
- A stale-set flag is self-correcting (hci_disable_advertising_sync() then
sends the disable) whereas a stale-clear flag is self-sustaining, which is
the bug here, so erring towards set is the safer direction.
Changes in v3:
- Shortened the subject and removed hard tabs from the changelog (GitLint).
Changes in v2:
- Rebased onto bluetooth-next; no functional change.
v1's hci_event.c context lacked the hci_store_wake_reason() call
present in mainline, so the hunk did not apply.
net/bluetooth/hci_event.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -5720,10 +5720,11 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
hci_dev_lock(hdev);
hci_store_wake_reason(hdev, bdaddr, bdaddr_type);
- /* All controllers implicitly stop advertising in the event of a
- * connection, so ensure that the state bit is cleared.
+ /* Advertising stops when a connection is created. On a failed
+ * connection it keeps running, so leave the state bit alone.
*/
- hci_dev_clear_flag(hdev, HCI_LE_ADV);
+ if (!status)
+ hci_dev_clear_flag(hdev, HCI_LE_ADV);
/* Check for existing connection:
*
--
2.34.1
prev parent reply other threads:[~2026-08-18 13:30 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 13:29 [PATCH v4 0/2] Bluetooth: fix endless adv params retry after a cancelled connection Valentin Kindschi
2026-08-18 13:29 ` [PATCH v4 1/2] Bluetooth: hci_conn: re-enable advertising only for peripheral role Valentin Kindschi
2026-08-18 14:59 ` Bluetooth: fix endless adv params retry after a cancelled connection bluez.test.bot
2026-08-18 13:29 ` Valentin Kindschi [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260818132935.1083808-3-valentin.kindschi@fiveco.ch \
--to=valentin.kindschi@fiveco.ch \
--cc=johan.hedberg@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox