Commit 22300354 authored by Alexander Duyck's avatar Alexander Duyck Committed by Jakub Kicinski
Browse files

eth: fbnic: support n-tuple filters



Add ethtool -n / -N support. Support only "un-ordered" rule sets
(RX_CLS_LOC_ANY), just for simplicity of the code. It's unclear
anyone actually cares about the rule ordering.

Signed-off-by: default avatarAlexander Duyck <alexanderduyck@fb.com>
Link: https://patch.msgid.link/20250206235334.1425329-6-kuba@kernel.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 3a265bd6
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -605,8 +605,11 @@ enum {
	FBNIC_RPC_ACT_TBL0_DEST_EI	= 4,
};

#define FBNIC_RPC_ACT_TBL0_Q_SEL		CSR_BIT(4)
#define FBNIC_RPC_ACT_TBL0_Q_ID			CSR_GENMASK(15, 8)
#define FBNIC_RPC_ACT_TBL0_DMA_HINT		CSR_GENMASK(24, 16)
#define FBNIC_RPC_ACT_TBL0_TS_ENA		CSR_BIT(28)
#define FBNIC_RPC_ACT_TBL0_ACT_TBL_IDX		CSR_BIT(29)
#define FBNIC_RPC_ACT_TBL0_RSS_CTXT_ID		CSR_BIT(30)

#define FBNIC_RPC_ACT_TBL1_DEFAULT	0x0840b		/* 0x2102c */
+646 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
#include <net/ipv6.h>

#include "fbnic.h"
#include "fbnic_netdev.h"
@@ -218,11 +219,234 @@ fbnic_get_rss_hash_opts(struct fbnic_net *fbn, struct ethtool_rxnfc *cmd)
	return 0;
}

static int fbnic_get_cls_rule_all(struct fbnic_net *fbn,
				  struct ethtool_rxnfc *cmd,
				  u32 *rule_locs)
{
	struct fbnic_dev *fbd = fbn->fbd;
	int i, cnt = 0;

	/* Report maximum rule count */
	cmd->data = FBNIC_RPC_ACT_TBL_NFC_ENTRIES;

	for (i = 0; i < FBNIC_RPC_ACT_TBL_NFC_ENTRIES; i++) {
		int idx = i + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
		struct fbnic_act_tcam *act_tcam;

		act_tcam = &fbd->act_tcam[idx];
		if (act_tcam->state != FBNIC_TCAM_S_VALID)
			continue;

		if (rule_locs) {
			if (cnt == cmd->rule_cnt)
				return -EMSGSIZE;

			rule_locs[cnt] = i;
		}

		cnt++;
	}

	return cnt;
}

