qcacld-3.0: Add interface to WDI-stats and quota limit

Add structure changes for querying WDI-stats, register the callback
functions for IPA to query WDI stats and sets the quota limit

Change-Id: I05d9d50fad735fa0a57c4de23c81461f6e12b648
CRs-Fixed: 1095710
diff --git a/Kbuild b/Kbuild
index a6d7320..02a1718 100644
--- a/Kbuild
+++ b/Kbuild
@@ -119,6 +119,7 @@
 	endif
 	ifeq ($(CONFIG_ARCH_MSM8998), y)
 	CONFIG_QCACLD_FEATURE_GREEN_AP := y
+	CONFIG_QCACLD_FEATURE_METERING := y
 	endif
 
 	ifeq ($(CONFIG_ARCH_SDM660), y)
@@ -1903,6 +1904,11 @@
 CDEFINES += -DFEATURE_GREEN_AP
 endif
 
+#Stats & Quota Metering feature
+ifeq ($(CONFIG_QCACLD_FEATURE_METERING),y)
+CDEFINES += -DFEATURE_METERING
+endif
+
 #Enable RX Full re-order OL feature only "LL and NON-MDM9630 platform"
 ifneq ($(CONFIG_ARCH_MDM9630), y)
 ifeq ($(CONFIG_HIF_PCI), 1)
diff --git a/core/dp/htt/htt_h2t.c b/core/dp/htt/htt_h2t.c
index a25fca2..0a770f1 100644
--- a/core/dp/htt/htt_h2t.c
+++ b/core/dp/htt/htt_h2t.c
@@ -1330,4 +1330,143 @@
 	HTT_SEND_HTC_PKT(pdev, pkt);
 	return A_OK;
 }
+
+/**
+ * htt_h2t_ipa_uc_get_share_stats() - WDI UC wifi sharing state request to FW
+ * @pdev: handle to the HTT instance
+ *
+ * Return: A_OK success
+ *         A_NO_MEMORY No memory fail
+ */
+int htt_h2t_ipa_uc_get_share_stats(struct htt_pdev_t *pdev, uint8_t reset_stats)
+{
+	struct htt_htc_pkt *pkt;
+	qdf_nbuf_t msg;
+	uint32_t *msg_word;
+
+	pkt = htt_htc_pkt_alloc(pdev);
+	if (!pkt)
+		return -A_NO_MEMORY;
+
+	/* show that this is not a tx frame download
+	 * (not required, but helpful)
+	 */
+	pkt->msdu_id = HTT_TX_COMPL_INV_MSDU_ID;
+	pkt->pdev_ctxt = NULL;  /* not used during send-done callback */
+
+	/* reserve room for HTC header */
+	msg = qdf_nbuf_alloc(pdev->osdev,
+		HTT_MSG_BUF_SIZE(HTT_WDI_IPA_OP_REQUEST_SZ)+
+		HTT_MSG_BUF_SIZE(HTT_WDI_IPA_OP_REQ_GET_SHARING_STATS_SZ),
+		HTC_HEADER_LEN + HTC_HDR_ALIGNMENT_PADDING, 4, false);
+	if (!msg) {
+		htt_htc_pkt_free(pdev, pkt);
+		return -A_NO_MEMORY;
+	}
+	/* set the length of the message */
+	qdf_nbuf_put_tail(msg, HTT_WDI_IPA_OP_REQUEST_SZ+
+			  HTT_WDI_IPA_OP_REQ_GET_SHARING_STATS_SZ);
+
+	/* fill in the message contents */
+	msg_word = (uint32_t *) qdf_nbuf_data(msg);
+
+	/* rewind beyond alignment pad to get to the HTC header reserved area */
+	qdf_nbuf_push_head(msg, HTC_HDR_ALIGNMENT_PADDING);
+
+	*msg_word = 0;
+	HTT_WDI_IPA_OP_REQUEST_OP_CODE_SET(*msg_word,
+				   HTT_WDI_IPA_OPCODE_GET_SHARING_STATS);
+	HTT_H2T_MSG_TYPE_SET(*msg_word, HTT_H2T_MSG_TYPE_WDI_IPA_OP_REQ);
+
+	msg_word++;
+	*msg_word = 0;
+	HTT_WDI_IPA_OP_REQ_GET_SHARING_STATS_RESET_STATS_SET(*msg_word,
+							     reset_stats);
+
+	SET_HTC_PACKET_INFO_TX(&pkt->htc_pkt,
+			       htt_h2t_send_complete_free_netbuf,
+			       qdf_nbuf_data(msg),
+			       qdf_nbuf_len(msg),
+			       pdev->htc_tx_endpoint,
+			       1); /* tag - not relevant here */
+
+	SET_HTC_PACKET_NET_BUF_CONTEXT(&pkt->htc_pkt, msg);
+	HTT_SEND_HTC_PKT(pdev, pkt);
+	return A_OK;
+}
+
+/**
+ * htt_h2t_ipa_uc_set_quota() - WDI UC state query request to firmware
+ * @pdev: handle to the HTT instance
+ *
+ * Return: A_OK success
+ *         A_NO_MEMORY No memory fail
+ */
+int htt_h2t_ipa_uc_set_quota(struct htt_pdev_t *pdev, uint64_t quota_bytes)
+{
+	struct htt_htc_pkt *pkt;
+	qdf_nbuf_t msg;
+	uint32_t *msg_word;
+
+	pkt = htt_htc_pkt_alloc(pdev);
+	if (!pkt)
+		return -A_NO_MEMORY;
+
+	/* show that this is not a tx frame download
+	 * (not required, but helpful)
+	 */
+	pkt->msdu_id = HTT_TX_COMPL_INV_MSDU_ID;
+	pkt->pdev_ctxt = NULL;  /* not used during send-done callback */
+
+	/* reserve room for HTC header */
+	msg = qdf_nbuf_alloc(pdev->osdev,
+		HTT_MSG_BUF_SIZE(HTT_WDI_IPA_OP_REQUEST_SZ)+
+		HTT_MSG_BUF_SIZE(HTT_WDI_IPA_OP_REQ_SET_QUOTA_SZ),
+		HTC_HEADER_LEN + HTC_HDR_ALIGNMENT_PADDING, 4, false);
+	if (!msg) {
+		htt_htc_pkt_free(pdev, pkt);
+		return -A_NO_MEMORY;
+	}
+	/* set the length of the message */
+	qdf_nbuf_put_tail(msg, HTT_WDI_IPA_OP_REQUEST_SZ+
+			  HTT_WDI_IPA_OP_REQ_SET_QUOTA_SZ);
+
+	/* fill in the message contents */
+	msg_word = (uint32_t *) qdf_nbuf_data(msg);
+
+	/* rewind beyond alignment pad to get to the HTC header reserved area */
+	qdf_nbuf_push_head(msg, HTC_HDR_ALIGNMENT_PADDING);
+
+	*msg_word = 0;
+	HTT_WDI_IPA_OP_REQUEST_OP_CODE_SET(*msg_word,
+					   HTT_WDI_IPA_OPCODE_SET_QUOTA);
+	HTT_H2T_MSG_TYPE_SET(*msg_word, HTT_H2T_MSG_TYPE_WDI_IPA_OP_REQ);
+
+	msg_word++;
+	*msg_word = 0;
+	HTT_WDI_IPA_OP_REQ_SET_QUOTA_SET_QUOTA_SET(*msg_word, quota_bytes > 0);
+
+	msg_word++;
+	*msg_word = 0;
+	HTT_WDI_IPA_OP_REQ_SET_QUOTA_QUOTA_LO_SET(*msg_word,
+			(uint32_t)(quota_bytes &
+				   HTT_WDI_IPA_OP_REQ_SET_QUOTA_QUOTA_LO_M));
+
+	msg_word++;
+	*msg_word = 0;
+	HTT_WDI_IPA_OP_REQ_SET_QUOTA_QUOTA_HI_SET(*msg_word,
+			(uint32_t)(quota_bytes>>32 &
+				   HTT_WDI_IPA_OP_REQ_SET_QUOTA_QUOTA_HI_M));
+
+	SET_HTC_PACKET_INFO_TX(&pkt->htc_pkt,
+			       htt_h2t_send_complete_free_netbuf,
+			       qdf_nbuf_data(msg),
+			       qdf_nbuf_len(msg),
+			       pdev->htc_tx_endpoint,
+			       1); /* tag - not relevant here */
+
+	SET_HTC_PACKET_NET_BUF_CONTEXT(&pkt->htc_pkt, msg);
+	HTT_SEND_HTC_PKT(pdev, pkt);
+	return A_OK;
+}
 #endif /* IPA_OFFLOAD */
