Driver core patches for 4.15-rc1
Here is the set of driver core / debugfs patches for 4.15-rc1. Not many here, mostly all are debugfs fixes to resolve some long-reported problems with files going away with references to them in userspace. There's also some SPDX cleanups for the debugfs code, as well as a few other minor driver core changes for issues reported by people. All of these have been in linux-next for a week or more with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCWg2NCA8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ymUNgCfYq434CFh+YtwITBNYdqkFYFf0ZAAn3qfhh2+ M3rmZzwk2MKBvNQ2npvt =/8+Y -----END PGP SIGNATURE----- Merge tag 'driver-core-4.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core updates from Greg KH: "Here is the set of driver core / debugfs patches for 4.15-rc1. Not many here, mostly all are debugfs fixes to resolve some long-reported problems with files going away with references to them in userspace. There's also some SPDX cleanups for the debugfs code, as well as a few other minor driver core changes for issues reported by people. All of these have been in linux-next for a week or more with no reported issues" * tag 'driver-core-4.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: driver core: Fix device link deferred probe debugfs: Remove redundant license text debugfs: add SPDX identifiers to all debugfs files debugfs: defer debugfs_fsdata allocation to first usage debugfs: call debugfs_real_fops() only after debugfs_file_get() debugfs: purge obsolete SRCU based removal protection IB/hfi1: convert to debugfs_file_get() and -put() debugfs: convert to debugfs_file_get() and -put() debugfs: debugfs_real_fops(): drop __must_hold sparse annotation debugfs: implement per-file removal protection debugfs: add support for more elaborate ->d_fsdata driver core: Move device_links_purge() after bus_remove_device() arch_topology: Fix section miss match warning due to free_raw_capacity() driver-core: pr_err() strings should end with newlines
This commit is contained in:
commit
b9743042b3
|
@ -105,7 +105,7 @@ subsys_initcall(register_cpu_capacity_sysctl);
|
||||||
static u32 capacity_scale;
|
static u32 capacity_scale;
|
||||||
static u32 *raw_capacity;
|
static u32 *raw_capacity;
|
||||||
|
|
||||||
static int __init free_raw_capacity(void)
|
static int free_raw_capacity(void)
|
||||||
{
|
{
|
||||||
kfree(raw_capacity);
|
kfree(raw_capacity);
|
||||||
raw_capacity = NULL;
|
raw_capacity = NULL;
|
||||||
|
|
|
@ -1958,7 +1958,6 @@ void device_del(struct device *dev)
|
||||||
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
|
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
|
||||||
BUS_NOTIFY_DEL_DEVICE, dev);
|
BUS_NOTIFY_DEL_DEVICE, dev);
|
||||||
|
|
||||||
device_links_purge(dev);
|
|
||||||
dpm_sysfs_remove(dev);
|
dpm_sysfs_remove(dev);
|
||||||
if (parent)
|
if (parent)
|
||||||
klist_del(&dev->p->knode_parent);
|
klist_del(&dev->p->knode_parent);
|
||||||
|
@ -1986,6 +1985,7 @@ void device_del(struct device *dev)
|
||||||
device_pm_remove(dev);
|
device_pm_remove(dev);
|
||||||
driver_deferred_probe_del(dev);
|
driver_deferred_probe_del(dev);
|
||||||
device_remove_properties(dev);
|
device_remove_properties(dev);
|
||||||
|
device_links_purge(dev);
|
||||||
|
|
||||||
/* Notify the platform of the removal, in case they
|
/* Notify the platform of the removal, in case they
|
||||||
* need to do anything...
|
* need to do anything...
|
||||||
|
|
|
@ -350,6 +350,15 @@ EXPORT_SYMBOL_GPL(device_bind_driver);
|
||||||
static atomic_t probe_count = ATOMIC_INIT(0);
|
static atomic_t probe_count = ATOMIC_INIT(0);
|
||||||
static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
|
static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
|
||||||
|
|
||||||
|
static void driver_deferred_probe_add_trigger(struct device *dev,
|
||||||
|
int local_trigger_count)
|
||||||
|
{
|
||||||
|
driver_deferred_probe_add(dev);
|
||||||
|
/* Did a trigger occur while probing? Need to re-trigger if yes */
|
||||||
|
if (local_trigger_count != atomic_read(&deferred_trigger_count))
|
||||||
|
driver_deferred_probe_trigger();
|
||||||
|
}
|
||||||
|
|
||||||
static int really_probe(struct device *dev, struct device_driver *drv)
|
static int really_probe(struct device *dev, struct device_driver *drv)
|
||||||
{
|
{
|
||||||
int ret = -EPROBE_DEFER;
|
int ret = -EPROBE_DEFER;
|
||||||
|
@ -369,6 +378,8 @@ static int really_probe(struct device *dev, struct device_driver *drv)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = device_links_check_suppliers(dev);
|
ret = device_links_check_suppliers(dev);
|
||||||
|
if (ret == -EPROBE_DEFER)
|
||||||
|
driver_deferred_probe_add_trigger(dev, local_trigger_count);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
@ -470,10 +481,7 @@ pinctrl_bind_failed:
|
||||||
case -EPROBE_DEFER:
|
case -EPROBE_DEFER:
|
||||||
/* Driver requested deferred probing */
|
/* Driver requested deferred probing */
|
||||||
dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
|
dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
|
||||||
driver_deferred_probe_add(dev);
|
driver_deferred_probe_add_trigger(dev, local_trigger_count);
|
||||||
/* Did a trigger occur while probing? Need to re-trigger if yes */
|
|
||||||
if (local_trigger_count != atomic_read(&deferred_trigger_count))
|
|
||||||
driver_deferred_probe_trigger();
|
|
||||||
break;
|
break;
|
||||||
case -ENODEV:
|
case -ENODEV:
|
||||||
case -ENXIO:
|
case -ENXIO:
|
||||||
|
|
|
@ -64,7 +64,7 @@ static int __init test_async_probe_init(void)
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
if (IS_ERR(async_dev_1)) {
|
if (IS_ERR(async_dev_1)) {
|
||||||
error = PTR_ERR(async_dev_1);
|
error = PTR_ERR(async_dev_1);
|
||||||
pr_err("failed to create async_dev_1: %d", error);
|
pr_err("failed to create async_dev_1: %d\n", error);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ static int __init test_async_probe_init(void)
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
if (IS_ERR(async_dev_2)) {
|
if (IS_ERR(async_dev_2)) {
|
||||||
error = PTR_ERR(async_dev_2);
|
error = PTR_ERR(async_dev_2);
|
||||||
pr_err("failed to create async_dev_2: %d", error);
|
pr_err("failed to create async_dev_2: %d\n", error);
|
||||||
goto err_unregister_async_driver;
|
goto err_unregister_async_driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ static int __init test_async_probe_init(void)
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
if (IS_ERR(sync_dev_1)) {
|
if (IS_ERR(sync_dev_1)) {
|
||||||
error = PTR_ERR(sync_dev_1);
|
error = PTR_ERR(sync_dev_1);
|
||||||
pr_err("failed to create sync_dev_1: %d", error);
|
pr_err("failed to create sync_dev_1: %d\n", error);
|
||||||
goto err_unregister_sync_driver;
|
goto err_unregister_sync_driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,13 +71,13 @@ static ssize_t hfi1_seq_read(
|
||||||
loff_t *ppos)
|
loff_t *ppos)
|
||||||
{
|
{
|
||||||
struct dentry *d = file->f_path.dentry;
|
struct dentry *d = file->f_path.dentry;
|
||||||
int srcu_idx;
|
|
||||||
ssize_t r;
|
ssize_t r;
|
||||||
|
|
||||||
r = debugfs_use_file_start(d, &srcu_idx);
|
r = debugfs_file_get(d);
|
||||||
if (likely(!r))
|
if (unlikely(r))
|
||||||
r = seq_read(file, buf, size, ppos);
|
return r;
|
||||||
debugfs_use_file_finish(srcu_idx);
|
r = seq_read(file, buf, size, ppos);
|
||||||
|
debugfs_file_put(d);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,13 +87,13 @@ static loff_t hfi1_seq_lseek(
|
||||||
int whence)
|
int whence)
|
||||||
{
|
{
|
||||||
struct dentry *d = file->f_path.dentry;
|
struct dentry *d = file->f_path.dentry;
|
||||||
int srcu_idx;
|
|
||||||
loff_t r;
|
loff_t r;
|
||||||
|
|
||||||
r = debugfs_use_file_start(d, &srcu_idx);
|
r = debugfs_file_get(d);
|
||||||
if (likely(!r))
|
if (unlikely(r))
|
||||||
r = seq_lseek(file, offset, whence);
|
return r;
|
||||||
debugfs_use_file_finish(srcu_idx);
|
r = seq_lseek(file, offset, whence);
|
||||||
|
debugfs_file_put(d);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* file.c - part of debugfs, a tiny little debug file system
|
* file.c - part of debugfs, a tiny little debug file system
|
||||||
*
|
*
|
||||||
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
|
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
|
||||||
* Copyright (C) 2004 IBM Inc.
|
* Copyright (C) 2004 IBM Inc.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License version
|
|
||||||
* 2 as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* debugfs is for people to use instead of /proc or /sys.
|
* debugfs is for people to use instead of /proc or /sys.
|
||||||
* See Documentation/filesystems/ for more details.
|
* See Documentation/filesystems/ for more details.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
@ -22,7 +18,6 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/srcu.h>
|
|
||||||
#include <asm/poll.h>
|
#include <asm/poll.h>
|
||||||
|
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
@ -48,66 +43,108 @@ const struct file_operations debugfs_noop_file_operations = {
|
||||||
.llseek = noop_llseek,
|
.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.
|
* @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
|
* Up to a matching call to debugfs_file_put(), any successive call
|
||||||
* successive call into the file removing functions debugfs_remove()
|
* into the file removing functions debugfs_remove() and
|
||||||
* and debugfs_remove_recursive() will block. Since associated private
|
* debugfs_remove_recursive() will block. Since associated private
|
||||||
* file data may only get freed after a successful return of any of
|
* file data may only get freed after a successful return of any of
|
||||||
* the removal functions, you may safely access it after a successful
|
* the removal functions, you may safely access it after a successful
|
||||||
* call to debugfs_use_file_start() without worrying about
|
* call to debugfs_file_get() without worrying about lifetime issues.
|
||||||
* lifetime issues.
|
|
||||||
*
|
*
|
||||||
* If -%EIO is returned, the file has already been removed and thus,
|
* 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 not safe to access any of its data. If, on the other hand,
|
||||||
* it is allowed to access the file data, zero is returned.
|
* 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)
|
int debugfs_file_get(struct dentry *dentry)
|
||||||
__acquires(&debugfs_srcu)
|
|
||||||
{
|
{
|
||||||
*srcu_idx = srcu_read_lock(&debugfs_srcu);
|
struct debugfs_fsdata *fsd;
|
||||||
barrier();
|
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))
|
if (d_unlinked(dentry))
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
|
if (!refcount_inc_not_zero(&fsd->active_users))
|
||||||
|
return -EIO;
|
||||||
|
|
||||||
return 0;
|
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
|
* debugfs_file_put - mark the end of file data access
|
||||||
* @srcu_idx: the SRCU index "created" by a former call to
|
* @dentry: the dentry object formerly passed to
|
||||||
* debugfs_use_file_start().
|
* debugfs_file_get().
|
||||||
*
|
*
|
||||||
* Allow any ongoing concurrent call into debugfs_remove() or
|
* Allow any ongoing concurrent call into debugfs_remove() or
|
||||||
* debugfs_remove_recursive() blocked by a former call to
|
* 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);
|
struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata);
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(debugfs_use_file_finish);
|
|
||||||
|
|
||||||
#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)
|
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;
|
const struct file_operations *real_fops = NULL;
|
||||||
int srcu_idx, r;
|
int r;
|
||||||
|
|
||||||
r = debugfs_use_file_start(dentry, &srcu_idx);
|
r = debugfs_file_get(dentry);
|
||||||
if (r) {
|
if (r)
|
||||||
r = -ENOENT;
|
return r == -EIO ? -ENOENT : r;
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
real_fops = debugfs_real_fops(filp);
|
real_fops = debugfs_real_fops(filp);
|
||||||
real_fops = fops_get(real_fops);
|
real_fops = fops_get(real_fops);
|
||||||
|
@ -124,7 +161,7 @@ static int open_proxy_open(struct inode *inode, struct file *filp)
|
||||||
r = real_fops->open(inode, filp);
|
r = real_fops->open(inode, filp);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
debugfs_use_file_finish(srcu_idx);
|
debugfs_file_put(dentry);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,16 +175,16 @@ const struct file_operations debugfs_open_proxy_file_operations = {
|
||||||
#define FULL_PROXY_FUNC(name, ret_type, filp, proto, args) \
|
#define FULL_PROXY_FUNC(name, ret_type, filp, proto, args) \
|
||||||
static ret_type full_proxy_ ## name(proto) \
|
static ret_type full_proxy_ ## name(proto) \
|
||||||
{ \
|
{ \
|
||||||
const struct dentry *dentry = F_DENTRY(filp); \
|
struct dentry *dentry = F_DENTRY(filp); \
|
||||||
const struct file_operations *real_fops = \
|
const struct file_operations *real_fops; \
|
||||||
debugfs_real_fops(filp); \
|
|
||||||
int srcu_idx; \
|
|
||||||
ret_type r; \
|
ret_type r; \
|
||||||
\
|
\
|
||||||
r = debugfs_use_file_start(dentry, &srcu_idx); \
|
r = debugfs_file_get(dentry); \
|
||||||
if (likely(!r)) \
|
if (unlikely(r)) \
|
||||||
r = real_fops->name(args); \
|
return r; \
|
||||||
debugfs_use_file_finish(srcu_idx); \
|
real_fops = debugfs_real_fops(filp); \
|
||||||
|
r = real_fops->name(args); \
|
||||||
|
debugfs_file_put(dentry); \
|
||||||
return r; \
|
return r; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,18 +209,16 @@ FULL_PROXY_FUNC(unlocked_ioctl, long, filp,
|
||||||
static unsigned int full_proxy_poll(struct file *filp,
|
static unsigned int full_proxy_poll(struct file *filp,
|
||||||
struct poll_table_struct *wait)
|
struct poll_table_struct *wait)
|
||||||
{
|
{
|
||||||
const struct dentry *dentry = F_DENTRY(filp);
|
struct dentry *dentry = F_DENTRY(filp);
|
||||||
const struct file_operations *real_fops = debugfs_real_fops(filp);
|
|
||||||
int srcu_idx;
|
|
||||||
unsigned int r = 0;
|
unsigned int r = 0;
|
||||||
|
const struct file_operations *real_fops;
|
||||||
|
|
||||||
if (debugfs_use_file_start(dentry, &srcu_idx)) {
|
if (debugfs_file_get(dentry))
|
||||||
debugfs_use_file_finish(srcu_idx);
|
|
||||||
return POLLHUP;
|
return POLLHUP;
|
||||||
}
|
|
||||||
|
|
||||||
|
real_fops = debugfs_real_fops(filp);
|
||||||
r = real_fops->poll(filp, wait);
|
r = real_fops->poll(filp, wait);
|
||||||
debugfs_use_file_finish(srcu_idx);
|
debugfs_file_put(dentry);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,16 +262,14 @@ static void __full_proxy_fops_init(struct file_operations *proxy_fops,
|
||||||
|
|
||||||
static int full_proxy_open(struct inode *inode, struct file *filp)
|
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;
|
const struct file_operations *real_fops = NULL;
|
||||||
struct file_operations *proxy_fops = NULL;
|
struct file_operations *proxy_fops = NULL;
|
||||||
int srcu_idx, r;
|
int r;
|
||||||
|
|
||||||
r = debugfs_use_file_start(dentry, &srcu_idx);
|
r = debugfs_file_get(dentry);
|
||||||
if (r) {
|
if (r)
|
||||||
r = -ENOENT;
|
return r == -EIO ? -ENOENT : r;
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
real_fops = debugfs_real_fops(filp);
|
real_fops = debugfs_real_fops(filp);
|
||||||
real_fops = fops_get(real_fops);
|
real_fops = fops_get(real_fops);
|
||||||
|
@ -274,7 +307,7 @@ free_proxy:
|
||||||
kfree(proxy_fops);
|
kfree(proxy_fops);
|
||||||
fops_put(real_fops);
|
fops_put(real_fops);
|
||||||
out:
|
out:
|
||||||
debugfs_use_file_finish(srcu_idx);
|
debugfs_file_put(dentry);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,13 +318,14 @@ const struct file_operations debugfs_full_proxy_file_operations = {
|
||||||
ssize_t debugfs_attr_read(struct file *file, char __user *buf,
|
ssize_t debugfs_attr_read(struct file *file, char __user *buf,
|
||||||
size_t len, loff_t *ppos)
|
size_t len, loff_t *ppos)
|
||||||
{
|
{
|
||||||
|
struct dentry *dentry = F_DENTRY(file);
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
int srcu_idx;
|
|
||||||
|
|
||||||
ret = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
|
ret = debugfs_file_get(dentry);
|
||||||
if (likely(!ret))
|
if (unlikely(ret))
|
||||||
ret = simple_attr_read(file, buf, len, ppos);
|
return ret;
|
||||||
debugfs_use_file_finish(srcu_idx);
|
ret = simple_attr_read(file, buf, len, ppos);
|
||||||
|
debugfs_file_put(dentry);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(debugfs_attr_read);
|
EXPORT_SYMBOL_GPL(debugfs_attr_read);
|
||||||
|
@ -299,13 +333,14 @@ EXPORT_SYMBOL_GPL(debugfs_attr_read);
|
||||||
ssize_t debugfs_attr_write(struct file *file, const char __user *buf,
|
ssize_t debugfs_attr_write(struct file *file, const char __user *buf,
|
||||||
size_t len, loff_t *ppos)
|
size_t len, loff_t *ppos)
|
||||||
{
|
{
|
||||||
|
struct dentry *dentry = F_DENTRY(file);
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
int srcu_idx;
|
|
||||||
|
|
||||||
ret = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
|
ret = debugfs_file_get(dentry);
|
||||||
if (likely(!ret))
|
if (unlikely(ret))
|
||||||
ret = simple_attr_write(file, buf, len, ppos);
|
return ret;
|
||||||
debugfs_use_file_finish(srcu_idx);
|
ret = simple_attr_write(file, buf, len, ppos);
|
||||||
|
debugfs_file_put(dentry);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(debugfs_attr_write);
|
EXPORT_SYMBOL_GPL(debugfs_attr_write);
|
||||||
|
@ -739,14 +774,14 @@ ssize_t debugfs_read_file_bool(struct file *file, char __user *user_buf,
|
||||||
{
|
{
|
||||||
char buf[3];
|
char buf[3];
|
||||||
bool val;
|
bool val;
|
||||||
int r, srcu_idx;
|
int r;
|
||||||
|
struct dentry *dentry = F_DENTRY(file);
|
||||||
|
|
||||||
r = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
|
r = debugfs_file_get(dentry);
|
||||||
if (likely(!r))
|
if (unlikely(r))
|
||||||
val = *(bool *)file->private_data;
|
|
||||||
debugfs_use_file_finish(srcu_idx);
|
|
||||||
if (r)
|
|
||||||
return r;
|
return r;
|
||||||
|
val = *(bool *)file->private_data;
|
||||||
|
debugfs_file_put(dentry);
|
||||||
|
|
||||||
if (val)
|
if (val)
|
||||||
buf[0] = 'Y';
|
buf[0] = 'Y';
|
||||||
|
@ -764,8 +799,9 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
|
||||||
char buf[32];
|
char buf[32];
|
||||||
size_t buf_size;
|
size_t buf_size;
|
||||||
bool bv;
|
bool bv;
|
||||||
int r, srcu_idx;
|
int r;
|
||||||
bool *val = file->private_data;
|
bool *val = file->private_data;
|
||||||
|
struct dentry *dentry = F_DENTRY(file);
|
||||||
|
|
||||||
buf_size = min(count, (sizeof(buf)-1));
|
buf_size = min(count, (sizeof(buf)-1));
|
||||||
if (copy_from_user(buf, user_buf, buf_size))
|
if (copy_from_user(buf, user_buf, buf_size))
|
||||||
|
@ -773,12 +809,11 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
|
||||||
|
|
||||||
buf[buf_size] = '\0';
|
buf[buf_size] = '\0';
|
||||||
if (strtobool(buf, &bv) == 0) {
|
if (strtobool(buf, &bv) == 0) {
|
||||||
r = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
|
r = debugfs_file_get(dentry);
|
||||||
if (likely(!r))
|
if (unlikely(r))
|
||||||
*val = bv;
|
|
||||||
debugfs_use_file_finish(srcu_idx);
|
|
||||||
if (r)
|
|
||||||
return r;
|
return r;
|
||||||
|
*val = bv;
|
||||||
|
debugfs_file_put(dentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
|
@ -840,14 +875,15 @@ static ssize_t read_file_blob(struct file *file, char __user *user_buf,
|
||||||
size_t count, loff_t *ppos)
|
size_t count, loff_t *ppos)
|
||||||
{
|
{
|
||||||
struct debugfs_blob_wrapper *blob = file->private_data;
|
struct debugfs_blob_wrapper *blob = file->private_data;
|
||||||
|
struct dentry *dentry = F_DENTRY(file);
|
||||||
ssize_t r;
|
ssize_t r;
|
||||||
int srcu_idx;
|
|
||||||
|
|
||||||
r = debugfs_use_file_start(F_DENTRY(file), &srcu_idx);
|
r = debugfs_file_get(dentry);
|
||||||
if (likely(!r))
|
if (unlikely(r))
|
||||||
r = simple_read_from_buffer(user_buf, count, ppos, blob->data,
|
return r;
|
||||||
blob->size);
|
r = simple_read_from_buffer(user_buf, count, ppos, blob->data,
|
||||||
debugfs_use_file_finish(srcu_idx);
|
blob->size);
|
||||||
|
debugfs_file_put(dentry);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* inode.c - part of debugfs, a tiny little debug file system
|
* inode.c - part of debugfs, a tiny little debug file system
|
||||||
*
|
*
|
||||||
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
|
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
|
||||||
* Copyright (C) 2004 IBM Inc.
|
* Copyright (C) 2004 IBM Inc.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License version
|
|
||||||
* 2 as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* debugfs is for people to use instead of /proc or /sys.
|
* debugfs is for people to use instead of /proc or /sys.
|
||||||
* See ./Documentation/core-api/kernel-api.rst for more details.
|
* See ./Documentation/core-api/kernel-api.rst for more details.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
@ -27,14 +23,11 @@
|
||||||
#include <linux/parser.h>
|
#include <linux/parser.h>
|
||||||
#include <linux/magic.h>
|
#include <linux/magic.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/srcu.h>
|
|
||||||
|
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
#define DEBUGFS_DEFAULT_MODE 0700
|
#define DEBUGFS_DEFAULT_MODE 0700
|
||||||
|
|
||||||
DEFINE_SRCU(debugfs_srcu);
|
|
||||||
|
|
||||||
static struct vfsmount *debugfs_mount;
|
static struct vfsmount *debugfs_mount;
|
||||||
static int debugfs_mount_count;
|
static int debugfs_mount_count;
|
||||||
static bool debugfs_registered;
|
static bool debugfs_registered;
|
||||||
|
@ -185,6 +178,14 @@ static const struct super_operations debugfs_super_operations = {
|
||||||
.evict_inode = debugfs_evict_inode,
|
.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)
|
static struct vfsmount *debugfs_automount(struct path *path)
|
||||||
{
|
{
|
||||||
debugfs_automount_t f;
|
debugfs_automount_t f;
|
||||||
|
@ -194,6 +195,7 @@ static struct vfsmount *debugfs_automount(struct path *path)
|
||||||
|
|
||||||
static const struct dentry_operations debugfs_dops = {
|
static const struct dentry_operations debugfs_dops = {
|
||||||
.d_delete = always_delete_dentry,
|
.d_delete = always_delete_dentry,
|
||||||
|
.d_release = debugfs_release_dentry,
|
||||||
.d_automount = debugfs_automount,
|
.d_automount = debugfs_automount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -358,7 +360,8 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
|
||||||
inode->i_private = data;
|
inode->i_private = data;
|
||||||
|
|
||||||
inode->i_fop = proxy_fops;
|
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);
|
d_instantiate(dentry, inode);
|
||||||
fsnotify_create(d_inode(dentry->d_parent), dentry);
|
fsnotify_create(d_inode(dentry->d_parent), dentry);
|
||||||
|
@ -615,18 +618,43 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(debugfs_create_symlink);
|
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)
|
static int __debugfs_remove(struct dentry *dentry, struct dentry *parent)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if (simple_positive(dentry)) {
|
if (simple_positive(dentry)) {
|
||||||
dget(dentry);
|
dget(dentry);
|
||||||
if (d_is_dir(dentry))
|
if (!d_is_reg(dentry)) {
|
||||||
ret = simple_rmdir(d_inode(parent), dentry);
|
if (d_is_dir(dentry))
|
||||||
else
|
ret = simple_rmdir(d_inode(parent), dentry);
|
||||||
simple_unlink(d_inode(parent), dentry);
|
else
|
||||||
if (!ret)
|
simple_unlink(d_inode(parent), dentry);
|
||||||
d_delete(dentry);
|
if (!ret)
|
||||||
|
d_delete(dentry);
|
||||||
|
} else {
|
||||||
|
__debugfs_remove_file(dentry, parent);
|
||||||
|
}
|
||||||
dput(dentry);
|
dput(dentry);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -660,8 +688,6 @@ void debugfs_remove(struct dentry *dentry)
|
||||||
inode_unlock(d_inode(parent));
|
inode_unlock(d_inode(parent));
|
||||||
if (!ret)
|
if (!ret)
|
||||||
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
|
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
|
||||||
|
|
||||||
synchronize_srcu(&debugfs_srcu);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(debugfs_remove);
|
EXPORT_SYMBOL_GPL(debugfs_remove);
|
||||||
|
|
||||||
|
@ -735,8 +761,6 @@ void debugfs_remove_recursive(struct dentry *dentry)
|
||||||
if (!__debugfs_remove(child, parent))
|
if (!__debugfs_remove(child, parent))
|
||||||
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
|
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
|
||||||
inode_unlock(d_inode(parent));
|
inode_unlock(d_inode(parent));
|
||||||
|
|
||||||
synchronize_srcu(&debugfs_srcu);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(debugfs_remove_recursive);
|
EXPORT_SYMBOL_GPL(debugfs_remove_recursive);
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* internal.h - declarations internal to debugfs
|
* internal.h - declarations internal to debugfs
|
||||||
*
|
*
|
||||||
* Copyright (C) 2016 Nicolai Stange <nicstange@gmail.com>
|
* Copyright (C) 2016 Nicolai Stange <nicstange@gmail.com>
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License version
|
|
||||||
* 2 as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _DEBUGFS_INTERNAL_H_
|
#ifndef _DEBUGFS_INTERNAL_H_
|
||||||
|
@ -19,4 +15,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_open_proxy_file_operations;
|
||||||
extern const struct file_operations debugfs_full_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_ */
|
#endif /* _DEBUGFS_INTERNAL_H_ */
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* debugfs.h - a tiny little debug file system
|
* debugfs.h - a tiny little debug file system
|
||||||
*
|
*
|
||||||
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
|
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
|
||||||
* Copyright (C) 2004 IBM Inc.
|
* Copyright (C) 2004 IBM Inc.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License version
|
|
||||||
* 2 as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* debugfs is for people to use instead of /proc or /sys.
|
* debugfs is for people to use instead of /proc or /sys.
|
||||||
* See Documentation/filesystems/ for more details.
|
* See Documentation/filesystems/ for more details.
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +20,6 @@
|
||||||
|
|
||||||
struct device;
|
struct device;
|
||||||
struct file_operations;
|
struct file_operations;
|
||||||
struct srcu_struct;
|
|
||||||
|
|
||||||
struct debugfs_blob_wrapper {
|
struct debugfs_blob_wrapper {
|
||||||
void *data;
|
void *data;
|
||||||
|
@ -43,25 +39,6 @@ struct debugfs_regset32 {
|
||||||
|
|
||||||
extern struct dentry *arch_debugfs_dir;
|
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(const 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) \
|
#define DEFINE_DEBUGFS_ATTRIBUTE(__fops, __get, __set, __fmt) \
|
||||||
static int __fops ## _open(struct inode *inode, struct file *file) \
|
static int __fops ## _open(struct inode *inode, struct file *file) \
|
||||||
{ \
|
{ \
|
||||||
|
@ -107,10 +84,10 @@ struct dentry *debugfs_create_automount(const char *name,
|
||||||
void debugfs_remove(struct dentry *dentry);
|
void debugfs_remove(struct dentry *dentry);
|
||||||
void debugfs_remove_recursive(struct dentry *dentry);
|
void debugfs_remove_recursive(struct dentry *dentry);
|
||||||
|
|
||||||
int debugfs_use_file_start(const struct dentry *dentry, int *srcu_idx)
|
const struct file_operations *debugfs_real_fops(const struct file *filp);
|
||||||
__acquires(&debugfs_srcu);
|
|
||||||
|
|
||||||
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,
|
ssize_t debugfs_attr_read(struct file *file, char __user *buf,
|
||||||
size_t len, loff_t *ppos);
|
size_t len, loff_t *ppos);
|
||||||
|
@ -239,15 +216,12 @@ static inline void debugfs_remove(struct dentry *dentry)
|
||||||
static inline void debugfs_remove_recursive(struct dentry *dentry)
|
static inline void debugfs_remove_recursive(struct dentry *dentry)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
static inline int debugfs_use_file_start(const struct dentry *dentry,
|
static inline int debugfs_file_get(struct dentry *dentry)
|
||||||
int *srcu_idx)
|
|
||||||
__acquires(&debugfs_srcu)
|
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void debugfs_use_file_finish(int srcu_idx)
|
static inline void debugfs_file_put(struct dentry *dentry)
|
||||||
__releases(&debugfs_srcu)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
static inline ssize_t debugfs_attr_read(struct file *file, char __user *buf,
|
static inline ssize_t debugfs_attr_read(struct file *file, char __user *buf,
|
||||||
|
|
|
@ -280,7 +280,6 @@ config PAGE_OWNER
|
||||||
|
|
||||||
config DEBUG_FS
|
config DEBUG_FS
|
||||||
bool "Debug Filesystem"
|
bool "Debug Filesystem"
|
||||||
select SRCU
|
|
||||||
help
|
help
|
||||||
debugfs is a virtual file system that kernel developers use to put
|
debugfs is a virtual file system that kernel developers use to put
|
||||||
debugging files into. Enable this option to be able to read and
|
debugging files into. Enable this option to be able to read and
|
||||||
|
|
Loading…
Reference in New Issue