From: sashiko-bot@kernel.org
To: christian.koever-draxl@student.uibk.ac.at
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH 3/7] Bluetooth: hci_aml: cope with a controller that is already running
Date: Sun, 25 Oct 2026 20:16:15 +0000 [thread overview]
Message-ID: <sashiko-outbox-184417@kernel.org> (raw)
In-Reply-To: <20261025192338.209574-4-christian.koever-draxl@student.uibk.ac.at>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Bluetooth: hci_aml: aml_fw_already_running() timeout stalls retries
- [High] Bluetooth: hci_aml: aml_resync() hangs in serdev_device_write()
- [Medium] Bluetooth: hci_aml: unbalanced power state in aml_setup() and aml_power_on()
--
commit 603cd1d2f458c40daa60bef1f089a3a6efc3085b
Author: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
Bluetooth: hci_aml: cope with a controller that is already running
This commit updates the Amlogic Bluetooth HCI UART driver to detect
already-running controller firmware via HCI_OP_READ_LOCAL_VERSION, retry
unanswered firmware download segments after sending UART resync bytes,
increase firmware startup polling delays, and keep the controller powered
across HCI close until driver removal or shutdown.
> diff --git a/drivers/bluetooth/hci_aml.c b/drivers/bluetooth/hci_aml.c
> index 5979b114f2e4f..8561618867e83 100644
> --- a/drivers/bluetooth/hci_aml.c
> +++ b/drivers/bluetooth/hci_aml.c
[ ... ]
> @@ -214,11 +238,33 @@ static int aml_send_firmware_segment(struct hci_dev *hdev,
> op_addr, (u32 *)seg, seg_size);
> }
>
> -static int aml_send_firmware(struct hci_dev *hdev, u8 fw_type,
> - u8 *fw, u32 fw_size, u32 offset)
> +static const u8 aml_resync_pattern[256];
> +
> +/* A command that goes unanswered has usually been half received: the
> + * controller took what did arrive for the start of a packet and waits for
> + * the rest, so every following command is swallowed as its payload. Send
> + * enough bytes to complete whatever it is reassembling. Zero is not a valid
> + * H4 packet type, so what is left over afterwards is discarded.
> + */
> +static void aml_resync(struct hci_uart *hu, unsigned int chunks)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < chunks; i++)
> + serdev_device_write(hu->serdev, aml_resync_pattern,
> + sizeof(aml_resync_pattern),
> + MAX_SCHEDULE_TIMEOUT);
> +
> + serdev_device_wait_until_sent(hu->serdev, 0);
> +}
[Severity: High]
Can calling serdev_device_write() with MAX_SCHEDULE_TIMEOUT in aml_resync()
hang the hdev->req_workqueue worker thread when a firmware segment retry is
triggered via aml_setup() -> aml_setup_controller() ->
aml_download_firmware() -> aml_send_firmware() -> aml_resync()?
In drivers/tty/serdev/core.c:serdev_device_write(), if the controller's
write_buf() callback accepts fewer than count bytes, it waits for
serdev->write_comp:
drivers/tty/serdev/core.c:serdev_device_write() {
mutex_lock(&serdev->write_lock);
do {
reinit_completion(&serdev->write_comp);
ret = ctrl->ops->write_buf(ctrl, buf, count);
if (ret < 0)
break;
written += ret;
buf += ret;
count -= ret;
if (count == 0)
break;
timeout = wait_for_completion_interruptible_timeout(&serdev->write_comp,
timeout);
} while (timeout > 0);
mutex_unlock(&serdev->write_lock);
}
serdev_device_write() requires the serdev client's write_wakeup callback to
call serdev_device_write_wakeup() in order to complete serdev->write_comp.
However, hci_serdev registers hci_uart_write_wakeup() in
drivers/bluetooth/hci_serdev.c, which only calls hci_uart_tx_wakeup(hu) and
never calls serdev_device_write_wakeup(serdev):
drivers/bluetooth/hci_serdev.c:hci_uart_write_wakeup() {
...
if (test_bit(HCI_UART_PROTO_READY, &hu->flags))
hci_uart_tx_wakeup(hu);
}
Because aml_resync() runs on a kernel workqueue thread with
MAX_SCHEDULE_TIMEOUT and serdev->write_comp is never completed, wouldn't any
partial write in serdev_device_write() block forever while holding
serdev->write_lock, as well as bypass hu->write_work without synchronizing
with any in-flight hu->tx_skb?
[ ... ]
> @@ -242,6 +302,35 @@ static int aml_send_firmware(struct hci_dev *hdev, u8 fw_type,
> return 0;
> }
>
> +/* Dropping the enable line does not reset these controllers: they keep
> + * running the firmware they were last given, at the baud rate they were last
> + * told to use. A warm reboot or a driver reload therefore finds an HCI device
> + * at the operational rate that no longer understands TCI commands. Read Local
> + * Version tells the two cases apart: the ROM does not answer it, running
> + * firmware does.
> + */
> +static bool aml_fw_already_running(struct hci_dev *hdev, struct hci_uart *hu)
> +{
> + struct sk_buff *skb;
> + int i;
> +
> + serdev_device_set_baudrate(hu->serdev, hu->proto->oper_speed);
> +
> + for (i = 0; i < AML_RESYNC_RETRIES; i++) {
> + skb = __hci_cmd_sync_ev(hdev, HCI_OP_READ_LOCAL_VERSION, 0,
> + NULL, HCI_EV_CMD_COMPLETE,
> + AML_PROBE_TIMEOUT);
> + if (!IS_ERR(skb)) {
> + kfree_skb(skb);
> + return true;
> + }
> + }
> +
> + serdev_device_set_baudrate(hu->serdev, hu->proto->init_speed);
> +
> + return false;
> +}
[Severity: High]
When the first __hci_cmd_sync_ev() call in aml_fw_already_running() times out
after AML_PROBE_TIMEOUT (500 ms), are the remaining retry iterations actually
transmitted over the UART?
On the first iteration (i = 0), hci_cmd_work() dequeues the command SKB,
calls hci_send_cmd_sync() which decrements hdev->cmd_cnt to 0, and arms
hdev->cmd_timer for HCI_CMD_TIMEOUT (2000 ms):
net/bluetooth/hci_core.c:hci_cmd_work() {
if (atomic_read(&hdev->cmd_cnt)) {
skb = skb_dequeue(&hdev->cmd_q);
if (!skb)
return;
err = hci_send_cmd_sync(hdev, skb);
if (err)
return;
rcu_read_lock();
if (test_bit(HCI_RESET, &hdev->flags) ||
hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE))
cancel_delayed_work(&hdev->cmd_timer);
else
queue_delayed_work(hdev->workqueue, &hdev->cmd_timer,
HCI_CMD_TIMEOUT);
rcu_read_unlock();
}
}
When the controller does not reply within 500 ms,
wait_event_interruptible_timeout() in
net/bluetooth/hci_sync.c:__hci_cmd_sync_sk() returns -ETIMEDOUT without
resetting hdev->cmd_cnt or canceling hdev->cmd_timer.
On iterations i = 1, 2, and 3, __hci_cmd_sync_ev() queues three more
HCI_OP_READ_LOCAL_VERSION SKBs onto hdev->cmd_q and schedules hdev->cmd_work,
but hci_cmd_work() sees atomic_read(&hdev->cmd_cnt) == 0 and returns without
sending them. At 2000 ms (4 * 500 ms == HCI_CMD_TIMEOUT), hci_cmd_timeout()
fires, cancels the final wait, sets hdev->cmd_cnt back to 1, and schedules
hdev->cmd_work:
net/bluetooth/hci_core.c:hci_cmd_timeout() {
...
atomic_set(&hdev->cmd_cnt, 1);
queue_work(hdev->workqueue, &hdev->cmd_work);
}
Doesn't this prevent retries 1..3 from ever being sent on the wire while
probing at oper_speed, and leave three unsent HCI_OP_READ_LOCAL_VERSION SKBs
in hdev->cmd_q that get transmitted asynchronously after
aml_fw_already_running() has already restored init_speed and returned false?
[ ... ]
> @@ -508,6 +597,7 @@ static int aml_power_on(struct aml_serdev *amldev)
> }
>
> gpiod_set_value_cansleep(amldev->bt_en_gpio, 1);
> + amldev->powered = true;
>
> /* Wait 20ms for bluetooth controller power on */
> msleep(20);
> @@ -516,6 +606,10 @@ static int aml_power_on(struct aml_serdev *amldev)
>
> static int aml_power_off(struct aml_serdev *amldev)
> {
> + if (!amldev->powered)
> + return 0;
> +
> + amldev->powered = false;
> gpiod_set_value_cansleep(amldev->bt_en_gpio, 0);
>
> clk_disable_unprepare(amldev->lpo_clk);
> @@ -568,7 +662,6 @@ static int aml_open(struct hci_uart *hu)
>
> static int aml_close(struct hci_uart *hu)
> {
> - struct aml_serdev *amldev = serdev_device_get_drvdata(hu->serdev);
> struct aml_data *aml_data = hu->priv;
>
> skb_queue_purge(&aml_data->txq);
> @@ -577,7 +670,13 @@ static int aml_close(struct hci_uart *hu)
>
> hu->priv = NULL;
>
> - return aml_power_off(amldev);
> + /* The controller is left powered. Setup, which powers it on, only
> + * runs once per registration, and dropping the enable line does not
> + * reset it but leaves it unreachable until the combo chip is power
> + * cycled, so powering off here would lose the controller on the next
> + * open. It is powered off on remove and shutdown.
> + */
> + return 0;
> }
[Severity: Medium]
What happens to the enable counts on amldev->bt_supply and amldev->lpo_clk if
aml_setup() fails after aml_power_on(amldev) succeeds, and the HCI device is
subsequently opened again?
In drivers/bluetooth/hci_aml.c:aml_setup():
err = aml_power_on(amldev);
if (err)
return err;
err = aml_setup_controller(hu, amldev, hdev);
if (err)
return err;
Previously, if hci_dev_init_sync() failed during hci_dev_open_sync(), the
error path called hdev->close(hdev) -> aml_close() -> aml_power_off(amldev).
With aml_power_off() removed from aml_close() and not called on error in
aml_setup(), a failure in aml_setup_controller(), aml_send_reset(),
aml_check_bdaddr(), or later in hci_dev_init_sync() leaves amldev->powered
set to true while HCI_SETUP remains set on hdev.
If another open attempt is made (for example via hciconfig up or btmgmt power
on), hci_dev_setup_sync() invokes aml_setup() -> aml_power_on(amldev) again.
Because aml_power_on() does not check amldev->powered before calling
regulator_enable(amldev->bt_supply) and clk_prepare_enable(amldev->lpo_clk),
won't their enable counts be incremented a second time while aml_power_off()
only disables amldev->lpo_clk and amldev->bt_supply once on remove or
shutdown?
Also, unlike aml_serdev_remove() which calls hci_uart_unregister_device()
first, aml_serdev_shutdown() calls aml_power_off(amldev) directly; can that
race with asynchronous aml_setup() work running on hdev->req_workqueue?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261025192338.209574-1-christian.koever-draxl@student.uibk.ac.at?part=3
next prev parent reply other threads:[~2026-10-25 20:16 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-25 19:23 [PATCH 0/7] Bluetooth: make the Amlogic W1 (W155S2) usable christian.koever-draxl
2026-10-25 19:23 ` [PATCH 1/7] Bluetooth: add a quirk for controllers that cannot enable Secure Connections christian.koever-draxl
2026-10-25 20:08 ` sashiko-bot
2026-10-25 19:23 ` [PATCH 2/7] Bluetooth: add a quirk for controllers that hang on Write Current IAC LAP christian.koever-draxl
2026-10-25 19:23 ` [PATCH 3/7] Bluetooth: hci_aml: cope with a controller that is already running christian.koever-draxl
2026-10-25 20:16 ` sashiko-bot [this message]
2026-10-25 19:23 ` [PATCH 4/7] dt-bindings: net: bluetooth: amlogic,w155s2-bt: allow local-bd-address christian.koever-draxl
2026-10-25 19:23 ` [PATCH 5/7] Bluetooth: hci_aml: take the address from the firmware description christian.koever-draxl
2026-10-25 19:23 ` [PATCH 6/7] dt-bindings: net: bluetooth: amlogic,w155s2-bt: add amlogic,shared-antenna christian.koever-draxl
2026-10-25 19:23 ` [PATCH 7/7] Bluetooth: hci_aml: configure the RF for a shared antenna christian.koever-draxl
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-184417@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=christian.koever-draxl@student.uibk.ac.at \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox