From 0ad48c88a2ed3b58164fa771977868503b0abac3 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Mon, 5 Oct 2026 11:15:02 -0400 Subject: [PATCH] Bluetooth: MGMT: Fix advertising instances with Global Advertising Global Advertising, enabled with MGMT_OP_SET_ADVERTISING, uses instance 0 and with extended advertising it has its own advertising set, but instances were still being treated as if they shared the same set: - Add Advertising and Add Extended Advertising Data would return success without ever enabling the instance. - Removing the last instance would disable all advertising sets, stopping Global Advertising while leaving its setting enabled. - Disabling Global Advertising would disable all sets, including active instances. Fix this by only overriding instances that share the advertising set of Global Advertising, and by tracking HCI_LE_ADV_0 rather than HCI_LE_ADV to determine the state of Global Advertising. Fixes: 0d92808024b4 ("Bluetooth: HCI: Fix tracking of advertisement set/instance 0x00") Assisted-by: OpenCode:claude-opus-5.5 Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_sync.h | 1 + net/bluetooth/hci_event.c | 11 ++++- net/bluetooth/hci_sync.c | 2 +- net/bluetooth/mgmt.c | 83 +++++++++++++++++++++++++++----- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h index a6579a868678..59cb918d1405 100644 --- a/include/net/bluetooth/hci_sync.h +++ b/include/net/bluetooth/hci_sync.h @@ -122,6 +122,7 @@ int hci_disable_per_advertising_sync(struct hci_dev *hdev, u8 instance); int hci_remove_advertising_sync(struct hci_dev *hdev, struct sock *sk, u8 instance, bool force); int hci_disable_advertising_sync(struct hci_dev *hdev); +int hci_disable_ext_adv_legacy_instance_sync(struct hci_dev *hdev); int hci_clear_adv_instance_sync(struct hci_dev *hdev, struct sock *sk, u8 instance, bool force); int hci_update_passive_scan_sync(struct hci_dev *hdev); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index a3359aaad001..0f7e08f8a0b8 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1686,15 +1686,22 @@ static u8 hci_cc_le_set_ext_adv_enable(struct hci_dev *hdev, void *data, hci_dev_clear_flag(hdev, HCI_LE_ADV_0); /* If just one instance was disabled check if there are - * any other instance enabled before clearing HCI_LE_ADV + * any other instance enabled, including instance 0, + * before clearing HCI_LE_ADV */ + if (hci_dev_test_flag(hdev, HCI_LE_ADV_0)) + goto unlock; + list_for_each_entry_safe(adv, n, &hdev->adv_instances, list) { if (adv->enabled) goto unlock; } } else { - /* All instances shall be considered disabled */ + /* All instances shall be considered disabled, except + * instance 0 whose state is preserved so it can be + * re-enabled by hci_resume_advertising_sync. + */ list_for_each_entry_safe(adv, n, &hdev->adv_instances, list) adv->enabled = false; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index a1cba1a7d6d5..8650a3f00834 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -1158,7 +1158,7 @@ int hci_update_random_address_sync(struct hci_dev *hdev, bool require_privacy, return 0; } -static int hci_disable_ext_adv_legacy_instance_sync(struct hci_dev *hdev) +int hci_disable_ext_adv_legacy_instance_sync(struct hci_dev *hdev) { struct hci_cp_le_set_ext_adv_enable *cp; struct hci_cp_ext_adv_set *set; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 2e35b09579d3..684e8a6c4820 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -6546,7 +6546,12 @@ static void set_advertising_complete(struct hci_dev *hdev, void *data, int err) return; } - if (hci_dev_test_flag(hdev, HCI_LE_ADV)) + /* With extended advertising global advertising (instance 0) has its + * own advertising set so only consider its state, other instances may + * still be enabled. + */ + if (ext_adv_capable(hdev) ? hci_dev_test_flag(hdev, HCI_LE_ADV_0) : + hci_dev_test_flag(hdev, HCI_LE_ADV)) hci_dev_set_flag(hdev, HCI_ADVERTISING); else hci_dev_clear_flag(hdev, HCI_ADVERTISING); @@ -6566,6 +6571,34 @@ static void set_advertising_complete(struct hci_dev *hdev, void *data, int err) list_empty(&hdev->adv_instances)) return; + /* With extended advertising instances are not overridden by global + * advertising, except the ones sharing its set, so only (re)start the + * instances that are not already enabled. + * + * Lookup instances by identifier while holding hdev->lock since the + * list may change while waiting for the HCI commands to complete. + */ + if (ext_adv_capable(hdev)) { + u16 i; + + for (i = 1; i <= hdev->le_num_of_adv_sets + 1; i++) { + bool enabled; + + hci_dev_lock(hdev); + adv_instance = hci_find_adv_instance(hdev, i); + enabled = !adv_instance || adv_instance->enabled; + hci_dev_unlock(hdev); + + if (enabled) + continue; + + err = hci_schedule_adv_instance_sync(hdev, i, true); + enable_advertising_instance(hdev, err); + } + + return; + } + instance = hdev->cur_adv_instance; if (!instance) { adv_instance = list_first_entry_or_null(&hdev->adv_instances, @@ -6621,6 +6654,11 @@ static int set_adv_sync(struct hci_dev *hdev, void *data) hci_update_scan_rsp_data_sync(hdev, 0x00); hci_enable_advertising_sync(hdev); } + } else if (ext_adv_capable(hdev)) { + /* Only disable global advertising (instance 0) leaving other + * instances untouched. + */ + hci_disable_ext_adv_legacy_instance_sync(hdev); } else { hci_disable_advertising_sync(hdev); } @@ -8952,6 +8990,21 @@ static bool adv_busy(struct hci_dev *hdev) return pending_find(MGMT_OP_SET_LE, hdev); } +/* Global advertising, enabled with MGMT_OP_SET_ADVERTISING, uses instance 0 + * and takes precedence over any instance that would share its advertising + * set: with legacy advertising there is only a single set so all instances + * are overridden, whereas with extended advertising only an instance using + * handle 0x00 is. + */ +static bool adv_overridden_by_global(struct hci_dev *hdev, + struct adv_info *adv) +{ + if (!hci_dev_test_flag(hdev, HCI_ADVERTISING)) + return false; + + return !ext_adv_capable(hdev) || !adv->handle; +} + static void add_adv_complete(struct hci_dev *hdev, struct sock *sk, u8 instance, int err) { @@ -9113,12 +9166,12 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev, schedule_instance = cp->instance; } - /* If the HCI_ADVERTISING flag is set or the device isn't powered or - * there is no instance to be advertised then we have no HCI - * communication to make. Simply return. + /* If the instance is overridden by global advertising or the device + * isn't powered or there is no instance to be advertised then we have + * no HCI communication to make. Simply return. */ if (!hdev_is_powered(hdev) || - hci_dev_test_flag(hdev, HCI_ADVERTISING) || + adv_overridden_by_global(hdev, adv) || !schedule_instance) { rp.instance = cp->instance; err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_ADD_ADVERTISING, @@ -9293,8 +9346,11 @@ static int add_ext_adv_params(struct sock *sk, struct hci_dev *hdev, goto unlock; } - /* Submit request for advertising params if ext adv available */ - if (ext_adv_capable(hdev)) { + /* Submit request for advertising params if ext adv available, unless + * the set is in use by global advertising in which case the params + * will be programmed once the instance is scheduled. + */ + if (ext_adv_capable(hdev) && !adv_overridden_by_global(hdev, adv)) { cmd = mgmt_pending_new(sk, MGMT_OP_ADD_EXT_ADV_PARAMS, hdev, data, data_len); if (!cmd) { @@ -9444,11 +9500,12 @@ static int add_ext_adv_data(struct sock *sk, struct hci_dev *hdev, void *data, schedule_instance = cp->instance; } - /* If the HCI_ADVERTISING flag is set or there is no instance to - * be advertised then we have no HCI communication to make. + /* If the instance is overridden by global advertising or there is no + * instance to be advertised then we have no HCI communication to make. * Simply return. */ - if (hci_dev_test_flag(hdev, HCI_ADVERTISING) || !schedule_instance) { + if (adv_overridden_by_global(hdev, adv_instance) || + !schedule_instance) { if (adv_instance->pending) { mgmt_advertising_added(sk, hdev, cp->instance); adv_instance->pending = false; @@ -9524,7 +9581,11 @@ static int remove_advertising_sync(struct hci_dev *hdev, void *data) if (err) return err; - if (list_empty(&hdev->adv_instances)) + /* Don't disable advertising if global advertising (instance 0) is + * enabled since it is not affected by removing instances. + */ + if (list_empty(&hdev->adv_instances) && + !hci_dev_test_flag(hdev, HCI_ADVERTISING)) err = hci_disable_advertising_sync(hdev); return err;