diff --git a/drivers/net/softnic/Makefile b/drivers/net/softnic/Makefile
index 09ed62e..4f7f6c9 100644
--- a/drivers/net/softnic/Makefile
+++ b/drivers/net/softnic/Makefile
@@ -51,7 +51,7 @@ LIBABIVER := 1
 #
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_SOFTNIC) += rte_eth_softnic.c
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_SOFTNIC) += rte_eth_softnic_tm.c
-
+SRCS-$(CONFIG_RTE_LIBRTE_PMD_SOFTNIC) += rte_eth_softnic_fc.c
 #
 # Export include files
 #
diff --git a/drivers/net/softnic/rte_eth_softnic.c b/drivers/net/softnic/rte_eth_softnic.c
index 3e47c2f..a5779d3 100644
--- a/drivers/net/softnic/rte_eth_softnic.c
+++ b/drivers/net/softnic/rte_eth_softnic.c
@@ -61,6 +61,12 @@
 #define PMD_PARAM_SOFT_TM_ENQ_BSZ			"soft_tm_enq_bsz"
 #define PMD_PARAM_SOFT_TM_DEQ_BSZ			"soft_tm_deq_bsz"
 
+#define PMD_PARAM_SOFT_FC					"soft_fc"
+#define PMD_PARAM_SOFT_FC_NB_RULES			"soft_fc_nb_rules"
+#define PMD_PARAM_SOFT_FC_FLOW_KEY_SIZE		"soft_fc_flow_key_size"
+#define PMD_PARAM_SOFT_FC_FLOW_KEY_OFFSET	"soft_fc_flow_key_offset"
+#define PMD_PARAM_SOFT_FC_FLOW_KEY_MASK		"soft_fc_flow_key_mask"
+
 #define PMD_PARAM_HARD_NAME					"hard_name"
 #define PMD_PARAM_HARD_TX_QUEUE_ID			"hard_tx_queue_id"
 
@@ -74,6 +80,11 @@ static const char *pmd_valid_args[] = {
 	PMD_PARAM_SOFT_TM_QSIZE3,
 	PMD_PARAM_SOFT_TM_ENQ_BSZ,
 	PMD_PARAM_SOFT_TM_DEQ_BSZ,
+	PMD_PARAM_SOFT_FC,
+	PMD_PARAM_SOFT_FC_NB_RULES,
+	PMD_PARAM_SOFT_FC_FLOW_KEY_SIZE,
+	PMD_PARAM_SOFT_FC_FLOW_KEY_OFFSET,
+	PMD_PARAM_SOFT_FC_FLOW_KEY_MASK,
 	PMD_PARAM_HARD_NAME,
 	PMD_PARAM_HARD_TX_QUEUE_ID,
 	NULL
@@ -96,6 +107,15 @@ static const struct rte_eth_dev_info pmd_dev_info = {
 	},
 };
 
+static __rte_always_inline int
+run_tm(struct rte_eth_dev *dev);
+
+static __rte_always_inline int
+run_default(struct rte_eth_dev *dev);
+
+static __rte_always_inline int
+run_fc(struct rte_eth_dev *dev);
+
 static void
 pmd_dev_infos_get(struct rte_eth_dev *dev __rte_unused,
 	struct rte_eth_dev_info *dev_info)
