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
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), ¶1, ¶2) > 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);
|
|
|