static int fbnic_get_cls_rule(struct fbnic_net *fbn, struct ethtool_rxnfc *cmd)
{
	struct ethtool_rx_flow_spec *fsp;
	struct fbnic_dev *fbd = fbn->fbd;
	struct fbnic_act_tcam *act_tcam;
	int idx;

	fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;

	if (fsp->location >= FBNIC_RPC_ACT_TBL_NFC_ENTRIES)
		return -EINVAL;

	idx = fsp->location + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
	act_tcam = &fbd->act_tcam[idx];

	if (act_tcam->state != FBNIC_TCAM_S_VALID)
		return -EINVAL;

	/* Report maximum rule count */
	cmd->data = FBNIC_RPC_ACT_TBL_NFC_ENTRIES;

	/* Set flow type field */
	if (!(act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_IP_VALID)) {
		fsp->flow_type = ETHER_FLOW;
		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX,
			       act_tcam->mask.tcam[1])) {
			struct fbnic_mac_addr *mac_addr;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX,
					act_tcam->value.tcam[1]);
			mac_addr = &fbd->mac_addr[idx];

			ether_addr_copy(fsp->h_u.ether_spec.h_dest,
					mac_addr->value.addr8);
			eth_broadcast_addr(fsp->m_u.ether_spec.h_dest);
		}
	} else if (act_tcam->value.tcam[1] &
		   FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID) {
		fsp->flow_type = IPV6_USER_FLOW;
		fsp->h_u.usr_ip6_spec.l4_proto = IPPROTO_IPV6;
		fsp->m_u.usr_ip6_spec.l4_proto = 0xff;

		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX,
			       act_tcam->mask.tcam[0])) {
			struct fbnic_ip_addr *ip_addr;
			int i;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX,
					act_tcam->value.tcam[0]);
			ip_addr = &fbd->ipo_src[idx];

			for (i = 0; i < 4; i++) {
				fsp->h_u.usr_ip6_spec.ip6src[i] =
					ip_addr->value.s6_addr32[i];
				fsp->m_u.usr_ip6_spec.ip6src[i] =
					~ip_addr->mask.s6_addr32[i];
			}
		}

		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX,
			       act_tcam->mask.tcam[0])) {
			struct fbnic_ip_addr *ip_addr;
			int i;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX,
					act_tcam->value.tcam[0]);
			ip_addr = &fbd->ipo_dst[idx];

			for (i = 0; i < 4; i++) {
				fsp->h_u.usr_ip6_spec.ip6dst[i] =
					ip_addr->value.s6_addr32[i];
				fsp->m_u.usr_ip6_spec.ip6dst[i] =
					~ip_addr->mask.s6_addr32[i];
			}
		}
	} else if ((act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_IP_IS_V6)) {
		if (act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_L4_VALID) {
			if (act_tcam->value.tcam[1] &
			    FBNIC_RPC_TCAM_ACT1_L4_IS_UDP)
				fsp->flow_type = UDP_V6_FLOW;
			else
				fsp->flow_type = TCP_V6_FLOW;
			fsp->h_u.tcp_ip6_spec.psrc =
				cpu_to_be16(act_tcam->value.tcam[3]);
			fsp->m_u.tcp_ip6_spec.psrc =
				cpu_to_be16(~act_tcam->mask.tcam[3]);
			fsp->h_u.tcp_ip6_spec.pdst =
				cpu_to_be16(act_tcam->value.tcam[4]);
			fsp->m_u.tcp_ip6_spec.pdst =
				cpu_to_be16(~act_tcam->mask.tcam[4]);
		} else {
			fsp->flow_type = IPV6_USER_FLOW;
		}

		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
			       act_tcam->mask.tcam[0])) {
			struct fbnic_ip_addr *ip_addr;
			int i;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
					act_tcam->value.tcam[0]);
			ip_addr = &fbd->ip_src[idx];

			for (i = 0; i < 4; i++) {
				fsp->h_u.usr_ip6_spec.ip6src[i] =
					ip_addr->value.s6_addr32[i];
				fsp->m_u.usr_ip6_spec.ip6src[i] =
					~ip_addr->mask.s6_addr32[i];
			}
		}

		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
			       act_tcam->mask.tcam[0])) {
			struct fbnic_ip_addr *ip_addr;
			int i;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
					act_tcam->value.tcam[0]);
			ip_addr = &fbd->ip_dst[idx];

			for (i = 0; i < 4; i++) {
				fsp->h_u.usr_ip6_spec.ip6dst[i] =
					ip_addr->value.s6_addr32[i];
				fsp->m_u.usr_ip6_spec.ip6dst[i] =
					~ip_addr->mask.s6_addr32[i];
			}
		}
	} else {
		if (act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_L4_VALID) {
			if (act_tcam->value.tcam[1] &
			    FBNIC_RPC_TCAM_ACT1_L4_IS_UDP)
				fsp->flow_type = UDP_V4_FLOW;
			else
				fsp->flow_type = TCP_V4_FLOW;
			fsp->h_u.tcp_ip4_spec.psrc =
				cpu_to_be16(act_tcam->value.tcam[3]);
			fsp->m_u.tcp_ip4_spec.psrc =
				cpu_to_be16(~act_tcam->mask.tcam[3]);
			fsp->h_u.tcp_ip4_spec.pdst =
				cpu_to_be16(act_tcam->value.tcam[4]);
			fsp->m_u.tcp_ip4_spec.pdst =
				cpu_to_be16(~act_tcam->mask.tcam[4]);
		} else {
			fsp->flow_type = IPV4_USER_FLOW;
			fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
		}

		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
			       act_tcam->mask.tcam[0])) {
			struct fbnic_ip_addr *ip_addr;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
					act_tcam->value.tcam[0]);
			ip_addr = &fbd->ip_src[idx];

			fsp->h_u.usr_ip4_spec.ip4src =
				ip_addr->value.s6_addr32[3];
			fsp->m_u.usr_ip4_spec.ip4src =
				~ip_addr->mask.s6_addr32[3];
		}

		if (!FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
			       act_tcam->mask.tcam[0])) {
			struct fbnic_ip_addr *ip_addr;

			idx = FIELD_GET(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
					act_tcam->value.tcam[0]);
			ip_addr = &fbd->ip_dst[idx];

			fsp->h_u.usr_ip4_spec.ip4dst =
				ip_addr->value.s6_addr32[3];
			fsp->m_u.usr_ip4_spec.ip4dst =
				~ip_addr->mask.s6_addr32[3];
		}
	}

	/* Record action */
	if (act_tcam->dest & FBNIC_RPC_ACT_TBL0_DROP)
		fsp->ring_cookie = RX_CLS_FLOW_DISC;
	else if (act_tcam->dest & FBNIC_RPC_ACT_TBL0_Q_SEL)
		fsp->ring_cookie = FIELD_GET(FBNIC_RPC_ACT_TBL0_Q_ID,
					     act_tcam->dest);
	else
		fsp->flow_type |= FLOW_RSS;

	cmd->rss_context = FIELD_GET(FBNIC_RPC_ACT_TBL0_RSS_CTXT_ID,
				     act_tcam->dest);

	return 0;
}