@@ -121,14 +141,28 @@ pmd_dev_configure(struct rte_eth_dev *dev)
 static int
 pmd_rx_queue_setup(struct rte_eth_dev *dev,
 	uint16_t rx_queue_id,
-	uint16_t nb_rx_desc __rte_unused,
+	uint16_t nb_rx_desc,
 	unsigned int socket_id,
 	const struct rte_eth_rxconf *rx_conf __rte_unused,
 	struct rte_mempool *mb_pool __rte_unused)
 {
 	struct pmd_internals *p = dev->data->dev_private;
 
-	if (p->params.soft.intrusive == 0) {
+	if (p->params.soft.intrusive == 0 && fc_enabled(dev)) {
+		uint32_t size = RTE_ETH_NAME_MAX_LEN + strlen("_rxq") + 4;
+		char name[size];
+		struct rte_ring *r;
+
+		snprintf(name, sizeof(name), "%s_rxq%04x",
+			dev->data->name, rx_queue_id);
+		r = rte_ring_create(name, nb_rx_desc, socket_id,
+			RING_F_SP_ENQ | RING_F_SC_DEQ);
+		if (r == NULL)
+			return -1;
+
+		dev->data->rx_queues[rx_queue_id] = r;
+
+	} else if (p->params.soft.intrusive == 0) {
 		struct pmd_rx_queue *rxq;
 
 		rxq = rte_zmalloc_socket(p->params.soft.name,
@@ -177,12 +211,34 @@ static int
 pmd_dev_start(struct rte_eth_dev *dev)
 {
 	struct pmd_internals *p = dev->data->dev_private;
+	uint32_t tx_ops_flag = p->params.tx_ops_data.ops_flag;
+	uint32_t rx_ops_flag = p->params.rx_ops_data.ops_flag;
+	uint32_t nb_tx_ops = 0, nb_rx_ops = 0;
+
+	memset(&p->params.tx_ops_data.ops, 0,
+		MAX_NB_OPS * sizeof(*p->params.tx_ops_data.ops));
+	memset(&p->params.rx_ops_data.ops, 0,
+		MAX_NB_OPS * sizeof(*p->params.rx_ops_data.ops));
 
 	if (tm_used(dev)) {
 		int status = tm_start(p);
 
 		if (status)
 			return status;
+
+		/** Set run time tx ops data*/
+		if (!(tx_ops_flag & PMD_FEATURE_TM)) {
+			p->params.tx_ops_data.ops_flag |= PMD_FEATURE_TM;
+			p->params.tx_ops_data.ops[nb_tx_ops++] = run_tm;
+		}
+	}
+
+	if (fc_enabled(dev)) {
+		/** Set run time rx ops data */
+		if (!(rx_ops_flag & PMD_FEATURE_FC)) {
+			p->params.rx_ops_data.ops_flag |= PMD_FEATURE_FC;
+			p->params.rx_ops_data.ops[nb_rx_ops++] = run_fc;
+		}
 	}
 
 	dev->data->dev_link.link_status = ETH_LINK_UP;
@@ -194,6 +250,12 @@ pmd_dev_start(struct rte_eth_dev *dev)
 		dev->rx_pkt_burst = hard_dev->rx_pkt_burst;
 	}
 
+	/* Set default if no tx ops */
+	if (nb_tx_ops == 0)
+		p->params.tx_ops_data.ops[nb_tx_ops++] = run_default;
+
+	p->params.rx_ops_data.nb_ops = nb_rx_ops;
+	p->params.tx_ops_data.nb_ops = nb_tx_ops;
 	return 0;
 }
 
@@ -234,6 +296,18 @@ pmd_tm_ops_get(struct rte_eth_dev *dev, void *arg)
 	return 0;
 }
 
+static int
+pmd_flow_ops_get(struct rte_eth_dev *dev,
+	__rte_unused enum rte_filter_type filter_type,
+	__rte_unused enum rte_filter_op filter_op,
+	void *arg)
+{
+	*(const struct rte_flow_ops **)arg =
+		(fc_enabled(dev)) ? &pmd_flow_ops : NULL;
+
+	return 0;
+}
+
 static const struct eth_dev_ops pmd_ops = {
 	.dev_configure = pmd_dev_configure,
 	.dev_start = pmd_dev_start,
@@ -244,10 +318,11 @@ static const struct eth_dev_ops pmd_ops = {
 	.rx_queue_setup = pmd_rx_queue_setup,
 	.tx_queue_setup = pmd_tx_queue_setup,
 	.tm_ops_get = pmd_tm_ops_get,
+	.filter_ctrl = pmd_flow_ops_get,
 };
 
 static uint16_t
-pmd_rx_pkt_burst(void *rxq,
+pmd_rx_pkt_burst_default(void *rxq,
 	struct rte_mbuf **rx_pkts,
 	uint16_t nb_pkts)
 {
@@ -260,6 +335,19 @@ pmd_rx_pkt_burst(void *rxq,
 }
 
 static uint16_t
+pmd_rx_pkt_burst(void *rxq,
+	struct rte_mbuf **rx_pkts,
+	uint16_t nb_pkts)
+{
+	struct rte_ring *rx_queue = rxq;
+
+	return (uint16_t)rte_ring_sc_dequeue_burst(rx_queue,
+			(void **)rx_pkts,
+			nb_pkts,
+			NULL);
+}
+
+static uint16_t
 pmd_tx_pkt_burst(void *txq,
 	struct rte_mbuf **tx_pkts,
 	uint16_t nb_pkts)
@@ -406,16 +494,138 @@ run_tm(struct rte_eth_dev *dev)
 	return 0;
 }
 
-int
-rte_pmd_softnic_run(uint16_t port_id)
+static __rte_always_inline int
+fc_flow_classifier_run(struct pmd_internals *p,
+	struct rte_mbuf **pkts,
+	const uint16_t nb_pkts,
+	uint32_t pkt_offset)
+{
+	struct rte_flow_action_queue *queue;
+	struct fc_table_entry *entry;
+	uint64_t pkts_mask = RTE_LEN2MASK(nb_pkts, uint64_t);
+	uint32_t nb_tables = p->soft.fc.nb_tables;
+	uint32_t table_mask = p->soft.fc.table_mask;
+	uint64_t action_mask;
+	uint32_t *ptr, i, j;
+	int ret = -EINVAL;
+
+	for (i = 0; i < nb_tables; i++) {
+		if (table_mask & (1LU << i)) {
+			struct fc_table *table = &p->soft.fc.tables[i];
+			uint64_t lookup_hit_mask;
+
+			ret = table->ops.f_lookup(table->h_table,
+				pkts, pkts_mask, &lookup_hit_mask,
+				(void **)p->soft.fc.entries);
+			if (ret)
+				return ret;
+
+			if (lookup_hit_mask) {
+				for (j = 0; j < nb_pkts; j++) {
+					uint64_t pkt_mask = 1LLU << j;
+
+					if ((lookup_hit_mask & pkt_mask) == 0)
+						continue;
+
+					/* Meta-data */
+					enum rte_flow_action_type act_type =
+						RTE_FLOW_ACTION_TYPE_QUEUE;
+					entry = p->soft.fc.entries[j];
+					action_mask = entry->action_mask;
+
+					if (action_mask & (1LLU << act_type)) {
+						queue = &entry->act.queue;
+						ptr = RTE_PKT_METADATA_PTR(
+							pkts[j], pkt_offset);
+						*ptr = queue->index;
+					}
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+static __rte_always_inline int
+run_fc(struct rte_eth_dev *dev)
+{
+	struct pmd_internals *p = dev->data->dev_private;
+
+	/* Persistent Context: Read Only (update not required) */
+	struct rte_eth_dev *hard_dev = DEV_HARD(p);
+	struct rte_mbuf **pkts = p->soft.fc.pkts;
+	uint16_t nb_rx_queues = hard_dev->data->nb_rx_queues;
+
+	/* Not part of the persistent context */
+	uint16_t nb_pkts, i;
+
+	/* Hard device RXQ read, Soft Device RXQ write */
+	for (i = 0; i < nb_rx_queues; i++) {
+		struct rte_ring *rxq = dev->data->rx_queues[i];
+
+		/* Hard device RXQ read */
+		nb_pkts = rte_eth_rx_burst(p->hard.port_id,
+					 i, pkts, DEFAULT_BURST_SIZE);
+
+		/* Classify packets when complete burst is available */
+		if (nb_pkts) {
+			fc_flow_classifier_run(p, pkts, nb_pkts,
+				FC_ACTION_METADATA_OFFSET);
+
+			/* Soft device RXQ write */
+			rte_ring_enqueue_burst(rxq, (void **)pkts,
+				nb_pkts, NULL);
+		}
+	}
+	return 0;
+}
+
+static int
+rte_pmd_softnic_rx_run(uint16_t port_id)
 {
 	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	struct pmd_internals *p = dev->data->dev_private;
+
+	uint32_t nb_rx_ops = p->params.rx_ops_data.nb_ops;
+	uint32_t i;
+
+#ifdef RTE_LIBRTE_ETHDEV_DEBUG
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, 0);
+#endif
+
+	for (i = 0; i < nb_rx_ops; i++)
+		p->params.rx_ops_data.ops[i](dev);
+
+	return 0;
+}
+
+static int
+rte_pmd_softnic_tx_run(uint16_t port_id)
+{
+	struct rte_eth_dev *dev = &rte_eth_devices[port_id];
+	struct pmd_internals *p = dev->data->dev_private;
+
+	uint32_t nb_tx_ops = p->params.tx_ops_data.nb_ops;
+	uint32_t i;
 
 #ifdef RTE_LIBRTE_ETHDEV_DEBUG
 	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, 0);
 #endif
 
-	return (tm_used(dev)) ? run_tm(dev) : run_default(dev);
+	for (i = 0; i < nb_tx_ops; i++)
+		p->params.tx_ops_data.ops[i](dev);
+
+	return 0;
+}
+
+int
+rte_pmd_softnic_run(uint16_t port_id)
+{
+	rte_pmd_softnic_rx_run(port_id);
+	rte_pmd_softnic_tx_run(port_id);
+
+	return 0;
 }
 
 static struct ether_addr eth_addr = { .addr_bytes = {0} };
@@ -502,6 +712,20 @@ pmd_init(struct pmd_params *params, int numa_node)
 		}
 	}
 
+	/* Flow Classification */
+	if (params->soft.flags & PMD_FEATURE_FC) {
+		status = fc_init(p, params, numa_node);
+		if (status) {
+			if (p->params.soft.flags & PMD_FEATURE_TM)
+				tm_free(p);
+
+			default_free(p);
+			free(p->params.hard.name);
+			rte_free(p);
+			return NULL;
+		}
+	}
+
 	return p;
 }
 
@@ -511,6 +735,9 @@ pmd_free(struct pmd_internals *p)
 	if (p->params.soft.flags & PMD_FEATURE_TM)
 		tm_free(p);
 
+	if (p->params.soft.flags & PMD_FEATURE_FC)
+		fc_free(p);
+
 	default_free(p);
 
 	free(p->params.hard.name);
@@ -539,9 +766,14 @@ pmd_ethdev_register(struct rte_vdev_device *vdev,
 		return -ENOMEM;
 
 	/* dev */
-	soft_dev->rx_pkt_burst = (params->soft.intrusive) ?
-		NULL : /* set up later */
-		pmd_rx_pkt_burst;
+	if (params->soft.intrusive == 0 &&
+		(params->soft.flags & PMD_FEATURE_FC))
+		soft_dev->rx_pkt_burst = pmd_rx_pkt_burst;
+	else if (params->soft.intrusive == 0)
+		soft_dev->rx_pkt_burst = pmd_rx_pkt_burst_default;
+	else
+		soft_dev->rx_pkt_burst = NULL; /* Set up later */
+
 	soft_dev->tx_pkt_burst = pmd_tx_pkt_burst;
 	soft_dev->tx_pkt_prepare = NULL;
 	soft_dev->dev_ops = &pmd_ops;
@@ -586,10 +818,63 @@ get_uint32(const char *key __rte_unused, const char *value, void *extra_args)
 	return 0;
 }
 
+static uint32_t
+get_hex_val(char c)
+{
+	switch (c) {
+	case '0': case '1': case '2': case '3': case '4': case '5':
+	case '6': case '7': case '8': case '9':
+		return c - '0';
+	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+		return c - 'A' + 10;
+	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+		return c - 'a' + 10;
+	default:
+		return 0;
+	}
+}
+
+static int
+get_hex_string(char *src, uint8_t *dst, uint32_t *size)
+{
+	char *c;
+	uint32_t len, i;
+
+	/* Check input parameters */
+	if ((src == NULL) ||
+		(dst == NULL) ||
+		(size == NULL) ||
+		(*size == 0))
+		return -1;
+
+	len = strlen(src);
+	if (((len & 3) != 0) ||
+		(len > (*size) * 2))
+		return -1;
+	*size = len / 2;
+
+	for (c = src; *c != 0; c++) {
+		if ((((*c) >= '0') && ((*c) <= '9')) ||
+			(((*c) >= 'A') && ((*c) <= 'F')) ||
+			(((*c) >= 'a') && ((*c) <= 'f')))
+			continue;
+
+		return -1;
+	}
+
+	/* Convert chars to bytes */
+	for (i = 0; i < *size; i++)
+		dst[i] = get_hex_val(src[2 * i]) * 16 +
+			get_hex_val(src[2 * i + 1]);
+
+	return 0;
+}
+
 static int
 pmd_parse_args(struct pmd_params *p, const char *name, const char *params)
 {
 	struct rte_kvargs *kvlist;
+	char key_mask_str[SOFTNIC_SOFT_FC_FLOW_RULE_KEY_MAX_SIZE * 2 + 1];
 	int i, ret;
 
 	kvlist = rte_kvargs_parse(params, pmd_valid_args);
@@ -606,6 +891,11 @@ pmd_parse_args(struct pmd_params *p, const char *name, const char *params)
 		p->soft.tm.qsize[i] = SOFTNIC_SOFT_TM_QUEUE_SIZE;
 	p->soft.tm.enq_bsz = SOFTNIC_SOFT_TM_ENQ_BSZ;
 	p->soft.tm.deq_bsz = SOFTNIC_SOFT_TM_DEQ_BSZ;
+	p->soft.fc.nb_rules = SOFTNIC_SOFT_FC_NB_RULES;
+	p->soft.fc.key_size = SOFTNIC_SOFT_FC_FLOW_RULE_KEY_SIZE;
+	p->soft.fc.key_offset = SOFTNIC_SOFT_FC_FLOW_RULE_KEY_OFFSET;
+	snprintf(key_mask_str, sizeof(key_mask_str),
+		"%s", "00FF0000FFFFFFFFFFFFFFFFFFFFFFFF");
 	p->hard.tx_queue_id = SOFTNIC_HARD_TX_QUEUE_ID;
 
 	/* SOFT: TM (optional) */
@@ -718,6 +1008,75 @@ pmd_parse_args(struct pmd_params *p, const char *name, const char *params)
 		p->soft.flags |= PMD_FEATURE_TM;
 	}
 
+	/* SOFT: FC (optional) */
+	if (rte_kvargs_count(kvlist, PMD_PARAM_SOFT_FC) == 1) {
+		char *s;
+
+		ret = rte_kvargs_process(kvlist, PMD_PARAM_SOFT_FC,
+			&get_string, &s);
+		if (ret < 0)
+			goto out_free;
+
+		if (strcmp(s, "on") == 0)
+			p->soft.flags |= PMD_FEATURE_FC;
+		else if (strcmp(s, "off") == 0)
+			p->soft.flags &= ~PMD_FEATURE_FC;
+		else
+			ret = -EINVAL;
+
+		free(s);
+		if (ret)
+			goto out_free;
+	}
+
+	/* SOFT: FC number of flow rules (optional) */
+	if (rte_kvargs_count(kvlist, PMD_PARAM_SOFT_FC_NB_RULES) == 1) {
+		ret = rte_kvargs_process(kvlist, PMD_PARAM_SOFT_FC_NB_RULES,
+			&get_uint32, &p->soft.fc.nb_rules);
+		if (ret < 0)
+			goto out_free;
+
+		p->soft.flags |= PMD_FEATURE_FC;
+	}
+
+	/* SOFT: FC flow rule key size (optional) */
+	if (rte_kvargs_count(kvlist, PMD_PARAM_SOFT_FC_FLOW_KEY_SIZE) == 1) {
+		ret = rte_kvargs_process(kvlist,
+				PMD_PARAM_SOFT_FC_FLOW_KEY_SIZE,
+				&get_uint32, &p->soft.fc.key_size);
+		if (ret < 0)
+			goto out_free;
+
+		p->soft.flags |= PMD_FEATURE_FC;
+	}
+
+	/* SOFT: FC flow rule key offset (optional) */
+	if (rte_kvargs_count(kvlist, PMD_PARAM_SOFT_FC_FLOW_KEY_OFFSET) == 1) {
+		ret = rte_kvargs_process(kvlist,
+				PMD_PARAM_SOFT_FC_FLOW_KEY_OFFSET,
+				&get_uint32, &p->soft.fc.key_offset);
+		if (ret < 0)
+			goto out_free;
+
+		p->soft.flags |= PMD_FEATURE_FC;
+	}
+
+	/* SOFT: FC flow rule key mask (optional) */
+	if (rte_kvargs_count(kvlist, PMD_PARAM_SOFT_FC_FLOW_KEY_MASK) == 1) {
+		ret = rte_kvargs_process(kvlist,
+				PMD_PARAM_SOFT_FC_FLOW_KEY_MASK,
+				&get_string, key_mask_str);
+		if (ret < 0)
+			goto out_free;
+
+		ret = get_hex_string(key_mask_str, p->soft.fc.key_mask,
+			&p->soft.fc.key_size);
+		if (ret < 0)
+			goto out_free;
+
+		p->soft.flags |= PMD_FEATURE_FC;
+	}
+
 	/* HARD: name (mandatory) */
 	if (rte_kvargs_count(kvlist, PMD_PARAM_HARD_NAME) == 1) {
 		ret = rte_kvargs_process(kvlist, PMD_PARAM_HARD_NAME,
@@ -786,6 +1145,13 @@ pmd_probe(struct rte_vdev_device *vdev)
 			return status;
 	}
 
+	if (p.soft.flags & PMD_FEATURE_FC) {
+		status = fc_params_check(&p);
+
+		if (status)
+			return status;
+	}
+
 	/* Allocate and initialize soft ethdev private data */
 	dev_private = pmd_init(&p, numa_node);
 	if (dev_private == NULL)
@@ -847,5 +1213,10 @@ RTE_PMD_REGISTER_PARAM_STRING(net_softnic,
 	PMD_PARAM_SOFT_TM_QSIZE3 "=<int> "
 	PMD_PARAM_SOFT_TM_ENQ_BSZ "=<int> "
 	PMD_PARAM_SOFT_TM_DEQ_BSZ "=<int> "
+	PMD_PARAM_SOFT_FC	 "=on|off "
+	PMD_PARAM_SOFT_FC_NB_RULES "=<int> "
+	PMD_PARAM_SOFT_FC_FLOW_KEY_SIZE "=<int> "
+	PMD_PARAM_SOFT_FC_FLOW_KEY_OFFSET "=<int> "
+	PMD_PARAM_SOFT_FC_FLOW_KEY_MASK "=<string> "
 	PMD_PARAM_HARD_NAME "=<string> "
 	PMD_PARAM_HARD_TX_QUEUE_ID "=<int>");
diff --git a/drivers/net/softnic/rte_eth_softnic.h b/drivers/net/softnic/rte_eth_softnic.h
index b49e582..1dd17b1 100644
--- a/drivers/net/softnic/rte_eth_softnic.h
+++ b/drivers/net/softnic/rte_eth_softnic.h
@@ -56,6 +56,22 @@ extern "C" {
 #define SOFTNIC_SOFT_TM_DEQ_BSZ				24
 #endif
 
+#ifndef SOFTNIC_SOFT_FC_NB_RULES
+#define SOFTNIC_SOFT_FC_NB_RULES			4096
+#endif
+
+#ifndef SOFTNIC_SOFT_FC_FLOW_RULE_KEY_SIZE
+#define SOFTNIC_SOFT_FC_FLOW_RULE_KEY_SIZE		16
+#endif
+
+#ifndef SOFTNIC_SOFT_FC_FLOW_RULE_KEY_OFFSET
+#define SOFTNIC_SOFT_FC_FLOW_RULE_KEY_OFFSET		278
+#endif
+
+#ifndef SOFTNIC_SOFT_FC_FLOW_RULE_KEY_MAX_SIZE
+#define SOFTNIC_SOFT_FC_FLOW_RULE_KEY_MAX_SIZE		64
+#endif
+
 #ifndef SOFTNIC_HARD_TX_QUEUE_ID
 #define SOFTNIC_HARD_TX_QUEUE_ID			0
 #endif
diff --git a/drivers/net/softnic/rte_eth_softnic_fc.c b/drivers/net/softnic/rte_eth_softnic_fc.c
new file mode 100644
index 0000000..83f7fd3
--- /dev/null
+++ b/drivers/net/softnic/rte_eth_softnic_fc.c
@@ -0,0 +1,377 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2017 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_malloc.h>
+
+#include "rte_eth_softnic_internals.h"
+
+static inline uint64_t
+hash_xor_key8(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0;
+
+	xor0 = seed ^ (k[0] & m[0]);
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key16(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key24(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+
+	xor0 ^= k[2] & m[2];
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key32(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0, xor1;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+	xor1 = (k[2] & m[2]) ^ (k[3] & m[3]);
+
+	xor0 ^= xor1;
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key40(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0, xor1;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+	xor1 = (k[2] & m[2]) ^ (k[3] & m[3]);
+
+	xor0 ^= xor1;
+
+	xor0 ^= k[4] & m[4];
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key48(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0, xor1, xor2;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+	xor1 = (k[2] & m[2]) ^ (k[3] & m[3]);
+	xor2 = (k[4] & m[4]) ^ (k[5] & m[5]);
+
+	xor0 ^= xor1;
+
+	xor0 ^= xor2;
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key56(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0, xor1, xor2;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+	xor1 = (k[2] & m[2]) ^ (k[3] & m[3]);
+	xor2 = (k[4] & m[4]) ^ (k[5] & m[5]);
+
+	xor0 ^= xor1;
+	xor2 ^= k[6] & m[6];
+
+	xor0 ^= xor2;
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+static inline uint64_t
+hash_xor_key64(void *key, void *mask, __rte_unused uint32_t key_size,
+	uint64_t seed)
+{
+	uint64_t *k = key;
+	uint64_t *m = mask;
+	uint64_t xor0, xor1, xor2, xor3;
+
+	xor0 = ((k[0] & m[0]) ^ seed) ^ (k[1] & m[1]);
+	xor1 = (k[2] & m[2]) ^ (k[3] & m[3]);
+	xor2 = (k[4] & m[4]) ^ (k[5] & m[5]);
+	xor3 = (k[6] & m[6]) ^ (k[7] & m[7]);
+
+	xor0 ^= xor1;
+	xor2 ^= xor3;
+
+	xor0 ^= xor2;
+
+	return (xor0 >> 32) ^ xor0;
+}
+
+rte_table_hash_op_hash hash_func[] = {
+	hash_xor_key8,
+	hash_xor_key16,
+	hash_xor_key24,
+	hash_xor_key32,
+	hash_xor_key40,
+	hash_xor_key48,
+	hash_xor_key56,
+	hash_xor_key64
+};
+
+int
+fc_params_check(struct pmd_params *params)
+{
+	/* nb_max_rules */
+	if (params->soft.fc.nb_rules == 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+fc_table_check_params(struct pmd_internals *p,
+	struct fc_table_params *params)
+{
+	/* Number of tables */
+	if (p->soft.fc.nb_tables == FC_TABLE_HASH_KEY_MAX)
+		return -EINVAL;
+
+	/* Parameter */
+	if (params == NULL)
+		return -EINVAL;
+
+	/* Ops */
+	if (params->ops == NULL)
+		return -EINVAL;
+
+	if (params->ops->f_create == NULL)
+		return -EINVAL;
+
+	if (params->ops->f_lookup == NULL)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+rte_flow_classify_table_create(struct pmd_internals *p,
+	struct fc_table *table,
+	struct fc_table_params *params,
+	int numa_node)
+{
+	void *h_table;
+	uint32_t entry_size;
+	int ret;
+
+	/* Check input arguments */
+	ret = fc_table_check_params(p, params);
+	if (ret != 0)
+		return ret;
+
+	/* calculate table entry size */
+	entry_size = sizeof(struct fc_table_entry);
+
+	/* Create the table */
+	h_table = params->ops->f_create(params->arg_create, numa_node,
+		entry_size);
+	if (h_table == NULL)
+		return -EINVAL;
+
+	table->type = params->type;
+	p->soft.fc.table_mask |= table->type;
+	p->soft.fc.nb_tables++;
+
+	/* Save input parameters */
+	memcpy(&table->ops, params->ops, sizeof(struct rte_table_ops));
+
+	/* Initialize table internal data structure */
+	table->entry_size = entry_size;
+	table->h_table = h_table;
+
+	return 0;
+}
+
+static int
+fc_tables_create(struct pmd_internals *p,
+	struct pmd_params *params, int numa_node)
+{
+	int ret;
+	struct fc_table *table;
+
+	/** Flow Classification: Hash table (Key size = 8 bytes) */
+	struct rte_table_hash_params table_hash_params = {
+		.name = params->soft.name,
+		.key_size = params->soft.fc.key_size,
+		.key_offset = params->soft.fc.key_offset,
+		.key_mask = params->soft.fc.key_mask,
+		.n_keys = params->soft.fc.nb_rules,
+		.n_buckets = rte_align32pow2(params->soft.fc.nb_rules / 4),
+		.f_hash = hash_func[(params->soft.fc.key_size / 8) - 1],
+		.seed = 0,
+	};
+
+	struct fc_table_params table_params = {
+		.ops = NULL,
+		.arg_create = &table_hash_params,
+		.type = FC_TABLE_HASH_KEY_MAX,
+	};
+
+	switch (params->soft.fc.key_size) {
+	case 8:
+		table_params.ops = &rte_table_hash_key8_ext_ops;
+		table_params.type = FC_TABLE_HASH_KEY8;
+		break;
+
+	case 16:
+		table_params.ops = &rte_table_hash_key16_ext_ops;
+		table_params.type = FC_TABLE_HASH_KEY16;
+		break;
+
+	default:
+		table_params.ops = &rte_table_hash_ext_ops;
+		table_params.type = FC_TABLE_HASH_KEY_EXT;
+	}
+
+	table = &p->soft.fc.tables[table_params.type];
+
+	/** Create hash table */
+	ret = rte_flow_classify_table_create(p, table,
+			&table_params,
+			numa_node);
+	if (ret)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void
+fc_flow_init(struct pmd_internals *p)
+{
+	/* Initialize classifier flow list */
+	TAILQ_INIT(&p->soft.fc.flow_list);
+}
+
+static void
+fc_flow_uninit(struct pmd_internals *p)
+{
+	/** Remove all rules from the list */
+	for ( ; ; ) {
+		struct rte_flow *flow;
+
+		flow = TAILQ_FIRST(&p->soft.fc.flow_list);
+		if (flow == NULL)
+			break;
+
+		TAILQ_REMOVE(&p->soft.fc.flow_list, flow, node);
+		free(flow);
+	}
+}
+
+int
+fc_init(struct pmd_internals *p,
+	struct pmd_params *params,
+	int numa_node)
+{
+	int ret;
+
+	memset(&p->soft.fc, 0, sizeof(p->soft.fc));
+
+	p->soft.fc.pkts = rte_zmalloc_socket(params->soft.name,
+		2 * DEFAULT_BURST_SIZE * sizeof(struct rte_mbuf *),
+		0,
+		numa_node);
+	if (p->soft.fc.pkts == NULL)
+		return -ENOMEM;
+
+	/** Create all tables/filters for classifiers */
+	ret = fc_tables_create(p, params, numa_node);
+	if (ret) {
+		rte_free(p->soft.fc.pkts);
+		return ret;
+	}
+
+	fc_flow_init(p);
+
+	return 0;
+}
+
+void
+fc_free(struct pmd_internals *p)
+{
+	fc_flow_uninit(p);
+	rte_free(p->soft.fc.pkts);
+}
+
+const struct rte_flow_ops pmd_flow_ops = {
+	.validate = NULL,
+	.create = NULL,
+	.destroy = NULL,
+	.flush = NULL,
+};
diff --git a/drivers/net/softnic/rte_eth_softnic_internals.h b/drivers/net/softnic/rte_eth_softnic_internals.h
index 1f75806..9e59d62 100644
--- a/drivers/net/softnic/rte_eth_softnic_internals.h
+++ b/drivers/net/softnic/rte_eth_softnic_internals.h
@@ -39,7 +39,10 @@
 #include <rte_mbuf.h>
 #include <rte_sched.h>
 #include <rte_ethdev.h>
+#include <rte_table.h>
+#include <rte_table_hash.h>
 #include <rte_tm_driver.h>
+#include <rte_flow_driver.h>
 
 #include "rte_eth_softnic.h"
 
@@ -48,13 +51,30 @@
  */
 
 enum pmd_feature {
-	PMD_FEATURE_TM = 1, /**< Traffic Management (TM) */
+	/** Traffic Management (TM) */
+	PMD_FEATURE_TM = 1 << 0,
+
+	/** Flow Clasification (FC) */
+	PMD_FEATURE_FC = 1 << 1,
 };
 
 #ifndef INTRUSIVE
 #define INTRUSIVE					0
 #endif
 
+#ifndef MAX_NB_OPS
+#define MAX_NB_OPS					32
+#endif
+
+#ifndef FC_FLOW_KEY_MAX_SIZE
+#define FC_FLOW_KEY_MAX_SIZE		64
+#endif
+
+/**
+ * Softnic operations
+ */
+typedef int (*soft_ops)(struct rte_eth_dev *dev);
+
 struct pmd_params {
 	/** Parameters for the soft device (to be created) */
 	struct {
@@ -77,6 +97,14 @@ struct pmd_params {
 			uint32_t enq_bsz; /**< Enqueue burst size */
 			uint32_t deq_bsz; /**< Dequeue burst size */
 		} tm;
+
+		/** Flow Classification (FC) */
+		struct {
+			uint32_t nb_rules; /**< Max number of flow rules */
+			uint32_t key_size; /**< Flow rule key */
+			uint8_t key_mask[FC_FLOW_KEY_MAX_SIZE]; /**< Key mask */
+			uint32_t key_offset; /**< Key offset */
+		} fc;
 	} soft;
 
 	/** Parameters for the hard device (existing) */
@@ -84,6 +112,19 @@ struct pmd_params {
 		char *name; /**< Name */
 		uint16_t tx_queue_id; /**< TX queue ID */
 	} hard;
+
+	/** Parameters for the softnic operations (rx & tx) */
+	struct {
+		uint32_t nb_ops;
+		uint32_t ops_flag;
+		soft_ops ops[MAX_NB_OPS];
+	} rx_ops_data;
+
+	struct {
+		uint32_t nb_ops;
+		uint32_t ops_flag;
+		soft_ops ops[MAX_NB_OPS];
+	} tx_ops_data;
 };
 
 /**
@@ -225,6 +266,102 @@ struct tm_internals {
 };
 
 /**
+ * Flow Classification (FC) Internals
+ */
+#ifndef FC_ACTION_METADATA_OFFSET
+#define FC_ACTION_METADATA_OFFSET			128
+#endif
+
+#ifndef FC_FLOW_PACKET_BURST_SIZE_MAX
+#define FC_FLOW_PACKET_BURST_SIZE_MAX		64
+#endif
+
+#define RTE_PKT_METADATA_PTR(pkt, offset)         \
+		(&((uint32_t *)(pkt))[offset])
+
+/**
+ * FC flow (PMD-specific definition of struct rte_flow)
+ */
+struct rte_flow {
+	TAILQ_ENTRY(rte_flow) node;
+	struct rte_flow_attr attr; /**< Attributes. */
+	struct rte_flow_item *pattern; /**< Items. */
+	struct rte_flow_action *actions; /**< Actions. */
+};
+
+TAILQ_HEAD(fc_flow_list, rte_flow);
+
+/* FC tables type */
+enum fc_table_hash_type {
+	FC_TABLE_HASH_KEY8 = 0,
+	FC_TABLE_HASH_KEY16,
+	FC_TABLE_HASH_KEY_EXT,
+	FC_TABLE_HASH_KEY_MAX,
+};
+
+/**
+ * FC Flow classification table parameters
+ */
+struct fc_table_params {
+	/** Table operations (specific to each table type) */
+	struct rte_table_ops *ops;
+	/** Opaque param to be passed to the table create operation when
+	 * invoked
+	 */
+	void *arg_create;
+
+	/** Classifier table type */
+	enum fc_table_hash_type type;
+};
+
+/**
+ * Head format for the table entry of table. For any given
+ * FC table, all table entries should have the same size and format. For
+ * any given table, the table entry has to start with a head of this
+ * structure, which contains actions mask and their associated
+ * meta-data.
+ */
+struct fc_table_entry {
+	/** Flow action mask */
+	uint64_t action_mask;
+
+	struct action {
+		/** Assign packets to a given queue index */
+		struct rte_flow_action_queue queue;
+
+		/** Traffic metering and policing (MTR) */
+		struct rte_flow_action_meter meter;
+
+	} act;
+};
+
+/** FC Flow table */
+struct fc_table {
+	/* Input parameters */
+	struct rte_table_ops ops;
+	uint32_t entry_size;
+	enum fc_table_hash_type type;
+
+	/* Handle to the low-level table object */
+	void *h_table;
+};
+
+struct fc_internals {
+	/** Flow rule list */
+	struct fc_flow_list flow_list;
+	uint32_t n_flow_rules;
+
+	/** Flow classification table */
+	struct fc_table tables[FC_TABLE_HASH_KEY_MAX];
+	uint32_t nb_tables;
+	uint32_t table_mask;
+
+	/** Run-time */
+	struct rte_mbuf **pkts;
+	struct fc_table_entry *entries[FC_FLOW_PACKET_BURST_SIZE_MAX];
+};
+
+/**
  * PMD Internals
  */
 struct pmd_internals {
@@ -235,6 +372,7 @@ struct pmd_internals {
 	struct {
 		struct default_internals def; /**< Default */
 		struct tm_internals tm; /**< Traffic Management */
+		struct fc_internals fc; /**< flow classification */
 	} soft;
 
 	/** Hard device */
@@ -256,6 +394,11 @@ struct pmd_rx_queue {
  */
 extern const struct rte_tm_ops pmd_tm_ops;
 
+/**
+ * Flow API operation
+ */
+extern const struct rte_flow_ops pmd_flow_ops;
+
 int
 tm_params_check(struct pmd_params *params, uint32_t hard_rate);
 
@@ -288,4 +431,21 @@ tm_used(struct rte_eth_dev *dev)
 		p->soft.tm.h.n_tm_nodes[TM_NODE_LEVEL_PORT];
 }
 
+int
+fc_params_check(struct pmd_params *params);
+
+int
+fc_init(struct pmd_internals *p, struct pmd_params *params, int numa_node);
+
+void
+fc_free(struct pmd_internals *p);
+
+static inline int
+fc_enabled(struct rte_eth_dev *dev)
+{
+	struct pmd_internals *p = dev->data->dev_private;
+
+	return (p->params.soft.flags & PMD_FEATURE_FC);
+}
+
 #endif /* __INCLUDE_RTE_ETH_SOFTNIC_INTERNALS_H__ */
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index 6a6a745..72d684c 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -60,7 +60,9 @@ _LDLIBS-y += -L$(RTE_SDK_BIN)/lib
 #
 _LDLIBS-$(CONFIG_RTE_LIBRTE_FLOW_CLASSIFY)  += -lrte_flow_classify
 _LDLIBS-$(CONFIG_RTE_LIBRTE_PIPELINE)       += -lrte_pipeline
+_LDLIBS-$(CONFIG_RTE_LIBRTE_TABLE)            += --whole-archive
 _LDLIBS-$(CONFIG_RTE_LIBRTE_TABLE)          += -lrte_table
+_LDLIBS-$(CONFIG_RTE_LIBRTE_TABLE)            += --no-whole-archive
 _LDLIBS-$(CONFIG_RTE_LIBRTE_PORT)           += -lrte_port
 
 _LDLIBS-$(CONFIG_RTE_LIBRTE_PDUMP)          += -lrte_pdump
