While developing the hwspinlock core or a device driver, it can be really helpful to change states from userspace. Add a debugsfs interface to request, free, lock, unlock, and test hwspinlocks. Because this is a potentially dangerous interface, it can only be enabled via source code. Similar to writeable attributes for clocks where this idea stems from. The created files take a single id for a single lock. To test all locks in a system, one could use this oneliner while in the debugfs directory:
for l in $(cut -d: -f1 hwspinlock_summary); do echo $l > test_lock; done Signed-off-by: Wolfram Sang <[email protected]> --- drivers/hwspinlock/hwspinlock_core.c | 122 +++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/drivers/hwspinlock/hwspinlock_core.c b/drivers/hwspinlock/hwspinlock_core.c index 84e9aae6ef44..052dea7e4850 100644 --- a/drivers/hwspinlock/hwspinlock_core.c +++ b/drivers/hwspinlock/hwspinlock_core.c @@ -908,6 +908,110 @@ static const struct seq_operations hwspinlock_sops = { }; DEFINE_SEQ_ATTRIBUTE(hwspinlock); +/* + * This can be dangerous, therefore don't provide any real compile time + * configuration option for this feature. + * People who want to use this will need to modify the source code directly. + */ +#undef HWSPINLOCK_LOW_LEVEL_DEBUG +#ifdef HWSPINLOCK_LOW_LEVEL_DEBUG + +static struct hwspinlock *hwspin_lock_ll_debug_get(u64 id) +{ + struct hwspinlock *hwlock; + unsigned long index = id; + + rcu_read_lock(); + hwlock = xa_find(&hwspinlocks, &index, ULONG_MAX, XA_PRESENT); + rcu_read_unlock(); + + return hwlock; +} + +static int fops_request_set(void *data, u64 id) +{ + struct hwspinlock *hwlock = hwspin_lock_request_specific(id); + + return hwlock ? 0 : -ENOENT; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_request, NULL, fops_request_set, "%llu\n"); + +static int fops_free_set(void *data, u64 id) +{ + struct hwspinlock *hwlock = hwspin_lock_ll_debug_get(id); + + return hwlock ? hwspin_lock_free(hwlock) : -ENOENT; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_free, NULL, fops_free_set, "%llu\n"); + +/* + * It is intentionally allowed to lock/free hwspinlocks which have not been + * requested before. Like error injection, these inconsistent states can be + * very useful for debugging. + */ +static int fops_trylock_raw_set(void *data, u64 id) +{ + struct hwspinlock *hwlock = hwspin_lock_ll_debug_get(id); + + return hwlock ? hwspin_trylock_raw(hwlock) : -ENOENT; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_trylock_raw, NULL, fops_trylock_raw_set, "%llu\n"); + +static int fops_unlock_raw_set(void *data, u64 id) +{ + struct hwspinlock *hwlock = hwspin_lock_ll_debug_get(id); + + if (hwlock) + hwspin_unlock_raw(hwlock); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_unlock_raw, NULL, fops_unlock_raw_set, "%llu\n"); + +static int fops_test_set(void *data, u64 id) +{ + struct hwspinlock *hwlock = hwspin_lock_request_specific(id); + int ret; + + if (!hwlock) + return -ENOENT; + + /* Try twice to see if unlocking was also successful */ + for (int i = 0; i < 2; i++) { + ret = hwspin_trylock(hwlock); + if (ret) { + hwspin_lock_free(hwlock); + return ret; + } + + ret = hwspin_trylock(hwlock); + if (ret != -EBUSY) { + hwspin_lock_free(hwlock); + return -EACCES; + } + + hwspin_unlock(hwlock); + } + + ret = hwspin_lock_free(hwlock); + if (ret) + return ret; + + pr_info("Successfully tested lock %llu\n", id); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_test, NULL, fops_test_set, "%llu\n"); + +static void hwspin_lock_low_level_debug_init(struct dentry *rootdir) +{ + debugfs_create_file("request", 0200, rootdir, NULL, &fops_request); + debugfs_create_file("free", 0200, rootdir, NULL, &fops_free); + debugfs_create_file("lock", 0200, rootdir, NULL, &fops_trylock_raw); + debugfs_create_file("unlock", 0200, rootdir, NULL, &fops_unlock_raw); + debugfs_create_file("test_lock", 0200, rootdir, NULL, &fops_test); +} +#endif + /* * subsys_initcall() is used here but controllers may already have been * registered earlier or will be later. The rationale is that debugfs is @@ -920,6 +1024,24 @@ static int __init hwspin_lock_init(void) debugfs_create_file("hwspinlock_summary", 0444, hwspinlock_debugfs, NULL, &hwspinlock_fops); + +#ifdef HWSPINLOCK_LOW_LEVEL_DEBUG + pr_warn("**********************************************************\n"); + pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n"); + pr_warn("** **\n"); + pr_warn("** Low Level Debug for hwspinlocks is compiled in! **\n"); + pr_warn("** **\n"); + pr_warn("** This means safety, security, stability can be easily **\n"); + pr_warn("** compromised from userspace! **\n"); + pr_warn("** **\n"); + pr_warn("** If you see this message and you are not debugging **\n"); + pr_warn("** the kernel, report this immediately to your vendor! **\n"); + pr_warn("** **\n"); + pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n"); + pr_warn("**********************************************************\n"); + hwspin_lock_low_level_debug_init(hwspinlock_debugfs); +#endif + return 0; } subsys_initcall(hwspin_lock_init); -- 2.51.0

