Subject: [PATCH 3/3] Add support for RX packet classification in a network device

From: [email-masked]
Date: Mon, 22 Dec 2008 10:45:57 -0800

From: Santwona Behera <[email-masked]>
Subject: [PATCH 3/3] Add TCAM classification configuration

Signed-off-by: Santwona Behera <[email-masked]>
---
 drivers/net/niu.c |  653 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 drivers/net/niu.h |   14 +-
 2 files changed, 640 insertions(+), 27 deletions(-)

diff --git a/drivers/net/niu.c b/drivers/net/niu.c
index 1b6f548..964902b 100644
--- a/drivers/net/niu.c
+++ b/drivers/net/niu.c
@@ -2873,7 +2873,6 @@ static int tcam_user_ip_class_enable(struct niu *np, unsigned long class,
 	return 0;
 }
 
-#if 0
 static int tcam_user_ip_class_set(struct niu *np, unsigned long class,
 				  int ipv6, u64 protocol_id,
 				  u64 tos_mask, u64 tos_val)
@@ -2901,7 +2900,6 @@ static int tcam_user_ip_class_set(struct niu *np, unsigned long class,
 
 	return 0;
 }
-#endif
 
 static int tcam_early_init(struct niu *np)
 {
@@ -3168,6 +3166,27 @@ static int niu_set_tcam_key(struct niu *np, unsigned long class_code, u64 key)
 	return 0;
 }
 
