Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
@ 2026-05-07 20:34 Vladimir V. Kondratyev
  2026-09-01 20:37 ` Vladimir V. Kondratyev
  0 siblings, 1 reply; 7+ messages in thread
From: Vladimir V. Kondratyev @ 2026-05-07 20:34 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: Chandrashekar Devegowda, Kiran K, Paul Menzel, Arnd Bergmann,
	linux-bluetooth, linux-kernel, Vladimir V. Kondratyev

btintel_pcie_set_dxstate() falls back to checking the controller state via
btintel_pcie_in_d3/d0() when the alive interrupt is missed. However, these
helpers read boot_stage_cache, which is only updated by the interrupt
handler. As such, if the interrupt was missed, the cache is stale and the
fallback check always fails, exhausting all retries and returning -EBUSY,
causing suspend to abort.

The fix involves re-reading the hardware register before the fallback state
check, consistent with btintel_pcie_resume().

Fixes: e57362f4911b ("Bluetooth: btintel_pcie: Add support for _suspend() / _resume()")

Signed-off-by: Vladimir V. Kondratyev <vladimirkondratyev2@gmail.com>
---
 drivers/bluetooth/btintel_pcie.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index fda474406003..7fca8eea6e01 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -2778,9 +2778,13 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
 					  BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
 
 		/* A hardware bug may cause the alive interrupt to be missed.
-		 * Check if the controller reached the expected state and retry
-		 * the operation only if it hasn't.
+		 * Refresh boot_stage_cache from hardware since it is only
+		 * updated by the interrupt handler, then check if the
+		 * controller reached the expected state and retry the
+		 * operation only if it hasn't.
 		 */
+		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))
 				return 0;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
@ 2026-08-30 15:15 Sergey Lebedev
  0 siblings, 0 replies; 7+ messages in thread
From: Sergey Lebedev @ 2026-08-30 15:15 UTC (permalink / raw)
  To: Vladimir V. Kondratyev, Marcel Holtmann, Luiz Augusto von Dentz
  Cc: Chandrashekar Devegowda, Kiran K, Tedd Ho-Jeong An, Paul Menzel,
	Arnd Bergmann, linux-bluetooth, linux-kernel

Hi Vladimir, Marcel, Luiz,

This patch is four months old and has had no reply other than the CI
bot. It fixes a real failure. Here is a report from the field and a
test of the patch itself.

Hardware: Microsoft Surface Pro 11 (Intel, Lunar Lake), Intel BE201,
8086:a876 rev 10, firmware timestamp 2026.8 buildtype 1 build 113003,
SHA1 0x08f159a0.

The failure, as it happened here on Ubuntu 7.0.0-28 before any
workaround existed:

  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

One device returning -EBUSY aborts the whole system suspend, so the
machine simply does not sleep. The workaround in use here since then
unloads btintel_pcie from a systemd sleep hook.

On the frequency I have to be honest rather than useful: it failed on
2 of the 3 suspends attempted before the workaround was installed, and
this week, on 7.0.0-30 with the workaround disabled, it succeeded 7
times out of 7 (3 of those with an LE scan running). Same firmware,
and no btintel_pcie change between the two kernels. So I cannot give
you a reproduction rate, and I am not going to invent one.

What I could do instead is exercise the exact path your patch touches,
deterministically. Method, so it can be judged:

  - built v7.0 drivers/bluetooth/btintel_pcie.c out of tree against the
    running kernel's headers, unmodified apart from the debug knob
    below, and confirmed it loads and works;

  - added a debug-only module parameter that returns from
    btintel_pcie_msix_gp0_handler() before the boot_stage_cache
    refresh, and only while alive_intr_ctxt is BTINTEL_PCIE_D0. That
    leaves boot_stage_cache stale and gp0_received false, which is
    precisely the state a genuinely missed alive interrupt leaves
    behind, while the controller itself still reaches D3.

Without your patch, the emulated missed interrupt reproduces the
production failure exactly:

  Timeout (200 ms) on alive interrupt for D2 entry, retry count 0
  Timeout (200 ms) on alive interrupt for D2 entry, retry count 1
  Timeout (200 ms) on alive interrupt for D2 entry, retry count 2
  btintel_pcie 0000:00:14.7: PM: failed to suspend async: error -16
  PM: Some devices failed to suspend, or early wake event detected

With your patch applied to that same build, and nothing else changed:

  PM: suspend entry (s2idle)
  Bluetooth: hci0: Timeout (200 ms) on alive interrupt for D2 entry, retry count 0
  PM: suspend exit

One timeout, then the re-read observes D3, set_dxstate() returns 0, and
the system suspends and resumes normally. That is the difference the
two added lines make.

The debug knob is a test fixture, not a proposal; I am happy to send it
to anyone who wants to reproduce this.

Worth noting that btintel_pcie_set_dxstate() is byte-identical in v7.0
and v7.1-rc7, so this is current, not historical.

Tested-by: Sergey Lebedev <lsa.uz@pm.me>

I have the hardware and can re-test any revision of this patch.

Thanks,
Sergey


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
  2026-05-07 20:34 Vladimir V. Kondratyev
@ 2026-09-01 20:37 ` Vladimir V. Kondratyev
  2026-09-02  5:35   ` Paul Menzel
  0 siblings, 1 reply; 7+ messages in thread
From: Vladimir V. Kondratyev @ 2026-09-01 20:37 UTC (permalink / raw)
  To: marcel, luiz.dentz, linux-bluetooth, linux-kernel
  Cc: Vladimir V. Kondratyev, Sergey Lebedev

btintel_pcie_set_dxstate() falls back to checking the controller state via
btintel_pcie_in_d3/d0() when the alive interrupt is missed. However, these
helpers read boot_stage_cache, which is only updated by the interrupt
handler. As such, if the interrupt was missed, the cache is stale and the
fallback check always fails, exhausting all retries and returning -EBUSY,
causing suspend to abort.

The fix involves re-reading the hardware register before the fallback state
check, consistent with btintel_pcie_resume().

Fixes: e57362f4911b ("Bluetooth: btintel_pcie: Add support for _suspend() / _resume()")

Signed-off-by: Vladimir V. Kondratyev <vladimirkondratyev2@gmail.com>
Tested-by: Sergey Lebedev <lsa.uz@pm.me>
---
 drivers/bluetooth/btintel_pcie.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index fda474406003..7fca8eea6e01 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -2778,9 +2778,13 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
 					  BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
 
 		/* A hardware bug may cause the alive interrupt to be missed.
-		 * Check if the controller reached the expected state and retry
-		 * the operation only if it hasn't.
+		 * Refresh boot_stage_cache from hardware since it is only
+		 * updated by the interrupt handler, then check if the
+		 * controller reached the expected state and retry the
+		 * operation only if it hasn't.
 		 */
+		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))
 				return 0;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
  2026-09-01 20:37 ` Vladimir V. Kondratyev
