mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-29 20:58:57 -04:00
Merge branch 'net-devmem-allow-rx-buf-size-page_size-per-binding'
Bobby Eshleman says:
====================
net: devmem: allow rx-buf-size > PAGE_SIZE per binding
Every devmem dmabuf binding hands the page_pool PAGE_SIZE niovs today.
On NICs that consume one descriptor per netmem, this caps a single RX
descriptor at PAGE_SIZE and burns CPU on buffer churn.
In this series, we add a bind-time netlink attribute,
NETDEV_A_DMABUF_RX_BUF_SIZE, that lets userspace request a larger niov
size (power of two >= PAGE_SIZE). Drivers must opt in via
queue_mgmt_ops.QCFG_RX_PAGE_SIZE.
Measurements:
Setup: kperf devmem RX/TX cuda, 4 flows, 64 MB messages, 60s, dctcp,
num-rx-queues=4, dmabuf-rx/tx-size-mb=2048, 10 runs per niov size,
mlx5.
niov RX dev Gbps RX flow avg Gbps app sys %
----- ---------------- ----------------- ----------------
4K 300.63 +/- 53.21 75.16 +/- 13.30 54.15 +/- 10.23
16K 321.35 +/- 28.20 80.34 +/- 7.05 41.05 +/- 8.87
32K 347.63 +/- 2.20 86.91 +/- 0.55 44.54 +/- 3.51
64K 332.11 +/- 14.26 83.03 +/- 3.56 35.47 +/- 3.11
RX app sys % drops ~19% from 4K to 64K.
kperf support (not yet merged):
8837577f92
====================
Link: https://patch.msgid.link/20260805-tcpdm-large-niovs-v8-0-3e0225e2808c@meta.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -6,6 +6,14 @@ doc: >-
|
||||
netdev configuration over generic netlink.
|
||||
|
||||
definitions:
|
||||
-
|
||||
type: const
|
||||
name: page-size
|
||||
# Dummy value, codegen needs a number. The real value comes from
|
||||
# the PAGE_SIZE macro in the header below.
|
||||
value: 0
|
||||
header: asm/page.h
|
||||
scope: kernel
|
||||
-
|
||||
type: flags
|
||||
name: xdp-act
|
||||
@@ -598,6 +606,16 @@ attribute-sets:
|
||||
type: u32
|
||||
checks:
|
||||
min: 1
|
||||
-
|
||||
name: rx-page-size
|
||||
doc: |
|
||||
Size in bytes of each device page the NIC writes into from the bound
|
||||
dmabuf. Must be a power of two and >= PAGE_SIZE; defaults to
|
||||
PAGE_SIZE.
|
||||
type: u32
|
||||
checks:
|
||||
min: page-size
|
||||
max: u32-max
|
||||
|
||||
operations:
|
||||
list:
|
||||
@@ -812,6 +830,7 @@ operations:
|
||||
- ifindex
|
||||
- fd
|
||||
- queues
|
||||
- rx-page-size
|
||||
reply:
|
||||
attributes:
|
||||
- id
|
||||
|
||||
@@ -219,6 +219,7 @@ enum {
|
||||
NETDEV_A_DMABUF_QUEUES,
|
||||
NETDEV_A_DMABUF_FD,
|
||||
NETDEV_A_DMABUF_ID,
|
||||
NETDEV_A_DMABUF_RX_PAGE_SIZE,
|
||||
|
||||
__NETDEV_A_DMABUF_MAX,
|
||||
NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1)
|
||||
|
||||
@@ -46,7 +46,7 @@ static dma_addr_t net_devmem_get_dma_addr(const struct net_iov *niov)
|
||||
|
||||
owner = net_devmem_iov_to_chunk_owner(niov);
|
||||
return owner->base_dma_addr +
|
||||
((dma_addr_t)net_iov_idx(niov) << PAGE_SHIFT);
|
||||
((dma_addr_t)net_iov_idx(niov) << owner->binding->niov_shift);
|
||||
}
|
||||
|
||||
static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref)
|
||||
@@ -93,13 +93,14 @@ net_devmem_alloc_dmabuf(struct net_devmem_dmabuf_binding *binding)
|
||||
ssize_t offset;
|
||||
ssize_t index;
|
||||
|
||||
dma_addr = gen_pool_alloc_owner(binding->chunk_pool, PAGE_SIZE,
|
||||
dma_addr = gen_pool_alloc_owner(binding->chunk_pool,
|
||||
1UL << binding->niov_shift,
|
||||
(void **)&owner);
|
||||
if (!dma_addr)
|
||||
return NULL;
|
||||
|
||||
offset = dma_addr - owner->base_dma_addr;
|
||||
index = offset / PAGE_SIZE;
|
||||
index = offset >> binding->niov_shift;
|
||||
niov = &owner->area.niovs[index];
|
||||
|
||||
niov->desc.pp_magic = 0;
|
||||
@@ -113,12 +114,13 @@ void net_devmem_free_dmabuf(struct net_iov *niov)
|
||||
{
|
||||
struct net_devmem_dmabuf_binding *binding = net_devmem_iov_binding(niov);
|
||||
unsigned long dma_addr = net_devmem_get_dma_addr(niov);
|
||||
size_t niov_size = 1UL << binding->niov_shift;
|
||||
|
||||
if (WARN_ON(!gen_pool_has_addr(binding->chunk_pool, dma_addr,
|
||||
PAGE_SIZE)))
|
||||
niov_size)))
|
||||
return;
|
||||
|
||||
gen_pool_free(binding->chunk_pool, dma_addr, PAGE_SIZE);
|
||||
gen_pool_free(binding->chunk_pool, dma_addr, niov_size);
|
||||
}
|
||||
|
||||
void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
|
||||
@@ -163,6 +165,9 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
|
||||
u32 xa_idx;
|
||||
int err;
|
||||
|
||||
if (binding->niov_shift != PAGE_SHIFT)
|
||||
mp_params.rx_page_size = 1U << binding->niov_shift;
|
||||
|
||||
err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -184,10 +189,12 @@ struct net_devmem_dmabuf_binding *
|
||||
net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
struct device *dma_dev,
|
||||
enum dma_data_direction direction,
|
||||
unsigned int dmabuf_fd, struct netdev_nl_sock *priv,
|
||||
unsigned int dmabuf_fd, unsigned int niov_shift,
|
||||
struct netdev_nl_sock *priv,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct net_devmem_dmabuf_binding *binding;
|
||||
size_t niov_size = 1UL << niov_shift;
|
||||
static u32 id_alloc_next;
|
||||
struct scatterlist *sg;
|
||||
struct dma_buf *dmabuf;
|
||||
@@ -213,6 +220,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
|
||||
binding->dev = dev;
|
||||
binding->vdev = vdev;
|
||||
binding->niov_shift = niov_shift;
|
||||
xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
|
||||
|
||||
err = percpu_ref_init(&binding->ref,
|
||||
@@ -255,11 +263,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
}
|
||||
}
|
||||
|
||||
/* For simplicity we expect to make PAGE_SIZE allocations, but the
|
||||
* binding can be much more flexible than that. We may be able to
|
||||
* allocate MTU sized chunks here. Leave that for future work...
|
||||
*/
|
||||
binding->chunk_pool = gen_pool_create(PAGE_SHIFT,
|
||||
binding->chunk_pool = gen_pool_create(niov_shift,
|
||||
dev_to_node(&dev->dev));
|
||||
if (!binding->chunk_pool) {
|
||||
err = -ENOMEM;
|
||||
@@ -273,9 +277,12 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
size_t len = sg_dma_len(sg);
|
||||
struct net_iov *niov;
|
||||
|
||||
if (!IS_ALIGNED(len, PAGE_SIZE)) {
|
||||
if (!IS_ALIGNED(dma_addr, niov_size) ||
|
||||
!IS_ALIGNED(len, niov_size)) {
|
||||
err = -EINVAL;
|
||||
NL_SET_ERR_MSG(extack, "dma-buf SG length must be PAGE_SIZE aligned");
|
||||
NL_SET_ERR_MSG_FMT(extack,
|
||||
"dmabuf sg entry (addr=%pad, len=%zu) not aligned to niov size %zu",
|
||||
&dma_addr, len, niov_size);
|
||||
goto err_free_chunks;
|
||||
}
|
||||
|
||||
@@ -288,7 +295,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
|
||||
owner->area.base_virtual = virtual;
|
||||
owner->base_dma_addr = dma_addr;
|
||||
owner->area.num_niovs = len / PAGE_SIZE;
|
||||
owner->area.num_niovs = len >> niov_shift;
|
||||
owner->binding = binding;
|
||||
|
||||
err = gen_pool_add_owner(binding->chunk_pool, dma_addr,
|
||||
@@ -454,7 +461,7 @@ int mp_dmabuf_devmem_init(struct page_pool *pool)
|
||||
pool->dma_sync = false;
|
||||
pool->dma_sync_for_cpu = false;
|
||||
|
||||
if (pool->p.order != 0)
|
||||
if (pool->p.order != binding->niov_shift - PAGE_SHIFT)
|
||||
return -E2BIG;
|
||||
|
||||
net_devmem_dmabuf_binding_get(binding);
|
||||
|
||||
@@ -71,6 +71,8 @@ struct net_devmem_dmabuf_binding {
|
||||
*/
|
||||
struct net_iov **tx_vec;
|
||||
|
||||
unsigned int niov_shift;
|
||||
|
||||
struct work_struct unbind_w;
|
||||
};
|
||||
|
||||
@@ -93,7 +95,8 @@ struct net_devmem_dmabuf_binding *
|
||||
net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
struct device *dma_dev,
|
||||
enum dma_data_direction direction,
|
||||
unsigned int dmabuf_fd, struct netdev_nl_sock *priv,
|
||||
unsigned int dmabuf_fd, unsigned int niov_shift,
|
||||
struct netdev_nl_sock *priv,
|
||||
struct netlink_ext_ack *extack);
|
||||
struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id);
|
||||
void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);
|
||||
@@ -122,10 +125,11 @@ static inline u32 net_devmem_iov_binding_id(const struct net_iov *niov)
|
||||
|
||||
static inline unsigned long net_iov_virtual_addr(const struct net_iov *niov)
|
||||
{
|
||||
struct net_iov_area *owner = net_iov_owner(niov);
|
||||
struct dmabuf_genpool_chunk_owner *co =
|
||||
net_devmem_iov_to_chunk_owner(niov);
|
||||
|
||||
return owner->base_virtual +
|
||||
((unsigned long)net_iov_idx(niov) << PAGE_SHIFT);
|
||||
return net_iov_owner(niov)->base_virtual +
|
||||
((unsigned long)net_iov_idx(niov) << co->binding->niov_shift);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -175,6 +179,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
|
||||
struct device *dma_dev,
|
||||
enum dma_data_direction direction,
|
||||
unsigned int dmabuf_fd,
|
||||
unsigned int niov_shift,
|
||||
struct netdev_nl_sock *priv,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <uapi/linux/netdev.h>
|
||||
#include <net/netdev_netlink.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
/* Integer value ranges */
|
||||
static const struct netlink_range_validation netdev_a_page_pool_id_range = {
|
||||
@@ -27,6 +28,11 @@ static const struct netlink_range_validation netdev_a_napi_defer_hard_irqs_range
|
||||
.max = S32_MAX,
|
||||
};
|
||||
|
||||
static const struct netlink_range_validation netdev_a_dmabuf_rx_page_size_range = {
|
||||
.min = PAGE_SIZE,
|
||||
.max = U32_MAX,
|
||||
};
|
||||
|
||||
/* Common nested types */
|
||||
const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1] = {
|
||||
[NETDEV_A_LEASE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
|
||||
@@ -106,10 +112,11 @@ static const struct nla_policy netdev_qstats_get_nl_policy[NETDEV_A_QSTATS_SCOPE
|
||||
};
|
||||
|
||||
/* NETDEV_CMD_BIND_RX - do */
|
||||
static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_FD + 1] = {
|
||||
static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_RX_PAGE_SIZE + 1] = {
|
||||
[NETDEV_A_DMABUF_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1),
|
||||
[NETDEV_A_DMABUF_FD] = { .type = NLA_U32, },
|
||||
[NETDEV_A_DMABUF_QUEUES] = NLA_POLICY_NESTED(netdev_queue_id_nl_policy),
|
||||
[NETDEV_A_DMABUF_RX_PAGE_SIZE] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_dmabuf_rx_page_size_range),
|
||||
};
|
||||
|
||||
/* NETDEV_CMD_NAPI_SET - do */
|
||||
@@ -219,7 +226,7 @@ static const struct genl_split_ops netdev_nl_ops[] = {
|
||||
.cmd = NETDEV_CMD_BIND_RX,
|
||||
.doit = netdev_nl_bind_rx_doit,
|
||||
.policy = netdev_bind_rx_nl_policy,
|
||||
.maxattr = NETDEV_A_DMABUF_FD,
|
||||
.maxattr = NETDEV_A_DMABUF_RX_PAGE_SIZE,
|
||||
.flags = GENL_UNS_ADMIN_PERM | GENL_CMD_CAP_DO,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <uapi/linux/netdev.h>
|
||||
#include <net/netdev_netlink.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
/* Common nested types */
|
||||
extern const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1];
|
||||
|
||||
@@ -1013,6 +1013,7 @@ netdev_nl_get_dma_dev(struct net_device *netdev, unsigned long *rxq_bitmap,
|
||||
int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct net_devmem_dmabuf_binding *binding;
|
||||
unsigned int niov_shift = PAGE_SHIFT;
|
||||
u32 ifindex, dmabuf_fd, rxq_idx;
|
||||
struct netdev_nl_sock *priv;
|
||||
struct net_device *netdev;
|
||||
@@ -1030,6 +1031,18 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
ifindex = nla_get_u32(info->attrs[NETDEV_A_DEV_IFINDEX]);
|
||||
dmabuf_fd = nla_get_u32(info->attrs[NETDEV_A_DMABUF_FD]);
|
||||
|
||||
if (info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]) {
|
||||
u32 rx_page_size = nla_get_u32(info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]);
|
||||
|
||||
if (!is_power_of_2(rx_page_size)) {
|
||||
NL_SET_ERR_MSG_ATTR(info->extack,
|
||||
info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE],
|
||||
"rx-page-size must be a power of 2");
|
||||
return -EINVAL;
|
||||
}
|
||||
niov_shift = ilog2(rx_page_size);
|
||||
}
|
||||
|
||||
priv = genl_sk_priv_get(&netdev_nl_family, NETLINK_CB(skb).sk);
|
||||
if (IS_ERR(priv))
|
||||
return PTR_ERR(priv);
|
||||
@@ -1080,7 +1093,8 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
}
|
||||
|
||||
binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE,
|
||||
dmabuf_fd, priv, info->extack);
|
||||
dmabuf_fd, niov_shift, priv,
|
||||
info->extack);
|
||||
if (IS_ERR(binding)) {
|
||||
err = PTR_ERR(binding);
|
||||
goto err_rxq_bitmap;
|
||||
@@ -1221,7 +1235,7 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
|
||||
binding = net_devmem_bind_dmabuf(bind_dev,
|
||||
bind_dev != netdev ? netdev : NULL,
|
||||
dma_dev, DMA_TO_DEVICE, dmabuf_fd,
|
||||
priv, info->extack);
|
||||
PAGE_SHIFT, priv, info->extack);
|
||||
if (IS_ERR(binding)) {
|
||||
err = PTR_ERR(binding);
|
||||
goto err_unlock_bind_dev;
|
||||
|
||||
@@ -219,6 +219,7 @@ enum {
|
||||
NETDEV_A_DMABUF_QUEUES,
|
||||
NETDEV_A_DMABUF_FD,
|
||||
NETDEV_A_DMABUF_ID,
|
||||
NETDEV_A_DMABUF_RX_PAGE_SIZE,
|
||||
|
||||
__NETDEV_A_DMABUF_MAX,
|
||||
NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from os import path
|
||||
from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
|
||||
from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds,
|
||||
run_rx_large_niov)
|
||||
from lib.py import ksft_run, ksft_exit, ksft_disruptive
|
||||
from lib.py import NetDrvEpEnv
|
||||
|
||||
@@ -30,11 +31,17 @@ def check_rx_hds(cfg) -> None:
|
||||
run_rx_hds(cfg)
|
||||
|
||||
|
||||
def check_rx_large_niov(cfg) -> None:
|
||||
"""Run the devmem RX test with rx-page-size = 16 KiB."""
|
||||
run_rx_large_niov(cfg)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the devmem test cases."""
|
||||
with NetDrvEpEnv(__file__) as cfg:
|
||||
setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
|
||||
ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds],
|
||||
ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds,
|
||||
check_rx_large_niov],
|
||||
args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# pylint: disable=invalid-name,too-many-arguments
|
||||
"""Shared helpers for devmem TCP selftests."""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
|
||||
@@ -8,19 +10,82 @@ from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
|
||||
NetdevFamily)
|
||||
|
||||
|
||||
def require_devmem(cfg):
|
||||
"""Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported."""
|
||||
if not hasattr(cfg, "devmem_probed"):
|
||||
probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
|
||||
cfg.devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
|
||||
cfg.devmem_probed = True
|
||||
RX_PAGE_SIZE_DEFAULT = 0
|
||||
RX_PAGE_SIZE_16K = 16384
|
||||
|
||||
if not cfg.devmem_supported:
|
||||
PROBE_RX_PAGE_SIZES = (RX_PAGE_SIZE_DEFAULT, RX_PAGE_SIZE_16K)
|
||||
|
||||
NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages"
|
||||
|
||||
|
||||
def _is_aligned(value, alignment):
|
||||
"""Equivalent of the kernel IS_ALIGNED(value, alignment).
|
||||
|
||||
alignment must be a power of two.
|
||||
"""
|
||||
return (value & (alignment - 1)) == 0
|
||||
|
||||
|
||||
def _restore_nr_hugepages(nr_hugepages):
|
||||
with open(NR_HUGEPAGES_FILE, 'w', encoding='utf-8') as f:
|
||||
f.write(str(nr_hugepages))
|
||||
|
||||
|
||||
def _reserve_hugepages(want=64):
|
||||
"""Raise nr_hugepages to @want and arrange for it to be restored."""
|
||||
with open(NR_HUGEPAGES_FILE, 'r+', encoding='utf-8') as f:
|
||||
nr_hugepages = int(f.read().strip())
|
||||
if nr_hugepages >= want:
|
||||
return
|
||||
f.seek(0)
|
||||
f.write(str(want))
|
||||
defer(_restore_nr_hugepages, nr_hugepages)
|
||||
|
||||
|
||||
def _probe_devmem(cfg, rx_page_size):
|
||||
"""Return True if ncdevmem can bind cfg.ifname at @rx_page_size."""
|
||||
probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
|
||||
if rx_page_size != RX_PAGE_SIZE_DEFAULT:
|
||||
probe_command += f" -b {rx_page_size}"
|
||||
return cmd(probe_command, fail=False, shell=True).ret == 0
|
||||
|
||||
|
||||
def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT):
|
||||
"""Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported."""
|
||||
if rx_page_size not in PROBE_RX_PAGE_SIZES:
|
||||
raise RuntimeError(
|
||||
f"rx-page-size={rx_page_size} is missing from "
|
||||
f"PROBE_RX_PAGE_SIZES, so it was never probed.")
|
||||
|
||||
if not hasattr(cfg, "devmem_supported"):
|
||||
_reserve_hugepages()
|
||||
# Probe every size upfront: in nk tests a leased queue may land in
|
||||
# ncdevmem's queue range and cause the probe to fail.
|
||||
cfg.devmem_supported = {size: _probe_devmem(cfg, size)
|
||||
for size in PROBE_RX_PAGE_SIZES}
|
||||
|
||||
if not cfg.devmem_supported[RX_PAGE_SIZE_DEFAULT]:
|
||||
raise KsftSkipEx("Test requires devmem support")
|
||||
|
||||
if rx_page_size != RX_PAGE_SIZE_DEFAULT:
|
||||
page_size = os.sysconf("SC_PAGE_SIZE")
|
||||
if not _is_aligned(rx_page_size, page_size):
|
||||
raise KsftSkipEx(
|
||||
f"rx-page-size={rx_page_size} is invalid for this platform "
|
||||
f"(must be a multiple of PAGE_SIZE={page_size})")
|
||||
|
||||
if not cfg.devmem_supported[rx_page_size]:
|
||||
raise KsftSkipEx(
|
||||
f"Test requires devmem rx-page-size={rx_page_size} support")
|
||||
|
||||
|
||||
def configure_nic(cfg):
|
||||
"""Channels, rings, RSS, queue lease for netkit devmem."""
|
||||
if not hasattr(cfg, "devmem_supported"):
|
||||
raise RuntimeError(
|
||||
"require_devmem() must be called before configure_nic(), which "
|
||||
"may lease a queue away and make later probes fail.")
|
||||
|
||||
if not hasattr(cfg, 'netns'):
|
||||
return
|
||||
|
||||
@@ -75,7 +140,8 @@ def set_flow_rule(cfg, port):
|
||||
return int(re.search(r'ID (\d+)', output).group(1))
|
||||
|
||||
|
||||
def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False):
|
||||
def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False,
|
||||
rx_page_size=RX_PAGE_SIZE_DEFAULT):
|
||||
"""Build the ncdevmem RX listener command."""
|
||||
if hasattr(cfg, 'netns'):
|
||||
flow_rule_id = set_flow_rule(cfg, port)
|
||||
@@ -95,6 +161,8 @@ def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False):
|
||||
extras.append("-v 7")
|
||||
if fail_on_linear:
|
||||
extras.append("-L")
|
||||
if rx_page_size != RX_PAGE_SIZE_DEFAULT:
|
||||
extras.append(f"-b {rx_page_size}")
|
||||
|
||||
parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}",
|
||||
f"-p {port}", *extras]
|
||||
@@ -201,6 +269,33 @@ def run_tx_chunks(cfg):
|
||||
ksft_eq(socat.stdout.strip(), "hello\nworld")
|
||||
|
||||
|
||||
def run_rx_large_niov(cfg):
|
||||
"""Run the devmem RX test with a large niov (rx-page-size > PAGE_SIZE).
|
||||
|
||||
Sweep payload sizes that straddle the niov boundary: below, equal to,
|
||||
and above rx_page_size, to exercise sub-niov, exact-niov, and multi-niov
|
||||
RX paths.
|
||||
"""
|
||||
require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_16K)
|
||||
_reserve_hugepages()
|
||||
configure_nic(cfg)
|
||||
netns = getattr(cfg, "netns", None)
|
||||
|
||||
for size in [1024, 4096, 8192, 16384, 32768, 65536]:
|
||||
port = rand_port()
|
||||
socat = socat_send(cfg, port)
|
||||
listen_cmd = ncdevmem_rx(cfg, port,
|
||||
flow_steer=not netns,
|
||||
rx_page_size=RX_PAGE_SIZE_16K)
|
||||
data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | "
|
||||
f"head -c {size} | {socat}")
|
||||
with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
|
||||
wait_port_listen(port, proto="tcp", ns=netns)
|
||||
cmd(data_pipe, host=cfg.remote, shell=True)
|
||||
ksft_eq(ncdevmem.ret, 0,
|
||||
f"large-niov failed for payload size {size}")
|
||||
|
||||
|
||||
def run_rx_hds(cfg):
|
||||
"""Run the HDS test by running devmem RX across a segment size sweep."""
|
||||
require_devmem(cfg)
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
#include <linux/uio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
@@ -61,6 +62,7 @@
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <linux/memfd.h>
|
||||
#include <sys/param.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/udmabuf.h>
|
||||
@@ -79,6 +81,7 @@
|
||||
#define PAGE_SHIFT 12
|
||||
#define TEST_PREFIX "ncdevmem"
|
||||
#define NUM_PAGES 16000
|
||||
#define MB(x) ((x) << 20)
|
||||
|
||||
#ifndef MSG_SOCK_DEVMEM
|
||||
#define MSG_SOCK_DEVMEM 0x2000000
|
||||
@@ -100,6 +103,7 @@ static unsigned int dmabuf_id;
|
||||
static uint32_t tx_dmabuf_id;
|
||||
static int waittime_ms = 500;
|
||||
static bool fail_on_linear;
|
||||
static uint32_t rx_page_size;
|
||||
|
||||
/* System state loaded by current_config_load() */
|
||||
#define MAX_FLOWS 8
|
||||
@@ -142,6 +146,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
|
||||
{
|
||||
struct udmabuf_create create;
|
||||
struct memory_buffer *ctx;
|
||||
unsigned int memfd_flags;
|
||||
int ret;
|
||||
|
||||
ctx = malloc(sizeof(*ctx));
|
||||
@@ -156,9 +161,14 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
|
||||
goto err_free_ctx;
|
||||
}
|
||||
|
||||
ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING);
|
||||
memfd_flags = MFD_ALLOW_SEALING;
|
||||
if (rx_page_size > getpagesize())
|
||||
memfd_flags |= MFD_HUGETLB | MFD_HUGE_2MB;
|
||||
|
||||
ctx->memfd = memfd_create("udmabuf-test", memfd_flags);
|
||||
if (ctx->memfd < 0) {
|
||||
pr_err("[skip,no-memfd]");
|
||||
pr_err("[skip,no-memfd%s]",
|
||||
(memfd_flags & MFD_HUGETLB) ? " (need hugepages)" : "");
|
||||
goto err_close_dev;
|
||||
}
|
||||
|
||||
@@ -168,6 +178,11 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
|
||||
goto err_close_memfd;
|
||||
}
|
||||
|
||||
if (memfd_flags & MFD_HUGETLB) {
|
||||
size = roundup(size, MB(2));
|
||||
ctx->size = size;
|
||||
}
|
||||
|
||||
ret = ftruncate(ctx->memfd, size);
|
||||
if (ret == -1) {
|
||||
pr_err("[FAIL,memfd-truncate]");
|
||||
@@ -699,6 +714,8 @@ static int bind_rx_queue(unsigned int ifindex, unsigned int dmabuf_fd,
|
||||
netdev_bind_rx_req_set_ifindex(req, ifindex);
|
||||
netdev_bind_rx_req_set_fd(req, dmabuf_fd);
|
||||
__netdev_bind_rx_req_set_queues(req, queues, n_queue_index);
|
||||
if (rx_page_size)
|
||||
netdev_bind_rx_req_set_rx_page_size(req, rx_page_size);
|
||||
|
||||
rsp = netdev_bind_rx(*ys, req);
|
||||
if (!rsp) {
|
||||
@@ -1411,7 +1428,7 @@ int main(int argc, char *argv[])
|
||||
int is_server = 0, opt;
|
||||
int ret, err = 1;
|
||||
|
||||
while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:n")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:nb:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'L':
|
||||
fail_on_linear = true;
|
||||
@@ -1446,6 +1463,19 @@ int main(int argc, char *argv[])
|
||||
case 'n':
|
||||
skip_config = 1;
|
||||
break;
|
||||
case 'b': {
|
||||
unsigned long val;
|
||||
|
||||
errno = 0;
|
||||
val = strtoul(optarg, NULL, 0);
|
||||
if ((val == ULONG_MAX && errno == ERANGE) ||
|
||||
val > UINT32_MAX) {
|
||||
pr_err("invalid rx_page_size: %s", optarg);
|
||||
return 1;
|
||||
}
|
||||
rx_page_size = val;
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
fprintf(stderr, "unknown option: %c\n", optopt);
|
||||
break;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"""Test devmem TCP with netkit."""
|
||||
|
||||
import os
|
||||
from devmem_lib import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
|
||||
from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds,
|
||||
run_rx_large_niov)
|
||||
from lib.py import ksft_run, ksft_exit, ksft_disruptive
|
||||
from lib.py import NetDrvContEnv
|
||||
|
||||
@@ -31,6 +32,11 @@ def check_nk_rx_hds(cfg) -> None:
|
||||
run_rx_hds(cfg)
|
||||
|
||||
|
||||
def check_nk_rx_large_niov(cfg) -> None:
|
||||
"""Run the devmem RX large-niov test through netkit."""
|
||||
run_rx_large_niov(cfg)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the netkit devmem test cases."""
|
||||
with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg:
|
||||
@@ -38,7 +44,7 @@ def main() -> None:
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"ncdevmem"))
|
||||
ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks,
|
||||
check_nk_rx_hds], args=(cfg,))
|
||||
check_nk_rx_hds, check_nk_rx_large_niov], args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user