+/* Entries for the ports are interleaved in the TCAM */
+static u16 tcam_get_index(struct niu *np, u16 idx)
+{
+	/* One entry reserved for IP fragment rule */
+	if (idx >= (np->clas.tcam_sz - 1))
+		idx = 0;
+	return (np->clas.tcam_top + ((idx+1) * np->parent->num_ports));
+}
+
+static u16 tcam_get_size(struct niu *np)
+{
+	/* One entry reserved for IP fragment rule */
+	return np->clas.tcam_sz - 1;
+}
+
+static u16 tcam_get_valid_entry_cnt(struct niu *np)
+{
+	/* One entry reserved for IP fragment rule */
+	return np->clas.tcam_valid_entries - 1;
+}
+
 static void niu_rx_skb_append(struct sk_buff *skb, struct page *page,
 			      u32 offset, u32 size)
 {
@@ -4837,8 +4856,7 @@ static int niu_set_ip_frag_rule(struct niu *np)
 	struct niu_tcam_entry *tp;
 	int index, err;
 
-	/* XXX fix this allocation scheme XXX */
-	index = cp->tcam_index;
+	index = cp->tcam_top;
 	tp = &parent->tcam[index];
 
 	/* Note that the noport bit is the same in both ipv4 and
@@ -4855,6 +4873,8 @@ static int niu_set_ip_frag_rule(struct niu *np)
 	err = tcam_assoc_write(np, index, tp->assoc_data);
 	if (err)
 		return err;
+	tp->valid = 1;
+	cp->tcam_valid_entries++;
 
 	return 0;
 }
@@ -6698,6 +6718,75 @@ static int niu_get_eeprom(struct net_device *dev,
 	return 0;
 }
 
+static void niu_ethflow_to_l3proto(int flow_type, u8 *pid)
+{
+	switch (flow_type) {
+	case TCP_V4_FLOW:
+	case TCP_V6_FLOW:
+		*pid = IPPROTO_TCP;
+		break;
+	case UDP_V4_FLOW:
+	case UDP_V6_FLOW:
+		*pid = IPPROTO_UDP;
+		break;
+	case SCTP_V4_FLOW:
+	case SCTP_V6_FLOW:
+		*pid = IPPROTO_SCTP;
+		break;
+	case AH_V4_FLOW:
+	case AH_V6_FLOW:
+		*pid = IPPROTO_AH;
+		break;
+	case ESP_V4_FLOW:
+	case ESP_V6_FLOW:
+		*pid = IPPROTO_ESP;
+		break;
+	default:
+		*pid = 0;
+		break;
+	}
+}
+
+static int niu_class_to_ethflow(u64 class, int *flow_type)
+{
+	switch (class) {
+	case CLASS_CODE_TCP_IPV4:
+		*flow_type = TCP_V4_FLOW;
+		break;
+	case CLASS_CODE_UDP_IPV4:
+		*flow_type = UDP_V4_FLOW;
+		break;
+	case CLASS_CODE_AH_ESP_IPV4:
+		*flow_type = AH_V4_FLOW;
+		break;
+	case CLASS_CODE_SCTP_IPV4:
+		*flow_type = SCTP_V4_FLOW;
+		break;
+	case CLASS_CODE_TCP_IPV6:
+		*flow_type = TCP_V6_FLOW;
+		break;
+	case CLASS_CODE_UDP_IPV6:
+		*flow_type = UDP_V6_FLOW;
+		break;
+	case CLASS_CODE_AH_ESP_IPV6:
+		*flow_type = AH_V6_FLOW;
+		break;
+	case CLASS_CODE_SCTP_IPV6:
+		*flow_type = SCTP_V6_FLOW;
+		break;
+	case CLASS_CODE_USER_PROG1:
+	case CLASS_CODE_USER_PROG2:
+	case CLASS_CODE_USER_PROG3:
+	case CLASS_CODE_USER_PROG4:
+		*flow_type = IP_USER_FLOW;
+		break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
 static int niu_ethflow_to_class(int flow_type, u64 *class)
 {
 	switch (flow_type) {
@@ -6707,7 +6796,8 @@ static int niu_ethflow_to_class(int flow_type, u64 *class)
 	case UDP_V4_FLOW:
 		*class = CLASS_CODE_UDP_IPV4;
 		break;
-	case AH_ESP_V4_FLOW:
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
 		*class = CLASS_CODE_AH_ESP_IPV4;
 		break;
 	case SCTP_V4_FLOW:
@@ -6719,7 +6809,8 @@ static int niu_ethflow_to_class(int flow_type, u64 *class)
 	case UDP_V6_FLOW:
 		*class = CLASS_CODE_UDP_IPV6;
 		break;
-	case AH_ESP_V6_FLOW:
+	case AH_V6_FLOW:
+	case ESP_V6_FLOW:
 		*class = CLASS_CODE_AH_ESP_IPV6;
 		break;
 	case SCTP_V6_FLOW:
@@ -6736,8 +6827,6 @@ static u64 niu_flowkey_to_ethflow(u64 flow_key)
 {
 	u64 ethflow = 0;
 
-	if (flow_key & FLOW_KEY_PORT)
-		ethflow |= RXH_DEV_PORT;
 	if (flow_key & FLOW_KEY_L2DA)
 		ethflow |= RXH_L2DA;
 	if (flow_key & FLOW_KEY_VLAN)
@@ -6761,8 +6850,6 @@ static int niu_ethflow_to_flowkey(u64 ethflow, u64 *flow_key)
 {
 	u64 key = 0;
 
-	if (ethflow & RXH_DEV_PORT)
-		key |= FLOW_KEY_PORT;
 	if (ethflow & RXH_L2DA)
 		key |= FLOW_KEY_L2DA;
 	if (ethflow & RXH_VLAN)
@@ -6784,41 +6871,255 @@ static int niu_ethflow_to_flowkey(u64 ethflow, u64 *flow_key)
 
 }
 
-static int niu_get_hash_opts(struct net_device *dev, struct ethtool_rxnfc *cmd)
+static int niu_get_hash_opts(struct niu *np, struct ethtool_rxnfc *nfc)
 {
-	struct niu *np = netdev_priv(dev);
 	u64 class;
 
-	cmd->data = 0;
+	nfc->data = 0;
 
-	if (!niu_ethflow_to_class(cmd->flow_type, &class))
+	if (!niu_ethflow_to_class(nfc->flow_type, &class))
 		return -EINVAL;
 
 	if (np->parent->tcam_key[class - CLASS_CODE_USER_PROG1] &
 	    TCAM_KEY_DISC)
-		cmd->data = RXH_DISCARD;
+		nfc->data = RXH_DISCARD;
 	else
-
-		cmd->data = niu_flowkey_to_ethflow(np->parent->flow_key[class -
+		nfc->data = niu_flowkey_to_ethflow(np->parent->flow_key[class -
 						      CLASS_CODE_USER_PROG1]);
 	return 0;
 }
 
-static int niu_set_hash_opts(struct net_device *dev, struct ethtool_rxnfc *cmd)
+static void niu_get_ip4fs_from_tcam_key(struct niu_tcam_entry *tp,
+					struct ethtool_rx_flow_spec *fsp)
+{
+
+	fsp->h_u.tcp_ip4_spec.ip4src = (tp->key[3] & TCAM_V4KEY3_SADDR) >>
+		TCAM_V4KEY3_SADDR_SHIFT;
+	fsp->h_u.tcp_ip4_spec.ip4dst = (tp->key[3] & TCAM_V4KEY3_DADDR) >>
+		TCAM_V4KEY3_DADDR_SHIFT;
+	fsp->m_u.tcp_ip4_spec.ip4src = (tp->key_mask[3] & TCAM_V4KEY3_SADDR) >>
+		TCAM_V4KEY3_SADDR_SHIFT;
+	fsp->m_u.tcp_ip4_spec.ip4dst = (tp->key_mask[3] & TCAM_V4KEY3_DADDR) >>
+		TCAM_V4KEY3_DADDR_SHIFT;
+
+	fsp->h_u.tcp_ip4_spec.tos = (tp->key[2] & TCAM_V4KEY2_TOS) >>
+		TCAM_V4KEY2_TOS_SHIFT;
+	fsp->m_u.tcp_ip4_spec.tos = (tp->key_mask[2] & TCAM_V4KEY2_TOS) >>
+		TCAM_V4KEY2_TOS_SHIFT;
+
+	switch (fsp->flow_type) {
+	case TCP_V4_FLOW:
+	case UDP_V4_FLOW:
+	case SCTP_V4_FLOW:
+		fsp->h_u.tcp_ip4_spec.psrc =
+			((tp->key[2] & TCAM_V4KEY2_PORT_SPI) >>
+			 TCAM_V4KEY2_PORT_SPI_SHIFT) >> 16;
+		fsp->h_u.tcp_ip4_spec.pdst =
+			((tp->key[2] & TCAM_V4KEY2_PORT_SPI) >>
+			 TCAM_V4KEY2_PORT_SPI_SHIFT) & 0xffff;
+		fsp->m_u.tcp_ip4_spec.psrc =
+			((tp->key_mask[2] & TCAM_V4KEY2_PORT_SPI) >>
+			 TCAM_V4KEY2_PORT_SPI_SHIFT) >> 16;
+		fsp->m_u.tcp_ip4_spec.pdst =
+			((tp->key_mask[2] & TCAM_V4KEY2_PORT_SPI) >>
+			 TCAM_V4KEY2_PORT_SPI_SHIFT) & 0xffff;
+		break;
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
+		fsp->h_u.ah_ip4_spec.spi =
+			(tp->key[2] & TCAM_V4KEY2_PORT_SPI) >>
+			TCAM_V4KEY2_PORT_SPI_SHIFT;
+		fsp->m_u.ah_ip4_spec.spi =
+			(tp->key_mask[2] & TCAM_V4KEY2_PORT_SPI) >>
+			TCAM_V4KEY2_PORT_SPI_SHIFT;
+		break;
+	case IP_USER_FLOW:
+		fsp->h_u.usr_ip4_spec.l4_4_bytes =
+			(tp->key[2] & TCAM_V4KEY2_PORT_SPI) >>
+			TCAM_V4KEY2_PORT_SPI_SHIFT;
+		fsp->m_u.usr_ip4_spec.l4_4_bytes =
+			(tp->key_mask[2] & TCAM_V4KEY2_PORT_SPI) >>
+			TCAM_V4KEY2_PORT_SPI_SHIFT;
+
+		fsp->h_u.usr_ip4_spec.proto =
+			(tp->key[2] & TCAM_V4KEY2_PROTO) >>
+			TCAM_V4KEY2_PROTO_SHIFT;
+		fsp->m_u.usr_ip4_spec.proto =
+			(tp->key_mask[2] & TCAM_V4KEY2_PROTO) >>
+			TCAM_V4KEY2_PROTO_SHIFT;
+
+		fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
+		break;
+	default:
+		break;
+	}
+}
+
+static int niu_get_ethtool_tcam_entry(struct niu *np,
+				      struct ethtool_rxnfc *nfc)
+{
+	struct niu_parent *parent = np->parent;
+	struct niu_tcam_entry *tp;
+	struct ethtool_rx_flow_spec *fsp = &nfc->fs;
+	u16 idx;
+	u64 class;
+	int ret = 0;
+
+	idx = tcam_get_index(np, (u16)nfc->fs.location);
+
+	tp = &parent->tcam[idx];
+	if (!tp->valid) {
+		pr_info(PFX "niu%d: %s entry [%d] invalid for idx[%d]\n",
+		parent->index, np->dev->name, (u16)nfc->fs.location, idx);
+		return -EINVAL;
+	}
+
+	/* fill the flow spec entry */
+	class = (tp->key[0] & TCAM_V4KEY0_CLASS_CODE) >>
+		TCAM_V4KEY0_CLASS_CODE_SHIFT;
+	ret = niu_class_to_ethflow(class, &fsp->flow_type);
+
+	if (ret < 0) {
+		pr_info(PFX "niu%d: %s niu_class_to_ethflow failed\n",
+		parent->index, np->dev->name);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (fsp->flow_type == AH_V4_FLOW || fsp->flow_type == AH_V6_FLOW) {
+		u32 proto = (tp->key[2] & TCAM_V4KEY2_PROTO) >>
+			TCAM_V4KEY2_PROTO_SHIFT;
+		if (proto == IPPROTO_ESP) {
+			if (fsp->flow_type == AH_V4_FLOW)
+				fsp->flow_type = ESP_V4_FLOW;
+			else
+				fsp->flow_type = ESP_V6_FLOW;
+		}
+	}
+
+	switch (fsp->flow_type) {
+	case TCP_V4_FLOW:
+	case UDP_V4_FLOW:
+	case SCTP_V4_FLOW:
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
+		niu_get_ip4fs_from_tcam_key(tp, fsp);
+		break;
+	case TCP_V6_FLOW:
+	case UDP_V6_FLOW:
+	case SCTP_V6_FLOW:
+	case AH_V6_FLOW:
+	case ESP_V6_FLOW:
+		/* Not yet implemented */
+		ret = -EINVAL;
+		break;
+	case IP_USER_FLOW:
+		niu_get_ip4fs_from_tcam_key(tp, fsp);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	if (ret < 0)
+		goto out;
+
+	if (tp->assoc_data & TCAM_ASSOCDATA_DISC)
+		fsp->ring_cookie = RX_CLS_FLOW_DISC;
+	else
+		fsp->ring_cookie = (tp->assoc_data & TCAM_ASSOCDATA_OFFSET) >>
+			TCAM_ASSOCDATA_OFFSET_SHIFT;
+
+	/* put the tcam size here */
+	nfc->data = tcam_get_size(np);
+out:
+	return ret;
+}
+
+static int niu_get_ethtool_tcam_all(struct niu *np,
+				    struct ethtool_rxnfc *nfc,
+				    u32 *rule_locs)
+{
+	struct niu_parent *parent = np->parent;
+	struct niu_tcam_entry *tp;
+	int i, idx, cnt;
+	u16 n_entries;
+	unsigned long flags;
+	
+
+	/* put the tcam size here */
+	nfc->data = tcam_get_size(np);
+
+	niu_lock_parent(np, flags);
+	n_entries = tcam_get_valid_entry_cnt(np);
+	for (cnt = 0, i = 0; i < nfc->data; i++) {
+		idx = tcam_get_index(np, i);
+		tp = &parent->tcam[idx];
+		if (!tp->valid)
+			continue;
+		rule_locs[cnt] = i;
+		cnt++;
+	}
+	niu_unlock_parent(np, flags);
+
+	if (n_entries != cnt) {
+		/* print warning, this should not happen */
+		pr_info(PFX "niu%d: %s In niu_get_ethtool_tcam_all, "
+			"n_entries[%d] != cnt[%d]!!!\n\n",
+			np->parent->index, np->dev->name, n_entries, cnt);
+	}
+
+	return 0;
+}
+
+static int niu_get_tcam_ent_cnt(struct net_device *dev)
 {
 	struct niu *np = netdev_priv(dev);
+
+	return tcam_get_valid_entry_cnt(np);
+}
+
+static int niu_get_nfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
+		       void *rule_locs)
+{
+	struct niu *np = netdev_priv(dev);
+	int ret = 0;
+
+	switch (cmd->cmd) {
+	case ETHTOOL_GRXFH:
+		ret = niu_get_hash_opts(np, cmd);
+		break;
+	case ETHTOOL_GRXRINGS:
+		cmd->data = np->num_rx_rings;
+		break;
+	case ETHTOOL_GRXCLSRULE:
+		ret = niu_get_ethtool_tcam_entry(np, cmd);
+		break;
+	case ETHTOOL_GRXCLSRLALL:
+		ret = niu_get_ethtool_tcam_all(np, cmd, (u32 *)rule_locs);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int niu_set_hash_opts(struct niu *np, struct ethtool_rxnfc *nfc)
+{
 	u64 class;
 	u64 flow_key = 0;
 	unsigned long flags;
 
-	if (!niu_ethflow_to_class(cmd->flow_type, &class))
+	if (!niu_ethflow_to_class(nfc->flow_type, &class))
 		return -EINVAL;
 
 	if (class < CLASS_CODE_USER_PROG1 ||
 	    class > CLASS_CODE_SCTP_IPV6)
 		return -EINVAL;
 
-	if (cmd->data & RXH_DISCARD) {
+	if (nfc->data & RXH_DISCARD) {
 		niu_lock_parent(np, flags);
 		flow_key = np->parent->tcam_key[class -
 					       CLASS_CODE_USER_PROG1];
@@ -6843,7 +7144,7 @@ static int niu_set_hash_opts(struct net_device *dev, struct ethtool_rxnfc *cmd)
 		}
 	}
 
-	if (!niu_ethflow_to_flowkey(cmd->data, &flow_key))
+	if (!niu_ethflow_to_flowkey(nfc->data, &flow_key))
 		return -EINVAL;
 
 	niu_lock_parent(np, flags);
@@ -6854,6 +7155,308 @@ static int niu_set_hash_opts(struct net_device *dev, struct ethtool_rxnfc *cmd)
 	return 0;
 }
 
+static void niu_get_tcamkey_from_ip4fs(struct ethtool_rx_flow_spec *fsp,
+				       struct niu_tcam_entry *tp,
+				       int l2_rdc_tab, u64 class)
+{
+	u8 pid = 0;
+
+	tp->key[0] = class << TCAM_V4KEY0_CLASS_CODE_SHIFT;
+	tp->key_mask[0] = TCAM_V4KEY0_CLASS_CODE;
+	tp->key[1] = (u64)l2_rdc_tab << TCAM_V4KEY1_L2RDCNUM_SHIFT;
+	tp->key_mask[1] = TCAM_V4KEY1_L2RDCNUM;
+
+	tp->key[3] = (u64)fsp->h_u.tcp_ip4_spec.ip4src <<
+		TCAM_V4KEY3_SADDR_SHIFT;
+	tp->key[3] |= fsp->h_u.tcp_ip4_spec.ip4dst;
+
+	tp->key_mask[3] = (u64)fsp->m_u.tcp_ip4_spec.ip4src <<
+		TCAM_V4KEY3_SADDR_SHIFT;
+	tp->key_mask[3] |= fsp->m_u.tcp_ip4_spec.ip4dst;
+
+	tp->key[2] |= ((u64)fsp->h_u.tcp_ip4_spec.tos <<
+		       TCAM_V4KEY2_TOS_SHIFT);
+	tp->key_mask[2] |= ((u64)fsp->m_u.tcp_ip4_spec.tos <<
+			    TCAM_V4KEY2_TOS_SHIFT);
+	switch (fsp->flow_type) {
+	case TCP_V4_FLOW:
+	case UDP_V4_FLOW:
+	case SCTP_V4_FLOW:
+		tp->key[2] |= (((u64)fsp->h_u.tcp_ip4_spec.psrc << 16)
+			       | fsp->h_u.tcp_ip4_spec.pdst);
+		tp->key_mask[2] |= (((u64)fsp->m_u.tcp_ip4_spec.psrc << 16)
+				    | fsp->m_u.tcp_ip4_spec.pdst);
+		niu_ethflow_to_l3proto(fsp->flow_type, &pid);
+		break;
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
+		tp->key[2] |= fsp->h_u.ah_ip4_spec.spi;
+		tp->key_mask[2] |= fsp->m_u.ah_ip4_spec.spi;
+		niu_ethflow_to_l3proto(fsp->flow_type, &pid);
+		break;
+	case IP_USER_FLOW:
+		tp->key[2] |= fsp->h_u.usr_ip4_spec.l4_4_bytes;
+		tp->key_mask[2] |= fsp->m_u.usr_ip4_spec.l4_4_bytes;
+		pid = fsp->h_u.usr_ip4_spec.proto;
+		break;
+	default:
+		break;
+	}
+
+	tp->key[2] |= ((u64)pid << TCAM_V4KEY2_PROTO_SHIFT);
+	if (pid) {
+		tp->key_mask[2] |= TCAM_V4KEY2_PROTO;
+	}
+}
+
+static int niu_add_ethtool_tcam_entry(struct niu *np,
+				      struct ethtool_rxnfc *nfc)
+{
+	struct niu_parent *parent = np->parent;
+	struct niu_tcam_entry *tp;
+	struct ethtool_rx_flow_spec *fsp = &nfc->fs;
+	struct niu_rdc_tables *rdc_table = &parent->rdc_group_cfg[np->port];
+	int l2_rdc_table = rdc_table->first_table_num;
+	u16 idx;
+	u64 class;
+	unsigned long flags;
+	int err, ret;
+
+	ret = 0;
+
+	idx = nfc->fs.location;
+	if (idx >= tcam_get_size(np))
+		return -EINVAL;
+
+	if (fsp->flow_type == IP_USER_FLOW) {
+		int i;
+		int add_usr_cls = 0;
+		int ipv6 = 0;
+		struct ethtool_usrip4_spec *uspec = &fsp->h_u.usr_ip4_spec;
+		struct ethtool_usrip4_spec *umask = &fsp->m_u.usr_ip4_spec;
+
+		niu_lock_parent(np, flags);
+
+		for (i = 0; i < NIU_L3_PROG_CLS; i++) {
+			if (parent->l3_cls[i]) {
+				if (uspec->proto == parent->l3_cls_pid[i]) {
+					class = parent->l3_cls[i];
+					parent->l3_cls_refcnt[i]++;
+					add_usr_cls = 1;
+					break;
+				}
+			} else {
+				/* Program new user IP class */
+				switch (i) {
+				case 0:
+					class = CLASS_CODE_USER_PROG1;
+					break;
+				case 1:
+					class = CLASS_CODE_USER_PROG2;
+					break;
+				case 2:
+					class = CLASS_CODE_USER_PROG3;
+					break;
+				case 3:
+					class = CLASS_CODE_USER_PROG4;
+					break;
+				default:
+					break;
+				}
+				if (uspec->ip_ver == ETH_RX_NFC_IP6)
+					ipv6 = 1;
+				ret = tcam_user_ip_class_set(np, class, ipv6,
+							     uspec->proto,
+							     uspec->tos,
+							     umask->tos);
+				if (ret)
+					goto out;
+
+				ret = tcam_user_ip_class_enable(np, class, 1);
+				if (ret)
+					goto out;
+				parent->l3_cls[i] = class;
+				parent->l3_cls_pid[i] = uspec->proto;
+				parent->l3_cls_refcnt[i]++;
+				add_usr_cls = 1;
+				break;
+			}
+		}
+		if (!add_usr_cls) {
+			pr_info(PFX "niu%d: %s niu_add_ethtool_tcam_entry: "
+				"Could not find/insert class for pid %d\n",
+				parent->index, np->dev->name, uspec->proto);
+			ret = -EINVAL;
+			goto out;
+		}
+		niu_unlock_parent(np, flags);
+	} else {
+		if (!niu_ethflow_to_class(fsp->flow_type, &class)) {
+			return -EINVAL;
+		}
+	}
+
+	niu_lock_parent(np, flags);
+
+	idx = tcam_get_index(np, idx);
+	tp = &parent->tcam[idx];
+
+	memset(tp, 0, sizeof(*tp));
+
+	/* fill in the tcam key and mask */
+	switch (fsp->flow_type) {
+	case TCP_V4_FLOW:
+	case UDP_V4_FLOW:
+	case SCTP_V4_FLOW:
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
+		niu_get_tcamkey_from_ip4fs(fsp, tp, l2_rdc_table, class);
+		break;
+	case TCP_V6_FLOW:
+	case UDP_V6_FLOW:
+	case SCTP_V6_FLOW:
+	case AH_V6_FLOW:
+	case ESP_V6_FLOW:
+		/* Not yet implemented */
+		pr_info(PFX "niu%d: %s In niu_add_ethtool_tcam_entry: "
+			"flow %d for IPv6 not implemented\n\n",
+			parent->index, np->dev->name, fsp->flow_type);
+		ret = -EINVAL;
+		goto out;
+	case IP_USER_FLOW:
+		if (fsp->h_u.usr_ip4_spec.ip_ver == ETH_RX_NFC_IP4) {
+			niu_get_tcamkey_from_ip4fs(fsp, tp, l2_rdc_table,
+						   class);
+		} else {
+			/* Not yet implemented */
+			pr_info(PFX "niu%d: %s In niu_add_ethtool_tcam_entry: "
+			"usr flow for IPv6 not implemented\n\n",
+			parent->index, np->dev->name);
+			ret = -EINVAL;
+			goto out;
+		}
+		break;
+	default:
+		pr_info(PFX "niu%d: %s In niu_add_ethtool_tcam_entry: "
+			"Unknown flow type %d\n\n",
+			parent->index, np->dev->name, fsp->flow_type);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* fill in the assoc data */
+	if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
+		tp->assoc_data = TCAM_ASSOCDATA_DISC;
+	} else {
+		tp->assoc_data = (TCAM_ASSOCDATA_TRES_USE_OFFSET |
+				  (fsp->ring_cookie <<
+				   TCAM_ASSOCDATA_OFFSET_SHIFT));
+	}
+
+	err = tcam_write(np, idx, tp->key, tp->key_mask);
+	if (err) {
+		ret = -EINVAL;
+		goto out;
+	}
+	err = tcam_assoc_write(np, idx, tp->assoc_data);
+	if (err) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* validate the entry */
+	tp->valid = 1;
+	np->clas.tcam_valid_entries++;
+out:
+	niu_unlock_parent(np, flags);
+
+	return ret;
+}
+
+static int niu_del_ethtool_tcam_entry(struct niu *np, u32 loc)
+{
+	struct niu_parent *parent = np->parent;
+	struct niu_tcam_entry *tp;
+	u16 idx;
+	unsigned long flags;
+	u64 class;
+	int ret = 0;
+
+	if (loc >= tcam_get_size(np))
+		return -EINVAL;
+
+	niu_lock_parent(np, flags);
+
+	idx = tcam_get_index(np, loc);
+	tp = &parent->tcam[idx];
+
+	/* if the entry is of a user defined class, then update*/
+	class = (tp->key[0] & TCAM_V4KEY0_CLASS_CODE) >>
+		TCAM_V4KEY0_CLASS_CODE_SHIFT;
+
+	if (class >= CLASS_CODE_USER_PROG1 && class <= CLASS_CODE_USER_PROG4) {
+		int i;
+		for (i = 0; i < NIU_L3_PROG_CLS; i++) {
+			if (parent->l3_cls[i] == class) {
+				parent->l3_cls_refcnt[i]--;
+				if (!parent->l3_cls_refcnt[i]) {
+					/* disable class */
+					ret = tcam_user_ip_class_enable(np,
+									class,
+									0);
+					if (ret)
+						goto out;
+					parent->l3_cls[i] = 0;
+					parent->l3_cls_pid[i] = 0;
+				}
+				break;
+			}
+		}
+		if (i == NIU_L3_PROG_CLS) {
+			pr_info(PFX "niu%d: %s In niu_del_ethtool_tcam_entry,"
+				"Usr class 0x%llx not found \n",
+				parent->index, np->dev->name, class);
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	ret = tcam_flush(np, idx);
+	if (ret)
+		goto out;
+
+	/* invalidate the entry */
+	tp->valid = 0;
+	np->clas.tcam_valid_entries--;
+out:
+	niu_unlock_parent(np, flags);
+
+	return ret;
+}
+
+static int niu_set_nfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
+{
+	struct niu *np = netdev_priv(dev);
+	int ret = 0;
+
+	switch (cmd->cmd) {
+	case ETHTOOL_SRXFH:
+		ret = niu_set_hash_opts(np, cmd);
+		break;
+	case ETHTOOL_SRXCLSRLINS:
+		ret = niu_add_ethtool_tcam_entry(np, cmd);
+		break;
+	case ETHTOOL_SRXCLSRLDEL:
+		ret = niu_del_ethtool_tcam_entry(np, cmd->fs.location);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
 static const struct {
 	const char string[ETH_GSTRING_LEN];
 } niu_xmac_stat_keys[] = {
@@ -7084,8 +7687,9 @@ static const struct ethtool_ops niu_ethtool_ops = {
 	.get_stats_count	= niu_get_stats_count,
 	.get_ethtool_stats	= niu_get_ethtool_stats,
 	.phys_id		= niu_phys_id,
-	.get_rxhash		= niu_get_hash_opts,
-	.set_rxhash		= niu_set_hash_opts,
+	.get_rxrule_cnt		= niu_get_tcam_ent_cnt,
+	.get_rxnfc		= niu_get_nfc,
+	.set_rxnfc		= niu_set_nfc,
 };
 
 static int niu_ldg_assign_ldn(struct niu *np, struct niu_parent *parent,
@@ -8155,7 +8759,8 @@ static int __devinit niu_classifier_swstate_init(struct niu *np)
 	niudbg(PROBE, "niu_classifier_swstate_init: num_tcam(%d)\n",
 	       np->parent->tcam_num_entries);
 
-	cp->tcam_index = (u16) np->port;
+	cp->tcam_top = (u16) np->port;
+	cp->tcam_sz = np->parent->tcam_num_entries / np->parent->num_ports;
 	cp->h1_init = 0xffffffff;
 	cp->h2_init = 0xffff;
 
diff --git a/drivers/net/niu.h b/drivers/net/niu.h
index 180ca8a..e876eea 100644
--- a/drivers/net/niu.h
+++ b/drivers/net/niu.h
@@ -3004,7 +3004,9 @@ struct niu_classifier {
 	struct niu_altmac_rdc	alt_mac_mappings[16];
 	struct niu_vlan_rdc	vlan_mappings[ENET_VLAN_TBL_NUM_ENTRIES];
 
-	u16			tcam_index;
+	u16			tcam_top;
+	u16			tcam_sz;
+	u16			tcam_valid_entries;
 	u16			num_alt_mac_mappings;
 
 	u32			h1_init;
@@ -3040,6 +3042,7 @@ struct phy_probe_info {
 };
 
 struct niu_tcam_entry {
+	u8			valid;
 	u64			key[4];
 	u64			key_mask[4];
 	u64			assoc_data;
@@ -3107,10 +3110,15 @@ struct niu_parent {
 	struct phy_probe_info	phy_probe_info;
 
 	struct niu_tcam_entry	tcam[NIU_TCAM_ENTRIES_MAX];
-	u64			l2_cls[2];
-	u64			l3_cls[4];
+
+#define	NIU_L2_PROG_CLS		2
+#define	NIU_L3_PROG_CLS		4
+	u64			l2_cls[NIU_L2_PROG_CLS];
+	u64			l3_cls[NIU_L3_PROG_CLS];
 	u64			tcam_key[12];
 	u64			flow_key[12];
+	u16			l3_cls_refcnt[NIU_L3_PROG_CLS];
+	u8			l3_cls_pid[NIU_L3_PROG_CLS];
 };
 
 struct niu_ops {
-- 
1.6.0.4