You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
13 KiB

/*
* Copyright (c) Hisilicon Technologies Co., Ltd. 2006-2020. All rights reserved.
* Description: proc drv
* Author: Hisilicon
* Create: 2006-8-2
*/
#include "drv_proc_ext.h"
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/ctype.h>
#include <linux/file.h>
#include <uapi/linux/major.h>
#include <linux/version.h>
#include "soc_log.h"
#include "drv_module_ext.h"
#include "drv_ioctl_userproc.h"
#include "drv_mem_ext.h"
#include "linux/huanglong/securec.h"
#define PROC_MAX_ENTRIES 256
#define PROC_DEFAULT_ECHO_DEVICE_HANDLE 0
struct proc_dir_entry *g_proc_cmpi = TD_NULL;
struct proc_dir_entry *g_proc_soc = TD_NULL;
static ext_proc_item g_proc_items[PROC_MAX_ENTRIES] = {{{0}}};
static DEFINE_MUTEX(g_proc_mutex_lock);
/* for some ttyXXX device major number */
#define PROC_SERIAL_MAJOR_NUM 204
static ext_proc_module_fn *g_proc_intf_para = TD_NULL;
struct proc_dir_entry *drv_proc_get_cmpi(td_void)
{
if (g_proc_cmpi == TD_NULL) {
soc_print("g_proc_cmpi is null!\n");
}
return g_proc_cmpi;
}
struct proc_dir_entry *drv_proc_get_soc(td_void)
{
if (g_proc_soc == TD_NULL) {
soc_print("g_proc_soc is null!\n");
}
return g_proc_soc;
}
td_s32 ext_drv_proc_register_para(ext_proc_module_fn *para)
{
if (para == TD_NULL) {
return TD_FAILURE;
}
if ((para->add_module_fn == TD_NULL) || (para->remove_module_fn == TD_NULL)) {
return TD_FAILURE;
}
g_proc_intf_para = para;
return TD_SUCCESS;
}
td_void ext_drv_proc_unregister_para(td_void)
{
g_proc_intf_para = TD_NULL;
return;
}
ext_proc_item *ext_drv_proc_add_module(const td_char *entry_name, ext_proc_fn_set *fn_set, td_void *data)
{
if (g_proc_intf_para != TD_NULL) {
if (g_proc_intf_para->add_module_fn) {
return g_proc_intf_para->add_module_fn(entry_name, fn_set, data);
}
}
return TD_NULL;
}
td_void ext_drv_proc_remove_module(const td_char *entry_name)
{
if (g_proc_intf_para != TD_NULL) {
if (g_proc_intf_para->remove_module_fn) {
g_proc_intf_para->remove_module_fn(entry_name);
}
}
return;
}
td_void drv_proc_echo_helper(const td_char *buf)
{
struct kstat stat = {0};
td_s32 ret;
if (buf == TD_NULL) {
soc_print("Invalid argument buf or size!\n");
return;
}
ret = vfs_fstat(PROC_DEFAULT_ECHO_DEVICE_HANDLE, &stat);
if (ret) {
soc_print("Default echo device handle(%u) invalid!\n", PROC_DEFAULT_ECHO_DEVICE_HANDLE);
return;
}
/*
* echo device must be chrdev and major number must be PROC_SERIAL_MAJOR_NUM or
* TTYAUX_MAJOR or UNIX98_PTY_SLAVE_MAJOR
*/
if (S_ISCHR(stat.mode) && (MAJOR(stat.rdev) == PROC_SERIAL_MAJOR_NUM || MAJOR(stat.rdev) == TTYAUX_MAJOR ||
MAJOR(stat.rdev) == UNIX98_PTY_SLAVE_MAJOR)) {
struct file *file = fget(PROC_DEFAULT_ECHO_DEVICE_HANDLE);
if (file != TD_NULL) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
mm_segment_t old_fs = get_fs();
#endif
loff_t pos = 0; /* file pos is invalid for chrdev */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
set_fs(KERNEL_DS);
#endif
ret = vfs_write(file, buf, strlen(buf), &pos);
if (ret < 0) {
soc_print("write to echo device failed(%d)!\n", ret);
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
set_fs(old_fs);
#endif
fput(file);
}
} else {
soc_print("Default echo device is invalid!\n");
}
}
/*
* echo string to current terminal display(serial console or tty).
* this implement implicit that current task file handle '0' must be terminal device file.
* otherwise do nothing.
*/
td_void ext_drv_proc_echo_helper_vargs(td_char *buf, td_u32 size, const td_char *fmt, va_list args)
{
td_s32 ret;
if ((buf == TD_NULL) || (size == 0) || (fmt == TD_NULL)) {
soc_print("Invalid argument buf or size or fmt!\n");
return;
}
ret = vsnprintf_s(buf, size, size - 1, fmt, args);
if (ret < 0) {
soc_print("vsnprintf_s error!\n");
return;
}
drv_proc_echo_helper(buf);
}
EXPORT_SYMBOL(ext_drv_proc_echo_helper_vargs);
/* general echo helper function */
td_void ext_drv_proc_echo_helper(const td_char *fmt, ...)
{
va_list args;
td_char *buf = TD_NULL;
if (fmt == TD_NULL) {
soc_print("Invalid argument fmt!\n");
return;
}
buf = SOC_KZALLOC(SOC_ID_PROC, EXT_USER_PROC_BUF_SIZE, GFP_KERNEL);
if (buf == TD_NULL) {
soc_print("Memory allocate failed for proc\n");
return;
}
va_start(args, fmt);
ext_drv_proc_echo_helper_vargs(buf, EXT_USER_PROC_BUF_SIZE, fmt, args);
va_end(args);
SOC_KFREE(SOC_ID_PROC, buf);
buf = TD_NULL;
}
EXPORT_SYMBOL(ext_drv_proc_echo_helper);
static td_s32 drv_proc_cmpi_open(struct inode *inode, struct file *file)
{
ext_proc_item *item = PDE_DATA(inode);
if (item != TD_NULL && item->read) {
return single_open(file, item->read, item);
}
return -ENOSYS;
}
static ssize_t drv_proc_cmpi_write(struct file *file, const td_char __user *buf, size_t count, loff_t *pos)
{
struct seq_file *s = file->private_data;
ext_proc_item *item = s->private;
if (item->write) {
return item->write(file, buf, count, pos);
}
return -ENOSYS;
}
static td_slong drv_proc_cmpi_unlocked_ioctl(struct file *file, td_u32 cmd, td_ulong arg)
{
struct seq_file *s = file->private_data;
ext_proc_item *item = s->private;
if (item->ioctl) {
return item->ioctl(s, cmd, arg);
}
return 0;
}
#ifdef CONFIG_COMPAT
static td_slong drv_proc_cmpi_compat_ioctl(struct file *file, td_u32 cmd, td_ulong arg)
{
return -ENOSYS;
}
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
static struct proc_ops g_proc_cmpi_ops = {
.proc_open = drv_proc_cmpi_open,
.proc_read = seq_read,
.proc_write = drv_proc_cmpi_write,
.proc_lseek = seq_lseek,
.proc_release = single_release,
.proc_ioctl = drv_proc_cmpi_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.proc_compat_ioctl = drv_proc_cmpi_compat_ioctl,
#endif
};
#else
static struct file_operations g_proc_cmpi_ops __attribute__((unused)) = {
.owner = THIS_MODULE,
.open = drv_proc_cmpi_open,
.read = seq_read,
.write = drv_proc_cmpi_write,
.llseek = seq_lseek,
.release = single_release,
.unlocked_ioctl = drv_proc_cmpi_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = drv_proc_cmpi_compat_ioctl,
#endif
};
#endif
static td_bool drv_proc_is_hex(const td_char *p)
{
td_s32 i, len;
len = (td_s32)strlen(p);
if (len <= 0x2) {
return TD_FALSE;
}
p += 0x2;
for (i = 0; i < len - 0x2; i++) {
if ((p[i] < '0') || p[i] > 'f') {
return TD_FALSE;
}
if ((p[i] > '9') && p[i] < 'a') {
return TD_FALSE;
}
}
return TD_TRUE;
}
static td_s32 drv_proc_str_to_digit(const unsigned char *str, td_u32 *digit)
{
td_s32 i, len;
td_u32 val = 0;
len = (td_s32)strlen(str);
if (len <= 0x2) {
return TD_FAILURE;
}
str += 0x2;
for (i = 0; i < len - 0x2; i++) {
val <<= 0x4;
if (str[i] <= '9') {
val += str[i] - '0';
} else {
val += str[i] - 'a' + 0x0a;
}
}
*digit = val;
return TD_SUCCESS;
}
static td_s32 drv_proc_cmpi_parse_cmd_data_deal(char *ptr, td_u32 size)
{
td_s32 i;
while (*ptr == ' ' && *ptr++ != '\0') {
}
/* covert into lowercase string */
for (i = 0; i < strlen(ptr); i++) {
if (i >= size) {
return -1;
}
ptr[i] = (char)tolower(ptr[i]);
}
for (i = (td_s32)strlen(ptr); i > 0; i--) {
if ((*(ptr + i - 1) < '0') || (*(ptr + i - 1) > 'f')) {
*(ptr + i - 1) = '\0';
} else if ((*(ptr + i - 1) > '9') && (*(ptr + i - 1) < 'a')) {
*(ptr + i - 1) = '\0';
} else {
break;
}
}
return TD_SUCCESS;
}
static td_s32 drv_proc_get_args(const td_char *ptr, td_char (*the_args)[0x40])
{
td_s32 i;
for (i = 0; i < 0x2; i++) {
td_s32 j = 0;
while (*ptr == ' ' && *ptr++ != '\0');
while ((*ptr != ' ') && (*ptr != '\0') && (j < 64)) { /* max row 64 */
the_args[i][j++] = *ptr++;
}
if (j < 64) { /* max row 64 */
the_args[i][j] = '\0';
} else {
the_args[i][64 - 1] = '\0'; /* max row 64 */
}
if (*ptr == '\0') {
i++;
break;
}
}
return i;
}
td_s32 drv_proc_cmpi_parse_cmd(char *ptr, td_u32 size, td_u32 *para1, td_u32 *para2)
{
td_s32 i;
td_char the_args[2][64]; /* row 64, col 2 */
if (ptr == TD_NULL) {
soc_log_err("ptr is null!\n");
return -1;
}
if (drv_proc_cmpi_parse_cmd_data_deal(ptr, size) != TD_SUCCESS) {
soc_log_err("drv_proc_cmpi_parse_cmd_data_deal failed!\n");
return -1;
}
i = drv_proc_get_args(ptr, the_args);
if ((the_args[0][0] != '0') || (the_args[0][1] != 'x') ||
(the_args[1][0] != '0') || (the_args[1][1] != 'x')) {
return -1;
}
if ((!drv_proc_is_hex(the_args[0])) || (!drv_proc_is_hex(the_args[1]))) {
return -1;
}
if (drv_proc_str_to_digit(the_args[0], para1) != TD_SUCCESS) {
return -1;
}
if (drv_proc_str_to_digit(the_args[1], para2) != TD_SUCCESS) {
return -1;
}
return i;
}
static td_void drv_proc_write_show_help(td_void)
{
ext_drv_proc_echo_helper("\nPLS type \"echo 0xxxxxxxxx 0xxxxxxxxx > /proc/msp/demux\"\n");
ext_drv_proc_echo_helper("E.g.: \"echo 0x40002 0x4010 > /proc/msp/demux\"\n");
}
ssize_t ext_drv_proc_module_write(struct file *file, const char __user *buf, size_t count,
loff_t *pos, ext_proc_ctrl_fn ctrl_fn)
{
char tmp_buf[64] = {0}; /* array num is 64 */
char *tmp_buf_ptr = tmp_buf;
td_u32 para1, para2;
if (ctrl_fn == TD_NULL || pos == TD_NULL) {
return -EFAULT;
}
if ((buf == TD_NULL) || (count >= sizeof(tmp_buf))) {
drv_proc_write_show_help();
goto out;
}
if (copy_from_user(tmp_buf_ptr, buf, count)) {
return -EFAULT;
}
tmp_buf[count > 1 ? count - 1 : 0] = '\0';
if (drv_proc_cmpi_parse_cmd(tmp_buf, sizeof(tmp_buf), &para1, &para2) > 0) {
ctrl_fn(para1, para2);
} else {
drv_proc_write_show_help();
}
out:
*pos = count;
return count;
}
static ext_proc_item *drv_proc_add_module(const td_char *entry_name, ext_proc_fn_set *fn_set, td_void *data)
{
struct proc_dir_entry *entry = TD_NULL;
td_s32 i;
td_s32 ret;
if ((entry_name == TD_NULL) || (strlen(entry_name) > EXT_PROC_MAX_ENTRY_NAME_LEN)) {
return TD_NULL;
}
mutex_lock(&g_proc_mutex_lock);
for (i = 0; i < PROC_MAX_ENTRIES; i++) {
if (!g_proc_items[i].entry) {
break;
}
}
if (i == PROC_MAX_ENTRIES) {
mutex_unlock(&g_proc_mutex_lock);
soc_print("ERROR: add proc entry %s over LIMIT:%#x\n", entry_name, PROC_MAX_ENTRIES);
return TD_NULL;
}
ret = strncpy_s(g_proc_items[i].entry_name, sizeof(g_proc_items[i].entry_name),
entry_name, sizeof(g_proc_items[i].entry_name) - 1);
if (ret != EOK) {
mutex_unlock(&g_proc_mutex_lock);
soc_print("strncpy_s error\n");
return TD_NULL;
}
if (fn_set != TD_NULL) {
g_proc_items[i].read = fn_set->read;
g_proc_items[i].write = fn_set->write;
g_proc_items[i].ioctl = fn_set->ioctl;
} else {
g_proc_items[i].read = TD_NULL;
g_proc_items[i].write = TD_NULL;
g_proc_items[i].ioctl = TD_NULL;
}
g_proc_items[i].data = data;
entry = proc_create_data(entry_name, 0, g_proc_cmpi, &g_proc_cmpi_ops, &g_proc_items[i]);
if (entry == TD_NULL) {
mutex_unlock(&g_proc_mutex_lock);
return TD_NULL;
}
g_proc_items[i].entry = entry;
mutex_unlock(&g_proc_mutex_lock);
return &g_proc_items[i];
}
static td_void drv_proc_remove_module(const td_char *entry_name)
{
td_s32 i;
mutex_lock(&g_proc_mutex_lock);
for (i = 0; i < PROC_MAX_ENTRIES; i++) {
if (!strncmp(g_proc_items[i].entry_name, entry_name, sizeof(g_proc_items[i].entry_name))) {
break;
}
}
if (i == PROC_MAX_ENTRIES) {
mutex_unlock(&g_proc_mutex_lock);
soc_print("Not find the entry:%s\n", entry_name);
return;
}
remove_proc_entry(g_proc_items[i].entry_name, g_proc_cmpi);
g_proc_items[i].entry = TD_NULL;
mutex_unlock(&g_proc_mutex_lock);
}
static ext_proc_module_fn g_proc_para = {
.add_module_fn = drv_proc_add_module,
.remove_module_fn = drv_proc_remove_module,
};
td_s32 drv_proc_init(td_void)
{
if (ext_drv_proc_register_para(&g_proc_para) != TD_SUCCESS) {
return TD_FAILURE;
}
return TD_SUCCESS;
}
td_void drv_proc_exit(td_void)
{
ext_drv_proc_unregister_para();
return;
}
EXPORT_SYMBOL(ext_drv_proc_module_write);
EXPORT_SYMBOL(ext_drv_proc_register_para);
EXPORT_SYMBOL(ext_drv_proc_unregister_para);
EXPORT_SYMBOL(ext_drv_proc_add_module);
EXPORT_SYMBOL(ext_drv_proc_remove_module);