root/drivers/net/ethernet/intel/libeth/rx.c
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2024-2025 Intel Corporation */

#define DEFAULT_SYMBOL_NAMESPACE        "LIBETH"

#include <linux/export.h>

#include <net/libeth/rx.h>

/* Rx buffer management */

/**
 * libeth_rx_hw_len_mtu - get the actual buffer size to be passed to HW
 * @pp: &page_pool_params of the netdev to calculate the size for
 * @max_len: maximum buffer size for a single descriptor
 *
 * Return: HW-writeable length per one buffer to pass it to the HW accounting:
 * MTU the @dev has, HW required alignment, minimum and maximum allowed values,
 * and system's page size.
 */
static u32 libeth_rx_hw_len_mtu(const struct page_pool_params *pp, u32 max_len)
{
        u32 len;

        len = READ_ONCE(pp->netdev->mtu) + LIBETH_RX_LL_LEN;
        len = ALIGN(len, LIBETH_RX_BUF_STRIDE);
        len = min3(len, ALIGN_DOWN(max_len ? : U32_MAX, LIBETH_RX_BUF_STRIDE),
                   pp->max_len);

        return len;
}

/**
 * libeth_rx_hw_len_truesize - get the short buffer size to be passed to HW
 * @pp: &page_pool_params of the netdev to calculate the size for
 * @max_len: maximum buffer size for a single descriptor
 * @truesize: desired truesize for the buffers
 *
 * Return: HW-writeable length per one buffer to pass it to the HW ignoring the
 * MTU and closest to the passed truesize. Can be used for "short" buffer
 * queues to fragment pages more efficiently.
 */
static u32 libeth_rx_hw_len_truesize(const struct page_pool_params *pp,
                                     u32 max_len, u32 truesize)
{
        u32 min, len;

        min = SKB_HEAD_ALIGN(pp->offset + LIBETH_RX_BUF_STRIDE);
        truesize = clamp(roundup_pow_of_two(truesize), roundup_pow_of_two(min),
                         PAGE_SIZE << LIBETH_RX_PAGE_ORDER);

        len = SKB_WITH_OVERHEAD(truesize - pp->offset);
        len = ALIGN_DOWN(len, LIBETH_RX_BUF_STRIDE) ? : LIBETH_RX_BUF_STRIDE;
        len = min3(len, ALIGN_DOWN(max_len ? : U32_MAX, LIBETH_RX_BUF_STRIDE),
                   pp->max_len);

        return len;
}

/**
 * libeth_rx_page_pool_params - calculate params with the stack overhead
 * @fq: buffer queue to calculate the size for
 * @pp: &page_pool_params of the netdev
 *
 * Set the PP params to will all needed stack overhead (headroom, tailroom) and
 * both the HW buffer length and the truesize for all types of buffers. For
 * "short" buffers, truesize never exceeds the "wanted" one; for the rest,
 * it can be up to the page size.
 *
 * Return: true on success, false on invalid input params.
 */
static bool libeth_rx_page_pool_params(struct libeth_fq *fq,
                                       struct page_pool_params *pp)
{
        pp->offset = fq->xdp ? LIBETH_XDP_HEADROOM : LIBETH_SKB_HEADROOM;
        /* HW-writeable / syncable length per one page */
        pp->max_len = LIBETH_RX_PAGE_LEN(pp->offset);

        /* HW-writeable length per buffer */
        switch (fq->type) {
        case LIBETH_FQE_MTU:
                fq->buf_len = libeth_rx_hw_len_mtu(pp, fq->buf_len);
                break;
        case LIBETH_FQE_SHORT:
                fq->buf_len = libeth_rx_hw_len_truesize(pp, fq->buf_len,
                                                        fq->truesize);
                break;
        case LIBETH_FQE_HDR:
                fq->buf_len = ALIGN(LIBETH_MAX_HEAD, LIBETH_RX_BUF_STRIDE);
                break;
        default:
                return false;
        }

        /* Buffer size to allocate */
        fq->truesize = roundup_pow_of_two(SKB_HEAD_ALIGN(pp->offset +
                                                         fq->buf_len));

        return true;
}

/**
 * libeth_rx_page_pool_params_zc - calculate params without the stack overhead
 * @fq: buffer queue to calculate the size for
 * @pp: &page_pool_params of the netdev
 *
 * Set the PP params to exclude the stack overhead and both the buffer length
 * and the truesize, which are equal for the data buffers. Note that this
 * requires separate header buffers to be always active and account the
 * overhead.
 * With the MTU == ``PAGE_SIZE``, this allows the kernel to enable the zerocopy
 * mode.
 *
 * Return: true on success, false on invalid input params.
 */
static bool libeth_rx_page_pool_params_zc(struct libeth_fq *fq,
                                          struct page_pool_params *pp)
{
        u32 mtu, max;

        pp->offset = 0;
        pp->max_len = PAGE_SIZE << LIBETH_RX_PAGE_ORDER;

        switch (fq->type) {
        case LIBETH_FQE_MTU:
                mtu = READ_ONCE(pp->netdev->mtu);
                break;
        case LIBETH_FQE_SHORT:
                mtu = fq->truesize;
                break;
        default:
                return false;
        }

        mtu = roundup_pow_of_two(mtu);
        max = min(rounddown_pow_of_two(fq->buf_len ? : U32_MAX),
                  pp->max_len);

