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: 0d92808024 ("Bluetooth: HCI: Fix tracking of advertisement set/instance 0x00")
Assisted-by: OpenCode:claude-opus-5.5
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Luiz Augusto von Dentz 2026-10-05 11:15:02 -04:00
parent 3d9526b171
commit 0ad48c88a2
4 changed files with 83 additions and 14 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;