| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Linux driver for VMware's vmxnet3 ethernet NIC. |
| * Copyright (C) 2008-2023, VMware, Inc. All Rights Reserved. |
| * Maintained by: pv-drivers@vmware.com |
| * |
| */ |
| |
| #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; |
| } |
| |
| /* This is the main xdp call used by kernel to set/unset eBPF program. */ |
| 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) { /* ndo_xdp_xmit */ |
| 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 { /* XDP buffer from page pool */ |
| 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); |
| |
| /* Setup the EOP desc */ |
| 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); |
| |
| /* set the last buf_info for the pkt */ |
| 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); |
| |
| /* No need to handle the case when tx_num_deferred doesn't reach |
| * threshold. Backend driver at hypervisor side will poll and reset |
| * tq->shared->txNumDeferred to 0. |
| */ |
| 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; |
| } |
| |
| /* ndo_xdp_xmit */ |
| 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; |
| } |
| |
| /* bpf prog might change len and data position. */ |
| skb_reserve(skb, xdp->data - xdp->data_hard_start); |
| skb_put(skb, xdp->data_end - xdp->data); |
| skb_mark_for_recycle(skb); |
| |
| return skb; |
| } |
| |
| /* Handle packets from DataRing. */ |
| 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); |
| |
| /* Must copy the data because it's at dataring. */ |
| 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; |
| |
| /* No need to refill. */ |
| 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, rbi->len, |
| page_pool_get_dma_dir(rq->page_pool)); |
| |
| xdp_init_buff(&xdp, PAGE_SIZE, &rq->xdp_rxq); |
| xdp_prepare_buff(&xdp, page_address(page), rq->page_pool->p.offset, |
| rbi->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; |
| } |