Merge branch 'eth-fbnic-support-rss-contexts-and-ntuple-filters'

Jakub Kicinski says:

====================
eth: fbnic: support RSS contexts and ntuple filters

Add support for RSS contexts and ntuple filters in fbnic.
The device has only one context, intended for use by TCP zero-copy Rx.

First two patches add a check we seem to be missing in the core,
to avoid having to copy it to all drivers.

  $ ./drivers/net/hw/rss_ctx.py
  KTAP version 1
  1..16
  ok 1 rss_ctx.test_rss_key_indir
  ok 2 rss_ctx.test_rss_queue_reconfigure
  ok 3 rss_ctx.test_rss_resize
  ok 4 rss_ctx.test_hitless_key_update
  ok 5 rss_ctx.test_rss_context
  # Failed to create context 2, trying to test what we got
  ok 6 rss_ctx.test_rss_context4 # SKIP Tested only 1 contexts, wanted 4
  # Increasing queue count 44 -> 66
  # Failed to create context 2, trying to test what we got
  ok 7 rss_ctx.test_rss_context32 # SKIP Tested only 1 contexts, wanted 32
  # Added only 1 out of 3 contexts
  ok 8 rss_ctx.test_rss_context_dump
  # Driver does not support rss + queue offset
  ok 9 rss_ctx.test_rss_context_queue_reconfigure
  ok 10 rss_ctx.test_rss_context_overlap
  ok 11 rss_ctx.test_rss_context_overlap2 # SKIP Test requires at least 2 contexts, but device only has 1
  ok 12 rss_ctx.test_rss_context_out_of_order # SKIP Test requires at least 4 contexts, but device only has 1
  # Failed to create context 2, trying to test what we got
  ok 13 rss_ctx.test_rss_context4_create_with_cfg # SKIP Tested only 1 contexts, wanted 4
  ok 14 rss_ctx.test_flow_add_context_missing
  ok 15 rss_ctx.test_delete_rss_context_busy
  ok 16 rss_ctx.test_rss_ntuple_addition # SKIP Ntuple filter with RSS and nonzero action not supported
  # Totals: pass:10 fail:0 xfail:0 xpass:0 skip:6 error:0
====================

Link: https://patch.msgid.link/20250206235334.1425329-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2025-02-10 08:26:54 -08:00
commit 39f54262ba
9 changed files with 1294 additions and 11 deletions

View File