static int fbnic_get_rxnfc(struct net_device *netdev,
			   struct ethtool_rxnfc *cmd, u32 *rule_locs)
{
	struct fbnic_net *fbn = netdev_priv(netdev);
	int ret = -EOPNOTSUPP;
	u32 special = 0;

	switch (cmd->cmd) {
	case ETHTOOL_GRXRINGS:
@@ -232,6 +456,22 @@ static int fbnic_get_rxnfc(struct net_device *netdev,
	case ETHTOOL_GRXFH:
		ret = fbnic_get_rss_hash_opts(fbn, cmd);
		break;
	case ETHTOOL_GRXCLSRULE:
		ret = fbnic_get_cls_rule(fbn, cmd);
		break;
	case ETHTOOL_GRXCLSRLCNT:
		rule_locs = NULL;
		special = RX_CLS_LOC_SPECIAL;
		fallthrough;
	case ETHTOOL_GRXCLSRLALL:
		ret = fbnic_get_cls_rule_all(fbn, cmd, rule_locs);
		if (ret < 0)
			break;

		cmd->data |= special;
		cmd->rule_cnt = ret;
		ret = 0;
		break;
	}

	return ret;
@@ -272,6 +512,406 @@ fbnic_set_rss_hash_opts(struct fbnic_net *fbn, const struct ethtool_rxnfc *cmd)
	return 0;
}

static int fbnic_cls_rule_any_loc(struct fbnic_dev *fbd)
{
	int i;

	for (i = FBNIC_RPC_ACT_TBL_NFC_ENTRIES; i--;) {
		int idx = i + FBNIC_RPC_ACT_TBL_NFC_OFFSET;

		if (fbd->act_tcam[idx].state != FBNIC_TCAM_S_VALID)
			return i;
	}

	return -ENOSPC;
}

static int fbnic_set_cls_rule_ins(struct fbnic_net *fbn,
				  const struct ethtool_rxnfc *cmd)
{
	u16 flow_value = 0, flow_mask = 0xffff, ip_value = 0, ip_mask = 0xffff;
	u16 sport = 0, sport_mask = ~0, dport = 0, dport_mask = ~0;
	u16 misc = 0, misc_mask = ~0;
	u32 dest = FIELD_PREP(FBNIC_RPC_ACT_TBL0_DEST_MASK,
			      FBNIC_RPC_ACT_TBL0_DEST_HOST);
	struct fbnic_ip_addr *ip_src = NULL, *ip_dst = NULL;
	struct fbnic_mac_addr *mac_addr = NULL;
	struct ethtool_rx_flow_spec *fsp;
	struct fbnic_dev *fbd = fbn->fbd;
	struct fbnic_act_tcam *act_tcam;
	struct in6_addr *addr6, *mask6;
	struct in_addr *addr4, *mask4;
	int hash_idx, location;
	u32 flow_type;
	int idx, j;

	fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;

	if (fsp->location != RX_CLS_LOC_ANY)
		return -EINVAL;
	location = fbnic_cls_rule_any_loc(fbd);
	if (location < 0)
		return location;

	if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
		dest = FBNIC_RPC_ACT_TBL0_DROP;
	} else if (fsp->flow_type & FLOW_RSS) {
		if (cmd->rss_context == 1)
			dest |= FBNIC_RPC_ACT_TBL0_RSS_CTXT_ID;
	} else {
		u32 ring_idx = ethtool_get_flow_spec_ring(fsp->ring_cookie);

		if (ring_idx >= fbn->num_rx_queues)
			return -EINVAL;

		dest |= FBNIC_RPC_ACT_TBL0_Q_SEL |
			FIELD_PREP(FBNIC_RPC_ACT_TBL0_Q_ID, ring_idx);
	}

