| /**************************************************************************** |
| * |
| * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved |
| * |
| ****************************************************************************/ |
| |
| #include <linux/uaccess.h> |
| #include <linux/ctype.h> |
| #include <scsc/scsc_logring.h> |
| #include "mifproc.h" |
| #include "scsc_mif_abs.h" |
| #include "miframman.h" |
| |
| #define MX_MAX_PROC_RAMMAN 2 /* Number of RAMMANs to track */ |
| |
| static struct proc_dir_entry *procfs_dir; |
| static struct proc_dir_entry *procfs_dir_ramman[MX_MAX_PROC_RAMMAN]; |
| |
| /* WARNING --- SINGLETON FOR THE TIME BEING */ |
| /* EXTEND PROC ENTRIES IF NEEDED!!!!! */ |
| static struct scsc_mif_abs *mif_global; |
| |
| static int mifprocfs_open_file_generic(struct inode *inode, struct file *file) |
| { |
| file->private_data = MIF_PDE_DATA(inode); |
| return 0; |
| } |
| |
| #ifdef CONFIG_SCSC_PCIE |
| MIF_PROCFS_RW_FILE_OPS(mif_dump); |
| MIF_PROCFS_RW_FILE_OPS(mif_writemem); |
| MIF_PROCFS_RW_FILE_OPS(mif_reg); |
| #endif |
| |
| /* miframman ops */ |
| MIF_PROCFS_RO_FILE_OPS(ramman_total); |
| MIF_PROCFS_RO_FILE_OPS(ramman_offset); |
| MIF_PROCFS_RO_FILE_OPS(ramman_start); |
| MIF_PROCFS_RO_FILE_OPS(ramman_free); |
| MIF_PROCFS_RO_FILE_OPS(ramman_used); |
| MIF_PROCFS_RO_FILE_OPS(ramman_size); |
| |
| MIF_PROCFS_SEQ_FILE_OPS(ramman_list); |
| |
| #ifdef CONFIG_SCSC_PCIE |
| static ssize_t mifprocfs_mif_reg_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| |
| /* Avoid unused parameter error */ |
| (void)file; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| |
| static ssize_t mifprocfs_mif_writemem_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| |
| /* Avoid unused parameter error */ |
| (void)file; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| static ssize_t mifprocfs_mif_dump_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| |
| /* Avoid unused parameter error */ |
| (void)file; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK"); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| static ssize_t mifprocfs_mif_writemem_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| char *sptr, *token; |
| unsigned int len = 0, pass = 0; |
| u32 value = 0, address = 0; |
| int match = 0; |
| void *mem; |
| |
| /* Avoid unused parameter error */ |
| (void)file; |
| (void)ppos; |
| |
| |
| len = min(count, sizeof(buf) - 1); |
| if (copy_from_user(buf, user_buf, len)) |
| return -EFAULT; |
| |
| buf[len] = '\0'; |
| sptr = buf; |
| |
| while ((token = strsep(&sptr, " ")) != NULL) { |
| switch (pass) { |
| /* register */ |
| case 0: |
| if ((token[0] == '0') && (token[1] == 'x')) { |
| if (kstrtou32(token, 16, &address)) { |
| SCSC_TAG_INFO(MIF, "Wrong format: <address> <value (hex)>\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); |
| goto error; |
| } |
| } else { |
| SCSC_TAG_INFO(MIF, "Wrong format: <address> <value (hex)>\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); |
| goto error; |
| } |
| break; |
| /* value */ |
| case 1: |
| if ((token[0] == '0') && (token[1] == 'x')) { |
| if (kstrtou32(token, 16, &value)) { |
| SCSC_TAG_INFO(MIF, "Wrong format: <address> <value (hex)>\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); |
| goto error; |
| } |
| } else { |
| SCSC_TAG_INFO(MIF, "Wrong format: <address> <value (hex)>\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); |
| goto error; |
| } |
| break; |
| } |
| pass++; |
| } |
| if (pass != 2 && !match) { |
| SCSC_TAG_INFO(MIF, "Wrong format: <address> <value (hex)>\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 0xcafecafe\"\n"); |
| goto error; |
| } |
| |
| /* Get memory offset */ |
| mem = mif_global->get_mifram_ptr(mif_global, 0); |
| if (!mem) { |
| SCSC_TAG_INFO(MIF, "Mem not allocated\n"); |
| goto error; |
| } |
| |
| SCSC_TAG_INFO(MIF, "Setting value 0x%x at address 0x%x offset\n", value, address); |
| |
| |
| *((u32 *)(mem + address)) = value; |
| error: |
| return count; |
| } |
| |
| static ssize_t mifprocfs_mif_dump_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| char *sptr, *token; |
| unsigned int len = 0, pass = 0; |
| u32 address = 0; |
| u32 size; |
| u8 unit; |
| void *mem; |
| |
| (void)file; |
| (void)ppos; |
| |
| len = min(count, sizeof(buf) - 1); |
| if (copy_from_user(buf, user_buf, len)) |
| return -EFAULT; |
| |
| buf[len] = '\0'; |
| sptr = buf; |
| |
| while ((token = strsep(&sptr, " ")) != NULL) { |
| switch (pass) { |
| /* address */ |
| case 0: |
| if ((token[0] == '0') && (token[1] == 'x')) { |
| if (kstrtou32(token, 16, &address)) { |
| SCSC_TAG_INFO(MIF, "Incorrect format,,,address should start by 0x\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 256 8\"\n"); |
| goto error; |
| } |
| SCSC_TAG_INFO(MIF, "address %d 0x%x\n", address, address); |
| } else { |
| SCSC_TAG_INFO(MIF, "Incorrect format,,,address should start by 0x\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 256 8\"\n"); |
| goto error; |
| } |
| break; |
| /* size */ |
| case 1: |
| if (kstrtou32(token, 0, &size)) { |
| SCSC_TAG_INFO(MIF, "Incorrect format,,, for size\n"); |
| goto error; |
| } |
| SCSC_TAG_INFO(MIF, "size: %d\n", size); |
| break; |
| |
| /* unit */ |
| case 2: |
| if (kstrtou8(token, 0, &unit)) { |
| SCSC_TAG_INFO(MIF, "Incorrect format,,, for unit\n"); |
| goto error; |
| } |
| if ((unit != 8) && (unit != 16) && (unit != 32)) { |
| SCSC_TAG_INFO(MIF, "Unit %d should be 8/16/32\n", unit); |
| goto error; |
| } |
| SCSC_TAG_INFO(MIF, "unit: %d\n", unit); |
| break; |
| } |
| pass++; |
| } |
| if (pass != 3) { |
| SCSC_TAG_INFO(MIF, "Wrong format: <start_address> <size> <unit>\n"); |
| SCSC_TAG_INFO(MIF, "Example: \"0xaaaabbbb 256 8\"\n"); |
| goto error; |
| } |
| |
| mem = mif_global->get_mifram_ptr(mif_global, 0); |
| if (!mem) { |
| SCSC_TAG_INFO(MIF, "Mem not allocated\n"); |
| goto error; |
| } |
| |
| /* Add offset */ |
| mem = mem + address; |
| |
| SCSC_TAG_INFO(MIF, "Phy addr :%p ref addr :%x\n", mem, address); |
| SCSC_TAG_INFO(MIF, "------------------------------------------------------------------------\n"); |
| print_hex_dump(KERN_WARNING, "ref addr offset: ", DUMP_PREFIX_OFFSET, 16, unit/8, mem, size, 1); |
| SCSC_TAG_INFO(MIF, "------------------------------------------------------------------------\n"); |
| error: |
| return count; |
| } |
| |
| static ssize_t mifprocfs_mif_reg_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| void *mem; |
| |
| if (!mif_global) { |
| SCSC_TAG_INFO(MIF, "Endpoint not registered\n"); |
| return 0; |
| } |
| |
| mem = mif_global->get_mifram_ptr(mif_global, 0); |
| |
| SCSC_TAG_INFO(MIF, "Phy addr :%p\n", mem); |
| |
| mif_global->mif_dump_registers(mif_global); |
| |
| return count; |
| } |
| #endif |
| /* |
| * TODO: Add here any debug message should be exported |
| static int mifprocfs_mif_dbg_show(struct seq_file *m, void *v) |
| { |
| (void)v; |
| |
| if (!mif_global) { |
| seq_puts(m, "endpoint not registered"); |
| return 0; |
| } |
| return 0; |
| } |
| */ |
| |
| /* Total space in the memory region containing this ramman (assumes one region per ramman) */ |
| static ssize_t mifprocfs_ramman_total_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| struct miframman *ramman = (struct miframman *)file->private_data; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", (ptrdiff_t)(ramman->start_dram - ramman->start_region) + ramman->size_pool); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| /* Offset of the pool within the region (the space before is reserved by FW) */ |
| static ssize_t mifprocfs_ramman_offset_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| struct miframman *ramman = (struct miframman *)file->private_data; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", (ptrdiff_t)(ramman->start_dram - ramman->start_region)); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| /* Start address of the pool within the region */ |
| static ssize_t mifprocfs_ramman_start_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| struct miframman *ramman = (struct miframman *)file->private_data; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%p\n", ramman->start_dram); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| /* Size of the pool */ |
| static ssize_t mifprocfs_ramman_size_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| struct miframman *ramman = (struct miframman *)file->private_data; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", ramman->size_pool); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| /* Space remaining within the pool */ |
| static ssize_t mifprocfs_ramman_free_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| struct miframman *ramman = (struct miframman *)file->private_data; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%u\n", ramman->free_mem); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| /* Bytes used within the pool */ |
| static ssize_t mifprocfs_ramman_used_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| char buf[128]; |
| int pos = 0; |
| const size_t bufsz = sizeof(buf); |
| struct miframman *ramman = (struct miframman *)file->private_data; |
| |
| pos += scnprintf(buf + pos, bufsz - pos, "%zd\n", ramman->size_pool - (size_t)ramman->free_mem); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, pos); |
| } |
| |
| /* List allocations per ramman */ |
| static int mifprocfs_ramman_list_show(struct seq_file *m, void *v) |
| { |
| struct miframman *ramman = (struct miframman *)m->private; |
| (void)v; |
| |
| miframman_log(ramman, m); |
| |
| return 0; |
| } |
| |
| static const char *procdir = "driver/mif_ctrl"; |
| static int refcount; |
| |
| #define MIF_DIRLEN 128 |
| |
| static struct proc_dir_entry *create_procfs_dir(void) |
| { |
| char dir[MIF_DIRLEN]; |
| |
| if (refcount++ == 0) { |
| (void)snprintf(dir, sizeof(dir), "%s", procdir); |
| procfs_dir = proc_mkdir(dir, NULL); |
| } |
| return procfs_dir; |
| } |
| |
| static void destroy_procfs_dir(void) |
| { |
| char dir[MIF_DIRLEN]; |
| |
| if (--refcount == 0) { |
| (void)snprintf(dir, sizeof(dir), "%s", procdir); |
| remove_proc_entry(dir, NULL); |
| procfs_dir = NULL; |
| } |
| WARN_ON(refcount < 0); |
| } |
| |
| |
| int mifproc_create_proc_dir(struct scsc_mif_abs *mif) |
| { |
| struct proc_dir_entry *parent; |
| |
| /* WARNING --- SINGLETON FOR THE TIME BEING */ |
| /* EXTEND PROC ENTRIES IF NEEDED!!!!! */ |
| if (mif_global) |
| return -EBUSY; |
| |
| /* Ref the root dir */ |
| parent = create_procfs_dir(); |
| if (parent) { |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) |
| parent->data = NULL; |
| #endif |
| |
| #ifdef CONFIG_SCSC_PCIE |
| MIF_PROCFS_ADD_FILE(NULL, mif_writemem, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(NULL, mif_dump, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(NULL, mif_reg, parent, S_IRUSR | S_IRGRP); |
| #endif |
| } else { |
| SCSC_TAG_INFO(MIF, "failed to create /proc dir\n"); |
| return -EINVAL; |
| } |
| |
| mif_global = mif; |
| |
| return 0; |
| /* |
| err: |
| return -EINVAL; |
| */ |
| } |
| |
| void mifproc_remove_proc_dir(void) |
| { |
| if (procfs_dir) { |
| MIF_PROCFS_REMOVE_FILE(mif_writemem, procfs_dir); |
| MIF_PROCFS_REMOVE_FILE(mif_dump, procfs_dir); |
| MIF_PROCFS_REMOVE_FILE(mif_reg, procfs_dir); |
| |
| /* De-ref the root dir */ |
| destroy_procfs_dir(); |
| } |
| |
| mif_global = NULL; |
| } |
| |
| /* /proc/driver/mif_ctrl/rammanX */ |
| static const char *ramman_procdir = "ramman"; |
| static struct miframman *proc_miframman[MX_MAX_PROC_RAMMAN]; |
| static int ramman_instance; |
| |
| int mifproc_create_ramman_proc_dir(struct miframman *ramman) |
| { |
| char dir[MIF_DIRLEN]; |
| struct proc_dir_entry *parent; |
| struct proc_dir_entry *root; |
| |
| if ((ramman_instance > ARRAY_SIZE(proc_miframman) - 1)) |
| return -EINVAL; |
| |
| /* Ref the root dir /proc/driver/mif_ctrl */ |
| root = create_procfs_dir(); |
| if (!root) |
| return -EINVAL; |
| |
| (void)snprintf(dir, sizeof(dir), "%s%d", ramman_procdir, ramman_instance); |
| parent = proc_mkdir(dir, root); |
| if (parent) { |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0)) |
| parent->data = NULL; |
| #endif |
| MIF_PROCFS_ADD_FILE(ramman, ramman_total, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(ramman, ramman_offset, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(ramman, ramman_start, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(ramman, ramman_size, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(ramman, ramman_free, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| MIF_PROCFS_ADD_FILE(ramman, ramman_used, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); |
| |
| MIF_PROCFS_SEQ_ADD_FILE(ramman, ramman_list, parent, S_IRUSR | S_IRGRP | S_IROTH); |
| |
| procfs_dir_ramman[ramman_instance] = parent; |
| proc_miframman[ramman_instance] = ramman; |
| |
| ramman_instance++; |
| |
| } else { |
| SCSC_TAG_INFO(MIF, "failed to create /proc dir\n"); |
| destroy_procfs_dir(); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| void mifproc_remove_ramman_proc_dir(struct miframman *ramman) |
| { |
| (void)ramman; |
| |
| if (ramman_instance <= 0) { |
| WARN_ON(ramman_instance < 0); |
| return; |
| } |
| |
| --ramman_instance; |
| |
| if (procfs_dir_ramman[ramman_instance]) { |
| char dir[MIF_DIRLEN]; |
| |
| MIF_PROCFS_REMOVE_FILE(ramman_total, procfs_dir_ramman[ramman_instance]); |
| MIF_PROCFS_REMOVE_FILE(ramman_offset, procfs_dir_ramman[ramman_instance]); |
| MIF_PROCFS_REMOVE_FILE(ramman_start, procfs_dir_ramman[ramman_instance]); |
| MIF_PROCFS_REMOVE_FILE(ramman_size, procfs_dir_ramman[ramman_instance]); |
| MIF_PROCFS_REMOVE_FILE(ramman_free, procfs_dir_ramman[ramman_instance]); |
| MIF_PROCFS_REMOVE_FILE(ramman_used, procfs_dir_ramman[ramman_instance]); |
| |
| MIF_PROCFS_REMOVE_FILE(ramman_list, procfs_dir_ramman[ramman_instance]); |
| |
| (void)snprintf(dir, sizeof(dir), "%s%d", ramman_procdir, ramman_instance); |
| remove_proc_entry(dir, procfs_dir); |
| procfs_dir_ramman[ramman_instance] = NULL; |
| proc_miframman[ramman_instance] = NULL; |
| } |
| |
| /* De-ref the root dir */ |
| destroy_procfs_dir(); |
| } |