Re: [PATCH v1] Bluetooth: mgmt: always reply when a queued mgmt command is cancelled

Shuai Zhang <[email protected]>
Newsgroups org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Hi Luiz

Sorry for the delayed response. I was OOO last week.

On 7/31/2026 11:25 PM, Luiz Augusto von Dentz wrote:
> Hi Shuai,
>
> On Fri, Jul 31, 2026 at 12:29 AM Shuai Zhang
> <[email protected]> wrote:
>> On boot, the kernel's HCI_AUTO_OFF timer powers a newly probed
>> controller back off after 2 seconds if nothing has taken ownership of
>> it yet. bluetooth.service is expected to take ownership within that
>> window by powering the adapter on, but on slower boots bluetoothd can
>> start late enough that the two race: HCI_AUTO_OFF fires and the
>> controller is torn down and re-initialised while bluetoothd is still
>> in the middle of its own adapter setup sequence.
>>
>> When that teardown happens, any mgmt command bluetoothd had already
>> queued for that adapter is cancelled - but userspace is never told.
>> bluez's mgmt command queue is strictly serialised, so a command that
>> never receives a reply blocks every command queued after it on that
>> adapter indefinitely. In practice this means bluetoothd gets stuck
>> before it can register the adapter, and reports no controller being
>> available at all, even though the controller itself is healthy and
>> fully usable moments later.
>>
>> Fix this by treating a cancelled command like any other failure
>> instead of silently dropping it: always send a reply back to
>> userspace, using the existing "cancelled" status value. This mirrors
>> how other mgmt commands already behave when they fail for other
>> reasons, and lets bluetoothd's normal error handling take over
>> (log the failure and continue) instead of hanging forever.
>>
>> Verified on a QCS6490 rb3gen2 board where this HCI_AUTO_OFF vs.
>> bluetooth.service race was reproduced: before this change, hitting
>> the race left the adapter permanently unavailable to bluetoothd until
>> the service was restarted; after this change, hitting the same race
>> no longer affects adapter registration and the controller comes up
>> normally.
>>
>> Signed-off-by: Shuai Zhang <[email protected]>
>> ---
>>   net/bluetooth/mgmt.c | 7 +++----
>>   1 file changed, 3 insertions(+), 4 deletions(-)
>>
>> diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
>> index 167d75e34..9031b5d5d 100644
>> --- a/net/bluetooth/mgmt.c
>> +++ b/net/bluetooth/mgmt.c
>> @@ -301,6 +301,8 @@ static u8 mgmt_errno_status(int err)
>>                  return MGMT_STATUS_ALREADY_CONNECTED;
>>          case -ENOTCONN:
>>                  return MGMT_STATUS_DISCONNECTED;
>> +       case -ECANCELED:
>> +               return MGMT_STATUS_CANCELLED;
>>          }
>>
>>          return MGMT_STATUS_FAILED;
>> @@ -1338,7 +1340,7 @@ static void mgmt_set_powered_complete(struct hci_dev *hdev, void *data, int err)
>>          struct mgmt_mode *cp;
>>
>>          /* Make sure cmd still outstanding. */
>> -       if (err == -ECANCELED || !mgmt_pending_valid(hdev, cmd))
>> +       if (!mgmt_pending_valid(hdev, cmd))
>>                  return;
> On set_powered_sync:
>
>      mutex_lock(&hdev->mgmt_pending_lock);
>
>      /* Make sure cmd still outstanding. */
>      if (!__mgmt_pending_listed(hdev, cmd)) {
>          mutex_unlock(&hdev->mgmt_pending_lock);
>          return -ECANCELED;
>      }
>
> Which is why we consider -ECANCELED as a NOP, also if you dig deeper
> you will see that hci_power_off -> hci_dev_do_close ->
> hci_dev_close_sync -> if (!auto_off) __mgmt_power_off ->
> mgmt_pending_foreach(0, hdev, true, cmd_complete_rsp, &match);
>
> So the reason this is not working as intended is because we are not
> clearing HCI_AUTO_OFF once we receive Set Powered command only once we
> run hci_power_on_sync which create a race where the likes of
> bluetoothd will be trying to power the adapter but power_off work is
> still pending, so we should probably clear HCI_AUTO_OFF whenever
> setting HCI_MGMT as at that point we know there is a user already
> there is no point in powering it off.

Agreed on mgmt_set_powered_complete, will drop that hunk in v2.

On mgmt_remove_adv_monitor_complete: the __mgmt_power_off() path you
describe only runs when auto_off=0, i.e. when bluetoothd explicitly
closes the adapter during normal operation.

In the race we are fixing, bluetoothd starts late and the HCI_AUTO_OFF
timer expires before it finishes adapter initialisation, so auto_off=1.
In that case __mgmt_power_off() is never called and there is no other
path that replies to the cancelled commands. hci_cmd_sync_clear()
cancels them with -ECANCELED, and the complete callback is the only
place left to send a reply. Silently returning there is what causes
bluetoothd's serialised mgmt queue to stall indefinitely.


>
>>          cp = cmd->param;
>> @@ -5651,9 +5653,6 @@ static void mgmt_remove_adv_monitor_complete(struct hci_dev *hdev,
>>          struct mgmt_pending_cmd *cmd = data;
>>          struct mgmt_cp_remove_adv_monitor *cp;
>>
>> -       if (status == -ECANCELED)
>> -               return;
>> -
>>          hci_dev_lock(hdev);
>>
>>          cp = cmd->param;
>> --
>> 2.34.1
>>

Thanks,

Shuai

>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.