diff --git a/core/dp/ol/inc/ol_htt_api.h b/core/dp/ol/inc/ol_htt_api.h
index c19827e..ba4c67d 100644
--- a/core/dp/ol/inc/ol_htt_api.h
+++ b/core/dp/ol/inc/ol_htt_api.h
@@ -243,6 +243,11 @@
 
 int htt_h2t_ipa_uc_get_stats(struct htt_pdev_t *pdev);
 
+int htt_h2t_ipa_uc_get_share_stats(struct htt_pdev_t *pdev,
+				   uint8_t reset_stats);
+
+int htt_h2t_ipa_uc_set_quota(struct htt_pdev_t *pdev, uint64_t quota_bytes);
+
 int htt_ipa_uc_attach(struct htt_pdev_t *pdev);
 
 void htt_ipa_uc_detach(struct htt_pdev_t *pdev);
@@ -341,6 +346,30 @@
 }
 
 /**
+ * htt_h2t_ipa_uc_get_share_stats() - WDI UC wifi sharing state request to FW
+ * @pdev: handle to the HTT instance
+ *
+ * Return: 0 success
+ */
+static inline int htt_h2t_ipa_uc_get_share_stats(struct htt_pdev_t *pdev,
+						uint8_t reset_stats)
+{
+	return 0;
+}
+
+/**
+ * htt_h2t_ipa_uc_set_quota() - WDI UC set quota request to firmware
+ * @pdev: handle to the HTT instance
+ *
+ * Return: 0 success
+ */
+static inline int htt_h2t_ipa_uc_set_quota(struct htt_pdev_t *pdev,
+					   uint64_t quota_bytes)
+{
+	return 0;
+}
+
+/**
  * htt_ipa_uc_attach() - Allocate UC data path resources
  * @pdev: handle to the HTT instance
  *
diff --git a/core/dp/txrx/ol_txrx.c b/core/dp/txrx/ol_txrx.c
index dc7632e..f5e2e2c 100644
--- a/core/dp/txrx/ol_txrx.c
+++ b/core/dp/txrx/ol_txrx.c
@@ -4368,6 +4368,20 @@
 	struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
 	htt_h2t_ipa_uc_get_stats(pdev->htt_pdev);
 }
+
+static void ol_txrx_ipa_uc_get_share_stats(struct cdp_pdev *ppdev,
+					   uint8_t reset_stats)
+{
+	struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
+	htt_h2t_ipa_uc_get_share_stats(pdev->htt_pdev, reset_stats);
+}
+
+static void ol_txrx_ipa_uc_set_quota(struct cdp_pdev *ppdev,
+				     uint64_t quota_bytes)
+{
+	struct ol_txrx_pdev_t *pdev = (struct ol_txrx_pdev_t *)ppdev;
+	htt_h2t_ipa_uc_set_quota(pdev->htt_pdev, quota_bytes);
+}
 #endif /* IPA_UC_OFFLOAD */
 
 /*
@@ -5357,7 +5371,9 @@
 	.ipa_register_op_cb = ol_txrx_ipa_uc_register_op_cb,
 	.ipa_get_stat = ol_txrx_ipa_uc_get_stat,
 	.ipa_tx_data_frame = ol_tx_send_ipa_data_frame,
-	.ipa_set_uc_tx_partition_base = ol_cfg_set_ipa_uc_tx_partition_base
+	.ipa_set_uc_tx_partition_base = ol_cfg_set_ipa_uc_tx_partition_base,
+	.ipa_uc_get_share_stats = ol_txrx_ipa_uc_get_share_stats,
+	.ipa_uc_set_quota = ol_txrx_ipa_uc_set_quota
 #endif /* IPA_OFFLOAD */
 };
 