	idx = location + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
	act_tcam = &fbd->act_tcam[idx];

	/* Do not allow overwriting for now.
	 * To support overwriting rules we will need to add logic to free
	 * any IP or MACDA TCAMs that may be associated with the old rule.
	 */
	if (act_tcam->state != FBNIC_TCAM_S_DISABLED)
		return -EBUSY;

	flow_type = fsp->flow_type & ~(FLOW_EXT | FLOW_RSS);
	hash_idx = fbnic_get_rss_hash_idx(flow_type);

	switch (flow_type) {
	case UDP_V4_FLOW:
udp4_flow:
		flow_value |= FBNIC_RPC_TCAM_ACT1_L4_IS_UDP;
		fallthrough;
	case TCP_V4_FLOW:
tcp4_flow:
		flow_value |= FBNIC_RPC_TCAM_ACT1_L4_VALID;
		flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_L4_IS_UDP |
			       FBNIC_RPC_TCAM_ACT1_L4_VALID);

		sport = be16_to_cpu(fsp->h_u.tcp_ip4_spec.psrc);
		sport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip4_spec.psrc);
		dport = be16_to_cpu(fsp->h_u.tcp_ip4_spec.pdst);
		dport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip4_spec.pdst);
		goto ip4_flow;
	case IP_USER_FLOW:
		if (!fsp->m_u.usr_ip4_spec.proto)
			goto ip4_flow;
		if (fsp->m_u.usr_ip4_spec.proto != 0xff)
			return -EINVAL;
		if (fsp->h_u.usr_ip4_spec.proto == IPPROTO_UDP)
			goto udp4_flow;
		if (fsp->h_u.usr_ip4_spec.proto == IPPROTO_TCP)
			goto tcp4_flow;
		return -EINVAL;
