root/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c
// SPDX-License-Identifier: GPL-2.0+

#include <linux/bpf.h>
#include <linux/bpf_trace.h>
#include <linux/filter.h>

#include "lan966x_main.h"

static int lan966x_xdp_setup(struct net_device *dev, struct netdev_bpf *xdp)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;
        struct bpf_prog *old_prog;
        bool old_xdp, new_xdp;
        int err;

        if (!lan966x->fdma) {
                NL_SET_ERR_MSG_MOD(xdp->extack,
                                   "Allow to set xdp only when using fdma");
                return -EOPNOTSUPP;
        }

        old_xdp = lan966x_xdp_present(lan966x);
        old_prog = xchg(&port->xdp_prog, xdp->prog);
        new_xdp = lan966x_xdp_present(lan966x);

        if (old_xdp == new_xdp)
                goto out;

        err = lan966x_fdma_reload_page_pool(lan966x);
        if (err) {
                xchg(&port->xdp_prog, old_prog);
                return err;
        }

out:
        if (old_prog)
                bpf_prog_put(old_prog);

        return 0;
}

int lan966x_xdp(struct net_device *dev, struct netdev_bpf *xdp)
{
        switch (xdp->command) {
        case XDP_SETUP_PROG:
                return lan966x_xdp_setup(dev, xdp);
        default:
                return -EINVAL;
        }
}

int lan966x_xdp_xmit(struct net_device *dev,
                     int n,
                     struct xdp_frame **frames,
                     u32 flags)
{
        struct lan966x_port *port = netdev_priv(dev);
        int nxmit = 0;

        for (int i = 0; i < n; ++i) {
                struct xdp_frame *xdpf = frames[i];
                int err;

                err = lan966x_fdma_xmit_xdpf(port, xdpf, 0);
                if (err)
                        break;

                nxmit++;
        }

        return nxmit;
}

int lan966x_xdp_run(struct lan966x_port *port, struct page *page, u32 data_len)
{
        struct bpf_prog *xdp_prog = port->xdp_prog;
        struct lan966x *lan966x = port->lan966x;
        struct xdp_buff xdp;
        u32 act;

        xdp_init_buff(&xdp, PAGE_SIZE << lan966x->rx.page_order,
                      &port->xdp_rxq);
        xdp_prepare_buff(&xdp, page_address(page),
                         IFH_LEN_BYTES + XDP_PACKET_HEADROOM,
                         data_len - IFH_LEN_BYTES, false);
        act = bpf_prog_run_xdp(xdp_prog, &xdp);
        switch (act) {
        case XDP_PASS:
                return FDMA_PASS;
        case XDP_TX:
                return lan966x_fdma_xmit_xdpf(port, page,
                                              data_len - IFH_LEN_BYTES) ?
                       FDMA_DROP : FDMA_TX;
        case XDP_REDIRECT:
                if (xdp_do_redirect(port->dev, &xdp, xdp_prog))
                        return FDMA_DROP;

                return FDMA_REDIRECT;
        default:
                bpf_warn_invalid_xdp_action(port->dev, xdp_prog, act);
                fallthrough;
        case XDP_ABORTED:
                trace_xdp_exception(port->dev, xdp_prog, act);
                fallthrough;
        case XDP_DROP:
                return FDMA_DROP;
        }
}

bool lan966x_xdp_present(struct lan966x *lan966x)
{
        for (int p = 0; p < lan966x->num_phys_ports; ++p) {
                if (!lan966x->ports[p])
                        continue;

                if (lan966x_xdp_port_present(lan966x->ports[p]))
                        return true;
        }

        return false;
}

int lan966x_xdp_port_init(struct lan966x_port *port)
{
        struct lan966x *lan966x = port->lan966x;

        return xdp_rxq_info_reg(&port->xdp_rxq, port->dev, 0,
                                lan966x->napi.napi_id);
}

void lan966x_xdp_port_deinit(struct lan966x_port *port)
{
        if (xdp_rxq_info_is_reg(&port->xdp_rxq))
                xdp_rxq_info_unreg(&port->xdp_rxq);
}