diff --git a/core/hdd/inc/wlan_hdd_ipa.h b/core/hdd/inc/wlan_hdd_ipa.h
index 63250fb..506669d 100644
--- a/core/hdd/inc/wlan_hdd_ipa.h
+++ b/core/hdd/inc/wlan_hdd_ipa.h
@@ -98,6 +98,10 @@
 	uint32_t *ipa_rx_diff);
 void hdd_ipa_uc_rt_debug_host_dump(hdd_context_t *hdd_ctx);
 void hdd_ipa_uc_stat_request(hdd_adapter_t *adapter, uint8_t reason);
+void hdd_ipa_uc_sharing_stats_request(hdd_adapter_t *adapter,
+				      uint8_t reset_stats);
+void hdd_ipa_uc_set_quota(hdd_adapter_t *adapter, uint8_t set_quota,
+			  uint64_t quota_bytes);
 bool hdd_ipa_is_enabled(hdd_context_t *pHddCtx);
 bool hdd_ipa_uc_is_enabled(hdd_context_t *pHddCtx);
 #ifndef QCA_LL_TX_FLOW_CONTROL_V2
diff --git a/core/hdd/src/wlan_hdd_ipa.c b/core/hdd/src/wlan_hdd_ipa.c
index 902fbc3..4825379 100644
--- a/core/hdd/src/wlan_hdd_ipa.c
+++ b/core/hdd/src/wlan_hdd_ipa.c
@@ -97,6 +97,11 @@
 	HDD_IPA_UC_OPCODE_RX_SUSPEND = 2,
 	HDD_IPA_UC_OPCODE_RX_RESUME = 3,
 	HDD_IPA_UC_OPCODE_STATS = 4,
+#ifdef FEATURE_METERING
+	HDD_IPA_UC_OPCODE_SHARING_STATS = 5,
+	HDD_IPA_UC_OPCODE_QUOTA_RSP = 6,
+	HDD_IPA_UC_OPCODE_QUOTA_IND = 7,
+#endif
 	HDD_IPA_UC_OPCODE_UC_READY = 8,
 	/* keep this last */
 	HDD_IPA_UC_OPCODE_MAX
@@ -375,6 +380,30 @@
 	uint64_t rx_destructor_call;
 };
 
+#ifdef FEATURE_METERING
+struct ipa_uc_sharing_stats {
+	uint64_t ipv4_rx_packets;
+	uint64_t ipv4_rx_bytes;
+	uint64_t ipv6_rx_packets;
+	uint64_t ipv6_rx_bytes;
+	uint64_t ipv4_tx_packets;
+	uint64_t ipv4_tx_bytes;
+	uint64_t ipv6_tx_packets;
+	uint64_t ipv6_tx_bytes;
+};
+
+struct ipa_uc_quota_rsp {
+	uint8_t success;
+	uint8_t reserved[3];
+	uint32_t quota_lo;		/* quota limit low bytes */
+	uint32_t quota_hi;		/* quota limit high bytes */
+};
+
+struct ipa_uc_quota_ind {
+	uint64_t quota_bytes;		/* quota limit in bytes */
+};
+#endif
+
 struct hdd_ipa_priv {
 	struct hdd_ipa_sys_pipe sys_pipe[HDD_IPA_MAX_SYSBAM_PIPE];
 	struct hdd_ipa_iface_context iface_context[HDD_IPA_MAX_IFACE];
@@ -454,9 +483,15 @@
 	/* IPA UC doorbell registers paddr */
 	qdf_dma_addr_t tx_comp_doorbell_paddr;
 	qdf_dma_addr_t rx_ready_doorbell_paddr;
-
 	uint8_t vdev_to_iface[CSR_ROAM_SESSION_MAX];
 	bool vdev_offload_enabled[CSR_ROAM_SESSION_MAX];
+#ifdef FEATURE_METERING
+	struct ipa_uc_sharing_stats ipa_sharing_stats;
+	struct ipa_uc_quota_rsp ipa_quota_rsp;
+	struct ipa_uc_quota_ind ipa_quota_ind;
+	struct completion ipa_uc_sharing_stats_comp;
+	struct completion ipa_uc_set_quota_comp;
+#endif
 };
 
 /**
@@ -596,6 +631,11 @@
 	}
 };
 
+#ifdef FEATURE_METERING
+#define IPA_UC_SHARING_STATES_WAIT_TIME	500
+#define IPA_UC_SET_QUOTA_WAIT_TIME	500
+#endif
+
 static struct hdd_ipa_priv *ghdd_ipa;
 
 /* Local Function Prototypes */