ip4_flow:
		addr4 = (struct in_addr *)&fsp->h_u.usr_ip4_spec.ip4src;
		mask4 = (struct in_addr *)&fsp->m_u.usr_ip4_spec.ip4src;
		if (mask4->s_addr) {
			ip_src = __fbnic_ip4_sync(fbd, fbd->ip_src,
						  addr4, mask4);
			if (!ip_src)
				return -ENOSPC;

			set_bit(idx, ip_src->act_tcam);
			ip_value |= FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
				    FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
					       ip_src - fbd->ip_src);
			ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
				     FBNIC_RPC_TCAM_ACT0_IPSRC_IDX);
		}

		addr4 = (struct in_addr *)&fsp->h_u.usr_ip4_spec.ip4dst;
		mask4 = (struct in_addr *)&fsp->m_u.usr_ip4_spec.ip4dst;
		if (mask4->s_addr) {
			ip_dst = __fbnic_ip4_sync(fbd, fbd->ip_dst,
						  addr4, mask4);
			if (!ip_dst) {
				if (ip_src && ip_src->state == FBNIC_TCAM_S_ADD)
					memset(ip_src, 0, sizeof(*ip_src));
				return -ENOSPC;
			}

			set_bit(idx, ip_dst->act_tcam);
			ip_value |= FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
				    FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
					       ip_dst - fbd->ip_dst);
			ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
				     FBNIC_RPC_TCAM_ACT0_IPDST_IDX);
		}
		flow_value |= FBNIC_RPC_TCAM_ACT1_IP_VALID |
			      FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
		flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_IP_IS_V6 |
			       FBNIC_RPC_TCAM_ACT1_IP_VALID |
			       FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID);
		break;
	case UDP_V6_FLOW:
udp6_flow:
		flow_value |= FBNIC_RPC_TCAM_ACT1_L4_IS_UDP;
		fallthrough;
	case TCP_V6_FLOW:
tcp6_flow:
		flow_value |= FBNIC_RPC_TCAM_ACT1_L4_VALID;
		flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_L4_IS_UDP |
			  FBNIC_RPC_TCAM_ACT1_L4_VALID);

		sport = be16_to_cpu(fsp->h_u.tcp_ip6_spec.psrc);
		sport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip6_spec.psrc);
		dport = be16_to_cpu(fsp->h_u.tcp_ip6_spec.pdst);
		dport_mask = ~be16_to_cpu(fsp->m_u.tcp_ip6_spec.pdst);
		goto ipv6_flow;
	case IPV6_USER_FLOW:
		if (!fsp->m_u.usr_ip6_spec.l4_proto)
			goto ipv6_flow;

		if (fsp->m_u.usr_ip6_spec.l4_proto != 0xff)
			return -EINVAL;
		if (fsp->h_u.usr_ip6_spec.l4_proto == IPPROTO_UDP)
			goto udp6_flow;
		if (fsp->h_u.usr_ip6_spec.l4_proto == IPPROTO_TCP)
			goto tcp6_flow;
		if (fsp->h_u.usr_ip6_spec.l4_proto != IPPROTO_IPV6)
			return -EINVAL;

		addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6src;
		mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6src;
		if (!ipv6_addr_any(mask6)) {
			ip_src = __fbnic_ip6_sync(fbd, fbd->ipo_src,
						  addr6, mask6);
			if (!ip_src)
				return -ENOSPC;

			set_bit(idx, ip_src->act_tcam);
			ip_value |=
				FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID |
				FIELD_PREP(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX,
					   ip_src - fbd->ipo_src);
			ip_mask &=
				~(FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID |
				  FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX);
		}

		addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6dst;
		mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6dst;
		if (!ipv6_addr_any(mask6)) {
			ip_dst = __fbnic_ip6_sync(fbd, fbd->ipo_dst,
						  addr6, mask6);
			if (!ip_dst) {
				if (ip_src && ip_src->state == FBNIC_TCAM_S_ADD)
					memset(ip_src, 0, sizeof(*ip_src));
				return -ENOSPC;
			}

			set_bit(idx, ip_dst->act_tcam);
			ip_value |=
				FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID |
				FIELD_PREP(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX,
					   ip_dst - fbd->ipo_dst);
			ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID |
				     FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX);
		}

		flow_value |= FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID;
		flow_mask &= FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID;
