qcacld-3.0: ipa exception packet forwarding crash fix

When WLAN subsystem is in suspend state, if packet deliever to
WLAN FW, WLAN FW will crash with bad state. So, if WLAN subsystem
is in suspend state, host should not pump down packet to FW.
IPA subsystem could not know WLAN power state. Then WLAN HDD
should inspect WLAN power state. If suspend state, packet should
queueing into local queue and when resume, send packet to FW

Change-Id: I60e1e8f0fff40c9f0ca4a798b76878548ca89ad7
CRs-fixed: 1077078
diff --git a/core/hdd/src/wlan_hdd_ipa.c b/core/hdd/src/wlan_hdd_ipa.c
index 8a2db97..b2140c9 100644
--- a/core/hdd/src/wlan_hdd_ipa.c
+++ b/core/hdd/src/wlan_hdd_ipa.c
@@ -202,6 +202,8 @@
 } __packed;
 
 struct hdd_ipa_pm_tx_cb {
+	bool exception;
+	hdd_adapter_t *adapter;
 	struct hdd_ipa_iface_context *iface_context;
 	struct ipa_rx_data *ipa_tx_desc;
 };
@@ -2510,7 +2512,58 @@
 }
 
 /**
- * __hdd_ipa_w2i_cb() - WLAN to IPA callback handler
+ * hdd_ipa_forward() - handle packet forwarding to wlan tx
+ * @hdd_ipa: pointer to hdd ipa context
+ * @adapter: network adapter
+ * @skb: data pointer
+ *
+ * if exception packet has set forward bit, copied new packet should be
+ * forwarded to wlan tx. if wlan subsystem is in suspend state, packet should
+ * put into pm queue and tx procedure will be differed
+ *
+ * Return: None
+ */
+void hdd_ipa_forward(struct hdd_ipa_priv *hdd_ipa,
+		hdd_adapter_t *adapter, qdf_nbuf_t skb)
+{
+	qdf_nbuf_t copy;
+	struct hdd_ipa_pm_tx_cb *pm_tx_cb;
+
+	copy = qdf_nbuf_copy(skb);
+	if (!copy) {
+		HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR, "copy packet alloc fail");
+		return;
+	}
+
+	qdf_spin_lock_bh(&hdd_ipa->pm_lock);
+	/* WLAN subsystem is in suspend, put int queue */
+	if (hdd_ipa->suspended) {
+		qdf_spin_unlock_bh(&hdd_ipa->pm_lock);
+		HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+			"TX in SUSPEND PUT QUEUE");
+		qdf_mem_set(copy->cb, sizeof(copy->cb), 0);
+		pm_tx_cb = (struct hdd_ipa_pm_tx_cb *)copy->cb;
+		pm_tx_cb->exception = true;
+		pm_tx_cb->adapter = adapter;
+		qdf_spin_lock_bh(&hdd_ipa->pm_lock);
+		qdf_nbuf_queue_add(&hdd_ipa->pm_queue_head, copy);
+		qdf_spin_unlock_bh(&hdd_ipa->pm_lock);
+		hdd_ipa->stats.num_tx_queued++;
+	} else {
+		/* Resume, put packet into WLAN TX */
+		qdf_spin_unlock_bh(&hdd_ipa->pm_lock);
+		if (hdd_softap_hard_start_xmit(copy, adapter->dev)) {
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+			    "packet tx fail");
+		} else {
+			hdd_ipa->stats.num_tx_bcmc++;
+			hdd_ipa->ipa_tx_forward++;
+		}
+	}
+}
+
+/**
+ * hdd_ipa_w2i_cb() - WLAN to IPA callback handler
  * @priv: pointer to private data registered with IPA (we register a
  *	pointer to the global IPA context)
  * @evt: the IPA event which triggered the callback
@@ -2527,9 +2580,7 @@
 	uint8_t iface_id;
 	uint8_t session_id;
 	struct hdd_ipa_iface_context *iface_context;
-	qdf_nbuf_t copy;
 	uint8_t fw_desc;
-	int ret;
 	QDF_STATUS status = QDF_STATUS_SUCCESS;
 
 	hdd_ipa = (struct hdd_ipa_priv *)priv;
@@ -2601,37 +2652,19 @@
 			 * only when DISCARD bit is not set.
 			 */
 			fw_desc = (uint8_t)skb->cb[1];