@@ -802,20 +842,13 @@
  *
  * Return: None
  */
-#ifdef IPA_UC_STA_OFFLOAD
 static inline void hdd_ipa_uc_sta_reset_sta_connected(
 		struct hdd_ipa_priv *hdd_ipa)
 {
-	vos_lock_acquire(&hdd_ipa->event_lock);
+	qdf_mutex_acquire(&hdd_ipa->ipa_lock);
 	hdd_ipa->sta_connected = 0;
-	vos_lock_release(&hdd_ipa->event_lock);
+	qdf_mutex_release(&hdd_ipa->ipa_lock);
 }
-#else
-static inline void hdd_ipa_uc_sta_reset_sta_connected(
-		struct hdd_ipa_priv *hdd_ipa)
-{
-}
-#endif
 
 /**
  * hdd_ipa_is_pre_filter_enabled() - Is IPA pre-filter enabled?
@@ -1019,7 +1052,8 @@
 	if (!dummy_ptr) {
 		hdd_ipa_uc_rt_debug_host_dump(hdd_ctx);
 		hdd_ipa_uc_stat_request(
-			hdd_get_adapter(hdd_ctx, QDF_SAP_MODE), 1);
+			hdd_get_adapter(hdd_ctx, QDF_SAP_MODE),
+			HDD_IPA_UC_STAT_REASON_DEBUG);
 	} else {
 		kfree(dummy_ptr);
 	}
@@ -1735,9 +1769,8 @@
 	hdd_context_t *hdd_ctx;
 	struct hdd_ipa_priv *hdd_ipa;
 
-	if (!adapter) {
+	if (!adapter)
 		return;
-	}
 
 	hdd_ctx = (hdd_context_t *)adapter->pHddCtx;
 
@@ -1755,12 +1788,14 @@
 	if ((HDD_IPA_UC_NUM_WDI_PIPE == hdd_ipa->activated_fw_pipe) &&
 		(false == hdd_ipa->resource_loading)) {
 		hdd_ipa->stat_req_reason = reason;
+		qdf_mutex_release(&hdd_ipa->ipa_lock);
 		wma_cli_set_command(
 			(int)adapter->sessionId,
 			(int)WMA_VDEV_TXRX_GET_IPA_UC_FW_STATS_CMDID,
 			0, VDEV_CMD);
+	} else {
+		qdf_mutex_release(&hdd_ipa->ipa_lock);
 	}
-	qdf_mutex_release(&hdd_ipa->ipa_lock);
 }
 
 /**
@@ -1777,6 +1812,85 @@
 	cds_ssr_unprotect(__func__);
 }
 
+#ifdef FEATURE_METERING
+/**
+ * hdd_ipa_uc_sharing_stats_request() - Get IPA stats from IPA.
+ * @adapter: network adapter
+ * @reset_stats: reset stat countis after response
+ *
+ * Return: None
+ */
+void hdd_ipa_uc_sharing_stats_request(hdd_adapter_t *adapter,
+				      uint8_t reset_stats)
+{
+	hdd_context_t *pHddCtx;
+	struct hdd_ipa_priv *hdd_ipa;
+
+	if (!adapter)
+		return;
+
+	pHddCtx = adapter->pHddCtx;
+	hdd_ipa = pHddCtx->hdd_ipa;
+	if (!hdd_ipa_is_enabled(pHddCtx) ||
+		!(hdd_ipa_uc_is_enabled(pHddCtx))) {
+		return;
+	}
+
+	HDD_IPA_LOG(LOG1, "SHARING_STATS: reset_stats=%d", reset_stats);
+	qdf_mutex_acquire(&hdd_ipa->ipa_lock);
+	if ((HDD_IPA_UC_NUM_WDI_PIPE == hdd_ipa->activated_fw_pipe) &&
+		(false == hdd_ipa->resource_loading)) {
+		qdf_mutex_release(&hdd_ipa->ipa_lock);
+		wma_cli_set_command(
+			(int)adapter->sessionId,
+			(int)WMA_VDEV_TXRX_GET_IPA_UC_SHARING_STATS_CMDID,
+			reset_stats, VDEV_CMD);
+	} else {
+		qdf_mutex_release(&hdd_ipa->ipa_lock);
+	}
+}
+
+/**
+ * hdd_ipa_uc_set_quota() - Set quota limit bytes from IPA.
+ * @adapter: network adapter
+ * @set_quota: when 1, FW starts quota monitoring
+ * @quota_bytes: quota limit in bytes
+ *
+ * Return: None
+ */
+void hdd_ipa_uc_set_quota(hdd_adapter_t *adapter, uint8_t set_quota,
+			  uint64_t quota_bytes)
+{
+	hdd_context_t *pHddCtx;
+	struct hdd_ipa_priv *hdd_ipa;
+
+	if (!adapter)
+		return;
+
+	pHddCtx = adapter->pHddCtx;
+	hdd_ipa = pHddCtx->hdd_ipa;
+	if (!hdd_ipa_is_enabled(pHddCtx) ||
+		!(hdd_ipa_uc_is_enabled(pHddCtx))) {
+		return;
+	}
+
+	HDD_IPA_LOG(LOG1, "SET_QUOTA: set_quota=%d, quota_bytes=%llu",
+		    set_quota, quota_bytes);
+
+	qdf_mutex_acquire(&hdd_ipa->ipa_lock);
+	if ((HDD_IPA_UC_NUM_WDI_PIPE == hdd_ipa->activated_fw_pipe) &&
+		(false == hdd_ipa->resource_loading)) {
+		qdf_mutex_release(&hdd_ipa->ipa_lock);
+		wma_cli_set_command(
+			(int)adapter->sessionId,
+			(int)WMA_VDEV_TXRX_SET_IPA_UC_QUOTA_CMDID,
+			(set_quota ? quota_bytes : 0), VDEV_CMD);
+	} else {
+		qdf_mutex_release(&hdd_ipa->ipa_lock);
+	}
+}
+#endif
+
 /**
  * hdd_ipa_uc_find_add_assoc_sta() - Find associated station
  * @hdd_ipa: Global HDD IPA context
@@ -2140,6 +2254,97 @@
 }
 
 /**
+ * hdd_ipa_uc_op_metering() - IPA uC operation for stats and quota limit
+ * @hdd_ctx: Global HDD context
+ * @op_msg: operation message received from firmware
+ *
+ * Return: QDF_STATUS enumeration
+ */
+#ifdef FEATURE_METERING
+static QDF_STATUS hdd_ipa_uc_op_metering(hdd_context_t *hdd_ctx,
+					 struct op_msg_type *op_msg)
+{
+	struct op_msg_type *msg = op_msg;
+	struct ipa_uc_sharing_stats *uc_sharing_stats;
+	struct ipa_uc_quota_rsp *uc_quota_rsp;
+	struct ipa_uc_quota_ind *uc_quota_ind;
+	struct hdd_ipa_priv *hdd_ipa;
+	hdd_adapter_t *adapter;
+
+	hdd_ipa = (struct hdd_ipa_priv *)hdd_ctx->hdd_ipa;
+
+	if (HDD_IPA_UC_OPCODE_SHARING_STATS == msg->op_code) {
+		/* fill-up ipa_uc_sharing_stats structure from FW */
+		uc_sharing_stats = (struct ipa_uc_sharing_stats *)
+			     ((uint8_t *)op_msg + sizeof(struct op_msg_type));
+
+		memcpy(&(hdd_ipa->ipa_sharing_stats), uc_sharing_stats,
+		       sizeof(struct ipa_uc_sharing_stats));
+
+		complete(&hdd_ipa->ipa_uc_sharing_stats_comp);
+
+		HDD_IPA_DP_LOG(QDF_TRACE_LEVEL_DEBUG,
+			       "%s: %llu,%llu,%llu,%llu,%llu,%llu,%llu,%llu",
+			       "HDD_IPA_UC_OPCODE_SHARING_STATS",
+			       hdd_ipa->ipa_sharing_stats.ipv4_rx_packets,
+			       hdd_ipa->ipa_sharing_stats.ipv4_rx_bytes,
+			       hdd_ipa->ipa_sharing_stats.ipv6_rx_packets,
+			       hdd_ipa->ipa_sharing_stats.ipv6_rx_bytes,
+			       hdd_ipa->ipa_sharing_stats.ipv4_tx_packets,
+			       hdd_ipa->ipa_sharing_stats.ipv4_tx_bytes,
+			       hdd_ipa->ipa_sharing_stats.ipv6_tx_packets,
+			       hdd_ipa->ipa_sharing_stats.ipv6_tx_bytes);
+	} else if (HDD_IPA_UC_OPCODE_QUOTA_RSP == msg->op_code) {
+		/* received set quota response */
+		uc_quota_rsp = (struct ipa_uc_quota_rsp *)
+			     ((uint8_t *)op_msg + sizeof(struct op_msg_type));
+
+		memcpy(&(hdd_ipa->ipa_quota_rsp), uc_quota_rsp,
+			   sizeof(struct ipa_uc_quota_rsp));
+
+		complete(&hdd_ipa->ipa_uc_set_quota_comp);
+		HDD_IPA_DP_LOG(QDF_TRACE_LEVEL_DEBUG,
+			      "%s: success=%d, quota_bytes=%llu",
+			      "HDD_IPA_UC_OPCODE_QUOTA_RSP",
+			      hdd_ipa->ipa_quota_rsp.success,
+			      ((uint64_t)(hdd_ipa->ipa_quota_rsp.quota_hi)<<32)|
+			      hdd_ipa->ipa_quota_rsp.quota_lo);
+	} else if (HDD_IPA_UC_OPCODE_QUOTA_IND == msg->op_code) {
+		/* hit quota limit */
+		uc_quota_ind = (struct ipa_uc_quota_ind *)
+			     ((uint8_t *)op_msg + sizeof(struct op_msg_type));
+
+		hdd_ipa->ipa_quota_ind.quota_bytes =
+					uc_quota_ind->quota_bytes;
+
+		/* send quota exceeded indication to IPA */
+		HDD_IPA_DP_LOG(QDF_TRACE_LEVEL_DEBUG,
+			"OPCODE_QUOTA_IND: quota exceed! (quota_bytes=%llu)",
+			hdd_ipa->ipa_quota_ind.quota_bytes);
+
+		adapter = hdd_get_adapter(hdd_ipa->hdd_ctx, QDF_STA_MODE);
+		if (adapter)
+			ipa_broadcast_wdi_quota_reach_ind(
+						adapter->dev->ifindex,
+						uc_quota_ind->quota_bytes);
+		else
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+					"Failed quota_reach_ind: NULL adapter");
+	} else {
+		return QDF_STATUS_E_INVAL;
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+#else
+static QDF_STATUS hdd_ipa_uc_op_metering(hdd_context_t *hdd_ctx,
+					 struct op_msg_type *op_msg)
+{
+	return QDF_STATUS_E_INVAL;
+}
+#endif
+
+/**
  * hdd_ipa_uc_op_cb() - IPA uC operation callback
  * @op_msg: operation message received from firmware
  * @usr_ctxt: user context registered with TL (we register the HDD Global
@@ -2426,9 +2631,9 @@
 		qdf_mutex_acquire(&hdd_ipa->ipa_lock);
 		hdd_ipa_uc_loaded_handler(hdd_ipa);
 		qdf_mutex_release(&hdd_ipa->ipa_lock);
-	} else {
-		HDD_IPA_LOG(LOGE, "INVALID REASON %d",
-			    hdd_ipa->stat_req_reason);
+	} else if (hdd_ipa_uc_op_metering(hdd_ctx, op_msg)) {
+		HDD_IPA_LOG(LOGE, "Invalid message: op_code=%d, reason=%d",
+			    msg->op_code, hdd_ipa->stat_req_reason);
 	}
 	qdf_mem_free(op_msg);
 }
@@ -2583,11 +2788,165 @@
  * Return: none
  */
 static void hdd_ipa_init_uc_op_work(struct work_struct *work,
-					work_func_t work_handler)
+				    work_func_t work_handler)
 {
 	INIT_WORK(work, work_handler);
 }
 