@ 2026-09-02  5:35   ` Paul Menzel
  2026-09-02  7:24     ` Sergey Lebedev
  2026-09-02  9:30     ` Sergey Lebedev
  0 siblings, 2 replies; 7+ messages in thread
From: Paul Menzel @ 2026-09-02  5:35 UTC (permalink / raw)
  To: Vladimir V. Kondratyev
  Cc: marcel, luiz.dentz, linux-bluetooth, linux-kernel, Sergey Lebedev,
	Chandrashekar Devegowda, Kiran K

[Cc: +Chandrashekar, +Kiran]

Dear Vladimir,


Thank you for your patch.

Am 01.09.26 um 22:37 schrieb Vladimir V. Kondratyev:
> btintel_pcie_set_dxstate() falls back to checking the controller state via
> btintel_pcie_in_d3/d0() when the alive interrupt is missed. However, these
> helpers read boot_stage_cache, which is only updated by the interrupt
> handler. As such, if the interrupt was missed, the cache is stale and the
> fallback check always fails, exhausting all retries and returning -EBUSY,
> causing suspend to abort.

Can you reproduce this on real hardware?

> The fix involves re-reading the hardware register before the fallback state
> check, consistent with btintel_pcie_resume().
> 
> Fixes: e57362f4911b ("Bluetooth: btintel_pcie: Add support for _suspend() / _resume()")
> 
> Signed-off-by: Vladimir V. Kondratyev <vladimirkondratyev2@gmail.com>
> Tested-by: Sergey Lebedev <lsa.uz@pm.me>
> ---
>   drivers/bluetooth/btintel_pcie.c | 8 ++++++--
>   1 file changed, 6 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
> index fda474406003..7fca8eea6e01 100644
> --- a/drivers/bluetooth/btintel_pcie.c
> +++ b/drivers/bluetooth/btintel_pcie.c
> @@ -2778,9 +2778,13 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate)
>   					  BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0);
>   
>   		/* A hardware bug may cause the alive interrupt to be missed.
> -		 * Check if the controller reached the expected state and retry
> -		 * the operation only if it hasn't.
> +		 * Refresh boot_stage_cache from hardware since it is only
> +		 * updated by the interrupt handler, then check if the
> +		 * controller reached the expected state and retry the
> +		 * operation only if it hasn't.

Re-flow for less lines?

>   		 */
> +		data->boot_stage_cache = btintel_pcie_rd_reg32(data,
> +							       BTINTEL_PCIE_CSR_BOOT_STAGE_REG);

To me it sounds strange, that a register name *boot stage* should change 
after the boot. ;-)

>   		if (dxstate == BTINTEL_PCIE_STATE_D0) {
>   			if (btintel_pcie_in_d0(data))
>   				return 0;


Kind regards,

Paul

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
  2026-09-02  5:35   ` Paul Menzel
