mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 11:37:06 +02:00
Merge branch 'bnxt_en-add-xdp-rss-hash-metadata-support'
Chris J Arges says: ==================== bnxt_en: Add XDP RSS hash metadata support This series adds XDP RSS hash metadata extraction support for the bnxt_en driver and includes selftests to validate the functionality. I was able to test this on a BCM57414 NIC. ==================== Link: https://patch.msgid.link/20260325201139.2501937-1-carges@cloudflare.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
22e49419d4
|
|
@ -2077,23 +2077,6 @@ static struct sk_buff *bnxt_rx_vlan(struct sk_buff *skb, u8 cmp_type,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static enum pkt_hash_types bnxt_rss_ext_op(struct bnxt *bp,
|
||||
struct rx_cmp *rxcmp)
|
||||
{
|
||||
u8 ext_op;
|
||||
|
||||
ext_op = RX_CMP_V3_HASH_TYPE(bp, rxcmp);
|
||||
switch (ext_op) {
|
||||
case EXT_OP_INNER_4:
|
||||
case EXT_OP_OUTER_4:
|
||||
case EXT_OP_INNFL_3:
|
||||
case EXT_OP_OUTFL_3:
|
||||
return PKT_HASH_TYPE_L4;
|
||||
default:
|
||||
return PKT_HASH_TYPE_L3;
|
||||
}
|
||||
}
|
||||
|
||||
/* returns the following:
|
||||
* 1 - 1 packet successfully received
|
||||
* 0 - successful TPA_START, packet not completed yet
|
||||
|
|
@ -2112,13 +2095,13 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
|
|||
u32 tmp_raw_cons = *raw_cons;
|
||||
u16 cons, prod, cp_cons = RING_CMP(tmp_raw_cons);
|
||||
struct skb_shared_info *sinfo;
|
||||
struct bnxt_xdp_buff bnxt_xdp;
|
||||
struct bnxt_sw_rx_bd *rx_buf;
|
||||
unsigned int len;
|
||||
u8 *data_ptr, agg_bufs, cmp_type;
|
||||
bool xdp_active = false;
|
||||
dma_addr_t dma_addr;
|
||||
struct sk_buff *skb;
|
||||
struct xdp_buff xdp;
|
||||
u32 flags, misc;
|
||||
u32 cmpl_ts;
|
||||
void *data;
|
||||
|
|
@ -2231,9 +2214,14 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
|
|||
dma_addr = rx_buf->mapping;
|
||||
|
||||
if (bnxt_xdp_attached(bp, rxr)) {
|
||||
bnxt_xdp_buff_init(bp, rxr, cons, data_ptr, len, &xdp);
|
||||
bnxt_xdp.rxcmp = rxcmp;
|
||||
bnxt_xdp.rxcmp1 = rxcmp1;
|
||||
bnxt_xdp.cmp_type = cmp_type;
|
||||
|
||||
bnxt_xdp_buff_init(bp, rxr, cons, data_ptr, len, &bnxt_xdp.xdp);
|
||||
if (agg_bufs) {
|
||||
u32 frag_len = bnxt_rx_agg_netmems_xdp(bp, cpr, &xdp,
|
||||
u32 frag_len = bnxt_rx_agg_netmems_xdp(bp, cpr,
|
||||
&bnxt_xdp.xdp,
|
||||
cp_cons,
|
||||
agg_bufs,
|
||||
false);
|
||||
|
|
@ -2245,12 +2233,13 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
|
|||
}
|
||||
|
||||
if (xdp_active) {
|
||||
if (bnxt_rx_xdp(bp, rxr, cons, &xdp, data, &data_ptr, &len, event)) {
|
||||
if (bnxt_rx_xdp(bp, rxr, cons, &bnxt_xdp.xdp, data, &data_ptr,
|
||||
&len, event)) {
|
||||
rc = 1;
|
||||
goto next_rx;
|
||||
}
|
||||
if (xdp_buff_has_frags(&xdp)) {
|
||||
sinfo = xdp_get_shared_info_from_buff(&xdp);
|
||||
if (xdp_buff_has_frags(&bnxt_xdp.xdp)) {
|
||||
sinfo = xdp_get_shared_info_from_buff(&bnxt_xdp.xdp);
|
||||
agg_bufs = sinfo->nr_frags;
|
||||
} else {
|
||||
agg_bufs = 0;
|
||||
|
|
@ -2261,7 +2250,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
|
|||
if (!xdp_active)
|
||||
skb = bnxt_copy_skb(bnapi, data_ptr, len, dma_addr);
|
||||
else
|
||||
skb = bnxt_copy_xdp(bnapi, &xdp, len, dma_addr);
|
||||
skb = bnxt_copy_xdp(bnapi, &bnxt_xdp.xdp, len,
|
||||
dma_addr);
|
||||
bnxt_reuse_rx_data(rxr, cons, data);
|
||||
if (!skb) {
|
||||
if (agg_bufs) {
|
||||
|
|
@ -2269,7 +2259,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
|
|||
bnxt_reuse_rx_agg_bufs(cpr, cp_cons, 0,
|
||||
agg_bufs, false);
|
||||
else
|
||||
bnxt_xdp_buff_frags_free(rxr, &xdp);
|
||||
bnxt_xdp_buff_frags_free(rxr,
|
||||
&bnxt_xdp.xdp);
|
||||
}
|
||||
goto oom_next_rx;
|
||||
}
|
||||
|
|
@ -2293,10 +2284,11 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
|
|||
if (!skb)
|
||||
goto oom_next_rx;
|
||||
} else {
|
||||
skb = bnxt_xdp_build_skb(bp, skb, agg_bufs, rxr, &xdp);
|
||||
skb = bnxt_xdp_build_skb(bp, skb, agg_bufs,
|
||||
rxr, &bnxt_xdp.xdp);
|
||||
if (!skb) {
|
||||
/* we should be able to free the old skb here */
|
||||
bnxt_xdp_buff_frags_free(rxr, &xdp);
|
||||
bnxt_xdp_buff_frags_free(rxr, &bnxt_xdp.xdp);
|
||||
goto oom_next_rx;
|
||||
}
|
||||
}
|
||||
|
|
@ -15902,6 +15894,10 @@ static const struct net_device_ops bnxt_netdev_ops = {
|
|||
.ndo_hwtstamp_set = bnxt_hwtstamp_set,
|
||||
};
|
||||
|
||||
static const struct xdp_metadata_ops bnxt_xdp_metadata_ops = {
|
||||
.xmo_rx_hash = bnxt_xdp_rx_hash,
|
||||
};
|
||||
|
||||
static void bnxt_get_queue_stats_rx(struct net_device *dev, int i,
|
||||
struct netdev_queue_stats_rx *stats)
|
||||
{
|
||||
|
|
@ -16786,6 +16782,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
goto init_err_free;
|
||||
|
||||
dev->netdev_ops = &bnxt_netdev_ops;
|
||||
dev->xdp_metadata_ops = &bnxt_xdp_metadata_ops;
|
||||
dev->stat_ops = &bnxt_stat_ops;
|
||||
dev->watchdog_timeo = BNXT_TX_TIMEOUT;
|
||||
dev->ethtool_ops = &bnxt_ethtool_ops;
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ struct rx_cmp {
|
|||
#define RX_CMP_FLAGS_ITYPE_UDP (3 << 12)
|
||||
#define RX_CMP_FLAGS_ITYPE_FCOE (4 << 12)
|
||||
#define RX_CMP_FLAGS_ITYPE_ROCE (5 << 12)
|
||||
#define RX_CMP_FLAGS_ITYPE_ICMP (7 << 12)
|
||||
#define RX_CMP_FLAGS_ITYPE_PTP_WO_TS (8 << 12)
|
||||
#define RX_CMP_FLAGS_ITYPE_PTP_W_TS (9 << 12)
|
||||
#define RX_CMP_LEN (0xffff << 16)
|
||||
|
|
@ -311,6 +312,7 @@ struct rx_cmp_ext {
|
|||
#define RX_CMP_FLAGS2_T_IP_CS_CALC (0x1 << 2)
|
||||
#define RX_CMP_FLAGS2_T_L4_CS_CALC (0x1 << 3)
|
||||
#define RX_CMP_FLAGS2_META_FORMAT_VLAN (0x1 << 4)
|
||||
#define RX_CMP_FLAGS2_IP_TYPE (0x1 << 8)
|
||||
__le32 rx_cmp_meta_data;
|
||||
#define RX_CMP_FLAGS2_METADATA_TCI_MASK 0xffff
|
||||
#define RX_CMP_FLAGS2_METADATA_VID_MASK 0xfff
|
||||
|
|
@ -2900,6 +2902,23 @@ static inline bool bnxt_sriov_cfg(struct bnxt *bp)
|
|||
#endif
|
||||
}
|
||||
|
||||
static inline enum pkt_hash_types bnxt_rss_ext_op(struct bnxt *bp,
|
||||
const struct rx_cmp *rxcmp)
|
||||
{
|
||||
u8 ext_op;
|
||||
|
||||
ext_op = RX_CMP_V3_HASH_TYPE(bp, rxcmp);
|
||||
switch (ext_op) {
|
||||
case EXT_OP_INNER_4:
|
||||
case EXT_OP_OUTER_4:
|
||||
case EXT_OP_INNFL_3:
|
||||
case EXT_OP_OUTFL_3:
|
||||
return PKT_HASH_TYPE_L4;
|
||||
default:
|
||||
return PKT_HASH_TYPE_L3;
|
||||
}
|
||||
}
|
||||
|
||||
extern const u16 bnxt_bstore_to_trace[];
|
||||
extern const u16 bnxt_lhint_arr[];
|
||||
|
||||
|
|
|
|||
|
|
@ -472,3 +472,60 @@ bnxt_xdp_build_skb(struct bnxt *bp, struct sk_buff *skb, u8 num_frags,
|
|||
xdp_buff_get_skb_flags(xdp));
|
||||
return skb;
|
||||
}
|
||||
|
||||
int bnxt_xdp_rx_hash(const struct xdp_md *ctx, u32 *hash,
|
||||
enum xdp_rss_hash_type *rss_type)
|
||||
{
|
||||
const struct bnxt_xdp_buff *xdp = (void *)ctx;
|
||||
const struct rx_cmp_ext *rxcmp1 = xdp->rxcmp1;
|
||||
const struct rx_cmp *rxcmp = xdp->rxcmp;
|
||||
enum xdp_rss_hash_type hash_type = 0;
|
||||
u32 itypes;
|
||||
|
||||
if (!rxcmp || !RX_CMP_HASH_VALID(rxcmp))
|
||||
return -ENODATA;
|
||||
|
||||
*hash = le32_to_cpu(rxcmp->rx_cmp_rss_hash);
|
||||
|
||||
if (!rxcmp1) {
|
||||
*rss_type = XDP_RSS_TYPE_L2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (xdp->cmp_type == CMP_TYPE_RX_L2_CMP) {
|
||||
itypes = RX_CMP_ITYPES(rxcmp);
|
||||
if (rxcmp1->rx_cmp_flags2 &
|
||||
cpu_to_le32(RX_CMP_FLAGS2_IP_TYPE)) {
|
||||
hash_type |= XDP_RSS_TYPE_L3_IPV6;
|
||||
} else {
|
||||
hash_type |= XDP_RSS_TYPE_L3_IPV4;
|
||||
}
|
||||
|
||||
switch (itypes) {
|
||||
case RX_CMP_FLAGS_ITYPE_TCP:
|
||||
hash_type |= XDP_RSS_L4 | XDP_RSS_L4_TCP;
|
||||
break;
|
||||
case RX_CMP_FLAGS_ITYPE_UDP:
|
||||
hash_type |= XDP_RSS_L4 | XDP_RSS_L4_UDP;
|
||||
break;
|
||||
case RX_CMP_FLAGS_ITYPE_ICMP:
|
||||
hash_type |= XDP_RSS_L4 | XDP_RSS_L4_ICMP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (xdp->cmp_type == CMP_TYPE_RX_L2_V3_CMP) {
|
||||
struct bnxt *bp = netdev_priv(xdp->xdp.rxq->dev);
|
||||
|
||||
if (rxcmp1->rx_cmp_flags2 & cpu_to_le32(RX_CMP_FLAGS2_IP_TYPE))
|
||||
hash_type |= XDP_RSS_TYPE_L3_IPV6;
|
||||
else
|
||||
hash_type |= XDP_RSS_TYPE_L3_IPV4;
|
||||
|
||||
if (bnxt_rss_ext_op(bp, rxcmp) == PKT_HASH_TYPE_L4)
|
||||
hash_type |= XDP_RSS_L4;
|
||||
}
|
||||
|
||||
*rss_type = hash_type;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@
|
|||
|
||||
DECLARE_STATIC_KEY_FALSE(bnxt_xdp_locking_key);
|
||||
|
||||
struct bnxt_xdp_buff {
|
||||
struct xdp_buff xdp;
|
||||
struct rx_cmp *rxcmp;
|
||||
struct rx_cmp_ext *rxcmp1;
|
||||
u8 cmp_type;
|
||||
};
|
||||
|
||||
struct bnxt_sw_tx_bd *bnxt_xmit_bd(struct bnxt *bp,
|
||||
struct bnxt_tx_ring_info *txr,
|
||||
dma_addr_t mapping, u32 len,
|
||||
|
|
@ -34,4 +41,7 @@ void bnxt_xdp_buff_frags_free(struct bnxt_rx_ring_info *rxr,
|
|||
struct sk_buff *bnxt_xdp_build_skb(struct bnxt *bp, struct sk_buff *skb,
|
||||
u8 num_frags, struct bnxt_rx_ring_info *rxr,
|
||||
struct xdp_buff *xdp);
|
||||
int bnxt_xdp_rx_hash(const struct xdp_md *ctx, u32 *hash,
|
||||
enum xdp_rss_hash_type *rss_type);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ TEST_PROGS = \
|
|||
rss_input_xfrm.py \
|
||||
toeplitz.py \
|
||||
tso.py \
|
||||
xdp_metadata.py \
|
||||
xsk_reconfig.py \
|
||||
#
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ try:
|
|||
from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
|
||||
fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, \
|
||||
wait_file, tool
|
||||
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
|
||||
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
|
||||
ksft_setup, ksft_variants, KsftNamedVariant
|
||||
|
|
@ -40,6 +41,7 @@ try:
|
|||
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
|
||||
"fd_read_timeout", "ip", "rand_port", "rand_ports",
|
||||
"wait_port_listen", "wait_file", "tool",
|
||||
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
|
||||
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
|
||||
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
|
||||
"ksft_setup", "ksft_variants", "KsftNamedVariant",
|
||||
|
|
|
|||
146
tools/testing/selftests/drivers/net/hw/xdp_metadata.py
Normal file
146
tools/testing/selftests/drivers/net/hw/xdp_metadata.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""
|
||||
Tests for XDP metadata kfuncs (e.g. bpf_xdp_metadata_rx_hash).
|
||||
|
||||
These tests load device-bound XDP programs from xdp_metadata.bpf.o
|
||||
that call metadata kfuncs, send traffic, and verify the extracted
|
||||
metadata via BPF maps.
|
||||
"""
|
||||
from lib.py import ksft_run, ksft_eq, ksft_exit, ksft_ge, ksft_ne, ksft_pr
|
||||
from lib.py import KsftNamedVariant, ksft_variants
|
||||
from lib.py import CmdExitFailure, KsftSkipEx, NetDrvEpEnv
|
||||
from lib.py import NetdevFamily
|
||||
from lib.py import bkg, cmd, rand_port, wait_port_listen
|
||||
from lib.py import ip, bpftool, defer
|
||||
from lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
|
||||
|
||||
def _load_xdp_metadata_prog(cfg, prog_name, bpf_file="xdp_metadata.bpf.o"):
|
||||
"""Load a device-bound XDP metadata program and return prog/map info.
|
||||
|
||||
Returns:
|
||||
dict with 'id', 'name', and 'maps' (name -> map_id).
|
||||
"""
|
||||
abs_path = cfg.net_lib_dir / bpf_file
|
||||
pin_dir = "/sys/fs/bpf/xdp_metadata_test"
|
||||
|
||||
cmd(f"rm -rf {pin_dir}", shell=True, fail=False)
|
||||
cmd(f"mkdir -p {pin_dir}", shell=True)
|
||||
|
||||
try:
|
||||
bpftool(f"prog loadall {abs_path} {pin_dir} type xdp "
|
||||
f"xdpmeta_dev {cfg.ifname}")
|
||||
except CmdExitFailure as e:
|
||||
cmd(f"rm -rf {pin_dir}", shell=True, fail=False)
|
||||
raise KsftSkipEx(
|
||||
f"Failed to load device-bound XDP program '{prog_name}'"
|
||||
) from e
|
||||
defer(cmd, f"rm -rf {pin_dir}", shell=True, fail=False)
|
||||
|
||||
pin_path = f"{pin_dir}/{prog_name}"
|
||||
ip(f"link set dev {cfg.ifname} xdpdrv pinned {pin_path}")
|
||||
defer(ip, f"link set dev {cfg.ifname} xdpdrv off")
|
||||
|
||||
xdp_info = ip(f"-d link show dev {cfg.ifname}", json=True)[0]
|
||||
prog_id = xdp_info["xdp"]["prog"]["id"]
|
||||
|
||||
return {"id": prog_id,
|
||||
"name": xdp_info["xdp"]["prog"]["name"],
|
||||
"maps": bpf_prog_map_ids(prog_id)}
|
||||
|
||||
|
||||
def _send_probe(cfg, port, proto="tcp"):
|
||||
"""Send a single payload from the remote end using socat.
|
||||
|
||||
Args:
|
||||
cfg: Configuration object containing network settings.
|
||||
port: Port number for the exchange.
|
||||
proto: Protocol to use, either "tcp" or "udp".
|
||||
"""
|
||||
cfg.require_cmd("socat", remote=True)
|
||||
|
||||
if proto == "tcp":
|
||||
rx_cmd = f"socat -{cfg.addr_ipver} -T 2 TCP-LISTEN:{port},reuseport STDOUT"
|
||||
tx_cmd = f"echo -n rss_hash_test | socat -t 2 -u STDIN TCP:{cfg.baddr}:{port}"
|
||||
else:
|
||||
rx_cmd = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport STDOUT"
|
||||
tx_cmd = f"echo -n rss_hash_test | socat -t 2 -u STDIN UDP:{cfg.baddr}:{port}"
|
||||
|
||||
with bkg(rx_cmd, exit_wait=True):
|
||||
wait_port_listen(port, proto=proto)
|
||||
cmd(tx_cmd, host=cfg.remote, shell=True)
|
||||
|
||||
|
||||
# BPF map keys matching the enums in xdp_metadata.bpf.c
|
||||
_SETUP_KEY_PORT = 1
|
||||
|
||||
_RSS_KEY_HASH = 0
|
||||
_RSS_KEY_TYPE = 1
|
||||
_RSS_KEY_PKT_CNT = 2
|
||||
_RSS_KEY_ERR_CNT = 3
|
||||
|
||||
XDP_RSS_L4 = 0x8 # BIT(3) from enum xdp_rss_hash_type
|
||||
|
||||
|
||||
@ksft_variants([
|
||||
KsftNamedVariant("tcp", "tcp"),
|
||||
KsftNamedVariant("udp", "udp"),
|
||||
])
|
||||
def test_xdp_rss_hash(cfg, proto):
|
||||
"""Test RSS hash metadata extraction via bpf_xdp_metadata_rx_hash().
|
||||
|
||||
This test will only run on devices that support xdp-rx-metadata-features.
|
||||
|
||||
Loads the xdp_rss_hash program from xdp_metadata, sends a packet using
|
||||
the specified protocol, and verifies that the program extracted a non-zero
|
||||
hash with an L4 hash type.
|
||||
"""
|
||||
dev_info = cfg.netnl.dev_get({"ifindex": cfg.ifindex})
|
||||
rx_meta = dev_info.get("xdp-rx-metadata-features", [])
|
||||
if "hash" not in rx_meta:
|
||||
raise KsftSkipEx("device does not support XDP rx hash metadata")
|
||||
|
||||
prog_info = _load_xdp_metadata_prog(cfg, "xdp_rss_hash")
|
||||
|
||||
port = rand_port()
|
||||
bpf_map_set("map_xdp_setup", _SETUP_KEY_PORT, port)
|
||||
|
||||
rss_map_id = prog_info["maps"]["map_rss"]
|
||||
|
||||
_send_probe(cfg, port, proto=proto)
|
||||
|
||||
rss = bpf_map_dump(rss_map_id)
|
||||
|
||||
pkt_cnt = rss.get(_RSS_KEY_PKT_CNT, 0)
|
||||
err_cnt = rss.get(_RSS_KEY_ERR_CNT, 0)
|
||||
hash_val = rss.get(_RSS_KEY_HASH, 0)
|
||||
hash_type = rss.get(_RSS_KEY_TYPE, 0)
|
||||
|
||||
ksft_ge(pkt_cnt, 1, comment="should have received at least one packet")
|
||||
ksft_eq(err_cnt, 0, comment=f"RSS hash error count: {err_cnt}")
|
||||
|
||||
ksft_ne(hash_val, 0,
|
||||
f"RSS hash should be non-zero for {proto.upper()} traffic")
|
||||
ksft_pr(f" RSS hash: {hash_val:#010x}")
|
||||
|
||||
ksft_pr(f" RSS hash type: {hash_type:#06x}")
|
||||
ksft_ne(hash_type & XDP_RSS_L4, 0,
|
||||
f"RSS hash type should include L4 for {proto.upper()} traffic")
|
||||
|
||||
|
||||
def main():
|
||||
"""Run XDP metadata kfunc tests against a real device."""
|
||||
with NetDrvEpEnv(__file__) as cfg:
|
||||
cfg.netnl = NetdevFamily()
|
||||
ksft_run(
|
||||
[
|
||||
test_xdp_rss_hash,
|
||||
],
|
||||
args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -24,6 +24,7 @@ try:
|
|||
from net.lib.py import CmdExitFailure
|
||||
from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
|
||||
fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file
|
||||
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
|
||||
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
|
||||
ksft_setup, ksft_variants, KsftNamedVariant
|
||||
|
|
@ -37,6 +38,7 @@ try:
|
|||
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
|
||||
"fd_read_timeout", "ip", "rand_port", "rand_ports",
|
||||
"wait_port_listen", "wait_file",
|
||||
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
|
||||
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
|
||||
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
|
||||
"ksft_setup", "ksft_variants", "KsftNamedVariant",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ from lib.py import KsftNamedVariant, ksft_variants
|
|||
from lib.py import KsftFailEx, NetDrvEpEnv
|
||||
from lib.py import EthtoolFamily, NetdevFamily, NlError
|
||||
from lib.py import bkg, cmd, rand_port, wait_port_listen
|
||||
from lib.py import ip, bpftool, defer
|
||||
from lib.py import ip, defer
|
||||
from lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
|
||||
|
||||
class TestConfig(Enum):
|
||||
|
|
@ -122,47 +123,11 @@ def _load_xdp_prog(cfg, bpf_info):
|
|||
xdp_info = ip(f"-d link show dev {cfg.ifname}", json=True)[0]
|
||||
prog_info["id"] = xdp_info["xdp"]["prog"]["id"]
|
||||
prog_info["name"] = xdp_info["xdp"]["prog"]["name"]
|
||||
prog_id = prog_info["id"]
|
||||
|
||||
map_ids = bpftool(f"prog show id {prog_id}", json=True)["map_ids"]
|
||||
prog_info["maps"] = {}
|
||||
for map_id in map_ids:
|
||||
name = bpftool(f"map show id {map_id}", json=True)["name"]
|
||||
prog_info["maps"][name] = map_id
|
||||
prog_info["maps"] = bpf_prog_map_ids(prog_info["id"])
|
||||
|
||||
return prog_info
|
||||
|
||||
|
||||
def format_hex_bytes(value):
|
||||
"""
|
||||
Helper function that converts an integer into a formatted hexadecimal byte string.
|
||||
|
||||
Args:
|
||||
value: An integer representing the number to be converted.
|
||||
|
||||
Returns:
|
||||
A string representing hexadecimal equivalent of value, with bytes separated by spaces.
|
||||
"""
|
||||
hex_str = value.to_bytes(4, byteorder='little', signed=True)
|
||||
return ' '.join(f'{byte:02x}' for byte in hex_str)
|
||||
|
||||
|
||||
def _set_xdp_map(map_name, key, value):
|
||||
"""
|
||||
Updates an XDP map with a given key-value pair using bpftool.
|
||||
|
||||
Args:
|
||||
map_name: The name of the XDP map to update.
|
||||
key: The key to update in the map, formatted as a hexadecimal string.
|
||||
value: The value to associate with the key, formatted as a hexadecimal string.
|
||||
"""
|
||||
key_formatted = format_hex_bytes(key)
|
||||
value_formatted = format_hex_bytes(value)
|
||||
bpftool(
|
||||
f"map update name {map_name} key hex {key_formatted} value hex {value_formatted}"
|
||||
)
|
||||
|
||||
|
||||
def _get_stats(xdp_map_id):
|
||||
"""
|
||||
Retrieves and formats statistics from an XDP map.
|
||||
|
|
@ -177,25 +142,11 @@ def _get_stats(xdp_map_id):
|
|||
Raises:
|
||||
KsftFailEx: If the stats retrieval fails.
|
||||
"""
|
||||
stats_dump = bpftool(f"map dump id {xdp_map_id}", json=True)
|
||||
if not stats_dump:
|
||||
stats = bpf_map_dump(xdp_map_id)
|
||||
if not stats:
|
||||
raise KsftFailEx(f"Failed to get stats for map {xdp_map_id}")
|
||||
|
||||
stats_formatted = {}
|
||||
for key in range(0, 5):
|
||||
val = stats_dump[key]["formatted"]["value"]
|
||||
if stats_dump[key]["formatted"]["key"] == XDPStats.RX.value:
|
||||
stats_formatted[XDPStats.RX.value] = val
|
||||
elif stats_dump[key]["formatted"]["key"] == XDPStats.PASS.value:
|
||||
stats_formatted[XDPStats.PASS.value] = val
|
||||
elif stats_dump[key]["formatted"]["key"] == XDPStats.DROP.value:
|
||||
stats_formatted[XDPStats.DROP.value] = val
|
||||
elif stats_dump[key]["formatted"]["key"] == XDPStats.TX.value:
|
||||
stats_formatted[XDPStats.TX.value] = val
|
||||
elif stats_dump[key]["formatted"]["key"] == XDPStats.ABORT.value:
|
||||
stats_formatted[XDPStats.ABORT.value] = val
|
||||
|
||||
return stats_formatted
|
||||
return stats
|
||||
|
||||
|
||||
def _test_pass(cfg, bpf_info, msg_sz):
|
||||
|
|
@ -211,8 +162,8 @@ def _test_pass(cfg, bpf_info, msg_sz):
|
|||
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||
port = rand_port()
|
||||
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.value)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.value)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
|
||||
ksft_eq(_test_udp(cfg, port, msg_sz), True, "UDP packet exchange failed")
|
||||
stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
|
||||
|
|
@ -258,8 +209,8 @@ def _test_drop(cfg, bpf_info, msg_sz):
|
|||
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||
port = rand_port()
|
||||
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.value)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.value)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
|
||||
ksft_eq(_test_udp(cfg, port, msg_sz), False, "UDP packet exchange should fail")
|
||||
stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
|
||||
|
|
@ -305,8 +256,8 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens):
|
|||
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||
port = rand_port()
|
||||
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
|
||||
expected_pkts = 0
|
||||
for payload_len in payload_lens:
|
||||
|
|
@ -454,15 +405,15 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst):
|
|||
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||
|
||||
# Configure the XDP map for tail adjustment
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJST.value)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJST.value)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
|
||||
for offset in offset_lst:
|
||||
tag = format(random.randint(65, 90), "02x")
|
||||
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
|
||||
if offset > 0:
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
|
||||
bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
|
||||
|
||||
for pkt_sz in pkt_sz_lst:
|
||||
test_str = "".join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz))
|
||||
|
|
@ -574,8 +525,8 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
|
|||
prog_info = _load_xdp_prog(cfg, BPFProgInfo(prog, "xdp_native.bpf.o", "xdp.frags", 9000))
|
||||
port = rand_port()
|
||||
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJST.value)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJST.value)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
|
||||
hds_thresh = get_hds_thresh(cfg)
|
||||
for offset in offset_lst:
|
||||
|
|
@ -595,11 +546,8 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
|
|||
test_str = ''.join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz))
|
||||
tag = format(random.randint(65, 90), '02x')
|
||||
|
||||
_set_xdp_map("map_xdp_setup",
|
||||
TestConfig.ADJST_OFFSET.value,
|
||||
offset)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
|
||||
|
||||
recvd_str = _exchg_udp(cfg, port, test_str)
|
||||
|
||||
|
|
@ -691,8 +639,8 @@ def test_xdp_native_qstats(cfg, act):
|
|||
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||
port = rand_port()
|
||||
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, act.value)
|
||||
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, act.value)
|
||||
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
|
||||
|
||||
# Discard the input, but we need a listener to avoid ICMP errors
|
||||
rx_udp = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport " + \
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from .nsim import NetdevSim, NetdevSimDev
|
|||
from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
|
||||
bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
|
||||
wait_file, tool
|
||||
from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
from .ynl import NlError, NlctrlFamily, YnlFamily, \
|
||||
EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
|
||||
from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
|
||||
|
|
@ -29,6 +30,7 @@ __all__ = ["KSRC",
|
|||
"CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
|
||||
"bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports",
|
||||
"wait_port_listen", "wait_file", "tool",
|
||||
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
|
||||
"NetdevSim", "NetdevSimDev",
|
||||
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
|
||||
"YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
|
||||
|
|
|
|||
68
tools/testing/selftests/net/lib/py/bpf.py
Normal file
68
tools/testing/selftests/net/lib/py/bpf.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""
|
||||
BPF helper utilities for kernel selftests.
|
||||
|
||||
Provides common operations for interacting with BPF maps and programs
|
||||
via bpftool, used by XDP and other BPF-based test files.
|
||||
"""
|
||||
|
||||
from .utils import bpftool
|
||||
|
||||
def _format_hex_bytes(value):
|
||||
"""
|
||||
Helper function that converts an integer into a formatted hexadecimal byte string.
|
||||
|
||||
Args:
|
||||
value: An integer representing the number to be converted.
|
||||
|
||||
Returns:
|
||||
A string representing hexadecimal equivalent of value, with bytes separated by spaces.
|
||||
"""
|
||||
hex_str = value.to_bytes(4, byteorder='little', signed=True)
|
||||
return ' '.join(f'{byte:02x}' for byte in hex_str)
|
||||
|
||||
|
||||
def bpf_map_set(map_name, key, value):
|
||||
"""
|
||||
Updates an XDP map with a given key-value pair using bpftool.
|
||||
|
||||
Args:
|
||||
map_name: The name of the XDP map to update.
|
||||
key: The key to update in the map, formatted as a hexadecimal string.
|
||||
value: The value to associate with the key, formatted as a hexadecimal string.
|
||||
"""
|
||||
key_formatted = _format_hex_bytes(key)
|
||||
value_formatted = _format_hex_bytes(value)
|
||||
bpftool(
|
||||
f"map update name {map_name} key hex {key_formatted} value hex {value_formatted}"
|
||||
)
|
||||
|
||||
def bpf_map_dump(map_id):
|
||||
"""Dump all entries of a BPF array map.
|
||||
|
||||
Args:
|
||||
map_id: Numeric map ID (as returned by bpftool prog show).
|
||||
|
||||
Returns:
|
||||
A dict mapping formatted key (int) to formatted value (int).
|
||||
"""
|
||||
raw = bpftool(f"map dump id {map_id}", json=True)
|
||||
return {e["formatted"]["key"]: e["formatted"]["value"] for e in raw}
|
||||
|
||||
|
||||
def bpf_prog_map_ids(prog_id):
|
||||
"""Get the map name-to-ID mapping for a loaded BPF program.
|
||||
|
||||
Args:
|
||||
prog_id: Numeric program ID.
|
||||
|
||||
Returns:
|
||||
A dict mapping map name (str) to map ID (int).
|
||||
"""
|
||||
map_ids = bpftool(f"prog show id {prog_id}", json=True)["map_ids"]
|
||||
maps = {}
|
||||
for mid in map_ids:
|
||||
name = bpftool(f"map show id {mid}", json=True)["name"]
|
||||
maps[name] = mid
|
||||
return maps
|
||||
163
tools/testing/selftests/net/lib/xdp_metadata.bpf.c
Normal file
163
tools/testing/selftests/net/lib/xdp_metadata.bpf.c
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <stddef.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/ipv6.h>
|
||||
#include <linux/udp.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
enum {
|
||||
XDP_PORT = 1,
|
||||
XDP_PROTO = 4,
|
||||
} xdp_map_setup_keys;
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 5);
|
||||
__type(key, __u32);
|
||||
__type(value, __s32);
|
||||
} map_xdp_setup SEC(".maps");
|
||||
|
||||
/* RSS hash results: key 0 = hash, key 1 = hash type,
|
||||
* key 2 = packet count, key 3 = error count.
|
||||
*/
|
||||
enum {
|
||||
RSS_KEY_HASH = 0,
|
||||
RSS_KEY_TYPE = 1,
|
||||
RSS_KEY_PKT_CNT = 2,
|
||||
RSS_KEY_ERR_CNT = 3,
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__type(key, __u32);
|
||||
__type(value, __u32);
|
||||
__uint(max_entries, 4);
|
||||
} map_rss SEC(".maps");
|
||||
|
||||
/* Mirror of enum xdp_rss_hash_type from include/net/xdp.h.
|
||||
* Needed because the enum is not part of UAPI headers.
|
||||
*/
|
||||
enum xdp_rss_hash_type {
|
||||
XDP_RSS_L3_IPV4 = 1U << 0,
|
||||
XDP_RSS_L3_IPV6 = 1U << 1,
|
||||
XDP_RSS_L3_DYNHDR = 1U << 2,
|
||||
XDP_RSS_L4 = 1U << 3,
|
||||
XDP_RSS_L4_TCP = 1U << 4,
|
||||
XDP_RSS_L4_UDP = 1U << 5,
|
||||
XDP_RSS_L4_SCTP = 1U << 6,
|
||||
XDP_RSS_L4_IPSEC = 1U << 7,
|
||||
XDP_RSS_L4_ICMP = 1U << 8,
|
||||
};
|
||||
|
||||
extern int bpf_xdp_metadata_rx_hash(const struct xdp_md *ctx, __u32 *hash,
|
||||
enum xdp_rss_hash_type *rss_type) __ksym;
|
||||
|
||||
static __always_inline __u16 get_dest_port(void *l4, void *data_end,
|
||||
__u8 protocol)
|
||||
{
|
||||
if (protocol == IPPROTO_UDP) {
|
||||
struct udphdr *udp = l4;
|
||||
|
||||
if ((void *)(udp + 1) > data_end)
|
||||
return 0;
|
||||
return udp->dest;
|
||||
} else if (protocol == IPPROTO_TCP) {
|
||||
struct tcphdr *tcp = l4;
|
||||
|
||||
if ((void *)(tcp + 1) > data_end)
|
||||
return 0;
|
||||
return tcp->dest;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int xdp_rss_hash(struct xdp_md *ctx)
|
||||
{
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
enum xdp_rss_hash_type rss_type = 0;
|
||||
struct ethhdr *eth = data;
|
||||
__u8 l4_proto = 0;
|
||||
__u32 hash = 0;
|
||||
__u32 key, val;
|
||||
void *l4 = NULL;
|
||||
__u32 *cnt;
|
||||
int ret;
|
||||
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
|
||||
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
|
||||
struct iphdr *iph = (void *)(eth + 1);
|
||||
|
||||
if ((void *)(iph + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
l4_proto = iph->protocol;
|
||||
l4 = (void *)(iph + 1);
|
||||
} else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
|
||||
struct ipv6hdr *ip6h = (void *)(eth + 1);
|
||||
|
||||
if ((void *)(ip6h + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
l4_proto = ip6h->nexthdr;
|
||||
l4 = (void *)(ip6h + 1);
|
||||
}
|
||||
|
||||
if (!l4)
|
||||
return XDP_PASS;
|
||||
|
||||
/* Filter on the configured protocol (map_xdp_setup key XDP_PROTO).
|
||||
* When set, only process packets matching the requested L4 protocol.
|
||||
*/
|
||||
key = XDP_PROTO;
|
||||
__s32 *proto_cfg = bpf_map_lookup_elem(&map_xdp_setup, &key);
|
||||
|
||||
if (proto_cfg && *proto_cfg != 0 && l4_proto != (__u8)*proto_cfg)
|
||||
return XDP_PASS;
|
||||
|
||||
/* Filter on the configured port (map_xdp_setup key XDP_PORT).
|
||||
* Only applies to protocols with ports (UDP, TCP).
|
||||
*/
|
||||
key = XDP_PORT;
|
||||
__s32 *port_cfg = bpf_map_lookup_elem(&map_xdp_setup, &key);
|
||||
|
||||
if (port_cfg && *port_cfg != 0) {
|
||||
__u16 dest = get_dest_port(l4, data_end, l4_proto);
|
||||
|
||||
if (!dest || bpf_ntohs(dest) != (__u16)*port_cfg)
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
ret = bpf_xdp_metadata_rx_hash(ctx, &hash, &rss_type);
|
||||
if (ret < 0) {
|
||||
key = RSS_KEY_ERR_CNT;
|
||||
cnt = bpf_map_lookup_elem(&map_rss, &key);
|
||||
if (cnt)
|
||||
__sync_fetch_and_add(cnt, 1);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
key = RSS_KEY_HASH;
|
||||
bpf_map_update_elem(&map_rss, &key, &hash, BPF_ANY);
|
||||
|
||||
key = RSS_KEY_TYPE;
|
||||
val = (__u32)rss_type;
|
||||
bpf_map_update_elem(&map_rss, &key, &val, BPF_ANY);
|
||||
|
||||
key = RSS_KEY_PKT_CNT;
|
||||
cnt = bpf_map_lookup_elem(&map_rss, &key);
|
||||
if (cnt)
|
||||
__sync_fetch_and_add(cnt, 1);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
Loading…
Reference in New Issue
Block a user