Skip to content

Commit 8c88a47

Browse files
committed
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>
1 parent f4acfcd commit 8c88a47

4 files changed

Lines changed: 137 additions & 1 deletion

File tree

fs/debugfs/file.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ int debugfs_file_get(struct dentry *dentry)
114114
lockdep_init_map(&fsd->lockdep_map, fsd->lock_name ?: "debugfs",
115115
&fsd->key, 0);
116116
#endif
117+
INIT_LIST_HEAD(&fsd->cancellations);
118+
mutex_init(&fsd->cancellations_mtx);
117119
}
118120

119121
/*
@@ -156,6 +158,86 @@ void debugfs_file_put(struct dentry *dentry)
156158
}
157159
EXPORT_SYMBOL_GPL(debugfs_file_put);
158160

161+
/**
162+
* debugfs_enter_cancellation - enter a debugfs cancellation
163+
* @file: the file being accessed
164+
* @cancellation: the cancellation object, the cancel callback
165+
* inside of it must be initialized
166+
*
167+
* When a debugfs file is removed it needs to wait for all active
168+
* operations to complete. However, the operation itself may need
169+
* to wait for hardware or completion of some asynchronous process
170+
* or similar. As such, it may need to be cancelled to avoid long
171+
* waits or even deadlocks.
172+
*
173+
* This function can be used inside a debugfs handler that may
174+
* need to be cancelled. As soon as this function is called, the
175+
* cancellation's 'cancel' callback may be called, at which point
176+
* the caller should proceed to call debugfs_leave_cancellation()
177+
* and leave the debugfs handler function as soon as possible.
178+
* Note that the 'cancel' callback is only ever called in the
179+
* context of some kind of debugfs_remove().
180+
*
181+
* This function must be paired with debugfs_leave_cancellation().
182+
*/
183+
void debugfs_enter_cancellation(struct file *file,
184+
struct debugfs_cancellation *cancellation)
185+
{
186+
struct debugfs_fsdata *fsd;
187+
struct dentry *dentry = F_DENTRY(file);
188+
189+
INIT_LIST_HEAD(&cancellation->list);
190+
191+
if (WARN_ON(!d_is_reg(dentry)))
192+
return;
193+
194+
if (WARN_ON(!cancellation->cancel))
195+
return;
196+
197+
fsd = READ_ONCE(dentry->d_fsdata);
198+
if (WARN_ON(!fsd ||
199+
((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
200+
return;
201+
202+
mutex_lock(&fsd->cancellations_mtx);
203+
list_add(&cancellation->list, &fsd->cancellations);
204+
mutex_unlock(&fsd->cancellations_mtx);
205+
206+
/* if we're already removing wake it up to cancel */
207+
if (d_unlinked(dentry))
208+
complete(&fsd->active_users_drained);
209+
}
210+
EXPORT_SYMBOL_GPL(debugfs_enter_cancellation);
211+
212+
/**
213+
* debugfs_leave_cancellation - leave cancellation section
214+
* @file: the file being accessed
215+
* @cancellation: the cancellation previously registered with
216+
* debugfs_enter_cancellation()
217+
*
218+
* See the documentation of debugfs_enter_cancellation().
219+
*/
220+
void debugfs_leave_cancellation(struct file *file,
221+
struct debugfs_cancellation *cancellation)
222+
{
223+
struct debugfs_fsdata *fsd;
224+
struct dentry *dentry = F_DENTRY(file);
225+
226+
if (WARN_ON(!d_is_reg(dentry)))
227+
return;
228+
229+
fsd = READ_ONCE(dentry->d_fsdata);
230+
if (WARN_ON(!fsd ||
231+
((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
232+
return;
233+
234+
mutex_lock(&fsd->cancellations_mtx);
235+
if (!list_empty(&cancellation->list))
236+
list_del(&cancellation->list);
237+
mutex_unlock(&fsd->cancellations_mtx);
238+
}
239+
EXPORT_SYMBOL_GPL(debugfs_leave_cancellation);
240+
159241
/*
160242
* Only permit access to world-readable files when the kernel is locked down.
161243
* We also need to exclude any file that has ways to write or alter it as root

fs/debugfs/inode.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ static void debugfs_release_dentry(struct dentry *dentry)
247247
lockdep_unregister_key(&fsd->key);
248248
kfree(fsd->lock_name);
249249
#endif
250+
WARN_ON(!list_empty(&fsd->cancellations));
251+
mutex_destroy(&fsd->cancellations_mtx);
250252
}
251253

252254
kfree(fsd);
@@ -756,8 +758,36 @@ static void __debugfs_file_removed(struct dentry *dentry)
756758
lock_map_acquire(&fsd->lockdep_map);
757759
lock_map_release(&fsd->lockdep_map);
758760

759-
if (!refcount_dec_and_test(&fsd->active_users))
761+
/* if we hit zero, just wait for all to finish */
762+
if (!refcount_dec_and_test(&fsd->active_users)) {
760763
wait_for_completion(&fsd->active_users_drained);
764+
return;
765+
}
766+
767+
/* if we didn't hit zero, try to cancel any we can */
768+
while (refcount_read(&fsd->active_users)) {
769+
struct debugfs_cancellation *c;
770+
771+
/*
772+
* Lock the cancellations. Note that the cancellations
773+
* structs are meant to be on the stack, so we need to
774+
* ensure we either use them here or don't touch them,
775+
* and debugfs_leave_cancellation() will wait for this
776+
* to be finished processing before exiting one. It may
777+
* of course win and remove the cancellation, but then
778+
* chances are we never even got into this bit, we only
779+
* do if the refcount isn't zero already.
780+
*/
781+
mutex_lock(&fsd->cancellations_mtx);
782+
while ((c = list_first_entry_or_null(&fsd->cancellations,
783+
typeof(*c), list))) {
784+
list_del_init(&c->list);
785+
c->cancel(dentry, c->cancel_data);
786+
}
787+
mutex_unlock(&fsd->cancellations_mtx);
788+
789+
wait_for_completion(&fsd->active_users_drained);
790+
}
761791
}
762792

763793
static void remove_one(struct dentry *victim)

fs/debugfs/internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#ifndef _DEBUGFS_INTERNAL_H_
99
#define _DEBUGFS_INTERNAL_H_
1010
#include <linux/lockdep.h>
11+
#include <linux/list.h>
1112

1213
struct file_operations;
1314

@@ -29,6 +30,10 @@ struct debugfs_fsdata {
2930
struct lock_class_key key;
3031
char *lock_name;
3132
#endif
33+
34+
/* protect cancellations */
35+
struct mutex cancellations_mtx;
36+
struct list_head cancellations;
3237
};
3338
};
3439
};

include/linux/debugfs.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
171171
ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
172172
size_t count, loff_t *ppos);
173173

174+
/**
175+
* struct debugfs_cancellation - cancellation data
176+
* @list: internal, for keeping track
177+
* @cancel: callback to call
178+
* @cancel_data: extra data for the callback to call
179+
*/
180+
struct debugfs_cancellation {
181+
struct list_head list;
182+
void (*cancel)(struct dentry *, void *);
183+
void *cancel_data;
184+
};
185+
186+
void __acquires(cancellation)
187+
debugfs_enter_cancellation(struct file *file,
188+
struct debugfs_cancellation *cancellation);
189+
void __releases(cancellation)
190+
debugfs_leave_cancellation(struct file *file,
191+
struct debugfs_cancellation *cancellation);
192+
174193
#else
175194

176195
#include <linux/err.h>

0 commit comments

Comments
 (0)