blob: ec336746b1e1edf9754d3f8ea9d36f03ccb12f88 [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.
:>
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h> /* everything... */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/aio.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/vmalloc.h>
#include <bcm_dev_log_task.h>
#include <bcmolt_dev_log_kernel.h>
#include <bcmolt_dev_log_ioctl.h>
int dev_log_chrdev_major = 215; /* Should comply the value in targets/makeDev */
module_param(dev_log_chrdev_major, int, S_IRUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(dev_log_chrdev_major, "devlog_major");
int dev_log_buffer_size_kb = 2048; /* Buffer size in kB */
module_param(dev_log_buffer_size_kb, int, S_IRUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(dev_log_buffer_size_kb, "devlog_bufsize_kb");
int dev_log_queue_size = 256; /* Queue size */
module_param(dev_log_queue_size, int, S_IRUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(dev_log_queue_size, "devlog_qsize");
static bcmos_mutex dev_log_lock;
static int dev_log_num_users;
static char *dev_log_buffer;
/*
* Open and close
*/
static int dev_log_chrdev_open(struct inode *inode, struct file *filp)
{
BCMOS_TRACE_DEBUG("\n");
bcmos_mutex_lock(&dev_log_lock);
if (dev_log_num_users >= 1)
{
bcmos_mutex_unlock(&dev_log_lock);
return -EBUSY;
}
++dev_log_num_users;
bcmos_mutex_unlock(&dev_log_lock);
return 0;
}
static int dev_log_chrdev_release(struct inode *inode, struct file *filp)
{
BCMOS_TRACE_DEBUG("\n");
--dev_log_num_users;
return 0;
}
static ssize_t dev_log_chrdev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
return -EOPNOTSUPP;
}
static long dev_log_chrdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int linux_rc = 0;
bcmos_errno rc;
dev_log_io_param io_param;
dev_log_id id;
/* don't even decode wrong cmds: better returning ENOTTY than EFAULT */
if (_IOC_TYPE(cmd) != DEV_LOG_CHRDEV_IOC_MAGIC)
return -ENOTTY;
/*
* the type is a bitmask, and VERIFY_WRITE catches R/W
* transfers. Note that the type is user-oriented, while
* verify_area is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
linux_rc = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
linux_rc = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (linux_rc)
return -EFAULT;
switch (cmd)
{
case DEV_LOG_CHRDEV_DB_READ:
{
int i;
for (i = 0; i < bcm_dev_log_get_num_of_entries(); i++)
{
dev_log_id_parm id_parm = {};
id = bcm_dev_log_id_get_by_index(i);
if (id == DEV_LOG_INVALID_ID)
{
break;
}
bcm_dev_log_id_get(id, &id_parm);
io_param.db_read.ids[i].index = i;
strncpy(io_param.db_read.ids[i].name, id_parm.name, sizeof(io_param.db_read.ids[i].name));
io_param.db_read.ids[i].default_type = id_parm.default_log_type;
io_param.db_read.ids[i].default_level = id_parm.default_log_level;
/* Reset to defaults to keep in sync with the user space */
bcm_dev_log_id_set_levels_and_type_to_default(id);
}
io_param.db_read.num_ids = i;
linux_rc = copy_to_user((char *)arg, (char *)&io_param.db_read, sizeof(io_param.db_read));
}
break;
case DEV_LOG_CHRDEV_LEVEL_SET:
{
linux_rc = copy_from_user((char *)&io_param.level_set, (char *)arg, sizeof(io_param.level_set));
if (linux_rc < 0)
break;
id = bcm_dev_log_id_get_by_index(io_param.level_set.index);
if (id == DEV_LOG_INVALID_ID)
{
BCMOS_TRACE_ERR("log_id index %u is invalid\n", io_param.level_set.index);
}
rc = bcm_dev_log_id_set_level(id, io_param.level_set.level_print, io_param.level_set.level_save);
if (rc)
{
BCMOS_TRACE_ERR("bcm_dev_log_id_set_level(%ld,%d,%d)->%s\n",
(long)id, (int)io_param.level_set.level_print, (int)io_param.level_set.level_save, bcmos_strerror(rc));
linux_rc = -EINVAL;
}
}
break;
case DEV_LOG_CHRDEV_TYPE_SET:
{
linux_rc = copy_from_user((char *)&io_param.type_set, (char *)arg, sizeof(io_param.type_set));
if (linux_rc < 0)
break;
/* Map index to log id */
id = bcm_dev_log_id_get_by_index(io_param.type_set.index);
if (id == DEV_LOG_INVALID_ID)
{
BCMOS_TRACE_ERR("log_id index %u is invalid\n", io_param.type_set.index);
}
rc = bcm_dev_log_id_set_type(id, io_param.type_set.type);
if (rc)
{
BCMOS_TRACE_ERR("bcm_dev_log_id_set_type(%ld,%d)->%s\n",
(long)id, (int)io_param.type_set.type, bcmos_strerror(rc));
linux_rc = -EINVAL;
}
}
break;
case DEV_LOG_CHRDEV_MSG_READ:
{
uint32_t buf_len = sizeof(io_param.msg_read.msg);
bcm_dev_log_file *log_file = bcm_dev_log_file_get(0);
if (!log_file)
{
linux_rc = -EINVAL;
break;
}
linux_rc = copy_from_user((char *)&io_param.msg_read, (char *)arg, sizeof(io_param.msg_read.offset));
if (linux_rc < 0)
break;
rc = bcm_dev_log_file_read(log_file, &io_param.msg_read.offset, io_param.msg_read.msg, buf_len);
if (rc <= 0)
{
if (rc == 0)
linux_rc = -EAGAIN;
else
linux_rc = -EINVAL;
break;
}
linux_rc = copy_to_user((char *)arg, (char *)&io_param.msg_read,
offsetof(dev_log_io_param, msg_read.msg) + rc);
}
break;
default: /* redundant, as cmd was checked against MAXNR */
rc = -ENOTTY;
break;
}
return linux_rc;
}
/*
* The fops
*/
static struct file_operations dev_log_chrdev_fops =
{
.owner = THIS_MODULE,
.open = dev_log_chrdev_open,
.release = dev_log_chrdev_release,
.write = dev_log_chrdev_write,
.unlocked_ioctl = dev_log_chrdev_ioctl
};
static struct cdev dev_log_chrdev_cdev;
static int is_chrdev_reg, is_cdev_add;
static bcmos_errno dev_log_chrdev_init(void)
{
dev_t dev = MKDEV(dev_log_chrdev_major, 0);
int linux_rc;
is_chrdev_reg = 0;
is_cdev_add = 0;
/*
* Register your major, and accept a dynamic number.
*/
if (!dev_log_chrdev_major)
return -1;
linux_rc = register_chrdev_region(dev, 0, "dev_log");
if (linux_rc < 0)
{
BCMOS_TRACE_RETURN(BCM_ERR_IO, "register_chrdev_region()->%d\n", linux_rc);
}
is_chrdev_reg = 1;
cdev_init(&dev_log_chrdev_cdev, &dev_log_chrdev_fops);
linux_rc = cdev_add(&dev_log_chrdev_cdev, dev, 1);
if (linux_rc < 0)
{
BCMOS_TRACE_RETURN(BCM_ERR_IO, "cdev_add()->%d\n", linux_rc);
}
is_cdev_add = 1;
return BCM_ERR_OK;
}
static void dev_log_chrdev_exit(void)
{
if (is_cdev_add)
cdev_del(&dev_log_chrdev_cdev);
if (is_chrdev_reg)
unregister_chrdev_region(MKDEV(dev_log_chrdev_major, 0), 1);
}
static int dev_log_time_to_str_cb(uint32_t time_stamp, char *time_str, int time_str_size)
{
return snprintf(time_str, time_str_size, "%u", time_stamp / 1000); /* Convert from usec to msec. */
}
/** Initialize dev_log linux kernel support */
bcmos_errno bcm_dev_log_linux_init(void)
{
void *addresses[1];
uint32_t sizes[1] = { dev_log_buffer_size_kb * 1024 };
uint32_t flags[1] = { BCM_DEV_LOG_FILE_FLAG_WRAP_AROUND };
bcmos_errno rc;
if (!dev_log_queue_size || !dev_log_buffer_size_kb)
return BCM_ERR_PARM;
rc = dev_log_chrdev_init();
if (rc)
return rc;
/* Initialize logger */
dev_log_buffer = vmalloc(dev_log_buffer_size_kb * 1024);
if (!dev_log_buffer)
{
dev_log_chrdev_exit();
BCMOS_TRACE_ERR("Can't allocate dev_log buffer (%d bytes)\n", dev_log_buffer_size_kb * 1024);
return BCM_ERR_NOMEM;
}
addresses[0] = dev_log_buffer;
rc = bcm_dev_log_init_default_logger(addresses, sizes, flags, BCM_SIZEOFARRAY(addresses), 0x4000, TASK_PRIORITY_DEV_LOG, dev_log_queue_size);
if (rc)
{
dev_log_chrdev_exit();
vfree(dev_log_buffer);
BCMOS_TRACE_ERR("Failed to create logger. Error %s\n", bcmos_strerror(rc));
return rc;
}
bcm_dev_log_set_time_to_str_cb(dev_log_time_to_str_cb);
bcmos_mutex_create(&dev_log_lock, 0, NULL);
return BCM_ERR_OK;
}
/** Clean-up dev_log linux kernel support */
void bcm_dev_log_linux_exit(void)
{
dev_log_chrdev_exit();
if (dev_log_buffer)
{
vfree(dev_log_buffer);
}
bcmos_mutex_destroy(&dev_log_lock);
}