blob: 0b4a0232a51d7c23ec884cbc0cca55d413884e86 [file] [log] [blame]
/*
<: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);