+#ifdef FEATURE_METERING
+/**
+ * __hdd_ipa_wdi_meter_notifier_cb() - WLAN to IPA callback handler.
+ * IPA calls to get WLAN stats or set quota limit.
+ * @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
+ * @data: data associated with the event
+ *
+ * Return: None
+ */
+static void __hdd_ipa_wdi_meter_notifier_cb(enum ipa_wdi_meter_evt_type evt,
+					  void *data)
+{
+	struct hdd_ipa_priv *hdd_ipa = ghdd_ipa;
+	hdd_adapter_t *adapter = NULL;
+	struct ipa_get_wdi_sap_stats *wdi_sap_stats;
+	struct ipa_set_wifi_quota *ipa_set_quota;
+	int ret = 0;
+
+	if (wlan_hdd_validate_context(hdd_ipa->hdd_ctx))
+		return;
+
+	adapter = hdd_get_adapter(hdd_ipa->hdd_ctx, QDF_STA_MODE);
+
+	HDD_IPA_LOG(QDF_TRACE_LEVEL_INFO, "event=%d", evt);
+
+	switch (evt) {
+	case IPA_GET_WDI_SAP_STATS:
+		/* fill-up ipa_get_wdi_sap_stats structure after getting
+		   ipa_uc_fw_stats from FW */
+		wdi_sap_stats = data;
+
+		if (!adapter) {
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+				"IPA uC share stats failed - no adapter");
+			wdi_sap_stats->stats_valid = 0;
+			return;
+		}
+
+		INIT_COMPLETION(hdd_ipa->ipa_uc_sharing_stats_comp);
+		INIT_COMPLETION(hdd_ipa->ipa_uc_set_quota_comp);
+		hdd_ipa_uc_sharing_stats_request(adapter,
+					     wdi_sap_stats->reset_stats);
+		ret = wait_for_completion_timeout(
+			&hdd_ipa->ipa_uc_sharing_stats_comp,
+			msecs_to_jiffies(IPA_UC_SHARING_STATES_WAIT_TIME));
+		if (!ret) {
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+					"IPA uC share stats request timed out");
+			wdi_sap_stats->stats_valid = 0;
+		} else {
+			wdi_sap_stats->stats_valid = 1;
+
+			wdi_sap_stats->ipv4_rx_packets =
+				hdd_ipa->ipa_sharing_stats.ipv4_rx_packets;
+			wdi_sap_stats->ipv4_rx_bytes =
+				hdd_ipa->ipa_sharing_stats.ipv4_rx_bytes;
+			wdi_sap_stats->ipv6_rx_packets =
+				hdd_ipa->ipa_sharing_stats.ipv6_rx_packets;
+			wdi_sap_stats->ipv6_rx_bytes =
+				hdd_ipa->ipa_sharing_stats.ipv6_rx_bytes;
+			wdi_sap_stats->ipv4_tx_packets =
+				hdd_ipa->ipa_sharing_stats.ipv4_tx_packets;
+			wdi_sap_stats->ipv4_tx_bytes =
+				hdd_ipa->ipa_sharing_stats.ipv4_tx_bytes;
+			wdi_sap_stats->ipv6_tx_packets =
+				hdd_ipa->ipa_sharing_stats.ipv6_tx_packets;
+			wdi_sap_stats->ipv6_tx_bytes =
+				hdd_ipa->ipa_sharing_stats.ipv6_tx_bytes;
+			HDD_IPA_DP_LOG(QDF_TRACE_LEVEL_DEBUG,
+				"%s:%d,%llu,%llu,%llu,%llu,%llu,%llu,%llu,%llu",
+				"IPA_GET_WDI_SAP_STATS",
+				wdi_sap_stats->stats_valid,
+				wdi_sap_stats->ipv4_rx_packets,
+				wdi_sap_stats->ipv4_rx_bytes,
+				wdi_sap_stats->ipv6_rx_packets,
+				wdi_sap_stats->ipv6_rx_bytes,
+				wdi_sap_stats->ipv4_tx_packets,
+				wdi_sap_stats->ipv4_tx_bytes,
+				wdi_sap_stats->ipv6_tx_packets,
+				wdi_sap_stats->ipv6_tx_bytes);
+		}
+		break;
+	case IPA_SET_WIFI_QUOTA:
+		/* get ipa_set_wifi_quota structure from IPA and pass to FW
+		   through quota_exceeded field in ipa_uc_fw_stats */
+		ipa_set_quota = data;
+
+		if (!adapter) {
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+				"IPA uC set quota failed - no adapter");
+			ipa_set_quota->set_valid = 0;
+			return;
+		}
+
+		hdd_ipa_uc_set_quota(adapter, ipa_set_quota->set_quota,
+				     ipa_set_quota->quota_bytes);
+
+		ret = wait_for_completion_timeout(
+				&hdd_ipa->ipa_uc_set_quota_comp,
+				msecs_to_jiffies(IPA_UC_SET_QUOTA_WAIT_TIME));
+		if (!ret) {
+			HDD_IPA_LOG(QDF_TRACE_LEVEL_ERROR,
+					"IPA uC set quota request timed out");
+			ipa_set_quota->set_valid = 0;
+		} else {
+			ipa_set_quota->quota_bytes =
+				((uint64_t)(hdd_ipa->ipa_quota_rsp.quota_hi)
+				  <<32)|hdd_ipa->ipa_quota_rsp.quota_lo;
+			ipa_set_quota->set_valid =
+				hdd_ipa->ipa_quota_rsp.success;
+		}
+
+		HDD_IPA_DP_LOG(QDF_TRACE_LEVEL_DEBUG, "SET_QUOTA: %llu, %d",
+			       ipa_set_quota->quota_bytes,
+			       ipa_set_quota->set_valid);
+		break;
+	}
+}
+
+/**
+ * hdd_ipa_wdi_meter_notifier_cb() - WLAN to IPA callback handler.
+ * IPA calls to get WLAN stats or set quota limit.
+ * @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
+ * @data: data associated with the event
+ *
+ * Return: None
+ */
+static void hdd_ipa_wdi_meter_notifier_cb(enum ipa_wdi_meter_evt_type evt,
+					  void *data)
+{
+	cds_ssr_protect(__func__);
+	__hdd_ipa_wdi_meter_notifier_cb(evt, data);
+	cds_ssr_unprotect(__func__);
+}
+
+static void hdd_ipa_init_metering(struct hdd_ipa_priv *ipa_ctxt,
+				  struct ipa_wdi_in_params *pipe_in)
+{
+	pipe_in->wdi_notify = hdd_ipa_wdi_meter_notifier_cb;
+
+	init_completion(&ipa_ctxt->ipa_uc_sharing_stats_comp);
+	init_completion(&ipa_ctxt->ipa_uc_set_quota_comp);
+}
+#else
+static void hdd_ipa_init_metering(struct hdd_ipa_priv *ipa_ctxt,
+				  struct ipa_wdi_in_params *pipe_in)
+{
+}
+#endif
+
 /**
  * hdd_ipa_uc_ol_init() - Initialize IPA uC offload
  * @hdd_ctx: Global HDD context
@@ -2682,25 +3041,32 @@
 			stat = QDF_STATUS_E_FAILURE;
 			goto fail_return;
 		}
+
 		/* Micro Controller Doorbell register */
 		HDD_IPA_LOG(QDF_TRACE_LEVEL_DEBUG,
-			"CONS DB pipe out 0x%x TX PIPE Handle 0x%x",
-			(unsigned int)pipe_out.uc_door_bell_pa,
-			ipa_ctxt->tx_pipe_handle);
-
+			    "CONS DB pipe out 0x%x TX PIPE Handle 0x%x",
+			    (unsigned int)pipe_out.uc_door_bell_pa,
+			    ipa_ctxt->tx_pipe_handle);
 		ipa_ctxt->tx_comp_doorbell_paddr = pipe_out.uc_door_bell_pa;
 
 		/* WLAN TX PIPE Handle */
 		ipa_ctxt->tx_pipe_handle = pipe_out.clnt_hdl;
 		HDD_IPA_LOG(QDF_TRACE_LEVEL_DEBUG,
-			"TX : 0x%x, %d, 0x%x, 0x%x, %d, %d, 0x%x",
-			(unsigned int)pipe_in.u.dl.comp_ring_base_pa,
-			pipe_in.u.dl.comp_ring_size,
-			(unsigned int)pipe_in.u.dl.ce_ring_base_pa,
-			(unsigned int)pipe_in.u.dl.ce_door_bell_pa,
-			pipe_in.u.dl.ce_ring_size,
-			pipe_in.u.dl.num_tx_buffers,
-			(unsigned int)ipa_ctxt->tx_comp_doorbell_paddr);
+		  "TX: %s 0x%x, %s %d, %s 0x%x, %s 0x%x, %s %d, %s %d, %s 0x%x",
+		  "comp_ring_base_pa",
+		  (unsigned int)pipe_in.u.dl.comp_ring_base_pa,
+		  "comp_ring_size",
+		  pipe_in.u.dl.comp_ring_size,
+		  "ce_ring_base_pa",
+		  (unsigned int)pipe_in.u.dl.ce_ring_base_pa,
+		  "ce_door_bell_pa",
+		  (unsigned int)pipe_in.u.dl.ce_door_bell_pa,
+		  "ce_ring_size",
+		  pipe_in.u.dl.ce_ring_size,
+		  "num_tx_buffers",
+		  pipe_in.u.dl.num_tx_buffers,
+		  "tx_comp_doorbell_paddr",
+		  (unsigned int)ipa_ctxt->tx_comp_doorbell_paddr);
 	}
 
 	/* RX PIPE */
