[PATCH] Bluetooth: hci_qca: Reset the in-band sleep state on power off
From: Birk Skyum
Date: Sat Oct 10 2026 - 07:36:43 EST
qca_power_off() cuts the power of the controller but leaves the host
side of the in-band sleep (IBS) protocol as it was. tx_ibs_state and
rx_ibs_state are initialised in qca_open() only, so a controller that
qca_setup() powers on again meets whatever state the last session ended
in.
If that state is HCI_IBS_TX_WAKING, the host is waiting for a wake
acknowledgment, and qca_hci_shutdown() has deleted the timer that
sends the wake indication again. The setup still works, because frames
bypass IBS while QCA_IBS_DISABLED is set. Once the flag is cleared,
qca_enqueue() puts every frame on tx_wait_q and nothing takes it off:
Bluetooth: hci0: QCA setup on UART is completed
Bluetooth: hci0: Opcode 0x0c03 failed: -110
A restore from hibernation gets there on a Lenovo Yoga Slim 7x
(WCN7850). The controller has been through a power cycle, while the
restored kernel holds the state from before the image was taken. Its
wake indications go unanswered, the commands behind them time out, and
the core closes the device and sets it up again. From then on
tx_ibs_state reads 1 in debugfs and Bluetooth stays dead until hci_uart
is reloaded.
When the controller loses power, put both sides back to asleep, which
is where qca_open() starts, and stop the two IBS timers.
Signed-off-by: Birk Skyum <birk.skyum@xxxxx>
---
Tested on a Lenovo Yoga Slim 7x with Arch Linux ARM's v7.2 kernel and
hci_uart rebuilt with this change. qca_open(), qca_enqueue() and
qca_hci_shutdown() are the same there as in bluetooth-next, which this
is based on and where it builds without warnings (W=1).
Each run is a fresh boot, a hibernation with a real power-off and a
resume, with Bluetooth powered before it:
- Without the patch Bluetooth is dead afterwards in four runs out of
four, with the log above. In the one run where I read it, debugfs
showed tx_ibs_state=1, ibs_sent_wakes=109 and ibs_recv_wake_acks=8.
- With the patch it is powered again by itself in two runs out of two,
about 20 seconds after the resume. The log shows a failed version
read and "Retry BT power ON:0" before a setup that works. In the run
where I read it, debugfs showed tx_ibs_state=0 afterwards.
- Powering the controller off and on with bluetoothctl still works.
The 20 seconds are the commands that time out before the core gives up
on the old session. This patch does not change that part.
The kernel also carried the hibernation changes in [1], [2] and [3],
without which this machine does not get this far, and a GENI serial
change that is not ready for the list.
[1] https://lore.kernel.org/r/179157746908.73485.2343672865542690797@xxxxx
[2] https://lore.kernel.org/r/179158127177.70185.5691100900886332545@xxxxx
[3] https://lore.kernel.org/r/179163141354.18006.80461347454353634@xxxxx
drivers/bluetooth/hci_qca.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c
index 7089e9b63..19a75faba 100644
--- a/drivers/bluetooth/hci_qca.c
+++ b/drivers/bluetooth/hci_qca.c
@@ -2235,6 +2235,11 @@ static void qca_power_off(struct hci_uart *hu)
spin_lock_irqsave(&qca->hci_ibs_lock, flags);
set_bit(QCA_IBS_DISABLED, &qca->flags);
qca_flush(hu);
+ /* A controller that loses power is asleep when it comes back. */
+ timer_delete(&qca->wake_retrans_timer);
+ timer_delete(&qca->tx_idle_timer);
+ qca->tx_ibs_state = HCI_IBS_TX_ASLEEP;
+ qca->rx_ibs_state = HCI_IBS_RX_ASLEEP;
spin_unlock_irqrestore(&qca->hci_ibs_lock, flags);
/* Non-serdev device usually is powered by external power
--
2.56.0