Files
omarchy-pkgs/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch
T

114 lines
3.8 KiB
Diff

diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -1012,6 +1012,10 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data)
submit_rx = false;
signal_waitq = false;
+ /* Serialize the alive_intr_ctxt claim against set_dxstate()'s
+ * register-based fallback, which can run concurrently.
+ */
+ spin_lock(&data->irq_lock);
switch (data->alive_intr_ctxt) {
case BTINTEL_PCIE_ROM:
data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL;
@@ -1067,6 +1071,7 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data)
data->alive_intr_ctxt);
break;
}
+ spin_unlock(&data->irq_lock);
if (submit_rx) {
btintel_pcie_reset_ia(data);
@@ -3047,7 +3052,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;
@@ -3059,30 +3064,66 @@ 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;
+ 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);
- 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)) {
+ bool armed;
+
+ /* Claim the transition under irq_lock so a
+ * concurrent gp0 handler run cannot also arm RX.
+ */
+ spin_lock(&data->irq_lock);
+ armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0;
+ if (!armed)
+ data->alive_intr_ctxt = BTINTEL_PCIE_D0;
+ spin_unlock(&data->irq_lock);
+
+ if (armed)
+ return 0;
+
+ /* Do what the handler's D3 -> D0 branch
+ * would have done, unless it already has.
+ */
+ btintel_pcie_reset_ia(data);
+ err = btintel_pcie_start_rx(data);
+ if (err)
+ return err;
+
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.
+ */
+ spin_lock(&data->irq_lock);
+ if (data->alive_intr_ctxt != BTINTEL_PCIE_D3)
+ data->alive_intr_ctxt = BTINTEL_PCIE_D3;
+ spin_unlock(&data->irq_lock);
+
return 0;
+ }
}
} while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES);