Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a758e489 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "debugfs: defer debugfs_fsdata allocation to first usage"

parents cab01d80 5455f7b4
Loading
Loading
Loading
Loading
+10 −10
Original line number Diff line number Diff line
@@ -67,13 +67,13 @@ static ssize_t hfi1_seq_read(
	loff_t *ppos)
{
	struct dentry *d = file->f_path.dentry;
	int srcu_idx;
	ssize_t r;

	r = debugfs_use_file_start(d, &srcu_idx);
	if (likely(!r))
	r = debugfs_file_get(d);
	if (unlikely(r))
		return r;
	r = seq_read(file, buf, size, ppos);
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(d);
	return r;
}

@@ -83,13 +83,13 @@ static loff_t hfi1_seq_lseek(
	int whence)
{
	struct dentry *d = file->f_path.dentry;
	int srcu_idx;
	loff_t r;

	r = debugfs_use_file_start(d, &srcu_idx);
	if (likely(!r))
	r = debugfs_file_get(d);
	if (unlikely(r))
		return r;
	r = seq_lseek(file, offset, whence);
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(d);
	return r;
}

+125 −85
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@
#include <linux/slab.h>
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/srcu.h>
#include <asm/poll.h>

#include "internal.h"
@@ -48,66 +47,108 @@ const struct file_operations debugfs_noop_file_operations = {
	.llseek =	noop_llseek,
};

#define F_DENTRY(filp) ((filp)->f_path.dentry)

const struct file_operations *debugfs_real_fops(const struct file *filp)
{
	struct debugfs_fsdata *fsd = F_DENTRY(filp)->d_fsdata;

	if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT) {
		/*
		 * Urgh, we've been called w/o a protecting
		 * debugfs_file_get().
		 */
		WARN_ON(1);
		return NULL;
	}

	return fsd->real_fops;
}
EXPORT_SYMBOL_GPL(debugfs_real_fops);

/**
 * debugfs_use_file_start - mark the beginning of file data access
 * debugfs_file_get - mark the beginning of file data access
 * @dentry: the dentry object whose data is being accessed.
 * @srcu_idx: a pointer to some memory to store a SRCU index in.
 *
 * Up to a matching call to debugfs_use_file_finish(), any
 * successive call into the file removing functions debugfs_remove()
 * and debugfs_remove_recursive() will block. Since associated private
 * 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_use_file_start() without worrying about
 * lifetime issues.
 * 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.
 *
 * Regardless of the return code, any call to
 * debugfs_use_file_start() must be followed by a matching call
 * to debugfs_use_file_finish().
 */
int debugfs_use_file_start(const struct dentry *dentry, int *srcu_idx)
	__acquires(&debugfs_srcu)
int debugfs_file_get(struct dentry *dentry)
{
	*srcu_idx = srcu_read_lock(&debugfs_srcu);
	barrier();
	struct debugfs_fsdata *fsd;
	void *d_fsd;

	d_fsd = READ_ONCE(dentry->d_fsdata);
	if (!((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)) {
		fsd = d_fsd;
	} else {
		fsd = kmalloc(sizeof(*fsd), GFP_KERNEL);
		if (!fsd)
			return -ENOMEM;

		fsd->real_fops = (void *)((unsigned long)d_fsd &
					~DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
		refcount_set(&fsd->active_users, 1);
		init_completion(&fsd->active_users_drained);
		if (cmpxchg(&dentry->d_fsdata, d_fsd, fsd) != d_fsd) {
			kfree(fsd);
			fsd = READ_ONCE(dentry->d_fsdata);
		}
	}

	/*
	 * 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;
}
EXPORT_SYMBOL_GPL(debugfs_use_file_start);
EXPORT_SYMBOL_GPL(debugfs_file_get);

/**
 * debugfs_use_file_finish - mark the end of file data access
 * @srcu_idx: the SRCU index "created" by a former call to
 *            debugfs_use_file_start().
 * 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_use_file_start() to proceed and return to its caller.
 * debugfs_file_get() to proceed and return to its caller.
 */
void debugfs_use_file_finish(int srcu_idx) __releases(&debugfs_srcu)
void debugfs_file_put(struct dentry *dentry)
{
	srcu_read_unlock(&debugfs_srcu, srcu_idx);
}
EXPORT_SYMBOL_GPL(debugfs_use_file_finish);
	struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata);

#define F_DENTRY(filp) ((filp)->f_path.dentry)
	if (refcount_dec_and_test(&fsd->active_users))
		complete(&fsd->active_users_drained);
}
EXPORT_SYMBOL_GPL(debugfs_file_put);

static int open_proxy_open(struct inode *inode, struct file *filp)
{
	const struct dentry *dentry = F_DENTRY(filp);
	struct dentry *dentry = F_DENTRY(filp);
	const struct file_operations *real_fops = NULL;
	int srcu_idx, r;
	int r;

	r = debugfs_use_file_start(dentry, &srcu_idx);
	if (r) {
		r = -ENOENT;
		goto out;
	}
	r = debugfs_file_get(dentry);
	if (r)
		return r == -EIO ? -ENOENT : r;

	real_fops = debugfs_real_fops(filp);
	real_fops = fops_get(real_fops);
@@ -124,7 +165,7 @@ static int open_proxy_open(struct inode *inode, struct file *filp)
		r = real_fops->open(inode, filp);

out:
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(dentry);
	return r;
}

@@ -138,16 +179,16 @@ const struct file_operations debugfs_open_proxy_file_operations = {
#define FULL_PROXY_FUNC(name, ret_type, filp, proto, args)		\
static ret_type full_proxy_ ## name(proto)				\
{									\
	const struct dentry *dentry = F_DENTRY(filp);			\
	const struct file_operations *real_fops =			\
		debugfs_real_fops(filp);				\
	int srcu_idx;							\
	struct dentry *dentry = F_DENTRY(filp);			\
	const struct file_operations *real_fops;			\
	ret_type r;							\
									\
	r = debugfs_use_file_start(dentry, &srcu_idx);			\
	if (likely(!r))						\
	r = debugfs_file_get(dentry);					\
	if (unlikely(r))						\
		return r;						\
	real_fops = debugfs_real_fops(filp);				\
	r = real_fops->name(args);					\
	debugfs_use_file_finish(srcu_idx);				\
	debugfs_file_put(dentry);					\
	return r;							\
}

@@ -172,18 +213,16 @@ FULL_PROXY_FUNC(unlocked_ioctl, long, filp,
static unsigned int full_proxy_poll(struct file *filp,
				struct poll_table_struct *wait)
{
	const struct dentry *dentry = F_DENTRY(filp);
	const struct file_operations *real_fops = debugfs_real_fops(filp);
	int srcu_idx;
	struct dentry *dentry = F_DENTRY(filp);
	unsigned int r = 0;
	const struct file_operations *real_fops;

	if (debugfs_use_file_start(dentry, &srcu_idx)) {
		debugfs_use_file_finish(srcu_idx);
	if (debugfs_file_get(dentry))
		return POLLHUP;
	}

	real_fops = debugfs_real_fops(filp);
	r = real_fops->poll(filp, wait);
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(dentry);
	return r;
}

@@ -227,16 +266,14 @@ static void __full_proxy_fops_init(struct file_operations *proxy_fops,

static int full_proxy_open(struct inode *inode, struct file *filp)
{
	const struct dentry *dentry = F_DENTRY(filp);
	struct dentry *dentry = F_DENTRY(filp);
	const struct file_operations *real_fops = NULL;
	struct file_operations *proxy_fops = NULL;
	int srcu_idx, r;
	int r;

	r = debugfs_use_file_start(dentry, &srcu_idx);
	if (r) {
		r = -ENOENT;
		goto out;
	}
	r = debugfs_file_get(dentry);
	if (r)
		return r == -EIO ? -ENOENT : r;

	real_fops = debugfs_real_fops(filp);
	real_fops = fops_get(real_fops);
@@ -274,7 +311,7 @@ static int full_proxy_open(struct inode *inode, struct file *filp)
	kfree(proxy_fops);
	fops_put(real_fops);
out:
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(dentry);
	return r;
}

@@ -285,13 +322,14 @@ const struct file_operations debugfs_full_proxy_file_operations = {
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;
	int srcu_idx;

	ret = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
	if (likely(!ret))
	ret = debugfs_file_get(dentry);
	if (unlikely(ret))
		return ret;
	ret = simple_attr_read(file, buf, len, ppos);
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(dentry);
	return ret;
}
EXPORT_SYMBOL_GPL(debugfs_attr_read);
@@ -299,13 +337,14 @@ EXPORT_SYMBOL_GPL(debugfs_attr_read);
ssize_t debugfs_attr_write(struct file *file, const char __user *buf,
			 size_t len, loff_t *ppos)
{
	struct dentry *dentry = F_DENTRY(file);
	ssize_t ret;
	int srcu_idx;

	ret = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
	if (likely(!ret))
	ret = debugfs_file_get(dentry);
	if (unlikely(ret))
		return ret;
	ret = simple_attr_write(file, buf, len, ppos);
	debugfs_use_file_finish(srcu_idx);
	debugfs_file_put(dentry);
	return ret;
}
EXPORT_SYMBOL_GPL(debugfs_attr_write);
@@ -739,14 +778,14 @@ ssize_t debugfs_read_file_bool(struct file *file, char __user *user_buf,
{
	char buf[3];
	bool val;
	int r, srcu_idx;
	int r;
	struct dentry *dentry = F_DENTRY(file);

	r = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
	if (likely(!r))
		val = *(bool *)file->private_data;
	debugfs_use_file_finish(srcu_idx);
	if (r)
	r = debugfs_file_get(dentry);
	if (unlikely(r))
		return r;
	val = *(bool *)file->private_data;
	debugfs_file_put(dentry);

	if (val)
		buf[0] = 'Y';
@@ -764,8 +803,9 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
	char buf[32];
	size_t buf_size;
	bool bv;
	int r, srcu_idx;
	int r;
	bool *val = file->private_data;
	struct dentry *dentry = F_DENTRY(file);

	buf_size = min(count, (sizeof(buf)-1));
	if (copy_from_user(buf, user_buf, buf_size))
@@ -773,12 +813,11 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,

	buf[buf_size] = '\0';
	if (strtobool(buf, &bv) == 0) {
		r = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
		if (likely(!r))
			*val = bv;
		debugfs_use_file_finish(srcu_idx);
		if (r)
		r = debugfs_file_get(dentry);
		if (unlikely(r))
			return r;
		*val = bv;
		debugfs_file_put(dentry);
	}

	return count;
@@ -840,14 +879,15 @@ 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;
	int srcu_idx;

	r = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
	if (likely(!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_use_file_finish(srcu_idx);
	debugfs_file_put(dentry);
	return r;
}

+42 −14
Original line number Diff line number Diff line
@@ -27,14 +27,11 @@
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <linux/srcu.h>

#include "internal.h"

#define DEBUGFS_DEFAULT_MODE	0700

DEFINE_SRCU(debugfs_srcu);

static struct vfsmount *debugfs_mount;
static int debugfs_mount_count;
static bool debugfs_registered;
@@ -185,6 +182,14 @@ static const struct super_operations debugfs_super_operations = {
	.evict_inode	= debugfs_evict_inode,
};

static void debugfs_release_dentry(struct dentry *dentry)
{
	void *fsd = dentry->d_fsdata;

	if (!((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))
		kfree(dentry->d_fsdata);
}

static struct vfsmount *debugfs_automount(struct path *path)
{
	debugfs_automount_t f;
@@ -194,6 +199,7 @@ static struct vfsmount *debugfs_automount(struct path *path)

static const struct dentry_operations debugfs_dops = {
	.d_delete = always_delete_dentry,
	.d_release = debugfs_release_dentry,
	.d_automount = debugfs_automount,
};

@@ -324,7 +330,8 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
	inode->i_private = data;

	inode->i_fop = proxy_fops;
	dentry->d_fsdata = (void *)real_fops;
	dentry->d_fsdata = (void *)((unsigned long)real_fops |
				DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);

	d_instantiate(dentry, inode);
	fsnotify_create(d_inode(dentry->d_parent), dentry);
@@ -581,18 +588,43 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent,
}
EXPORT_SYMBOL_GPL(debugfs_create_symlink);

static void __debugfs_remove_file(struct dentry *dentry, struct dentry *parent)
{
	struct debugfs_fsdata *fsd;

	simple_unlink(d_inode(parent), dentry);
	d_delete(dentry);

	/*
	 * 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 ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
		return;
	if (!refcount_dec_and_test(&fsd->active_users))
		wait_for_completion(&fsd->active_users_drained);
}

static int __debugfs_remove(struct dentry *dentry, struct dentry *parent)
{
	int ret = 0;

	if (simple_positive(dentry)) {
		dget(dentry);
		if (!d_is_reg(dentry)) {
			if (d_is_dir(dentry))
				ret = simple_rmdir(d_inode(parent), dentry);
			else
				simple_unlink(d_inode(parent), dentry);
			if (!ret)
				d_delete(dentry);
		} else {
			__debugfs_remove_file(dentry, parent);
		}
		dput(dentry);
	}
	return ret;
@@ -626,8 +658,6 @@ void debugfs_remove(struct dentry *dentry)
	inode_unlock(d_inode(parent));
	if (!ret)
		simple_release_fs(&debugfs_mount, &debugfs_mount_count);

	synchronize_srcu(&debugfs_srcu);
}
EXPORT_SYMBOL_GPL(debugfs_remove);

@@ -701,8 +731,6 @@ void debugfs_remove_recursive(struct dentry *dentry)
	if (!__debugfs_remove(child, parent))
		simple_release_fs(&debugfs_mount, &debugfs_mount_count);
	inode_unlock(d_inode(parent));

	synchronize_srcu(&debugfs_srcu);
}
EXPORT_SYMBOL_GPL(debugfs_remove_recursive);

+16 −0
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@
#ifndef _DEBUGFS_INTERNAL_H_
#define _DEBUGFS_INTERNAL_H_

#include <linux/refcount.h>

struct file_operations;

/* declared over in file.c */
@@ -19,4 +21,18 @@ 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;

struct debugfs_fsdata {
	const struct file_operations *real_fops;
	refcount_t active_users;
	struct completion active_users_drained;
};

/*
 * A dentry's ->d_fsdata either points to the real fops or to a
 * dynamically allocated debugfs_fsdata instance.
 * In order to distinguish between these two cases, a real fops
 * pointer gets its lowest bit set.
 */
#define DEBUGFS_FSDATA_IS_REAL_FOPS_BIT BIT(0)

#endif /* _DEBUGFS_INTERNAL_H_ */
+5 −28
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@

struct device;
struct file_operations;
struct srcu_struct;

struct debugfs_blob_wrapper {
	void *data;
@@ -43,25 +42,6 @@ struct debugfs_regset32 {

extern struct dentry *arch_debugfs_dir;

extern struct srcu_struct debugfs_srcu;

/**
 * debugfs_real_fops - getter for the real file operation
 * @filp: a pointer to a struct file
 *
 * Must only be called under the protection established by
 * debugfs_use_file_start().
 */
static inline const struct file_operations *debugfs_real_fops(struct file *filp)
	__must_hold(&debugfs_srcu)
{
	/*
	 * Neither the pointer to the struct file_operations, nor its
	 * contents ever change -- srcu_dereference() is not needed here.
	 */
	return filp->f_path.dentry->d_fsdata;
}

#define DEFINE_DEBUGFS_ATTRIBUTE(__fops, __get, __set, __fmt)		\
static int __fops ## _open(struct inode *inode, struct file *file)	\
{									\
@@ -105,10 +85,10 @@ struct dentry *debugfs_create_automount(const char *name,
void debugfs_remove(struct dentry *dentry);
void debugfs_remove_recursive(struct dentry *dentry);

int debugfs_use_file_start(const struct dentry *dentry, int *srcu_idx)
	__acquires(&debugfs_srcu);
const struct file_operations *debugfs_real_fops(const struct file *filp);

void debugfs_use_file_finish(int srcu_idx) __releases(&debugfs_srcu);
int debugfs_file_get(struct dentry *dentry);
void debugfs_file_put(struct dentry *dentry);

ssize_t debugfs_attr_read(struct file *file, char __user *buf,
			size_t len, loff_t *ppos);
@@ -223,15 +203,12 @@ static inline void debugfs_remove(struct dentry *dentry)
static inline void debugfs_remove_recursive(struct dentry *dentry)
{ }

static inline int debugfs_use_file_start(const struct dentry *dentry,
					int *srcu_idx)
	__acquires(&debugfs_srcu)
static inline int debugfs_file_get(struct dentry *dentry)
{
	return 0;
}

static inline void debugfs_use_file_finish(int srcu_idx)
	__releases(&debugfs_srcu)
static inline void debugfs_file_put(struct dentry *dentry)
{ }

static inline ssize_t debugfs_attr_read(struct file *file, char __user *buf,
Loading