#include "vmxnet3_int.h"
#include "vmxnet3_xdp.h"
static void
vmxnet3_xdp_exchange_program(struct vmxnet3_adapter *adapter,
struct bpf_prog *prog)
{
rcu_assign_pointer(adapter->xdp_bpf_prog, prog);
}
static inline struct vmxnet3_tx_queue *
vmxnet3_xdp_get_tq(struct vmxnet3_adapter *adapter)
{
struct vmxnet3_tx_queue *tq;
int tq_number;
int cpu;
tq_number = adapter->num_tx_queues;
cpu = smp_processor_id();
if (likely(cpu < tq_number))
tq = &adapter->tx_queue[cpu];
else
tq = &adapter->tx_queue[reciprocal_scale(cpu, tq_number)];
return tq;
}
static int
vmxnet3_xdp_set(struct net_device *netdev, struct netdev_bpf *bpf,
struct netlink_ext_ack *extack)
{
struct vmxnet3_adapter *adapter = netdev_priv(netdev);
struct bpf_prog *new_bpf_prog = bpf->prog;
struct bpf_prog *old_bpf_prog;
bool need_update;
bool running;
int err;
if (new_bpf_prog && netdev->mtu > VMXNET3_XDP_MAX_MTU) {
NL_SET_ERR_MSG_FMT_MOD(extack, "MTU %u too large for XDP",
netdev->mtu);
return -EOPNOTSUPP;
}
if (adapter->netdev->features & NETIF_F_LRO) {
NL_SET_ERR_MSG_MOD(extack, "LRO is not supported with XDP");
adapter->netdev->features &= ~NETIF_F_LRO;
}
old_bpf_prog = rcu_dereference(adapter->xdp_bpf_prog);
if (!new_bpf_prog && !old_bpf_prog)
return 0;
running = netif_running(netdev);
need_update = !!old_bpf_prog != !!new_bpf_prog;
if (running && need_update)
vmxnet3_quiesce_dev(adapter);
vmxnet3_xdp_exchange_program(adapter, new_bpf_prog);
if (old_bpf_prog)
bpf_prog_put(old_bpf_prog);
if (!running || !need_update)
return 0;
if (new_bpf_prog)
xdp_features_set_redirect_target(netdev, false);
else
xdp_features_clear_redirect_target(netdev);
vmxnet3_reset_dev(adapter);
vmxnet3_rq_destroy_all(adapter);
vmxnet3_adjust_rx_ring_size(adapter);
err = vmxnet3_rq_create_all(adapter);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"failed to re-create rx queues for XDP.");
return -EOPNOTSUPP;
}
err = vmxnet3_activate_dev(adapter);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"failed to activate device for XDP.");
return -EOPNOTSUPP;
}
clear_bit(VMXNET3_STATE_BIT_RESETTING, &adapter->state);
return 0;
}
int
vmxnet3_xdp(struct net_device *netdev, struct netdev_bpf *bpf)
{
switch (bpf->command) {
case XDP_SETUP_PROG:
return vmxnet3_xdp_set(netdev, bpf, bpf->extack);
default:
return -EINVAL;
}
return 0;
}
static int
vmxnet3_xdp_xmit_frame(struct vmxnet3_adapter *adapter,
struct xdp_frame *xdpf,
struct vmxnet3_tx_queue *tq, bool dma_map)
{
struct vmxnet3_tx_buf_info *tbi = NULL;
union Vmxnet3_GenericDesc *gdesc;
struct vmxnet3_tx_ctx ctx;
int tx_num_deferred;
struct page *page;
u32 buf_size;
u32 dw2;
dw2 = (tq->tx_ring.gen ^ 0x1) << VMXNET3_TXD_GEN_SHIFT;
dw2 |= xdpf->len;
ctx.sop_txd = tq->tx_ring.base + tq->tx_ring.next2fill;
gdesc = ctx.sop_txd;
buf_size = xdpf->len;
tbi = tq->buf_info + tq->tx_ring.next2fill;
if (vmxnet3_cmd_ring_desc_avail(&tq->tx_ring) == 0) {
tq->stats.tx_ring_full++;
return -ENOSPC;
}
tbi->map_type = VMXNET3_MAP_XDP;
if (dma_map) {
tbi->dma_addr = dma_map_single(&adapter->pdev->dev,
xdpf->data, buf_size,
DMA_TO_DEVICE);
if (dma_mapping_error(&adapter->pdev->dev, tbi->dma_addr))
return -EFAULT;
tbi->map_type |= VMXNET3_MAP_SINGLE;
} else {
page = virt_to_page(xdpf->data);
tbi->dma_addr = page_pool_get_dma_addr(page) +
VMXNET3_XDP_HEADROOM;
dma_sync_single_for_device(&adapter->pdev->dev,
tbi->dma_addr, buf_size,
DMA_TO_DEVICE);
}
tbi->xdpf = xdpf;
tbi->len = buf_size;
gdesc = tq->tx_ring.base + tq->tx_ring.next2fill;
WARN_ON_ONCE(gdesc->txd.gen == tq->tx_ring.gen);
gdesc->txd.addr = cpu_to_le64(tbi->dma_addr);
gdesc->dword[2] = cpu_to_le32(dw2);
gdesc->dword[3] = cpu_to_le32(VMXNET3_TXD_CQ | VMXNET3_TXD_EOP);
gdesc->txd.om = 0;
gdesc->txd.msscof = 0;
gdesc->txd.hlen = 0;
gdesc->txd.ti = 0;
tx_num_deferred = le32_to_cpu(tq->shared->txNumDeferred);
le32_add_cpu(&tq->shared->txNumDeferred, 1);
tx_num_deferred++;
vmxnet3_cmd_ring_adv_next2fill(&tq->tx_ring);
tbi->sop_idx = ctx.sop_txd - tq->tx_ring.base;
dma_wmb();
gdesc->dword[2] = cpu_to_le32(le32_to_cpu(gdesc->dword[2]) ^
VMXNET3_TXD_GEN);
if (tx_num_deferred >= le32_to_cpu(tq->shared->txThreshold)) {
tq->shared->txNumDeferred = 0;
VMXNET3_WRITE_BAR0_REG(adapter,
VMXNET3_REG_TXPROD + tq->qid * 8,
tq->tx_ring.next2fill);
}
return 0;
}
static int
vmxnet3_xdp_xmit_back(struct vmxnet3_adapter *adapter,
struct xdp_frame *xdpf)
{
struct vmxnet3_tx_queue *tq;
struct netdev_queue *nq;
int err;
tq = vmxnet3_xdp_get_tq(adapter);
if (tq->stopped)
return -ENETDOWN;
nq = netdev_get_tx_queue(adapter->netdev, tq->qid);
__netif_tx_lock(nq, smp_processor_id());
err = vmxnet3_xdp_xmit_frame(adapter, xdpf, tq, false);
__netif_tx_unlock(nq);
return err;
}
int
vmxnet3_xdp_xmit(struct net_device *dev,
int n, struct xdp_frame **frames, u32 flags)
{
struct vmxnet3_adapter *adapter = netdev_priv(dev);
struct vmxnet3_tx_queue *tq;
int i;
if (unlikely(test_bit(VMXNET3_STATE_BIT_QUIESCED, &adapter->state)))
return -ENETDOWN;
if (unlikely(test_bit(VMXNET3_STATE_BIT_RESETTING, &adapter->state)))
return -EINVAL;
tq = vmxnet3_xdp_get_tq(adapter);
if (tq->stopped)
return -ENETDOWN;
for (i = 0; i < n; i++) {
if (vmxnet3_xdp_xmit_frame(adapter, frames[i], tq, true)) {
tq->stats.xdp_xmit_err++;
break;
}
}
tq->stats.xdp_xmit += i;
return i;
}
static int
vmxnet3_run_xdp(struct vmxnet3_rx_queue *rq, struct xdp_buff *xdp,
struct bpf_prog *prog)
{
struct xdp_frame *xdpf;
struct page *page;
int err;
u32 act;
rq->stats.xdp_packets++;
act = bpf_prog_run_xdp(prog, xdp);
page = virt_to_page(xdp->data_hard_start);
switch (act) {
case XDP_PASS:
return act;
case XDP_REDIRECT:
err = xdp_do_redirect(rq->adapter->netdev, xdp, prog);
if (!err) {
rq->stats.xdp_redirects++;
} else {
rq->stats.xdp_drops++;
page_pool_recycle_direct(rq->page_pool, page);
}
return act;
case XDP_TX:
xdpf = xdp_convert_buff_to_frame(xdp);
if (unlikely(!xdpf ||
vmxnet3_xdp_xmit_back(rq->adapter, xdpf))) {
rq->stats.xdp_drops++;
page_pool_recycle_direct(rq->page_pool, page);
} else {
rq->stats.xdp_tx++;
}
return act;
default:
bpf_warn_invalid_xdp_action(rq->adapter->netdev, prog, act);
fallthrough;
case XDP_ABORTED:
trace_xdp_exception(rq->adapter->netdev, prog, act);
rq->stats.xdp_aborted++;
break;
case XDP_DROP:
rq->stats.xdp_drops++;
break;
}
page_pool_recycle_direct(rq->page_pool, page);
return act;
}
static struct sk_buff *
vmxnet3_build_skb(struct vmxnet3_rx_queue *rq, struct page *page,
const struct xdp_buff *xdp)
{
struct sk_buff *skb;
skb = build_skb(page_address(page), PAGE_SIZE);
if (unlikely(!skb)) {
page_pool_recycle_direct(rq->page_pool, page);
rq->stats.rx_buf_alloc_failure++;
return NULL;
}
skb_reserve(skb, xdp->data - xdp->data_hard_start);
skb_put(skb, xdp->data_end - xdp->data);
skb_mark_for_recycle(skb);
return skb;
}
int
vmxnet3_process_xdp_small(struct vmxnet3_adapter *adapter,
struct vmxnet3_rx_queue *rq,
void *data, int len,
struct sk_buff **skb_xdp_pass)
{
struct bpf_prog *xdp_prog;
struct xdp_buff xdp;
struct page *page;
int act;
page = page_pool_alloc_pages(rq->page_pool, GFP_ATOMIC);
if (unlikely(!page)) {
rq->stats.rx_buf_alloc_failure++;
return XDP_DROP;
}
xdp_init_buff(&xdp, PAGE_SIZE, &rq->xdp_rxq);
xdp_prepare_buff(&xdp, page_address(page), rq->page_pool->p.offset,
len, false);
xdp_buff_clear_frags_flag(&xdp);
memcpy(xdp.data, data, len);
xdp_prog = rcu_dereference(rq->adapter->xdp_bpf_prog);
if (!xdp_prog) {
act = XDP_PASS;
goto out_skb;
}
act = vmxnet3_run_xdp(rq, &xdp, xdp_prog);
if (act != XDP_PASS)
return act;
out_skb:
*skb_xdp_pass = vmxnet3_build_skb(rq, page, &xdp);
if (!*skb_xdp_pass)
return XDP_DROP;
return likely(*skb_xdp_pass) ? act : XDP_DROP;
}
int
vmxnet3_process_xdp(struct vmxnet3_adapter *adapter,
struct vmxnet3_rx_queue *rq,
struct Vmxnet3_RxCompDesc *rcd,
struct vmxnet3_rx_buf_info *rbi,
struct Vmxnet3_RxDesc *rxd,
struct sk_buff **skb_xdp_pass)
{
struct bpf_prog *xdp_prog;
dma_addr_t new_dma_addr;
struct xdp_buff xdp;
struct page *page;
void *new_data;
int act;
page = rbi->page;
dma_sync_single_for_cpu(&adapter->pdev->dev,
page_pool_get_dma_addr(page) +
rq->page_pool->p.offset, rcd->len,
page_pool_get_dma_dir(rq->page_pool));
xdp_init_buff(&xdp, rbi->len, &rq->xdp_rxq);
xdp_prepare_buff(&xdp, page_address(page), rq->page_pool->p.offset,
rcd->len, false);
xdp_buff_clear_frags_flag(&xdp);
xdp_prog = rcu_dereference(rq->adapter->xdp_bpf_prog);
if (!xdp_prog) {
act = XDP_PASS;
goto out_skb;
}
act = vmxnet3_run_xdp(rq, &xdp, xdp_prog);
if (act == XDP_PASS) {
out_skb:
*skb_xdp_pass = vmxnet3_build_skb(rq, page, &xdp);
if (!*skb_xdp_pass)
act = XDP_DROP;
}
new_data = vmxnet3_pp_get_buff(rq->page_pool, &new_dma_addr,
GFP_ATOMIC);
if (!new_data) {
rq->stats.rx_buf_alloc_failure++;
return XDP_DROP;
}
rbi->page = virt_to_page(new_data);
rbi->dma_addr = new_dma_addr;
rxd->addr = cpu_to_le64(rbi->dma_addr);
rxd->len = rbi->len;
return act;
}