diff options
| author | Tejun Heo <tj@kernel.org> | 2026-09-14 22:12:34 -1000 |
|---|---|---|
| committer | Tejun Heo <tj@kernel.org> | 2026-09-15 06:57:20 -1000 |
| commit | a9e3760b0838299649c0d57cca44daaf40ba3c33 (patch) | |
| tree | 95807ff0e511c05602ef34808cfc660303309057 /fs/debugfs | |
| download | linux-stable-a9e3760b0838299649c0d57cca44daaf40ba3c33.tar.gz linux-stable-a9e3760b0838299649c0d57cca44daaf40ba3c33.zip | |
sched_ext: Maintain an online cid mask in the scheduler arenagrafted
Schedulers on the default cid mapping treat [0, nr_online_cids) as the
online set and restart on hotplug. Schedulers that install their own mapping
with scx_bpf_cid_override() have no way to learn which cids are online: the
count no longer identifies members and the CPU-form cpumask is unusable from
cid programs. This is an obvious hole in the cid API.
Add scx_bpf_online_cmask(), a kernel-maintained cmask in the scheduler's
arena, allocated alongside the per-CPU scratch masks and populated after the
cid mapping is finalized and before ops.init(), for child schedulers too.
The pointer stays valid through ops.exit() with no reference to take. It is
the arena offset as a void pointer, the same form struct_ops arena arguments
arrive in. The verifier types the void return as a scalar for the program's
arena cast.
The mask follows the SCX hotplug notifications: seeded from cpu_active_mask
and updated before ops.cid_online/offline() runs, so it lags cpu_online_mask
only inside a hotplug transition. Updates walk the scheduler list under the
lock that also serializes unlinking. Reads are live, not atomic snapshots.
Root initialization excludes hotplug.
v2: Reworded the getter kerneldoc (Andrea Righi).
Signed-off-by: Tejun Heo <tj@kernel.org>
Reviewed-by: Andrea Righi <arighi@nvidia.com>
Diffstat (limited to 'fs/debugfs')
| -rw-r--r-- | fs/debugfs/Makefile | 5 | ||||
| -rw-r--r-- | fs/debugfs/file.c | 1435 | ||||
| -rw-r--r-- | fs/debugfs/inode.c | 929 | ||||
| -rw-r--r-- | fs/debugfs/internal.h | 58 |
4 files changed, 2427 insertions, 0 deletions
diff --git a/fs/debugfs/Makefile b/fs/debugfs/Makefile new file mode 100644 index 000000000..9c0fe38cf --- /dev/null +++ b/fs/debugfs/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +debugfs-objs := inode.o file.o + +obj-$(CONFIG_DEBUG_FS) += debugfs.o + diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c new file mode 100644 index 000000000..edd6aafbf --- /dev/null +++ b/fs/debugfs/file.c @@ -0,0 +1,1435 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * file.c - part of debugfs, a tiny little debug file system + * + * Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2004 IBM Inc. + * + * debugfs is for people to use instead of /proc or /sys. + * See Documentation/filesystems/ for more details. + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/pagemap.h> +#include <linux/debugfs.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/atomic.h> +#include <linux/device.h> +#include <linux/pm_runtime.h> +#include <linux/poll.h> +#include <linux/security.h> + +#include "internal.h" + +struct poll_table_struct; + +static ssize_t default_read_file(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t default_write_file(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return count; +} + +const struct file_operations debugfs_noop_file_operations = { + .read = default_read_file, + .write = default_write_file, + .open = simple_open, + .llseek = noop_llseek, +}; + +#define F_DENTRY(filp) ((filp)->f_path.dentry) + +void *debugfs_get_aux(const struct file *file) +{ + return DEBUGFS_I(file_inode(file))->aux; +} +EXPORT_SYMBOL_GPL(debugfs_get_aux); + +enum dbgfs_get_mode { + DBGFS_GET_ALREADY, + DBGFS_GET_REGULAR, + DBGFS_GET_SHORT, +}; + +static int __debugfs_file_get(struct dentry *dentry, enum dbgfs_get_mode mode) +{ + struct debugfs_fsdata *fsd; + void *d_fsd; + + /* + * This could only happen if some debugfs user erroneously calls + * debugfs_file_get() on a dentry that isn't even a file, let + * them know about it. + */ + if (WARN_ON(!d_is_reg(dentry))) + return -EINVAL; + + d_fsd = READ_ONCE(dentry->d_fsdata); + if (d_fsd) { + fsd = d_fsd; + } else { + struct inode *inode = dentry->d_inode; + unsigned int methods = 0; + + if (WARN_ON(mode == DBGFS_GET_ALREADY)) + return -EINVAL; + + fsd = kmalloc_obj(*fsd); + if (!fsd) + return -ENOMEM; + + if (mode == DBGFS_GET_SHORT) { + const struct debugfs_short_fops *ops; + ops = fsd->short_fops = DEBUGFS_I(inode)->short_fops; + if (ops->llseek) + methods |= HAS_LSEEK; + if (ops->read) + methods |= HAS_READ; + if (ops->write) + methods |= HAS_WRITE; + fsd->real_fops = NULL; + } else { + const struct file_operations *ops; + ops = fsd->real_fops = DEBUGFS_I(inode)->real_fops; + if (ops->llseek) + methods |= HAS_LSEEK; + if (ops->read) + methods |= HAS_READ; + if (ops->write) + methods |= HAS_WRITE; + if (ops->unlocked_ioctl) + methods |= HAS_IOCTL; + if (ops->poll) + methods |= HAS_POLL; + fsd->short_fops = NULL; + } + fsd->methods = methods; + refcount_set(&fsd->active_users, 1); + init_completion(&fsd->active_users_drained); + INIT_LIST_HEAD(&fsd->cancellations); + mutex_init(&fsd->cancellations_mtx); + + d_fsd = cmpxchg(&dentry->d_fsdata, NULL, fsd); + if (d_fsd) { + mutex_destroy(&fsd->cancellations_mtx); + kfree(fsd); + fsd = d_fsd; + } + } + + /* + * In case of a successful cmpxchg() above, this check is + * strictly necessary and must follow it, see the comment in + * __debugfs_remove_file(). + * OTOH, if the cmpxchg() hasn't been executed or wasn't + * successful, this serves the purpose of not starving + * removers. + */ + if (d_unlinked(dentry)) + return -EIO; + + if (!refcount_inc_not_zero(&fsd->active_users)) + return -EIO; + + return 0; +} + +/** + * debugfs_file_get - mark the beginning of file data access + * @dentry: the dentry object whose data is being accessed. + * + * Up to a matching call to debugfs_file_put(), any successive call + * into the file removing functions debugfs_remove() and + * debugfs_remove_recursive() will block. Since associated private + * file data may only get freed after a successful return of any of + * the removal functions, you may safely access it after a successful + * call to debugfs_file_get() without worrying about lifetime issues. + * + * If -%EIO is returned, the file has already been removed and thus, + * it is not safe to access any of its data. If, on the other hand, + * it is allowed to access the file data, zero is returned. + */ +int debugfs_file_get(struct dentry *dentry) +{ + return __debugfs_file_get(dentry, DBGFS_GET_ALREADY); +} +EXPORT_SYMBOL_GPL(debugfs_file_get); + +/** + * debugfs_file_put - mark the end of file data access + * @dentry: the dentry object formerly passed to + * debugfs_file_get(). + * + * Allow any ongoing concurrent call into debugfs_remove() or + * debugfs_remove_recursive() blocked by a former call to + * debugfs_file_get() to proceed and return to its caller. + */ +void debugfs_file_put(struct dentry *dentry) +{ + struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata); + + if (refcount_dec_and_test(&fsd->active_users)) + complete(&fsd->active_users_drained); +} +EXPORT_SYMBOL_GPL(debugfs_file_put); + +/** + * debugfs_enter_cancellation - enter a debugfs cancellation + * @file: the file being accessed + * @cancellation: the cancellation object, the cancel callback + * inside of it must be initialized + * + * When a debugfs file is removed it needs to wait for all active + * operations to complete. However, the operation itself may need + * to wait for hardware or completion of some asynchronous process + * or similar. As such, it may need to be cancelled to avoid long + * waits or even deadlocks. + * + * This function can be used inside a debugfs handler that may + * need to be cancelled. As soon as this function is called, the + * cancellation's 'cancel' callback may be called, at which point + * the caller should proceed to call debugfs_leave_cancellation() + * and leave the debugfs handler function as soon as possible. + * Note that the 'cancel' callback is only ever called in the + * context of some kind of debugfs_remove(). + * + * This function must be paired with debugfs_leave_cancellation(). + */ +void debugfs_enter_cancellation(struct file *file, + struct debugfs_cancellation *cancellation) +{ + struct debugfs_fsdata *fsd; + struct dentry *dentry = F_DENTRY(file); + + INIT_LIST_HEAD(&cancellation->list); + + if (WARN_ON(!d_is_reg(dentry))) + return; + + if (WARN_ON(!cancellation->cancel)) + return; + + fsd = READ_ONCE(dentry->d_fsdata); + if (WARN_ON(!fsd)) + return; + + mutex_lock(&fsd->cancellations_mtx); + list_add(&cancellation->list, &fsd->cancellations); + mutex_unlock(&fsd->cancellations_mtx); + + /* if we're already removing wake it up to cancel */ + if (d_unlinked(dentry)) + complete(&fsd->active_users_drained); +} +EXPORT_SYMBOL_GPL(debugfs_enter_cancellation); + +/** + * debugfs_leave_cancellation - leave cancellation section + * @file: the file being accessed + * @cancellation: the cancellation previously registered with + * debugfs_enter_cancellation() + * + * See the documentation of debugfs_enter_cancellation(). + */ +void debugfs_leave_cancellation(struct file *file, + struct debugfs_cancellation *cancellation) +{ + struct debugfs_fsdata *fsd; + struct dentry *dentry = F_DENTRY(file); + + if (WARN_ON(!d_is_reg(dentry))) + return; + + fsd = READ_ONCE(dentry->d_fsdata); + if (WARN_ON(!fsd)) + return; + + mutex_lock(&fsd->cancellations_mtx); + if (!list_empty(&cancellation->list)) + list_del(&cancellation->list); + mutex_unlock(&fsd->cancellations_mtx); +} +EXPORT_SYMBOL_GPL(debugfs_leave_cancellation); + +/* + * Only permit access to world-readable files when the kernel is locked down. + * We also need to exclude any file that has ways to write or alter it as root + * can bypass the permissions check. + */ +static int debugfs_locked_down(struct inode *inode, + struct file *filp, + const struct file_operations *real_fops) +{ + if ((inode->i_mode & 07777 & ~0444) == 0 && + !(filp->f_mode & FMODE_WRITE) && + (!real_fops || + (!real_fops->unlocked_ioctl && + !real_fops->compat_ioctl && + !real_fops->mmap))) + return 0; + + if (security_locked_down(LOCKDOWN_DEBUGFS)) + return -EPERM; + + return 0; +} + +static int open_proxy_open(struct inode *inode, struct file *filp) +{ + struct dentry *dentry = F_DENTRY(filp); + const struct file_operations *real_fops = DEBUGFS_I(inode)->real_fops; + int r; + + r = __debugfs_file_get(dentry, DBGFS_GET_REGULAR); + if (r) + return r == -EIO ? -ENOENT : r; + + r = debugfs_locked_down(inode, filp, real_fops); + if (r) + goto out; + + if (!fops_get(real_fops)) { +#ifdef CONFIG_MODULES + if (real_fops->owner && + real_fops->owner->state == MODULE_STATE_GOING) { + r = -ENXIO; + goto out; + } +#endif + + /* Huh? Module did not clean up after itself at exit? */ + WARN(1, "debugfs file owner did not clean up at exit: %pd", + dentry); + r = -ENXIO; + goto out; + } + replace_fops(filp, real_fops); + + if (real_fops->open) + r = real_fops->open(inode, filp); + +out: + debugfs_file_put(dentry); + return r; +} + +const struct file_operations debugfs_open_proxy_file_operations = { + .open = open_proxy_open, +}; + +#define PROTO(args...) args +#define ARGS(args...) args + +#define FULL_PROXY_FUNC(name, ret_type, filp, proto, args, bit, ret) \ +static ret_type full_proxy_ ## name(proto) \ +{ \ + struct dentry *dentry = F_DENTRY(filp); \ + struct debugfs_fsdata *fsd = dentry->d_fsdata; \ + ret_type r; \ + \ + if (!(fsd->methods & bit)) \ + return ret; \ + r = debugfs_file_get(dentry); \ + if (unlikely(r)) \ + return r; \ + r = fsd->real_fops->name(args); \ + debugfs_file_put(dentry); \ + return r; \ +} + +#define SHORT_PROXY_FUNC(name, ret_type, filp, proto, args, bit, ret) \ +static ret_type short_proxy_ ## name(proto) \ +{ \ + struct dentry *dentry = F_DENTRY(filp); \ + struct debugfs_fsdata *fsd = dentry->d_fsdata; \ + ret_type r; \ + \ + if (!(fsd->methods & bit)) \ + return ret; \ + r = debugfs_file_get(dentry); \ + if (unlikely(r)) \ + return r; \ + r = fsd->short_fops->name(args); \ + debugfs_file_put(dentry); \ + return r; \ +} + +SHORT_PROXY_FUNC(llseek, loff_t, filp, + PROTO(struct file *filp, loff_t offset, int whence), + ARGS(filp, offset, whence), HAS_LSEEK, -ESPIPE); + +FULL_PROXY_FUNC(llseek, loff_t, filp, + PROTO(struct file *filp, loff_t offset, int whence), + ARGS(filp, offset, whence), HAS_LSEEK, -ESPIPE); + +SHORT_PROXY_FUNC(read, ssize_t, filp, + PROTO(struct file *filp, char __user *buf, size_t size, + loff_t *ppos), + ARGS(filp, buf, size, ppos), HAS_READ, -EINVAL); + +FULL_PROXY_FUNC(read, ssize_t, filp, + PROTO(struct file *filp, char __user *buf, size_t size, + loff_t *ppos), + ARGS(filp, buf, size, ppos), HAS_READ, -EINVAL); + +SHORT_PROXY_FUNC(write, ssize_t, filp, + PROTO(struct file *filp, const char __user *buf, + size_t size, loff_t *ppos), + ARGS(filp, buf, size, ppos), HAS_WRITE, -EINVAL); + +FULL_PROXY_FUNC(write, ssize_t, filp, + PROTO(struct file *filp, const char __user *buf, + size_t size, loff_t *ppos), + ARGS(filp, buf, size, ppos), HAS_WRITE, -EINVAL); + +FULL_PROXY_FUNC(unlocked_ioctl, long, filp, + PROTO(struct file *filp, unsigned int cmd, unsigned long arg), + ARGS(filp, cmd, arg), HAS_IOCTL, -ENOTTY); + +static __poll_t full_proxy_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct dentry *dentry = F_DENTRY(filp); + struct debugfs_fsdata *fsd = dentry->d_fsdata; + __poll_t r = 0; + + if (!(fsd->methods & HAS_POLL)) + return DEFAULT_POLLMASK; + if (debugfs_file_get(dentry)) + return EPOLLHUP; + + r = fsd->real_fops->poll(filp, wait); + debugfs_file_put(dentry); + return r; +} + +static int full_proxy_release(struct inode *inode, struct file *file) +{ + struct debugfs_fsdata *fsd = F_DENTRY(file)->d_fsdata; + const struct file_operations *real_fops = fsd->real_fops; + int r = 0; + + /* + * We must not protect this against removal races here: the + * original releaser should be called unconditionally in order + * not to leak any resources. Releasers must not assume that + * ->i_private is still being meaningful here. + */ + if (real_fops->release) + r = real_fops->release(inode, file); + + fops_put(real_fops); + return r; +} + +static int full_proxy_open_regular(struct inode *inode, struct file *filp) +{ + struct dentry *dentry = F_DENTRY(filp); + const struct file_operations *real_fops; + struct debugfs_fsdata *fsd; + int r; + + r = __debugfs_file_get(dentry, DBGFS_GET_REGULAR); + if (r) + return r == -EIO ? -ENOENT : r; + + fsd = dentry->d_fsdata; + real_fops = fsd->real_fops; + r = debugfs_locked_down(inode, filp, real_fops); + if (r) + goto out; + + if (!fops_get(real_fops)) { +#ifdef CONFIG_MODULES + if (real_fops->owner && + real_fops->owner->state == MODULE_STATE_GOING) { + r = -ENXIO; + goto out; + } +#endif + + /* Huh? Module did not cleanup after itself at exit? */ + WARN(1, "debugfs file owner did not clean up at exit: %pd", + dentry); + r = -ENXIO; + goto out; + } + + if (real_fops->open) { + r = real_fops->open(inode, filp); + if (r) { + fops_put(real_fops); + } else if (filp->f_op != &debugfs_full_proxy_file_operations) { + /* No protection against file removal anymore. */ + WARN(1, "debugfs file owner replaced proxy fops: %pd", + dentry); + fops_put(real_fops); + } + } +out: + debugfs_file_put(dentry); + return r; +} + +const struct file_operations debugfs_full_proxy_file_operations = { + .open = full_proxy_open_regular, + .release = full_proxy_release, + .llseek = full_proxy_llseek, + .read = full_proxy_read, + .write = full_proxy_write, + .poll = full_proxy_poll, + .unlocked_ioctl = full_proxy_unlocked_ioctl +}; + +static int full_proxy_open_short(struct inode *inode, struct file *filp) +{ + struct dentry *dentry = F_DENTRY(filp); + int r; + + r = __debugfs_file_get(dentry, DBGFS_GET_SHORT); + if (r) + return r == -EIO ? -ENOENT : r; + r = debugfs_locked_down(inode, filp, NULL); + if (!r) + r = simple_open(inode, filp); + debugfs_file_put(dentry); + return r; +} + +const struct file_operations debugfs_full_short_proxy_file_operations = { + .open = full_proxy_open_short, + .llseek = short_proxy_llseek, + .read = short_proxy_read, + .write = short_proxy_write, +}; + +ssize_t debugfs_attr_read(struct file *file, char __user *buf, + size_t len, loff_t *ppos) +{ + struct dentry *dentry = F_DENTRY(file); + ssize_t ret; + + ret = debugfs_file_get(dentry); + if (unlikely(ret)) + return ret; + ret = simple_attr_read(file, buf, len, ppos); + debugfs_file_put(dentry); + return ret; +} +EXPORT_SYMBOL_GPL(debugfs_attr_read); + +static ssize_t debugfs_attr_write_xsigned(struct file *file, const char __user *buf, + size_t len, loff_t *ppos, bool is_signed) +{ + struct dentry *dentry = F_DENTRY(file); + ssize_t ret; + + ret = debugfs_file_get(dentry); + if (unlikely(ret)) + return ret; + if (is_signed) + ret = simple_attr_write_signed(file, buf, len, ppos); + else + ret = simple_attr_write(file, buf, len, ppos); + debugfs_file_put(dentry); + return ret; +} + +ssize_t debugfs_attr_write(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + return debugfs_attr_write_xsigned(file, buf, len, ppos, false); +} +EXPORT_SYMBOL_GPL(debugfs_attr_write); + +ssize_t debugfs_attr_write_signed(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + return debugfs_attr_write_xsigned(file, buf, len, ppos, true); +} +EXPORT_SYMBOL_GPL(debugfs_attr_write_signed); + +static struct dentry *debugfs_create_mode_unsafe(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) +{ + /* if there are no write bits set, make read only */ + if (!(mode & S_IWUGO)) + return debugfs_create_file_unsafe(name, mode, parent, value, + fops_ro); + /* if there are no read bits set, make write only */ + if (!(mode & S_IRUGO)) + return debugfs_create_file_unsafe(name, mode, parent, value, + fops_wo); + + return debugfs_create_file_unsafe(name, mode, parent, value, fops); +} + +static int debugfs_u8_set(void *data, u64 val) +{ + *(u8 *)data = val; + return 0; +} +static int debugfs_u8_get(void *data, u64 *val) +{ + *val = *(u8 *)data; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_u8, debugfs_u8_get, debugfs_u8_set, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u8_ro, debugfs_u8_get, NULL, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u8_wo, NULL, debugfs_u8_set, "%llu\n"); + +/** + * debugfs_create_u8 - create a debugfs file that is used to read and write an unsigned 8-bit 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. + */ +void debugfs_create_u8(const char *name, umode_t mode, struct dentry *parent, + u8 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_u8, + &fops_u8_ro, &fops_u8_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_u8); + +static int debugfs_u16_set(void *data, u64 val) +{ + *(u16 *)data = val; + return 0; +} +static int debugfs_u16_get(void *data, u64 *val) +{ + *val = *(u16 *)data; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_u16, debugfs_u16_get, debugfs_u16_set, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u16_ro, debugfs_u16_get, NULL, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u16_wo, NULL, debugfs_u16_set, "%llu\n"); + +/** + * debugfs_create_u16 - create a debugfs file that is used to read and write an unsigned 16-bit 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. + */ +void debugfs_create_u16(const char *name, umode_t mode, struct dentry *parent, + u16 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_u16, + &fops_u16_ro, &fops_u16_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_u16); + +static int debugfs_u32_set(void *data, u64 val) +{ + *(u32 *)data = val; + return 0; +} +static int debugfs_u32_get(void *data, u64 *val) +{ + *val = *(u32 *)data; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_u32, debugfs_u32_get, debugfs_u32_set, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u32_ro, debugfs_u32_get, NULL, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u32_wo, NULL, debugfs_u32_set, "%llu\n"); + +/** + * debugfs_create_u32 - create a debugfs file that is used to read and write an unsigned 32-bit 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. + */ +void debugfs_create_u32(const char *name, umode_t mode, struct dentry *parent, + u32 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_u32, + &fops_u32_ro, &fops_u32_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_u32); + +static int debugfs_u64_set(void *data, u64 val) +{ + *(u64 *)data = val; + return 0; +} + +static int debugfs_u64_get(void *data, u64 *val) +{ + *val = *(u64 *)data; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_u64, debugfs_u64_get, debugfs_u64_set, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u64_ro, debugfs_u64_get, NULL, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_u64_wo, NULL, debugfs_u64_set, "%llu\n"); + +/** + * debugfs_create_u64 - create a debugfs file that is used to read and write an unsigned 64-bit 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. + */ +void debugfs_create_u64(const char *name, umode_t mode, struct dentry *parent, + u64 *value) +{ + debugfs_create_mode_unsafe(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_DEBUGFS_ATTRIBUTE(fops_ulong, debugfs_ulong_get, debugfs_ulong_set, + "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_ulong_ro, debugfs_ulong_get, NULL, "%llu\n"); +DEFINE_DEBUGFS_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. + */ +void debugfs_create_ulong(const char *name, umode_t mode, struct dentry *parent, + unsigned long *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_ulong, + &fops_ulong_ro, &fops_ulong_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_ulong); + +DEFINE_DEBUGFS_ATTRIBUTE(fops_x8, debugfs_u8_get, debugfs_u8_set, "0x%02llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x8_ro, debugfs_u8_get, NULL, "0x%02llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x8_wo, NULL, debugfs_u8_set, "0x%02llx\n"); + +DEFINE_DEBUGFS_ATTRIBUTE(fops_x16, debugfs_u16_get, debugfs_u16_set, + "0x%04llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x16_ro, debugfs_u16_get, NULL, "0x%04llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x16_wo, NULL, debugfs_u16_set, "0x%04llx\n"); + +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, debugfs_u32_get, debugfs_u32_set, + "0x%08llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32_ro, debugfs_u32_get, NULL, "0x%08llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x32_wo, NULL, debugfs_u32_set, "0x%08llx\n"); + +DEFINE_DEBUGFS_ATTRIBUTE(fops_x64, debugfs_u64_get, debugfs_u64_set, + "0x%016llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(fops_x64_ro, debugfs_u64_get, NULL, "0x%016llx\n"); +DEFINE_DEBUGFS_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 + * + * These functions are exactly the same as the above functions (but use a hex + * output for the decimal challenged). For details look at the above unsigned + * decimal functions. + */ + +/** + * debugfs_create_x8 - create a debugfs file that is used to read and write an unsigned 8-bit 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. + */ +void debugfs_create_x8(const char *name, umode_t mode, struct dentry *parent, + u8 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_x8, + &fops_x8_ro, &fops_x8_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_x8); + +/** + * debugfs_create_x16 - create a debugfs file that is used to read and write an unsigned 16-bit 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. + */ +void debugfs_create_x16(const char *name, umode_t mode, struct dentry *parent, + u16 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_x16, + &fops_x16_ro, &fops_x16_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_x16); + +/** + * debugfs_create_x32 - create a debugfs file that is used to read and write an unsigned 32-bit 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. + */ +void debugfs_create_x32(const char *name, umode_t mode, struct dentry *parent, + u32 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_x32, + &fops_x32_ro, &fops_x32_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_x32); + +/** + * debugfs_create_x64 - create a debugfs file that is used to read and write an unsigned 64-bit 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. + */ +void debugfs_create_x64(const char *name, umode_t mode, struct dentry *parent, + u64 *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_x64, + &fops_x64_ro, &fops_x64_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_x64); + + +static int debugfs_size_t_set(void *data, u64 val) +{ + *(size_t *)data = val; + return 0; +} +static int debugfs_size_t_get(void *data, u64 *val) +{ + *val = *(size_t *)data; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_size_t, debugfs_size_t_get, debugfs_size_t_set, + "%llu\n"); /* %llu and %zu are more or less the same */ +DEFINE_DEBUGFS_ATTRIBUTE(fops_size_t_ro, debugfs_size_t_get, NULL, "%llu\n"); +DEFINE_DEBUGFS_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 + * @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. + */ +void debugfs_create_size_t(const char *name, umode_t mode, + struct dentry *parent, size_t *value) +{ + debugfs_create_mode_unsafe(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_DEBUGFS_ATTRIBUTE_SIGNED(fops_atomic_t, debugfs_atomic_t_get, + debugfs_atomic_t_set, "%lld\n"); +DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(fops_atomic_t_ro, debugfs_atomic_t_get, NULL, + "%lld\n"); +DEFINE_DEBUGFS_ATTRIBUTE_SIGNED(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. + */ +void debugfs_create_atomic_t(const char *name, umode_t mode, + struct dentry *parent, atomic_t *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_atomic_t, + &fops_atomic_t_ro, &fops_atomic_t_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_atomic_t); + +ssize_t debugfs_read_file_bool(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[2]; + bool val; + int r; + struct dentry *dentry = F_DENTRY(file); + + r = debugfs_file_get(dentry); + if (unlikely(r)) + return r; + val = *(bool *)file->private_data; + debugfs_file_put(dentry); + + if (val) + buf[0] = 'Y'; + else + buf[0] = 'N'; + buf[1] = '\n'; + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} +EXPORT_SYMBOL_GPL(debugfs_read_file_bool); + +ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + bool bv; + int r; + bool *val = file->private_data; + struct dentry *dentry = F_DENTRY(file); + + r = kstrtobool_from_user(user_buf, count, &bv); + if (!r) { + r = debugfs_file_get(dentry); + if (unlikely(r)) + return r; + *val = bv; + debugfs_file_put(dentry); + } + + return count; +} +EXPORT_SYMBOL_GPL(debugfs_write_file_bool); + +static const struct file_operations fops_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, +}; + +/** + * debugfs_create_bool - create a debugfs file that is used to read and write a boolean 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. + */ +void debugfs_create_bool(const char *name, umode_t mode, struct dentry *parent, + bool *value) +{ + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_bool, + &fops_bool_ro, &fops_bool_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_bool); + +ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct dentry *dentry = F_DENTRY(file); + char *str, *copy = NULL; + int copy_len, len; + ssize_t ret; + + ret = debugfs_file_get(dentry); + if (unlikely(ret)) + return ret; + + str = *(char **)file->private_data; + len = strlen(str) + 1; + copy = kmalloc(len, GFP_KERNEL); + if (!copy) { + debugfs_file_put(dentry); + return -ENOMEM; + } + + copy_len = strscpy(copy, str, len); + debugfs_file_put(dentry); + if (copy_len < 0) { + kfree(copy); + return copy_len; + } + + copy[copy_len] = '\n'; + + ret = simple_read_from_buffer(user_buf, count, ppos, copy, len); + kfree(copy); + + return ret; +} + +static ssize_t debugfs_write_file_str(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct dentry *dentry = F_DENTRY(file); + char *old, *new = NULL; + int pos = *ppos; + int r; + + r = debugfs_file_get(dentry); + if (unlikely(r)) + return r; + + old = *(char **)file->private_data; + + /* only allow strict concatenation */ + r = -EINVAL; + if (pos && pos != strlen(old)) + goto error; + + r = -E2BIG; + if (pos + count + 1 > PAGE_SIZE) + goto error; + + r = -ENOMEM; + new = kmalloc(pos + count + 1, GFP_KERNEL); + if (!new) + goto error; + + if (pos) + memcpy(new, old, pos); + + r = -EFAULT; + if (copy_from_user(new + pos, user_buf, count)) + goto error; + + new[pos + count] = '\0'; + strim(new); + + rcu_assign_pointer(*(char __rcu **)file->private_data, new); + synchronize_rcu(); + kfree(old); + + debugfs_file_put(dentry); + return count; + +error: + kfree(new); + debugfs_file_put(dentry); + return r; +} + +static const struct file_operations fops_str = { + .read = debugfs_read_file_str, + .write = debugfs_write_file_str, + .open = simple_open, + .llseek = default_llseek, +}; + +static const struct file_operations fops_str_ro = { + .read = debugfs_read_file_str, + .open = simple_open, + .llseek = default_llseek, +}; + +static const struct file_operations fops_str_wo = { + .write = debugfs_write_file_str, + .open = simple_open, + .llseek = default_llseek, +}; + +/** + * debugfs_create_str - create a debugfs file that is used to read and write a string 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 pointer and the string it points to must not be %NULL. + * + * 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. + */ +void debugfs_create_str(const char *name, umode_t mode, + struct dentry *parent, char **value) +{ + if (WARN_ON(!value || !*value)) + return; + + debugfs_create_mode_unsafe(name, mode, parent, value, &fops_str, + &fops_str_ro, &fops_str_wo); +} +EXPORT_SYMBOL_GPL(debugfs_create_str); + +static ssize_t read_file_blob(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct debugfs_blob_wrapper *blob = file->private_data; + struct dentry *dentry = F_DENTRY(file); + ssize_t r; + + r = debugfs_file_get(dentry); + if (unlikely(r)) + return r; + r = simple_read_from_buffer(user_buf, count, ppos, blob->data, + blob->size); + debugfs_file_put(dentry); + return r; +} + +static ssize_t write_file_blob(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct debugfs_blob_wrapper *blob = file->private_data; + struct dentry *dentry = F_DENTRY(file); + ssize_t r; + + r = debugfs_file_get(dentry); + if (unlikely(r)) + return r; + r = simple_write_to_buffer(blob->data, blob->size, ppos, user_buf, + count); + + debugfs_file_put(dentry); + return r; +} + +static const struct file_operations fops_blob = { + .read = read_file_blob, + .write = write_file_blob, + .open = simple_open, + .llseek = default_llseek, +}; + +/** + * debugfs_create_blob - create a debugfs file that is used to read and write + * a binary blob + * @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. + * @blob: a pointer to a struct debugfs_blob_wrapper which contains a pointer + * to the blob data and the size of the data. + * + * This function creates a file in debugfs with the given name that exports + * @blob->data as a binary blob. 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, ERR_PTR(-ERROR) will be + * returned. + * + * If debugfs is not enabled in the kernel, the value ERR_PTR(-ENODEV) will + * be returned. + */ +struct dentry *debugfs_create_blob(const char *name, umode_t mode, + struct dentry *parent, + struct debugfs_blob_wrapper *blob) +{ + return debugfs_create_file_unsafe(name, mode & 0644, parent, blob, &fops_blob); +} +EXPORT_SYMBOL_GPL(debugfs_create_blob); + +static size_t u32_format_array(char *buf, size_t bufsize, + u32 *array, int array_size) +{ + size_t ret = 0; + + while (--array_size >= 0) { + size_t len; + char term = array_size ? ' ' : '\n'; + + len = snprintf(buf, bufsize, "%u%c", *array++, term); + ret += len; + + buf += len; + bufsize -= len; + } + return ret; +} + +static int u32_array_open(struct inode *inode, struct file *file) +{ + struct debugfs_u32_array *data = inode->i_private; + int size, elements = data->n_elements; + char *buf; + + /* + * Max size: + * - 10 digits + ' '/'\n' = 11 bytes per number + * - terminating NUL character + */ + size = elements*11; + buf = kmalloc(size+1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + buf[size] = 0; + + file->private_data = buf; + u32_format_array(buf, size, data->array, data->n_elements); + + return nonseekable_open(inode, file); +} + +static ssize_t u32_array_read(struct file *file, char __user *buf, size_t len, + loff_t *ppos) +{ + size_t size = strlen(file->private_data); + + return simple_read_from_buffer(buf, len, ppos, + file->private_data, size); +} + +static int u32_array_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + + return 0; +} + +static const struct file_operations u32_array_fops = { + .owner = THIS_MODULE, + .open = u32_array_open, + .release = u32_array_release, + .read = u32_array_read, +}; + +/** + * debugfs_create_u32_array - create a debugfs file that is used to read u32 + * array. + * @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. + * @array: wrapper struct containing data pointer and size of the array. + * + * This function creates a file in debugfs with the given name that exports + * @array as data. If the @mode variable is so set it can be read from. + * Writing is not supported. Seek within the file is also not supported. + * Once array is created its size can not be changed. + */ +void debugfs_create_u32_array(const char *name, umode_t mode, + struct dentry *parent, + struct debugfs_u32_array *array) +{ + debugfs_create_file_unsafe(name, mode, parent, array, &u32_array_fops); +} +EXPORT_SYMBOL_GPL(debugfs_create_u32_array); + +#ifdef CONFIG_HAS_IOMEM + +/* + * The regset32 stuff is used to print 32-bit registers using the + * seq_file utilities. We offer printing a register set in an already-opened + * sequential file or create a debugfs file that only prints a regset32. + */ + +/** + * debugfs_print_regs32 - use seq_print to describe a set of registers + * @s: the seq_file structure being used to generate output + * @regs: an array if struct debugfs_reg32 structures + * @nregs: the length of the above array + * @base: the base address to be used in reading the registers + * @prefix: a string to be prefixed to every output line + * + * This function outputs a text block describing the current values of + * some 32-bit hardware registers. It is meant to be used within debugfs + * files based on seq_file that need to show registers, intermixed with other + * information. The prefix argument may be used to specify a leading string, + * because some peripherals have several blocks of identical registers, + * for example configuration of dma channels + */ +void debugfs_print_regs32(struct seq_file *s, const struct debugfs_reg32 *regs, + int nregs, void __iomem *base, char *prefix) +{ + int i; + + for (i = 0; i < nregs; i++, regs++) { + if (prefix) + seq_printf(s, "%s", prefix); + seq_printf(s, "%s = 0x%08x\n", regs->name, + readl(base + regs->offset)); + if (seq_has_overflowed(s)) + break; + } +} +EXPORT_SYMBOL_GPL(debugfs_print_regs32); + +static int debugfs_regset32_show(struct seq_file *s, void *data) +{ + struct debugfs_regset32 *regset = s->private; + + if (regset->dev) + pm_runtime_get_sync(regset->dev); + + debugfs_print_regs32(s, regset->regs, regset->nregs, regset->base, ""); + + if (regset->dev) + pm_runtime_put(regset->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(debugfs_regset32); + +/** + * debugfs_create_regset32 - create a debugfs file that returns register values + * @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. + * @regset: a pointer to a struct debugfs_regset32, which contains a pointer + * to an array of register definitions, the array size and the base + * address where the register bank is to be found. + * + * This function creates a file in debugfs with the given name that reports + * the names and values of a set of 32-bit registers. If the @mode variable + * is so set it can be read from. Writing is not supported. + */ +void debugfs_create_regset32(const char *name, umode_t mode, + struct dentry *parent, + struct debugfs_regset32 *regset) +{ + debugfs_create_file(name, mode, parent, regset, &debugfs_regset32_fops); +} +EXPORT_SYMBOL_GPL(debugfs_create_regset32); + +#endif /* CONFIG_HAS_IOMEM */ + +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. + */ +void 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; + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + entry->read = read_fn; + entry->dev = dev; + + debugfs_create_file(name, S_IRUGO, parent, entry, + &debugfs_devm_entry_ops); +} +EXPORT_SYMBOL_GPL(debugfs_create_devm_seqfile); diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c new file mode 100644 index 000000000..459814235 --- /dev/null +++ b/fs/debugfs/inode.c @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * inode.c - part of debugfs, a tiny little debug file system + * + * Copyright (C) 2004,2019 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2004 IBM Inc. + * Copyright (C) 2019 Linux Foundation <gregkh@linuxfoundation.org> + * + * debugfs is for people to use instead of /proc or /sys. + * See ./Documentation/core-api/kernel-api.rst for more details. + */ + +#define pr_fmt(fmt) "debugfs: " fmt + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/fs_context.h> +#include <linux/fs_parser.h> +#include <linux/pagemap.h> +#include <linux/init.h> +#include <linux/kobject.h> +#include <linux/namei.h> +#include <linux/debugfs.h> +#include <linux/fsnotify.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#include <linux/magic.h> +#include <linux/slab.h> +#include <linux/security.h> + +#include "internal.h" + +#define DEBUGFS_DEFAULT_MODE 0700 + +static struct vfsmount *debugfs_mount; +static int debugfs_mount_count; +static bool debugfs_registered; +static bool debugfs_enabled __ro_after_init = IS_ENABLED(CONFIG_DEBUG_FS_ALLOW_ALL); + +/* + * Don't allow access attributes to be changed whilst the kernel is locked down + * so that we can use the file mode as part of a heuristic to determine whether + * to lock down individual files. + */ +static int debugfs_setattr(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *ia) +{ + int ret; + + if (ia->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) { + ret = security_locked_down(LOCKDOWN_DEBUGFS); + if (ret) + return ret; + } + return simple_setattr(&nop_mnt_idmap, dentry, ia); +} + +static const struct inode_operations debugfs_file_inode_operations = { + .setattr = debugfs_setattr, +}; +static const struct inode_operations debugfs_dir_inode_operations = { + .lookup = simple_lookup, + .setattr = debugfs_setattr, +}; +static const struct inode_operations debugfs_symlink_inode_operations = { + .get_link = simple_get_link, + .setattr = debugfs_setattr, +}; + +static struct inode *debugfs_get_inode(struct super_block *sb) +{ + struct inode *inode = new_inode(sb); + if (inode) { + inode->i_ino = get_next_ino(); + simple_inode_init_ts(inode); + } + return inode; +} + +struct debugfs_fs_info { + kuid_t uid; + kgid_t gid; + umode_t mode; + /* Opt_* bitfield. */ + unsigned int opts; +}; + +enum { + Opt_uid, + Opt_gid, + Opt_mode, + Opt_source, +}; + +static const struct fs_parameter_spec debugfs_param_specs[] = { + fsparam_gid ("gid", Opt_gid), + fsparam_u32oct ("mode", Opt_mode), + fsparam_uid ("uid", Opt_uid), + fsparam_string ("source", Opt_source), + {} +}; + +static int debugfs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct debugfs_fs_info *opts = fc->s_fs_info; + struct fs_parse_result result; + int opt; + + opt = fs_parse(fc, debugfs_param_specs, param, &result); + if (opt < 0) { + /* + * We might like to report bad mount options here; but + * traditionally debugfs has ignored all mount options + */ + if (opt == -ENOPARAM) + return 0; + + return opt; + } + + switch (opt) { + case Opt_uid: + opts->uid = result.uid; + break; + case Opt_gid: + opts->gid = result.gid; + break; + case Opt_mode: + opts->mode = result.uint_32 & S_IALLUGO; + break; + case Opt_source: + if (fc->source) + return invalfc(fc, "Multiple sources specified"); + fc->source = param->string; + param->string = NULL; + break; + /* + * We might like to report bad mount options here; + * but traditionally debugfs has ignored all mount options + */ + } + + opts->opts |= BIT(opt); + + return 0; +} + +static void _debugfs_apply_options(struct super_block *sb, bool remount) +{ + struct debugfs_fs_info *fsi = sb->s_fs_info; + struct inode *inode = d_inode(sb->s_root); + + /* + * On remount, only reset mode/uid/gid if they were provided as mount + * options. + */ + + if (!remount || fsi->opts & BIT(Opt_mode)) { + inode->i_mode &= ~S_IALLUGO; + inode->i_mode |= fsi->mode; + } + + if (!remount || fsi->opts & BIT(Opt_uid)) + inode->i_uid = fsi->uid; + + if (!remount || fsi->opts & BIT(Opt_gid)) + inode->i_gid = fsi->gid; +} + +static void debugfs_apply_options(struct super_block *sb) +{ + _debugfs_apply_options(sb, false); +} + +static void debugfs_apply_options_remount(struct super_block *sb) +{ + _debugfs_apply_options(sb, true); +} + +static int debugfs_reconfigure(struct fs_context *fc) +{ + struct super_block *sb = fc->root->d_sb; + struct debugfs_fs_info *sb_opts = sb->s_fs_info; + struct debugfs_fs_info *new_opts = fc->s_fs_info; + + if (!new_opts) + return 0; + + sync_filesystem(sb); + + /* structure copy of new mount options to sb */ + *sb_opts = *new_opts; + debugfs_apply_options_remount(sb); + + return 0; +} + +static int debugfs_show_options(struct seq_file *m, struct dentry *root) +{ + struct debugfs_fs_info *fsi = root->d_sb->s_fs_info; + + if (!uid_eq(fsi->uid, GLOBAL_ROOT_UID)) + seq_printf(m, ",uid=%u", + from_kuid_munged(&init_user_ns, fsi->uid)); + if (!gid_eq(fsi->gid, GLOBAL_ROOT_GID)) + seq_printf(m, ",gid=%u", + from_kgid_munged(&init_user_ns, fsi->gid)); + if (fsi->mode != DEBUGFS_DEFAULT_MODE) + seq_printf(m, ",mode=%o", fsi->mode); + + return 0; +} + +static struct kmem_cache *debugfs_inode_cachep __ro_after_init; + +static void init_once(void *foo) +{ + struct debugfs_inode_info *info = foo; + inode_init_once(&info->vfs_inode); +} + +static struct inode *debugfs_alloc_inode(struct super_block *sb) +{ + struct debugfs_inode_info *info; + info = alloc_inode_sb(sb, debugfs_inode_cachep, GFP_KERNEL); + if (!info) + return NULL; + return &info->vfs_inode; +} + +static void debugfs_free_inode(struct inode *inode) +{ + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); + kmem_cache_free(debugfs_inode_cachep, DEBUGFS_I(inode)); +} + +static const struct super_operations debugfs_super_operations = { + .statfs = simple_statfs, + .show_options = debugfs_show_options, + .alloc_inode = debugfs_alloc_inode, + .free_inode = debugfs_free_inode, +}; + +static void debugfs_release_dentry(struct dentry *dentry) +{ + struct debugfs_fsdata *fsd = dentry->d_fsdata; + + if (fsd) { + WARN_ON(!list_empty(&fsd->cancellations)); + mutex_destroy(&fsd->cancellations_mtx); + } + kfree(fsd); +} + +static struct vfsmount *debugfs_automount(struct path *path) +{ + struct inode *inode = path->dentry->d_inode; + + return DEBUGFS_I(inode)->automount(path->dentry, inode->i_private); +} + +static const struct dentry_operations debugfs_dops = { + .d_release = debugfs_release_dentry, + .d_automount = debugfs_automount, +}; + +static int debugfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + static const struct tree_descr debug_files[] = {{""}}; + int err; + + err = simple_fill_super(sb, DEBUGFS_MAGIC, debug_files); + if (err) + return err; + + sb->s_op = &debugfs_super_operations; + set_default_d_op(sb, &debugfs_dops); + sb->s_d_flags |= DCACHE_DONTCACHE; + + debugfs_apply_options(sb); + + return 0; +} + +static int debugfs_get_tree(struct fs_context *fc) +{ + int err; + + err = get_tree_single(fc, debugfs_fill_super); + if (err) + return err; + + return debugfs_reconfigure(fc); +} + +static void debugfs_free_fc(struct fs_context *fc) +{ + kfree(fc->s_fs_info); +} + +static const struct fs_context_operations debugfs_context_ops = { + .free = debugfs_free_fc, + .parse_param = debugfs_parse_param, + .get_tree = debugfs_get_tree, + .reconfigure = debugfs_reconfigure, +}; + +static int debugfs_init_fs_context(struct fs_context *fc) +{ + struct debugfs_fs_info *fsi; + + fsi = kzalloc_obj(struct debugfs_fs_info); + if (!fsi) + return -ENOMEM; + + fsi->mode = DEBUGFS_DEFAULT_MODE; + + fc->s_fs_info = fsi; + fc->ops = &debugfs_context_ops; + return 0; +} + +static struct file_system_type debug_fs_type = { + .owner = THIS_MODULE, + .name = "debugfs", + .init_fs_context = debugfs_init_fs_context, + .parameters = debugfs_param_specs, + .kill_sb = kill_anon_super, +}; +MODULE_ALIAS_FS("debugfs"); + +/** + * debugfs_lookup() - look up an existing debugfs file + * @name: a pointer to a string containing the name of the file to look up. + * @parent: a pointer to the parent dentry of the file. + * + * This function will return a pointer to a dentry if it succeeds. If the file + * doesn't exist or an error occurs, %NULL will be returned. The returned + * dentry must be passed to dput() when it is no longer needed. + * + * If debugfs is not enabled in the kernel, the value -%ENODEV will be + * returned. + */ +struct dentry *debugfs_lookup(const char *name, struct dentry *parent) +{ + struct dentry *dentry; + + if (!debugfs_initialized() || IS_ERR_OR_NULL(name) || IS_ERR(parent)) + return NULL; + + if (!parent) + parent = debugfs_mount->mnt_root; + + dentry = lookup_noperm_positive_unlocked(&QSTR(name), parent); + if (IS_ERR(dentry)) + return NULL; + return dentry; +} +EXPORT_SYMBOL_GPL(debugfs_lookup); + +static struct dentry *debugfs_start_creating(const char *name, + struct dentry *parent) +{ + struct dentry *dentry; + int error; + + if (!debugfs_enabled) + return ERR_PTR(-EPERM); + + if (!debugfs_initialized()) + return ERR_PTR(-ENOENT); + + pr_debug("creating file '%s'\n", name); + + if (IS_ERR(parent)) + return parent; + + error = simple_pin_fs(&debug_fs_type, &debugfs_mount, + &debugfs_mount_count); + if (error) { + pr_err("Unable to pin filesystem for file '%s'\n", name); + return ERR_PTR(error); + } + + /* If the parent is not specified, we create it in the root. + * We need the root dentry to do this, which is in the super + * block. A pointer to that is in the struct vfsmount that we + * have around. + */ + if (!parent) + parent = debugfs_mount->mnt_root; + + dentry = simple_start_creating(parent, name); + if (IS_ERR(dentry)) { + if (dentry == ERR_PTR(-EEXIST)) + pr_err("'%s' already exists in '%pd'\n", name, parent); + simple_release_fs(&debugfs_mount, &debugfs_mount_count); + } + return dentry; +} + +static struct dentry *debugfs_failed_creating(struct dentry *dentry) +{ + simple_done_creating(dentry); + simple_release_fs(&debugfs_mount, &debugfs_mount_count); + return ERR_PTR(-ENOMEM); +} + +static struct dentry *debugfs_end_creating(struct dentry *dentry) +{ + simple_done_creating(dentry); + return dentry; // borrowed +} + +static struct dentry *__debugfs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const void *aux, + const struct file_operations *proxy_fops, + const void *real_fops) +{ + struct dentry *dentry; + struct inode *inode; + + if (!(mode & S_IFMT)) + mode |= S_IFREG; + BUG_ON(!S_ISREG(mode)); + dentry = debugfs_start_creating(name, parent); + + if (IS_ERR(dentry)) + return dentry; + + inode = debugfs_get_inode(dentry->d_sb); + if (unlikely(!inode)) { + pr_err("out of free dentries, can not create file '%s'\n", + name); + return debugfs_failed_creating(dentry); + } + + inode->i_mode = mode; + inode->i_private = data; + + inode->i_op = &debugfs_file_inode_operations; + if (!real_fops) + proxy_fops = &debugfs_noop_file_operations; + inode->i_fop = proxy_fops; + DEBUGFS_I(inode)->raw = real_fops; + DEBUGFS_I(inode)->aux = (void *)aux; + + d_make_persistent(dentry, inode); + fsnotify_create(d_inode(dentry->d_parent), dentry); + return debugfs_end_creating(dentry); +} + +struct dentry *debugfs_create_file_full(const char *name, umode_t mode, + struct dentry *parent, void *data, + const void *aux, + const struct file_operations *fops) +{ + return __debugfs_create_file(name, mode, parent, data, aux, + &debugfs_full_proxy_file_operations, + fops); +} +EXPORT_SYMBOL_GPL(debugfs_create_file_full); + +struct dentry *debugfs_create_file_short(const char *name, umode_t mode, + struct dentry *parent, void *data, + const void *aux, + const struct debugfs_short_fops *fops) +{ + return __debugfs_create_file(name, mode, parent, data, aux, + &debugfs_full_short_proxy_file_operations, + fops); +} +EXPORT_SYMBOL_GPL(debugfs_create_file_short); + +/** + * debugfs_create_file_unsafe - create a file in the debugfs filesystem + * @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. + * @data: a pointer to something that the caller will want to get to later + * on. The inode.i_private pointer will point to this value on + * the open() call. + * @fops: a pointer to a struct file_operations that should be used for + * this file. + * + * debugfs_create_file_unsafe() is completely analogous to + * debugfs_create_file(), the only difference being that the fops + * handed it will not get protected against file removals by the + * debugfs core. + * + * It is your responsibility to protect your struct file_operation + * methods against file removals by means of debugfs_file_get() + * and debugfs_file_put(). ->open() is still protected by + * debugfs though. + * + * Any struct file_operations defined by means of + * DEFINE_DEBUGFS_ATTRIBUTE() is protected against file removals and + * thus, may be used here. + */ +struct dentry *debugfs_create_file_unsafe(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + + return __debugfs_create_file(name, mode, parent, data, NULL, + &debugfs_open_proxy_file_operations, + fops); +} +EXPORT_SYMBOL_GPL(debugfs_create_file_unsafe); + +/** + * debugfs_create_file_size - create a file in the debugfs filesystem + * @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. + * @data: a pointer to something that the caller will want to get to later + * on. The inode.i_private pointer will point to this value on + * the open() call. + * @fops: a pointer to a struct file_operations that should be used for + * this file. + * @file_size: initial file size + * + * This is the basic "create a file" function for debugfs. It allows for a + * wide range of flexibility in creating a file, or a directory (if you want + * to create a directory, the debugfs_create_dir() function is + * recommended to be used instead.) + */ +void debugfs_create_file_size(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + loff_t file_size) +{ + struct dentry *de = debugfs_create_file(name, mode, parent, data, fops); + + if (!IS_ERR(de)) + d_inode(de)->i_size = file_size; +} +EXPORT_SYMBOL_GPL(debugfs_create_file_size); + +/** + * debugfs_create_dir - create a directory in the debugfs filesystem + * @name: a pointer to a string containing the name of the directory to + * create. + * @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 + * directory will be created in the root of the debugfs filesystem. + * + * This function creates a directory in debugfs with the given name. + * + * 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, ERR_PTR(-ERROR) will be + * returned. + * + * If debugfs is not enabled in the kernel, the value -%ENODEV will be + * returned. + * + * NOTE: it's expected that most callers should _ignore_ the errors returned + * by this function. Other debugfs functions handle the fact that the "dentry" + * passed to them could be an error and they don't crash in that case. + * Drivers should generally work fine even if debugfs fails to init anyway. + */ +struct dentry *debugfs_create_dir(const char *name, struct dentry *parent) +{ + struct dentry *dentry = debugfs_start_creating(name, parent); + struct inode *inode; + + if (IS_ERR(dentry)) + return dentry; + + inode = debugfs_get_inode(dentry->d_sb); + if (unlikely(!inode)) { + pr_err("out of free dentries, can not create directory '%s'\n", + name); + return debugfs_failed_creating(dentry); + } + + inode->i_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; + inode->i_op = &debugfs_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + d_make_persistent(dentry, inode); + inc_nlink(d_inode(dentry->d_parent)); + fsnotify_mkdir(d_inode(dentry->d_parent), dentry); + return debugfs_end_creating(dentry); +} +EXPORT_SYMBOL_GPL(debugfs_create_dir); + +/** + * debugfs_create_automount - create automount point in the debugfs filesystem + * @name: a pointer to a string containing the name of the file to create. + * @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. + * @f: function to be called when pathname resolution steps on that one. + * @data: opaque argument to pass to f(). + * + * @f should return what ->d_automount() would. + */ +struct dentry *debugfs_create_automount(const char *name, + struct dentry *parent, + debugfs_automount_t f, + void *data) +{ + struct dentry *dentry = debugfs_start_creating(name, parent); + struct inode *inode; + + if (IS_ERR(dentry)) + return dentry; + + inode = debugfs_get_inode(dentry->d_sb); + if (unlikely(!inode)) { + pr_err("out of free dentries, can not create automount '%s'\n", + name); + return debugfs_failed_creating(dentry); + } + + make_empty_dir_inode(inode); + inode->i_flags |= S_AUTOMOUNT; + inode->i_private = data; + DEBUGFS_I(inode)->automount = f; + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + d_make_persistent(dentry, inode); + inc_nlink(d_inode(dentry->d_parent)); + fsnotify_mkdir(d_inode(dentry->d_parent), dentry); + return debugfs_end_creating(dentry); +} +EXPORT_SYMBOL(debugfs_create_automount); + +/** + * debugfs_create_symlink- create a symbolic link in the debugfs filesystem + * @name: a pointer to a string containing the name of the symbolic link to + * create. + * @parent: a pointer to the parent dentry for this symbolic link. This + * should be a directory dentry if set. If this parameter is NULL, + * then the symbolic link will be created in the root of the debugfs + * filesystem. + * @target: a pointer to a string containing the path to the target of the + * symbolic link. + * + * This function creates a symbolic link with the given name in debugfs that + * links to the given target path. + * + * This function will return a pointer to a dentry if it succeeds. This + * pointer must be passed to the debugfs_remove() function when the symbolic + * link is to be removed (no automatic cleanup happens if your module is + * unloaded, you are responsible here.) If an error occurs, ERR_PTR(-ERROR) + * will be returned. + * + * If debugfs is not enabled in the kernel, the value -%ENODEV will be + * returned. + */ +struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent, + const char *target) +{ + struct dentry *dentry; + struct inode *inode; + char *link = kstrdup(target, GFP_KERNEL); + if (!link) + return ERR_PTR(-ENOMEM); + + dentry = debugfs_start_creating(name, parent); + if (IS_ERR(dentry)) { + kfree(link); + return dentry; + } + + inode = debugfs_get_inode(dentry->d_sb); + if (unlikely(!inode)) { + pr_err("out of free dentries, can not create symlink '%s'\n", + name); + kfree(link); + return debugfs_failed_creating(dentry); + } + inode->i_mode = S_IFLNK | S_IRWXUGO; + inode->i_op = &debugfs_symlink_inode_operations; + inode->i_link = link; + d_make_persistent(dentry, inode); + return debugfs_end_creating(dentry); +} +EXPORT_SYMBOL_GPL(debugfs_create_symlink); + +static void __debugfs_file_removed(struct dentry *dentry) +{ + struct debugfs_fsdata *fsd; + + /* + * Paired with the closing smp_mb() implied by a successful + * cmpxchg() in debugfs_file_get(): either + * debugfs_file_get() must see a dead dentry or we must see a + * debugfs_fsdata instance at ->d_fsdata here (or both). + */ + smp_mb(); + fsd = READ_ONCE(dentry->d_fsdata); + if (!fsd) + return; + + /* if this was the last reference, we're done */ + if (refcount_dec_and_test(&fsd->active_users)) + return; + + /* + * If there's still a reference, the code that obtained it can + * be in different states: + * - The common case of not using cancellations, or already + * after debugfs_leave_cancellation(), where we just need + * to wait for debugfs_file_put() which signals the completion; + * - inside a cancellation section, i.e. between + * debugfs_enter_cancellation() and debugfs_leave_cancellation(), + * in which case we need to trigger the ->cancel() function, + * and then wait for debugfs_file_put() just like in the + * previous case; + * - before debugfs_enter_cancellation() (but obviously after + * debugfs_file_get()), in which case we may not see the + * cancellation in the list on the first round of the loop, + * but debugfs_enter_cancellation() signals the completion + * after adding it, so this code gets woken up to call the + * ->cancel() function. + */ + while (refcount_read(&fsd->active_users)) { + struct debugfs_cancellation *c; + + /* + * Lock the cancellations. Note that the cancellations + * structs are meant to be on the stack, so we need to + * ensure we either use them here or don't touch them, + * and debugfs_leave_cancellation() will wait for this + * to be finished processing before exiting one. It may + * of course win and remove the cancellation, but then + * chances are we never even got into this bit, we only + * do if the refcount isn't zero already. + */ + mutex_lock(&fsd->cancellations_mtx); + while ((c = list_first_entry_or_null(&fsd->cancellations, + typeof(*c), list))) { + list_del_init(&c->list); + c->cancel(dentry, c->cancel_data); + } + mutex_unlock(&fsd->cancellations_mtx); + + wait_for_completion(&fsd->active_users_drained); + } +} + +static void remove_one(struct dentry *victim) +{ + if (d_is_reg(victim)) + __debugfs_file_removed(victim); + simple_release_fs(&debugfs_mount, &debugfs_mount_count); +} + +/** + * debugfs_remove - recursively removes a directory + * @dentry: a pointer to a the dentry of the directory to be removed. If this + * parameter is NULL or an error value, nothing will be done. + * + * This function recursively removes a directory tree in debugfs that + * was previously created with a call to another debugfs function + * (like debugfs_create_file() or variants thereof.) + * + * This function is required to be called in order for the file to be + * removed, no automatic cleanup of files will happen when a module is + * removed, you are responsible here. + */ +void debugfs_remove(struct dentry *dentry) +{ + if (IS_ERR_OR_NULL(dentry)) + return; + + simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); + simple_recursive_removal(dentry, remove_one); + simple_release_fs(&debugfs_mount, &debugfs_mount_count); +} +EXPORT_SYMBOL_GPL(debugfs_remove); + +/** + * debugfs_lookup_and_remove - lookup a directory or file and recursively remove it + * @name: a pointer to a string containing the name of the item to look up. + * @parent: a pointer to the parent dentry of the item. + * + * This is the equlivant of doing something like + * debugfs_remove(debugfs_lookup(..)) but with the proper reference counting + * handled for the directory being looked up. + */ +void debugfs_lookup_and_remove(const char *name, struct dentry *parent) +{ + struct dentry *dentry; + + dentry = debugfs_lookup(name, parent); + if (!dentry) + return; + + debugfs_remove(dentry); + dput(dentry); +} +EXPORT_SYMBOL_GPL(debugfs_lookup_and_remove); + +/** + * debugfs_change_name - rename a file/directory in the debugfs filesystem + * @dentry: dentry of an object to be renamed. + * @fmt: format for new name + * + * This function renames a file/directory in debugfs. The target must not + * exist for rename to succeed. + * + * This function will return 0 on success and -E... on failure. + * + * If debugfs is not enabled in the kernel, the value -%ENODEV will be + * returned. + */ +int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, ...) +{ + int error = 0; + const char *new_name; + struct name_snapshot old_name; + struct dentry *target; + struct renamedata rd = {}; + struct inode *dir; + va_list ap; + + if (IS_ERR_OR_NULL(dentry)) + return 0; + + va_start(ap, fmt); + new_name = kvasprintf_const(GFP_KERNEL, fmt, ap); + va_end(ap); + if (!new_name) + return -ENOMEM; + + rd.old_parent = dget_parent(dentry); + rd.new_parent = rd.old_parent; + rd.flags = RENAME_NOREPLACE; + target = lookup_noperm_unlocked(&QSTR(new_name), rd.new_parent); + if (IS_ERR(target)) { + error = PTR_ERR(target); + goto out_free; + } + + error = start_renaming_two_dentries(&rd, dentry, target); + if (error) { + if (error == -EEXIST && target == dentry) + /* it isn't an error to rename a thing to itself */ + error = 0; + goto out; + } + + dir = d_inode(rd.old_parent); + take_dentry_name_snapshot(&old_name, dentry); + simple_rename_timestamp(dir, dentry, dir, rd.new_dentry); + d_move(dentry, rd.new_dentry); + fsnotify_move(dir, dir, &old_name.name, d_is_dir(dentry), NULL, dentry); + release_dentry_name_snapshot(&old_name); + end_renaming(&rd); +out: + dput(rd.old_parent); + dput(target); +out_free: + kfree_const(new_name); + return error; +} +EXPORT_SYMBOL_GPL(debugfs_change_name); + +/** + * debugfs_initialized - Tells whether debugfs has been registered + */ +bool debugfs_initialized(void) +{ + return debugfs_registered; +} +EXPORT_SYMBOL_GPL(debugfs_initialized); + +static int __init debugfs_kernel(char *str) +{ + if (str) { + if (!strcmp(str, "on")) + debugfs_enabled = true; + else if (!strcmp(str, "off")) + debugfs_enabled = false; + else if (!strcmp(str, "no-mount")) { + pr_notice("debugfs=no-mount is a deprecated alias " + "for debugfs=off\n"); + debugfs_enabled = false; + } + } + + return 0; +} +early_param("debugfs", debugfs_kernel); + +static int __init debugfs_init(void) +{ + int retval; + + if (!debugfs_enabled) + return -EPERM; + + retval = sysfs_create_mount_point(kernel_kobj, "debug"); + if (retval) + return retval; + + debugfs_inode_cachep = kmem_cache_create("debugfs_inode_cache", + sizeof(struct debugfs_inode_info), 0, + SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT, + init_once); + if (debugfs_inode_cachep == NULL) { + sysfs_remove_mount_point(kernel_kobj, "debug"); + return -ENOMEM; + } + + retval = register_filesystem(&debug_fs_type); + if (retval) { // Really not going to happen + sysfs_remove_mount_point(kernel_kobj, "debug"); + kmem_cache_destroy(debugfs_inode_cachep); + return retval; + } + debugfs_registered = true; + return 0; +} +core_initcall(debugfs_init); diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h new file mode 100644 index 000000000..c95699b27 --- /dev/null +++ b/fs/debugfs/internal.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * internal.h - declarations internal to debugfs + * + * Copyright (C) 2016 Nicolai Stange <nicstange@gmail.com> + */ + +#ifndef _DEBUGFS_INTERNAL_H_ +#define _DEBUGFS_INTERNAL_H_ +#include <linux/list.h> + +struct file_operations; + +struct debugfs_inode_info { + struct inode vfs_inode; + union { + const void *raw; + const struct file_operations *real_fops; + const struct debugfs_short_fops *short_fops; + debugfs_automount_t automount; + }; + void *aux; +}; + +static inline struct debugfs_inode_info *DEBUGFS_I(struct inode *inode) +{ + return container_of(inode, struct debugfs_inode_info, vfs_inode); +} + +/* declared over in file.c */ +extern const struct file_operations debugfs_noop_file_operations; +extern const struct file_operations debugfs_open_proxy_file_operations; +extern const struct file_operations debugfs_full_proxy_file_operations; +extern const struct file_operations debugfs_full_short_proxy_file_operations; + +struct debugfs_fsdata { + const struct file_operations *real_fops; + const struct debugfs_short_fops *short_fops; + struct { + refcount_t active_users; + struct completion active_users_drained; + + /* protect cancellations */ + struct mutex cancellations_mtx; + struct list_head cancellations; + unsigned int methods; + }; +}; + +enum { + HAS_READ = 1, + HAS_WRITE = 2, + HAS_LSEEK = 4, + HAS_POLL = 8, + HAS_IOCTL = 16 +}; + +#endif /* _DEBUGFS_INTERNAL_H_ */ |
