qcacmn: Add a new feature to support tagging of IPv4/v6 flows

Tags are programmed using wlanconfig commands. Rx IPv4/v6
TCP/UDP packets matching a 5-tuple are tagged using HawkeyeV2 hardware.
Tags are populated in the skb->cb in the REO/exception/monitor data
path and sent to upper stack

CRs-Fixed: 2502311
Change-Id: I7c999e75fab43b6ecb6f9d9fd4b0351f0b9cfda8
diff --git a/dp/inc/cdp_txrx_cmn_struct.h b/dp/inc/cdp_txrx_cmn_struct.h
index 5a9128d..2b117b1 100644
--- a/dp/inc/cdp_txrx_cmn_struct.h
+++ b/dp/inc/cdp_txrx_cmn_struct.h
@@ -1827,4 +1827,75 @@
 	uint8_t cookie;
 	struct cdp_stats_cookie *ctx;
 };
+
+/**
+ * cdp_flow_stats - Per-Flow (5-tuple) statistics
+ * @msdu_count: number of rx msdus matching this flow
+ *
+ * HW also includes msdu_byte_count and timestamp, which
+ * are not currently tracked in SW.
+ */
+struct cdp_flow_stats {
+	uint32_t msdu_count;
+};
+
+/**
+ * cdp_flow_fst_operation - RX FST operations allowed
+ */
+enum cdp_flow_fst_operation {
+	CDP_FLOW_FST_ENTRY_ADD,
+	CDP_FLOW_FST_ENTRY_DEL,
+	CDP_FLOW_FST_RX_BYPASS_ENABLE,
+	CDP_FLOW_FST_RX_BYPASS_DISABLE
+};
+
+/**
+ * cdp_flow_protocol_type - RX FST supported protocol types, mapped to HW spec
+ */
+enum cdp_flow_protocol_type {
+	CDP_FLOW_PROTOCOL_TYPE_TCP = 6,
+	CDP_FLOW_PROTOCOL_TYPE_UDP = 17,
+};
+
+/**
+ * cdp_rx_flow_tuple_info - RX flow tuple info used for addition/deletion
+ * @dest_ip_127_96: destination IP address bit fields 96-127
+ * @dest_ip_95_64: destination IP address bit fields 64-95
+ * @dest_ip_63_32: destination IP address bit fields 32-63
+ * @dest_ip_31_0: destination IP address bit fields 0-31
+ * @src_ip_127_96: source IP address bit fields 96-127
+ * @src_ip_95_64: source IP address bit fields 64-95
+ * @src_ip_63_32: source IP address bit fields 32-63
+ * @src_ip_31_0: source IP address bit fields 0-31
+ * @dest_port: destination port of flow
+ * @src_port: source port of flow
+ * @l4_protocol: protocol type in flow (TCP/UDP)
+ */
+struct cdp_rx_flow_tuple_info {
+	uint32_t dest_ip_127_96;
+	uint32_t dest_ip_95_64;
+	uint32_t dest_ip_63_32;
+	uint32_t dest_ip_31_0;
+	uint32_t src_ip_127_96;
+	uint32_t src_ip_95_64;
+	uint32_t src_ip_63_32;
+	uint32_t src_ip_31_0;
+	uint16_t dest_port;
+	uint16_t src_port;
+	uint16_t l4_protocol;
+};
+
+/**
+ * cdp_rx_flow_info - RX flow info used for addition/deletion
+ * @is_addr_ipv4: indicates whether given IP address is IPv4/IPv6
+ * @op_code: add/delete/enable/disable operation requested
+ * @flow_tupe_info: structure containing tuple info
+ * @fse_metadata: metadata to be set in RX flow
+ */
+struct cdp_rx_flow_info {
+	bool is_addr_ipv4;
+	enum cdp_flow_fst_operation op_code;
+	struct cdp_rx_flow_tuple_info flow_tuple_info;
+	uint16_t fse_metadata;
+};
 #endif
diff --git a/dp/inc/cdp_txrx_ctrl.h b/dp/inc/cdp_txrx_ctrl.h
index 36090e7..347c70a 100644
--- a/dp/inc/cdp_txrx_ctrl.h
+++ b/dp/inc/cdp_txrx_ctrl.h
@@ -844,4 +844,58 @@
 								rssi);
 }
 #endif
