瀏覽代碼

sh: intc: userimask support.

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>
Paul Mundt 15 年之前
父節點
當前提交
43b8774dc4
共有 5 個文件被更改,包括 94 次插入2 次删除
  1. 2 0
      arch/sh/Kconfig
  2. 3 0
      arch/sh/kernel/cpu/sh4a/setup-sh7786.c
  3. 13 0
      drivers/sh/Kconfig
  4. 67 2
      drivers/sh/intc.c
  5. 9 0
      include/linux/sh_intc.h

+ 2 - 0
arch/sh/Kconfig

@@ -732,6 +732,8 @@ config GUSA_RB
 	  LLSC, this should be more efficient than the other alternative of
 	  LLSC, this should be more efficient than the other alternative of
 	  disabling interrupts around the atomic sequence.
 	  disabling interrupts around the atomic sequence.
 
 
+source "drivers/sh/Kconfig"
+
 endmenu
 endmenu
 
 
 menu "Boot options"
 menu "Boot options"

+ 3 - 0
arch/sh/kernel/cpu/sh4a/setup-sh7786.c

@@ -21,6 +21,7 @@
 #include <linux/mm.h>
 #include <linux/mm.h>
 #include <linux/dma-mapping.h>
 #include <linux/dma-mapping.h>
 #include <linux/sh_timer.h>
 #include <linux/sh_timer.h>
+#include <linux/sh_intc.h>
 #include <cpu/dma-register.h>
 #include <cpu/dma-register.h>
 #include <asm/mmzone.h>
 #include <asm/mmzone.h>
 #include <asm/dmaengine.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_INTMSK2	INTMSK2
 #define INTC_INTMSKCLR1	CnINTMSKCLR1
 #define INTC_INTMSKCLR1	CnINTMSKCLR1
 #define INTC_INTMSKCLR2	INTMSKCLR2
 #define INTC_INTMSKCLR2	INTMSKCLR2
+#define INTC_USERIMASK	0xfe411000
 
 
 void __init plat_irq_setup(void)
 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);
 	__raw_writel(__raw_readl(INTC_ICR0) & ~0x00c00000, INTC_ICR0);
 
 
 	register_intc_controller(&intc_desc);
 	register_intc_controller(&intc_desc);
+	register_intc_userimask(INTC_USERIMASK);
 }
 }
 
 
 void __init plat_irq_setup_pins(int mode)
 void __init plat_irq_setup_pins(int mode)

+ 13 - 0
drivers/sh/Kconfig

@@ -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.

+ 67 - 2
drivers/sh/intc.c

@@ -27,6 +27,7 @@
 #include <linux/topology.h>
 #include <linux/topology.h>
 #include <linux/bitmap.h>
 #include <linux/bitmap.h>
 #include <linux/cpumask.h>
 #include <linux/cpumask.h>
+#include <asm/sizes.h>
 
 
 #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \
 #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \
 	((shift) | ((width) << 5) | ((fn) << 9) | ((mode) << 13) | \
 	((shift) | ((width) << 5) | ((fn) << 9) | ((mode) << 13) | \
@@ -94,7 +95,8 @@ static DEFINE_SPINLOCK(vector_lock);
 #define SMP_NR(d, x) 1
 #define SMP_NR(d, x) 1
 #endif
 #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 unsigned long ack_handle[NR_IRQS];
 
 
 static inline struct intc_desc_int *get_intc_desc(unsigned int irq)
 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
 	/* set priority level
 	 * - this needs to be at least 2 for 5-bit priorities on 7780
 	 * - 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 */
 	/* enable secondary masking method if present */
 	if (data[!primary])
 	if (data[!primary])
@@ -1037,6 +1039,64 @@ err0:
 	return -ENOMEM;
 	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
 static ssize_t
 show_intc_name(struct sys_device *dev, struct sysdev_attribute *attr, char *buf)
 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;
 	int id = 0;
 
 
 	error = sysdev_class_register(&intc_sysdev_class);
 	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) {
 	if (!error) {
 		list_for_each_entry(d, &intc_list, list) {
 		list_for_each_entry(d, &intc_list, list) {
 			d->sysdev.id = id;
 			d->sysdev.id = id;

+ 9 - 0
include/linux/sh_intc.h

@@ -99,6 +99,15 @@ struct intc_desc symbol __initdata = {					\
 int __init register_intc_controller(struct intc_desc *desc);
 int __init register_intc_controller(struct intc_desc *desc);
 int intc_set_priority(unsigned int irq, unsigned int prio);
 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);
 int reserve_irq_vector(unsigned int irq);
 void reserve_irq_legacy(void);
 void reserve_irq_legacy(void);