mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 09:16:33 +00:00
debugfs: add API to allow debugfs operations cancellation
In some cases there might be longer-running hardware accesses in debugfs files, or attempts to acquire locks, and we want to still be able to quickly remove the files. Introduce a cancellations API to use inside the debugfs handler functions to be able to cancel such operations on a per-file basis. Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
parent
f4acfcd4de
commit
8c88a47435
@ -114,6 +114,8 @@ int debugfs_file_get(struct dentry *dentry)
|
||||
lockdep_init_map(&fsd->lockdep_map, fsd->lock_name ?: "debugfs",
|
||||
&fsd->key, 0);
|
||||
#endif
|
||||
INIT_LIST_HEAD(&fsd->cancellations);
|
||||
mutex_init(&fsd->cancellations_mtx);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -156,6 +158,86 @@ void debugfs_file_put(struct dentry *dentry)
|
||||
}
|
||||
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 ||
|
||||
((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
|
||||
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 ||
|
||||
((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
|
||||
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
|
||||
|
@ -247,6 +247,8 @@ static void debugfs_release_dentry(struct dentry *dentry)
|
||||
lockdep_unregister_key(&fsd->key);
|
||||
kfree(fsd->lock_name);
|
||||
#endif
|
||||
WARN_ON(!list_empty(&fsd->cancellations));
|
||||
mutex_destroy(&fsd->cancellations_mtx);
|
||||
}
|
||||
|
||||
kfree(fsd);
|
||||
@ -756,8 +758,36 @@ static void __debugfs_file_removed(struct dentry *dentry)
|
||||
lock_map_acquire(&fsd->lockdep_map);
|
||||
lock_map_release(&fsd->lockdep_map);
|
||||
|
||||
if (!refcount_dec_and_test(&fsd->active_users))
|
||||
/* if we hit zero, just wait for all to finish */
|
||||
if (!refcount_dec_and_test(&fsd->active_users)) {
|
||||
wait_for_completion(&fsd->active_users_drained);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if we didn't hit zero, try to cancel any we can */
|
||||
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)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#ifndef _DEBUGFS_INTERNAL_H_
|
||||
#define _DEBUGFS_INTERNAL_H_
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
struct file_operations;
|
||||
|
||||
@ -29,6 +30,10 @@ struct debugfs_fsdata {
|
||||
struct lock_class_key key;
|
||||
char *lock_name;
|
||||
#endif
|
||||
|
||||
/* protect cancellations */
|
||||
struct mutex cancellations_mtx;
|
||||
struct list_head cancellations;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -171,6 +171,25 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
|
||||
ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos);
|
||||
|
||||
/**
|
||||
* struct debugfs_cancellation - cancellation data
|
||||
* @list: internal, for keeping track
|
||||
* @cancel: callback to call
|
||||
* @cancel_data: extra data for the callback to call
|
||||
*/
|
||||
struct debugfs_cancellation {
|
||||
struct list_head list;
|
||||
void (*cancel)(struct dentry *, void *);
|
||||
void *cancel_data;
|
||||
};
|
||||
|
||||
void __acquires(cancellation)
|
||||
debugfs_enter_cancellation(struct file *file,
|
||||
struct debugfs_cancellation *cancellation);
|
||||
void __releases(cancellation)
|
||||
debugfs_leave_cancellation(struct file *file,
|
||||
struct debugfs_cancellation *cancellation);
|
||||
|
||||
#else
|
||||
|
||||
#include <linux/err.h>
|
||||
|
Loading…
Reference in New Issue
Block a user