@@ -2727,6 +3093,8 @@
 		ipa_ctxt->ipa_resource.rx_proc_done_idx_paddr;
 	HDD_IPA_WDI2_SET(pipe_in, ipa_ctxt);
 
+	hdd_ipa_init_metering(ipa_ctxt, &pipe_in);
+
 	qdf_mem_copy(&ipa_ctxt->prod_pipe_in, &pipe_in,
 		     sizeof(struct ipa_wdi_in_params));
 	hdd_ipa_uc_get_db_paddr(&ipa_ctxt->rx_ready_doorbell_paddr,
@@ -2749,10 +3117,14 @@
 			(unsigned int)pipe_out.uc_door_bell_pa,
 			ipa_ctxt->tx_pipe_handle);
 		HDD_IPA_LOG(QDF_TRACE_LEVEL_DEBUG,
-			"RX : RRBPA 0x%x, RRS %d, PDIPA 0x%x, RDY_DB_PAD 0x%x",
+			"RX: %s 0x%x, %s %d, %s 0x%x, %s 0x%x",
+			"rdy_ring_base_pa",
 			(unsigned int)pipe_in.u.ul.rdy_ring_base_pa,
+			"rdy_ring_size",
 			pipe_in.u.ul.rdy_ring_size,
+			"rdy_ring_rp_pa",
 			(unsigned int)pipe_in.u.ul.rdy_ring_rp_pa,
+			"rx_ready_doorbell_paddr",
 			(unsigned int)ipa_ctxt->rx_ready_doorbell_paddr);
 	}
 
