sh: intc: userimask support.
Paul Mundt [Tue, 13 Apr 2010 05:43:03 +0000 (14:43 +0900)]
This adds support for hardware-assisted userspace irq masking for
special priority levels. Due to the SR.IMASK interactivity, only some
platforms implement this in hardware (including but not limited to
SH-4A interrupt controllers, and ARM-based SH-Mobile CPUs). Each CPU
needs to wire this up on its own, for now only SH7786 is wired up as an
example.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>

arch/sh/Kconfig
arch/sh/kernel/cpu/sh4a/setup-sh7786.c
drivers/sh/Kconfig [new file with mode: 0644]
drivers/sh/intc.c
include/linux/sh_intc.h

index 8d90564..ba86bfb 100644 (file)
@@ -732,6 +732,8 @@ config GUSA_RB
          LLSC, this should be more efficient than the other alternative of
          disabling interrupts around the atomic sequence.
 
+source "drivers/sh/Kconfig"
+
 endmenu
 
 menu "Boot options"
index 61e5491..235edf8 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/mm.h>
 #include <linux/dma-mapping.h>
 #include <linux/sh_timer.h>
+#include <linux/sh_intc.h>
 #include <cpu/dma-register.h>
 #include <asm/mmzone.h>
 #include <asm/dmaengine.h>
@@ -907,6 +908,7 @@ static DECLARE_INTC_DESC(intc_desc_irl4567, "sh7786-irl4567", vectors_irl4567,
 #define INTC_INTMSK2   INTMSK2
 #define INTC_INTMSKCLR1        CnINTMSKCLR1
 #define INTC_INTMSKCLR2        INTMSKCLR2
+#define INTC_USERIMASK 0xfe411000
 
 void __init plat_irq_setup(void)
 {
@@ -921,6 +923,7 @@ void __init plat_irq_setup(void)
        __raw_writel(__raw_readl(INTC_ICR0) & ~0x00c00000, INTC_ICR0);
 
        register_intc_controller(&intc_desc);
+       register_intc_userimask(INTC_USERIMASK);
 }
 
 void __init plat_irq_setup_pins(int mode)
diff --git a/drivers/sh/Kconfig b/drivers/sh/Kconfig
new file mode 100644 (file)
index 0000000..22c3cda
--- /dev/null
@@ -0,0 +1,13 @@
+config INTC_USERIMASK
+       bool "Userspace interrupt masking support"
+       depends on ARCH_SHMOBILE || (SUPERH && CPU_SH4A)
+       help
+         This enables support for hardware-assisted userspace hardirq
+         masking.
+
+         SH-4A and newer interrupt blocks all support a special shadowed
+         page with all non-masking registers obscured when mapped in to
+         userspace. This is primarily for use by userspace device
+         drivers that are using special priority levels.
+
+         If in doubt, say N.
index 65e1582..77d10ac 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/topology.h>
 #include <linux/bitmap.h>
 #include <linux/cpumask.h>
+#include <asm/sizes.h>
 
 #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \
        ((shift) | ((width) << 5) | ((fn) << 9) | ((mode) << 13) | \
@@ -94,7 +95,8 @@ static DEFINE_SPINLOCK(vector_lock);
 #define SMP_NR(d, x) 1
 #endif
 
-static unsigned int intc_prio_level[NR_IRQS]; /* for now */
+static unsigned int intc_prio_level[NR_IRQS];  /* for now */
+static unsigned int default_prio_level = 2;    /* 2 - 16 */
 static unsigned long ack_handle[NR_IRQS];
 
 static inline struct intc_desc_int *get_intc_desc(unsigned int irq)
@@ -787,7 +789,7 @@ static void __init intc_register_irq(struct intc_desc *desc,
        /* set priority level
         * - this needs to be at least 2 for 5-bit priorities on 7780
         */
-       intc_prio_level[irq] = 2;
+       intc_prio_level[irq] = default_prio_level;
 
        /* enable secondary masking method if present */
        if (data[!primary])
@@ -1037,6 +1039,64 @@ err0:
        return -ENOMEM;
 }
 
+#ifdef CONFIG_INTC_USERIMASK
+static void __iomem *uimask;
+
+int register_intc_userimask(unsigned long addr)
+{
+       if (unlikely(uimask))
+               return -EBUSY;
+
+       uimask = ioremap_nocache(addr, SZ_4K);
+       if (unlikely(!uimask))
+               return -ENOMEM;
+
+       pr_info("intc: userimask support registered for levels 0 -> %d\n",
+               default_prio_level - 1);
+
+       return 0;
+}
+
+static ssize_t
+show_intc_userimask(struct sysdev_class *cls,
+                   struct sysdev_class_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", (__raw_readl(uimask) >> 4) & 0xf);
+}
+
+static ssize_t
+store_intc_userimask(struct sysdev_class *cls,
+                    struct sysdev_class_attribute *attr,
+                    const char *buf, size_t count)
+{
+       unsigned long level;
+
+       level = simple_strtoul(buf, NULL, 10);
+
+       /*
+        * Minimal acceptable IRQ levels are in the 2 - 16 range, but
+        * these are chomped so as to not interfere with normal IRQs.
+        *
+        * Level 1 is a special case on some CPUs in that it's not
+        * directly settable, but given that USERIMASK cuts off below a
+        * certain level, we don't care about this limitation here.
+        * Level 0 on the other hand equates to user masking disabled.
+        *
+        * We use default_prio_level as a cut off so that only special
+        * case opt-in IRQs can be mangled.
+        */
+       if (level >= default_prio_level)
+               return -EINVAL;
+
+       __raw_writel(0xa5 << 24 | level << 4, uimask);
+
+       return count;
+}
+
+static SYSDEV_CLASS_ATTR(userimask, S_IRUSR | S_IWUSR,
+                        show_intc_userimask, store_intc_userimask);
+#endif
+
 static ssize_t
 show_intc_name(struct sys_device *dev, struct sysdev_attribute *attr, char *buf)
 {
@@ -1108,6 +1168,11 @@ static int __init register_intc_sysdevs(void)
        int id = 0;
 
        error = sysdev_class_register(&intc_sysdev_class);
+#ifdef CONFIG_INTC_USERIMASK
+       if (!error && uimask)
+               error = sysdev_class_create_file(&intc_sysdev_class,
+                                                &attr_userimask);
+#endif
        if (!error) {
                list_for_each_entry(d, &intc_list, list) {
                        d->sysdev.id = id;
index 01d8168..f0e8cca 100644 (file)
@@ -99,6 +99,15 @@ struct intc_desc symbol __initdata = {                                       \
 int __init register_intc_controller(struct intc_desc *desc);
 int intc_set_priority(unsigned int irq, unsigned int prio);
 
+#ifdef CONFIG_INTC_USERIMASK
+int register_intc_userimask(unsigned long addr);
+#else
+static inline int register_intc_userimask(unsigned long addr)
+{
+       return 0;
+}
+#endif
+
 int reserve_irq_vector(unsigned int irq);
 void reserve_irq_legacy(void);