Browse Source

qcacld-3.0: Fix race condition between PM suspend and fw irq

PM suspend and FW interrupt handler is running at same time
which is leading to multiple race conditions and host is missing
FW wake up interrupt which is causing APPS not waking up reliably.

Define .suspend_noirq and .resume_noirq callbacks and make
sure there is no pending FW interrupt before allowing PM
suspend to complete. Kernel PM suspend framework gives guarantee
that suspend_noirq and device interrupt handler can't run
simultaneously. This helps WLAN driver to reliably detect FW
wake up interrupt during PM suspend and fail PM suspend gracefully
if FW has requested for initial wake up.

Change-Id: Ic82d7dd6b6b743dd3f55b036e012ad0c8bebd50d
CRs-Fixed: 1060748
Rajeev Kumar 8 years ago
parent
commit
99805a7ac3
3 changed files with 202 additions and 14 deletions
  1. 178 12
      core/hdd/src/wlan_hdd_driver_ops.c
  2. 1 0
      core/wma/inc/wma_api.h
  3. 23 2
      core/wma/src/wma_features.c

+ 178 - 12
core/hdd/src/wlan_hdd_driver_ops.c

@@ -530,17 +530,17 @@ static int __wlan_hdd_bus_suspend(pm_message_t state)
 	if (err)
 		goto done;
 
+	if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) {
+		hdd_info("Driver Module closed return success");
+		return 0;
+	}
+
 	hif_ctx = cds_get_context(QDF_MODULE_ID_HIF);
 	if (NULL == hif_ctx) {
 		err = -EINVAL;
 		goto done;
 	}
 
-	if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) {
-		hdd_info("Driver Module closed return success");
-		return 0;
-	}
-
 	err = qdf_status_to_os_return(
 			ol_txrx_bus_suspend());
 	if (err)
@@ -554,16 +554,9 @@ static int __wlan_hdd_bus_suspend(pm_message_t state)
 	if (err)
 		goto resume_wma;
 
-	err = wma_is_target_wake_up_received();
-	if (err)
-		goto resume_hif;
-
 	hdd_err("suspend done");
 	return err;
 
-resume_hif:
-	status = hif_bus_resume(hif_ctx);
-	QDF_BUG(!status);
 resume_wma:
 	status = wma_bus_resume();
 	QDF_BUG(!status);
@@ -596,6 +589,79 @@ int wlan_hdd_bus_suspend(pm_message_t state)
 	return ret;
 }
 