-
 			if (fw_desc & HDD_IPA_FW_RX_DESC_FORWARD_M) {
 				HDD_IPA_DP_LOG(
 					QDF_TRACE_LEVEL_DEBUG,
 					"Forward packet to Tx (fw_desc=%d)",
 					fw_desc);
-				copy = qdf_nbuf_copy(skb);
-				if (copy) {
-					hdd_ipa->ipa_tx_forward++;
-					ret = hdd_softap_hard_start_xmit(
-						(struct sk_buff *)copy,
-						adapter->dev);
-					if (ret) {
-						HDD_IPA_LOG(
-							QDF_TRACE_LEVEL_DEBUG,
-							"Forward packet tx fail");
-						hdd_ipa->stats.
-							num_tx_bcmc_err++;
-					} else {
-						hdd_ipa->stats.num_tx_bcmc++;
-					}
-				}
+				hdd_ipa_forward(hdd_ipa, adapter, skb);
 			}
-
 			if (fw_desc & HDD_IPA_FW_RX_DESC_DISCARD_M) {
 				HDD_IPA_INCREASE_INTERNAL_DROP_COUNT(hdd_ipa);
 				hdd_ipa->ipa_rx_discard++;
 				kfree_skb(skb);
 				break;
 			}
-
 		} else {
 			HDD_IPA_LOG(QDF_TRACE_LEVEL_INFO_HIGH,
 				"Intra-BSS FWD is disabled-skip forward to Tx");
@@ -2769,7 +2802,7 @@
 }
 
 /**
- * hdd_ipa_pm_send_pkt_to_tl() - Send queued packets to TL
+ * hdd_ipa_pm_flush() - flush queued packets
  * @work: pointer to the scheduled work
  *
  * Called during PM resume to send packets to TL which were queued
@@ -2777,7 +2810,7 @@
  *
  * Return: None
  */
-static void hdd_ipa_pm_send_pkt_to_tl(struct work_struct *work)
+static void hdd_ipa_pm_flush(struct work_struct *work)
 {
 	struct hdd_ipa_priv *hdd_ipa = container_of(work,
 						    struct hdd_ipa_priv,
@@ -2786,23 +2819,28 @@
 	qdf_nbuf_t skb;
 	uint32_t dequeued = 0;
 
+	qdf_wake_lock_acquire(&hdd_ipa->wake_lock,
+			      WIFI_POWER_EVENT_WAKELOCK_IPA);
 	qdf_spin_lock_bh(&hdd_ipa->pm_lock);
-
 	while (((skb = qdf_nbuf_queue_remove(&hdd_ipa->pm_queue_head))
 								!= NULL)) {
 		qdf_spin_unlock_bh(&hdd_ipa->pm_lock);
 
 		pm_tx_cb = (struct hdd_ipa_pm_tx_cb *)skb->cb;
-
 		dequeued++;
-
-		hdd_ipa_send_pkt_to_tl(pm_tx_cb->iface_context,
+		if (pm_tx_cb->exception) {
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+				"FLUSH EXCEPTION");
+			hdd_softap_hard_start_xmit(skb, pm_tx_cb->adapter->dev);
+		} else {
+			hdd_ipa_send_pkt_to_tl(pm_tx_cb->iface_context,
 				       pm_tx_cb->ipa_tx_desc);
-
+		}
 		qdf_spin_lock_bh(&hdd_ipa->pm_lock);
 	}
-
 	qdf_spin_unlock_bh(&hdd_ipa->pm_lock);
+	qdf_wake_lock_release(&hdd_ipa->wake_lock,
+			      WIFI_POWER_EVENT_WAKELOCK_IPA);
 
 	hdd_ipa->stats.num_tx_dequeued += dequeued;
 	if (dequeued > hdd_ipa->stats.num_max_pm_queue)
@@ -4158,7 +4196,7 @@
 		qdf_spinlock_create(&iface_context->interface_lock);
 	}
 
-	INIT_WORK(&hdd_ipa->pm_work, hdd_ipa_pm_send_pkt_to_tl);
+	INIT_WORK(&hdd_ipa->pm_work, hdd_ipa_pm_flush);
 	qdf_spinlock_create(&hdd_ipa->pm_lock);
 	qdf_nbuf_queue_init(&hdd_ipa->pm_queue_head);