mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-05 20:30:41 +00:00

Add XSk counterparts for preparing XSk &libeth_xdp_buff (adding head and frags), running the program, and handling the verdict, inc. XDP_PASS. Shortcuts in comparison with regular Rx: frags and all verdicts except XDP_REDIRECT are under unlikely() and out of line; no checks for XDP program presence as it's always true for XSk. Suggested-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com> # optimizations Signed-off-by: Alexander Lobakin <aleksander.lobakin@intel.com> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
452 lines
12 KiB
C
452 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (C) 2025 Intel Corporation */
|
|
|
|
#define DEFAULT_SYMBOL_NAMESPACE "LIBETH_XDP"
|
|
|
|
#include <linux/export.h>
|
|
|
|
#include <net/libeth/xdp.h>
|
|
|
|
#include "priv.h"
|
|
|
|
/* XDPSQ sharing */
|
|
|
|
DEFINE_STATIC_KEY_FALSE(libeth_xdpsq_share);
|
|
EXPORT_SYMBOL_GPL(libeth_xdpsq_share);
|
|
|
|
void __libeth_xdpsq_get(struct libeth_xdpsq_lock *lock,
|
|
const struct net_device *dev)
|
|
{
|
|
bool warn;
|
|
|
|
spin_lock_init(&lock->lock);
|
|
lock->share = true;
|
|
|
|
warn = !static_key_enabled(&libeth_xdpsq_share);
|
|
static_branch_inc(&libeth_xdpsq_share);
|
|
|
|
if (warn && net_ratelimit())
|
|
netdev_warn(dev, "XDPSQ sharing enabled, possible XDP Tx slowdown\n");
|
|
}
|
|
EXPORT_SYMBOL_GPL(__libeth_xdpsq_get);
|
|
|
|
void __libeth_xdpsq_put(struct libeth_xdpsq_lock *lock,
|
|
const struct net_device *dev)
|
|
{
|
|
static_branch_dec(&libeth_xdpsq_share);
|
|
|
|
if (!static_key_enabled(&libeth_xdpsq_share) && net_ratelimit())
|
|
netdev_notice(dev, "XDPSQ sharing disabled\n");
|
|
|
|
lock->share = false;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__libeth_xdpsq_put);
|
|
|
|
void __acquires(&lock->lock)
|
|
__libeth_xdpsq_lock(struct libeth_xdpsq_lock *lock)
|
|
{
|
|
spin_lock(&lock->lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(__libeth_xdpsq_lock);
|
|
|
|
void __releases(&lock->lock)
|
|
__libeth_xdpsq_unlock(struct libeth_xdpsq_lock *lock)
|
|
{
|
|
spin_unlock(&lock->lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(__libeth_xdpsq_unlock);
|
|
|
|
/* XDPSQ clean-up timers */
|
|
|
|
/**
|
|
* libeth_xdpsq_init_timer - initialize an XDPSQ clean-up timer
|
|
* @timer: timer to initialize
|
|
* @xdpsq: queue this timer belongs to
|
|
* @lock: corresponding XDPSQ lock
|
|
* @poll: queue polling/completion function
|
|
*
|
|
* XDPSQ clean-up timers must be set up before using at the queue configuration
|
|
* time. Set the required pointers and the cleaning callback.
|
|
*/
|
|
void libeth_xdpsq_init_timer(struct libeth_xdpsq_timer *timer, void *xdpsq,
|
|
struct libeth_xdpsq_lock *lock,
|
|
void (*poll)(struct work_struct *work))
|
|
{
|
|
timer->xdpsq = xdpsq;
|
|
timer->lock = lock;
|
|
|
|
INIT_DELAYED_WORK(&timer->dwork, poll);
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdpsq_init_timer);
|
|
|
|
/* ``XDP_TX`` bulking */
|
|
|
|
static void __cold
|
|
libeth_xdp_tx_return_one(const struct libeth_xdp_tx_frame *frm)
|
|
{
|
|
if (frm->len_fl & LIBETH_XDP_TX_MULTI)
|
|
libeth_xdp_return_frags(frm->data + frm->soff, true);
|
|
|
|
libeth_xdp_return_va(frm->data, true);
|
|
}
|
|
|
|
static void __cold
|
|
libeth_xdp_tx_return_bulk(const struct libeth_xdp_tx_frame *bq, u32 count)
|
|
{
|
|
for (u32 i = 0; i < count; i++) {
|
|
const struct libeth_xdp_tx_frame *frm = &bq[i];
|
|
|
|
if (!(frm->len_fl & LIBETH_XDP_TX_FIRST))
|
|
continue;
|
|
|
|
libeth_xdp_tx_return_one(frm);
|
|
}
|
|
}
|
|
|
|
static void __cold libeth_trace_xdp_exception(const struct net_device *dev,
|
|
const struct bpf_prog *prog,
|
|
u32 act)
|
|
{
|
|
trace_xdp_exception(dev, prog, act);
|
|
}
|
|
|
|
/**
|
|
* libeth_xdp_tx_exception - handle Tx exceptions of XDP frames
|
|
* @bq: XDP Tx frame bulk
|
|
* @sent: number of frames sent successfully (from this bulk)
|
|
* @flags: internal libeth_xdp flags (XSk, .ndo_xdp_xmit etc.)
|
|
*
|
|
* Cold helper used by __libeth_xdp_tx_flush_bulk(), do not call directly.
|
|
* Reports XDP Tx exceptions, frees the frames that won't be sent or adjust
|
|
* the Tx bulk to try again later.
|
|
*/
|
|
void __cold libeth_xdp_tx_exception(struct libeth_xdp_tx_bulk *bq, u32 sent,
|
|
u32 flags)
|
|
{
|
|
const struct libeth_xdp_tx_frame *pos = &bq->bulk[sent];
|
|
u32 left = bq->count - sent;
|
|
|
|
if (!(flags & LIBETH_XDP_TX_NDO))
|
|
libeth_trace_xdp_exception(bq->dev, bq->prog, XDP_TX);
|
|
|
|
if (!(flags & LIBETH_XDP_TX_DROP)) {
|
|
memmove(bq->bulk, pos, left * sizeof(*bq->bulk));
|
|
bq->count = left;
|
|
|
|
return;
|
|
}
|
|
|
|
if (flags & LIBETH_XDP_TX_XSK)
|
|
libeth_xsk_tx_return_bulk(pos, left);
|
|
else if (!(flags & LIBETH_XDP_TX_NDO))
|
|
libeth_xdp_tx_return_bulk(pos, left);
|
|
else
|
|
libeth_xdp_xmit_return_bulk(pos, left, bq->dev);
|
|
|
|
bq->count = 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_tx_exception);
|
|
|
|
/* .ndo_xdp_xmit() implementation */
|
|
|
|
u32 __cold libeth_xdp_xmit_return_bulk(const struct libeth_xdp_tx_frame *bq,
|
|
u32 count, const struct net_device *dev)
|
|
{
|
|
u32 n = 0;
|
|
|
|
for (u32 i = 0; i < count; i++) {
|
|
const struct libeth_xdp_tx_frame *frm = &bq[i];
|
|
dma_addr_t dma;
|
|
|
|
if (frm->flags & LIBETH_XDP_TX_FIRST)
|
|
dma = *libeth_xdp_xmit_frame_dma(frm->xdpf);
|
|
else
|
|
dma = dma_unmap_addr(frm, dma);
|
|
|
|
dma_unmap_page(dev->dev.parent, dma, dma_unmap_len(frm, len),
|
|
DMA_TO_DEVICE);
|
|
|
|
/* Actual xdp_frames are freed by the core */
|
|
n += !!(frm->flags & LIBETH_XDP_TX_FIRST);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_xmit_return_bulk);
|
|
|
|
/* Rx polling path */
|
|
|
|
/**
|
|
* libeth_xdp_load_stash - recreate an &xdp_buff from libeth_xdp buffer stash
|
|
* @dst: target &libeth_xdp_buff to initialize
|
|
* @src: source stash
|
|
*
|
|
* External helper used by libeth_xdp_init_buff(), do not call directly.
|
|
* Recreate an onstack &libeth_xdp_buff using the stash saved earlier.
|
|
* The only field untouched (rxq) is initialized later in the
|
|
* abovementioned function.
|
|
*/
|
|
void libeth_xdp_load_stash(struct libeth_xdp_buff *dst,
|
|
const struct libeth_xdp_buff_stash *src)
|
|
{
|
|
dst->data = src->data;
|
|
dst->base.data_end = src->data + src->len;
|
|
dst->base.data_meta = src->data;
|
|
dst->base.data_hard_start = src->data - src->headroom;
|
|
|
|
dst->base.frame_sz = src->frame_sz;
|
|
dst->base.flags = src->flags;
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_load_stash);
|
|
|
|
/**
|
|
* libeth_xdp_save_stash - convert &xdp_buff to a libeth_xdp buffer stash
|
|
* @dst: target &libeth_xdp_buff_stash to initialize
|
|
* @src: source XDP buffer
|
|
*
|
|
* External helper used by libeth_xdp_save_buff(), do not call directly.
|
|
* Use the fields from the passed XDP buffer to initialize the stash on the
|
|
* queue, so that a partially received frame can be finished later during
|
|
* the next NAPI poll.
|
|
*/
|
|
void libeth_xdp_save_stash(struct libeth_xdp_buff_stash *dst,
|
|
const struct libeth_xdp_buff *src)
|
|
{
|
|
dst->data = src->data;
|
|
dst->headroom = src->data - src->base.data_hard_start;
|
|
dst->len = src->base.data_end - src->data;
|
|
|
|
dst->frame_sz = src->base.frame_sz;
|
|
dst->flags = src->base.flags;
|
|
|
|
WARN_ON_ONCE(dst->flags != src->base.flags);
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_save_stash);
|
|
|
|
void __libeth_xdp_return_stash(struct libeth_xdp_buff_stash *stash)
|
|
{
|
|
LIBETH_XDP_ONSTACK_BUFF(xdp);
|
|
|
|
libeth_xdp_load_stash(xdp, stash);
|
|
libeth_xdp_return_buff_slow(xdp);
|
|
|
|
stash->data = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__libeth_xdp_return_stash);
|
|
|
|
/**
|
|
* libeth_xdp_return_buff_slow - free &libeth_xdp_buff
|
|
* @xdp: buffer to free/return
|
|
*
|
|
* Slowpath version of libeth_xdp_return_buff() to be called on exceptions,
|
|
* queue clean-ups etc., without unwanted inlining.
|
|
*/
|
|
void __cold libeth_xdp_return_buff_slow(struct libeth_xdp_buff *xdp)
|
|
{
|
|
__libeth_xdp_return_buff(xdp, false);
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_return_buff_slow);
|
|
|
|
/**
|
|
* libeth_xdp_buff_add_frag - add frag to XDP buffer
|
|
* @xdp: head XDP buffer
|
|
* @fqe: Rx buffer containing the frag
|
|
* @len: frag length reported by HW
|
|
*
|
|
* External helper used by libeth_xdp_process_buff(), do not call directly.
|
|
* Frees both head and frag buffers on error.
|
|
*
|
|
* Return: true success, false on error (no space for a new frag).
|
|
*/
|
|
bool libeth_xdp_buff_add_frag(struct libeth_xdp_buff *xdp,
|
|
const struct libeth_fqe *fqe,
|
|
u32 len)
|
|
{
|
|
netmem_ref netmem = fqe->netmem;
|
|
|
|
if (!xdp_buff_add_frag(&xdp->base, netmem,
|
|
fqe->offset + netmem_get_pp(netmem)->p.offset,
|
|
len, fqe->truesize))
|
|
goto recycle;
|
|
|
|
return true;
|
|
|
|
recycle:
|
|
libeth_rx_recycle_slow(netmem);
|
|
libeth_xdp_return_buff_slow(xdp);
|
|
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_buff_add_frag);
|
|
|
|
/**
|
|
* libeth_xdp_prog_exception - handle XDP prog exceptions
|
|
* @bq: XDP Tx bulk
|
|
* @xdp: buffer to process
|
|
* @act: original XDP prog verdict
|
|
* @ret: error code if redirect failed
|
|
*
|
|
* External helper used by __libeth_xdp_run_prog() and
|
|
* __libeth_xsk_run_prog_slow(), do not call directly.
|
|
* Reports invalid @act, XDP exception trace event and frees the buffer.
|
|
*
|
|
* Return: libeth_xdp XDP prog verdict.
|
|
*/
|
|
u32 __cold libeth_xdp_prog_exception(const struct libeth_xdp_tx_bulk *bq,
|
|
struct libeth_xdp_buff *xdp,
|
|
enum xdp_action act, int ret)
|
|
{
|
|
if (act > XDP_REDIRECT)
|
|
bpf_warn_invalid_xdp_action(bq->dev, bq->prog, act);
|
|
|
|
libeth_trace_xdp_exception(bq->dev, bq->prog, act);
|
|
|
|
if (xdp->base.rxq->mem.type == MEM_TYPE_XSK_BUFF_POOL)
|
|
return libeth_xsk_prog_exception(xdp, act, ret);
|
|
|
|
libeth_xdp_return_buff_slow(xdp);
|
|
|
|
return LIBETH_XDP_DROP;
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_prog_exception);
|
|
|
|
/* Tx buffer completion */
|
|
|
|
static void libeth_xdp_put_netmem_bulk(netmem_ref netmem,
|
|
struct xdp_frame_bulk *bq)
|
|
{
|
|
if (unlikely(bq->count == XDP_BULK_QUEUE_SIZE))
|
|
xdp_flush_frame_bulk(bq);
|
|
|
|
bq->q[bq->count++] = netmem;
|
|
}
|
|
|
|
/**
|
|
* libeth_xdp_return_buff_bulk - free &xdp_buff as part of a bulk
|
|
* @sinfo: shared info corresponding to the buffer
|
|
* @bq: XDP frame bulk to store the buffer
|
|
* @frags: whether the buffer has frags
|
|
*
|
|
* Same as xdp_return_frame_bulk(), but for &libeth_xdp_buff, speeds up Tx
|
|
* completion of ``XDP_TX`` buffers and allows to free them in same bulks
|
|
* with &xdp_frame buffers.
|
|
*/
|
|
void libeth_xdp_return_buff_bulk(const struct skb_shared_info *sinfo,
|
|
struct xdp_frame_bulk *bq, bool frags)
|
|
{
|
|
if (!frags)
|
|
goto head;
|
|
|
|
for (u32 i = 0; i < sinfo->nr_frags; i++)
|
|
libeth_xdp_put_netmem_bulk(skb_frag_netmem(&sinfo->frags[i]),
|
|
bq);
|
|
|
|
head:
|
|
libeth_xdp_put_netmem_bulk(virt_to_netmem(sinfo), bq);
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_return_buff_bulk);
|
|
|
|
/* Misc */
|
|
|
|
/**
|
|
* libeth_xdp_queue_threshold - calculate XDP queue clean/refill threshold
|
|
* @count: number of descriptors in the queue
|
|
*
|
|
* The threshold is the limit at which RQs start to refill (when the number of
|
|
* empty buffers exceeds it) and SQs get cleaned up (when the number of free
|
|
* descriptors goes below it). To speed up hotpath processing, threshold is
|
|
* always pow-2, closest to 1/4 of the queue length.
|
|
* Don't call it on hotpath, calculate and cache the threshold during the
|
|
* queue initialization.
|
|
*
|
|
* Return: the calculated threshold.
|
|
*/
|
|
u32 libeth_xdp_queue_threshold(u32 count)
|
|
{
|
|
u32 quarter, low, high;
|
|
|
|
if (likely(is_power_of_2(count)))
|
|
return count >> 2;
|
|
|
|
quarter = DIV_ROUND_CLOSEST(count, 4);
|
|
low = rounddown_pow_of_two(quarter);
|
|
high = roundup_pow_of_two(quarter);
|
|
|
|
return high - quarter <= quarter - low ? high : low;
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_queue_threshold);
|
|
|
|
/**
|
|
* __libeth_xdp_set_features - set XDP features for netdev
|
|
* @dev: &net_device to configure
|
|
* @xmo: XDP metadata ops (Rx hints)
|
|
* @zc_segs: maximum number of S/G frags the HW can transmit
|
|
* @tmo: XSk Tx metadata ops (Tx hints)
|
|
*
|
|
* Set all the features libeth_xdp supports. Only the first argument is
|
|
* necessary; without the third one (zero), XSk support won't be advertised.
|
|
* Use the non-underscored versions in drivers instead.
|
|
*/
|
|
void __libeth_xdp_set_features(struct net_device *dev,
|
|
const struct xdp_metadata_ops *xmo,
|
|
u32 zc_segs,
|
|
const struct xsk_tx_metadata_ops *tmo)
|
|
{
|
|
xdp_set_features_flag(dev,
|
|
NETDEV_XDP_ACT_BASIC |
|
|
NETDEV_XDP_ACT_REDIRECT |
|
|
NETDEV_XDP_ACT_NDO_XMIT |
|
|
(zc_segs ? NETDEV_XDP_ACT_XSK_ZEROCOPY : 0) |
|
|
NETDEV_XDP_ACT_RX_SG |
|
|
NETDEV_XDP_ACT_NDO_XMIT_SG);
|
|
dev->xdp_metadata_ops = xmo;
|
|
|
|
tmo = tmo == libeth_xsktmo ? &libeth_xsktmo_slow : tmo;
|
|
|
|
dev->xdp_zc_max_segs = zc_segs ? : 1;
|
|
dev->xsk_tx_metadata_ops = zc_segs ? tmo : NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__libeth_xdp_set_features);
|
|
|
|
/**
|
|
* libeth_xdp_set_redirect - toggle the XDP redirect feature
|
|
* @dev: &net_device to configure
|
|
* @enable: whether XDP is enabled
|
|
*
|
|
* Use this when XDPSQs are not always available to dynamically enable
|
|
* and disable redirect feature.
|
|
*/
|
|
void libeth_xdp_set_redirect(struct net_device *dev, bool enable)
|
|
{
|
|
if (enable)
|
|
xdp_features_set_redirect_target(dev, true);
|
|
else
|
|
xdp_features_clear_redirect_target(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(libeth_xdp_set_redirect);
|
|
|
|
/* Module */
|
|
|
|
static const struct libeth_xdp_ops xdp_ops __initconst = {
|
|
.bulk = libeth_xdp_return_buff_bulk,
|
|
.xsk = libeth_xsk_buff_free_slow,
|
|
};
|
|
|
|
static int __init libeth_xdp_module_init(void)
|
|
{
|
|
libeth_attach_xdp(&xdp_ops);
|
|
|
|
return 0;
|
|
}
|
|
module_init(libeth_xdp_module_init);
|
|
|
|
static void __exit libeth_xdp_module_exit(void)
|
|
{
|
|
libeth_detach_xdp();
|
|
}
|
|
module_exit(libeth_xdp_module_exit);
|
|
|
|
MODULE_DESCRIPTION("Common Ethernet library - XDP infra");
|
|
MODULE_IMPORT_NS("LIBETH");
|
|
MODULE_LICENSE("GPL");
|