@ -60,6 +60,12 @@ struct fbnic_dev {
u8 mac_addr_boundary;
u8 tce_tcam_last;
/* IP TCAM */
struct fbnic_ip_addr ip_src[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
struct fbnic_ip_addr ip_dst[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
struct fbnic_ip_addr ipo_src[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
struct fbnic_ip_addr ipo_dst[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
/* Number of TCQs/RCQs available on hardware */
u16 max_num_queues;

View File

@ -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 */
@ -677,6 +680,9 @@ enum {
#define FBNIC_RPC_TCAM_OUTER_IPSRC(m, n)\
(0x08c00 + 0x08 * (n) + (m)) /* 0x023000 + 32*n + 4*m */
#define FBNIC_RPC_TCAM_IP_ADDR_VALUE CSR_GENMASK(15, 0)
#define FBNIC_RPC_TCAM_IP_ADDR_MASK CSR_GENMASK(31, 16)
#define FBNIC_RPC_TCAM_OUTER_IPDST(m, n)\
(0x08c48 + 0x08 * (n) + (m)) /* 0x023120 + 32*n + 4*m */
#define FBNIC_RPC_TCAM_IPSRC(m, n)\

View File

@ -44,6 +44,132 @@ static int fbnic_dbg_mac_addr_show(struct seq_file *s, void *v)
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_mac_addr);
static int fbnic_dbg_tce_tcam_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
int i, tcam_idx = 0;
char hdr[80];
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-17s %s\n",
"Idx", "S", "TCAM Bitmap", "Addr/Mask");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < ARRAY_SIZE(fbd->mac_addr); i++) {
struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[i];
/* Verify BMC bit is set */
if (!test_bit(FBNIC_MAC_ADDR_T_BMC, mac_addr->act_tcam))
continue;
if (tcam_idx == FBNIC_TCE_TCAM_NUM_ENTRIES)
break;
seq_printf(s, "%02d %d %64pb %pm\n",
tcam_idx, mac_addr->state, mac_addr->act_tcam,
mac_addr->value.addr8);
seq_printf(s, " %pm\n",
mac_addr->mask.addr8);
tcam_idx++;
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_tce_tcam);
static int fbnic_dbg_act_tcam_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
char hdr[80];
int i;
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-55s %-4s %s\n",
"Idx", "S", "Value/Mask", "RSS", "Dest");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < FBNIC_RPC_TCAM_ACT_NUM_ENTRIES; i++) {
struct fbnic_act_tcam *act_tcam = &fbd->act_tcam[i];
seq_printf(s, "%02d %d %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %08x\n",
i, act_tcam->state,
act_tcam->value.tcam[10], act_tcam->value.tcam[9],
act_tcam->value.tcam[8], act_tcam->value.tcam[7],
act_tcam->value.tcam[6], act_tcam->value.tcam[5],
act_tcam->value.tcam[4], act_tcam->value.tcam[3],
act_tcam->value.tcam[2], act_tcam->value.tcam[1],
act_tcam->value.tcam[0], act_tcam->rss_en_mask,
act_tcam->dest);
seq_printf(s, " %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x\n",
act_tcam->mask.tcam[10], act_tcam->mask.tcam[9],
act_tcam->mask.tcam[8], act_tcam->mask.tcam[7],
act_tcam->mask.tcam[6], act_tcam->mask.tcam[5],
act_tcam->mask.tcam[4], act_tcam->mask.tcam[3],
act_tcam->mask.tcam[2], act_tcam->mask.tcam[1],
act_tcam->mask.tcam[0]);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_act_tcam);
static int fbnic_dbg_ip_addr_show(struct seq_file *s,
struct fbnic_ip_addr *ip_addr)
{
char hdr[80];
int i;
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-17s %s %s\n",
"Idx", "S", "TCAM Bitmap", "V", "Addr/Mask");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES; i++, ip_addr++) {
seq_printf(s, "%02d %d %64pb %d %pi6\n",
i, ip_addr->state, ip_addr->act_tcam,
ip_addr->version, &ip_addr->value);
seq_printf(s, " %pi6\n",
&ip_addr->mask);
}
return 0;
}
static int fbnic_dbg_ip_src_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ip_src);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ip_src);
static int fbnic_dbg_ip_dst_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ip_dst);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ip_dst);
static int fbnic_dbg_ipo_src_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ipo_src);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ipo_src);
static int fbnic_dbg_ipo_dst_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ipo_dst);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ipo_dst);
static int fbnic_dbg_pcie_stats_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
@ -84,6 +210,18 @@ void fbnic_dbg_fbd_init(struct fbnic_dev *fbd)
&fbnic_dbg_pcie_stats_fops);
debugfs_create_file("mac_addr", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_mac_addr_fops);
debugfs_create_file("tce_tcam", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_tce_tcam_fops);
debugfs_create_file("act_tcam", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_act_tcam_fops);
debugfs_create_file("ip_src", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ip_src_fops);
debugfs_create_file("ip_dst", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ip_dst_fops);
debugfs_create_file("ipo_src", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ipo_src_fops);
debugfs_create_file("ipo_dst", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ipo_dst_fops);
}
void fbnic_dbg_fbd_exit(struct fbnic_dev *fbd)

View File

