diff options
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/lockup.c | 265 |
2 files changed, 266 insertions, 0 deletions
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 37543479a741..d50db65642c7 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-y += lockup.o diff --git a/drivers/misc/lockup.c b/drivers/misc/lockup.c new file mode 100644 index 000000000000..55d95dc83b3c --- /dev/null +++ b/drivers/misc/lockup.c @@ -0,0 +1,265 @@ +/* + * lockup.c + * + * Deliberately do "stupid" things to see if we can debug them + * properly. + * + * The sole purpose of this module is to help with debugging of system + * debug tools. + * + * Copyright (C) 2014 Linaro Limited + * Daniel Thompson <daniel.thompson@linaro.org> + */ + +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/debug_locks.h> + +static DEFINE_SPINLOCK(lockup_lock); + +static int lockup_do_action_on_cpu(int cpu, long (*action)(void *)) +{ + if (cpu == -1) + (void) action(NULL); + + if (cpu < 0 || cpu > num_possible_cpus()) + return -EINVAL; + + /* work_on_cpu() ends up performing an uninterruptible + * wait-for-completion. This means we'll lose the prompt + * regardless of the CPU we send the work to. + */ + work_on_cpu(cpu, action, NULL); + + return 0; +} + +#define DEFINE_LOCKUP_ATTRIBUTE(__fops, __action) \ +static int __fops ## _set(void *data, u64 val) \ +{ \ + lockup_do_action_on_cpu(val, __action); \ + return 0; \ +} \ +DEFINE_SIMPLE_ATTRIBUTE(__fops, NULL, __fops ## _set, "%llu\n") + + +static long do_lockup_spin_lock(void *info) +{ + pr_warn("lockup[%u]: About to wedge in spin_lock\n", get_cpu()); + spin_lock(&lockup_lock); + debug_locks_off(); + spin_lock(&lockup_lock); + spin_unlock(&lockup_lock); + spin_unlock(&lockup_lock); + return 0; +} +DEFINE_LOCKUP_ATTRIBUTE(lockup_spin_lock_fops, do_lockup_spin_lock); + +static long do_lockup_spin_lock_irqsave(void *info) +{ + unsigned long flags1, flags2; + + pr_warn("lockup[%u]: About to wedge in spin_lock_irqsave\n", get_cpu()); + spin_lock_irqsave(&lockup_lock, flags1); + debug_locks_off(); + spin_lock_irqsave(&lockup_lock, flags2); + spin_unlock_irqrestore(&lockup_lock, flags2); + spin_unlock_irqrestore(&lockup_lock, flags1); + return 0; +} +DEFINE_LOCKUP_ATTRIBUTE(lockup_spin_lock_irqsave_fops, + do_lockup_spin_lock_irqsave); + +/* --- */ + +#ifdef CONFIG_ARM64 + +#include <asm/sysreg.h> + +#define DEFAULT_PMR_VALUE 0xf0 + +#define DECLARE_RW_ACCESSOR(x) \ +static inline __maybe_unused void writeq_##x(u64 val) \ +{ \ + asm volatile("msr_s " __stringify(x) ", %0" : : "r"(val)); \ +} \ +static inline __maybe_unused u64 readq_##x(void) \ +{ \ + u64 val; \ +\ + asm volatile("mrs_s %0, " __stringify(x) : "=r" (val)); \ + return val; \ +} + +#define MIDR_EL1 sys_reg(3, 0, 0, 0, 0) +#define MPIDR_EL1 sys_reg(3, 0, 0, 0, 5) +#define VPIDR_EL2 sys_reg(3, 6, 0, 0, 0) + +#define ICC_PMR_EL1 sys_reg(3, 0, 4, 6, 0) +#define ICC_SRE_EL1 sys_reg(3, 0, 12, 12, 5) +#define ICC_SRE_EL2 sys_reg(3, 0, 12, 12, 5) + +DECLARE_RW_ACCESSOR(MIDR_EL1) +DECLARE_RW_ACCESSOR(MPIDR_EL1) +DECLARE_RW_ACCESSOR(VPIDR_EL2) +DECLARE_RW_ACCESSOR(ICC_PMR_EL1) +DECLARE_RW_ACCESSOR(ICC_SRE_EL1) + +#define PR_REG(x) pr_info("%20s %llx\n", #x, readq_##x()) + +static long do_lockup_disable_interrupts(void *info) +{ + pr_warn("lockup[%u]: About to wedge by disabling interrupts\n", + get_cpu()); + + PR_REG(MIDR_EL1); + PR_REG(MPIDR_EL1); + PR_REG(ICC_SRE_EL1); + writeq_ICC_SRE_EL1(0x01); + PR_REG(ICC_SRE_EL1); + PR_REG(ICC_PMR_EL1); + /*PR_REG(VPIDR_EL2);*/ + /*PR_REG(ICC_SRE_EL1);*/ + /*PR_REG(ICC_PMR_EL1);*/ + /*writeq_ICC_PMR_EL1(0x80);*/ + /*PR_REG(ICC_PMR_EL1);*/ + + return 0; +} +DEFINE_LOCKUP_ATTRIBUTE(lockup_disable_interrupts_fops, + do_lockup_disable_interrupts); +#endif + +unsigned long icc_pmr_el1_masked = 0xf0; +EXPORT_SYMBOL(icc_pmr_el1_masked); + +#define PR_FN(x) ({pr_info("%s\n", #x); x; }) + +static int __init lockup_init(void) +{ + unsigned long flags = 0; + unsigned long flags2 = 0; + +#define E(x) { #x, &lockup_##x##_fops } + const struct { + const char *name; + const struct file_operations *fops; + } fops_table[] = { + E(spin_lock), + E(spin_lock_irqsave), +#ifdef CONFIG_ARM64 + E(disable_interrupts), +#endif + }; +#undef E + + struct dentry *dir; + unsigned int i; + + dir = debugfs_create_dir("lockup", NULL); + if (dir) + for (i = 0; i < ARRAY_SIZE(fops_table); i++) + (void)debugfs_create_file(fops_table[i].name, + S_IRUGO | S_IWUSR, dir, NULL, + fops_table[i].fops); + + + pr_info("lockup: create 'lockup' attribute\n"); + + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_disable()); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_enable()); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_save(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_restore(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_save(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_save(flags2)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_restore(flags2)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_restore(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(icc_pmr_el1_masked = 0x70); + + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_disable()); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_enable()); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_save(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_restore(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_save(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_save(flags2)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_restore(flags2)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + PR_FN(local_irq_restore(flags)); + PR_REG(ICC_PMR_EL1); + pr_info("irqs_disabled %d flags %lu flags2 %lu\n", irqs_disabled(), + flags, flags2); + + return 0; +} + +module_init(lockup_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Daniel Thompson"); |