ipv6_flow:
		addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6src;
		mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6src;
		if (!ip_src && !ipv6_addr_any(mask6)) {
			ip_src = __fbnic_ip6_sync(fbd, fbd->ip_src,
						  addr6, mask6);
			if (!ip_src)
				return -ENOSPC;

			set_bit(idx, ip_src->act_tcam);
			ip_value |= FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
				    FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPSRC_IDX,
					       ip_src - fbd->ip_src);
			ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
				       FBNIC_RPC_TCAM_ACT0_IPSRC_IDX);
		}

		addr6 = (struct in6_addr *)fsp->h_u.usr_ip6_spec.ip6dst;
		mask6 = (struct in6_addr *)fsp->m_u.usr_ip6_spec.ip6dst;
		if (!ip_dst && !ipv6_addr_any(mask6)) {
			ip_dst = __fbnic_ip6_sync(fbd, fbd->ip_dst,
						  addr6, mask6);
			if (!ip_dst) {
				if (ip_src && ip_src->state == FBNIC_TCAM_S_ADD)
					memset(ip_src, 0, sizeof(*ip_src));
				return -ENOSPC;
			}

			set_bit(idx, ip_dst->act_tcam);
			ip_value |= FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
				    FIELD_PREP(FBNIC_RPC_TCAM_ACT0_IPDST_IDX,
					       ip_dst - fbd->ip_dst);
			ip_mask &= ~(FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
				       FBNIC_RPC_TCAM_ACT0_IPDST_IDX);
		}

		flow_value |= FBNIC_RPC_TCAM_ACT1_IP_IS_V6 |
			      FBNIC_RPC_TCAM_ACT1_IP_VALID |
			      FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
		flow_mask &= ~(FBNIC_RPC_TCAM_ACT1_IP_IS_V6 |
			       FBNIC_RPC_TCAM_ACT1_IP_VALID |
			       FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID);
		break;
	case ETHER_FLOW:
		if (!is_zero_ether_addr(fsp->m_u.ether_spec.h_dest)) {
			u8 *addr = fsp->h_u.ether_spec.h_dest;
			u8 *mask = fsp->m_u.ether_spec.h_dest;

			/* Do not allow MAC addr of 0 */
			if (is_zero_ether_addr(addr))
				return -EINVAL;

			/* Only support full MAC address to avoid
			 * conflicts with other MAC addresses.
			 */
			if (!is_broadcast_ether_addr(mask))
				return -EINVAL;

			if (is_multicast_ether_addr(addr))
				mac_addr = __fbnic_mc_sync(fbd, addr);
			else
				mac_addr = __fbnic_uc_sync(fbd, addr);

			if (!mac_addr)
				return -ENOSPC;

			set_bit(idx, mac_addr->act_tcam);
			flow_value |=
				FIELD_PREP(FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX,
					   mac_addr - fbd->mac_addr);
			flow_mask &= ~FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX;
		}

		flow_value |= FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
		flow_mask &= ~FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID;
		break;
	default:
		return -EINVAL;
	}

	/* Write action table values */
	act_tcam->dest = dest;
	act_tcam->rss_en_mask = fbnic_flow_hash_2_rss_en_mask(fbn, hash_idx);

	/* Write IP Match value/mask to action_tcam[0] */
	act_tcam->value.tcam[0] = ip_value;
	act_tcam->mask.tcam[0] = ip_mask;

	/* Write flow type value/mask to action_tcam[1] */
	act_tcam->value.tcam[1] = flow_value;
	act_tcam->mask.tcam[1] = flow_mask;

	/* Write error, DSCP, extra L4 matches to action_tcam[2] */
	act_tcam->value.tcam[2] = misc;
	act_tcam->mask.tcam[2] = misc_mask;

	/* Write source/destination port values */
	act_tcam->value.tcam[3] = sport;
	act_tcam->mask.tcam[3] = sport_mask;
	act_tcam->value.tcam[4] = dport;
	act_tcam->mask.tcam[4] = dport_mask;

	for (j = 5; j < FBNIC_RPC_TCAM_ACT_WORD_LEN; j++)
		act_tcam->mask.tcam[j] = 0xffff;

	act_tcam->state = FBNIC_TCAM_S_UPDATE;
	fsp->location = location;

	if (netif_running(fbn->netdev)) {
		fbnic_write_rules(fbd);
		if (ip_src || ip_dst)
			fbnic_write_ip_addr(fbd);
		if (mac_addr)
			fbnic_write_macda(fbd);
	}

	return 0;
}

