| /****************************************************************************** |
| * |
| * 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); |
| } |