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);