mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02: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
					
				
					 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", | ||||
| 				 &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 a new issue
	
	 Johannes Berg
						Johannes Berg