-#endif
+
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+/**
+ * cdp_set_rx_flow_tag() - wrapper function to set the flow
+ *                         tag in CDP layer from cfg layer
+ * @soc: SOC TXRX handle
+ * @pdev: CDP pdev pointer
+ * @flow_info: Flow 5-tuple, along with tag, if any, that needs to added/deleted
+ *
+ * Return: Success when add/del operation is successful, error otherwise
+ */
+static inline QDF_STATUS
+cdp_set_rx_flow_tag(ol_txrx_soc_handle soc, struct cdp_pdev *pdev,
+		    struct cdp_rx_flow_info *flow_info)
+{
+	if (!soc || !soc->ops) {
+		dp_err("Invalid SOC instance");
+		QDF_BUG(0);
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	if (!soc->ops->ctrl_ops ||
+	    !soc->ops->ctrl_ops->txrx_set_rx_flow_tag)
+		return QDF_STATUS_E_FAILURE;
+
+	return soc->ops->ctrl_ops->txrx_set_rx_flow_tag(pdev, flow_info);
+}
+
+/**
+ * cdp_dump_rx_flow_tag_stats() - wrapper function to dump the flow
+ *                                tag statistics for given flow
+ * @soc: SOC TXRX handle
+ * @pdev: CDP pdev pointer
+ * @flow_info: Flow tuple for which we want to print the statistics
+ *
+ * Return: Success when flow is found and stats are printed, error otherwise
+ */
+static inline QDF_STATUS
+cdp_dump_rx_flow_tag_stats(ol_txrx_soc_handle soc, struct cdp_pdev *pdev,
+			   struct cdp_rx_flow_info *flow_info)
+{
+	if (!soc || !soc->ops) {
+		dp_err("Invalid SOC instance");
+		QDF_BUG(0);
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	if (!soc->ops->ctrl_ops ||
+	    !soc->ops->ctrl_ops->txrx_dump_rx_flow_tag_stats)
+		return QDF_STATUS_E_FAILURE;
+
+	return soc->ops->ctrl_ops->txrx_dump_rx_flow_tag_stats(pdev, flow_info);
+}
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+#endif /* _CDP_TXRX_CTRL_H_ */
diff --git a/dp/inc/cdp_txrx_ops.h b/dp/inc/cdp_txrx_ops.h
index 11faeb0..b15c76d 100644
--- a/dp/inc/cdp_txrx_ops.h
+++ b/dp/inc/cdp_txrx_ops.h
@@ -668,6 +668,14 @@
 				uint16_t protocol_type);
 #endif /* WLAN_SUPPORT_RX_TAG_STATISTICS */
 #endif /* WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG */
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+	QDF_STATUS (*txrx_set_rx_flow_tag)(
+		struct cdp_pdev *txrx_pdev_handle,
+		struct cdp_rx_flow_info *flow_info);
+	QDF_STATUS (*txrx_dump_rx_flow_tag_stats)(
+		struct cdp_pdev *txrx_pdev_handle,
+		struct cdp_rx_flow_info *flow_info);
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
 #ifdef QCA_MULTIPASS_SUPPORT
 	void (*txrx_peer_set_vlan_id)(ol_txrx_soc_handle soc,
 				      struct cdp_vdev *vdev, uint8_t *peer_mac,
diff --git a/dp/wifi3.0/dp_flow.c b/dp/wifi3.0/dp_flow.c
new file mode 100644
index 0000000..cdddd18
--- /dev/null
+++ b/dp/wifi3.0/dp_flow.c
@@ -0,0 +1,723 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+#include "dp_types.h"
+#include "qdf_mem.h"
+#include "qdf_nbuf.h"
+#include "cfg_dp.h"
+#include "wlan_cfg.h"
+#include "dp_types.h"
+#include "hal_rx_flow.h"
+#include "dp_htt.h"
+
+/**
+ * In Hawkeye, a hardware bug disallows SW to only clear a single flow entry
+ * when added/deleted by upper layer. Workaround is to clear entire cache,
+ * which can have a performance impact. Flow additions/deletions
+ * are bundled together over 100ms to save HW cycles if upper layer
+ * adds/deletes multiple flows together. Use a longer timeout during setup
+ * stage since no flows are anticipated at this time.
+ */
+#define HW_RX_FSE_CACHE_INVALIDATE_BUNDLE_PERIOD_MS (100)
+#define HW_RX_FSE_CACHE_INVALIDATE_DELAYED_FST_SETUP_MS (5000)
+
+/**
+ * dp_rx_flow_get_fse() - Obtain flow search entry from flow hash
+ * @fst: Rx FST Handle
+ * @flow_hash: Computed hash value of flow
+ *
+ * Return: Handle to flow search table entry
+ */
+static inline struct dp_rx_fse *
+dp_rx_flow_get_fse(struct dp_rx_fst *fst, uint32_t flow_hash)
+{
+	struct dp_rx_fse *fse;
+	uint32_t idx = hal_rx_get_hal_hash(fst->hal_rx_fst, flow_hash);
+
+	fse = (struct dp_rx_fse *)((uint8_t *)fst->base + (idx *
+					sizeof(struct dp_rx_fse)));
+
+	return fse;
+}
+
+/**
+ * dp_rx_flow_dump_flow_entry() - Print flow search entry from 5-tuple
+ * @fst: Rx FST Handle
+ * @flow_info: Flow 5-tuple
+ *
+ * Return: None
+ */
+void dp_rx_flow_dump_flow_entry(struct dp_rx_fst *fst,
+				struct cdp_rx_flow_info *flow_info)
+{
+	dp_info("Dest IP address %x:%x:%x:%x",
+		flow_info->flow_tuple_info.dest_ip_127_96,
+		flow_info->flow_tuple_info.dest_ip_95_64,
+		flow_info->flow_tuple_info.dest_ip_63_32,
+		flow_info->flow_tuple_info.dest_ip_31_0);
+	dp_info("Source IP address %x:%x:%x:%x",
+		flow_info->flow_tuple_info.src_ip_127_96,
+		flow_info->flow_tuple_info.src_ip_95_64,
+		flow_info->flow_tuple_info.src_ip_63_32,
+		flow_info->flow_tuple_info.src_ip_31_0);
+	dp_info("Dest port %u, Src Port %u, Protocol %u",
+		flow_info->flow_tuple_info.dest_port,
+		flow_info->flow_tuple_info.src_port,
+		flow_info->flow_tuple_info.l4_protocol);
+}
+
+/**
+ * dp_rx_flow_compute_flow_hash() - Print flow search entry from 5-tuple
+ * @fst: Rx FST Handle
+ * @rx_flow_info: DP Rx Flow 5-tuple programmed by upper layer
+ * @flow: HAL (HW) flow entry
+ *
+ * Return: Computed Toeplitz hash
+ */
+uint32_t dp_rx_flow_compute_flow_hash(struct dp_rx_fst *fst,
+				      struct cdp_rx_flow_info *rx_flow_info,
+				      struct hal_rx_flow *flow)
+{
+	flow->tuple_info.dest_ip_127_96 =
+			rx_flow_info->flow_tuple_info.dest_ip_127_96;
+	flow->tuple_info.dest_ip_95_64 =
+			rx_flow_info->flow_tuple_info.dest_ip_95_64;
+	flow->tuple_info.dest_ip_63_32 =
+			rx_flow_info->flow_tuple_info.dest_ip_63_32;
+	flow->tuple_info.dest_ip_31_0 =
+			rx_flow_info->flow_tuple_info.dest_ip_31_0;
+	flow->tuple_info.src_ip_127_96 =
+			rx_flow_info->flow_tuple_info.src_ip_127_96;
+	flow->tuple_info.src_ip_95_64 =
+			rx_flow_info->flow_tuple_info.src_ip_95_64;
+	flow->tuple_info.src_ip_63_32 =
+			rx_flow_info->flow_tuple_info.src_ip_63_32;
+	flow->tuple_info.src_ip_31_0 =
+			rx_flow_info->flow_tuple_info.src_ip_31_0;
+	flow->tuple_info.dest_port =
+			rx_flow_info->flow_tuple_info.dest_port;
+	flow->tuple_info.src_port =
+			rx_flow_info->flow_tuple_info.src_port;
+	flow->tuple_info.l4_protocol =
+			rx_flow_info->flow_tuple_info.l4_protocol;
+
+	return hal_flow_toeplitz_hash(fst->hal_rx_fst, flow);
+}
+
+/**
+ * dp_rx_flow_alloc_entry() - Create DP and HAL flow entries in FST
+ * @fst: Rx FST Handle
+ * @rx_flow_info: DP Rx Flow 5-tuple to be added to DP FST
+ * @flow: HAL (HW) flow entry that is created
+ *
+ * Return: Computed Toeplitz hash
+ */
+struct dp_rx_fse *dp_rx_flow_alloc_entry(struct dp_rx_fst *fst,
+					 struct cdp_rx_flow_info *rx_flow_info,
+					 struct hal_rx_flow *flow)
+{
+	struct dp_rx_fse *fse = NULL;
+	uint32_t flow_hash;
+	uint32_t flow_idx;
+	QDF_STATUS status;
+
+	flow_hash = dp_rx_flow_compute_flow_hash(fst, rx_flow_info, flow);
+
+	status = hal_rx_insert_flow_entry(fst->hal_rx_fst,
+					  flow_hash,
+					  &rx_flow_info->flow_tuple_info,
+					  &flow_idx);
+	if (status != QDF_STATUS_SUCCESS) {
+		dp_err("Add entry failed with status %d for tuple with hash %u",
+		       status, flow_hash);
+		return NULL;
+	}
+
+	fse = dp_rx_flow_get_fse(fst, flow_idx);
+	fse->is_ipv4_addr_entry = rx_flow_info->is_addr_ipv4;
+	fse->flow_hash = flow_hash;
+	fse->flow_id = flow_idx;
+	fse->stats.msdu_count = 0;
+	fse->is_valid = true;
+
+	return fse;
+}
+
+/**
+ * dp_rx_flow_find_entry_by_tuple() - Find the DP FSE matching a given 5-tuple
+ * @fst: Rx FST Handle
+ * @rx_flow_info: DP Rx Flow 5-tuple
+ * @flow: Pointer to the  HAL (HW) flow entry
+ *
+ * Return: Pointer to the DP FSE entry
+ */
+struct dp_rx_fse *
+dp_rx_flow_find_entry_by_tuple(struct dp_rx_fst *fst,
+			       struct cdp_rx_flow_info *rx_flow_info,
+			       struct hal_rx_flow *flow)
+{
+	uint32_t flow_hash;
+	uint32_t flow_idx;
+	QDF_STATUS status;
+
+	flow_hash = dp_rx_flow_compute_flow_hash(fst, rx_flow_info, flow);
+
+	status = hal_rx_find_flow_from_tuple(fst->hal_rx_fst,
+					     flow_hash,
+					     &rx_flow_info->flow_tuple_info,
+					     &flow_idx);
+	if (status != QDF_STATUS_SUCCESS) {
+		dp_err("Could not find tuple with hash %u", flow_hash);
+		dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+		return NULL;
+	}
+	return dp_rx_flow_get_fse(fst, flow_idx);
+}
+
+/**
+ * dp_rx_flow_find_entry_by_flowid() - Find DP FSE matching a given flow index
+ * @fst: Rx FST Handle
+ * @flow_id: Flow index of the requested flow
+ *
+ * Return: Pointer to the DP FSE entry
+ */
+struct dp_rx_fse *
+dp_rx_flow_find_entry_by_flowid(struct dp_rx_fst *fst,
+				uint32_t flow_id)
+{
+	struct dp_rx_fse *fse = NULL;
+
+	fse = dp_rx_flow_get_fse(fst, flow_id);
+	if (!fse->is_valid)
+		return NULL;
+
+	dp_info("flow_idx= %d, flow_addr = %pK", flow_id, fse);
+	qdf_assert_always(fse->flow_id == flow_id);
+
+	return fse;
+}
+
+/**
+ * dp_rx_flow_send_htt_operation_cmd() - Send HTT FSE command to FW for flow
+ *					 addition/removal
+ * @pdev: Pdev instance
+ * @op: Add/delete operation
+ * @info: DP Flow parameters of the flow added/deleted
+ *
+ * Return: Success on sending HTT command to FW, error on failure
+ */
+QDF_STATUS dp_rx_flow_send_htt_operation_cmd(struct dp_pdev *pdev,
+					     enum dp_htt_flow_fst_operation op,
+					     struct cdp_rx_flow_info *info)
+{
+	struct dp_htt_rx_flow_fst_operation fst_op;
+	struct wlan_cfg_dp_soc_ctxt *cfg = pdev->soc->wlan_cfg_ctx;
+
+	qdf_mem_set(&fst_op, 0, sizeof(struct dp_htt_rx_flow_fst_operation));
+
+	if (qdf_unlikely(wlan_cfg_is_rx_flow_search_table_per_pdev(cfg))) {
+		/* Firmware pdev ID starts from 1 */
+		fst_op.pdev_id = DP_SW2HW_MACID(pdev->pdev_id);
+	} else {
+		fst_op.pdev_id = 0;
+	}
+
+	fst_op.op_code = op;
+	fst_op.rx_flow = info;
+
+	return dp_htt_rx_flow_fse_operation(pdev, &fst_op);
+}
+
+/**
+ * dp_rx_flow_add_entry() - Add a flow entry to flow search table
+ * @pdev: DP pdev instance
+ * @rx_flow_info: DP flow paramaters
+ *
+ * Return: Success when flow is added, no-memory or already exists on error
+ */
+QDF_STATUS dp_rx_flow_add_entry(struct dp_pdev *pdev,
+				struct cdp_rx_flow_info *rx_flow_info)
+{
+	struct hal_rx_flow flow = { 0 };
+	struct dp_rx_fse *fse;
+	struct dp_soc *soc = pdev->soc;
+	struct dp_rx_fst *fst;
+
+	fst = pdev->rx_fst;
+
+	/* Initialize unused bits in IPv6 address for IPv4 address */
+	if (rx_flow_info->is_addr_ipv4) {
+		rx_flow_info->flow_tuple_info.dest_ip_63_32 = 0;
+		rx_flow_info->flow_tuple_info.dest_ip_95_64 = 0;
+		rx_flow_info->flow_tuple_info.dest_ip_127_96 =
+			HAL_IP_DA_SA_PREFIX_IPV4_COMPATIBLE_IPV6;
+
+		rx_flow_info->flow_tuple_info.src_ip_63_32 = 0;
+		rx_flow_info->flow_tuple_info.src_ip_95_64 = 0;
+		rx_flow_info->flow_tuple_info.src_ip_127_96 =
+			HAL_IP_DA_SA_PREFIX_IPV4_COMPATIBLE_IPV6;
+	}
+
+	/* Allocate entry in DP FST */
+	fse = dp_rx_flow_alloc_entry(fst, rx_flow_info, &flow);
+	if (NULL == fse) {
+		dp_err("RX FSE alloc failed");
+		dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+		return QDF_STATUS_E_NOMEM;
+	}
+	dp_info("flow_addr = %pK, flow_id = %u, valid = %d, v4 = %d\n",
+		fse, fse->flow_id, fse->is_valid, fse->is_ipv4_addr_entry);
+
+	/* Initialize other parameters for HW flow & populate HW FSE entry */
+	flow.reo_destination_indication = (fse->flow_hash &
+				HAL_REO_DEST_IND_HASH_MASK);
+
+	/**
+	 * Reo destination of each flow is mapped to match the same used
+	 * by RX Hash algorithm. If RX Hash is disabled, then the REO
+	 * destination below is directly got from pdev, rather than using
+	 * dp_peer_setup_get_reo_hash since we do not have vdev handle here.
+	 */
+	if (wlan_cfg_is_rx_hash_enabled(soc->wlan_cfg_ctx)) {
+		flow.reo_destination_indication |=
+			HAL_REO_DEST_IND_START_OFFSET;
+	} else {
+		flow.reo_destination_indication = pdev->reo_dest;
+	}
+
+	flow.reo_destination_handler = HAL_RX_FSE_REO_DEST_FT;
+	flow.fse_metadata = rx_flow_info->fse_metadata;
+	fse->hal_rx_fse = hal_rx_flow_setup_fse(fst->hal_rx_fst,
+						fse->flow_id, &flow);
+	if (qdf_unlikely(!fse->hal_rx_fse)) {
+		dp_err("Unable to alloc FSE entry");
+		dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+		/* Free up the FSE entry as returning failure */
+		fse->is_valid = false;
+		return QDF_STATUS_E_EXISTS;
+	}
+
+	/* Increment number of valid entries in table */
+	fst->num_entries++;
+	dp_info("FST num_entries = %d, reo_dest_ind = %d, reo_dest_hand = %u",
+		fst->num_entries, flow.reo_destination_indication,
+		flow.reo_destination_handler);
+
+	if (soc->is_rx_fse_full_cache_invalidate_war_enabled) {
+		qdf_atomic_set(&fst->is_cache_update_pending, 1);
+	} else {
+		QDF_STATUS status;
+		/**
+		 * Send HTT cache invalidation command to firmware to
+		 * reflect the added flow
+		 */
+		status = dp_rx_flow_send_htt_operation_cmd(
+					pdev,
+					DP_HTT_FST_CACHE_INVALIDATE_ENTRY,
+					rx_flow_info);
+
+		if (QDF_STATUS_SUCCESS != status) {
+			dp_err("Send cache invalidate entry to fw failed: %u",
+			       status);
+			dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+			/* Free DP FSE and HAL FSE */
+			hal_rx_flow_delete_entry(fst->hal_rx_fst,
+						 fse->hal_rx_fse);
+			fse->is_valid = false;
+			return status;
+		}
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * dp_rx_flow_delete_entry() - Delete a flow entry from flow search table
+ * @pdev: pdev handle
+ * @rx_flow_info: DP flow parameters
+ *
+ * Return: Success when flow is deleted, error on failure
+ */
+QDF_STATUS dp_rx_flow_delete_entry(struct dp_pdev *pdev,
+				   struct cdp_rx_flow_info *rx_flow_info)
+{
+	struct hal_rx_flow flow = { 0 };
+	struct dp_rx_fse *fse;
+	struct dp_soc *soc = pdev->soc;
+	struct dp_rx_fst *fst;
+	QDF_STATUS status;
+
+	fst = pdev->rx_fst;
+
+	/* Find the given flow entry DP FST */
+	fse = dp_rx_flow_find_entry_by_tuple(fst, rx_flow_info, &flow);
+	if (!fse) {
+		dp_err("RX flow delete entry failed");
+		dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	/* Delete the FSE in HW FST */
+	status = hal_rx_flow_delete_entry(fst->hal_rx_fst, fse->hal_rx_fse);
+	qdf_assert_always(status == QDF_STATUS_SUCCESS);
+
+	/* Free the FSE in DP FST */
+	fse->is_valid = false;
+
+	/* Decrement number of valid entries in table */
+	fst->num_entries--;
+
+	if (soc->is_rx_fse_full_cache_invalidate_war_enabled) {
+		qdf_atomic_set(&fst->is_cache_update_pending, 1);
+	} else {
+		/**
+		 * Send HTT cache invalidation command to firmware
+		 * to reflect the deleted flow
+		 */
+		status = dp_rx_flow_send_htt_operation_cmd(
+					pdev,
+					DP_HTT_FST_CACHE_INVALIDATE_ENTRY,
+					rx_flow_info);
+
+		if (QDF_STATUS_SUCCESS != status) {
+			dp_err("Send cache invalidate entry to fw failed: %u",
+			       status);
+			dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+			/* Do not add entry back in DP FSE and HAL FSE */
+			return status;
+		}
+	}
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/* dp_rx_flow_update_fse_stats() - Update a flow's statistics
+ * @pdev: pdev handle
+ * @flow_id: flow index (truncated hash) in the Rx FST
+ *
+ * Return: Success when flow statistcs is updated, error on failure
+ */
+QDF_STATUS dp_rx_flow_update_fse_stats(struct dp_pdev *pdev, uint32_t flow_id)
+{
+	struct dp_rx_fse *fse;
+
+	fse = dp_rx_flow_find_entry_by_flowid(pdev->rx_fst, flow_id);
+
+	if (NULL == fse) {
+		dp_err("Flow not found, flow ID %u", flow_id);
+		return QDF_STATUS_E_NOENT;
+	}
+
+	fse->stats.msdu_count += 1;
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * dp_rx_flow_get_fse_stats() - Fetch a flow's stats based on DP flow parameter
+ * @pdev: pdev handle
+ * @rx_flow_info: Pointer to the DP flow struct of the requested flow
+ * @stats: Matching flow's stats returned to caller
+ *
+ * Return: Success when flow statistcs is updated, error on failure
+ */
+QDF_STATUS dp_rx_flow_get_fse_stats(struct dp_pdev *pdev,
+				    struct cdp_rx_flow_info *rx_flow_info,
+				    struct cdp_flow_stats *stats)
+{
+	struct dp_rx_fst *fst;
+	struct dp_rx_fse *fse;
+	struct hal_rx_flow flow;
+
+	fst = pdev->rx_fst;
+
+	/* Find the given flow entry DP FST */
+	fse = dp_rx_flow_find_entry_by_tuple(fst, rx_flow_info, &flow);
+	if (!fse) {
+		dp_err("RX flow entry search failed");
+		dp_rx_flow_dump_flow_entry(fst, rx_flow_info);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	stats->msdu_count = fse->stats.msdu_count;
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * dp_rx_flow_cache_invalidate_timer_handler() - Timer handler used for bundling
+ * flows before invalidating entire cache
+ * @ctx: Pdev handle
+ *
+ * Return: None
+ */
+void dp_rx_flow_cache_invalidate_timer_handler(void *ctx)
+{
+	struct dp_pdev *pdev = (struct dp_pdev *)ctx;
+	struct dp_rx_fst *fst;
+	bool is_update_pending;
+	QDF_STATUS status;
+
+	fst = pdev->rx_fst;
+
+	qdf_assert_always(fst);
+	is_update_pending = qdf_atomic_read(&fst->is_cache_update_pending);
+	qdf_atomic_set(&fst->is_cache_update_pending, 0);
+
+	if (is_update_pending) {
+		/* Send full cache invalidate command to firmware */
+		status = dp_rx_flow_send_htt_operation_cmd(
+				pdev,
+				DP_HTT_FST_CACHE_INVALIDATE_FULL,
+				NULL);
+
+		if (QDF_STATUS_SUCCESS != status)
+			dp_err("Send full cache inv to fw failed: %u", status);
+	}
+
+	qdf_timer_start(&fst->cache_invalidate_timer,
+			HW_RX_FSE_CACHE_INVALIDATE_BUNDLE_PERIOD_MS);
+}
+
+/**
+ * dp_rx_fst_attach() - Initialize Rx FST and setup necessary parameters
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: Handle to flow search table entry
+ */
+QDF_STATUS dp_rx_fst_attach(struct dp_soc *soc, struct dp_pdev *pdev)
+{
+	struct dp_rx_fst *fst;
+	uint8_t *hash_key;
+	struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx;
+	bool is_rx_flow_search_table_per_pdev =
+		wlan_cfg_is_rx_flow_search_table_per_pdev(cfg);
+
+	if (qdf_unlikely(!wlan_cfg_is_rx_flow_tag_enabled(cfg))) {
+		dp_err("RX Flow tag feature disabled");
+		return QDF_STATUS_E_NOSUPPORT;
+	}
+
+	if (!wlan_psoc_nif_fw_ext_cap_get((void *)pdev->ctrl_pdev,
+					  WLAN_SOC_CEXT_RX_FSE_SUPPORT)) {
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "rx fse disabled in FW\n");
+		wlan_cfg_set_rx_flow_tag_enabled(cfg, false);
+		return QDF_STATUS_E_NOSUPPORT;
+	}
+	/**
+	 * Func. is called for every pdev. If FST is per SOC, then return
+	 * if it was already called once.
+	 */
+	if (!is_rx_flow_search_table_per_pdev && soc->rx_fst) {
+		pdev->rx_fst = soc->rx_fst;
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "RX FST for SoC is already initialized");
+		return QDF_STATUS_SUCCESS;
+	}
+
+	/**
+	 * Func. is called for this pdev already. This is an error.
+	 * Return failure
+	 */
+	if (is_rx_flow_search_table_per_pdev && pdev->rx_fst) {
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "RX FST for PDEV %u is already initialized",
+			  pdev->pdev_id);
+		return QDF_STATUS_E_EXISTS;
+	}
+
+	fst = qdf_mem_malloc(sizeof(struct dp_rx_fst));
+	if (!fst) {
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "RX FST allocation failed\n");
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	qdf_mem_set(fst, 0, sizeof(struct dp_rx_fst));
+
+	fst->max_skid_length = wlan_cfg_rx_fst_get_max_search(cfg);
+	fst->max_entries = wlan_cfg_get_rx_flow_search_table_size(cfg);
+	hash_key = wlan_cfg_rx_fst_get_hash_key(cfg);
+
+	if (!(fst->max_entries &&
+	      (!(fst->max_entries & (fst->max_entries - 1))))) {
+		uint32_t next_power_of_2 = fst->max_entries - 1;
+
+		next_power_of_2 |= (next_power_of_2 >> 1);
+		next_power_of_2 |= (next_power_of_2 >> 2);
+		next_power_of_2 |= (next_power_of_2 >> 4);
+		next_power_of_2 |= (next_power_of_2 >> 8);
+		next_power_of_2 |= (next_power_of_2 >> 16);
+		next_power_of_2++;
+		if (next_power_of_2 > WLAN_CFG_RX_FLOW_SEARCH_TABLE_SIZE_MAX)
+			next_power_of_2 =
+				 WLAN_CFG_RX_FLOW_SEARCH_TABLE_SIZE_MAX;
+		dp_info("Num entries in cfg is not a ^2:%u, using next ^2:%u",
+			fst->max_entries, next_power_of_2);
+		fst->max_entries = next_power_of_2;
+	}
+	fst->hash_mask = fst->max_entries - 1;
+	fst->num_entries = 0;
+
+	fst->base = (uint8_t *) qdf_mem_malloc(sizeof(struct dp_rx_fse) *
+					       fst->max_entries);
+
+	if (!fst->base) {
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "Rx fst->base allocation failed, #entries:%d\n",
+			  fst->max_entries);
+
+		qdf_mem_free(fst);
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	qdf_mem_set((uint8_t *)fst->base, 0,
+		    (sizeof(struct dp_rx_fse) * fst->max_entries));
+
+	fst->hal_rx_fst = hal_rx_fst_attach(
+				soc->osdev,
+				&fst->hal_rx_fst_base_paddr,
+				fst->max_entries,
+				fst->max_skid_length,
+				hash_key);
+
+	if (qdf_unlikely(!fst->hal_rx_fst)) {
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "Rx Hal fst allocation failed, #entries:%d\n",
+			  fst->max_entries);
+		qdf_mem_free(fst->base);
+		qdf_mem_free(fst);
+		return QDF_STATUS_E_NOMEM;
+	}
+	if (!is_rx_flow_search_table_per_pdev)
+		soc->rx_fst = fst;
+
+	pdev->rx_fst = fst;
+
+	if (soc->is_rx_fse_full_cache_invalidate_war_enabled) {
+		QDF_STATUS status;
+
+		status = qdf_timer_init(
+				soc->osdev,
+				&fst->cache_invalidate_timer,
+				dp_rx_flow_cache_invalidate_timer_handler,
+				(void *)pdev,
+				QDF_TIMER_TYPE_SW);
+
+		qdf_assert_always(status == QDF_STATUS_SUCCESS);
+
+		/* Start the timer */
+		qdf_timer_start(
+			&fst->cache_invalidate_timer,
+			HW_RX_FSE_CACHE_INVALIDATE_DELAYED_FST_SETUP_MS);
+
+		qdf_atomic_set(&fst->is_cache_update_pending, false);
+	}
+
+	QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_INFO,
+		  "Rx FST attach successful, #entries:%d\n",
+		  fst->max_entries);
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * dp_rx_fst_detach() - De-initialize Rx FST
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: None
+ */
+void dp_rx_fst_detach(struct dp_soc *soc, struct dp_pdev *pdev)
+{
+	struct dp_rx_fst *dp_fst;
+	struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx;
+
+	if (qdf_unlikely(wlan_cfg_is_rx_flow_search_table_per_pdev(cfg))) {
+		dp_fst = pdev->rx_fst;
+		pdev->rx_fst = NULL;
+	} else {
+		dp_fst = soc->rx_fst;
+		soc->rx_fst = NULL;
+	}
+
+	if (qdf_likely(dp_fst)) {
+		hal_rx_fst_detach(dp_fst->hal_rx_fst, soc->osdev);
+		if (soc->is_rx_fse_full_cache_invalidate_war_enabled) {
+			qdf_timer_sync_cancel(&dp_fst->cache_invalidate_timer);
+			qdf_timer_stop(&dp_fst->cache_invalidate_timer);
+			qdf_timer_free(&dp_fst->cache_invalidate_timer);
+		}
+		qdf_mem_free(dp_fst->base);
+		qdf_mem_free(dp_fst);
+	}
+	QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_DEBUG,
+		  "Rx FST detached for pdev %u\n", pdev->pdev_id);
+}
+
+/**
+ * dp_rx_flow_send_fst_fw_setup() - Program FST parameters in FW/HW post-attach
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: Success when fst parameters are programmed in FW, error otherwise
+ */
+QDF_STATUS dp_rx_flow_send_fst_fw_setup(struct dp_soc *soc,
+					struct dp_pdev *pdev)
+{
+	struct dp_htt_rx_flow_fst_setup fst_setup;
+	struct dp_rx_fst *fst;
+	QDF_STATUS status;
+	struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx;
+
+	if (qdf_unlikely(!wlan_cfg_is_rx_flow_tag_enabled(cfg)))
+		return QDF_STATUS_SUCCESS;
+
+	qdf_mem_set(&fst_setup, 0, sizeof(struct dp_htt_rx_flow_fst_setup));
+
+	if (qdf_unlikely(wlan_cfg_is_rx_flow_search_table_per_pdev(cfg))) {
+		/* Firmware pdev ID starts from 1 */
+		fst_setup.pdev_id = DP_SW2HW_MACID(pdev->pdev_id);
+		fst = pdev->rx_fst;
+	} else {
+		fst_setup.pdev_id = 0;
+		fst = soc->rx_fst;
+	}
+	fst_setup.max_entries = fst->max_entries;
+	fst_setup.max_search = fst->max_skid_length;
+	fst_setup.base_addr_lo = (uint32_t)fst->hal_rx_fst_base_paddr;
+	fst_setup.base_addr_hi =
+		(uint32_t)((uint64_t)fst->hal_rx_fst_base_paddr >> 32);
+	fst_setup.ip_da_sa_prefix =
+		HAL_FST_IP_DA_SA_PFX_TYPE_IPV4_COMPATIBLE_IPV6;
+	fst_setup.hash_key =  wlan_cfg_rx_fst_get_hash_key(cfg);
+	fst_setup.hash_key_len = HAL_FST_HASH_KEY_SIZE_BYTES;
+
+	status = dp_htt_rx_flow_fst_setup(pdev, &fst_setup);
+	if (status == QDF_STATUS_SUCCESS) {
+		fst->fse_setup_done = true;
+		return status;
+	}
+	QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+		  "Failed to send Rx FSE Setup pdev%d status %d\n",
+		  pdev->pdev_id, status);
+	/* Free all the memory allocations and data structures */
+	dp_rx_fst_detach(pdev->soc, pdev);
+	return status;
+}
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
diff --git a/dp/wifi3.0/dp_htt.c b/dp/wifi3.0/dp_htt.c
index 7a9f8be..c10607d 100644
--- a/dp/wifi3.0/dp_htt.c
+++ b/dp/wifi3.0/dp_htt.c
@@ -4160,3 +4160,277 @@
 		qdf_err("Invalid tag_type");
 	}
 }
+
+/**
+ * dp_htt_rx_flow_fst_setup(): Send HTT Rx FST setup message to FW
+ * @pdev: DP pdev handle
+ * @fse_setup_info: FST setup parameters
+ *
+ * Return: Success when HTT message is sent, error on failure
+ */
+QDF_STATUS
+dp_htt_rx_flow_fst_setup(struct dp_pdev *pdev,
+			 struct dp_htt_rx_flow_fst_setup *fse_setup_info)
+{
+	struct htt_soc *soc = pdev->soc->htt_handle;
+	struct dp_htt_htc_pkt *pkt;
+	qdf_nbuf_t msg;
+	u_int32_t *msg_word;
+	struct htt_h2t_msg_rx_fse_setup_t *fse_setup;
+	uint8_t *htt_logger_bufp;
+	u_int32_t *key;
+
+	msg = qdf_nbuf_alloc(
+		soc->osdev,
+		HTT_MSG_BUF_SIZE(sizeof(struct htt_h2t_msg_rx_fse_setup_t)),
+		/* reserve room for the HTC header */
+		HTC_HEADER_LEN + HTC_HDR_ALIGNMENT_PADDING, 4, TRUE);
+
+	if (!msg)
+		return QDF_STATUS_E_NOMEM;
+
+	/*
+	 * Set the length of the message.
+	 * The contribution from the HTC_HDR_ALIGNMENT_PADDING is added
+	 * separately during the below call to qdf_nbuf_push_head.
+	 * The contribution from the HTC header is added separately inside HTC.
+	 */
+	if (!qdf_nbuf_put_tail(msg,
+			       sizeof(struct htt_h2t_msg_rx_fse_setup_t))) {
+		qdf_err("Failed to expand head for HTT RX_FSE_SETUP msg");
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	/* fill in the message contents */
+	msg_word = (u_int32_t *)qdf_nbuf_data(msg);
+
+	memset(msg_word, 0, sizeof(struct htt_h2t_msg_rx_fse_setup_t));
+	/* rewind beyond alignment pad to get to the HTC header reserved area */
+	qdf_nbuf_push_head(msg, HTC_HDR_ALIGNMENT_PADDING);
+	htt_logger_bufp = (uint8_t *)msg_word;
+
+	*msg_word = 0;
+	HTT_H2T_MSG_TYPE_SET(*msg_word, HTT_H2T_MSG_TYPE_RX_FSE_SETUP_CFG);
+
+	fse_setup = (struct htt_h2t_msg_rx_fse_setup_t *)msg_word;
+
+	HTT_RX_FSE_SETUP_PDEV_ID_SET(*msg_word, fse_setup_info->pdev_id);
+
+	msg_word++;
+	HTT_RX_FSE_SETUP_NUM_REC_SET(*msg_word, fse_setup_info->max_entries);
+	HTT_RX_FSE_SETUP_MAX_SEARCH_SET(*msg_word, fse_setup_info->max_search);
+	HTT_RX_FSE_SETUP_IP_DA_SA_PREFIX_SET(*msg_word,
+					     fse_setup_info->ip_da_sa_prefix);
+
+	msg_word++;
+	HTT_RX_FSE_SETUP_BASE_ADDR_LO_SET(*msg_word,
+					  fse_setup_info->base_addr_lo);
+	msg_word++;
+	HTT_RX_FSE_SETUP_BASE_ADDR_HI_SET(*msg_word,
+					  fse_setup_info->base_addr_hi);
+
+	key = (u_int32_t *)fse_setup_info->hash_key;
+	fse_setup->toeplitz31_0 = *key++;
+	fse_setup->toeplitz63_32 = *key++;
+	fse_setup->toeplitz95_64 = *key++;
+	fse_setup->toeplitz127_96 = *key++;
+	fse_setup->toeplitz159_128 = *key++;
+	fse_setup->toeplitz191_160 = *key++;
+	fse_setup->toeplitz223_192 = *key++;
+	fse_setup->toeplitz255_224 = *key++;
+	fse_setup->toeplitz287_256 = *key++;
+	fse_setup->toeplitz314_288 = *key;
+
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz31_0);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz63_32);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz95_64);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz127_96);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz159_128);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz191_160);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz223_192);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz255_224);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_VALUE_SET(*msg_word, fse_setup->toeplitz287_256);
+	msg_word++;
+	HTT_RX_FSE_SETUP_HASH_314_288_SET(*msg_word,
+					  fse_setup->toeplitz314_288);
+
+	pkt = htt_htc_pkt_alloc(soc);
+	if (!pkt) {
+		qdf_err("Fail to allocate dp_htt_htc_pkt buffer");
+		qdf_assert(0);
+		qdf_nbuf_free(msg);
+		return QDF_STATUS_E_RESOURCES; /* failure */
+	}
+
+	pkt->soc_ctxt = NULL; /* not used during send-done callback */
+
+	SET_HTC_PACKET_INFO_TX(
+		&pkt->htc_pkt,
+		dp_htt_h2t_send_complete_free_netbuf,
+		qdf_nbuf_data(msg),
+		qdf_nbuf_len(msg),
+		soc->htc_endpoint,
+		1); /* tag - not relevant here */
+
+	SET_HTC_PACKET_NET_BUF_CONTEXT(&pkt->htc_pkt, msg);
+
+	DP_HTT_SEND_HTC_PKT(soc, pkt, HTT_H2T_MSG_TYPE_RX_FSE_SETUP_CFG,
+			    htt_logger_bufp);
+
+	qdf_info("HTT_H2T RX_FSE_SETUP sent to FW for pdev = %u",
+		 fse_setup_info->pdev_id);
+	QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_DEBUG,
+			   (void *)fse_setup_info->hash_key,
+			   fse_setup_info->hash_key_len);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * dp_htt_rx_flow_fse_operation(): Send HTT Flow Search Entry msg to
+ * add/del a flow in HW
+ * @pdev: DP pdev handle
+ * @fse_op_info: Flow entry parameters
+ *
+ * Return: Success when HTT message is sent, error on failure
+ */
+QDF_STATUS
+dp_htt_rx_flow_fse_operation(struct dp_pdev *pdev,
+			     struct dp_htt_rx_flow_fst_operation *fse_op_info)
+{
+	struct htt_soc *soc = pdev->soc->htt_handle;
+	struct dp_htt_htc_pkt *pkt;
+	qdf_nbuf_t msg;
+	u_int32_t *msg_word;
+	struct htt_h2t_msg_rx_fse_operation_t *fse_operation;
+	uint8_t *htt_logger_bufp;
+
+	msg = qdf_nbuf_alloc(
+		soc->osdev,
+		HTT_MSG_BUF_SIZE(sizeof(struct htt_h2t_msg_rx_fse_operation_t)),
+		/* reserve room for the HTC header */
+		HTC_HEADER_LEN + HTC_HDR_ALIGNMENT_PADDING, 4, TRUE);
+	if (!msg)
+		return QDF_STATUS_E_NOMEM;
+
+	/*
+	 * Set the length of the message.
+	 * The contribution from the HTC_HDR_ALIGNMENT_PADDING is added
+	 * separately during the below call to qdf_nbuf_push_head.
+	 * The contribution from the HTC header is added separately inside HTC.
+	 */
+	if (!qdf_nbuf_put_tail(msg,
+			       sizeof(struct htt_h2t_msg_rx_fse_operation_t))) {
+		qdf_err("Failed to expand head for HTT_RX_FSE_OPERATION msg");
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	/* fill in the message contents */
+	msg_word = (u_int32_t *)qdf_nbuf_data(msg);
+
+	memset(msg_word, 0, sizeof(struct htt_h2t_msg_rx_fse_operation_t));
+	/* rewind beyond alignment pad to get to the HTC header reserved area */
+	qdf_nbuf_push_head(msg, HTC_HDR_ALIGNMENT_PADDING);
+	htt_logger_bufp = (uint8_t *)msg_word;
+
+	*msg_word = 0;
+	HTT_H2T_MSG_TYPE_SET(*msg_word, HTT_H2T_MSG_TYPE_RX_FSE_OPERATION_CFG);
+
+	fse_operation = (struct htt_h2t_msg_rx_fse_operation_t *)msg_word;
+
+	HTT_RX_FSE_OPERATION_PDEV_ID_SET(*msg_word, fse_op_info->pdev_id);
+	msg_word++;
+	HTT_RX_FSE_IPSEC_VALID_SET(*msg_word, false);
+	if (fse_op_info->op_code == DP_HTT_FST_CACHE_INVALIDATE_ENTRY) {
+		HTT_RX_FSE_OPERATION_SET(*msg_word,
+					 HTT_RX_FSE_CACHE_INVALIDATE_ENTRY);
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.src_ip_31_0));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.src_ip_63_32));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.src_ip_95_64));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.src_ip_127_96));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.dest_ip_31_0));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.dest_ip_63_32));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(fse_op_info->rx_flow->flow_tuple_info.dest_ip_95_64));
+		msg_word++;
+		HTT_RX_FSE_OPERATION_IP_ADDR_SET(
+		*msg_word,
+		qdf_htonl(
+		fse_op_info->rx_flow->flow_tuple_info.dest_ip_127_96));
+		msg_word++;
+		HTT_RX_FSE_SOURCEPORT_SET(
+			*msg_word,
+			fse_op_info->rx_flow->flow_tuple_info.src_port);
+		HTT_RX_FSE_DESTPORT_SET(
+			*msg_word,
+			fse_op_info->rx_flow->flow_tuple_info.dest_port);
+		msg_word++;
+		HTT_RX_FSE_L4_PROTO_SET(
+			*msg_word,
+			fse_op_info->rx_flow->flow_tuple_info.l4_protocol);
+	} else if (fse_op_info->op_code == DP_HTT_FST_CACHE_INVALIDATE_FULL) {
+		HTT_RX_FSE_OPERATION_SET(*msg_word,
+					 HTT_RX_FSE_CACHE_INVALIDATE_FULL);
+	} else if (fse_op_info->op_code == DP_HTT_FST_DISABLE) {
+		HTT_RX_FSE_OPERATION_SET(*msg_word, HTT_RX_FSE_DISABLE);
+	} else if (fse_op_info->op_code == DP_HTT_FST_ENABLE) {
+		HTT_RX_FSE_OPERATION_SET(*msg_word, HTT_RX_FSE_ENABLE);
+	}
+
+	pkt = htt_htc_pkt_alloc(soc);
+	if (!pkt) {
+		qdf_err("Fail to allocate dp_htt_htc_pkt buffer");
+		qdf_assert(0);
+		qdf_nbuf_free(msg);
+		return QDF_STATUS_E_RESOURCES; /* failure */
+	}
+
+	pkt->soc_ctxt = NULL; /* not used during send-done callback */
+
+	SET_HTC_PACKET_INFO_TX(
+		&pkt->htc_pkt,
+		dp_htt_h2t_send_complete_free_netbuf,
+		qdf_nbuf_data(msg),
+		qdf_nbuf_len(msg),
+		soc->htc_endpoint,
+		1); /* tag - not relevant here */
+
+	SET_HTC_PACKET_NET_BUF_CONTEXT(&pkt->htc_pkt, msg);
+
+	DP_HTT_SEND_HTC_PKT(soc, pkt, HTT_H2T_MSG_TYPE_RX_FSE_OPERATION_CFG,
+			    htt_logger_bufp);
+
+	qdf_info("HTT_H2T RX_FSE_OPERATION_CFG sent to FW for pdev = %u",
+		 fse_op_info->pdev_id);
+
+	return QDF_STATUS_SUCCESS;
+}
diff --git a/dp/wifi3.0/dp_htt.h b/dp/wifi3.0/dp_htt.h
index aed9233..9cb24f0 100644
--- a/dp/wifi3.0/dp_htt.h
+++ b/dp/wifi3.0/dp_htt.h
@@ -207,6 +207,56 @@
 	uint16_t rx_attn_offset;
 };
 
+/**
+ * struct dp_htt_rx_flow_fst_setup - Rx FST setup message
+ * @pdev_id: DP Pdev identifier
+ * @max_entries: Size of Rx FST in number of entries
+ * @max_search: Number of collisions allowed
+ * @base_addr_lo: lower 32-bit physical address
+ * @base_addr_hi: upper 32-bit physical address
+ * @ip_da_sa_prefix: IPv4 prefix to map to IPv6 address scheme
+ * @hash_key_len: Rx FST hash key size
+ * @hash_key: Rx FST Toeplitz hash key
+ */
+struct dp_htt_rx_flow_fst_setup {
+	uint8_t pdev_id;
+	uint32_t max_entries;
+	uint32_t max_search;
+	uint32_t base_addr_lo;
+	uint32_t base_addr_hi;
+	uint32_t ip_da_sa_prefix;
+	uint32_t hash_key_len;
+	uint8_t *hash_key;
+};
+
+/**
+ * enum dp_htt_flow_fst_operation - FST related operations allowed
+ * @DP_HTT_FST_CACHE_OP_NONE: Cache no-op
+ * @DP_HTT_FST_CACHE_INVALIDATE_ENTRY: Invalidate single cache entry
+ * @DP_HTT_FST_CACHE_INVALIDATE_FULL: Invalidate entire cache
+ * @DP_HTT_FST_ENABLE: Bypass FST is enabled
+ * @DP_HTT_FST_DISABLE: Disable bypass FST
+ */
+enum dp_htt_flow_fst_operation {
+	DP_HTT_FST_CACHE_OP_NONE,
+	DP_HTT_FST_CACHE_INVALIDATE_ENTRY,
+	DP_HTT_FST_CACHE_INVALIDATE_FULL,
+	DP_HTT_FST_ENABLE,
+	DP_HTT_FST_DISABLE
+};
+
+/**
+ * struct dp_htt_rx_flow_fst_setup - Rx FST setup message
+ * @pdev_id: DP Pdev identifier
+ * @op_code: FST operation to be performed by FW/HW
+ * @rx_flow: Rx Flow information on which operation is to be performed
+ */
+struct dp_htt_rx_flow_fst_operation {
+	uint8_t pdev_id;
+	enum dp_htt_flow_fst_operation op_code;
+	struct cdp_rx_flow_info *rx_flow;
+};
+
 /*
  * htt_soc_initialize() - SOC level HTT initialization
  * @htt_soc: Opaque htt SOC handle
@@ -325,4 +375,26 @@
 dp_ppdu_desc_user_stats_update(struct dp_pdev *pdev,
 			       struct ppdu_info *ppdu_info);
 
+/**
+ * dp_htt_rx_flow_fst_setup(): Send HTT Rx FST setup message to FW
+ * @pdev: DP pdev handle
+ * @fse_setup_info: FST setup parameters
+ *
+ * Return: Success when HTT message is sent, error on failure
+ */
+QDF_STATUS
+dp_htt_rx_flow_fst_setup(struct dp_pdev *pdev,
+			 struct dp_htt_rx_flow_fst_setup *setup_info);
+
+/**
+ * dp_htt_rx_flow_fse_operation(): Send HTT Flow Search Entry msg to
+ * add/del a flow in HW
+ * @pdev: DP pdev handle
+ * @fse_op_info: Flow entry parameters
+ *
+ * Return: Success when HTT message is sent, error on failure
+ */
+QDF_STATUS
+dp_htt_rx_flow_fse_operation(struct dp_pdev *pdev,
+			     struct dp_htt_rx_flow_fst_operation *op_info);
 #endif /* _DP_HTT_H_ */
diff --git a/dp/wifi3.0/dp_internal.h b/dp/wifi3.0/dp_internal.h
index 14f8e67..c064589 100644
--- a/dp/wifi3.0/dp_internal.h
+++ b/dp/wifi3.0/dp_internal.h
@@ -1405,4 +1405,90 @@
 {
 	return (struct cdp_soc_t *)psoc;
 }
+
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+/**
+ * dp_rx_flow_update_fse_stats() - Update a flow's statistics
+ * @pdev: pdev handle
+ * @flow_id: flow index (truncated hash) in the Rx FST
+ *
+ * Return: Success when flow statistcs is updated, error on failure
+ */
+QDF_STATUS dp_rx_flow_get_fse_stats(struct dp_pdev *pdev,
+				    struct cdp_rx_flow_info *rx_flow_info,
+				    struct cdp_flow_stats *stats);
+
+/**
+ * dp_rx_flow_delete_entry() - Delete a flow entry from flow search table
+ * @pdev: pdev handle
+ * @rx_flow_info: DP flow parameters
+ *
+ * Return: Success when flow is deleted, error on failure
+ */
+QDF_STATUS dp_rx_flow_delete_entry(struct dp_pdev *pdev,
+				   struct cdp_rx_flow_info *rx_flow_info);
+
+/**
+ * dp_rx_flow_add_entry() - Add a flow entry to flow search table
+ * @pdev: DP pdev instance
+ * @rx_flow_info: DP flow paramaters
+ *
+ * Return: Success when flow is added, no-memory or already exists on error
+ */
+QDF_STATUS dp_rx_flow_add_entry(struct dp_pdev *pdev,
+				struct cdp_rx_flow_info *rx_flow_info);
+
+/**
+ * dp_rx_fst_attach() - Initialize Rx FST and setup necessary parameters
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: Handle to flow search table entry
+ */
+QDF_STATUS dp_rx_fst_attach(struct dp_soc *soc, struct dp_pdev *pdev);
+
+/**
+ * dp_rx_fst_detach() - De-initialize Rx FST
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: None
+ */
+void dp_rx_fst_detach(struct dp_soc *soc, struct dp_pdev *pdev);
+
+/**
+ * dp_rx_flow_send_fst_fw_setup() - Program FST parameters in FW/HW post-attach
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: Success when fst parameters are programmed in FW, error otherwise
+ */
+QDF_STATUS dp_rx_flow_send_fst_fw_setup(struct dp_soc *soc,
+					struct dp_pdev *pdev);
+#else
+/**
+ * dp_rx_fst_attach() - Initialize Rx FST and setup necessary parameters
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: Handle to flow search table entry
+ */
+static inline
+QDF_STATUS dp_rx_fst_attach(struct dp_soc *soc, struct dp_pdev *pdev)
+{
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * dp_rx_fst_detach() - De-initialize Rx FST
+ * @soc: SoC handle
+ * @pdev: Pdev handle
+ *
+ * Return: None
+ */
+static inline
+void dp_rx_fst_detach(struct dp_soc *soc, struct dp_pdev *pdev)
+{
+}
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
 #endif /* #ifndef _DP_INTERNAL_H_ */
diff --git a/dp/wifi3.0/dp_main.c b/dp/wifi3.0/dp_main.c
index a89c2f4..834a970 100644
--- a/dp/wifi3.0/dp_main.c
+++ b/dp/wifi3.0/dp_main.c
@@ -3835,6 +3835,7 @@
 
 	dp_pktlogmod_exit(pdev);
 
+	dp_rx_fst_detach(soc, pdev);
 	dp_rx_pdev_detach(pdev);
 	dp_rx_pdev_mon_detach(pdev);
 	dp_neighbour_peers_detach(pdev);
@@ -4539,6 +4540,50 @@
 #endif
 
 /*
+ * dp_rx_target_fst_config() - configure the RXOLE Flow Search Engine
+ *
+ * This function is used to configure the FSE HW block in RX OLE on a
+ * per pdev basis. Here, we will be programming parameters related to
+ * the Flow Search Table.
+ *
+ * @soc: data path SoC handle
+ *
+ * Return: zero on success, non-zero on failure
+ */
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+static QDF_STATUS
+dp_rx_target_fst_config(struct dp_soc *soc)
+{
+	int i;
+	QDF_STATUS status = QDF_STATUS_SUCCESS;
+
+	for (i = 0; i < MAX_PDEV_CNT; i++) {
+		struct dp_pdev *pdev = soc->pdev_list[i];
+
+		if (pdev) {
+			status = dp_rx_flow_send_fst_fw_setup(pdev->soc, pdev);
+			if (status != QDF_STATUS_SUCCESS)
+				break;
+		}
+	}
+	return status;
+}
+#else
+/**
+ * dp_rx_target_fst_config() - Configure RX OLE FSE engine in HW
+ * @soc: SoC handle
+ *
+ * Return: Success
+ */
+static inline QDF_STATUS
+dp_rx_target_fst_config(struct dp_soc *soc)
+{
+	return QDF_STATUS_SUCCESS;
+}
+
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+
+/*
  * dp_soc_attach_target_wifi3() - SOC initialization in the target
  * @cdp_soc: Opaque Datapath SOC handle
  *
@@ -4564,6 +4609,12 @@
 		return status;
 	}
 
+	status = dp_rx_target_fst_config(soc);
+	if (status != QDF_STATUS_SUCCESS) {
+		dp_err("Failed to send htt fst setup config message to target");
+		return status;
+	}
+
 	DP_STATS_INIT(soc);
 
 	/* initialize work queue for stats processing */
@@ -6277,10 +6328,13 @@
 			htt_tlv_filter.enable_mo = 0;
 		} else if (pdev->rx_enh_capture_mode ==
 			   CDP_RX_ENH_CAPTURE_MPDU_MSDU) {
+			bool is_rx_mon_proto_flow_tag_enabled =
+			    wlan_cfg_is_rx_mon_protocol_flow_tag_enabled(
+						    soc->wlan_cfg_ctx);
 			htt_tlv_filter.header_per_msdu = 1;
 			htt_tlv_filter.enable_mo = 0;
-			if (pdev->is_rx_protocol_tagging_enabled ||
-			    pdev->is_rx_enh_capture_trailer_enabled)
+			if (pdev->is_rx_enh_capture_trailer_enabled ||
+			    is_rx_mon_proto_flow_tag_enabled)
 				htt_tlv_filter.msdu_end = 1;
 		}
 	}
@@ -8626,6 +8680,97 @@
 }
 #endif /* WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG */
 
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+/**
+ * dp_set_rx_flow_tag - add/delete a flow
+ * @pdev_handle: cdp_pdev handle
+ * @flow_info: flow tuple that is to be added to/deleted from flow search table
+ *
+ * Return: 0 for success, nonzero for failure
+ */
+static inline QDF_STATUS
+dp_set_rx_flow_tag(struct cdp_pdev *pdev_handle,
+		   struct cdp_rx_flow_info *flow_info)
+{
+	struct dp_pdev *pdev = (struct dp_pdev *)pdev_handle;
+	struct wlan_cfg_dp_soc_ctxt *cfg = pdev->soc->wlan_cfg_ctx;
+
+	if (qdf_unlikely(!wlan_cfg_is_rx_flow_tag_enabled(cfg))) {
+		dp_err("RX Flow tag feature disabled");
+		return QDF_STATUS_E_NOSUPPORT;
+	}
+
+	if (flow_info->op_code == CDP_FLOW_FST_ENTRY_ADD)
+		return dp_rx_flow_add_entry(pdev, flow_info);
+	if (flow_info->op_code == CDP_FLOW_FST_ENTRY_DEL)
+		return dp_rx_flow_delete_entry(pdev, flow_info);
+
+	return QDF_STATUS_E_INVAL;
+}
+
+/**
+ * dp_dump_rx_flow_tag_stats - dump the number of packets tagged for
+ * given flow 5-tuple
+ * @pdev_handle: cdp_pdev handle
+ * @flow_info: flow 5-tuple for which stats should be displayed
+ *
+ * Return: 0 for success, nonzero for failure
+ */
+static inline QDF_STATUS
+dp_dump_rx_flow_tag_stats(struct cdp_pdev *pdev_handle,
+			  struct cdp_rx_flow_info *flow_info)
+{
+	QDF_STATUS status;
+	struct cdp_flow_stats stats;
+	struct dp_pdev *pdev = (struct dp_pdev *)pdev_handle;
+	struct wlan_cfg_dp_soc_ctxt *cfg = pdev->soc->wlan_cfg_ctx;
+
+	if (qdf_unlikely(!wlan_cfg_is_rx_flow_tag_enabled(cfg))) {
+		dp_err("RX Flow tag feature disabled");
+		return QDF_STATUS_E_NOSUPPORT;
+	}
+
+	status = dp_rx_flow_get_fse_stats(pdev, flow_info, &stats);
+
+	if (status != QDF_STATUS_SUCCESS) {
+		dp_err("Unable to get flow stats, error: %u", status);
+		return status;
+	}
+
+	DP_PRINT_STATS("Dest IP address %x:%x:%x:%x",
+		       flow_info->flow_tuple_info.dest_ip_127_96,
+		       flow_info->flow_tuple_info.dest_ip_95_64,
+		       flow_info->flow_tuple_info.dest_ip_63_32,
+		       flow_info->flow_tuple_info.dest_ip_31_0);
+	DP_PRINT_STATS("Source IP address %x:%x:%x:%x",
+		       flow_info->flow_tuple_info.src_ip_127_96,
+		       flow_info->flow_tuple_info.src_ip_95_64,
+		       flow_info->flow_tuple_info.src_ip_63_32,
+		       flow_info->flow_tuple_info.src_ip_31_0);
+	DP_PRINT_STATS("Dest port %u, Src Port %u, Protocol %u",
+		       flow_info->flow_tuple_info.dest_port,
+		       flow_info->flow_tuple_info.src_port,
+		       flow_info->flow_tuple_info.l4_protocol);
+	DP_PRINT_STATS("MSDU Count: %u", stats.msdu_count);
+
+	return status;
+}
+#else
+static inline QDF_STATUS
+dp_set_rx_flow_tag(struct cdp_pdev *pdev,
+		   struct cdp_rx_flow_info *flow_info)
+{
+	return QDF_STATUS_E_FAILURE;
+}
+
+static inline QDF_STATUS
+dp_dump_rx_flow_tag_stats(struct cdp_pdev *pdev,
+			  struct cdp_rx_flow_info *flow_info)
+{
+	return QDF_STATUS_E_FAILURE;
+}
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+
 static QDF_STATUS dp_peer_map_attach_wifi3(struct cdp_soc_t  *soc_hdl,
 					   uint32_t max_peers,
 					   uint32_t max_ast_index,
@@ -9086,6 +9231,10 @@
 				dp_dump_pdev_rx_protocol_tag_stats,
 #endif /* WLAN_SUPPORT_RX_TAG_STATISTICS */
 #endif /* WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG */
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+	.txrx_set_rx_flow_tag = dp_set_rx_flow_tag,
+	.txrx_dump_rx_flow_tag_stats = dp_dump_rx_flow_tag_stats,
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
 #ifdef QCA_MULTIPASS_SUPPORT
 	.txrx_peer_set_vlan_id = dp_peer_set_vlan_id,
 #endif /*QCA_MULTIPASS_SUPPORT*/
@@ -9647,6 +9796,7 @@
 					       REO_DST_RING_SIZE_QCA8074);
 		wlan_cfg_set_raw_mode_war(soc->wlan_cfg_ctx, true);
 		soc->da_war_enabled = true;
+		soc->is_rx_fse_full_cache_invalidate_war_enabled = true;
 		break;
 	case TARGET_TYPE_QCA8074V2:
 	case TARGET_TYPE_QCA6018:
@@ -9658,6 +9808,7 @@
 		soc->per_tid_basize_max_tid = 8;
 		soc->num_hw_dscp_tid_map = HAL_MAX_HW_DSCP_TID_V2_MAPS;
 		soc->da_war_enabled = false;
+		soc->is_rx_fse_full_cache_invalidate_war_enabled = true;
 		break;
 	default:
 		qdf_print("%s: Unknown tgt type %d\n", __func__, target_type);
diff --git a/dp/wifi3.0/dp_rx.c b/dp/wifi3.0/dp_rx.c
index db468cb..a31c5ea 100644
--- a/dp/wifi3.0/dp_rx.c
+++ b/dp/wifi3.0/dp_rx.c
@@ -2064,6 +2064,9 @@
 		dp_rx_update_protocol_tag(soc, vdev, nbuf, rx_tlv_hdr,
 					  reo_ring_num, false, true);
 
+		/* Update the flow tag in SKB based on FSE metadata */
+		dp_rx_update_flow_tag(soc, vdev, nbuf, rx_tlv_hdr, true);
+
 		dp_rx_msdu_stats_update(soc, nbuf, rx_tlv_hdr, peer,
 					ring_id, tid_stats);
 
@@ -2371,6 +2374,8 @@
 	uint32_t rx_sw_desc_weight;
 	struct dp_srng *dp_rxdma_srng;
 	struct rx_desc_pool *rx_desc_pool;
+	QDF_STATUS ret_val;
+
 
 	if (wlan_cfg_get_dp_pdev_nss_enabled(pdev->wlan_cfg_ctx)) {
 		QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO,
@@ -2394,6 +2399,15 @@
 	rx_desc_pool->owner = DP_WBM2SW_RBM;
 	/* For Rx buffers, WBM release ring is SW RING 3,for all pdev's */
 
+	ret_val = dp_rx_fst_attach(soc, pdev);
+	if ((ret_val != QDF_STATUS_SUCCESS) &&
+	    (ret_val != QDF_STATUS_E_NOSUPPORT)) {
+		QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR,
+			  "RX Flow Search Table attach failed: pdev %d err %d",
+			  pdev_id, ret_val);
+		return ret_val;
+	}
+
 	return dp_pdev_rx_buffers_attach(soc, pdev_id, dp_rxdma_srng,
 					 rx_desc_pool, rxdma_entries - 1);
 }
diff --git a/dp/wifi3.0/dp_rx.h b/dp/wifi3.0/dp_rx.h
index c98787e..6c9b2e8 100644
--- a/dp/wifi3.0/dp_rx.h
+++ b/dp/wifi3.0/dp_rx.h
@@ -901,6 +901,7 @@
 {
 }
 #endif /* WLAN_SUPPORT_RX_TAG_STATISTICS */
+
 /**
  * dp_rx_update_protocol_tag() - Reads CCE metadata from the RX MSDU end TLV
  *                              and set the corresponding tag in QDF packet
@@ -977,7 +978,7 @@
 	protocol_tag = pdev->rx_proto_tag_map[cce_metadata].tag;
 	qdf_nbuf_set_rx_protocol_tag(nbuf, protocol_tag);
 	QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO_LOW,
-		  "Seq:%u decap:%u CCE Match:%d ProtoID:%u Tag:%u US:%d",
+		  "Seq:%u dcap:%u CCE Match:%u ProtoID:%u Tag:%u stats:%u",
 		  hal_rx_get_rx_sequence(rx_tlv_hdr),
 		  vdev->rx_decap_type, cce_match, cce_metadata,
 		  protocol_tag, is_update_stats);
@@ -1007,23 +1008,122 @@
 #endif /* WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG */
 
 /**
- * dp_rx_mon_update_protocol_tag() - Performs necessary checks for monitor mode
- *				and then tags appropriate packets
+ * dp_rx_update_rx_flow_tag_stats() - Update stats for given flow index
+ * @pdev: TXRX pdev context for which stats should be incremented
+ * @flow_index: flow index for which the stats should be incremented
+ *
+ * Return: void
+ */
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+QDF_STATUS dp_rx_flow_update_fse_stats(struct dp_pdev *pdev, uint32_t flow_id);
+
+static inline void dp_rx_update_rx_flow_tag_stats(struct dp_pdev *pdev,
+						  uint32_t flow_index)
+{
+	dp_rx_flow_update_fse_stats(pdev, flow_index);
+}
+#else
+static inline void dp_rx_update_rx_flow_tag_stats(struct dp_pdev *pdev,
+						  uint32_t flow_index)
+{
+}
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+
+/**
+ * dp_rx_update_flow_tag() - Reads FSE metadata from the RX MSDU end TLV
+ *                           and set the corresponding tag in QDF packet
+ * @soc: core txrx main context
+ * @vdev: vdev on which the packet is received
+ * @nbuf: QDF pkt buffer on which the protocol tag should be set
+ * @rx_tlv_hdr: base address where the RX TLVs starts
+ * @is_update_stats: flag to indicate whether to update stats or not
+ *
+ * Return: void
+ */
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+static inline void
+dp_rx_update_flow_tag(struct dp_soc *soc, struct dp_vdev *vdev,
+		      qdf_nbuf_t nbuf, uint8_t *rx_tlv_hdr, bool update_stats)
+{
+	bool flow_idx_invalid, flow_idx_timeout;
+	uint32_t flow_idx, fse_metadata;
+	struct dp_pdev *pdev;
+
+	if (qdf_unlikely(!vdev))
+		return;
+
+	pdev = vdev->pdev;
+	if (qdf_likely(!wlan_cfg_is_rx_flow_tag_enabled(soc->wlan_cfg_ctx)))
+		return;
+
+	/**
+	 * In case of raw frames, rx_msdu_end tlv may be stale or invalid.
+	 * Do not tag such frames in normal REO path.
+	 * Default decap_type is set to ethernet for monitor vdev currently,
+	 * therefore, we will not check decap_type for monitor mode.
+	 * We will call this only for eth frames from dp_rx_mon_dest.c.
+	 */
+	if (qdf_likely((vdev->rx_decap_type !=  htt_cmn_pkt_type_ethernet)))
+		return;
+
+	flow_idx_invalid = hal_rx_msdu_flow_idx_invalid(rx_tlv_hdr);
+	hal_rx_msdu_get_flow_params(rx_tlv_hdr, &flow_idx_invalid,
+				    &flow_idx_timeout, &flow_idx);
+	if (qdf_unlikely(flow_idx_invalid))
+		return;
+
+	if (qdf_unlikely(flow_idx_timeout))
+		return;
+
+	/**
+	 * Limit FSE metadata to 16 bit as we have allocated only
+	 * 16 bits for flow_tag field in skb->cb
+	 */
+	fse_metadata = hal_rx_msdu_fse_metadata_get(rx_tlv_hdr) & 0xFFFF;
+
+	/* update the skb->cb with the user-specified tag/metadata */
+	qdf_nbuf_set_rx_flow_tag(nbuf, fse_metadata);
+
+	QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO_LOW,
+		  "Seq:%u dcap:%u invalid:%u timeout:%u flow:%u tag:%u stat:%u",
+		  hal_rx_get_rx_sequence(rx_tlv_hdr),
+		  vdev->rx_decap_type, flow_idx_invalid, flow_idx_timeout,
+		  flow_idx, fse_metadata, update_stats);
+
+	if (qdf_likely(update_stats))
+		dp_rx_update_rx_flow_tag_stats(pdev, flow_idx);
+}
+#else
+static inline void
+dp_rx_update_flow_tag(struct dp_soc *soc, struct dp_vdev *vdev,
+		      qdf_nbuf_t nbuf, uint8_t *rx_tlv_hdr, bool update_stats)
+{
+}
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+
+/**
+ * dp_rx_mon_update_protocol_flow_tag() - Performs necessary checks for monitor
+ *                                       mode and then tags appropriate packets
  * @soc: core txrx main context
  * @vdev: pdev on which packet is received
  * @msdu: QDF packet buffer on which the protocol tag should be set
  * @rx_desc: base address where the RX TLVs start
  * Return: void
  */
-#ifdef WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG
+#if defined(WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG) ||\
+	defined(WLAN_SUPPORT_RX_FLOW_TAG)
 static inline
-void dp_rx_mon_update_protocol_tag(struct dp_soc *soc, struct dp_pdev *dp_pdev,
-				   qdf_nbuf_t msdu, void *rx_desc)
+void dp_rx_mon_update_protocol_flow_tag(struct dp_soc *soc,
+					struct dp_pdev *dp_pdev,
+					qdf_nbuf_t msdu, void *rx_desc)
 {
 	uint32_t msdu_ppdu_id = 0;
 	struct mon_rx_status *mon_recv_status;
 
-	if (qdf_likely(!dp_pdev->is_rx_protocol_tagging_enabled))
+	bool is_mon_protocol_flow_tag_enabled =
+		wlan_cfg_is_rx_mon_protocol_flow_tag_enabled(soc->wlan_cfg_ctx);
+
+	if (qdf_likely(!is_mon_protocol_flow_tag_enabled))
 		return;
 
 	if (qdf_likely(!dp_pdev->monitor_vdev))
@@ -1043,29 +1143,35 @@
 		return;
 	}
 
-	/*
-	 * Update the protocol tag in SKB for packets received on BSS.
-	 * Do not update tag stats since it would double actual received count
-	 */
 	mon_recv_status = &dp_pdev->ppdu_info.rx_status;
 	if (mon_recv_status->frame_control_info_valid &&
 	    ((mon_recv_status->frame_control & IEEE80211_FC0_TYPE_MASK) ==
 	      IEEE80211_FC0_TYPE_DATA)) {
+		/*
+		 * Update the protocol tag in SKB for packets received on BSS.
+		 * Do not update tag stats since it would double actual
+		 * received count.
+		 */
 		dp_rx_update_protocol_tag(soc,
 					  dp_pdev->monitor_vdev,
 					  msdu, rx_desc,
 					  MAX_REO_DEST_RINGS,
 					  false, false);
+		/* Update the flow tag in SKB based on FSE metadata */
+		dp_rx_update_flow_tag(soc, dp_pdev->monitor_vdev,
+				      msdu, rx_desc, false);
 	}
 }
 #else
 static inline
-void dp_rx_mon_update_protocol_tag(struct dp_soc *soc, struct dp_pdev *dp_pdev,
-				   qdf_nbuf_t msdu, void *rx_desc)
+void dp_rx_mon_update_protocol_flow_tag(struct dp_soc *soc,
+					struct dp_pdev *dp_pdev,
+					qdf_nbuf_t msdu, void *rx_desc)
 {
 	/* Stub API */
 }
-#endif /* WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG */
+#endif /* WLAN_SUPPORT_RX_PROTOCOL_TYPE_TAG || WLAN_SUPPORT_RX_FLOW_TAG */
+
 /*
  * dp_rx_buffers_replenish() - replenish rxdma ring with rx nbufs
  *			       called during dp rx initialization
diff --git a/dp/wifi3.0/dp_rx_err.c b/dp/wifi3.0/dp_rx_err.c
index c38d732..bd69385 100644
--- a/dp/wifi3.0/dp_rx_err.c
+++ b/dp/wifi3.0/dp_rx_err.c
@@ -819,6 +819,10 @@
 						  EXCEPTION_DEST_RING_ID,
 						  true, true);
 
+			/* Update the flow tag in SKB based on FSE metadata */
+			dp_rx_update_flow_tag(soc, vdev, nbuf,
+					      rx_tlv_hdr, true);
+
 			if (qdf_unlikely(hal_rx_msdu_end_da_is_mcbc_get(
 						rx_tlv_hdr) &&
 					 (vdev->rx_decap_type ==
@@ -1000,6 +1004,8 @@
 		/* Update the protocol tag in SKB based on CCE metadata */
 		dp_rx_update_protocol_tag(soc, vdev, nbuf, rx_tlv_hdr,
 					  EXCEPTION_DEST_RING_ID, true, true);
+		/* Update the flow tag in SKB based on FSE metadata */
+		dp_rx_update_flow_tag(soc, vdev, nbuf, rx_tlv_hdr, true);
 		DP_STATS_INC(peer, rx.to_stack.num, 1);
 		vdev->osif_rx(vdev->osif_vdev, nbuf);
 	}
diff --git a/dp/wifi3.0/dp_rx_mon_dest.c b/dp/wifi3.0/dp_rx_mon_dest.c
index 939a088..ca77f89 100644
--- a/dp/wifi3.0/dp_rx_mon_dest.c
+++ b/dp/wifi3.0/dp_rx_mon_dest.c
@@ -758,8 +758,9 @@
 			is_first_frag = 0;
 		}
 
-		/* Update protocol tag for MSDU */
-		dp_rx_mon_update_protocol_tag(soc, dp_pdev, msdu_orig, rx_desc);
+		/* Update protocol and flow tag for MSDU */
+		dp_rx_mon_update_protocol_flow_tag(soc, dp_pdev,
+						   msdu_orig, rx_desc);
 
 		dest = qdf_nbuf_put_tail(prev_buf,
 				msdu_llc_len + amsdu_pad);
diff --git a/dp/wifi3.0/dp_stats.c b/dp/wifi3.0/dp_stats.c
index 4e558e1..2819489 100644
--- a/dp/wifi3.0/dp_stats.c
+++ b/dp/wifi3.0/dp_stats.c
@@ -4525,6 +4525,12 @@
 		       soc_cfg_ctx->tx_sw_internode_queue);
 	DP_PRINT_STATS("RXDMA err dst ring: %u ",
 		       soc_cfg_ctx->rxdma_err_dst_ring);
+	DP_PRINT_STATS("RX Flow Tag Enabled: %u ",
+		       soc_cfg_ctx->is_rx_flow_tag_enabled);
+	DP_PRINT_STATS("RX Flow Search Table Size (# of entries): %u ",
+		       soc_cfg_ctx->rx_flow_search_table_size);
+	DP_PRINT_STATS("RX Flow Search Table Per PDev : %u ",
+		       soc_cfg_ctx->is_rx_flow_search_table_per_pdev);
 }
 
 void
diff --git a/dp/wifi3.0/dp_types.h b/dp/wifi3.0/dp_types.h
index 2796253..3f51331 100644
--- a/dp/wifi3.0/dp_types.h
+++ b/dp/wifi3.0/dp_types.h
@@ -43,6 +43,7 @@
 #include <hal_api.h>
 #include <hal_api_mon.h>
 #include "hal_rx.h"
+//#include "hal_rx_flow.h"
 
 #define MAX_BW 7
 #define MAX_RETRIES 4
@@ -116,6 +117,7 @@
 union dp_rx_desc_list_elem_t;
 struct cdp_peer_rate_stats_ctx;
 struct cdp_soc_rate_stats_ctx;
+struct dp_rx_fst;
 
 #define DP_PDEV_ITERATE_VDEV_LIST(_pdev, _vdev) \
 	TAILQ_FOREACH((_vdev), &(_pdev)->vdev_list, vdev_list_elem)
@@ -1128,6 +1130,19 @@
 	qdf_atomic_t num_tx_outstanding;
 	/* Num Tx allowed */
 	uint32_t num_tx_allowed;
+
+	/**
+	 * Flag to indicate whether WAR to address single cache entry
+	 * invalidation bug is enabled or not
+	 */
+	bool is_rx_fse_full_cache_invalidate_war_enabled;
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+	/**
+	 * Pointer to DP RX Flow FST at SOC level if
+	 * is_rx_flow_search_table_per_pdev is false
+	 */
+	struct dp_rx_fst *rx_fst;
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
 };
 
 #ifdef IPA_OFFLOAD
@@ -1292,13 +1307,13 @@
  * at end of each MSDU in monitor-lite mode
  * @reserved1: reserved for future use
  * @reserved2: reserved for future use
- * @reserved3: reserved for future use
+ * @flow_tag: flow tag value read from skb->cb
  * @protocol_tag: protocol tag value read from skb->cb
  */
 struct dp_rx_mon_enh_trailer_data {
 	uint16_t reserved1;
 	uint16_t reserved2;
-	uint16_t reserved3;
+	uint16_t flow_tag;
 	uint16_t protocol_tag;
 };
 #endif /* WLAN_RX_PKT_CAPTURE_ENH */
@@ -1647,6 +1662,13 @@
 	 * belonging to one ppdu
 	 */
 	qdf_nbuf_queue_t rx_ppdu_buf_q;
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+	/**
+	 * Pointer to DP Flow FST at SOC level if
+	 * is_rx_flow_search_table_per_pdev is true
+	 */
+	struct dp_rx_fst *rx_fst;
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
 };
 
 struct dp_peer;
@@ -1998,4 +2020,49 @@
 	uint8_t data[QDF_MAC_ADDR_SIZE];
 };
 
+#ifdef WLAN_SUPPORT_RX_FLOW_TAG
+struct hal_rx_fst;
+
+struct dp_rx_fse {
+	/* HAL Rx Flow Search Entry which matches HW definition */
+	void *hal_rx_fse;
+	/* Toeplitz hash value */
+	uint32_t flow_hash;
+	/* Flow index, equivalent to hash value truncated to FST size */
+	uint32_t flow_id;
+	/* Stats tracking for this flow */
+	struct cdp_flow_stats stats;
+	/* Flag indicating whether flow is IPv4 address tuple */
+	bool is_ipv4_addr_entry;
+	/* Flag indicating whether flow is valid */
+	bool is_valid;
+};
+
+struct dp_rx_fst {
+	/* Software (DP) FST */
+	uint8_t *base;
+	/* Pointer to HAL FST */
+	struct hal_rx_fst *hal_rx_fst;
+	/* Base physical address of HAL RX HW FST */
+	uint64_t hal_rx_fst_base_paddr;
+	/* Maximum number of flows FSE supports */
+	uint16_t max_entries;
+	/* Num entries in flow table */
+	uint16_t num_entries;
+	/* SKID Length */
+	uint16_t max_skid_length;
+	/* Hash mask to obtain legitimate hash entry */
+	uint32_t hash_mask;
+	/* Timer for bundling of flows */
+	qdf_timer_t cache_invalidate_timer;
+	/**
+	 * Flag which tracks whether cache update
+	 * is needed on timer expiry
+	 */
+	qdf_atomic_t is_cache_update_pending;
+	/* Flag to indicate completion of FSE setup in HW/FW */
+	bool fse_setup_done;
+};
+#endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+
 #endif /* _DP_TYPES_H_ */
diff --git a/hal/wifi3.0/hal_api_mon.h b/hal/wifi3.0/hal_api_mon.h
index 12572fd..14cd162 100644
--- a/hal/wifi3.0/hal_api_mon.h
+++ b/hal/wifi3.0/hal_api_mon.h
@@ -469,10 +469,18 @@
 
 /**
  * struct hal_rx_ppdu_msdu_info - struct for msdu info from HW TLVs
- * @cce_metadata: cached metadata value received in the MSDU_END TLV
+ * @cce_metadata: cached CCE metadata value received in the MSDU_END TLV
+ * @is_flow_idx_timeout: flag to indicate if flow search timeout occurred
+ * @is_flow_idx_invalid: flag to indicate if flow idx is valid or not
+ * @fse_metadata: cached FSE metadata value received in the MSDU END TLV
+ * @flow_idx: flow idx matched in FSE received in the MSDU END TLV
  */
 struct hal_rx_ppdu_msdu_info {
 	uint16_t cce_metadata;
+	bool is_flow_idx_timeout;
+	bool is_flow_idx_invalid;
+	uint32_t fse_metadata;
+	uint32_t flow_idx;
 };
 
 struct hal_rx_ppdu_info {
diff --git a/hal/wifi3.0/hal_flow.h b/hal/wifi3.0/hal_flow.h
new file mode 100644
index 0000000..b89146d
--- /dev/null
+++ b/hal/wifi3.0/hal_flow.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef __HAL_FLOW_H
+#define __HAL_FLOW_H
+
+#define HAL_SET_FLD_SM(block, field, value) \
+	(((value) << (block ## _ ## field ## _LSB)) & \
+	 (block ## _ ## field ## _MASK))
+
+#define HAL_SET_FLD_MS(block, field, value) \
+	(((value) & (block ## _ ## field ## _MASK)) >> \
+	 (block ## _ ## field ## _LSB))
+
+#define HAL_CLR_FLD(desc, block, field) \
+do { \
+	uint32_t val; \
+	typeof(desc) desc_ = desc; \
+	val = *((uint32_t *)((uint8_t *)(desc_) + \
+		HAL_OFFSET(block, field))); \
+	val &= ~(block ## _ ## field ## _MASK); \
+	HAL_SET_FLD(desc_, block, field) = val; \
+} while (0)
+
+#define HAL_GET_FLD(desc, block, field) \
+	    ((*((uint32_t *)((uint8_t *)(desc) + HAL_OFFSET(block, field))) & \
+	    (block ## _ ## field ## _MASK)) >> (block ## _ ## field ## _LSB))
+
+/**
+ * struct hal_flow_tuple_info - Hal Flow 5-tuple
+ * @dest_ip_127_96: Destination IP address bits 96-127
+ * @dest_ip_95_64: Destination IP address bits 64-95
+ * @dest_ip_63_32: Destination IP address bits 32-63
+ * @dest_ip_31_0: Destination IP address bits 0-31
+ * @src_ip_127_96: Source IP address bits 96-127
+ * @src_ip_95_64: Source IP address bits 64-95
+ * @src_ip_63_32: Source IP address bits 32-63
+ * @src_ip_31_0: Source IP address bits 0-31
+ * @dest_port: Destination Port
+ * @src_port: Source Port
+ * @l4_protocol: Layer-4 protocol type (TCP/UDP)
+ */
+struct hal_flow_tuple_info {
+	uint32_t dest_ip_127_96;
+	uint32_t dest_ip_95_64;
+	uint32_t dest_ip_63_32;
+	uint32_t dest_ip_31_0;
+	uint32_t src_ip_127_96;
+	uint32_t src_ip_95_64;
+	uint32_t src_ip_63_32;
+	uint32_t src_ip_31_0;
+	uint16_t dest_port;
+	uint16_t src_port;
+	uint16_t l4_protocol;
+};
+
+/**
+ * key_bitwise_shift_left() - Bitwise left shift (in place) an array of bytes
+ * @key: Pointer to array to key bytes
+ * @len: size of array (number of key bytes)
+ * @shift: number of shift operations to be performed
+ *
+ * Return:
+ */
+static inline void
+key_bitwise_shift_left(uint8_t *key, int len, int shift)
+{
+	int i;
+	int next;
+
+	while (shift--) {
+		for (i = len - 1; i >= 0 ; i--) {
+			if (i > 0)
+				next = (key[i - 1] & 0x80 ? 1 : 0);
+			else
+				next = 0;
+			key[i] = (key[i] << 1) | next;
+		}
+	}
+}
+
+/**
+ * key_reverse() - Reverse the key buffer from MSB to LSB
+ * @dest: pointer to the destination key
+ * @src: pointer to the source key which should be shifted
+ * @len: size of key in bytes
+ *
+ * Return:
+ */
+static inline void
+key_reverse(uint8_t *dest, uint8_t *src, int len)
+{
+	int i, j;
+
+	for (i = 0, j = len  - 1; i < len; i++, j--)
+		dest[i] = src[j];
+}
+#endif /* HAL_FLOW_H */
diff --git a/hal/wifi3.0/hal_generic_api.h b/hal/wifi3.0/hal_generic_api.h
index 4baccfc..df22e81 100644
--- a/hal/wifi3.0/hal_generic_api.h
+++ b/hal/wifi3.0/hal_generic_api.h
@@ -1403,6 +1403,14 @@
 		if (user_id < HAL_MAX_UL_MU_USERS) {
 			ppdu_info->rx_msdu_info[user_id].cce_metadata =
 				HAL_RX_MSDU_END_CCE_METADATA_GET(rx_tlv);
+			ppdu_info->rx_msdu_info[user_id].fse_metadata =
+				HAL_RX_MSDU_END_FSE_METADATA_GET(rx_tlv);
+			ppdu_info->rx_msdu_info[user_id].is_flow_idx_timeout =
+				HAL_RX_MSDU_END_FLOW_IDX_TIMEOUT_GET(rx_tlv);
+			ppdu_info->rx_msdu_info[user_id].is_flow_idx_invalid =
+				HAL_RX_MSDU_END_FLOW_IDX_INVALID_GET(rx_tlv);
+			ppdu_info->rx_msdu_info[user_id].flow_idx =
+				HAL_RX_MSDU_END_FLOW_IDX_GET(rx_tlv);
 		}
 		return HAL_TLV_STATUS_MSDU_END;
 	case 0:
diff --git a/hal/wifi3.0/hal_rx.h b/hal/wifi3.0/hal_rx.h
index 9fff9cf..0e7478d 100644
--- a/hal/wifi3.0/hal_rx.h
+++ b/hal/wifi3.0/hal_rx.h
@@ -1926,13 +1926,13 @@
 #define HAL_RX_MSDU_END_CCE_METADATA_GET(_rx_msdu_end)	\
 	(_HAL_MS((*_OFFSET_TO_WORD_PTR(_rx_msdu_end,	\
 		RX_MSDU_END_16_CCE_METADATA_OFFSET)),	\
-		RX_MSDU_END_16_CCE_METADATA_MASK,		\
+		RX_MSDU_END_16_CCE_METADATA_MASK,	\
 		RX_MSDU_END_16_CCE_METADATA_LSB))
 
 /**
  * hal_rx_msdu_cce_metadata_get: API to get CCE metadata
  * from rx_msdu_end TLV
- * @ buf: pointer to the start of RX PKT TLV headers
+ * @buf: pointer to the start of RX PKT TLV headers
  * Return: last_msdu
  */
 
@@ -3443,4 +3443,118 @@
 	return true;
 }
 #endif
+
+#define HAL_RX_MSDU_END_FSE_METADATA_GET(_rx_msdu_end)  \
+		(_HAL_MS((*_OFFSET_TO_WORD_PTR(_rx_msdu_end,  \
+		RX_MSDU_END_15_FSE_METADATA_OFFSET)),  \
+		RX_MSDU_END_15_FSE_METADATA_MASK,    \
+		RX_MSDU_END_15_FSE_METADATA_LSB))
+
+/**
+ * hal_rx_msdu_fse_metadata_get: API to get FSE metadata
+ * from rx_msdu_end TLV
+ * @buf: pointer to the start of RX PKT TLV headers
+ *
+ * Return: fse metadata value from MSDU END TLV
+ */
+static inline uint32_t hal_rx_msdu_fse_metadata_get(uint8_t *buf)
+{
+	struct rx_pkt_tlvs *pkt_tlvs = (struct rx_pkt_tlvs *)buf;
+	struct rx_msdu_end *msdu_end = &pkt_tlvs->msdu_end_tlv.rx_msdu_end;
+	uint32_t fse_metadata;
+
+	fse_metadata = HAL_RX_MSDU_END_FSE_METADATA_GET(msdu_end);
+	return fse_metadata;
+}
+
+#define HAL_RX_MSDU_END_FLOW_IDX_GET(_rx_msdu_end)  \
+		(_HAL_MS((*_OFFSET_TO_WORD_PTR(_rx_msdu_end,  \
+		RX_MSDU_END_14_FLOW_IDX_OFFSET)),  \
+		RX_MSDU_END_14_FLOW_IDX_MASK,    \
+		RX_MSDU_END_14_FLOW_IDX_LSB))
+
+/**
+ * hal_rx_msdu_flow_idx_get: API to get flow index
+ * from rx_msdu_end TLV
+ * @buf: pointer to the start of RX PKT TLV headers
+ *
+ * Return: flow index value from MSDU END TLV
+ */
+static inline uint32_t hal_rx_msdu_flow_idx_get(uint8_t *buf)
+{
+	struct rx_pkt_tlvs *pkt_tlvs = (struct rx_pkt_tlvs *)buf;
+	struct rx_msdu_end *msdu_end = &pkt_tlvs->msdu_end_tlv.rx_msdu_end;
+	uint32_t flow_idx;
+
+	flow_idx = HAL_RX_MSDU_END_FLOW_IDX_GET(msdu_end);
+	return flow_idx;
+}
+
+#define HAL_RX_MSDU_END_FLOW_IDX_TIMEOUT_GET(_rx_msdu_end)  \
+		(_HAL_MS((*_OFFSET_TO_WORD_PTR(_rx_msdu_end,  \
+		RX_MSDU_END_5_FLOW_IDX_TIMEOUT_OFFSET)),  \
+		RX_MSDU_END_5_FLOW_IDX_TIMEOUT_MASK,    \
+		RX_MSDU_END_5_FLOW_IDX_TIMEOUT_LSB))
+
+/**
+ * hal_rx_msdu_flow_idx_timeout: API to get flow index timeout
+ * from rx_msdu_end TLV
+ * @buf: pointer to the start of RX PKT TLV headers
+ *
+ * Return: flow index timeout value from MSDU END TLV
+ */
+static inline bool hal_rx_msdu_flow_idx_timeout(uint8_t *buf)
+{
+	struct rx_pkt_tlvs *pkt_tlvs = (struct rx_pkt_tlvs *)buf;
+	struct rx_msdu_end *msdu_end = &pkt_tlvs->msdu_end_tlv.rx_msdu_end;
+	bool timeout;
+
+	timeout = HAL_RX_MSDU_END_FLOW_IDX_TIMEOUT_GET(msdu_end);
+	return timeout;
+}
+
+#define HAL_RX_MSDU_END_FLOW_IDX_INVALID_GET(_rx_msdu_end)  \
+		(_HAL_MS((*_OFFSET_TO_WORD_PTR(_rx_msdu_end,  \
+		RX_MSDU_END_5_FLOW_IDX_INVALID_OFFSET)),  \
+		RX_MSDU_END_5_FLOW_IDX_INVALID_MASK,    \
+		RX_MSDU_END_5_FLOW_IDX_INVALID_LSB))
+/**
+ * hal_rx_msdu_flow_idx_invalid: API to get flow index invalid
+ * from rx_msdu_end TLV
+ * @buf: pointer to the start of RX PKT TLV headers
+ *
+ * Return: flow index invalid value from MSDU END TLV
+ */
+static inline bool hal_rx_msdu_flow_idx_invalid(uint8_t *buf)
+{
+	struct rx_pkt_tlvs *pkt_tlvs = (struct rx_pkt_tlvs *)buf;
+	struct rx_msdu_end *msdu_end = &pkt_tlvs->msdu_end_tlv.rx_msdu_end;
+	bool invalid;
+
+	invalid = HAL_RX_MSDU_END_FLOW_IDX_INVALID_GET(msdu_end);
+	return invalid;
+}
+
+/**
+ * hal_rx_msdu_get_flow_params: API to get flow index, flow index invalid
+ * and flow index timeout from rx_msdu_end TLV
+ * @buf: pointer to the start of RX PKT TLV headers
+ * @flow_invalid: pointer to return value of flow_idx_valid
+ * @flow_timeout: pointer to return value of flow_idx_timeout
+ * @flow_index: pointer to return value of flow_idx
+ *
+ * Return: none
+ */
+static inline void hal_rx_msdu_get_flow_params(uint8_t *buf,
+					       bool *flow_invalid,
+					       bool *flow_timeout,
+					       uint32_t *flow_index)
+{
+	struct rx_pkt_tlvs *pkt_tlvs = (struct rx_pkt_tlvs *)buf;
+	struct rx_msdu_end *msdu_end = &pkt_tlvs->msdu_end_tlv.rx_msdu_end;
+
+	*flow_invalid = HAL_RX_MSDU_END_FLOW_IDX_INVALID_GET(msdu_end);
+	*flow_timeout = HAL_RX_MSDU_END_FLOW_IDX_TIMEOUT_GET(msdu_end);
+	*flow_index = HAL_RX_MSDU_END_FLOW_IDX_GET(msdu_end);
+}
 #endif /* _HAL_RX_H */
diff --git a/hal/wifi3.0/hal_rx_flow.h b/hal/wifi3.0/hal_rx_flow.h
new file mode 100644
index 0000000..353f9e6
--- /dev/null
+++ b/hal/wifi3.0/hal_rx_flow.h
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef __HAL_RX_FLOW_H
+#define __HAL_RX_FLOW_H
+
+#include "hal_flow.h"
+#include "wlan_cfg.h"
+#include "hal_api.h"
+#include "qdf_mem.h"
+#include "rx_flow_search_entry.h"
+
+#define HAL_FST_HASH_KEY_SIZE_BITS 315
+#define HAL_FST_HASH_KEY_SIZE_BYTES 40
+#define HAL_FST_HASH_KEY_SIZE_WORDS 10
+#define HAL_FST_HASH_DATA_SIZE 37
+#define HAL_FST_HASH_MASK 0x7ffff
+#define HAL_RX_FST_ENTRY_SIZE (NUM_OF_DWORDS_RX_FLOW_SEARCH_ENTRY * 4)
+
+/**
+ * Four possible options for IP SA/DA prefix, currently use 0x0 which
+ * maps to type 2 in HW spec
+ */
+#define HAL_FST_IP_DA_SA_PFX_TYPE_IPV4_COMPATIBLE_IPV6 2
+
+#define HAL_IP_DA_SA_PREFIX_IPV4_COMPATIBLE_IPV6 0x0
+
+/**
+ * REO destination indication is a lower 4-bits of hash value
+ * This should match the REO destination used in Rx hash based routing.
+ */
+#define HAL_REO_DEST_IND_HASH_MASK	0xF
+
+/**
+ * REO destinations are valid from 16-31 for Hawkeye
+ * and 0-15 are not setup for SW
+ */
+#define HAL_REO_DEST_IND_START_OFFSET 0x10
+
+/**
+ * struct hal_rx_flow - Rx Flow parameters to be sent to HW
+ * @tuple_info: Rx Flow 5-tuple (src & dest IP, src & dest ports, L4 protocol)
+ * @reo_destination_handler: REO destination for this flow
+ * @reo_destination_indication: REO indication for this flow
+ * @fse_metadata: Flow metadata or tag passed to HW for marking packets
+ */
+struct hal_rx_flow {
+	struct hal_flow_tuple_info tuple_info;
+	uint8_t reo_destination_handler;
+	uint8_t reo_destination_indication;
+	uint32_t fse_metadata;
+};
+
+/**
+ * enum hal_rx_fse_reo_destination_handler
+ * @HAL_RX_FSE_REO_DEST_FT: Use this entry's destination indication
+ * @HAL_RX_FSE_REO_DEST_ASPT: Use Address Search + Peer Table's entry
+ * @HAL_RX_FSE_REO_DEST_FT2: Use FT2's destination indication
+ * @HAL_RX_FSE_REO_DEST_CCE: Use CCE's destination indication for this entry
+ */
+enum hal_rx_fse_reo_destination_handler {
+	HAL_RX_FSE_REO_DEST_FT = 0,
+	HAL_RX_FSE_REO_DEST_ASPT = 1,
+	HAL_RX_FSE_REO_DEST_FT2 = 2,
+	HAL_RX_FSE_REO_DEST_CCE = 3,
+};
+
+/**
+ * struct hal_rx_fst - HAL RX Flow search table context
+ * @base_vaddr: Virtual Base address of HW FST
+ * @base_paddr: Physical Base address of HW FST
+ * @key: Pointer to 320-bit Key read from cfg
+ * @shifted_key: Pointer to left-shifted 320-bit Key used for Toeplitz Hash
+ * @max_entries : Max number of entries in flow searchh  table
+ * @max_skid_length : Max search length if there is hash collision
+ * @hash_mask: Hash mask to apply to index into FST
+ * @key_cache: Toepliz Key Cache configured key
+ */
+struct hal_rx_fst {
+	uint8_t *base_vaddr;
+	qdf_dma_addr_t base_paddr;
+	uint8_t *key;
+	uint8_t  shifted_key[HAL_FST_HASH_KEY_SIZE_BYTES];
+	uint16_t max_entries;
+	uint16_t max_skid_length;
+	uint16_t hash_mask;
+	uint32_t key_cache[HAL_FST_HASH_KEY_SIZE_BYTES][1 << 8];
+};
+
+/**
+ * hal_rx_flow_setup_fse() - Setup a flow search entry in HW FST
+ * @fst: Pointer to the Rx Flow Search Table
+ * @table_offset: offset into the table where the flow is to be setup
+ * @flow: Flow Parameters
+ *
+ * Return: Success/Failure
+ */
+static void *
+hal_rx_flow_setup_fse(struct hal_rx_fst *fst, uint32_t table_offset,
+		      struct hal_rx_flow *flow)
+{
+	uint8_t *fse;
+	bool fse_valid;
+
+	if (table_offset >= fst->max_entries) {
+		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
+			  "HAL FSE table offset %u exceeds max entries %u",
+			  table_offset, fst->max_entries);
+		return NULL;
+	}
+
+	fse = (uint8_t *)fst->base_vaddr +
+			(table_offset * HAL_RX_FST_ENTRY_SIZE);
+
+	fse_valid = HAL_GET_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, VALID);
+
+	if (fse_valid) {
+		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_DEBUG,
+			  "HAL FSE %pK already valid", fse);
+		return NULL;
+	}
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_0, SRC_IP_127_96) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_0, SRC_IP_127_96,
+			       qdf_htonl(flow->tuple_info.src_ip_127_96));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_1, SRC_IP_95_64) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_1, SRC_IP_95_64,
+			       qdf_htonl(flow->tuple_info.src_ip_95_64));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_2, SRC_IP_63_32) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_2, SRC_IP_63_32,
+			       qdf_htonl(flow->tuple_info.src_ip_63_32));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_3, SRC_IP_31_0) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_3, SRC_IP_31_0,
+			       qdf_htonl(flow->tuple_info.src_ip_31_0));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_4, DEST_IP_127_96) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_4, DEST_IP_127_96,
+			       qdf_htonl(flow->tuple_info.dest_ip_127_96));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_5, DEST_IP_95_64) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_5, DEST_IP_95_64,
+			       qdf_htonl(flow->tuple_info.dest_ip_95_64));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_6, DEST_IP_63_32) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_6, DEST_IP_63_32,
+			       qdf_htonl(flow->tuple_info.dest_ip_63_32));
+
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_7, DEST_IP_31_0) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_7, DEST_IP_31_0,
+			       qdf_htonl(flow->tuple_info.dest_ip_31_0));
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_8, DEST_PORT);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_8, DEST_PORT) |=
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_8, DEST_PORT,
+			       (flow->tuple_info.dest_port));
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_8, SRC_PORT);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_8, SRC_PORT) |=
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_8, SRC_PORT,
+			       (flow->tuple_info.src_port));
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, L4_PROTOCOL);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, L4_PROTOCOL) |=
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_9, L4_PROTOCOL,
+			       flow->tuple_info.l4_protocol);
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, REO_DESTINATION_HANDLER);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, REO_DESTINATION_HANDLER) |=
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_9, REO_DESTINATION_HANDLER,
+			       flow->reo_destination_handler);
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, VALID);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, VALID) |=
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_9, VALID, 1);
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_10, METADATA);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_10, METADATA) =
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_10, METADATA,
+			       flow->fse_metadata);
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_11, REO_DESTINATION_INDICATION);
+	HAL_SET_FLD(fse, RX_FLOW_SEARCH_ENTRY_11, REO_DESTINATION_INDICATION) |=
+		HAL_SET_FLD_SM(RX_FLOW_SEARCH_ENTRY_11,
+			       REO_DESTINATION_INDICATION,
+			       flow->reo_destination_indication);
+
+	/* Reset all the other fields in FSE */
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, RESERVED_9);
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_11, MSDU_DROP);
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_11, RESERVED_11);
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_11, MSDU_COUNT);
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_12, MSDU_BYTE_COUNT);
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_13, TIMESTAMP);
+
+	return fse;
+}
+
+/**
+ * hal_rx_flow_delete_entry() - Delete a flow from the Rx Flow Search Table
+ * @fst: Pointer to the Rx Flow Search Table
+ * @hal_rx_fse: Pointer to the Rx Flow that is to be deleted from the FST
+ *
+ * Return: Success/Failure
+ */
+static inline QDF_STATUS
+hal_rx_flow_delete_entry(struct hal_rx_fst *fst, void *hal_rx_fse)
+{
+	uint8_t *fse = (uint8_t *)hal_rx_fse;
+
+	if (!HAL_GET_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, VALID))
+		return QDF_STATUS_E_NOENT;
+
+	HAL_CLR_FLD(fse, RX_FLOW_SEARCH_ENTRY_9, VALID);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * hal_rx_fst_key_configure() - Configure the Toeplitz key in the FST
+ * @fst: Pointer to the Rx Flow Search Table
+ *
+ * Return: Success/Failure
+ */
+static void hal_rx_fst_key_configure(struct hal_rx_fst *fst)
+{
+	uint8_t key_bytes[HAL_FST_HASH_KEY_SIZE_BYTES];
+
+	qdf_mem_copy(key_bytes, fst->key, HAL_FST_HASH_KEY_SIZE_BYTES);
+
+	/**
+	 * The Toeplitz algorithm as per the Microsoft spec works in a
+	 * “big-endian” manner, using the MSBs of the key to hash the
+	 * initial bytes of the input going on to use up the lower order bits
+	 * of the key to hash further bytes of the input until the LSBs of the
+	 * key are used finally.
+	 *
+	 * So first, rightshift 320-bit input key 5 times to get 315 MS bits
+	 */
+	key_bitwise_shift_left(key_bytes, HAL_FST_HASH_KEY_SIZE_BYTES, 5);
+	key_reverse(fst->shifted_key, key_bytes, HAL_FST_HASH_KEY_SIZE_BYTES);
+}
+
+/**
+ * hal_rx_fst_get_base() - Retrieve the virtual base address of the Rx FST
+ * @fst: Pointer to the Rx Flow Search Table
+ *
+ * Return: Success/Failure
+ */
+static inline void *hal_rx_fst_get_base(struct hal_rx_fst *fst)
+{
+	return fst->base_vaddr;
+}
+
+/**
+ * hal_rx_fst_get_fse_size() - Retrieve the size of each entry(flow) in Rx FST
+ *
+ * Return: size of each entry/flow in Rx FST
+ */
+static inline uint32_t hal_rx_fst_get_fse_size(void)
+{
+	return HAL_RX_FST_ENTRY_SIZE;
+}
+
+/**
+ * hal_rx_flow_get_tuple_info() - Retrieve the 5-tuple flow info for an entry
+ * @hal_fse: Pointer to the Flow in Rx FST
+ * @tuple_info: 5-tuple info of the flow returned to the caller
+ *
+ * Return: Success/Failure
+ */
+QDF_STATUS hal_rx_flow_get_tuple_info(void *hal_fse,
+				      struct hal_flow_tuple_info *tuple_info)
+{
+	if (!hal_fse || !tuple_info)
+		return QDF_STATUS_E_INVAL;
+
+	if (!HAL_GET_FLD(hal_fse, RX_FLOW_SEARCH_ENTRY_9, VALID))
+		return QDF_STATUS_E_NOENT;
+
+	tuple_info->src_ip_127_96 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_0, SRC_IP_127_96));
+	tuple_info->src_ip_95_64 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_1, SRC_IP_95_64));
+	tuple_info->src_ip_63_32 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_2, SRC_IP_63_32));
+	tuple_info->src_ip_31_0 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_3, SRC_IP_31_0));
+	tuple_info->dest_ip_127_96 =
+		 qdf_ntohl(HAL_GET_FLD(hal_fse,
+				       RX_FLOW_SEARCH_ENTRY_4, DEST_IP_127_96));
+	tuple_info->dest_ip_95_64 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_5, DEST_IP_95_64));
+	tuple_info->dest_ip_63_32 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_6, DEST_IP_63_32));
+	tuple_info->dest_ip_31_0 = qdf_ntohl(HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_7, DEST_IP_31_0));
+	tuple_info->dest_port = (HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_8, DEST_PORT));
+	tuple_info->src_port = (HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_8, SRC_PORT));
+	tuple_info->l4_protocol = HAL_GET_FLD(hal_fse,
+					RX_FLOW_SEARCH_ENTRY_9, L4_PROTOCOL);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * hal_flow_toeplitz_create_cache() - Calculate hashes for each possible
+ * byte value with the key taken as is
+ *
+ * @fst: FST Handle
+ * @key: Hash Key
+ *
+ * Return: Success/Failure
+ */
+void hal_flow_toeplitz_create_cache(struct hal_rx_fst *fst)
+{
+	int bit;
+	int val;
+	int i;
+	uint8_t *key = fst->shifted_key;
+
+	/*
+	 * Initialise to first 32 bits of the key; shift in further key material
+	 * through the loop
+	 */
+	uint32_t cur_key = (key[0] << 24) | (key[1] << 16) | (key[2] << 8) |
+		key[3];
+
+	for (i = 0; i < HAL_FST_HASH_KEY_SIZE_BYTES; i++) {
+		uint8_t new_key_byte;
+		uint32_t shifted_key[8];
+
+		if (i + 4 < HAL_FST_HASH_KEY_SIZE_BYTES)
+			new_key_byte = key[i + 4];
+		else
+			new_key_byte = 0;
+
+		shifted_key[0] = cur_key;
+
+		for (bit = 1; bit < 8; bit++) {
+			/*
+			 * For each iteration, shift out one more bit of the
+			 * current key and shift in one more bit of the new key
+			 * material
+			 */
+			shifted_key[bit] = cur_key << bit |
+				new_key_byte >> (8 - bit);
+		}
+
+		for (val = 0; val < (1 << 8); val++) {
+			uint32_t hash = 0;
+			int mask;
+
+			/*
+			 * For each bit set in the input, XOR in
+			 * the appropriately shifted key
+			 */
+			for (bit = 0, mask = 1 << 7; bit < 8; bit++, mask >>= 1)
+				if ((val & mask))
+					hash ^= shifted_key[bit];
+
+			fst->key_cache[i][val] = hash;
+		}
+
+		cur_key = cur_key << 8 | new_key_byte;
+	}
+}
+
+/**
+ * hal_rx_fst_attach() - Initialize Rx flow search table in HW FST
+ *
+ * @qdf_dev: QDF device handle
+ * @hal_fst_base_paddr: Pointer to the physical base address of the Rx FST
+ * @max_entries: Max number of flows allowed in the FST
+ * @max_search: Number of collisions allowed in the hash-based FST
+ * @hash_key: Toeplitz key used for the hash FST
+ *
+ * Return:
+ */
+static struct hal_rx_fst *
+hal_rx_fst_attach(qdf_device_t qdf_dev,
+		  uint64_t *hal_fst_base_paddr, uint16_t max_entries,
+		  uint16_t max_search, uint8_t *hash_key)
+{
+	struct hal_rx_fst *fst = qdf_mem_malloc(sizeof(struct hal_rx_fst));
+
+	if (!fst) {
+		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
+			  FL("hal fst allocation failed,"));
+		return NULL;
+	}
+
+	qdf_mem_set(fst, 0, sizeof(struct hal_rx_fst));
+
+	fst->key = hash_key;
+	fst->max_skid_length = max_search;
+	fst->max_entries = max_entries;
+	fst->hash_mask = max_entries - 1;
+
+	QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_DEBUG,
+		  "HAL FST allocation %x %d * %d\n", fst,
+		  fst->max_entries, HAL_RX_FST_ENTRY_SIZE);
+
+	fst->base_vaddr = (uint8_t *)qdf_mem_alloc_consistent(qdf_dev,
+				qdf_dev->dev,
+				(fst->max_entries * HAL_RX_FST_ENTRY_SIZE),
+				&fst->base_paddr);
+
+	if (!fst->base_vaddr) {
+		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
+			  FL("hal fst->base_vaddr allocation failed"));
+		qdf_mem_free(fst);
+		return NULL;
+	}
+	QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_DEBUG,
+			   (void *)fst->key, HAL_FST_HASH_KEY_SIZE_BYTES);
+
+	qdf_mem_set((uint8_t *)fst->base_vaddr, 0,
+		    (fst->max_entries * HAL_RX_FST_ENTRY_SIZE));
+
+	hal_rx_fst_key_configure(fst);
+	hal_flow_toeplitz_create_cache(fst);
+	*hal_fst_base_paddr = (uint64_t)fst->base_paddr;
+	return fst;
+}
+
+/**
+ * hal_rx_fst_detach() - De-init the Rx flow search table from HW
+ *
+ * @rx_fst: Pointer to the Rx FST
+ * @qdf_dev: QDF device handle
+ *
+ * Return:
+ */
+void hal_rx_fst_detach(struct hal_rx_fst *rx_fst,
+		       qdf_device_t qdf_dev)
+{
+	if (!rx_fst || !qdf_dev)
+		return;
+
+	qdf_mem_free_consistent(qdf_dev, qdf_dev->dev,
+				rx_fst->max_entries * HAL_RX_FST_ENTRY_SIZE,
+				rx_fst->base_vaddr, rx_fst->base_paddr, 0);
+
+	qdf_mem_free(rx_fst);
+}
+
+/**
+ * hal_flow_toeplitz_hash() - Calculate Toeplitz hash by using the cached key
+ *
+ * @hal_fst: FST Handle
+ * @flow: Flow Parameters
+ *
+ * Return: Success/Failure
+ */
+static inline uint32_t
+hal_flow_toeplitz_hash(void *hal_fst, struct hal_rx_flow *flow)
+{
+	int i, j;
+	uint32_t hash = 0;
+	struct hal_rx_fst *fst = (struct hal_rx_fst *)hal_fst;
+	uint32_t input[HAL_FST_HASH_KEY_SIZE_WORDS];
+	uint8_t *tuple;
+
+	qdf_mem_zero(input, HAL_FST_HASH_KEY_SIZE_BYTES);
+	*(uint32_t *)&input[0] = qdf_htonl(flow->tuple_info.src_ip_127_96);
+	*(uint32_t *)&input[1] = qdf_htonl(flow->tuple_info.src_ip_95_64);
+	*(uint32_t *)&input[2] = qdf_htonl(flow->tuple_info.src_ip_63_32);
+	*(uint32_t *)&input[3] = qdf_htonl(flow->tuple_info.src_ip_31_0);
+	*(uint32_t *)&input[4] = qdf_htonl(flow->tuple_info.dest_ip_127_96);
+	*(uint32_t *)&input[5] = qdf_htonl(flow->tuple_info.dest_ip_95_64);
+	*(uint32_t *)&input[6] = qdf_htonl(flow->tuple_info.dest_ip_63_32);
+	*(uint32_t *)&input[7] = qdf_htonl(flow->tuple_info.dest_ip_31_0);
+	*(uint32_t *)&input[8] = (flow->tuple_info.dest_port << 16) |
+				 (flow->tuple_info.src_port);
+	*(uint32_t *)&input[9] = flow->tuple_info.l4_protocol;
+
+	tuple = (uint8_t *)input;
+	QDF_TRACE_HEX_DUMP(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_DEBUG,
+			   tuple, sizeof(input));
+	for (i = 0, j = HAL_FST_HASH_DATA_SIZE - 1;
+	     i < HAL_FST_HASH_KEY_SIZE_BYTES && j >= 0; i++, j--) {
+		hash ^= fst->key_cache[i][tuple[j]];
+	}
+
+	QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_INFO_LOW,
+		  "Hash value %u %u truncated hash %u\n", hash,
+		  (hash >> 12), (hash >> 12) % (fst->max_entries));
+
+	hash >>= 12;
+	hash &= (fst->max_entries - 1);
+
+	return hash;
+}
+
+/**
+ * hal_rx_get_hal_hash() - Retrieve hash index of a flow in the FST table
+ *
+ * @hal_fst: HAL Rx FST Handle
+ * @flow_hash: Flow hash computed from flow tuple
+ *
+ * Return: hash index truncated to the size of the hash table
+ */
+inline
+uint32_t hal_rx_get_hal_hash(struct hal_rx_fst *hal_fst, uint32_t flow_hash)
+{
+	uint32_t trunc_hash = flow_hash;
+
+	/* Take care of hash wrap around scenario */
+	if (flow_hash >= hal_fst->max_entries)
+		trunc_hash &= hal_fst->hash_mask;
+	return trunc_hash;
+}
+
+/**
+ * hal_rx_insert_flow_entry() - Add a flow into the FST table
+ *
+ * @hal_fst: HAL Rx FST Handle
+ * @flow_hash: Flow hash computed from flow tuple
+ * @flow_tuple_info: Flow tuple used to compute the hash
+ * @flow_index: Hash index of the flow in the table when inserted successfully
+ *
+ * Return: Success if flow is inserted into the table, error otherwise
+ */
+QDF_STATUS
+hal_rx_insert_flow_entry(struct hal_rx_fst *fst, uint32_t flow_hash,
+			 void *flow_tuple_info, uint32_t *flow_idx) {
+	int i;
+	void *hal_fse;
+	uint32_t hal_hash;
+	struct hal_flow_tuple_info hal_tuple_info = { 0 };
+	QDF_STATUS status;
+
+	for (i = 0; i < fst->max_skid_length; i++) {
+		hal_hash = hal_rx_get_hal_hash(fst, (flow_hash + i));
+		hal_fse = (uint8_t *)fst->base_vaddr +
+			(hal_hash * HAL_RX_FST_ENTRY_SIZE);
+		status = hal_rx_flow_get_tuple_info(hal_fse, &hal_tuple_info);
+		if (QDF_STATUS_E_NOENT == status)
+			break;
+
+		/* Find the matching flow entry in HW FST */
+		if (!qdf_mem_cmp(&hal_tuple_info,
+				 flow_tuple_info,
+				 sizeof(struct hal_flow_tuple_info))) {
+			dp_err("Duplicate flow entry in FST %u at skid %u ",
+			       hal_hash, i);
+			return QDF_STATUS_E_EXISTS;
+		}
+	}
+	if (i == fst->max_skid_length) {
+		dp_err("Max skid length reached for hash %u", flow_hash);
+		return QDF_STATUS_E_RANGE;
+	}
+	*flow_idx = hal_hash;
+	dp_info("flow_hash = %u, skid_entry = %d, flow_addr = %pK flow_idx = %d",
+		flow_hash, i, hal_fse, *flow_idx);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+/**
+ * hal_rx_find_flow_from_tuple() - Find a flow in the FST table
+ *
+ * @fst: HAL Rx FST Handle
+ * @flow_hash: Flow hash computed from flow tuple
+ * @flow_tuple_info: Flow tuple used to compute the hash
+ * @flow_index: Hash index of the flow in the table when found
+ *
+ * Return: Success if matching flow is found in the table, error otherwise
+ */
+QDF_STATUS
+hal_rx_find_flow_from_tuple(struct hal_rx_fst *fst, uint32_t flow_hash,
+			    void *flow_tuple_info, uint32_t *flow_idx)
+{
+	int i;
+	void *hal_fse;
+	uint32_t hal_hash;
+	struct hal_flow_tuple_info hal_tuple_info = { 0 };
+	QDF_STATUS status;
+
+	for (i = 0; i < fst->max_skid_length; i++) {
+		hal_hash = hal_rx_get_hal_hash(fst, (flow_hash + i));
+		hal_fse = (uint8_t *)fst->base_vaddr +
+			(hal_hash * HAL_RX_FST_ENTRY_SIZE);
+		status = hal_rx_flow_get_tuple_info(hal_fse, &hal_tuple_info);
+		if (QDF_STATUS_SUCCESS != status)
+			continue;
+
+		/* Find the matching flow entry in HW FST */
+		if (!qdf_mem_cmp(&hal_tuple_info,
+				 flow_tuple_info,
+				 sizeof(struct hal_flow_tuple_info))) {
+			break;
+		}
+	}
+
+	if (i == fst->max_skid_length) {
+		dp_err("Max skid length reached for hash %u", flow_hash);
+		return QDF_STATUS_E_RANGE;
+	}
+
+	*flow_idx = hal_hash;
+	dp_info("flow_hash = %u, skid_entry = %d, flow_addr = %pK flow_idx = %d",
+		flow_hash, i, hal_fse, *flow_idx);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+#endif /* HAL_RX_FLOW_H */