ARM: Expose some CPU control registers via sysfs
authorMans Rullgard <mans@mansr.com>
Tue, 10 Nov 2009 00:39:21 +0000 (00:39 +0000)
committerGrazvydas Ignotas <notasas@gmail.com>
Sat, 14 Jan 2012 00:00:29 +0000 (02:00 +0200)
This creates sysfs files under /sys/devices/system/cpu/cpuN
exposing the values of the control register, auxiliary control
register, and L2 cache auxiliary control register.  Writing to
the files allows setting the value of bits which are safe to
change at any time.

[notasas@gmail.com: add missing include for 3.2]
Signed-off-by: Mans Rullgard <mans@mansr.com>
arch/arm/Kconfig
arch/arm/kernel/Makefile
arch/arm/kernel/sysfs_v7.c [new file with mode: 0644]

index 198d7d4..420755f 100644 (file)
@@ -1828,6 +1828,11 @@ config DEPRECATED_PARAM_STRUCT
          This was deprecated in 2001 and announced to live on for 5 years.
          Some old boot loaders still use this way.
 
+config CPU_V7_SYSFS
+       bool
+       depends on CPU_V7 && SYSFS
+       default y
+
 endmenu
 
 menu "Boot options"
index 16eed6a..7bffc16 100644 (file)
@@ -61,6 +61,7 @@ obj-$(CONFIG_CRASH_DUMP)      += crash_dump.o
 obj-$(CONFIG_SWP_EMULATE)      += swp_emulate.o
 CFLAGS_swp_emulate.o           := -Wa,-march=armv7-a
 obj-$(CONFIG_HAVE_HW_BREAKPOINT)       += hw_breakpoint.o
+obj-$(CONFIG_CPU_V7_SYSFS)     += sysfs_v7.o
 
 obj-$(CONFIG_CRUNCH)           += crunch.o crunch-bits.o
 AFLAGS_crunch-bits.o           := -Wa,-mcpu=ep9312
diff --git a/arch/arm/kernel/sysfs_v7.c b/arch/arm/kernel/sysfs_v7.c
new file mode 100644 (file)
index 0000000..f3654ae
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ *  linux/arch/arm/kernel/sysfs.c
+ *
+ *  Copyright (C) 2008 Mans Rullgard
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/cpu.h>
+#include <linux/sysdev.h>
+#include <linux/fs.h>
+#include <asm/page.h>
+
+#define SETBITS(val, bits, new)                        \
+       do {                                    \
+               val &= ~bits;                   \
+               val |= new & bits;              \
+       } while (0)
+
+#define SHOW_REG(name, opc1, crn, crm, opc2)                           \
+static ssize_t name##_show(struct sys_device *dev,                     \
+                          struct sysdev_attribute *attr,               \
+                          char *buf)                                   \
+{                                                                      \
+       unsigned val;                                                   \
+       asm ("mrc p15,"#opc1", %0,"#crn","#crm","#opc2 : "=r"(val));    \
+       return snprintf(buf, PAGE_SIZE, "%08x\n", val);                 \
+}
+
+#define STORE_REG(name, opc1, crn, crm, opc2, bits)                    \
+static ssize_t name##_store(struct sys_device *dev,                    \
+                           struct sysdev_attribute *attr,              \
+                           const char *buf, size_t size)               \
+{                                                                      \
+       char *end;                                                      \
+       unsigned new = simple_strtoul(buf, &end, 0);                    \
+       unsigned val;                                                   \
+                                                                       \
+       if (end == buf)                                                 \
+               return -EINVAL;                                         \
+                                                                       \
+       asm ("mrc p15,"#opc1", %0,"#crn","#crm","#opc2 : "=r"(val));    \
+       SETBITS(val, bits, new);                                        \
+       asm ("mcr p15,"#opc1", %0,"#crn","#crm","#opc2 :: "r"(val));    \
+                                                                       \
+       return end - buf;                                               \
+}
+
+#define RD_REG(name, opc1, crn, crm, opc2)                             \
+       SHOW_REG(name, opc1, crn, crm, opc2)                            \
+       static SYSDEV_ATTR(name, S_IRUGO|S_IWUSR, name##_show, NULL)
+
+#define RDWR_REG(name, opc1, crn, crm, opc2, bits)                     \
+       SHOW_REG(name, opc1, crn, crm, opc2)                            \
+       STORE_REG(name, opc1, crn, crm, opc2, bits)                     \
+       static SYSDEV_ATTR(name, S_IRUGO|S_IWUSR, name##_show, name##_store)
+
+RDWR_REG(control, 0, c1, c0, 0, 0x802);
+
+SHOW_REG(aux_ctl, 0, c1, c0, 1)
+
+#ifdef CONFIG_ARCH_OMAP34XX
+static ssize_t aux_ctl_store(struct sys_device *dev,
+                            struct sysdev_attribute *attr,
+                            const char *buf, size_t size)
+{
+       char *end;
+       unsigned new = simple_strtoul(buf, &end, 0);
+       unsigned val;
+
+       if (end == buf)
+               return -EINVAL;
+
+       asm ("mrc p15, 0, %0, c1, c0, 1" : "=r"(val));
+       SETBITS(val, 0xff8, new);
+       val &= ~2;
+       asm ("mov r0,  %0       \n\t"
+            "mov r12, #3       \n\t"
+            "smc #0            \n\t"
+            :: "r"(val) : "r0", "r12");
+
+       return end - buf;
+}
+#define AUX_WR S_IWUSR
+#else
+#define aux_ctl_store NULL
+#define AUX_WR 0
+#endif
+
+static SYSDEV_ATTR(aux_control, S_IRUGO|AUX_WR, aux_ctl_show, aux_ctl_store);
+
+SHOW_REG(l2_aux_ctl, 1, c9, c0, 2)
+
+#ifdef CONFIG_ARCH_OMAP34XX
+static ssize_t l2_aux_ctl_store(struct sys_device *dev,
+                               struct sysdev_attribute *attr,
+                               const char *buf, size_t size)
+{
+       char *end;
+       unsigned new = simple_strtoul(buf, &end, 0);
+       unsigned val;
+
+       if (end == buf)
+               return -EINVAL;
+
+       asm ("mrc p15, 1, %0, c9, c0, 2" : "=r"(val));
+       SETBITS(val, 0xbc00000, new);
+       asm ("mov r0,  %0       \n\t"
+            "mov r12, #2       \n\t"
+            "smc #0            \n\t"
+            :: "r"(val) : "r0", "r12");
+
+       return end - buf;
+}
+#define L2AUX_WR S_IWUSR
+#else
+#define l2_aux_ctl_store NULL
+#define L2AUX_WR 0
+#endif
+
+static SYSDEV_ATTR(l2_aux_control, S_IRUGO|L2AUX_WR,
+                  l2_aux_ctl_show, l2_aux_ctl_store);
+
+#define REG_ATTR(sysdev, name)                                         \
+       do {                                                            \
+               int err = sysfs_create_file(&sysdev->kobj, &name.attr); \
+               WARN_ON(err != 0);                                      \
+       } while (0)
+
+static int __init cpu_sysfs_init(void)
+{
+       struct sys_device *sysdev;
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               sysdev = get_cpu_sysdev(cpu);
+               REG_ATTR(sysdev, attr_control);
+               REG_ATTR(sysdev, attr_aux_control);
+               REG_ATTR(sysdev, attr_l2_aux_control);
+       }
+
+       return 0;
+}
+device_initcall(cpu_sysfs_init);