* [PATCH v4 2/4] Bluetooth: btintel_pcie: fix PM flow for S0ix, S3 and S4
2026-09-26 8:52 [PATCH v4 1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check Ravindra
@ 2026-09-26 8:53 ` Ravindra
2026-09-26 8:53 ` [PATCH v4 3/4] Bluetooth: btintel_pcie: verify state after alive interrupt Ravindra
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Ravindra @ 2026-09-26 8:53 UTC (permalink / raw)
To: linux-bluetooth, lsa.uz, vladimirkondratyev2
Cc: chethan.tumkur.narayan, kiran.k, Paul Menzel, Ravindra
Fix two issues in the PM suspend/resume path:
1. S3 was handled the same as S0ix, keeping the controller on the
D3hot-style path. That caused resume instability because S3 can
remove power from the controller, unlike s2idle/S0ix. Use
pm_suspend_target_state to distinguish them: D3_HOT for S0ix and
D3_COLD for S3/S4. Add .restore to force FLR-based firmware recovery
after S4 and S3 (PM_SUSPEND_MEM), as power is lost. S0ix resumes via
a normal D0 transition.
2. During hibernation, .freeze() puts the controller into the D3cold
state without any loss of power, and the flow normally continues to
.poweroff(). If hibernation instead fails, .thaw() is called to bring
the controller back up, and the old code routed it through
btintel_pcie_resume(), which forced FLR-based firmware recovery
whenever data->pm_sx_event was PM_EVENT_FREEZE. That check was
incorrect: since the controller's power was never actually removed on
this failed-hibernation path, FLR-based recovery is unnecessary. Remove
pm_sx_event and route .thaw through a normal D0 transition instead;
FLR-based recovery is retained only in .restore, where genuine S4
power loss requires it.
Tested with:
S0ix: sudo sh -c 'echo "+40" > /sys/class/rtc/rtc0/wakealarm' && \
echo freeze | sudo tee /sys/power/state
S3: sudo rtcwake -m mem -s 60
S4: sudo rtcwake -m disk -s 60
Fixes: e57362f4911b ("Bluetooth: btintel_pcie: Add support for _suspend() / _resume()")
Assisted-by: GitHub-Copilot:GPT5
Signed-off-by: Ravindra <ravindra@intel.com>
Tested-by: Sergey Lebedev <lsa.uz@pm.me>
Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
---
drivers/bluetooth/btintel_pcie.c | 58 +++++++++++++++++++++-----------
drivers/bluetooth/btintel_pcie.h | 2 --
2 files changed, 39 insertions(+), 21 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 677749903173..082f5ec8ba71 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/acpi.h>
+#include <linux/suspend.h>
#include <linux/unaligned.h>
#include <linux/devcoredump.h>
@@ -4199,7 +4200,8 @@ static void btintel_pcie_coredump(struct device *dev)
static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
{
- int retry = 0, status;
+ int retry = 0;
+ long status;
u32 dx_intr_timeout_ms = 200;
do {
@@ -4251,18 +4253,23 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
data = pci_get_drvdata(pdev);
- dxstate = (mesg.event == PM_EVENT_SUSPEND ?
- BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD);
-
- data->pm_sx_event = mesg.event;
+ /* S0ix (s2idle) uses D3_HOT; S3, freeze and hibernate use D3_COLD. */
+ if (mesg.event == PM_EVENT_SUSPEND &&
+ pm_suspend_target_state == PM_SUSPEND_TO_IDLE)
+ dxstate = BTINTEL_PCIE_STATE_D3_HOT;
+ else
+ dxstate = BTINTEL_PCIE_STATE_D3_COLD;
start = ktime_get();
/* Refer: 6.4.11.7 -> Platform power management */
err = btintel_pcie_set_dxstate(data, dxstate);
- if (err)
+ if (err) {
+ bt_dev_err(data->hdev, "Failed to set dxstate:%u (%d)",
+ dxstate, err);
return err;
+ }
bt_dev_dbg(data->hdev,
"device entered into d3 state from d0 in %lld us",
@@ -4285,7 +4292,7 @@ static int btintel_pcie_freeze(struct device *dev)
return btintel_pcie_suspend_late(dev, PMSG_FREEZE);
}
-static int btintel_pcie_resume(struct device *dev)
+static int btintel_pcie_resume_event(struct device *dev, pm_message_t mesg)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct btintel_pcie_data *data;
@@ -4293,19 +4300,15 @@ static int btintel_pcie_resume(struct device *dev)
int err;
data = pci_get_drvdata(pdev);
- data->gp0_received = false;
start = ktime_get();
- /* When the system enters S4 (hibernate) mode, bluetooth device loses
- * power, which results in the erasure of its loaded firmware.
- * Consequently, function level reset (flr) is required on system
- * resume to bring the controller back into an operational state by
- * initiating a new firmware download.
+ /* S3 and S4 may cut power, erasing the firmware. Force FLR to recover
+ * instead of a normal D0 transition.
*/
-
- if (data->pm_sx_event == PM_EVENT_FREEZE ||
- data->pm_sx_event == PM_EVENT_HIBERNATE) {
+ if (mesg.event == PM_EVENT_RESTORE ||
+ (mesg.event == PM_EVENT_RESUME &&
+ pm_suspend_target_state == PM_SUSPEND_MEM)) {
set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR);
return 0;
@@ -4314,7 +4317,9 @@ static int btintel_pcie_resume(struct device *dev)
/* Refer: 6.4.11.7 -> Platform power management */
err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0);
- if (err == 0) {
+ if (err) {
+ bt_dev_err(data->hdev, "Failed to set D0 state (%d)", err);
+ } else {
bt_dev_dbg(data->hdev,
"device entered into d0 state from d3 in %lld us",
ktime_to_us(ktime_get() - start));
@@ -4339,13 +4344,28 @@ static int btintel_pcie_resume(struct device *dev)
return err;
}
+static int btintel_pcie_resume(struct device *dev)
+{
+ return btintel_pcie_resume_event(dev, PMSG_RESUME);
+}
+
+static int btintel_pcie_restore(struct device *dev)
+{
+ return btintel_pcie_resume_event(dev, PMSG_RESTORE);
+}
+
+static int btintel_pcie_thaw(struct device *dev)
+{
+ return btintel_pcie_resume_event(dev, PMSG_THAW);
+}
+
static const struct dev_pm_ops btintel_pcie_pm_ops = {
.suspend = btintel_pcie_suspend,
.resume = btintel_pcie_resume,
.freeze = btintel_pcie_freeze,
- .thaw = btintel_pcie_resume,
+ .thaw = btintel_pcie_thaw,
.poweroff = btintel_pcie_hibernate,
- .restore = btintel_pcie_resume,
+ .restore = btintel_pcie_restore,
};
static struct pci_driver btintel_pcie_driver = {
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 016795fcbccc..3030b4e8b750 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -713,7 +713,6 @@ struct btintel_pcie_ini_dump_info {
* @txq: TX Queue struct
* @rxq: RX Queue struct
* @alive_intr_ctxt: Alive interrupt context
- * @pm_sx_event: PM event on which system got suspended
*/
struct btintel_pcie_data {
struct pci_dev *pdev;
@@ -773,7 +772,6 @@ struct btintel_pcie_data {
struct btintel_pcie_dbgc dbgc;
struct btintel_pcie_mdbgc mdbgc;
struct btintel_pcie_dump_header dmp_hdr;
- u8 pm_sx_event;
u32 debug_evt_addr;
u32 debug_evt_size;
dma_addr_t debug_table_addr;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 3/4] Bluetooth: btintel_pcie: verify state after alive interrupt
2026-09-26 8:52 [PATCH v4 1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check Ravindra
2026-09-26 8:53 ` [PATCH v4 2/4] Bluetooth: btintel_pcie: fix PM flow for S0ix, S3 and S4 Ravindra
@ 2026-09-26 8:53 ` Ravindra
2026-09-26 8:53 ` [PATCH v4 4/4] Bluetooth: btintel_pcie: clear the GP0 cause with a W1C write Ravindra
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Ravindra @ 2026-09-26 8:53 UTC (permalink / raw)
To: linux-bluetooth, lsa.uz, vladimirkondratyev2
Cc: chethan.tumkur.narayan, kiran.k, Paul Menzel, Ravindra
From: Sergey Lebedev <lsa.uz@pm.me>
The gp0_received flag only reports that the GP0 handler ran. It does
not guarantee that the handler completed the requested D-state
transition: a matched case can leave the state unchanged.
Refresh the boot-stage register before treating the transition as
successful. When the controller is in D0 but the handler did not
re-arm the interface, restore the alive context, reset the interface
arrays, restart RX, and complete the mailbox/alive handshake. Propagate
RX setup failures to the PM caller. Likewise, when the controller is in
D3 but the handler did not record it, restore the alive context so the
next transition dispatches correctly.
Signed-off-by: Ravindra <ravindra@intel.com>
Signed-off-by: Sergey Lebedev <lsa.uz@pm.me>
---
drivers/bluetooth/btintel_pcie.c | 64 ++++++++++++++++++++++++--------
1 file changed, 48 insertions(+), 16 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 082f5ec8ba71..553fa927cf0a 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -4200,7 +4200,7 @@ static void btintel_pcie_coredump(struct device *dev)
static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
{
- int retry = 0;
+ int retry = 0, err;
long status;
u32 dx_intr_timeout_ms = 200;
@@ -4212,30 +4212,62 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
status = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
msecs_to_jiffies(dx_intr_timeout_ms));
- if (status)
- return 0;
-
- bt_dev_warn(data->hdev,
- "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d",
- dx_intr_timeout_ms, dxstate, retry);
+ if (!status) {
+ bt_dev_warn(data->hdev,
+ "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d",
+ dx_intr_timeout_ms, dxstate, retry);
- /* clear gp0 cause */
- btintel_pcie_clr_reg_bits(data,
- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES,
- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
+ /* clear gp0 cause */
+ btintel_pcie_clr_reg_bits(data,
+ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES,
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
+ }
- /* A hardware bug may cause the alive interrupt to be missed. Refresh
- * boot_stage_cache from hardware, since only the interrupt handler
- * updates it. Finally retry only if the state check still fails.
+ /* gp0_received is set at the top of the handler, before the switch on
+ * alive_intr_ctxt. Error and lockdown are filtered out above it, but a
+ * matched case can still complete without doing anything - D3 breaks
+ * unchanged while the controller has not reached D0 - and a hardware
+ * bug may drop the interrupt outright. Either way the flag says a gp0
+ * was handled, not that the transition completed, and only the register
+ * knows. Refresh the cache here and retry only if the state check still
+ * fails.
*/
data->boot_stage_cache = btintel_pcie_rd_reg32(data,
BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
if (dxstate == BTINTEL_PCIE_STATE_D0) {
- if (btintel_pcie_in_d0(data))
+ if (btintel_pcie_in_d0(data)) {
+ /* Do what the handler's D3 -> D0 branch
+ * would have done, unless it already has.
+ */
+ if (data->alive_intr_ctxt == BTINTEL_PCIE_D0)
+ return 0;
+
+ data->alive_intr_ctxt = BTINTEL_PCIE_D0;
+ btintel_pcie_reset_ia(data);
+ err = btintel_pcie_start_rx(data);
+ if (err)
+ return err;
+
+ /* Complete the mbox<->alive handshake */
+ if (test_and_clear_bit(BTINTEL_PCIE_MBOX_PARSE_PENDING,
+ &data->flags)) {
+ set_bit(BTINTEL_PCIE_MBOX_PARSE_READY, &data->flags);
+ wake_up(&data->mbox_parse_wait_q);
+ }
+
return 0;
+ }
} else {
- if (btintel_pcie_in_d3(data))
+ if (btintel_pcie_in_d3(data)) {
+ /* Do what the handler's D0 -> D3 branch
+ * would have done, unless it already has.
+ */
+ if (data->alive_intr_ctxt == BTINTEL_PCIE_D3)
+ return 0;
+
+ data->alive_intr_ctxt = BTINTEL_PCIE_D3;
return 0;
+ }
}
} while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 4/4] Bluetooth: btintel_pcie: clear the GP0 cause with a W1C write
2026-09-26 8:52 [PATCH v4 1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check Ravindra
2026-09-26 8:53 ` [PATCH v4 2/4] Bluetooth: btintel_pcie: fix PM flow for S0ix, S3 and S4 Ravindra
2026-09-26 8:53 ` [PATCH v4 3/4] Bluetooth: btintel_pcie: verify state after alive interrupt Ravindra
@ 2026-09-26 8:53 ` Ravindra
2026-09-26 14:45 ` Sergey Lebedev
2026-09-27 23:23 ` [v4,1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check bluez.test.bot
2026-09-29 15:10 ` [PATCH v4 1/4] " patchwork-bot+bluetooth
4 siblings, 1 reply; 7+ messages in thread
From: Ravindra @ 2026-09-26 8:53 UTC (permalink / raw)
To: linux-bluetooth, lsa.uz, vladimirkondratyev2
Cc: chethan.tumkur.narayan, kiran.k, Paul Menzel, Ravindra
MSIX_HW_INT_CAUSES is a write-one-to-clear register. Using a
read-modify-write helper writes zero to GP0, leaving the cause uncleared,
and can acknowledge unrelated pending causes through the read value.
Write only the GP0 bit directly, matching the interrupt handler's W1C
acknowledgment convention.
Fixes: 88c6216a52ea ("Bluetooth: btintel_pcie: Suspend/Resume: Controller doorbell interrupt handling")
Signed-off-by: Ravindra <ravindra@intel.com>
---
drivers/bluetooth/btintel_pcie.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 553fa927cf0a..9fda057ca9a1 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -4217,10 +4217,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
"Timeout (%u ms) on alive interrupt for D%d entry, retry count %d",
dx_intr_timeout_ms, dxstate, retry);
- /* clear gp0 cause */
- btintel_pcie_clr_reg_bits(data,
- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES,
- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
+ /* MSIX_HW_INT_CAUSES is W1C. Write only GP0 so other
+ * pending causes are not acknowledged here.
+ */
+ btintel_pcie_wr_reg32(data,
+ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES,
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
}
/* gp0_received is set at the top of the handler, before the switch on
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* RE: [v4,1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
2026-09-26 8:52 [PATCH v4 1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check Ravindra
` (2 preceding siblings ...)
2026-09-26 8:53 ` [PATCH v4 4/4] Bluetooth: btintel_pcie: clear the GP0 cause with a W1C write Ravindra
@ 2026-09-27 23:23 ` bluez.test.bot
2026-09-29 15:10 ` [PATCH v4 1/4] " patchwork-bot+bluetooth
4 siblings, 0 replies; 7+ messages in thread
From: bluez.test.bot @ 2026-09-27 23:23 UTC (permalink / raw)
To: linux-bluetooth, ravindra
[-- Attachment #1: Type: text/plain, Size: 2524 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1174568/
---Test result---
Test Summary:
CheckPatch FAIL 2.79 seconds
VerifyFixes PASS 0.07 seconds
VerifySignedoff PASS 0.07 seconds
GitLint FAIL 0.88 seconds
SubjectPrefix PASS 0.25 seconds
BuildKernel PASS 30.53 seconds
CheckAllWarning PASS 35.22 seconds
CheckSparse PASS 36.22 seconds
BuildKernel32 PASS 29.30 seconds
CheckKernelLLVM PASS 35.32 seconds
TestRunnerSetup PASS 803.97 seconds
IncrementalBuild PASS 32.58 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[v4,1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#87:
Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 0
total: 0 errors, 1 warnings, 22 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluetooth-next/bluetooth-next/src/patch/14849956.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v4,1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
9: B1 Line exceeds max length (82>80): " Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 0"
10: B1 Line exceeds max length (82>80): " Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 1"
11: B1 Line exceeds max length (82>80): " Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 2"
12: B1 Line exceeds max length (98>80): " btintel_pcie 0000:00:14.7: PM: pci_pm_suspend(): btintel_pcie_suspend [btintel_pcie] returns -16"
https://github.com/bluez/bluetooth-next/pull/822
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH v4 1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
2026-09-26 8:52 [PATCH v4 1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check Ravindra
` (3 preceding siblings ...)
2026-09-27 23:23 ` [v4,1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check bluez.test.bot
@ 2026-09-29 15:10 ` patchwork-bot+bluetooth
4 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-29 15:10 UTC (permalink / raw)
To: Ravindra
Cc: linux-bluetooth, lsa.uz, vladimirkondratyev2,
chethan.tumkur.narayan, kiran.k, pmenzel
Hello:
This series was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Sat, 26 Sep 2026 14:22:59 +0530 you wrote:
> From: "Vladimir V. Kondratyev" <vladimirkondratyev2@gmail.com>
>
> btintel_pcie returns -16 (EBUSY) during suspend, causing the entire
> suspend operation to abort on Intel Lunar Lake hardware. The system
> immediately resumes after every suspend attempt:
>
> Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 0
> Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 1
> Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 2
> btintel_pcie 0000:00:14.7: PM: pci_pm_suspend(): btintel_pcie_suspend [btintel_pcie] returns -16
> btintel_pcie 0000:00:14.7: PM: dpm_run_callback(): pci_pm_suspend returns -16
> btintel_pcie 0000:00:14.7: PM: failed to suspend async: error -16
> PM: Some devices failed to suspend, or early wake event detected
>
> [...]
Here is the summary with links:
- [v4,1/4] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
https://git.kernel.org/bluetooth/bluetooth-next/c/20dfccf8d854
- [v4,2/4] Bluetooth: btintel_pcie: fix PM flow for S0ix, S3 and S4
https://git.kernel.org/bluetooth/bluetooth-next/c/79463c0de429
- [v4,3/4] Bluetooth: btintel_pcie: verify state after alive interrupt
(no matching commit)
- [v4,4/4] Bluetooth: btintel_pcie: clear the GP0 cause with a W1C write
https://git.kernel.org/bluetooth/bluetooth-next/c/6091d2771395
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 7+ messages in thread