#include #include #include #include #include #include #include #include #include #include #include /* ----------------------------------------------------------------- */ #if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0) static void *PDE_DATA(const struct inode *inode) { return PROC_I(inode)->pde->data; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ #ifndef CONFIG_MACH_BCM963138 static int single_open_size(struct file *file, int (*show)(struct seq_file *, void *), void *data, size_t size) { char *buf = kmalloc(size, GFP_KERNEL); int ret; if (!buf) return -ENOMEM; ret = single_open(file, show, data); if (ret) { kfree(buf); return ret; } ((struct seq_file *)file->private_data)->buf = buf; ((struct seq_file *)file->private_data)->size = size; return 0; } #endif #endif /* ----------------------------------------------------------------- */ LIST_HEAD(simple_proc_file_list); struct simple_proc_file_internal{ struct list_head list; struct file_operations fops; char *path; int (*kernel_input) (char*, void *); void (*kernel_output) (struct seq_file*, void *); void *priv_data; size_t expected_output_buffer_size; }; static int simple_proc_file_show(struct seq_file *m, void *offset __maybe_unused) { struct simple_proc_file_internal *spf = m->private; if (spf->kernel_output){ sp_debug_print( "offset: %#p\n", offset); spf->kernel_output(m, spf->priv_data); } return 0; } static int simple_proc_file_open(struct inode *inode, struct file *file) { unsigned int res; struct simple_proc_file_internal *spf = PDE_DATA(inode); sp_debug_print("call open with simple_proc_file_entry %p: %s, priv=%p\n", spf, spf->path, spf->priv_data); if (spf->expected_output_buffer_size){ res = single_open_size(file, simple_proc_file_show, spf, spf->expected_output_buffer_size); } else { res = single_open(file, simple_proc_file_show, spf); } return res; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static ssize_t simple_proc_file_write(struct file *file, const char *buffer, size_t count, loff_t *offset __maybe_unused) { unsigned char *procfs_buffer; ssize_t res; size_t procfs_buffer_size; struct simple_proc_file_internal *spf; #if LINUX_VERSION_CODE < KERNEL_VERSION(3,9,0) spf = PDE_DATA(file->f_path.dentry->d_inode) ; #else spf = PDE_DATA(file->f_inode) ; #endif if ( spf->kernel_input == NULL ) { return -EFAULT; } /* * get buffer size: * we need one extra byte in our buffer: the terminating 0-byte * count variable contains user data len, this data is not 0-terminated */ if (count >= SP_MAX_INPUT_BUF ) { procfs_buffer_size = SP_MAX_INPUT_BUF; } else { procfs_buffer_size = count + 1; } /* alloc buffer */ if(procfs_buffer_size > PAGE_SIZE) { procfs_buffer = vmalloc(procfs_buffer_size); } else { procfs_buffer = kmalloc(procfs_buffer_size, GFP_KERNEL); } if (!procfs_buffer){ return -ENOMEM; } /* write data to the buffer */ if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size - 1) ) { res = -EFAULT; goto fini; } procfs_buffer[procfs_buffer_size - 1] = 0; res = spf->kernel_input( procfs_buffer, spf->priv_data ); if (res >= 0 ) { res = procfs_buffer_size - 1; } fini: if(is_vmalloc_addr(procfs_buffer)) { vfree(procfs_buffer); } else { kfree(procfs_buffer); } return res; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int add_simple_proc_file( const char* path, int (*kernel_input)(char *, void *), void (*kernel_output)(struct seq_file *, void *), void *priv_data) { struct simple_proc_file_internal *simple_proc_file_entry = NULL; unsigned int path_len; /* path check */ path_len = strlen(path) + 1; if (path_len > SP_MAX_PATH_LEN ){ return -EINVAL; } /* alloc private data structure */ simple_proc_file_entry = kzalloc( sizeof(*simple_proc_file_entry) + path_len, GFP_KERNEL ); if (!simple_proc_file_entry){ return -ENOMEM; } simple_proc_file_entry->path = (char *)( simple_proc_file_entry + 1); /* setup fops */ simple_proc_file_entry->fops.open = simple_proc_file_open; simple_proc_file_entry->fops.read = seq_read; simple_proc_file_entry->fops.llseek = seq_lseek; simple_proc_file_entry->fops.release = single_release; if ( kernel_input ){ simple_proc_file_entry->fops.write = simple_proc_file_write; } /*setup more */ simple_proc_file_entry->priv_data = priv_data; simple_proc_file_entry->kernel_input = kernel_input; simple_proc_file_entry->kernel_output = kernel_output; /* copy path */ memcpy(simple_proc_file_entry->path, path, path_len); /* create proc file */ if (! proc_create_data( path, 0, NULL, &simple_proc_file_entry->fops, simple_proc_file_entry ) ) { kfree( simple_proc_file_entry ); pr_err("%s: proc_create_data failed \n", __func__); return -EFAULT; } list_add(&simple_proc_file_entry->list, &simple_proc_file_list); sp_debug_print("register simple_proc_file_entry %p\n", simple_proc_file_entry); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void remove_simple_proc_file(const char* path){ struct simple_proc_file_internal *spf; list_for_each_entry(spf, &simple_proc_file_list, list) { if (strncmp(spf->path, path, SP_MAX_PATH_LEN) == 0) { list_del( &spf->list ); // kfree(spf) is done in remove_proc_entry remove_proc_entry( path, NULL ); sp_debug_print("remove entry %s\n", path); return ; } } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ int add_simple_proc_file_array( struct simple_proc_file *array ){ int i; int res = 0; for(i = 0; array[i].path != NULL; i++){ if (array[i].enabled){ int tres = add_simple_proc_file( array[i].path, array[i].kernel_input, array[i].kernel_output, array[i].priv_data ); if ( tres ) res = tres; } } return res; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void remove_simple_proc_file_array( struct simple_proc_file *array ){ int i; for(i = 0; array[i].path != NULL; i++){ if (array[i].enabled) remove_simple_proc_file(array[i].path); } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void simple_proc_file_setup_expected_output_size( const char* path, size_t expected_output_size ){ struct simple_proc_file_internal *spf; list_for_each_entry(spf, &simple_proc_file_list, list) { if (strncmp(spf->path, path, SP_MAX_PATH_LEN) == 0) { spf->expected_output_buffer_size = expected_output_size; } } } /* ----------------------------------------------------------------- */ EXPORT_SYMBOL(add_simple_proc_file); EXPORT_SYMBOL(remove_simple_proc_file); EXPORT_SYMBOL(simple_proc_file_setup_expected_output_size); EXPORT_SYMBOL(add_simple_proc_file_array); EXPORT_SYMBOL(remove_simple_proc_file_array); /* ----------------------------------------------------------------- */ MODULE_LICENSE("GPL");