@ -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;
@ -374,6 +1020,61 @@ fbnic_set_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh,
return 0;
}
static int
fbnic_modify_rxfh_context(struct net_device *netdev,
struct ethtool_rxfh_context *ctx,
const struct ethtool_rxfh_param *rxfh,
struct netlink_ext_ack *extack)
{
struct fbnic_net *fbn = netdev_priv(netdev);
const u32 *indir = rxfh->indir;
unsigned int changes;
if (!indir)
indir = ethtool_rxfh_context_indir(ctx);
changes = fbnic_set_indir(fbn, rxfh->rss_context, indir);
if (changes && netif_running(netdev))
fbnic_rss_reinit_hw(fbn->fbd, fbn);
return 0;
}
static int
fbnic_create_rxfh_context(struct net_device *netdev,
struct ethtool_rxfh_context *ctx,
const struct ethtool_rxfh_param *rxfh,
struct netlink_ext_ack *extack)
{
struct fbnic_net *fbn = netdev_priv(netdev);
if (rxfh->hfunc && rxfh->hfunc != ETH_RSS_HASH_TOP) {
NL_SET_ERR_MSG_MOD(extack, "RSS hash function not supported");
return -EOPNOTSUPP;
}
ctx->hfunc = ETH_RSS_HASH_TOP;
if (!rxfh->indir) {
u32 *indir = ethtool_rxfh_context_indir(ctx);
unsigned int num_rx = fbn->num_rx_queues;
unsigned int i;
for (i = 0; i < FBNIC_RPC_RSS_TBL_SIZE; i++)
indir[i] = ethtool_rxfh_indir_default(i, num_rx);
}
return fbnic_modify_rxfh_context(netdev, ctx, rxfh, extack);
}
static int
fbnic_remove_rxfh_context(struct net_device *netdev,
struct ethtool_rxfh_context *ctx, u32 rss_context,
struct netlink_ext_ack *extack)
{
/* Nothing to do, contexts are allocated statically */
return 0;
}
static void fbnic_get_channels(struct net_device *netdev,
struct ethtool_channels *ch)
{
@ -586,6 +1287,7 @@ fbnic_get_eth_mac_stats(struct net_device *netdev,
}
static const struct ethtool_ops fbnic_ethtool_ops = {
.rxfh_max_num_contexts = FBNIC_RPC_RSS_TBL_COUNT,
.get_drvinfo = fbnic_get_drvinfo,
.get_regs_len = fbnic_get_regs_len,
.get_regs = fbnic_get_regs,
@ -598,6 +1300,9 @@ static const struct ethtool_ops fbnic_ethtool_ops = {
.get_rxfh_indir_size = fbnic_get_rxfh_indir_size,
.get_rxfh = fbnic_get_rxfh,
.set_rxfh = fbnic_set_rxfh,
.create_rxfh_context = fbnic_create_rxfh_context,
.modify_rxfh_context = fbnic_modify_rxfh_context,
.remove_rxfh_context = fbnic_remove_rxfh_context,
.get_channels = fbnic_get_channels,
.set_channels = fbnic_set_channels,
.get_ts_info = fbnic_get_ts_info,

View File

@ -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;

View File

@ -3,6 +3,7 @@
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <net/ipv6.h>
#include "fbnic.h"
#include "fbnic_netdev.h"
@ -60,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;
@ -698,6 +699,359 @@ void fbnic_write_tce_tcam(struct fbnic_dev *fbd)
__fbnic_write_tce_tcam(fbd);
}
struct fbnic_ip_addr *__fbnic_ip4_sync(struct fbnic_dev *fbd,
struct fbnic_ip_addr *ip_addr,
const struct in_addr *addr,
const struct in_addr *mask)
{
struct fbnic_ip_addr *avail_addr = NULL;
unsigned int i;
/* Scan from top of list to bottom, filling bottom up. */
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES; i++, ip_addr++) {
struct in6_addr *m = &ip_addr->mask;
if (ip_addr->state == FBNIC_TCAM_S_DISABLED) {
avail_addr = ip_addr;
continue;
}
if (ip_addr->version != 4)
continue;
/* Drop avail_addr if mask is a subset of our current mask,
* This prevents us from inserting a longer prefix behind a
* shorter one.
*
* The mask is stored inverted value so as an example:
* m ffff ffff ffff ffff ffff ffff ffff 0000 0000
* mask 0000 0000 0000 0000 0000 0000 0000 ffff ffff
*
* "m" and "mask" represent typical IPv4 mask stored in
* the TCAM and those provided by the stack. The code below
* should return a non-zero result if there is a 0 stored
* anywhere in "m" where "mask" has a 0.
*/
if (~m->s6_addr32[3] & ~mask->s_addr) {
avail_addr = NULL;
continue;
}
/* Check to see if the mask actually contains fewer bits than
* our new mask "m". The XOR below should only result in 0 if
* "m" is masking a bit that we are looking for in our new
* "mask", we eliminated the 0^0 case with the check above.
*
* If it contains fewer bits we need to stop here, otherwise
* we might be adding an unreachable rule.
*/
if (~(m->s6_addr32[3] ^ mask->s_addr))
break;
if (ip_addr->value.s6_addr32[3] == addr->s_addr) {
avail_addr = ip_addr;
break;
}
}
if (avail_addr && avail_addr->state == FBNIC_TCAM_S_DISABLED) {
ipv6_addr_set(&avail_addr->value, 0, 0, 0, addr->s_addr);
ipv6_addr_set(&avail_addr->mask, htonl(~0), htonl(~0),
htonl(~0), ~mask->s_addr);
avail_addr->version = 4;
avail_addr->state = FBNIC_TCAM_S_ADD;
}
return avail_addr;
}
struct fbnic_ip_addr *__fbnic_ip6_sync(struct fbnic_dev *fbd,
struct fbnic_ip_addr *ip_addr,
const struct in6_addr *addr,
const struct in6_addr *mask)
{
struct fbnic_ip_addr *avail_addr = NULL;
unsigned int i;
ip_addr = &ip_addr[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES - 1];
/* Scan from bottom of list to top, filling top down. */
for (i = FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES; i--; ip_addr--) {
struct in6_addr *m = &ip_addr->mask;
if (ip_addr->state == FBNIC_TCAM_S_DISABLED) {
avail_addr = ip_addr;
continue;
}
if (ip_addr->version != 6)
continue;
/* Drop avail_addr if mask is a superset of our current mask.
* This prevents us from inserting a longer prefix behind a
* shorter one.
*
* The mask is stored inverted value so as an example:
* m 0000 0000 0000 0000 0000 0000 0000 0000 0000
* mask ffff ffff ffff ffff ffff ffff ffff ffff ffff
*
* "m" and "mask" represent typical IPv6 mask stored in
* the TCAM and those provided by the stack. The code below
* should return a non-zero result which will cause us
* to drop the avail_addr value that might be cached
* to prevent us from dropping a v6 address behind it.
*/
if ((m->s6_addr32[0] & mask->s6_addr32[0]) |
(m->s6_addr32[1] & mask->s6_addr32[1]) |
(m->s6_addr32[2] & mask->s6_addr32[2]) |
(m->s6_addr32[3] & mask->s6_addr32[3])) {
avail_addr = NULL;
continue;
}
/* The previous test eliminated any overlap between the
* two values so now we need to check for gaps.
*
* If the mask is equal to our current mask then it should
* result with m ^ mask = ffff ffff, if however the value
* stored in m is bigger then we should see a 0 appear
* somewhere in the mask.
*/
if (~(m->s6_addr32[0] ^ mask->s6_addr32[0]) |
~(m->s6_addr32[1] ^ mask->s6_addr32[1]) |
~(m->s6_addr32[2] ^ mask->s6_addr32[2]) |
~(m->s6_addr32[3] ^ mask->s6_addr32[3]))
break;
if (ipv6_addr_cmp(&ip_addr->value, addr))
continue;
avail_addr = ip_addr;
break;
}
if (avail_addr && avail_addr->state == FBNIC_TCAM_S_DISABLED) {
memcpy(&avail_addr->value, addr, sizeof(*addr));
ipv6_addr_set(&avail_addr->mask,
~mask->s6_addr32[0], ~mask->s6_addr32[1],
~mask->s6_addr32[2], ~mask->s6_addr32[3]);
avail_addr->version = 6;
avail_addr->state = FBNIC_TCAM_S_ADD;
}
return avail_addr;
}
int __fbnic_ip_unsync(struct fbnic_ip_addr *ip_addr, unsigned int tcam_idx)
{
if (!test_and_clear_bit(tcam_idx, ip_addr->act_tcam))
return -ENOENT;
if (bitmap_empty(ip_addr->act_tcam, FBNIC_RPC_TCAM_ACT_NUM_ENTRIES))
ip_addr->state = FBNIC_TCAM_S_DELETE;
return 0;
}
static void fbnic_clear_ip_src_entry(struct fbnic_dev *fbd, unsigned int idx)
{
int i;
/* Invalidate entry and clear addr state info */
for (i = 0; i <= FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_IPSRC(idx, i), 0);
}
static void fbnic_clear_ip_dst_entry(struct fbnic_dev *fbd, unsigned int idx)
{
int i;
/* Invalidate entry and clear addr state info */
for (i = 0; i <= FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_IPDST(idx, i), 0);
}
static void fbnic_clear_ip_outer_src_entry(struct fbnic_dev *fbd,
unsigned int idx)
{
int i;
/* Invalidate entry and clear addr state info */
for (i = 0; i <= FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_OUTER_IPSRC(idx, i), 0);
}
static void fbnic_clear_ip_outer_dst_entry(struct fbnic_dev *fbd,
unsigned int idx)
{
int i;
/* Invalidate entry and clear addr state info */
for (i = 0; i <= FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_OUTER_IPDST(idx, i), 0);
}
static void fbnic_write_ip_src_entry(struct fbnic_dev *fbd, unsigned int idx,
struct fbnic_ip_addr *ip_addr)
{
__be16 *mask, *value;
int i;
mask = &ip_addr->mask.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
value = &ip_addr->value.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_IPSRC(idx, i),
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_MASK, ntohs(*mask--)) |
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_VALUE, ntohs(*value--)));
wrfl(fbd);
/* Bit 129 is used to flag for v4/v6 */
wr32(fbd, FBNIC_RPC_TCAM_IPSRC(idx, i),
(ip_addr->version == 6) | FBNIC_RPC_TCAM_VALIDATE);
}
static void fbnic_write_ip_dst_entry(struct fbnic_dev *fbd, unsigned int idx,
struct fbnic_ip_addr *ip_addr)
{
__be16 *mask, *value;
int i;
mask = &ip_addr->mask.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
value = &ip_addr->value.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_IPDST(idx, i),
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_MASK, ntohs(*mask--)) |
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_VALUE, ntohs(*value--)));
wrfl(fbd);
/* Bit 129 is used to flag for v4/v6 */
wr32(fbd, FBNIC_RPC_TCAM_IPDST(idx, i),
(ip_addr->version == 6) | FBNIC_RPC_TCAM_VALIDATE);
}
static void fbnic_write_ip_outer_src_entry(struct fbnic_dev *fbd,
unsigned int idx,
struct fbnic_ip_addr *ip_addr)
{
__be16 *mask, *value;
int i;
mask = &ip_addr->mask.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
value = &ip_addr->value.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_OUTER_IPSRC(idx, i),
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_MASK, ntohs(*mask--)) |
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_VALUE, ntohs(*value--)));
wrfl(fbd);
wr32(fbd, FBNIC_RPC_TCAM_OUTER_IPSRC(idx, i), FBNIC_RPC_TCAM_VALIDATE);
}
static void fbnic_write_ip_outer_dst_entry(struct fbnic_dev *fbd,
unsigned int idx,
struct fbnic_ip_addr *ip_addr)
{
__be16 *mask, *value;
int i;
mask = &ip_addr->mask.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
value = &ip_addr->value.s6_addr16[FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN - 1];
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN; i++)
wr32(fbd, FBNIC_RPC_TCAM_OUTER_IPDST(idx, i),
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_MASK, ntohs(*mask--)) |
FIELD_PREP(FBNIC_RPC_TCAM_IP_ADDR_VALUE, ntohs(*value--)));
wrfl(fbd);
wr32(fbd, FBNIC_RPC_TCAM_OUTER_IPDST(idx, i), FBNIC_RPC_TCAM_VALIDATE);
}
void fbnic_write_ip_addr(struct fbnic_dev *fbd)
{
int idx;
for (idx = ARRAY_SIZE(fbd->ip_src); idx--;) {
struct fbnic_ip_addr *ip_addr = &fbd->ip_src[idx];
/* Check if update flag is set else skip. */
if (!(ip_addr->state & FBNIC_TCAM_S_UPDATE))
continue;
/* Clear by writing 0s. */
if (ip_addr->state == FBNIC_TCAM_S_DELETE) {
/* Invalidate entry and clear addr state info */
fbnic_clear_ip_src_entry(fbd, idx);
memset(ip_addr, 0, sizeof(*ip_addr));
continue;
}
fbnic_write_ip_src_entry(fbd, idx, ip_addr);
ip_addr->state = FBNIC_TCAM_S_VALID;
}
/* Repeat process for other IP TCAMs */
for (idx = ARRAY_SIZE(fbd->ip_dst); idx--;) {
struct fbnic_ip_addr *ip_addr = &fbd->ip_dst[idx];
if (!(ip_addr->state & FBNIC_TCAM_S_UPDATE))
continue;
if (ip_addr->state == FBNIC_TCAM_S_DELETE) {
fbnic_clear_ip_dst_entry(fbd, idx);
memset(ip_addr, 0, sizeof(*ip_addr));
continue;
}
fbnic_write_ip_dst_entry(fbd, idx, ip_addr);
ip_addr->state = FBNIC_TCAM_S_VALID;
}
for (idx = ARRAY_SIZE(fbd->ipo_src); idx--;) {
struct fbnic_ip_addr *ip_addr = &fbd->ipo_src[idx];
if (!(ip_addr->state & FBNIC_TCAM_S_UPDATE))
continue;
if (ip_addr->state == FBNIC_TCAM_S_DELETE) {
fbnic_clear_ip_outer_src_entry(fbd, idx);
memset(ip_addr, 0, sizeof(*ip_addr));
continue;
}
fbnic_write_ip_outer_src_entry(fbd, idx, ip_addr);
ip_addr->state = FBNIC_TCAM_S_VALID;
}
for (idx = ARRAY_SIZE(fbd->ipo_dst); idx--;) {
struct fbnic_ip_addr *ip_addr = &fbd->ipo_dst[idx];
if (!(ip_addr->state & FBNIC_TCAM_S_UPDATE))
continue;
if (ip_addr->state == FBNIC_TCAM_S_DELETE) {
fbnic_clear_ip_outer_dst_entry(fbd, idx);
memset(ip_addr, 0, sizeof(*ip_addr));
continue;
}
fbnic_write_ip_outer_dst_entry(fbd, idx, ip_addr);
ip_addr->state = FBNIC_TCAM_S_VALID;
}
}
void fbnic_clear_rules(struct fbnic_dev *fbd)
{
u32 dest = FIELD_PREP(FBNIC_RPC_ACT_TBL0_DEST_MASK,

View File

@ -7,6 +7,8 @@
#include <uapi/linux/in6.h>
#include <linux/bitfield.h>
struct in_addr;
/* The TCAM state definitions follow an expected ordering.
* They start out disabled, then move through the following states:
* Disabled 0 -> Add 2
@ -32,6 +34,12 @@ enum {
#define FBNIC_RPC_TCAM_MACDA_WORD_LEN 3
#define FBNIC_RPC_TCAM_MACDA_NUM_ENTRIES 32
/* 8 IPSRC and IPDST TCAM Entries each
* 8 registers, Validate each
*/
#define FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN 8
#define FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES 8
#define FBNIC_RPC_TCAM_ACT_WORD_LEN 11
#define FBNIC_RPC_TCAM_ACT_NUM_ENTRIES 64
@ -47,6 +55,13 @@ struct fbnic_mac_addr {
DECLARE_BITMAP(act_tcam, FBNIC_RPC_TCAM_ACT_NUM_ENTRIES);
};
struct fbnic_ip_addr {
struct in6_addr mask, value;
unsigned char version;
unsigned char state;
DECLARE_BITMAP(act_tcam, FBNIC_RPC_TCAM_ACT_NUM_ENTRIES);
};
struct fbnic_act_tcam {
struct {
u16 tcam[FBNIC_RPC_TCAM_ACT_WORD_LEN];
@ -81,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.
@ -88,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.
@ -168,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,
@ -177,6 +201,17 @@ struct fbnic_mac_addr *__fbnic_mc_sync(struct fbnic_dev *fbd,
void fbnic_sift_macda(struct fbnic_dev *fbd);
void fbnic_write_macda(struct fbnic_dev *fbd);
struct fbnic_ip_addr *__fbnic_ip4_sync(struct fbnic_dev *fbd,
struct fbnic_ip_addr *ip_addr,
const struct in_addr *addr,
const struct in_addr *mask);
struct fbnic_ip_addr *__fbnic_ip6_sync(struct fbnic_dev *fbd,
struct fbnic_ip_addr *ip_addr,
const struct in6_addr *addr,
const struct in6_addr *mask);
int __fbnic_ip_unsync(struct fbnic_ip_addr *ip_addr, unsigned int tcam_idx);
void fbnic_write_ip_addr(struct fbnic_dev *fbd);
static inline int __fbnic_uc_unsync(struct fbnic_mac_addr *mac_addr)
{
return __fbnic_xc_unsync(mac_addr, FBNIC_MAC_ADDR_T_UNICAST);

View File

@ -993,10 +993,14 @@ static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
return rc;
/* Nonzero ring with RSS only makes sense if NIC adds them together */
if (cmd == ETHTOOL_SRXCLSRLINS && info.fs.flow_type & FLOW_RSS &&
!ops->cap_rss_rxnfc_adds &&
ethtool_get_flow_spec_ring(info.fs.ring_cookie))
return -EINVAL;
if (cmd == ETHTOOL_SRXCLSRLINS && info.fs.flow_type & FLOW_RSS) {
if (!ops->cap_rss_rxnfc_adds &&
ethtool_get_flow_spec_ring(info.fs.ring_cookie))
return -EINVAL;
if (!xa_load(&dev->ethtool->rss_ctx, info.rss_context))
return -EINVAL;
}
if (cmd == ETHTOOL_SRXFH && ops->get_rxfh) {
struct ethtool_rxfh_param rxfh = {};

View File

@ -4,7 +4,8 @@
import datetime
import random
import re
from lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ne, ksft_ge, ksft_lt, ksft_true
from lib.py import ksft_run, ksft_pr, ksft_exit
from lib.py import ksft_eq, ksft_ne, ksft_ge, ksft_in, ksft_lt, ksft_true, ksft_raises
from lib.py import NetDrvEpEnv
from lib.py import EthtoolFamily, NetdevFamily
from lib.py import KsftSkipEx, KsftFailEx
@ -58,6 +59,14 @@ def require_ntuple(cfg):
raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
def require_context_cnt(cfg, need_cnt):
# There's no good API to get the context count, so the tests
# which try to add a lot opportunisitically set the count they
# discovered. Careful with test ordering!
if need_cnt and cfg.context_cnt and cfg.context_cnt < need_cnt:
raise KsftSkipEx(f"Test requires at least {need_cnt} contexts, but device only has {cfg.context_cnt}")
# Get Rx packet counts for all queues, as a simple list of integers
# if @prev is specified the prev counts will be subtracted
def _get_rx_cnts(cfg, prev=None):
@ -456,6 +465,8 @@ def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
raise
ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")
ctx_cnt = i
if cfg.context_cnt is None:
cfg.context_cnt = ctx_cnt
break
_rss_key_check(cfg, context=ctx_id)
@ -511,8 +522,7 @@ def test_rss_context_out_of_order(cfg, ctx_cnt=4):
"""
require_ntuple(cfg)
requested_ctx_cnt = ctx_cnt
require_context_cnt(cfg, 4)
# Try to allocate more queues when necessary
qcnt = len(_get_rx_cnts(cfg))
@ -577,9 +587,6 @@ def test_rss_context_out_of_order(cfg, ctx_cnt=4):
remove_ctx(-1)
check_traffic()
if requested_ctx_cnt != ctx_cnt:
raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
def test_rss_context_overlap(cfg, other_ctx=0):
"""
@ -588,6 +595,8 @@ def test_rss_context_overlap(cfg, other_ctx=0):
"""
require_ntuple(cfg)
if other_ctx:
require_context_cnt(cfg, 2)
queue_cnt = len(_get_rx_cnts(cfg))
if queue_cnt < 4:
@ -649,6 +658,29 @@ def test_rss_context_overlap2(cfg):
test_rss_context_overlap(cfg, True)
def test_flow_add_context_missing(cfg):
"""
Test that we are not allowed to add a rule pointing to an RSS context
which was never created.
"""
require_ntuple(cfg)
# Find a context which doesn't exist
for ctx_id in range(1, 100):
try:
get_rss(cfg, context=ctx_id)
except CmdExitFailure:
break
with ksft_raises(CmdExitFailure) as cm:
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port 1234 context {ctx_id}"
ntuple_id = ethtool_create(cfg, "-N", flow)
ethtool(f"-N {cfg.ifname} delete {ntuple_id}")
if cm.exception:
ksft_in('Invalid argument', cm.exception.cmd.stderr)
def test_delete_rss_context_busy(cfg):
"""
Test that deletion returns -EBUSY when an rss context is being used
@ -717,6 +749,7 @@ def test_rss_ntuple_addition(cfg):
def main() -> None:
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
cfg.context_cnt = None
cfg.ethnl = EthtoolFamily()
cfg.netdevnl = NetdevFamily()
@ -726,6 +759,7 @@ def main() -> None:
test_rss_context_dump, test_rss_context_queue_reconfigure,
test_rss_context_overlap, test_rss_context_overlap2,
test_rss_context_out_of_order, test_rss_context4_create_with_cfg,
test_flow_add_context_missing,
test_delete_rss_context_busy, test_rss_ntuple_addition],
args=(cfg, ))
ksft_exit()