        fq->buf_len = clamp(mtu, LIBETH_RX_BUF_STRIDE, max);
        fq->truesize = fq->buf_len;

        return true;
}

/**
 * libeth_rx_fq_create - create a PP with the default libeth settings
 * @fq: buffer queue struct to fill
 * @napi: &napi_struct covering this PP (no usage outside its poll loops)
 *
 * Return: %0 on success, -%errno on failure.
 */
int libeth_rx_fq_create(struct libeth_fq *fq, struct napi_struct *napi)
{
        struct page_pool_params pp = {
                .flags          = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
                .order          = LIBETH_RX_PAGE_ORDER,
                .pool_size      = fq->count,
                .nid            = fq->nid,
                .dev            = napi->dev->dev.parent,
                .netdev         = napi->dev,
                .napi           = napi,
        };
        struct libeth_fqe *fqes;
        struct page_pool *pool;
        int ret;

        pp.dma_dir = fq->xdp ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE;

        if (!fq->hsplit)
                ret = libeth_rx_page_pool_params(fq, &pp);
        else
                ret = libeth_rx_page_pool_params_zc(fq, &pp);
        if (!ret)
                return -EINVAL;

        pool = page_pool_create(&pp);
        if (IS_ERR(pool))
                return PTR_ERR(pool);

        fqes = kvcalloc_node(fq->count, sizeof(*fqes), GFP_KERNEL, fq->nid);
        if (!fqes) {
                ret = -ENOMEM;
                goto err_buf;
        }

        ret = xdp_reg_page_pool(pool);
        if (ret)
                goto err_mem;

        fq->fqes = fqes;
        fq->pp = pool;

        return 0;

err_mem:
        kvfree(fqes);
err_buf:
        page_pool_destroy(pool);

        return ret;
}
EXPORT_SYMBOL_GPL(libeth_rx_fq_create);

/**
 * libeth_rx_fq_destroy - destroy a &page_pool created by libeth
 * @fq: buffer queue to process
 */
void libeth_rx_fq_destroy(struct libeth_fq *fq)
{
        xdp_unreg_page_pool(fq->pp);
        kvfree(fq->fqes);
        page_pool_destroy(fq->pp);
}
EXPORT_SYMBOL_GPL(libeth_rx_fq_destroy);

/**
 * libeth_rx_recycle_slow - recycle libeth netmem
 * @netmem: network memory to recycle
 *
 * To be used on exceptions or rare cases not requiring fast inline recycling.
 */
void __cold libeth_rx_recycle_slow(netmem_ref netmem)
{
        page_pool_put_full_netmem(netmem_get_pp(netmem), netmem, false);
}
EXPORT_SYMBOL_GPL(libeth_rx_recycle_slow);

/* Converting abstract packet type numbers into a software structure with
 * the packet parameters to do O(1) lookup on Rx.
 */

static const u16 libeth_rx_pt_xdp_oip[] = {
        [LIBETH_RX_PT_OUTER_L2]         = XDP_RSS_TYPE_NONE,
        [LIBETH_RX_PT_OUTER_IPV4]       = XDP_RSS_L3_IPV4,
        [LIBETH_RX_PT_OUTER_IPV6]       = XDP_RSS_L3_IPV6,
};

static const u16 libeth_rx_pt_xdp_iprot[] = {
        [LIBETH_RX_PT_INNER_NONE]       = XDP_RSS_TYPE_NONE,
        [LIBETH_RX_PT_INNER_UDP]        = XDP_RSS_L4_UDP,
        [LIBETH_RX_PT_INNER_TCP]        = XDP_RSS_L4_TCP,
        [LIBETH_RX_PT_INNER_SCTP]       = XDP_RSS_L4_SCTP,
        [LIBETH_RX_PT_INNER_ICMP]       = XDP_RSS_L4_ICMP,
        [LIBETH_RX_PT_INNER_TIMESYNC]   = XDP_RSS_TYPE_NONE,
};

static const u16 libeth_rx_pt_xdp_pl[] = {
        [LIBETH_RX_PT_PAYLOAD_NONE]     = XDP_RSS_TYPE_NONE,
        [LIBETH_RX_PT_PAYLOAD_L2]       = XDP_RSS_TYPE_NONE,
        [LIBETH_RX_PT_PAYLOAD_L3]       = XDP_RSS_TYPE_NONE,
        [LIBETH_RX_PT_PAYLOAD_L4]       = XDP_RSS_L4,
};

/**
 * libeth_rx_pt_gen_hash_type - generate an XDP RSS hash type for a PT
 * @pt: PT structure to evaluate
 *
 * Generates ```hash_type``` field with XDP RSS type values from the parsed
 * packet parameters if they're obtained dynamically at runtime.
 */
void libeth_rx_pt_gen_hash_type(struct libeth_rx_pt *pt)
{
        pt->hash_type = 0;
        pt->hash_type |= libeth_rx_pt_xdp_oip[pt->outer_ip];
        pt->hash_type |= libeth_rx_pt_xdp_iprot[pt->inner_prot];
        pt->hash_type |= libeth_rx_pt_xdp_pl[pt->payload_layer];
}
EXPORT_SYMBOL_GPL(libeth_rx_pt_gen_hash_type);

/* Module */

MODULE_DESCRIPTION("Common Ethernet library");
MODULE_LICENSE("GPL");