[PATCH v4] Bluetooth: hci_sync: pause advertising for the scan address update
From: Valentin Kindschi
Date: Wed Sep 16 2026 - 06:13:27 EST
hci_active_scan_sync() programs a non-resolvable private address with LE
Set Random Address on every active scan start.
BLUETOOTH CORE SPECIFICATION Vol 4, Part E, 7.8.4 says the controller
shall return Command Disallowed (0x0C) for that command while legacy
advertising or scanning is enabled. hci_pause_addr_resolution(), called
just above, only stops advertising when LL privacy is in use, so on a
controller without it the command is issued while advertising is still
on:
Bluetooth: hci0: Opcode 0x2005 failed: -16
It does not converge either. hdev->random_addr is only set on a
successful command complete, so it stays BDADDR_ANY, and the deferral
added by commit c2994b008492 ("Bluetooth: hci_sync: Fix not setting
Random Address when required") is skipped in exactly that case. Observed
on a BCM43455, which has no LL privacy and no extended advertising, at
the scan restart period of about 10 s, for as long as discovery keeps
restarting:
< LE Set Random Address Address: 02:16:91:90:F1:D4 (Non-Resolvable)
> Command Complete LE Set Random Address, Command Disallowed
< LE Set Random Address Address: 26:90:57:96:9A:3E (Non-Resolvable)
> Command Complete LE Set Random Address, Command Disallowed
< LE Set Random Address Address: 16:32:01:BC:B5:CE (Non-Resolvable)
> Command Complete LE Set Random Address, Command Disallowed
Pause advertising for the address update regardless of privacy, and
resume it on every exit path. The resume was previously guarded by
ll_privacy_capable() and only reached on the error path, which matched
the pause being privacy-only.
With the patch, on the same hardware:
< LE Set Advertising Enable Success
< LE Set Random Address Success
< LE Set Scan Parameters Success
< LE Set Scan Enable Success
< LE Set Advertising Parameters Success
< LE Set Advertising Enable Success
The last two commands are the resume, issued with the scan already
running, and both succeed: on this controller re-enabling legacy
advertising during an active scan does not need a further address write,
so it is not refused.
Over 35425 btmon records and about 4 minutes of continuous active
discovery with advertising enabled there were no Command Disallowed
responses of any opcode, against one per scan restart before, and a
central could still connect.
One caveat this widens, flagged on the previous posting. When
HCI_ADVERTISING is set, hci_pause_advertising_sync() also clears
HCI_DISCOVERABLE and HCI_LIMITED_DISCOVERABLE and zeroes discov_timeout,
and hci_resume_advertising_sync() restores only HCI_ADVERTISING, so the
discoverable state is lost for good. It reproduces today on an LL privacy
controller through hci_pause_addr_resolution(), with no scan patch
involved; this patch makes it reachable on controllers without LL privacy
as well. On a BCM43455 carrying this patch:
btmgmt -i hci0 connectable yes
btmgmt -i hci0 advertising on
btmgmt -i hci0 discov yes
current settings: powered connectable discoverable le advertising ...
btmgmt -i hci0 find -l
current settings: powered connectable le advertising ...
hci_suspend_sync() already calls hci_pause_advertising_sync()
unconditionally, with no privacy guard, so a device that suspends loses
the same state on any controller today. The clear is also reached only
with HCI_ADVERTISING set, i.e. when advertising is mgmt-managed, not for
a device made discoverable without it.
That asymmetry is pre-existing and independent of this patch, so it is
left alone here rather than folded into a scan path fix.
Tooling disclosure (Documentation/process/generated-content.rst): an AI
coding assistant was used to investigate this and to draft the change;
the patch text and code are its output, reviewed by me. Inputs were btmon
captures and kernel logs from the affected device, with the request to
identify what re-issues LE Set Random Address every ~10 s and to fix it.
Three earlier explanations it proposed were discarded after being checked
against the captures: bluetoothd restarting service discovery; RPA
rotation (excluded, Privacy=off); and the static-address branch of
hci_update_random_address_sync() (excluded, both random_address and
static_address read 00:00:00:00:00:00). The cause was only established
after decoding the command payloads, which showed a freshly generated
non-resolvable address per attempt. Testing is as described above, on the
device, using btmon and a second device to confirm connectability.
Fixes: abfeea476c68 ("Bluetooth: hci_sync: Convert MGMT_OP_START_DISCOVERY")
Assisted-by: Claude:claude-opus-5 btmon
Signed-off-by: Valentin Kindschi <valentin.kindschi@xxxxxxxxx>
---
Changes in v4:
- Report the HCI_DISCOVERABLE asymmetry raised in review rather than fix
it: hci_pause_advertising_sync() clears HCI_DISCOVERABLE and
HCI_LIMITED_DISCOVERABLE and zeroes discov_timeout, and the resume
restores only HCI_ADVERTISING. It is pre-existing on LL privacy
controllers, which reach the same pause through
hci_pause_addr_resolution(); this patch widens it to controllers
without LL privacy. The commit message now carries a reproducer.
- Keep the explicit return on the success path, so the resume runs there
and the function no longer falls through into failed:.
- Address the review question on whether that resume can itself be
refused while the scan is running: the capture above shows both resume
commands succeeding with LE Set Scan Enable already sent.
- Shorten the added comments to one line each.
Changes in v3:
- Resend, no code change; v2 had no reply. Rechecked that it still applies
to bluetooth-next.
- Added the second capture described above, taken with the two patches from
the "endless adv params retry" series applied, since in bluetooth-next,
confirming the fix holds with cancelled outgoing connections in the mix.
Changes in v2:
- Rebased onto bluetooth-next: mainline renamed use_ll_privacy() to
ll_privacy_capable(). No functional change.
net/bluetooth/hci_sync.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -6264,6 +6264,11 @@ static int hci_active_scan_sync(struct hci_dev *hdev, uint16_t interval)
if (err)
goto failed;
+ /* LE Set Random Address is disallowed while advertising is enabled. */
+ err = hci_pause_advertising_sync(hdev);
+ if (err)
+ goto failed;
+
/* All active scans will be done with either a resolvable private
* address (when privacy feature has been enabled) or non-resolvable
* private address.
@@ -6292,13 +6297,14 @@ static int hci_active_scan_sync(struct hci_dev *hdev, uint16_t interval)
err = hci_start_scan_sync(hdev, LE_SCAN_ACTIVE, interval,
hdev->le_scan_window_discovery,
own_addr_type, filter_policy, filter_dup);
- if (!err)
+ if (!err) {
+ hci_resume_advertising_sync(hdev);
return err;
+ }
failed:
- /* Resume advertising if it was paused */
- if (ll_privacy_capable(hdev))
- hci_resume_advertising_sync(hdev);
+ /* No-op when advertising was not paused. */
+ hci_resume_advertising_sync(hdev);
/* Resume passive scanning */
hci_update_passive_scan_sync(hdev);
--
2.34.1