@ 2026-09-02  7:24     ` Sergey Lebedev
  2026-09-02  8:38       ` Paul Menzel
  2026-09-02  9:30     ` Sergey Lebedev
  1 sibling, 1 reply; 7+ messages in thread
From: Sergey Lebedev @ 2026-09-02  7:24 UTC (permalink / raw)
  To: Paul Menzel
  Cc: Vladimir V . Kondratyev, Marcel Holtmann, Luiz Augusto von Dentz,
	Chandrashekar Devegowda, Kiran K, linux-bluetooth, linux-kernel

Paul,

Two of your three points I can answer from the hardware, since the
Tested-by on this patch is mine.

> Can you reproduce this on real hardware?

Yes. Surface Pro 11 (Intel, Lunar Lake), Intel BE201, 8086:a876 rev 10.
It first appeared in ordinary use, not in a test:

  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 returns -16
  PM: Some devices failed to suspend, or early wake event detected

One device returning -EBUSY aborts the whole system suspend, so the
machine does not sleep at all.

I have to be honest about the rate rather than useful: it failed on 2 of
the 3 suspends I attempted before installing a workaround, and later, on
a newer kernel with the workaround removed, it succeeded 7 times out of 7
with no btintel_pcie change in between. So the spontaneous failure is not
something I can produce on demand.

What I could do was exercise the exact path deterministically. I built
the driver out of tree with one debug-only parameter that returns from
btintel_pcie_msix_gp0_handler() before the boot_stage_cache refresh, and
only while alive_intr_ctxt is BTINTEL_PCIE_D0. That leaves the cache
stale and gp0_received false — precisely the state a genuinely missed
alive interrupt leaves behind — while the controller still reaches D3.

Without the patch, that reproduces the production failure exactly: three
timeouts, -EBUSY, suspend aborted. With the patch and nothing else
changed: one timeout, the re-read observes D3, set_dxstate() returns 0,
and the system suspends and resumes normally.

Full method and logs are in my earlier message on this thread,
Message-ID <20260830151550.44687-1-lsa.uz@pm.me>.

> To me it sounds strange, that a register name *boot stage* should
> change after the boot. ;-)

It is a fair reaction to the name, but the register genuinely carries
live power state. From the driver's own header:

  #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY  (BIT(24))

and both helpers test that one bit and nothing else:

  static bool btintel_pcie_in_d3(struct btintel_pcie_data *data)
  {
      return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY;
  }

  static bool btintel_pcie_in_d0(struct btintel_pcie_data *data)
  {
      return !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY);
  }

So bit 24 is updated by the controller on every D-state transition, long
after boot. The name describes the register's original purpose rather
than everything Intel later put in it. btintel_pcie_resume() already
re-reads it for exactly this reason, which is what makes the patch's
change consistent rather than novel.

The re-flow is Vladimir's call and I have no opinion on it. If a v3 comes
out of this, I still have the hardware and the test fixture and will
re-run both halves against it.

Thanks,
Sergey


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
  2026-09-02  7:24     ` Sergey Lebedev