@@ -3865,7 +4237,7 @@
 }
 
 /**
- * hdd_ipa_w2i_cb() - WLAN to IPA callback handler
+ * __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
diff --git a/core/wma/inc/wma.h b/core/wma/inc/wma.h
index 5bbb730..604ad78 100644
--- a/core/wma/inc/wma.h
+++ b/core/wma/inc/wma.h
@@ -1750,6 +1750,8 @@
  * @WMA_VDEV_IBSS_PS_SET_1RX_CHAIN_IN_ATIM_WINDOW: set IBSS power save ATIM
  * @WMA_VDEV_DFS_CONTROL_CMDID: DFS control command
  * @WMA_VDEV_TXRX_GET_IPA_UC_FW_STATS_CMDID: get IPA microcontroller fw stats
+ * @WMA_VDEV_TXRX_GET_IPA_UC_SHARING_STATS_CMDID: get IPA uC wifi-sharing stats
+ * @WMA_VDEV_TXRX_SET_IPA_UC_QUOTA_CMDID: set IPA uC quota limit
  *
  * wma command ids for configuration request which
  * does not involve sending a wmi command.
@@ -1769,6 +1771,8 @@
 	WMA_VDEV_IBSS_PS_SET_1RX_CHAIN_IN_ATIM_WINDOW,
 	WMA_VDEV_DFS_CONTROL_CMDID,
 	WMA_VDEV_TXRX_GET_IPA_UC_FW_STATS_CMDID,
+	WMA_VDEV_TXRX_GET_IPA_UC_SHARING_STATS_CMDID,
+	WMA_VDEV_TXRX_SET_IPA_UC_QUOTA_CMDID,
 	WMA_CMD_ID_MAX
 };
 
diff --git a/core/wma/src/wma_main.c b/core/wma/src/wma_main.c
index 4d3b9db..5eae24e 100644
--- a/core/wma/src/wma_main.c
+++ b/core/wma/src/wma_main.c
@@ -786,6 +786,7 @@
 
 	case WMA_VDEV_TXRX_GET_IPA_UC_FW_STATS_CMDID:
 	{
+		void *soc = cds_get_context(QDF_MODULE_ID_SOC);
 		struct cdp_pdev *pdev;
 
 		pdev = cds_get_context(QDF_MODULE_ID_TXRX);
@@ -793,8 +794,43 @@
 			WMA_LOGE("pdev NULL for uc stat");
 			return -EINVAL;
 		}
-		cdp_ipa_get_stat(cds_get_context(QDF_MODULE_ID_SOC),
-			pdev);
+		cdp_ipa_get_stat(soc, pdev);
+	}
+		break;
+
+	case WMA_VDEV_TXRX_GET_IPA_UC_SHARING_STATS_CMDID:
+	{
+		void *soc = cds_get_context(QDF_MODULE_ID_SOC);
+		struct cdp_pdev *pdev;
+		uint8_t reset_stats = privcmd->param_value;
+
+		WMA_LOGE("%s: reset_stats=%d",
+			 "WMA_VDEV_TXRX_GET_IPA_UC_SHARING_STATS_CMDID",
+			 reset_stats);
+		pdev = cds_get_context(QDF_MODULE_ID_TXRX);
+		if (!pdev) {
+			WMA_LOGE("pdev NULL for uc stat");
+			return -EINVAL;
+		}
+		cdp_ipa_uc_get_share_stats(soc, pdev, reset_stats);
+	}
+		break;
+
+	case WMA_VDEV_TXRX_SET_IPA_UC_QUOTA_CMDID:
+	{
+		void *soc = cds_get_context(QDF_MODULE_ID_SOC);
+		struct cdp_pdev *pdev;
+		uint64_t quota_bytes = privcmd->param_value;
+
+		WMA_LOGE("%s: quota_bytes=%llu",
+			 "WMA_VDEV_TXRX_SET_IPA_UC_QUOTA_CMDID",
+			 quota_bytes);
+		pdev = cds_get_context(QDF_MODULE_ID_TXRX);
+		if (!pdev) {
+			WMA_LOGE("pdev NULL for uc stat");
+			return -EINVAL;
+		}
+		cdp_ipa_uc_set_quota(soc, pdev, quota_bytes);
 	}
 		break;