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


Reply via email to