--- zzzz-none-000/linux-3.10.107/fs/debugfs/file.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/fs/debugfs/file.c 2021-02-04 17:41:59.000000000 +0000 @@ -17,10 +17,12 @@ #include #include #include -#include #include #include #include +#include +#include +#include static ssize_t default_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) @@ -41,16 +43,21 @@ .llseek = noop_llseek, }; -static void *debugfs_follow_link(struct dentry *dentry, struct nameidata *nd) +static struct dentry *debugfs_create_mode(const char *name, umode_t mode, + struct dentry *parent, void *value, + const struct file_operations *fops, + const struct file_operations *fops_ro, + const struct file_operations *fops_wo) { - nd_set_link(nd, dentry->d_inode->i_private); - return NULL; -} + /* if there are no write bits set, make read only */ + if (!(mode & S_IWUGO)) + return debugfs_create_file(name, mode, parent, value, fops_ro); + /* if there are no read bits set, make write only */ + if (!(mode & S_IRUGO)) + return debugfs_create_file(name, mode, parent, value, fops_wo); -const struct inode_operations debugfs_link_operations = { - .readlink = generic_readlink, - .follow_link = debugfs_follow_link, -}; + return debugfs_create_file(name, mode, parent, value, fops); +} static int debugfs_u8_set(void *data, u64 val) { @@ -93,14 +100,8 @@ struct dentry *debugfs_create_u8(const char *name, umode_t mode, struct dentry *parent, u8 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u8_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u8_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_u8); + return debugfs_create_mode(name, mode, parent, value, &fops_u8, + &fops_u8_ro, &fops_u8_wo); } EXPORT_SYMBOL_GPL(debugfs_create_u8); @@ -145,14 +146,8 @@ struct dentry *debugfs_create_u16(const char *name, umode_t mode, struct dentry *parent, u16 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u16_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u16_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_u16); + return debugfs_create_mode(name, mode, parent, value, &fops_u16, + &fops_u16_ro, &fops_u16_wo); } EXPORT_SYMBOL_GPL(debugfs_create_u16); @@ -197,14 +192,8 @@ struct dentry *debugfs_create_u32(const char *name, umode_t mode, struct dentry *parent, u32 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u32_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u32_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_u32); + return debugfs_create_mode(name, mode, parent, value, &fops_u32, + &fops_u32_ro, &fops_u32_wo); } EXPORT_SYMBOL_GPL(debugfs_create_u32); @@ -250,17 +239,59 @@ struct dentry *debugfs_create_u64(const char *name, umode_t mode, struct dentry *parent, u64 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u64_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_u64_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_u64); + return debugfs_create_mode(name, mode, parent, value, &fops_u64, + &fops_u64_ro, &fops_u64_wo); } EXPORT_SYMBOL_GPL(debugfs_create_u64); +static int debugfs_ulong_set(void *data, u64 val) +{ + *(unsigned long *)data = val; + return 0; +} + +static int debugfs_ulong_get(void *data, u64 *val) +{ + *val = *(unsigned long *)data; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(fops_ulong, debugfs_ulong_get, debugfs_ulong_set, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_ulong_ro, debugfs_ulong_get, NULL, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_ulong_wo, NULL, debugfs_ulong_set, "%llu\n"); + +/** + * debugfs_create_ulong - create a debugfs file that is used to read and write + * an unsigned long value. + * @name: a pointer to a string containing the name of the file to create. + * @mode: the permission that the file should have + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * file will be created in the root of the debugfs filesystem. + * @value: a pointer to the variable that the file should read to and write + * from. + * + * This function creates a file in debugfs with the given name that + * contains the value of the variable @value. If the @mode variable is so + * set, it can be read from, and written to. + * + * This function will return a pointer to a dentry if it succeeds. This + * pointer must be passed to the debugfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here.) If an error occurs, %NULL will be returned. + * + * If debugfs is not enabled in the kernel, the value -%ENODEV will be + * returned. It is not wise to check for this value, but rather, check for + * %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling + * code. + */ +struct dentry *debugfs_create_ulong(const char *name, umode_t mode, + struct dentry *parent, unsigned long *value) +{ + return debugfs_create_mode(name, mode, parent, value, &fops_ulong, + &fops_ulong_ro, &fops_ulong_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_ulong); + DEFINE_SIMPLE_ATTRIBUTE(fops_x8, debugfs_u8_get, debugfs_u8_set, "0x%02llx\n"); DEFINE_SIMPLE_ATTRIBUTE(fops_x8_ro, debugfs_u8_get, NULL, "0x%02llx\n"); DEFINE_SIMPLE_ATTRIBUTE(fops_x8_wo, NULL, debugfs_u8_set, "0x%02llx\n"); @@ -274,6 +305,8 @@ DEFINE_SIMPLE_ATTRIBUTE(fops_x32_wo, NULL, debugfs_u32_set, "0x%08llx\n"); DEFINE_SIMPLE_ATTRIBUTE(fops_x64, debugfs_u64_get, debugfs_u64_set, "0x%016llx\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_x64_ro, debugfs_u64_get, NULL, "0x%016llx\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_x64_wo, NULL, debugfs_u64_set, "0x%016llx\n"); /* * debugfs_create_x{8,16,32,64} - create a debugfs file that is used to read and write an unsigned {8,16,32,64}-bit value @@ -296,14 +329,8 @@ struct dentry *debugfs_create_x8(const char *name, umode_t mode, struct dentry *parent, u8 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_x8_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_x8_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_x8); + return debugfs_create_mode(name, mode, parent, value, &fops_x8, + &fops_x8_ro, &fops_x8_wo); } EXPORT_SYMBOL_GPL(debugfs_create_x8); @@ -320,14 +347,8 @@ struct dentry *debugfs_create_x16(const char *name, umode_t mode, struct dentry *parent, u16 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_x16_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_x16_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_x16); + return debugfs_create_mode(name, mode, parent, value, &fops_x16, + &fops_x16_ro, &fops_x16_wo); } EXPORT_SYMBOL_GPL(debugfs_create_x16); @@ -344,14 +365,8 @@ struct dentry *debugfs_create_x32(const char *name, umode_t mode, struct dentry *parent, u32 *value) { - /* if there are no write bits set, make read only */ - if (!(mode & S_IWUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_x32_ro); - /* if there are no read bits set, make write only */ - if (!(mode & S_IRUGO)) - return debugfs_create_file(name, mode, parent, value, &fops_x32_wo); - - return debugfs_create_file(name, mode, parent, value, &fops_x32); + return debugfs_create_mode(name, mode, parent, value, &fops_x32, + &fops_x32_ro, &fops_x32_wo); } EXPORT_SYMBOL_GPL(debugfs_create_x32); @@ -368,7 +383,8 @@ struct dentry *debugfs_create_x64(const char *name, umode_t mode, struct dentry *parent, u64 *value) { - return debugfs_create_file(name, mode, parent, value, &fops_x64); + return debugfs_create_mode(name, mode, parent, value, &fops_x64, + &fops_x64_ro, &fops_x64_wo); } EXPORT_SYMBOL_GPL(debugfs_create_x64); @@ -385,6 +401,8 @@ } DEFINE_SIMPLE_ATTRIBUTE(fops_size_t, debugfs_size_t_get, debugfs_size_t_set, "%llu\n"); /* %llu and %zu are more or less the same */ +DEFINE_SIMPLE_ATTRIBUTE(fops_size_t_ro, debugfs_size_t_get, NULL, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_size_t_wo, NULL, debugfs_size_t_set, "%llu\n"); /** * debugfs_create_size_t - create a debugfs file that is used to read and write an size_t value @@ -399,17 +417,51 @@ struct dentry *debugfs_create_size_t(const char *name, umode_t mode, struct dentry *parent, size_t *value) { - return debugfs_create_file(name, mode, parent, value, &fops_size_t); + return debugfs_create_mode(name, mode, parent, value, &fops_size_t, + &fops_size_t_ro, &fops_size_t_wo); } EXPORT_SYMBOL_GPL(debugfs_create_size_t); +static int debugfs_atomic_t_set(void *data, u64 val) +{ + atomic_set((atomic_t *)data, val); + return 0; +} +static int debugfs_atomic_t_get(void *data, u64 *val) +{ + *val = atomic_read((atomic_t *)data); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t, debugfs_atomic_t_get, + debugfs_atomic_t_set, "%lld\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_ro, debugfs_atomic_t_get, NULL, "%lld\n"); +DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_wo, NULL, debugfs_atomic_t_set, "%lld\n"); + +/** + * debugfs_create_atomic_t - create a debugfs file that is used to read and + * write an atomic_t value + * @name: a pointer to a string containing the name of the file to create. + * @mode: the permission that the file should have + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * file will be created in the root of the debugfs filesystem. + * @value: a pointer to the variable that the file should read to and write + * from. + */ +struct dentry *debugfs_create_atomic_t(const char *name, umode_t mode, + struct dentry *parent, atomic_t *value) +{ + return debugfs_create_mode(name, mode, parent, value, &fops_atomic_t, + &fops_atomic_t_ro, &fops_atomic_t_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_atomic_t); -static ssize_t read_file_bool(struct file *file, char __user *user_buf, - size_t count, loff_t *ppos) +ssize_t debugfs_read_file_bool(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) { char buf[3]; - u32 *val = file->private_data; - + bool *val = file->private_data; + if (*val) buf[0] = 'Y'; else @@ -418,28 +470,43 @@ buf[2] = 0x00; return simple_read_from_buffer(user_buf, count, ppos, buf, 2); } +EXPORT_SYMBOL_GPL(debugfs_read_file_bool); -static ssize_t write_file_bool(struct file *file, const char __user *user_buf, - size_t count, loff_t *ppos) +ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) { char buf[32]; size_t buf_size; bool bv; - u32 *val = file->private_data; + bool *val = file->private_data; buf_size = min(count, (sizeof(buf)-1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; + buf[buf_size] = '\0'; if (strtobool(buf, &bv) == 0) *val = bv; return count; } +EXPORT_SYMBOL_GPL(debugfs_write_file_bool); static const struct file_operations fops_bool = { - .read = read_file_bool, - .write = write_file_bool, + .read = debugfs_read_file_bool, + .write = debugfs_write_file_bool, + .open = simple_open, + .llseek = default_llseek, +}; + +static const struct file_operations fops_bool_ro = { + .read = debugfs_read_file_bool, + .open = simple_open, + .llseek = default_llseek, +}; + +static const struct file_operations fops_bool_wo = { + .write = debugfs_write_file_bool, .open = simple_open, .llseek = default_llseek, }; @@ -469,9 +536,10 @@ * code. */ struct dentry *debugfs_create_bool(const char *name, umode_t mode, - struct dentry *parent, u32 *value) + struct dentry *parent, bool *value) { - return debugfs_create_file(name, mode, parent, value, &fops_bool); + return debugfs_create_mode(name, mode, parent, value, &fops_bool, + &fops_bool_ro, &fops_bool_wo); } EXPORT_SYMBOL_GPL(debugfs_create_bool); @@ -649,26 +717,147 @@ * because some peripherals have several blocks of identical registers, * for example configuration of dma channels */ -int debugfs_print_regs32(struct seq_file *s, const struct debugfs_reg32 *regs, - int nregs, void __iomem *base, char *prefix) +void debugfs_print_regs32(struct seq_file *s, const struct debugfs_reg32 *regs, + int nregs, void __iomem *base, char *prefix) { - int i, ret = 0; + int i; for (i = 0; i < nregs; i++, regs++) { if (prefix) - ret += seq_printf(s, "%s", prefix); - ret += seq_printf(s, "%s = 0x%08x\n", regs->name, - readl(base + regs->offset)); + seq_printf(s, "%s", prefix); + seq_printf(s, "%#x: %s = 0x%08x\n",(unsigned int)(base + regs->offset), + regs->name, readl(base + regs->offset)); + if (seq_has_overflowed(s)) + break; } - return ret; } EXPORT_SYMBOL_GPL(debugfs_print_regs32); +#if defined(CONFIG_AVM_ENHANCED) +static void debugfs_print_regs32_with_read_fn(struct seq_file *s, + struct debugfs_regset32 *regset) +{ + const struct debugfs_reg32 *reg = regset->regs; + int i; + + for (i = 0; i < regset->nregs; i++, reg++) { + uint32_t addr = (uint32_t)regset->base + reg->offset; + seq_printf(s, "%#x: %s = 0x%08x\n", addr ,reg->name, + regset->reg_read_fn(regset->rw_context, addr)); + } + return; +} + +/* + * this write function allows writing to our regset like this: + * echo 'example_reg=0xdeadbeaf' > /sys/kernel/debug/our_regset + */ +static ssize_t debugfs_write_regset32(struct file *file, + const char *buffer, + size_t count, + loff_t *offset __maybe_unused) { + + unsigned char *debugfs_buffer; + unsigned char *val_buffer = NULL; + ssize_t fs_res = -EINVAL; + size_t debugfs_buffer_size; + struct debugfs_regset32 *regset; + unsigned int i; + + regset =((struct seq_file *) file->private_data)->private; + pr_debug("[%s] enter\n", __func__); + + /* + * 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 >= SD_MAX_INPUT_BUF ) { + debugfs_buffer_size = SD_MAX_INPUT_BUF; + } + else { + debugfs_buffer_size = count + 1; + } + + /* alloc buffer */ + debugfs_buffer = vmalloc(debugfs_buffer_size); + if (!debugfs_buffer){ + fs_res = -ENOMEM; + goto exit; + } + + /* write data to the buffer */ + if ( copy_from_user(debugfs_buffer, buffer, debugfs_buffer_size - 1) ) { + fs_res = -EFAULT; + goto free_exit; + } + + + debugfs_buffer[debugfs_buffer_size - 1] = 0; + + /* looking for '=' */ + for (i = 0; i < debugfs_buffer_size - 1; i ++){ + if (debugfs_buffer[i] == '='){ + debugfs_buffer[i] = 0; + val_buffer = &debugfs_buffer[i+1]; + } + } + if ( !val_buffer ){ + goto free_exit; + } + + for (i = 0; i < regset->nregs; i++){ + if ( strncmp(regset->regs[i].name, debugfs_buffer, (val_buffer - debugfs_buffer)) == 0) { + const struct debugfs_reg32 *reg = ®set->regs[i]; + unsigned int new_val; + pr_debug("lookup successful: %s in %d\n", debugfs_buffer, i); + if ( kstrtouint( val_buffer, 0, &new_val) == 0){ + pr_debug("new_val = %d\n", new_val); + fs_res = debugfs_buffer_size - 1; + if ( regset->reg_write_fn ){ + int reg_write_res; + pr_debug("using write fn = %pF\n", regset->reg_write_fn); + reg_write_res = regset->reg_write_fn(regset->rw_context, + (uint32_t)regset->base + reg->offset, + new_val); + if (reg_write_res < 0 ) + fs_res = -EIO; + } else { + pr_debug("no write fn, doing direct mem access at %#x + %#x\n", + (unsigned int)regset->base , (unsigned int)reg->offset ); + writel(new_val, regset->base + reg->offset); + } + } + goto free_exit; + } + } + +free_exit: + vfree(debugfs_buffer); +exit: + return fs_res; +} + + +#endif + + static int debugfs_show_regset32(struct seq_file *s, void *data) { struct debugfs_regset32 *regset = s->private; +#if defined(CONFIG_AVM_ENHANCED) + + if (regset->reg_read_fn) + debugfs_print_regs32_with_read_fn(s, regset); + else + debugfs_print_regs32(s, regset->regs, + regset->nregs, regset->base, ""); + +#else debugfs_print_regs32(s, regset->regs, regset->nregs, regset->base, ""); + +#endif /* defined(CONFIG_AVM_ENHANCED) */ return 0; } @@ -682,6 +871,9 @@ .read = seq_read, .llseek = seq_lseek, .release = single_release, +#if defined(CONFIG_AVM_ENHANCED) + .write = debugfs_write_regset32, +#endif }; /** @@ -718,3 +910,211 @@ EXPORT_SYMBOL_GPL(debugfs_create_regset32); #endif /* CONFIG_HAS_IOMEM */ + +#if defined(CONFIG_AVM_ENHANCED) + +LIST_HEAD(simple_debugfs_file_list); + +struct simple_debugfs_file_internal { + struct list_head list; + struct file_operations fops; + struct dentry *dentry; + struct dentry *parent; + int (*kernel_input)(char *, void *); + void (*kernel_output) (struct seq_file*, void *); + void *priv_data; + size_t expected_output_buffer_size; +}; + +static int simple_debugfs_file_show(struct seq_file *m, + void *offset __maybe_unused) { + struct simple_debugfs_file_internal *sdf = m->private; + if (sdf->kernel_output){ + pr_debug( "offset: %p\n", offset); + sdf->kernel_output(m, sdf->priv_data); + } + return 0; +} + +static int simple_debugfs_file_open(struct inode *inode, struct file *file) { + unsigned int res; + struct simple_debugfs_file_internal *sdf = inode->i_private; + pr_debug("call open with simple_debugfs_file_entry %p: priv=%p\n", + sdf, sdf->priv_data); + + if (sdf->expected_output_buffer_size){ + res = single_open_size(file, simple_debugfs_file_show, + sdf, sdf->expected_output_buffer_size); + } else { + res = single_open(file, simple_debugfs_file_show, sdf); + } + return res; +} + +static ssize_t simple_debugfs_file_write(struct file *file, + const char *buffer, + size_t count, + loff_t *offset __maybe_unused) { + + unsigned char *debugfs_buffer; + ssize_t res; + size_t debugfs_buffer_size; + struct simple_debugfs_file_internal *sdf; + + sdf =((struct seq_file *) file->private_data)->private; + + if ( sdf->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 >= SD_MAX_INPUT_BUF ) { + debugfs_buffer_size = SD_MAX_INPUT_BUF; + } + else { + debugfs_buffer_size = count + 1; + } + + /* alloc buffer */ + debugfs_buffer = vmalloc(debugfs_buffer_size); + if (!debugfs_buffer){ + return -ENOMEM; + } + + /* write data to the buffer */ + if ( copy_from_user(debugfs_buffer, buffer, debugfs_buffer_size - 1) ) { + vfree(debugfs_buffer); + return -EFAULT; + } + + debugfs_buffer[debugfs_buffer_size - 1] = 0; + res = sdf->kernel_input( debugfs_buffer, sdf->priv_data ); + if (res >= 0 ) + res = debugfs_buffer_size - 1; + + vfree(debugfs_buffer); + return res; +} + +int add_simple_debugfs_file( const char* fname, + struct dentry *parent, + int (*kernel_input)(char *, void *), + void (*kernel_output)(struct seq_file *, void *), + void *priv_data) +{ + + struct simple_debugfs_file_internal *simple_debugfs_file_entry = NULL; + + /* alloc private data structure */ + simple_debugfs_file_entry = + kzalloc( sizeof(*simple_debugfs_file_entry), GFP_KERNEL ); + if (!simple_debugfs_file_entry) { + return -ENOMEM; + } + + /* setup fops */ + simple_debugfs_file_entry->fops.open = simple_debugfs_file_open; + simple_debugfs_file_entry->fops.read = seq_read; + simple_debugfs_file_entry->fops.llseek = seq_lseek; + simple_debugfs_file_entry->fops.release = single_release; + if ( kernel_input ){ + simple_debugfs_file_entry->fops.write = + simple_debugfs_file_write; + } + + /* setup more */ + simple_debugfs_file_entry->parent = parent; + simple_debugfs_file_entry->priv_data = priv_data; + simple_debugfs_file_entry->kernel_input = kernel_input; + simple_debugfs_file_entry->kernel_output = kernel_output; + + /* create debugfs file */ + simple_debugfs_file_entry->dentry = debugfs_create_file( + fname, 0444, parent, + simple_debugfs_file_entry, + &simple_debugfs_file_entry->fops + ); + if (! simple_debugfs_file_entry->dentry ) { + kfree( simple_debugfs_file_entry ); + pr_debug("debugfs_crate_data failed \n"); + return -EFAULT; + } + + list_add(&simple_debugfs_file_entry->list, &simple_debugfs_file_list); + pr_debug("register simple_debugfs_file_entry %p\n", + simple_debugfs_file_entry); + return 0; +} + +void remove_simple_debugfs_file( struct dentry *dentry ){ + struct simple_debugfs_file_internal *sdf; + + list_for_each_entry(sdf, &simple_debugfs_file_list, list) { + if ( sdf->dentry == dentry ) { + list_del( &sdf->list ); + debugfs_remove( dentry ); + pr_debug("remove entry \n" ); + return ; + } + } +} + +EXPORT_SYMBOL(add_simple_debugfs_file); +EXPORT_SYMBOL(remove_simple_debugfs_file); + +#endif /* CONFIG_AVM_ENHANCED */ +struct debugfs_devm_entry { + int (*read)(struct seq_file *seq, void *data); + struct device *dev; +}; + +static int debugfs_devm_entry_open(struct inode *inode, struct file *f) +{ + struct debugfs_devm_entry *entry = inode->i_private; + + return single_open(f, entry->read, entry->dev); +} + +static const struct file_operations debugfs_devm_entry_ops = { + .owner = THIS_MODULE, + .open = debugfs_devm_entry_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek +}; + +/** + * debugfs_create_devm_seqfile - create a debugfs file that is bound to device. + * + * @dev: device related to this debugfs file. + * @name: name of the debugfs file. + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * file will be created in the root of the debugfs filesystem. + * @read_fn: function pointer called to print the seq_file content. + */ +struct dentry *debugfs_create_devm_seqfile(struct device *dev, const char *name, + struct dentry *parent, + int (*read_fn)(struct seq_file *s, + void *data)) +{ + struct debugfs_devm_entry *entry; + + if (IS_ERR(parent)) + return ERR_PTR(-ENOENT); + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return ERR_PTR(-ENOMEM); + + entry->read = read_fn; + entry->dev = dev; + + return debugfs_create_file(name, S_IRUGO, parent, entry, + &debugfs_devm_entry_ops); +} +EXPORT_SYMBOL_GPL(debugfs_create_devm_seqfile); +