blob: f81ab188ba1a80b76e5f3bc2e3b4a422de59d19e [file] [log] [blame]
/*
* asr netdevice skb ring buffer driver
*
* Copyright (C) 2020 ASR Micro Limited
*
*/
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/in.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <asm/atomic.h>
#include <linux/skbrb.h>
/* minimal memory requaired for skbrb */
#define SKBRB_MIN_MEM_REQUIRE (64 * 1024 * 1024)
#define SKBRB_MIN_FREE_SLOTS 10
static struct skbrb skb_rb[SKBRB_TYPE_MAX];
static inline void skbrb_slot_free(void *data, void *ptr __maybe_unused,
size_t len __maybe_unused)
{
struct skbrb_slot *rx_slot = (struct skbrb_slot *)data;
struct skbrb *rb = (struct skbrb *)&skb_rb[rx_slot->type];
rx_slot->inuse = false;
atomic_inc(&rb->free_cnt);
}
static struct sk_buff *__skbrb_alloc_skb(int type, unsigned int length)
{
struct sk_buff *skb = NULL;
struct skbrb *rb = (struct skbrb *)&skb_rb[type];
int curr_slot;
int i = 0;
if (unlikely(!rb->addr)) {
skb = dev_alloc_skb(length);
return skb;
}
spin_lock(&rb->lock);
curr_slot = rb->curr_slot;
do {
if (rb->rx_slots[curr_slot].inuse == false)
break;
if (atomic_read(&rb->free_cnt) < SKBRB_MIN_FREE_SLOTS) {
spin_unlock(&rb->lock);
return NULL;
}
curr_slot++;
if (curr_slot == rb->slot_cnt)
curr_slot = 0;
i++;
BUG_ON(i > rb->slot_cnt);
} while (1);
rb->rx_slots[curr_slot].inuse = true;
rb->curr_slot = curr_slot;
rb->curr_slot++;
if (rb->curr_slot == rb->slot_cnt)
rb->curr_slot = 0;
spin_unlock(&rb->lock);
atomic_dec(&rb->free_cnt);
skb = alloc_skb_p(rb->rx_slots[curr_slot].offset,
rb->slot_size, skbrb_slot_free,
&rb->rx_slots[curr_slot], GFP_ATOMIC);
if (skb)
/* bypass the skb copy operation in fastpath & pipe */
GET_SKBRB_CB(skb)->flag = SKB_FROM_RB_BIT;
else
/* Put back this slot */
rb->rx_slots[curr_slot].inuse = false;
return skb;
}
static struct sk_buff *skbrb_alloc_skb(int type, unsigned int length)
{
struct sk_buff *skb;
skb = __skbrb_alloc_skb(type, length);
if (!skb)
skb = dev_alloc_skb(length);
return skb;
}
static int skbrb_init(int type, int slot_size, int slot_cnt)
{
int i;
struct skbrb *rb;
#if 0
if (totalram_pages() < (SKBRB_MIN_MEM_REQUIRE / PAGE_SIZE)) {
pr_warn("minimal memory required for skbrb is %dMB.\n",
SKBRB_MIN_MEM_REQUIRE / 1024 / 1024);
return -1;
}
#endif
rb = (struct skbrb *)&skb_rb[type];
if (rb->addr) {
pr_info("skb rx ring buffer exist already.\n");
return 0;
}
spin_lock_init(&rb->lock);
rb->slot_size = slot_size;
rb->slot_cnt = slot_cnt;
rb->addr = (char *)kmalloc(slot_size * slot_cnt, GFP_KERNEL);
if (!rb->addr) {
pr_err("failed to allocate rx ring buffer memory\n");
return -1;
}
rb->rx_slots = (struct skbrb_slot *)kmalloc(
slot_cnt * sizeof(struct skbrb_slot), GFP_KERNEL);
if (!rb->rx_slots) {
pr_err("failed to allocate rx slots\n");
kfree(rb->addr);
rb->addr = NULL;
return -1;
}
for (i = 0; i < slot_cnt; i++) {
rb->rx_slots[i].index = i;
rb->rx_slots[i].inuse = false;
rb->rx_slots[i].offset = rb->addr + slot_size * i;
rb->rx_slots[i].type = type;
}
atomic_set(&rb->free_cnt, slot_cnt);
rb->curr_slot = 0;
pr_info("skbrb_init(0x%x): type: %d, slot size: %d, slot cnt: %d. Done.\n",
(unsigned int)rb->addr, type, slot_size, slot_cnt);
return 0;
}
static void skbrb_release(int type)
{
struct skbrb *rb = (struct skbrb *)&skb_rb[type];
if (rb->rx_slots)
kfree(rb->rx_slots);
if (rb->addr)
kfree(rb->addr);
memset(rb, 0, sizeof(struct skbrb));
}
struct sk_buff *emac_skbrb_alloc_skb(unsigned int length)
{
return __skbrb_alloc_skb(SKBRB_TYPE_EMAC, length);
}
struct sk_buff *wifi_skbrb_alloc_skb(unsigned int length)
{
return skbrb_alloc_skb(SKBRB_TYPE_WIFI, length);
}
int emac_skbrb_init(int slot_size, int slot_cnt)
{
return skbrb_init(SKBRB_TYPE_EMAC, slot_size, slot_cnt);
}
void emac_skbrb_release(void)
{
skbrb_release(SKBRB_TYPE_EMAC);
}
int wifi_skbrb_init(int slot_size, int slot_cnt)
{
return skbrb_init(SKBRB_TYPE_WIFI, slot_size, slot_cnt);
}
void wifi_skbrb_release(void)
{
skbrb_release(SKBRB_TYPE_WIFI);
}