root/drivers/staging/octeon/ethernet-mem.c
// SPDX-License-Identifier: GPL-2.0
/*
 * This file is based on code from OCTEON SDK by Cavium Networks.
 *
 * Copyright (c) 2003-2010 Cavium Networks
 */

#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/slab.h>

#include "octeon-ethernet.h"
#include "ethernet-mem.h"
#include "ethernet-defines.h"

/**
 * cvm_oct_fill_hw_skbuff - fill the supplied hardware pool with skbuffs
 * @pool:     Pool to allocate an skbuff for
 * @size:     Size of the buffer needed for the pool
 * @elements: Number of buffers to allocate
 *
 * Returns the actual number of buffers allocated.
 */
static int cvm_oct_fill_hw_skbuff(int pool, int size, int elements)
{
        int freed = elements;

        while (freed) {
                struct sk_buff *skb = dev_alloc_skb(size + 256);

                if (unlikely(!skb))
                        break;
                skb_reserve(skb, 256 - (((unsigned long)skb->data) & 0x7f));
                *(struct sk_buff **)(skb->data - sizeof(void *)) = skb;
                cvmx_fpa_free(skb->data, pool, size / 128);
                freed--;
        }
        return elements - freed;
}

/**
 * cvm_oct_free_hw_skbuff- free hardware pool skbuffs
 * @pool:     Pool to allocate an skbuff for
 * @size:     Size of the buffer needed for the pool
 * @elements: Number of buffers to allocate
 */
static void cvm_oct_free_hw_skbuff(int pool, int size, int elements)
{
        char *memory;

        do {
                memory = cvmx_fpa_alloc(pool);
                if (memory) {
                        struct sk_buff *skb =
                            *(struct sk_buff **)(memory - sizeof(void *));
                        elements--;
                        dev_kfree_skb(skb);
                }
        } while (memory);

        if (elements < 0)
                pr_warn("Freeing of pool %u had too many skbuffs (%d)\n",
                        pool, elements);
        else if (elements > 0)
                pr_warn("Freeing of pool %u is missing %d skbuffs\n",
                        pool, elements);
}

/**
 * cvm_oct_fill_hw_memory - fill a hardware pool with memory.
 * @pool:     Pool to populate
 * @size:     Size of each buffer in the pool
 * @elements: Number of buffers to allocate
 *
 * Returns the actual number of buffers allocated.
 */
static int cvm_oct_fill_hw_memory(int pool, int size, int elements)
{
        char *memory;
        char *fpa;
        int freed = elements;

        while (freed) {
                /*
                 * FPA memory must be 128 byte aligned.  Since we are
                 * aligning we need to save the original pointer so we
                 * can feed it to kfree when the memory is returned to
                 * the kernel.
                 *
                 * We allocate an extra 256 bytes to allow for
                 * alignment and space for the original pointer saved
                 * just before the block.
                 */
                memory = kmalloc(size + 256, GFP_ATOMIC);
                if (unlikely(!memory)) {
                        pr_warn("Unable to allocate %u bytes for FPA pool %d\n",
                                elements * size, pool);
                        break;
                }
                fpa = (char *)(((unsigned long)memory + 256) & ~0x7fUL);
                *((char **)fpa - 1) = memory;
                cvmx_fpa_free(fpa, pool, 0);
                freed--;
        }
        return elements - freed;
}

/**
 * cvm_oct_free_hw_memory - Free memory allocated by cvm_oct_fill_hw_memory
 * @pool:     FPA pool to free
 * @size:     Size of each buffer in the pool
 * @elements: Number of buffers that should be in the pool
 */
static void cvm_oct_free_hw_memory(int pool, int size, int elements)
{
        char *memory;
        char *fpa;

        do {
                fpa = cvmx_fpa_alloc(pool);
                if (fpa) {
                        elements--;
                        fpa = (char *)phys_to_virt(cvmx_ptr_to_phys(fpa));
                        memory = *((char **)fpa - 1);
                        kfree(memory);
                }
        } while (fpa);

        if (elements < 0)
                pr_warn("Freeing of pool %u had too many buffers (%d)\n",
                        pool, elements);
        else if (elements > 0)
                pr_warn("Warning: Freeing of pool %u is missing %d buffers\n",
                        pool, elements);
}

int cvm_oct_mem_fill_fpa(int pool, int size, int elements)
{
        int freed;

        if (pool == CVMX_FPA_PACKET_POOL)
                freed = cvm_oct_fill_hw_skbuff(pool, size, elements);
        else
                freed = cvm_oct_fill_hw_memory(pool, size, elements);
        return freed;
}

void cvm_oct_mem_empty_fpa(int pool, int size, int elements)
{
        if (pool == CVMX_FPA_PACKET_POOL)
                cvm_oct_free_hw_skbuff(pool, size, elements);
        else
                cvm_oct_free_hw_memory(pool, size, elements);
}