+/**
+ * __wlan_hdd_bus_suspend_noirq() - handle .suspend_noirq callback
+ *
+ * This function is called by the platform driver to complete the
+ * bus suspend callback when device interrupts are disabled by kernel.
+ * Call HIF and WMA suspend_noirq callbacks to make sure there is no
+ * wake up pending from FW before allowing suspend.
+ *
+ * Return: 0 for success and -EBUSY if FW is requesting wake up
+ */
+int __wlan_hdd_bus_suspend_noirq(void)
+{
+	hdd_context_t *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
+	void *hif_ctx;
+	int err = wlan_hdd_validate_context(hdd_ctx);
+	int status;
+
+	if (err) {
+		hdd_err("Invalid HDD context: %d", err);
+		return err;
+	}
+
+	if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) {
+		hdd_info("Driver Module closed return success");
+		return 0;
+	}
+
+	hif_ctx = cds_get_context(QDF_MODULE_ID_HIF);
+	if (NULL == hif_ctx) {
+		err = -EINVAL;
+		goto done;
+	}
+
+	err = hif_bus_suspend_noirq(hif_ctx);
+	if (err)
+		goto done;
+
+	err = wma_is_target_wake_up_received();
+	if (err)
+		goto resume_hif_noirq;
+
+	hdd_info("suspend_noirq done");
+	return err;
+
+resume_hif_noirq:
+	status = hif_bus_resume_noirq(hif_ctx);
+	QDF_BUG(!status);
+done:
+	hdd_err("suspend_noirq failed, status = %d", err);
+	return err;
+}
+
+/**
+ * wlan_hdd_bus_suspend_noirq() - handle .suspend_noirq callback
+ *
+ * This function is called by the platform driver to complete the
+ * bus suspend callback when device interrupts are disabled by kernel.
+ * Call HIF and WMA suspend_noirq callbacks to make sure there is no
+ * wake up pending from FW before allowing suspend.
+ *
+ * Return: 0 for success and -EBUSY if FW is requesting wake up
+ */
+int wlan_hdd_bus_suspend_noirq(void)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+	ret = __wlan_hdd_bus_suspend_noirq();
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
 /**
  * __wlan_hdd_bus_resume() - handles platform resume
  *
@@ -659,6 +725,65 @@ int wlan_hdd_bus_resume(void)
 	return ret;
 }
 
+/**
+ * __wlan_hdd_bus_resume_noirq(): handle bus resume no irq
+ *
+ * This function is called by the platform driver to do bus
+ * resume no IRQ before calling resume callback. Call WMA and HIF
+ * layers to complete the resume_noirq.
+ *
+ * Return: 0 for success and negative error code for failure
+ */
+int __wlan_hdd_bus_resume_noirq(void)
+{
+	hdd_context_t *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
+	void *hif_ctx;
+	int status = wlan_hdd_validate_context(hdd_ctx);
+
+	if (status) {
+		hdd_err("Invalid HDD context: %d", status);
+		return status;
+	}
+
+	if (hdd_ctx->driver_status != DRIVER_MODULES_ENABLED) {
+		hdd_info("Driver Module closed return success");
+		return 0;
+	}
+
+	hif_ctx = cds_get_context(QDF_MODULE_ID_HIF);
+	if (NULL == hif_ctx)
+		return -EINVAL;
+
+	status = wma_clear_target_wake_up();
+	QDF_BUG(!status);
+
+	status = hif_bus_resume_noirq(hif_ctx);
+	QDF_BUG(!status);
+
+	hdd_info("resume_noirq done");
+	return status;
+}
+
+/**
+ * wlan_hdd_bus_resume_noirq(): handle bus resume no irq
+ *
+ * This function is called by the platform driver to do bus
+ * resume no IRQ before calling resume callback. Call WMA and HIF
+ * layers to complete the resume_noirq.
+ *
+ * Return: 0 for success and negative error code for failure
+ */
+int wlan_hdd_bus_resume_noirq(void)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+	ret = __wlan_hdd_bus_resume_noirq();
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
 /**
  * wlan_hdd_bus_reset_resume() - resume wlan bus after reset
  *
@@ -921,6 +1046,45 @@ static int wlan_hdd_pld_resume(struct device *dev,
 	return wlan_hdd_bus_resume();
 }
 
+
+/**
+ * wlan_hdd_pld_suspend_noirq() - handle suspend no irq
+ * @dev: device
+ * @pld_bus_type: PLD bus type
+ *
+ * Complete the actions started by suspend().  Carry out any
+ * additional operations required for suspending the device that might be
+ * racing with its driver's interrupt handler, which is guaranteed not to
+ * run while suspend_noirq() is being executed. Make sure to resume device
+ * if FW has sent initial wake up message and expecting APPS to wake up.
+ *
+ * Return: 0 on success
+ */
+static int wlan_hdd_pld_suspend_noirq(struct device *dev,
+		     enum pld_bus_type bus_type)
+{
+	return wlan_hdd_bus_suspend_noirq();
+}
+
+/**
+ * wlan_hdd_pld_resume_noirq() - handle resume no irq
+ * @dev: device
+ * @pld_bus_type: PLD bus type
+ *
+ * Prepare for the execution of resume() by carrying out any
+ * operations required for resuming the device that might be racing with
+ * its driver's interrupt handler, which is guaranteed not to run while
+ * resume_noirq() is being executed. Make sure to clear target initial
+ * wake up request such that next suspend can happen cleanly.
+ *
+ * Return: 0 on success
+ */
+static int wlan_hdd_pld_resume_noirq(struct device *dev,
+		    enum pld_bus_type bus_type)
+{
+	return wlan_hdd_bus_resume_noirq();
+}
+
 /**
  * wlan_hdd_pld_reset_resume() - reset resume function registered to PLD
  * @dev: device
@@ -985,6 +1149,8 @@ struct pld_driver_ops wlan_drv_ops = {
 	.crash_shutdown = wlan_hdd_pld_crash_shutdown,
 	.suspend    = wlan_hdd_pld_suspend,
 	.resume     = wlan_hdd_pld_resume,
+	.suspend_noirq = wlan_hdd_pld_suspend_noirq,
+	.resume_noirq  = wlan_hdd_pld_resume_noirq,
 	.reset_resume = wlan_hdd_pld_reset_resume,
 	.modem_status = wlan_hdd_pld_notify_handler,
 #ifdef FEATURE_RUNTIME_PM

+ 1 - 0
core/wma/inc/wma_api.h

@@ -146,6 +146,7 @@ int wma_runtime_suspend(void);
 int wma_runtime_resume(void);
 int wma_bus_suspend(void);
 int wma_is_target_wake_up_received(void);
+int wma_clear_target_wake_up(void);
 QDF_STATUS wma_suspend_target(WMA_HANDLE handle, int disable_target_intr);
 void wma_target_suspend_acknowledge(void *context, bool wow_nack);
 void wma_handle_initial_wake_up(void);

+ 23 - 2
core/wma/src/wma_features.c

@@ -6611,9 +6611,30 @@ int wma_is_target_wake_up_received(void)
 	if (wma->wow_initial_wake_up) {
 		WMA_LOGE("Target initial wake up received try again");
 		return -EAGAIN;
-	} else {
-		return 0;
 	}
+
+	return 0;
+}
+
+/**
+ * wma_clear_target_wake_up() - clear initial wake up
+ *
+ * Clear target initial wake up reason
+ *
+ * Return: 0 for success and negative error code for failure
+ */
+int wma_clear_target_wake_up(void)
+{
+	tp_wma_handle wma = cds_get_context(QDF_MODULE_ID_WMA);
+
+	if (NULL == wma) {
+		WMA_LOGE("%s: wma is NULL", __func__);
+		return -EFAULT;
+	}
+
+	wma->wow_initial_wake_up = false;
+
+	return 0;
 }
 
 /**