/* * 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 #include #include #include #include #include #include #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);