@ 2026-09-02  8:38       ` Paul Menzel
  0 siblings, 0 replies; 7+ messages in thread
From: Paul Menzel @ 2026-09-02  8:38 UTC (permalink / raw)
  To: Sergey Lebedev
  Cc: Vladimir V . Kondratyev, Marcel Holtmann, Luiz Augusto von Dentz,
	Chandrashekar Devegowda, Kiran K, linux-bluetooth, linux-kernel

Dear Sergey,


Thank you for your immediate response.

Am 02.09.26 um 09:24 schrieb Sergey Lebedev:

> Two of your three points I can answer from the hardware, since the
> Tested-by on this patch is mine.
> 
>> Can you reproduce this on real hardware?
> 
> Yes. Surface Pro 11 (Intel, Lunar Lake), Intel BE201, 8086:a876 rev 10.
> It first appeared in ordinary use, not in a test:
> 
>    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 returns -16
>    PM: Some devices failed to suspend, or early wake event detected
> 
> One device returning -EBUSY aborts the whole system suspend, so the
> machine does not sleep at all.
> 
> I have to be honest about the rate rather than useful: it failed on 2 of
> the 3 suspends I attempted before installing a workaround, and later, on
> a newer kernel with the workaround removed, it succeeded 7 times out of 7
> with no btintel_pcie change in between. So the spontaneous failure is not
> something I can produce on demand.
> 
> What I could do was exercise the exact path deterministically. I built
> the driver out of tree with one debug-only parameter that returns from
> btintel_pcie_msix_gp0_handler() before the boot_stage_cache refresh, and
> only while alive_intr_ctxt is BTINTEL_PCIE_D0. That leaves the cache
> stale and gp0_received false — precisely the state a genuinely missed
> alive interrupt leaves behind — while the controller still reaches D3.
> 
> Without the patch, that reproduces the production failure exactly: three
> timeouts, -EBUSY, suspend aborted. With the patch and nothing else
> changed: one timeout, the re-read observes D3, set_dxstate() returns 0,
> and the system suspends and resumes normally.
> 
> Full method and logs are in my earlier message on this thread,
> Message-ID <20260830151550.44687-1-lsa.uz@pm.me>.

It’d be great if the thread could be referenced via Link: tag, and also 
the problem summarized in the commit message including the log messages.

>> To me it sounds strange, that a register name *boot stage* should
>> change after the boot. ;-)
> 
> It is a fair reaction to the name, but the register genuinely carries
> live power state. From the driver's own header:
> 
>    #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY  (BIT(24))
> 
> and both helpers test that one bit and nothing else:
> 
>    static bool btintel_pcie_in_d3(struct btintel_pcie_data *data)
>    {
>        return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY;
>    }
> 
>    static bool btintel_pcie_in_d0(struct btintel_pcie_data *data)
>    {
>        return !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY);
>    }
> 
> So bit 24 is updated by the controller on every D-state transition, long
> after boot. The name describes the register's original purpose rather
> than everything Intel later put in it. btintel_pcie_resume() already
> re-reads it for exactly this reason, which is what makes the patch's
> change consistent rather than novel.

Maybe a comment could be added above the macro definition.

> The re-flow is Vladimir's call and I have no opinion on it. If a v3 comes
> out of this, I still have the hardware and the test fixture and will
> re-run both halves against it.

Awesome.

Also note, gemini/gemini-3.1-pro-preview found the same issue in a 
different review [1].


Kind regards,

Paul


[1]: 
https://sashiko.dev/#/patchset/20260902042840.2432862-1-ravindra%40intel.com

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check
  2026-09-02  5:35   ` Paul Menzel
  2026-09-02  7:24     ` Sergey Lebedev
@ 2026-09-02  9:30     ` Sergey Lebedev
  1 sibling, 0 replies; 7+ messages in thread
From: Sergey Lebedev @ 2026-09-02  9:30 UTC (permalink / raw)
  To: Paul Menzel; +Cc: Vladimir V . Kondratyev, Ravindra, linux-bluetooth

Paul,

Thank you for the pointer — it was the useful part of your mail.

Ravindra's patch changes the same function, and the two turn out to fix
different halves of one failure: his makes a *late* alive interrupt count
by moving the flag out of the retry loop, while Vladimir's makes a
*missing* one survivable, because the fallback check still reads the
stale boot_stage_cache. The hunks also sit on adjacent lines, so whichever
lands second will need a rebase.

I have followed that up in Ravindra's thread rather than here, since it
concerns his patch:

  Message-ID <20260902091021.20160-1-lsa.uz@pm.me>

There I report what our test fixture does under each patch, and offer to
assemble the two as a series if both authors would rather have it that way.

Your other two points — the Link: tag with a fuller commit message, and a
comment above the macro definition — are Vladimir's to act on, and I have
no opinion to add beyond agreeing they would help.

Thanks,
Sergey


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-02  9:31 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-30 15:15 [PATCH] Bluetooth: btintel_pcie: fix stale cache in set_dxstate fallback check Sergey Lebedev
  -- strict thread matches above, loose matches on Subject: below --
2026-05-07 20:34 Vladimir V. Kondratyev
2026-09-01 20:37 ` Vladimir V. Kondratyev
2026-09-02  5:35   ` Paul Menzel
2026-09-02  7:24     ` Sergey Lebedev
2026-09-02  8:38       ` Paul Menzel
2026-09-02  9:30     ` Sergey Lebedev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox