| /* |
| <:copyright-BRCM:2016:DUAL/GPL:standard |
| |
| Broadcom Proprietary and Confidential.(c) 2016 Broadcom |
| All Rights Reserved |
| |
| Unless you and Broadcom execute a separate written software license |
| agreement governing use of this software, this software is licensed |
| to you under the terms of the GNU General Public License version 2 |
| (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php, |
| with the following added to such license: |
| |
| As a special exception, the copyright holders of this software give |
| you permission to link this software with independent modules, and |
| to copy and distribute the resulting executable under terms of your |
| choice, provided that you also meet, for each linked independent |
| module, the terms and conditions of the license of that module. |
| An independent module is a module which is not derived from this |
| software. The special exception does not apply to any modifications |
| of the software. |
| |
| Not withstanding the above, under no circumstances may you combine |
| this software in any way with any other Broadcom software provided |
| under a license other than the GPL, without Broadcom's express prior |
| written consent. |
| |
| :> |
| */ |
| |
| /* |
| * bcmos_system.c |
| * |
| * This file implements a subset of OS Abstraction services |
| * for linux kernel |
| */ |
| |
| #include <bcmos_system.h> |
| |
| #include <linux/delay.h> |
| #include <linux/pci.h> |
| #include <asm-generic/pci-dma-compat.h> |
| #include <linux/fs.h> |
| #include <linux/fdtable.h> |
| |
| #include <bcmolt_llpcie.h> |
| |
| /* task control blocks */ |
| extern STAILQ_HEAD(task_list, bcmos_task) task_list; |
| |
| /* global OS lock */ |
| extern bcmos_mutex bcmos_res_lock; |
| |
| /* Initialize system library */ |
| bcmos_errno bcmos_sys_init(void) |
| { |
| return BCM_ERR_OK; |
| } |
| /* Clean-up system library */ |
| void bcmos_sys_exit(void) |
| { |
| } |
| |
| /* |
| * Thread handler |
| */ |
| static int bcmos_task_handler(void *data) |
| { |
| bcmos_task *task = (bcmos_task *)data; |
| F_bcmos_task_handler handler; |
| long handler_data; |
| |
| task->sys_task.t = current; |
| |
| if (task->parm.handler) |
| { |
| /* "traditional task */ |
| handler = task->parm.handler; |
| handler_data = task->parm.data; |
| } |
| else |
| { |
| /* "integrated" task */ |
| handler = bcmos_dft_task_handler; |
| handler_data = (long)task; |
| } |
| handler(handler_data); |
| |
| return 0; |
| } |
| |
| void _bcmos_backtrace(void) |
| { |
| /*todo implement this*/ |
| } |
| |
| /* Create a new task. |
| * Attention! Priority is ignored. |
| */ |
| bcmos_errno bcmos_task_create(bcmos_task *task, const bcmos_task_parm *parm) |
| { |
| struct task_struct *t; |
| bcmos_errno rc; |
| |
| if (!task || !parm) |
| return BCM_ERR_PARM; |
| |
| memset(task, 0, sizeof(*task)); |
| task->parm = *parm; |
| if (!parm->handler) |
| { |
| /* Initialize and lock mutex to wait on */ |
| rc = bcmos_sem_create(&task->active_sem, 0, task->parm.flags, parm->name); |
| if (rc) |
| { |
| BCMOS_TRACE_ERR("Task %s: can't create active_sem. Error %s (%d)\n", |
| task->parm.name, bcmos_strerror(rc), rc); |
| return rc; |
| } |
| } |
| /* Copy name to make sure that it is not released - in case it was on the stack */ |
| if (task->parm.name) |
| { |
| strncpy(task->name, task->parm.name, sizeof(task->name) - 1); |
| task->parm.name = task->name; |
| } |
| bcmos_fastlock_init(&task->active_lock, 0); |
| task->magic = BCMOS_TASK_MAGIC; |
| bcmos_mutex_lock(&bcmos_res_lock); |
| STAILQ_INSERT_TAIL(&task_list, task, list); |
| bcmos_mutex_unlock(&bcmos_res_lock); |
| |
| t = kthread_run(bcmos_task_handler, task, parm->name); |
| if (t == ERR_PTR(-ENOMEM)) |
| { |
| bcmos_mutex_lock(&bcmos_res_lock); |
| STAILQ_REMOVE(&task_list, task, bcmos_task, list); |
| bcmos_mutex_unlock(&bcmos_res_lock); |
| task->magic = 0; |
| return BCM_ERR_NOMEM; |
| } |
| |
| return BCM_ERR_OK; |
| } |
| |
| /* Destroy task */ |
| bcmos_errno bcmos_task_destroy(bcmos_task *task) |
| { |
| if (task->magic != BCMOS_TASK_MAGIC) |
| { |
| return BCM_ERR_PARM; |
| } |
| if (!task->sys_task.t) |
| { |
| return BCM_ERR_NOENT; |
| } |
| task->destroy_request = BCMOS_TRUE; |
| task->magic = BCMOS_TASK_MAGIC_DESTROYED; |
| bcmos_mutex_lock(&bcmos_res_lock); |
| STAILQ_REMOVE(&task_list, task, bcmos_task, list); |
| bcmos_mutex_unlock(&bcmos_res_lock); |
| /* The task may be waiting on semaphore. Kick it */ |
| if (!task->parm.handler) |
| { |
| bcmos_sem_post(&task->active_sem); |
| } |
| |
| /* Sometimes by the time we get here the task has already been disposed of. |
| * TODO: investigate why */ |
| if (task->sys_task.t->cred != NULL) |
| { |
| kthread_stop(task->sys_task.t); |
| } |
| return BCM_ERR_OK; |
| } |
| |
| /** Get current task |
| * \returns task handle or NULL if not in task context |
| */ |
| bcmos_task *bcmos_task_current(void) |
| { |
| struct task_struct *kt = current; |
| bcmos_task *t, *tmp; |
| |
| STAILQ_FOREACH_SAFE(t, &task_list, list, tmp) |
| { |
| if (t->sys_task.t == kt) |
| break; |
| } |
| return t; |
| } |
| |
| /* timer signal handler */ |
| static void sys_timer_handler(unsigned long data) |
| { |
| bcmos_sys_timer *timer = (bcmos_sys_timer *)data; |
| timer->handler(timer->data); |
| } |
| |
| /* Create timer */ |
| bcmos_errno bcmos_sys_timer_create(bcmos_sys_timer *timer, bcmos_sys_timer_handler handler, void *data) |
| { |
| if (!timer || !handler) |
| BCMOS_TRACE_RETURN(BCM_ERR_PARM, "timer %p, handler %p\n", timer, handler); |
| |
| timer->handler = handler; |
| timer->data = data; |
| init_timer(&timer->t); |
| timer->t.function = sys_timer_handler; |
| timer->t.data = (unsigned long)timer; |
| |
| return BCM_ERR_OK; |
| } |
| |
| /* Destroy timer */ |
| void bcmos_sys_timer_destroy(bcmos_sys_timer *timer) |
| { |
| del_timer_sync(&timer->t); |
| } |
| |
| /* (Re)start timer */ |
| void bcmos_sys_timer_start(bcmos_sys_timer *timer, uint32_t delay) |
| { |
| /* Convert delay us --> ticks */ |
| uint32_t ticks = usecs_to_jiffies(delay); |
| mod_timer(&timer->t, jiffies + ticks); |
| } |
| |
| /* Stop timer if running */ |
| void bcmos_sys_timer_stop(bcmos_sys_timer *timer) |
| { |
| /* No need to do anything. System timer isn't running */ |
| } |
| |
| void bcmos_usleep(uint32_t u) |
| { |
| if (u >= 10000) |
| msleep((u + 999) / 1000); |
| else |
| udelay(u); |
| } |
| |
| /* |
| * Semaphore |
| */ |
| |
| /* Decrement semaphore counter. Wait if the counter is 0. |
| */ |
| bcmos_errno bcmos_sem_wait(bcmos_sem *sem, uint32_t timeout) |
| { |
| if (timeout) |
| { |
| if (timeout != BCMOS_WAIT_FOREVER) |
| { |
| if (down_timeout(&sem->s, usecs_to_jiffies(timeout))) |
| return BCM_ERR_TIMEOUT; |
| } |
| else |
| { |
| if (down_interruptible(&sem->s)) |
| return BCM_ERR_INTERNAL; |
| } |
| return BCM_ERR_OK; |
| } |
| |
| /* 0 timeout */ |
| if (down_trylock(&sem->s)) |
| { |
| return BCM_ERR_NOENT; |
| } |
| return BCM_ERR_OK; |
| } |
| |
| |
| /* |
| * Byte memory pool |
| */ |
| |
| /* Memory block header */ |
| typedef struct bcmos_byte_memblk |
| { |
| bcmos_byte_pool *pool; /** pool that owns the block */ |
| uint32_t size; /** block size (bytes) including bcmos_byte_memblk header */ |
| #ifdef BCMOS_MEM_DEBUG |
| uint32_t magic; /** magic number */ |
| #define BCMOS_MEM_MAGIC_ALLOC (('m'<<24) | ('b' << 16) | ('l' << 8) | 'k') |
| #define BCMOS_MEM_MAGIC_FREE (('m'<<24) | ('b' << 16) | ('l' << 8) | '~') |
| #endif |
| } bcmos_byte_memblk; |
| |
| /* Create byte memory pool */ |
| bcmos_errno bcmos_byte_pool_create(bcmos_byte_pool *pool, const bcmos_byte_pool_parm *parm) |
| { |
| if (!pool || !parm) |
| { |
| BCMOS_TRACE_RETURN(BCM_ERR_PARM, "pool %p, parm %p\n", pool, parm); |
| } |
| |
| BCM_MEMZERO_STRUCT(pool); |
| pool->parm = *parm; |
| if (!pool->parm.size) |
| { |
| BCMOS_TRACE_RETURN(BCM_ERR_PARM, "size %u\n", parm->size); |
| } |
| #ifdef BCMOS_MEM_DEBUG |
| pool->magic = BCMOS_BYTE_POOL_VALID; |
| #endif |
| return BCM_ERR_OK; |
| } |
| |
| /* Destroy memory pool */ |
| bcmos_errno bcmos_byte_pool_destroy(bcmos_byte_pool *pool) |
| { |
| if (pool->allocated) |
| { |
| BCMOS_TRACE_RETURN(BCM_ERR_STATE, "%u bytes of memory are still allocated from the pool %s\n", |
| pool->allocated, pool->parm.name); |
| } |
| #ifdef BCMOS_MEM_DEBUG |
| pool->magic = BCMOS_BYTE_POOL_DELETED; |
| #endif |
| return BCM_ERR_OK; |
| } |
| |
| /* Allocate memory from memory pool */ |
| void *bcmos_byte_pool_alloc(bcmos_byte_pool *pool, uint32_t size) |
| { |
| bcmos_byte_memblk *blk; |
| uint32_t byte_size; |
| void *ptr; |
| |
| #ifdef BCMOS_MEM_DEBUG |
| BUG_ON(pool->magic != BCMOS_BYTE_POOL_VALID); |
| #endif |
| |
| if (size + pool->allocated > pool->parm.size) |
| return NULL; |
| |
| byte_size = size + sizeof(bcmos_byte_memblk); |
| #ifdef BCMOS_MEM_DEBUG |
| byte_size += sizeof(uint32_t); /* block suffix */ |
| #endif |
| /* ToDo: Maintain LL of allocated blocks */ |
| blk = (bcmos_byte_memblk *)bcmos_alloc(byte_size); |
| if (!blk) |
| return NULL; |
| ptr = (void *)(blk + 1); |
| blk->size = byte_size; |
| pool->allocated += byte_size; |
| blk->pool = pool; |
| #ifdef BCMOS_MEM_DEBUG |
| blk->magic = BCMOS_MEM_MAGIC_ALLOC; |
| *(uint32_t *)((long)blk + byte_size - sizeof(uint32_t)) = BCMOS_MEM_MAGIC_ALLOC; |
| #endif |
| |
| return ptr; |
| } |
| |
| /* Release memory allocated using bcmos_byte_pool_alloc() */ |
| void bcmos_byte_pool_free(void *ptr) |
| { |
| bcmos_byte_memblk *blk; |
| bcmos_byte_pool *pool; |
| |
| BUG_ON(!ptr); |
| blk = (bcmos_byte_memblk *)((long)ptr - sizeof(bcmos_byte_memblk)); |
| pool = blk->pool; |
| #ifdef BCMOS_MEM_DEBUG |
| BUG_ON(pool->magic != BCMOS_BYTE_POOL_VALID); |
| BUG_ON(blk->magic != BCMOS_MEM_MAGIC_ALLOC); |
| BUG_ON(*(uint32_t *)((long)blk + blk->size - sizeof(uint32_t)) != BCMOS_MEM_MAGIC_ALLOC); |
| blk->magic = BCMOS_MEM_MAGIC_FREE; |
| #endif |
| pool->allocated -= blk->size; |
| bcmos_free(blk); |
| } |
| |
| /* |
| * DMA-able memory management |
| */ |
| |
| /* Dma-able block header */ |
| typedef struct |
| { |
| struct pci_dev *pdev; |
| dma_addr_t dma_handle; |
| uint32_t size; |
| } bcmos_dma_mem_hdr; |
| |
| /* Allocate DMA-able memory */ |
| void *bcmos_dma_alloc(uint8_t device, uint32_t size) |
| { |
| bcmos_dma_mem_hdr hdr = |
| { |
| .pdev = (struct pci_dev *)bcm_ll_pcie_dev_get(device), |
| .size = size |
| }; |
| void *ptr; |
| |
| if (!hdr.pdev) |
| { |
| return NULL; |
| } |
| |
| ptr = pci_alloc_consistent(hdr.pdev, size + sizeof(bcmos_dma_mem_hdr), &hdr.dma_handle); |
| if (ptr) |
| { |
| memcpy(ptr, &hdr, sizeof(hdr)); |
| } |
| return (void *)((long)ptr + sizeof(hdr)); |
| } |
| |
| /* Release DMA-able memory */ |
| void bcmos_dma_free(uint8_t device, void *ptr) |
| { |
| bcmos_dma_mem_hdr hdr; |
| |
| /* Step back to prefix area */ |
| ptr = (void *)((long)ptr - sizeof(hdr)); |
| memcpy(&hdr, ptr, sizeof(hdr)); |
| |
| /* Sanity check */ |
| if (!hdr.pdev || hdr.pdev != bcm_ll_pcie_dev_get(device)) |
| { |
| BCMOS_TRACE_ERR("!!!Attempt to release insane DMA-able pointer. ptr=%p pdev=%p/%p\n", |
| ptr, hdr.pdev, bcm_ll_pcie_dev_get(device)); |
| return; |
| } |
| pci_free_consistent(hdr.pdev, hdr.size, ptr, hdr.dma_handle); |
| } |
| |
| /* |
| * Print to the current process' stdout |
| */ |
| |
| #ifndef STDOUT_FILENO |
| #define STDOUT_FILENO 1 |
| #endif |
| |
| static int _bcmos_write(struct file *fd, const unsigned char *buf, int len) |
| { |
| mm_segment_t oldfs; |
| int len0 = len; |
| oldfs = get_fs(); |
| set_fs(KERNEL_DS); |
| while(len) |
| { |
| int lw; |
| lw = vfs_write(fd, (char *)buf, len, &fd->f_pos); |
| if (lw < 0) |
| break; |
| len -= lw; |
| if (len) |
| { |
| if (msleep_interruptible(1)) |
| break; |
| buf += lw; |
| } |
| } |
| set_fs(oldfs); |
| return (len0 - len); |
| } |
| |
| int bcmos_sys_vprintf(const char *format, va_list args) |
| { |
| struct file *f; |
| int rc=0; |
| |
| /* Get stdout file handle if any */ |
| if (in_interrupt() || !current || !current->files) |
| f = 0; |
| else |
| f = fcheck(STDOUT_FILENO); |
| if (!f) |
| { |
| rc = vprintk(format, args); |
| } |
| else |
| { |
| char printbuf[1024]; |
| char *p1=printbuf, *p2; |
| vscnprintf(printbuf, sizeof(printbuf)-1, format, args); |
| printbuf[sizeof(printbuf)-1]=0; |
| do |
| { |
| p2 = strchr(p1, '\n'); |
| if (p2) |
| { |
| rc += _bcmos_write(f, (unsigned char*) p1, p2-p1+1); |
| rc += _bcmos_write(f, (unsigned char*)"\r", 1); |
| } |
| else |
| { |
| rc += _bcmos_write(f, (unsigned char*)p1, strlen(p1)); |
| } |
| p1 = p2 + 1; |
| } while(p2); |
| } |
| return rc; |
| } |
| |
| static int os_linux_module_init(void) |
| { |
| bcmos_errno rc; |
| |
| rc = bcmos_init(); |
| |
| return rc ? -EINVAL : 0; |
| } |
| |
| static void os_linux_module_exit(void) |
| { |
| bcmos_exit(); |
| } |
| |
| module_init(os_linux_module_init); |
| module_exit(os_linux_module_exit); |
| |
| MODULE_DESCRIPTION("OS Abstraction"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| |
| EXPORT_SYMBOL(bcmos_task_create); |
| EXPORT_SYMBOL(bcmos_task_destroy); |
| EXPORT_SYMBOL(bcmos_task_current); |
| EXPORT_SYMBOL(bcmos_sem_wait); |
| EXPORT_SYMBOL(bcmos_sys_trace_level); |
| EXPORT_SYMBOL(bcmos_usleep); |
| EXPORT_SYMBOL(bcmos_byte_pool_create); |
| EXPORT_SYMBOL(bcmos_byte_pool_destroy); |
| EXPORT_SYMBOL(bcmos_byte_pool_alloc); |
| EXPORT_SYMBOL(bcmos_byte_pool_free); |
| EXPORT_SYMBOL(bcmos_sys_vprintf); |
| EXPORT_SYMBOL(bcmos_dma_alloc); |
| EXPORT_SYMBOL(bcmos_dma_free); |
| EXPORT_SYMBOL(_bcmos_backtrace); |
| EXPORT_SYMBOL(sw_error_handler); |
| EXPORT_SYMBOL(bcmos_exit); |