mirror of
https://github.com/torvalds/linux.git
synced 2026-05-28 17:13:52 +02:00
The only user of frag_size field in XDP RxQ info is
bpf_xdp_frags_increase_tail(). It clearly expects whole buffer size instead
of DMA write size. Different assumptions in idpf driver configuration lead
to negative tailroom.
To make it worse, buffer sizes are not actually uniform in idpf when
splitq is enabled, as there are several buffer queues, so rxq->rx_buf_size
is meaningless in this case.
Use truesize of the first bufq in AF_XDP ZC, as there is only one. Disable
growing tail for regular splitq.
Fixes: ac8a861f63 ("idpf: prepare structures to support XDP")
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Larysa Zaremba <larysa.zaremba@intel.com>
Link: https://patch.msgid.link/20260305111253.2317394-8-larysa.zaremba@intel.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
530 lines
12 KiB
C
530 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (C) 2025 Intel Corporation */
|
|
|
|
#include "idpf.h"
|
|
#include "idpf_ptp.h"
|
|
#include "idpf_virtchnl.h"
|
|
#include "xdp.h"
|
|
#include "xsk.h"
|
|
|
|
static int idpf_rxq_for_each(const struct idpf_q_vec_rsrc *rsrc,
|
|
int (*fn)(struct idpf_rx_queue *rxq, void *arg),
|
|
void *arg)
|
|
{
|
|
bool splitq = idpf_is_queue_model_split(rsrc->rxq_model);
|
|
|
|
if (!rsrc->rxq_grps)
|
|
return -ENETDOWN;
|
|
|
|
for (u32 i = 0; i < rsrc->num_rxq_grp; i++) {
|
|
const struct idpf_rxq_group *rx_qgrp = &rsrc->rxq_grps[i];
|
|
u32 num_rxq;
|
|
|
|
if (splitq)
|
|
num_rxq = rx_qgrp->splitq.num_rxq_sets;
|
|
else
|
|
num_rxq = rx_qgrp->singleq.num_rxq;
|
|
|
|
for (u32 j = 0; j < num_rxq; j++) {
|
|
struct idpf_rx_queue *q;
|
|
int err;
|
|
|
|
if (splitq)
|
|
q = &rx_qgrp->splitq.rxq_sets[j]->rxq;
|
|
else
|
|
q = rx_qgrp->singleq.rxqs[j];
|
|
|
|
err = fn(q, arg);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __idpf_xdp_rxq_info_init(struct idpf_rx_queue *rxq, void *arg)
|
|
{
|
|
const struct idpf_vport *vport = rxq->q_vector->vport;
|
|
const struct idpf_q_vec_rsrc *rsrc;
|
|
u32 frag_size = 0;
|
|
bool split;
|
|
int err;
|
|
|
|
if (idpf_queue_has(XSK, rxq))
|
|
frag_size = rxq->bufq_sets[0].bufq.truesize;
|
|
|
|
err = __xdp_rxq_info_reg(&rxq->xdp_rxq, vport->netdev, rxq->idx,
|
|
rxq->q_vector->napi.napi_id,
|
|
frag_size);
|
|
if (err)
|
|
return err;
|
|
|
|
rsrc = &vport->dflt_qv_rsrc;
|
|
split = idpf_is_queue_model_split(rsrc->rxq_model);
|
|
|
|
if (idpf_queue_has(XSK, rxq)) {
|
|
err = xdp_rxq_info_reg_mem_model(&rxq->xdp_rxq,
|
|
MEM_TYPE_XSK_BUFF_POOL,
|
|
rxq->pool);
|
|
if (err)
|
|
goto unreg;
|
|
} else {
|
|
const struct page_pool *pp;
|
|
|
|
pp = split ? rxq->bufq_sets[0].bufq.pp : rxq->pp;
|
|
xdp_rxq_info_attach_page_pool(&rxq->xdp_rxq, pp);
|
|
}
|
|
|
|
if (!split)
|
|
return 0;
|
|
|
|
rxq->xdpsqs = &vport->txqs[rsrc->xdp_txq_offset];
|
|
rxq->num_xdp_txq = vport->num_xdp_txq;
|
|
|
|
return 0;
|
|
|
|
unreg:
|
|
xdp_rxq_info_unreg(&rxq->xdp_rxq);
|
|
|
|
return err;
|
|
}
|
|
|
|
int idpf_xdp_rxq_info_init(struct idpf_rx_queue *rxq)
|
|
{
|
|
return __idpf_xdp_rxq_info_init(rxq, NULL);
|
|
}
|
|
|
|
int idpf_xdp_rxq_info_init_all(const struct idpf_q_vec_rsrc *rsrc)
|
|
{
|
|
return idpf_rxq_for_each(rsrc, __idpf_xdp_rxq_info_init, NULL);
|
|
}
|
|
|
|
static int __idpf_xdp_rxq_info_deinit(struct idpf_rx_queue *rxq, void *arg)
|
|
{
|
|
if (idpf_is_queue_model_split((size_t)arg)) {
|
|
rxq->xdpsqs = NULL;
|
|
rxq->num_xdp_txq = 0;
|
|
}
|
|
|
|
if (!idpf_queue_has(XSK, rxq))
|
|
xdp_rxq_info_detach_mem_model(&rxq->xdp_rxq);
|
|
|
|
xdp_rxq_info_unreg(&rxq->xdp_rxq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void idpf_xdp_rxq_info_deinit(struct idpf_rx_queue *rxq, u32 model)
|
|
{
|
|
__idpf_xdp_rxq_info_deinit(rxq, (void *)(size_t)model);
|
|
}
|
|
|
|
void idpf_xdp_rxq_info_deinit_all(const struct idpf_q_vec_rsrc *rsrc)
|
|
{
|
|
idpf_rxq_for_each(rsrc, __idpf_xdp_rxq_info_deinit,
|
|
(void *)(size_t)rsrc->rxq_model);
|
|
}
|
|
|
|
static int idpf_xdp_rxq_assign_prog(struct idpf_rx_queue *rxq, void *arg)
|
|
{
|
|
struct bpf_prog *prog = arg;
|
|
struct bpf_prog *old;
|
|
|
|
if (prog)
|
|
bpf_prog_inc(prog);
|
|
|
|
old = rcu_replace_pointer(rxq->xdp_prog, prog, lockdep_rtnl_is_held());
|
|
if (old)
|
|
bpf_prog_put(old);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void idpf_xdp_copy_prog_to_rqs(const struct idpf_q_vec_rsrc *rsrc,
|
|
struct bpf_prog *xdp_prog)
|
|
{
|
|
idpf_rxq_for_each(rsrc, idpf_xdp_rxq_assign_prog, xdp_prog);
|
|
}
|
|
|
|
static void idpf_xdp_tx_timer(struct work_struct *work);
|
|
|
|
int idpf_xdpsqs_get(const struct idpf_vport *vport)
|
|
{
|
|
struct libeth_xdpsq_timer **timers __free(kvfree) = NULL;
|
|
struct net_device *dev;
|
|
u32 sqs;
|
|
|
|
if (!idpf_xdp_enabled(vport))
|
|
return 0;
|
|
|
|
timers = kvzalloc_objs(*timers, vport->num_xdp_txq);
|
|
if (!timers)
|
|
return -ENOMEM;
|
|
|
|
for (u32 i = 0; i < vport->num_xdp_txq; i++) {
|
|
timers[i] = kzalloc_node(sizeof(*timers[i]), GFP_KERNEL,
|
|
cpu_to_mem(i));
|
|
if (!timers[i]) {
|
|
for (int j = i - 1; j >= 0; j--)
|
|
kfree(timers[j]);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
dev = vport->netdev;
|
|
sqs = vport->dflt_qv_rsrc.xdp_txq_offset;
|
|
|
|
for (u32 i = sqs; i < vport->num_txq; i++) {
|
|
struct idpf_tx_queue *xdpsq = vport->txqs[i];
|
|
|
|
xdpsq->complq = xdpsq->txq_grp->complq;
|
|
kfree(xdpsq->refillq);
|
|
xdpsq->refillq = NULL;
|
|
|
|
idpf_queue_clear(FLOW_SCH_EN, xdpsq);
|
|
idpf_queue_clear(FLOW_SCH_EN, xdpsq->complq);
|
|
idpf_queue_set(NOIRQ, xdpsq);
|
|
idpf_queue_set(XDP, xdpsq);
|
|
idpf_queue_set(XDP, xdpsq->complq);
|
|
|
|
xdpsq->timer = timers[i - sqs];
|
|
libeth_xdpsq_get(&xdpsq->xdp_lock, dev, vport->xdpsq_share);
|
|
libeth_xdpsq_init_timer(xdpsq->timer, xdpsq, &xdpsq->xdp_lock,
|
|
idpf_xdp_tx_timer);
|
|
|
|
xdpsq->pending = 0;
|
|
xdpsq->xdp_tx = 0;
|
|
xdpsq->thresh = libeth_xdp_queue_threshold(xdpsq->desc_count);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void idpf_xdpsqs_put(const struct idpf_vport *vport)
|
|
{
|
|
struct net_device *dev;
|
|
u32 sqs;
|
|
|
|
if (!idpf_xdp_enabled(vport))
|
|
return;
|
|
|
|
dev = vport->netdev;
|
|
sqs = vport->dflt_qv_rsrc.xdp_txq_offset;
|
|
|
|
for (u32 i = sqs; i < vport->num_txq; i++) {
|
|
struct idpf_tx_queue *xdpsq = vport->txqs[i];
|
|
|
|
if (!idpf_queue_has_clear(XDP, xdpsq))
|
|
continue;
|
|
|
|
libeth_xdpsq_deinit_timer(xdpsq->timer);
|
|
libeth_xdpsq_put(&xdpsq->xdp_lock, dev);
|
|
|
|
kfree(xdpsq->timer);
|
|
xdpsq->refillq = NULL;
|
|
idpf_queue_clear(NOIRQ, xdpsq);
|
|
}
|
|
}
|
|
|
|
static int idpf_xdp_parse_cqe(const struct idpf_splitq_4b_tx_compl_desc *desc,
|
|
bool gen)
|
|
{
|
|
u32 val;
|
|
|
|
#ifdef __LIBETH_WORD_ACCESS
|
|
val = *(const u32 *)desc;
|
|
#else
|
|
val = ((u32)le16_to_cpu(desc->q_head_compl_tag.q_head) << 16) |
|
|
le16_to_cpu(desc->qid_comptype_gen);
|
|
#endif
|
|
if (!!(val & IDPF_TXD_COMPLQ_GEN_M) != gen)
|
|
return -ENODATA;
|
|
|
|
if (unlikely((val & GENMASK(IDPF_TXD_COMPLQ_GEN_S - 1, 0)) !=
|
|
FIELD_PREP(IDPF_TXD_COMPLQ_COMPL_TYPE_M,
|
|
IDPF_TXD_COMPLT_RS)))
|
|
return -EINVAL;
|
|
|
|
return upper_16_bits(val);
|
|
}
|
|
|
|
u32 idpf_xdpsq_poll(struct idpf_tx_queue *xdpsq, u32 budget)
|
|
{
|
|
struct idpf_compl_queue *cq = xdpsq->complq;
|
|
u32 tx_ntc = xdpsq->next_to_clean;
|
|
u32 tx_cnt = xdpsq->desc_count;
|
|
u32 ntc = cq->next_to_clean;
|
|
u32 cnt = cq->desc_count;
|
|
u32 done_frames;
|
|
bool gen;
|
|
|
|
gen = idpf_queue_has(GEN_CHK, cq);
|
|
|
|
for (done_frames = 0; done_frames < budget; ) {
|
|
int ret;
|
|
|
|
ret = idpf_xdp_parse_cqe(&cq->comp_4b[ntc], gen);
|
|
if (ret >= 0) {
|
|
done_frames = ret > tx_ntc ? ret - tx_ntc :
|
|
ret + tx_cnt - tx_ntc;
|
|
goto next;
|
|
}
|
|
|
|
switch (ret) {
|
|
case -ENODATA:
|
|
goto out;
|
|
case -EINVAL:
|
|
break;
|
|
}
|
|
|
|
next:
|
|
if (unlikely(++ntc == cnt)) {
|
|
ntc = 0;
|
|
gen = !gen;
|
|
idpf_queue_change(GEN_CHK, cq);
|
|
}
|
|
}
|
|
|
|
out:
|
|
cq->next_to_clean = ntc;
|
|
|
|
return done_frames;
|
|
}
|
|
|
|
static u32 idpf_xdpsq_complete(void *_xdpsq, u32 budget)
|
|
{
|
|
struct libeth_xdpsq_napi_stats ss = { };
|
|
struct idpf_tx_queue *xdpsq = _xdpsq;
|
|
u32 tx_ntc = xdpsq->next_to_clean;
|
|
u32 tx_cnt = xdpsq->desc_count;
|
|
struct xdp_frame_bulk bq;
|
|
struct libeth_cq_pp cp = {
|
|
.dev = xdpsq->dev,
|
|
.bq = &bq,
|
|
.xss = &ss,
|
|
.napi = true,
|
|
};
|
|
u32 done_frames;
|
|
|
|
done_frames = idpf_xdpsq_poll(xdpsq, budget);
|
|
if (unlikely(!done_frames))
|
|
return 0;
|
|
|
|
xdp_frame_bulk_init(&bq);
|
|
|
|
for (u32 i = 0; likely(i < done_frames); i++) {
|
|
libeth_xdp_complete_tx(&xdpsq->tx_buf[tx_ntc], &cp);
|
|
|
|
if (unlikely(++tx_ntc == tx_cnt))
|
|
tx_ntc = 0;
|
|
}
|
|
|
|
xdp_flush_frame_bulk(&bq);
|
|
|
|
xdpsq->next_to_clean = tx_ntc;
|
|
xdpsq->pending -= done_frames;
|
|
xdpsq->xdp_tx -= cp.xdp_tx;
|
|
|
|
return done_frames;
|
|
}
|
|
|
|
static u32 idpf_xdp_tx_prep(void *_xdpsq, struct libeth_xdpsq *sq)
|
|
{
|
|
struct idpf_tx_queue *xdpsq = _xdpsq;
|
|
u32 free;
|
|
|
|
libeth_xdpsq_lock(&xdpsq->xdp_lock);
|
|
|
|
free = xdpsq->desc_count - xdpsq->pending;
|
|
if (free < xdpsq->thresh)
|
|
free += idpf_xdpsq_complete(xdpsq, xdpsq->thresh);
|
|
|
|
*sq = (struct libeth_xdpsq){
|
|
.sqes = xdpsq->tx_buf,
|
|
.descs = xdpsq->desc_ring,
|
|
.count = xdpsq->desc_count,
|
|
.lock = &xdpsq->xdp_lock,
|
|
.ntu = &xdpsq->next_to_use,
|
|
.pending = &xdpsq->pending,
|
|
.xdp_tx = &xdpsq->xdp_tx,
|
|
};
|
|
|
|
return free;
|
|
}
|
|
|
|
LIBETH_XDP_DEFINE_START();
|
|
LIBETH_XDP_DEFINE_TIMER(static idpf_xdp_tx_timer, idpf_xdpsq_complete);
|
|
LIBETH_XDP_DEFINE_FLUSH_TX(idpf_xdp_tx_flush_bulk, idpf_xdp_tx_prep,
|
|
idpf_xdp_tx_xmit);
|
|
LIBETH_XDP_DEFINE_FLUSH_XMIT(static idpf_xdp_xmit_flush_bulk, idpf_xdp_tx_prep,
|
|
idpf_xdp_tx_xmit);
|
|
LIBETH_XDP_DEFINE_END();
|
|
|
|
int idpf_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames,
|
|
u32 flags)
|
|
{
|
|
const struct idpf_netdev_priv *np = netdev_priv(dev);
|
|
const struct idpf_vport *vport = np->vport;
|
|
u32 xdp_txq_offset;
|
|
|
|
if (unlikely(!netif_carrier_ok(dev) || !vport->link_up))
|
|
return -ENETDOWN;
|
|
|
|
xdp_txq_offset = vport->dflt_qv_rsrc.xdp_txq_offset;
|
|
|
|
return libeth_xdp_xmit_do_bulk(dev, n, frames, flags,
|
|
&vport->txqs[xdp_txq_offset],
|
|
vport->num_xdp_txq,
|
|
idpf_xdp_xmit_flush_bulk,
|
|
idpf_xdp_tx_finalize);
|
|
}
|
|
|
|
static int idpf_xdpmo_rx_hash(const struct xdp_md *ctx, u32 *hash,
|
|
enum xdp_rss_hash_type *rss_type)
|
|
{
|
|
const struct libeth_xdp_buff *xdp = (typeof(xdp))ctx;
|
|
struct idpf_xdp_rx_desc desc __uninitialized;
|
|
const struct idpf_rx_queue *rxq;
|
|
struct libeth_rx_pt pt;
|
|
|
|
rxq = libeth_xdp_buff_to_rq(xdp, typeof(*rxq), xdp_rxq);
|
|
|
|
idpf_xdp_get_qw0(&desc, xdp->desc);
|
|
|
|
pt = rxq->rx_ptype_lkup[idpf_xdp_rx_pt(&desc)];
|
|
if (!libeth_rx_pt_has_hash(rxq->xdp_rxq.dev, pt))
|
|
return -ENODATA;
|
|
|
|
idpf_xdp_get_qw2(&desc, xdp->desc);
|
|
|
|
return libeth_xdpmo_rx_hash(hash, rss_type, idpf_xdp_rx_hash(&desc),
|
|
pt);
|
|
}
|
|
|
|
static int idpf_xdpmo_rx_timestamp(const struct xdp_md *ctx, u64 *timestamp)
|
|
{
|
|
const struct libeth_xdp_buff *xdp = (typeof(xdp))ctx;
|
|
struct idpf_xdp_rx_desc desc __uninitialized;
|
|
const struct idpf_rx_queue *rxq;
|
|
u64 cached_time, ts_ns;
|
|
u32 ts_high;
|
|
|
|
rxq = libeth_xdp_buff_to_rq(xdp, typeof(*rxq), xdp_rxq);
|
|
|
|
if (!idpf_queue_has(PTP, rxq))
|
|
return -ENODATA;
|
|
|
|
idpf_xdp_get_qw1(&desc, xdp->desc);
|
|
|
|
if (!(idpf_xdp_rx_ts_low(&desc) & VIRTCHNL2_RX_FLEX_TSTAMP_VALID))
|
|
return -ENODATA;
|
|
|
|
cached_time = READ_ONCE(rxq->cached_phc_time);
|
|
|
|
idpf_xdp_get_qw3(&desc, xdp->desc);
|
|
|
|
ts_high = idpf_xdp_rx_ts_high(&desc);
|
|
ts_ns = idpf_ptp_tstamp_extend_32b_to_64b(cached_time, ts_high);
|
|
|
|
*timestamp = ts_ns;
|
|
return 0;
|
|
}
|
|
|
|
static const struct xdp_metadata_ops idpf_xdpmo = {
|
|
.xmo_rx_hash = idpf_xdpmo_rx_hash,
|
|
.xmo_rx_timestamp = idpf_xdpmo_rx_timestamp,
|
|
};
|
|
|
|
void idpf_xdp_set_features(const struct idpf_vport *vport)
|
|
{
|
|
if (!idpf_is_queue_model_split(vport->dflt_qv_rsrc.rxq_model))
|
|
return;
|
|
|
|
libeth_xdp_set_features_noredir(vport->netdev, &idpf_xdpmo,
|
|
idpf_get_max_tx_bufs(vport->adapter),
|
|
libeth_xsktmo);
|
|
}
|
|
|
|
static int idpf_xdp_setup_prog(struct idpf_vport *vport,
|
|
const struct netdev_bpf *xdp)
|
|
{
|
|
const struct idpf_netdev_priv *np = netdev_priv(vport->netdev);
|
|
const struct idpf_q_vec_rsrc *rsrc = &vport->dflt_qv_rsrc;
|
|
struct bpf_prog *old, *prog = xdp->prog;
|
|
struct idpf_vport_config *cfg;
|
|
int ret;
|
|
|
|
cfg = vport->adapter->vport_config[vport->idx];
|
|
|
|
if (test_bit(IDPF_REMOVE_IN_PROG, vport->adapter->flags) ||
|
|
!test_bit(IDPF_VPORT_REG_NETDEV, cfg->flags) ||
|
|
!!vport->xdp_prog == !!prog) {
|
|
if (test_bit(IDPF_VPORT_UP, np->state))
|
|
idpf_xdp_copy_prog_to_rqs(rsrc, prog);
|
|
|
|
old = xchg(&vport->xdp_prog, prog);
|
|
if (old)
|
|
bpf_prog_put(old);
|
|
|
|
cfg->user_config.xdp_prog = prog;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!vport->num_xdp_txq && vport->num_txq == cfg->max_q.max_txq) {
|
|
NL_SET_ERR_MSG_MOD(xdp->extack,
|
|
"No Tx queues available for XDP, please decrease the number of regular SQs");
|
|
return -ENOSPC;
|
|
}
|
|
|
|
old = cfg->user_config.xdp_prog;
|
|
cfg->user_config.xdp_prog = prog;
|
|
|
|
ret = idpf_initiate_soft_reset(vport, IDPF_SR_Q_CHANGE);
|
|
if (ret) {
|
|
NL_SET_ERR_MSG_MOD(xdp->extack,
|
|
"Could not reopen the vport after XDP setup");
|
|
|
|
cfg->user_config.xdp_prog = old;
|
|
old = prog;
|
|
}
|
|
|
|
if (old)
|
|
bpf_prog_put(old);
|
|
|
|
libeth_xdp_set_redirect(vport->netdev, vport->xdp_prog);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int idpf_xdp(struct net_device *dev, struct netdev_bpf *xdp)
|
|
{
|
|
struct idpf_vport *vport;
|
|
int ret;
|
|
|
|
idpf_vport_ctrl_lock(dev);
|
|
vport = idpf_netdev_to_vport(dev);
|
|
|
|
if (!idpf_is_queue_model_split(vport->dflt_qv_rsrc.txq_model))
|
|
goto notsupp;
|
|
|
|
switch (xdp->command) {
|
|
case XDP_SETUP_PROG:
|
|
ret = idpf_xdp_setup_prog(vport, xdp);
|
|
break;
|
|
case XDP_SETUP_XSK_POOL:
|
|
ret = idpf_xsk_pool_setup(vport, xdp);
|
|
break;
|
|
default:
|
|
notsupp:
|
|
ret = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
|
|
idpf_vport_ctrl_unlock(dev);
|
|
|
|
return ret;
|
|
}
|