static void fbnic_clear_nfc_macda(struct fbnic_net *fbn,
				  unsigned int tcam_idx)
{
	struct fbnic_dev *fbd = fbn->fbd;
	int idx;

	for (idx = ARRAY_SIZE(fbd->mac_addr); idx--;)
		__fbnic_xc_unsync(&fbd->mac_addr[idx], tcam_idx);

	/* Write updates to hardware */
	if (netif_running(fbn->netdev))
		fbnic_write_macda(fbd);
}

static void fbnic_clear_nfc_ip_addr(struct fbnic_net *fbn,
				    unsigned int tcam_idx)
{
	struct fbnic_dev *fbd = fbn->fbd;
	int idx;

	for (idx = ARRAY_SIZE(fbd->ip_src); idx--;)
		__fbnic_ip_unsync(&fbd->ip_src[idx], tcam_idx);
	for (idx = ARRAY_SIZE(fbd->ip_dst); idx--;)
		__fbnic_ip_unsync(&fbd->ip_dst[idx], tcam_idx);
	for (idx = ARRAY_SIZE(fbd->ipo_src); idx--;)
		__fbnic_ip_unsync(&fbd->ipo_src[idx], tcam_idx);
	for (idx = ARRAY_SIZE(fbd->ipo_dst); idx--;)
		__fbnic_ip_unsync(&fbd->ipo_dst[idx], tcam_idx);

	/* Write updates to hardware */
	if (netif_running(fbn->netdev))
		fbnic_write_ip_addr(fbd);
}

static int fbnic_set_cls_rule_del(struct fbnic_net *fbn,
				  const struct ethtool_rxnfc *cmd)
{
	struct ethtool_rx_flow_spec *fsp;
	struct fbnic_dev *fbd = fbn->fbd;
	struct fbnic_act_tcam *act_tcam;
	int idx;

	fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;

	if (fsp->location >= FBNIC_RPC_ACT_TBL_NFC_ENTRIES)
		return -EINVAL;

	idx = fsp->location + FBNIC_RPC_ACT_TBL_NFC_OFFSET;
	act_tcam = &fbd->act_tcam[idx];

	if (act_tcam->state != FBNIC_TCAM_S_VALID)
		return -EINVAL;

	act_tcam->state = FBNIC_TCAM_S_DELETE;

	if ((act_tcam->value.tcam[1] & FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID) &&
	    (~act_tcam->mask.tcam[1] & FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX))
		fbnic_clear_nfc_macda(fbn, idx);

	if ((act_tcam->value.tcam[0] &
	     (FBNIC_RPC_TCAM_ACT0_IPSRC_VALID |
	      FBNIC_RPC_TCAM_ACT0_IPDST_VALID |
	      FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID |
	      FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID)) &&
	    (~act_tcam->mask.tcam[0] &
	     (FBNIC_RPC_TCAM_ACT0_IPSRC_IDX |
	      FBNIC_RPC_TCAM_ACT0_IPDST_IDX |
	      FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX |
	      FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX)))
		fbnic_clear_nfc_ip_addr(fbn, idx);

	if (netif_running(fbn->netdev))
		fbnic_write_rules(fbd);

	return 0;
}

