114 lines
3.8 KiB
Diff
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);
|