blob: 27c3e2f302ff807f3cd33b65bbc2f764c1207162 [file] [log] [blame]
/******************************************************************************
*
* Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved
*
*****************************************************************************/
#include <linux/string.h>
#include <linux/spinlock.h>
#include "mbulk.h"
#include "hip4_sampler.h"
#include "debug.h"
/* mbulk descriptor is aligned to 64 bytes considering the host processor's
* cache line size
*/
#define MBULK_ALIGN (64)
#define MBULK_IS_ALIGNED(s) (((uintptr_t)(s) & (MBULK_ALIGN - 1)) == 0)
#define MBULK_SZ_ROUNDUP(s) round_up(s, MBULK_ALIGN)
/* a magic number to allocate the remaining buffer to the bulk buffer
* in a segment. Used in chained mbulk allocation.
*/
#define MBULK_DAT_BUFSZ_REQ_BEST_MAGIC ((u32)(-2))
static DEFINE_SPINLOCK(mbulk_pool_lock);
static inline void mbulk_debug(struct mbulk *m)
{
(void)m; /* may be unused */
SLSI_DBG1_NODEV(SLSI_MBULK, "m->next_offset %p: %d\n", &m->next_offset, m->next_offset);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->flag %p: %d\n", &m->flag, m->flag);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->clas %p: %d\n", &m->clas, m->clas);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->pid %p: %d\n", &m->pid, m->pid);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->refcnt %p: %d\n", &m->refcnt, m->refcnt);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->dat_bufsz %p: %d\n", &m->dat_bufsz, m->dat_bufsz);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->sig_bufsz %p: %d\n", &m->sig_bufsz, m->sig_bufsz);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->len %p: %d\n", &m->len, m->len);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->head %p: %d\n", &m->head, m->head);
SLSI_DBG1_NODEV(SLSI_MBULK, "m->chain_next_offset %p: %d\n", &m->chain_next_offset, m->chain_next_offset);
}
/* Mbulk tracker - tracks mbulks sent and returned by fw */
struct mbulk_tracker {
mbulk_colour colour;
};
/* mbulk pool */
struct mbulk_pool {
bool valid; /** is valid */
u8 pid; /** pool id */
struct mbulk *free_list; /** head of free segment list */
int free_cnt; /** current number of free segments */
int usage[MBULK_CLASS_MAX]; /** statistics of usage per mbulk clas*/
char *base_addr; /** base address of the pool */
char *end_addr; /** exclusive end address of the pool */
mbulk_len_t seg_size; /** segment size in bytes "excluding" struct mbulk */
u8 guard; /** pool guard **/
int tot_seg_num; /** total number of segments in this pool */
struct mbulk_tracker *mbulk_tracker;
char shift;
#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING
int minor;
#endif
};
/* get a segment from a pool */
static inline struct mbulk *mbulk_pool_get(struct mbulk_pool *pool, enum mbulk_class clas)
{
struct mbulk *m;
u8 guard = pool->guard;
spin_lock_bh(&mbulk_pool_lock);
m = pool->free_list;
if (m == NULL || pool->free_cnt <= guard) { /* guard */
spin_unlock_bh(&mbulk_pool_lock);
return NULL;
}
pool->free_cnt--;
pool->usage[clas]++;
SCSC_HIP4_SAMPLER_MBULK(pool->minor, (pool->free_cnt & 0x100) >> 8, (pool->free_cnt & 0xff), pool->pid);
if (m->next_offset == 0)
pool->free_list = NULL;
else
pool->free_list = (struct mbulk *)((uintptr_t)pool->free_list + m->next_offset);
memset(m, 0, sizeof(*m));
m->pid = pool->pid;
m->clas = clas;
spin_unlock_bh(&mbulk_pool_lock);
return m;
}
/* put a segment to a pool */
static inline void mbulk_pool_put(struct mbulk_pool *pool, struct mbulk *m)
{
if (m->flag == MBULK_F_FREE)
return;
spin_lock_bh(&mbulk_pool_lock);
pool->usage[m->clas]--;
pool->free_cnt++;
SCSC_HIP4_SAMPLER_MBULK(pool->minor, (pool->free_cnt & 0x100) >> 8, (pool->free_cnt & 0xff), pool->pid);
m->flag = MBULK_F_FREE;
if (pool->free_list != NULL)
m->next_offset = (uintptr_t)pool->free_list - (uintptr_t)m;
else
m->next_offset = 0;
pool->free_list = m;
spin_unlock_bh(&mbulk_pool_lock);
}
/** mbulk pool configuration */
struct mbulk_pool_config {
mbulk_len_t seg_sz; /** segment size "excluding" struct mbulk */
int seg_num; /** number of segments. If -1, all remaining space is used */
};
/** mbulk pools */
static struct mbulk_pool mbulk_pools[MBULK_POOL_ID_MAX];
/**
* allocate a mbulk segment from the pool
*
* Note that the refcnt would be zero if \dat_bufsz is zero, as there is no
* allocated bulk data.
* If \dat_bufsz is \MBULK_DAT_BUFSZ_REQ_BEST_MAGIC, then this function
* allocates all remaining buffer space to the bulk buffer.
*
*/
static struct mbulk *mbulk_seg_generic_alloc(struct mbulk_pool *pool,
enum mbulk_class clas, size_t sig_bufsz, size_t dat_bufsz)
{
struct mbulk *m;
if (pool == NULL)
return NULL;
/* get a segment from the pool */
m = mbulk_pool_get(pool, clas);
if (m == NULL)
return NULL;
/* signal buffer */
m->sig_bufsz = (mbulk_len_t)sig_bufsz;
if (sig_bufsz)
m->flag = MBULK_F_SIG;
/* data buffer.
* Note that data buffer size can be larger than the requested.
*/
m->head = m->sig_bufsz;
if (dat_bufsz == 0) {
m->dat_bufsz = 0;
m->refcnt = 0;
} else if (dat_bufsz == MBULK_DAT_BUFSZ_REQ_BEST_MAGIC) {
m->dat_bufsz = pool->seg_size - m->sig_bufsz;
m->refcnt = 1;
} else {
m->dat_bufsz = (mbulk_len_t)dat_bufsz;
m->refcnt = 1;
}
mbulk_debug(m);
return m;
}
int mbulk_pool_get_free_count(u8 pool_id)
{
struct mbulk_pool *pool;
int num_free;
if (pool_id >= MBULK_POOL_ID_MAX) {
WARN_ON(pool_id >= MBULK_POOL_ID_MAX);
return -EIO;
}
spin_lock_bh(&mbulk_pool_lock);
pool = &mbulk_pools[pool_id];
if (!pool->valid) {
WARN_ON(!pool->valid);
spin_unlock_bh(&mbulk_pool_lock);
return -EIO;
}
num_free = pool->free_cnt;
spin_unlock_bh(&mbulk_pool_lock);
return num_free;
}
/**
* Allocate a bulk buffer with an in-lined signal buffer
*
* A mbulk segment is allocated from the given the pool, if its size
* meeting the requested size.
*
*/
struct mbulk *mbulk_with_signal_alloc_by_pool(u8 pool_id, mbulk_colour colour,
enum mbulk_class clas, size_t sig_bufsz_req, size_t dat_bufsz)
{
struct mbulk_pool *pool;
size_t sig_bufsz;
size_t tot_bufsz;
struct mbulk *m_ret;
u32 index;
/* data buffer should be aligned */
sig_bufsz = MBULK_SIG_BUFSZ_ROUNDUP(sizeof(struct mbulk) + sig_bufsz_req) - sizeof(struct mbulk);
if (pool_id >= MBULK_POOL_ID_MAX) {
WARN_ON(pool_id >= MBULK_POOL_ID_MAX);
return NULL;
}
pool = &mbulk_pools[pool_id];
if (!pool->valid) {
WARN_ON(!pool->valid);
return NULL;
}
/* check if this pool meets the size */
tot_bufsz = sig_bufsz + dat_bufsz;
if (dat_bufsz != MBULK_DAT_BUFSZ_REQ_BEST_MAGIC &&
pool->seg_size < tot_bufsz)
return NULL;
m_ret = mbulk_seg_generic_alloc(pool, clas, sig_bufsz, dat_bufsz);
index = (((uintptr_t)pool->end_addr - (uintptr_t)m_ret) >> pool->shift) - 1;
if (index >= pool->tot_seg_num)
return NULL;
pool->mbulk_tracker[index].colour = colour;
return m_ret;
}
mbulk_colour mbulk_get_colour(u8 pool_id, struct mbulk *m)
{
struct mbulk_pool *pool;
u16 index;
pool = &mbulk_pools[pool_id];
if (!pool->valid) {
WARN_ON(1);
return 0;
}
index = (((uintptr_t)pool->end_addr - (uintptr_t)m) >> pool->shift) - 1;
if (index >= pool->tot_seg_num)
return 0;
return pool->mbulk_tracker[index].colour;
}
#ifdef MBULK_SUPPORT_SG_CHAIN
/**
* allocate a chained mbulk buffer from a specific mbulk pool
*
*/
struct mbulk *mbulk_chain_with_signal_alloc_by_pool(u8 pool_id,
enum mbulk_class clas, size_t sig_bufsz, size_t dat_bufsz)
{
size_t tot_len;
struct mbulk *m, *head, *pre;
head = mbulk_with_signal_alloc_by_pool(pool_id, clas, sig_bufsz,
MBULK_DAT_BUFSZ_REQ_BEST_MAGIC);
if (head == NULL || MBULK_SEG_TAILROOM(head) >= dat_bufsz)
return head;
head->flag |= (MBULK_F_CHAIN_HEAD | MBULK_F_CHAIN);
tot_len = MBULK_SEG_TAILROOM(head);
pre = head;
while (tot_len < dat_bufsz) {
m = mbulk_with_signal_alloc_by_pool(pool_id, clas, 0,
MBULK_DAT_BUFSZ_REQ_BEST_MAGIC);
if (m == NULL)
break;
/* all mbulk in this chain has an attribue, MBULK_F_CHAIN */
m->flag |= MBULK_F_CHAIN;
tot_len += MBULK_SEG_TAILROOM(m);
pre->chain_next = m;
pre = m;
}
if (tot_len < dat_bufsz) {
mbulk_chain_free(head);
return NULL;
}
return head;
}
/**
* free a chained mbulk
*/
void mbulk_chain_free(struct mbulk *sg)
{
struct mbulk *chain_next, *m;
/* allow null pointer */
if (sg == NULL)
return;
m = sg;
while (m != NULL) {
chain_next = m->chain_next;
/* is not scatter-gather anymore */
m->flag &= ~(MBULK_F_CHAIN | MBULK_F_CHAIN_HEAD);
mbulk_seg_free(m);
m = chain_next;
}
}
/**
* get a tail mbulk in the chain
*
*/
struct mbulk *mbulk_chain_tail(struct mbulk *m)
{
while (m->chain_next != NULL)
m = m->chain_next;
return m;
}
/**
* total buffer size in a chanied mbulk
*
*/
size_t mbulk_chain_bufsz(struct mbulk *m)
{
size_t tbufsz = 0;
while (m != NULL) {
tbufsz += m->dat_bufsz;
m = m->chain_next;
}
return tbufsz;
}
/**
* total data length in a chanied mbulk
*
*/
size_t mbulk_chain_tlen(struct mbulk *m)
{
size_t tlen = 0;
while (m != NULL) {
tlen += m->len;
m = m->chain_next;
}
return tlen;
}
#endif /*MBULK_SUPPORT_SG_CHAIN*/
/**
* add a memory zone to a mbulk pool list
*
*/
#ifdef CONFIG_SCSC_WLAN_DEBUG
int mbulk_pool_add(u8 pool_id, char *base, char *end, size_t seg_size, u8 guard, int minor)
#else
int mbulk_pool_add(u8 pool_id, char *base, char *end, size_t seg_size, u8 guard)
#endif
{
struct mbulk_pool *pool;
struct mbulk *next;
size_t byte_per_block;
if (pool_id >= MBULK_POOL_ID_MAX) {
WARN_ON(pool_id >= MBULK_POOL_ID_MAX);
return -EIO;
}
pool = &mbulk_pools[pool_id];
if (!MBULK_IS_ALIGNED(base)) {
WARN_ON(!MBULK_IS_ALIGNED(base));
return -EIO;
}
/* total required memory per block */
byte_per_block = MBULK_SZ_ROUNDUP(sizeof(struct mbulk) + seg_size);
if (byte_per_block == 0)
return -EIO;
/* init pool structure */
memset(pool, 0, sizeof(*pool));
pool->pid = pool_id;
pool->base_addr = base;
pool->end_addr = end;
pool->seg_size = (mbulk_len_t)(byte_per_block - sizeof(struct mbulk));
pool->guard = guard;
pool->shift = ffs(byte_per_block) - 1;
/* allocate segments */
next = (struct mbulk *)base;
while (((uintptr_t)next + byte_per_block) <= (uintptr_t)end) {
memset(next, 0, sizeof(struct mbulk));
next->pid = pool_id;
/* add to the free list */
if (pool->free_list == NULL)
next->next_offset = 0;
else
next->next_offset = (uintptr_t)pool->free_list - (uintptr_t)next;
next->flag = MBULK_F_FREE;
pool->free_list = next;
pool->tot_seg_num++;
pool->free_cnt++;
next = (struct mbulk *)((uintptr_t)next + byte_per_block);
}
pool->valid = (pool->free_cnt) ? true : false;
#ifdef CONFIG_SCSC_WLAN_DEBUG
pool->minor = minor;
#endif
/* create a mbulk tracker object */
pool->mbulk_tracker = (struct mbulk_tracker *)vmalloc(pool->tot_seg_num * sizeof(struct mbulk_tracker));
if (pool->mbulk_tracker == NULL)
return -EIO;
return 0;
}
void mbulk_pool_remove(u8 pool_id)
{
struct mbulk_pool *pool;
if (pool_id >= MBULK_POOL_ID_MAX) {
WARN_ON(pool_id >= MBULK_POOL_ID_MAX);
return;
}
pool = &mbulk_pools[pool_id];
/* Destroy mbulk tracker */
vfree(pool->mbulk_tracker);
pool->mbulk_tracker = NULL;
}
/**
* add mbulk pools in MIF address space
*/
void mbulk_pool_dump(u8 pool_id, int max_cnt)
{
struct mbulk_pool *pool;
struct mbulk *m;
int cnt = max_cnt;
pool = &mbulk_pools[pool_id];
m = pool->free_list;
while (m != NULL && cnt--)
m = (m->next_offset == 0) ? NULL :
(struct mbulk *)(pool->base_addr + m->next_offset);
}
/**
* free a mbulk in the virtual host
*/
void mbulk_free_virt_host(struct mbulk *m)
{
u8 pool_id;
struct mbulk_pool *pool;
if (m == NULL)
return;
pool_id = m->pid & 0x1;
pool = &mbulk_pools[pool_id];
if (!pool->valid) {
WARN_ON(!pool->valid);
return;
}
/* put to the pool */
mbulk_pool_put(pool, m);
}