static int fbnic_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
{
	struct fbnic_net *fbn = netdev_priv(netdev);
@@ -281,6 +921,12 @@ static int fbnic_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
	case ETHTOOL_SRXFH:
		ret = fbnic_set_rss_hash_opts(fbn, cmd);
		break;
	case ETHTOOL_SRXCLSRLINS:
		ret = fbnic_set_cls_rule_ins(fbn, cmd);
		break;
	case ETHTOOL_SRXCLSRLDEL:
		ret = fbnic_set_cls_rule_del(fbn, cmd);
		break;
	}

	return ret;
+1 −0
Original line number Diff line number Diff line
@@ -639,6 +639,7 @@ struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd)
	netdev->hw_features |= netdev->features;
	netdev->vlan_features |= netdev->features;
	netdev->hw_enc_features |= netdev->features;
	netdev->features |= NETIF_F_NTUPLE;

	netdev->min_mtu = IPV6_MIN_MTU;
	netdev->max_mtu = FBNIC_MAX_JUMBO_FRAME_SIZE - ETH_HLEN;
+1 −1
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ void fbnic_rss_disable_hw(struct fbnic_dev *fbd)
#define FBNIC_FH_2_RSSEM_BIT(_fh, _rssem, _val)		\
	FIELD_PREP(FBNIC_RPC_ACT_TBL1_RSS_ENA_##_rssem,	\
		   FIELD_GET(RXH_##_fh, _val))
static u16 fbnic_flow_hash_2_rss_en_mask(struct fbnic_net *fbn, int flow_type)
u16 fbnic_flow_hash_2_rss_en_mask(struct fbnic_net *fbn, int flow_type)
{
	u32 flow_hash = fbn->rss_flow_hash[flow_type];
	u32 rss_en_mask = 0;
+9 −0
Original line number Diff line number Diff line
@@ -96,6 +96,11 @@ enum {
#define FBNIC_RPC_ACT_TBL_BMC_OFFSET		0
#define FBNIC_RPC_ACT_TBL_BMC_ALL_MULTI_OFFSET	1

/* This should leave us with 48 total entries in the TCAM that can be used
 * for NFC after also deducting the 14 needed for RSS table programming.
 */
#define FBNIC_RPC_ACT_TBL_NFC_OFFSET		2

/* We reserve the last 14 entries for RSS rules on the host. The BMC
 * unicast rule will need to be populated above these and is expected to
 * use MACDA TCAM entry 23 to store the BMC MAC address.
@@ -103,6 +108,9 @@ enum {
#define FBNIC_RPC_ACT_TBL_RSS_OFFSET \
	(FBNIC_RPC_ACT_TBL_NUM_ENTRIES - FBNIC_RSS_EN_NUM_ENTRIES)

#define FBNIC_RPC_ACT_TBL_NFC_ENTRIES \
	(FBNIC_RPC_ACT_TBL_RSS_OFFSET - FBNIC_RPC_ACT_TBL_NFC_OFFSET)

/* Flags used to identify the owner for this MAC filter. Note that any
 * flags set for Broadcast thru Promisc indicate that the rule belongs
 * to the RSS filters for the host.
@@ -183,6 +191,7 @@ void fbnic_rss_init_en_mask(struct fbnic_net *fbn);
void fbnic_rss_disable_hw(struct fbnic_dev *fbd);
void fbnic_rss_reinit_hw(struct fbnic_dev *fbd, struct fbnic_net *fbn);
void fbnic_rss_reinit(struct fbnic_dev *fbd, struct fbnic_net *fbn);
u16 fbnic_flow_hash_2_rss_en_mask(struct fbnic_net *fbn, int flow_type);

int __fbnic_xc_unsync(struct fbnic_mac_addr *mac_addr, unsigned int tcam_idx);
struct fbnic_mac_addr *__fbnic_uc_sync(struct fbnic_dev *fbd,