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

Luiz Augusto von Dentz <[email protected]> Fri, 31 Jul 2026 11:25:31 -0400
Newsgroups org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel
Message-ID <CABBYNZJuenvKSvHuf6DCkt9hpdhuECm3gf5hiV+FyS2E9AM7nQ@mail.gmail.com>
Hi Shuai,

On Fri, Jul 31, 2026 at 12:29=E2=80=AFAM 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_de=
v *hdev, void *data, int err)
>         struct mgmt_mode *cp;
>
>         /* Make sure cmd still outstanding. */
> -       if (err =3D=3D -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.

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


--=20
Luiz Augusto von Dentz