forked from mirrors/linux
		
	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
					
				
					 4 changed files with 137 additions and 1 deletions
				
			
		| 
						 | 
					@ -114,6 +114,8 @@ int debugfs_file_get(struct dentry *dentry)
 | 
				
			||||||
		lockdep_init_map(&fsd->lockdep_map, fsd->lock_name ?: "debugfs",
 | 
							lockdep_init_map(&fsd->lockdep_map, fsd->lock_name ?: "debugfs",
 | 
				
			||||||
				 &fsd->key, 0);
 | 
									 &fsd->key, 0);
 | 
				
			||||||
#endif
 | 
					#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);
 | 
					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.
 | 
					 * 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
 | 
					 * 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);
 | 
							lockdep_unregister_key(&fsd->key);
 | 
				
			||||||
		kfree(fsd->lock_name);
 | 
							kfree(fsd->lock_name);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
							WARN_ON(!list_empty(&fsd->cancellations));
 | 
				
			||||||
 | 
							mutex_destroy(&fsd->cancellations_mtx);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	kfree(fsd);
 | 
						kfree(fsd);
 | 
				
			||||||
| 
						 | 
					@ -756,8 +758,36 @@ static void __debugfs_file_removed(struct dentry *dentry)
 | 
				
			||||||
	lock_map_acquire(&fsd->lockdep_map);
 | 
						lock_map_acquire(&fsd->lockdep_map);
 | 
				
			||||||
	lock_map_release(&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);
 | 
							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)
 | 
					static void remove_one(struct dentry *victim)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@
 | 
				
			||||||
#ifndef _DEBUGFS_INTERNAL_H_
 | 
					#ifndef _DEBUGFS_INTERNAL_H_
 | 
				
			||||||
#define _DEBUGFS_INTERNAL_H_
 | 
					#define _DEBUGFS_INTERNAL_H_
 | 
				
			||||||
#include <linux/lockdep.h>
 | 
					#include <linux/lockdep.h>
 | 
				
			||||||
 | 
					#include <linux/list.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct file_operations;
 | 
					struct file_operations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,10 @@ struct debugfs_fsdata {
 | 
				
			||||||
			struct lock_class_key key;
 | 
								struct lock_class_key key;
 | 
				
			||||||
			char *lock_name;
 | 
								char *lock_name;
 | 
				
			||||||
#endif
 | 
					#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,
 | 
					ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
 | 
				
			||||||
			      size_t count, loff_t *ppos);
 | 
								      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
 | 
					#else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <linux/err.h>
 | 
					#include <linux/err.h>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue