sparc32,leon: Implemented SMP IPIs for LEON CPU
[cascardo/linux.git] / arch / sparc / kernel / leon_smp.c
index de9506d..d95e456 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/smp.h>
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
+#include <linux/of.h>
 #include <linux/init.h>
 #include <linux/spinlock.h>
 #include <linux/mm.h>
@@ -29,6 +30,7 @@
 #include <asm/ptrace.h>
 #include <asm/atomic.h>
 #include <asm/irq_regs.h>
+#include <asm/traps.h>
 
 #include <asm/delay.h>
 #include <asm/irq.h>
@@ -52,6 +54,10 @@ static int smp_processors_ready;
 extern volatile unsigned long cpu_callin_map[NR_CPUS];
 extern cpumask_t smp_commenced_mask;
 void __init leon_configure_cache_smp(void);
+static void leon_ipi_init(void);
+
+/* IRQ number of LEON IPIs */
+int leon_ipi_irq = LEON3_IRQ_IPI_DEFAULT;
 
 static inline unsigned long do_swap(volatile unsigned long *ptr,
                                    unsigned long val)
@@ -176,13 +182,16 @@ void __init leon_boot_cpus(void)
        int nrcpu = leon_smp_nrcpus();
        int me = smp_processor_id();
 
+       /* Setup IPI */
+       leon_ipi_init();
+
        printk(KERN_INFO "%d:(%d:%d) cpus mpirq at 0x%x\n", (unsigned int)me,
               (unsigned int)nrcpu, (unsigned int)NR_CPUS,
               (unsigned int)&(leon3_irqctrl_regs->mpstatus));
 
        leon_enable_irq_cpu(LEON3_IRQ_CROSS_CALL, me);
        leon_enable_irq_cpu(LEON3_IRQ_TICKER, me);
-       leon_enable_irq_cpu(LEON3_IRQ_RESCHEDULE, me);
+       leon_enable_irq_cpu(leon_ipi_irq, me);
 
        leon_smp_setbroadcast(1 << LEON3_IRQ_TICKER);
 
@@ -237,7 +246,7 @@ int __cpuinit leon_boot_one_cpu(int i)
        } else {
                leon_enable_irq_cpu(LEON3_IRQ_CROSS_CALL, i);
                leon_enable_irq_cpu(LEON3_IRQ_TICKER, i);
-               leon_enable_irq_cpu(LEON3_IRQ_RESCHEDULE, i);
+               leon_enable_irq_cpu(leon_ipi_irq, i);
        }
 
        local_flush_cache_all();
@@ -293,6 +302,99 @@ void leon_irq_rotate(int cpu)
 {
 }
 
+struct leon_ipi_work {
+       int single;
+       int msk;
+       int resched;
+};
+
+static DEFINE_PER_CPU_SHARED_ALIGNED(struct leon_ipi_work, leon_ipi_work);
+
+/* Initialize IPIs on the LEON, in order to save IRQ resources only one IRQ
+ * is used for all three types of IPIs.
+ */
+static void __init leon_ipi_init(void)
+{
+       int cpu, len;
+       struct leon_ipi_work *work;
+       struct property *pp;
+       struct device_node *rootnp;
+       struct tt_entry *trap_table;
+       unsigned long flags;
+
+       /* Find IPI IRQ or stick with default value */
+       rootnp = of_find_node_by_path("/ambapp0");
+       if (rootnp) {
+               pp = of_find_property(rootnp, "ipi_num", &len);
+               if (pp && (*(int *)pp->value))
+                       leon_ipi_irq = *(int *)pp->value;
+       }
+       printk(KERN_INFO "leon: SMP IPIs at IRQ %d\n", leon_ipi_irq);
+
+       /* Adjust so that we jump directly to smpleon_ipi */
+       local_irq_save(flags);
+       trap_table = &sparc_ttable[SP_TRAP_IRQ1 + (leon_ipi_irq - 1)];
+       trap_table->inst_three += smpleon_ipi - real_irq_entry;
+       local_flush_cache_all();
+       local_irq_restore(flags);
+
+       for_each_possible_cpu(cpu) {
+               work = &per_cpu(leon_ipi_work, cpu);
+               work->single = work->msk = work->resched = 0;
+       }
+}
+
+static void leon_ipi_single(int cpu)
+{
+       struct leon_ipi_work *work = &per_cpu(leon_ipi_work, cpu);
+
+       /* Mark work */
+       work->single = 1;
+
+       /* Generate IRQ on the CPU */
+       set_cpu_int(cpu, leon_ipi_irq);
+}
+
+static void leon_ipi_mask_one(int cpu)
+{
+       struct leon_ipi_work *work = &per_cpu(leon_ipi_work, cpu);
+
+       /* Mark work */
+       work->msk = 1;
+
+       /* Generate IRQ on the CPU */
+       set_cpu_int(cpu, leon_ipi_irq);
+}
+
+static void leon_ipi_resched(int cpu)
+{
+       struct leon_ipi_work *work = &per_cpu(leon_ipi_work, cpu);
+
+       /* Mark work */
+       work->resched = 1;
+
+       /* Generate IRQ on the CPU (any IRQ will cause resched) */
+       set_cpu_int(cpu, leon_ipi_irq);
+}
+
+void leonsmp_ipi_interrupt(void)
+{
+       struct leon_ipi_work *work = &__get_cpu_var(leon_ipi_work);
+
+       if (work->single) {
+               work->single = 0;
+               smp_call_function_single_interrupt();
+       }
+       if (work->msk) {
+               work->msk = 0;
+               smp_call_function_interrupt();
+       }
+       if (work->resched) {
+               work->resched = 0;
+               smp_resched_interrupt();
+       }
+}
+
 static struct smp_funcall {
        smpfunc_t func;
        unsigned long arg1;
@@ -446,6 +548,9 @@ void __init leon_init_smp(void)
        BTFIXUPSET_CALL(smp_cross_call, leon_cross_call, BTFIXUPCALL_NORM);
        BTFIXUPSET_CALL(__hard_smp_processor_id, __leon_processor_id,
                        BTFIXUPCALL_NORM);
+       BTFIXUPSET_CALL(smp_ipi_resched, leon_ipi_resched, BTFIXUPCALL_NORM);
+       BTFIXUPSET_CALL(smp_ipi_single, leon_ipi_single, BTFIXUPCALL_NORM);
+       BTFIXUPSET_CALL(smp_ipi_mask_one, leon_ipi_mask_one, BTFIXUPCALL_NORM);
 }
 
 #endif /* CONFIG_SPARC_LEON */