MIPS: Loongson 3: Add CPU hotplug support
authorHuacai Chen <chenhc@lemote.com>
Fri, 21 Mar 2014 10:44:09 +0000 (18:44 +0800)
committerRalf Baechle <ralf@linux-mips.org>
Mon, 31 Mar 2014 16:17:12 +0000 (18:17 +0200)
Tips of Loongson's CPU hotplug:
1, To fully shutdown a core in Loongson 3, the target core should go to
   CKSEG1 and flush all L1 cache entries at first. Then, another core
   (usually Core 0) can safely disable the clock of the target core. So
   play_dead() call loongson3_play_dead() via CKSEG1 (both uncached and
   unmmaped).
2, The default clocksource of Loongson is MIPS. Since clock source is a
   global device, timekeeping need the CP0' Count registers of each core
   be synchronous. Thus, when a core is up, we use a SMP_ASK_C0COUNT IPI
   to ask Core-0's Count.

Signed-off-by: Huacai Chen <chenhc@lemote.com>
Signed-off-by: Hongliang Tao <taohl@lemote.com>
Signed-off-by: Hua Yan <yanh@lemote.com>
Tested-by: Alex Smith <alex.smith@imgtec.com>
Reviewed-by: Alex Smith <alex.smith@imgtec.com>
Cc: John Crispin <john@phrozen.org>
Cc: Steven J. Hill <Steven.Hill@imgtec.com>
Cc: Aurelien Jarno <aurelien@aurel32.net>
Cc: linux-mips@linux-mips.org
Cc: Fuxin Zhang <zhangfx@lemote.com>
Cc: Zhangjin Wu <wuzhangjin@gmail.com>
Patchwork: https://patchwork.linux-mips.org/patch/6639
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/include/asm/mach-loongson/irq.h
arch/mips/include/asm/mach-loongson/loongson.h
arch/mips/include/asm/smp.h
arch/mips/loongson/Kconfig
arch/mips/loongson/loongson-3/irq.c
arch/mips/loongson/loongson-3/smp.c

index 0c77b22a663012637c0344a3848691dd275625ac..34560bda6626da7fc7e18fab15744f1671ed5e7c 100644 (file)
@@ -37,6 +37,7 @@
 
 #endif
 
+extern void fixup_irqs(void);
 extern void loongson3_ipi_interrupt(struct pt_regs *regs);
 
 #include_next <irq.h>
index f185907e8ce3ba3fdb666dfefbe6e73c921f5a77..f3fd1eb8e3ddeb3695c49cc838b06841c610da44 100644 (file)
@@ -249,6 +249,9 @@ static inline void do_perfcnt_IRQ(void)
 #define LOONGSON_PXARB_CFG             LOONGSON_REG(LOONGSON_REGBASE + 0x68)
 #define LOONGSON_PXARB_STATUS          LOONGSON_REG(LOONGSON_REGBASE + 0x6c)
 
+/* Chip Config */
+#define LOONGSON_CHIPCFG0              LOONGSON_REG(LOONGSON_REGBASE + 0x80)
+
 /* pcimap */
 
 #define LOONGSON_PCIMAP_PCIMAP_LO0     0x0000003f
@@ -264,9 +267,6 @@ static inline void do_perfcnt_IRQ(void)
 #ifdef CONFIG_CPU_SUPPORTS_CPUFREQ
 #include <linux/cpufreq.h>
 extern struct cpufreq_frequency_table loongson2_clockmod_table[];
-
-/* Chip Config */
-#define LOONGSON_CHIPCFG0              LOONGSON_REG(LOONGSON_REGBASE + 0x80)
 #endif
 
 /*
index eb60087584844381a717bf6dc1ffdf39508dcf1a..efa02acd3dd5593849f470f90749f54b760d525f 100644 (file)
@@ -42,6 +42,7 @@ extern int __cpu_logical_map[NR_CPUS];
 #define SMP_ICACHE_FLUSH       0x4
 /* Used by kexec crashdump to save all cpu's state */
 #define SMP_DUMP               0x8
+#define SMP_ASK_C0COUNT                0x10
 
 extern volatile cpumask_t cpu_callin_map;
 
index a5d46f5efe03d1dea580e0a94f5862b709813ed4..7397be226a06a2a7d0481fac7b2dd476d4fe6d3a 100644 (file)
@@ -79,6 +79,7 @@ config LEMOTE_MACH3A
        select SYS_HAS_CPU_LOONGSON3
        select SYS_HAS_EARLY_PRINTK
        select SYS_SUPPORTS_SMP
+       select SYS_SUPPORTS_HOTPLUG_CPU
        select SYS_SUPPORTS_64BIT_KERNEL
        select SYS_SUPPORTS_HIGHMEM
        select SYS_SUPPORTS_LITTLE_ENDIAN
index 088fd5e4fd64475ab7d7732eee2525fb1ba88d87..f240828181ff47ad8f7b2e807c456447daa42b3a 100644 (file)
@@ -114,3 +114,13 @@ void __init mach_init_irq(void)
 
        set_c0_status(STATUSF_IP2 | STATUSF_IP6);
 }
+
+#ifdef CONFIG_HOTPLUG_CPU
+
+void fixup_irqs(void)
+{
+       irq_cpu_offline();
+       clear_c0_status(ST0_IM);
+}
+
+#endif
index 93483c25b4b98163e8518b7e58ce794ebb52c9da..c665fe16d4c94046568e63a2a24bb6cb3614a836 100644 (file)
 #include <asm/time.h>
 #include <asm/clock.h>
 #include <asm/tlbflush.h>
+#include <asm/cacheflush.h>
 #include <loongson.h>
 
 #include "smp.h"
 
+DEFINE_PER_CPU(int, cpu_state);
+DEFINE_PER_CPU(uint32_t, core0_c0count);
+
 /* read a 32bit value from ipi register */
 #define loongson3_ipi_read32(addr) readl(addr)
 /* read a 64bit value from ipi register */
@@ -158,8 +162,8 @@ loongson3_send_ipi_mask(const struct cpumask *mask, unsigned int action)
 
 void loongson3_ipi_interrupt(struct pt_regs *regs)
 {
-       int cpu = smp_processor_id();
-       unsigned int action;
+       int i, cpu = smp_processor_id();
+       unsigned int action, c0count;
 
        /* Load the ipi register to figure out what we're supposed to do */
        action = loongson3_ipi_read32(ipi_status0_regs[cpu]);
@@ -172,14 +176,24 @@ void loongson3_ipi_interrupt(struct pt_regs *regs)
 
        if (action & SMP_CALL_FUNCTION)
                smp_call_function_interrupt();
+
+       if (action & SMP_ASK_C0COUNT) {
+               BUG_ON(cpu != 0);
+               c0count = read_c0_count();
+               for (i = 1; i < loongson_sysconf.nr_cpus; i++)
+                       per_cpu(core0_c0count, i) = c0count;
+       }
 }
 
+#define MAX_LOOPS 1111
 /*
  * SMP init and finish on secondary CPUs
  */
 static void loongson3_init_secondary(void)
 {
        int i;
+       uint32_t initcount;
+       unsigned int cpu = smp_processor_id();
        unsigned int imask = STATUSF_IP7 | STATUSF_IP6 |
                             STATUSF_IP3 | STATUSF_IP2;
 
@@ -188,6 +202,21 @@ static void loongson3_init_secondary(void)
 
        for (i = 0; i < loongson_sysconf.nr_cpus; i++)
                loongson3_ipi_write32(0xffffffff, ipi_en0_regs[i]);
+
+       per_cpu(cpu_state, cpu) = CPU_ONLINE;
+
+       i = 0;
+       __get_cpu_var(core0_c0count) = 0;
+       loongson3_send_ipi_single(0, SMP_ASK_C0COUNT);
+       while (!__get_cpu_var(core0_c0count)) {
+               i++;
+               cpu_relax();
+       }
+
+       if (i > MAX_LOOPS)
+               i = MAX_LOOPS;
+       initcount = __get_cpu_var(core0_c0count) + i;
+       write_c0_count(initcount);
 }
 
 static void loongson3_smp_finish(void)
@@ -222,6 +251,8 @@ static void __init loongson3_smp_setup(void)
 
 static void __init loongson3_prepare_cpus(unsigned int max_cpus)
 {
+       init_cpu_present(cpu_possible_mask);
+       per_cpu(cpu_state, smp_processor_id()) = CPU_ONLINE;
 }
 
 /*
@@ -255,6 +286,147 @@ static void __init loongson3_cpus_done(void)
 {
 }
 
+#ifdef CONFIG_HOTPLUG_CPU
+
+static int loongson3_cpu_disable(void)
+{
+       unsigned long flags;
+       unsigned int cpu = smp_processor_id();
+
+       if (cpu == 0)
+               return -EBUSY;
+
+       set_cpu_online(cpu, false);
+       cpu_clear(cpu, cpu_callin_map);
+       local_irq_save(flags);
+       fixup_irqs();
+       local_irq_restore(flags);
+       flush_cache_all();
+       local_flush_tlb_all();
+
+       return 0;
+}
+
+
+static void loongson3_cpu_die(unsigned int cpu)
+{
+       while (per_cpu(cpu_state, cpu) != CPU_DEAD)
+               cpu_relax();
+
+       mb();
+}
+
+/* To shutdown a core in Loongson 3, the target core should go to CKSEG1 and
+ * flush all L1 entries at first. Then, another core (usually Core 0) can
+ * safely disable the clock of the target core. loongson3_play_dead() is
+ * called via CKSEG1 (uncached and unmmaped) */
+static void loongson3_play_dead(int *state_addr)
+{
+       register int val;
+       register long cpuid, core, node, count;
+       register void *addr, *base, *initfunc;
+
+       __asm__ __volatile__(
+               "   .set push                     \n"
+               "   .set noreorder                \n"
+               "   li %[addr], 0x80000000        \n" /* KSEG0 */
+               "1: cache 0, 0(%[addr])           \n" /* flush L1 ICache */
+               "   cache 0, 1(%[addr])           \n"
+               "   cache 0, 2(%[addr])           \n"
+               "   cache 0, 3(%[addr])           \n"
+               "   cache 1, 0(%[addr])           \n" /* flush L1 DCache */
+               "   cache 1, 1(%[addr])           \n"
+               "   cache 1, 2(%[addr])           \n"
+               "   cache 1, 3(%[addr])           \n"
+               "   addiu %[sets], %[sets], -1    \n"
+               "   bnez  %[sets], 1b             \n"
+               "   addiu %[addr], %[addr], 0x20  \n"
+               "   li    %[val], 0x7             \n" /* *state_addr = CPU_DEAD; */
+               "   sw    %[val], (%[state_addr]) \n"
+               "   sync                          \n"
+               "   cache 21, (%[state_addr])     \n" /* flush entry of *state_addr */
+               "   .set pop                      \n"
+               : [addr] "=&r" (addr), [val] "=&r" (val)
+               : [state_addr] "r" (state_addr),
+                 [sets] "r" (cpu_data[smp_processor_id()].dcache.sets));
+
+       __asm__ __volatile__(
+               "   .set push                         \n"
+               "   .set noreorder                    \n"
+               "   .set mips64                       \n"
+               "   mfc0  %[cpuid], $15, 1            \n"
+               "   andi  %[cpuid], 0x3ff             \n"
+               "   dli   %[base], 0x900000003ff01000 \n"
+               "   andi  %[core], %[cpuid], 0x3      \n"
+               "   sll   %[core], 8                  \n" /* get core id */
+               "   or    %[base], %[base], %[core]   \n"
+               "   andi  %[node], %[cpuid], 0xc      \n"
+               "   dsll  %[node], 42                 \n" /* get node id */
+               "   or    %[base], %[base], %[node]   \n"
+               "1: li    %[count], 0x100             \n" /* wait for init loop */
+               "2: bnez  %[count], 2b                \n" /* limit mailbox access */
+               "   addiu %[count], -1                \n"
+               "   ld    %[initfunc], 0x20(%[base])  \n" /* get PC via mailbox */
+               "   beqz  %[initfunc], 1b             \n"
+               "   nop                               \n"
+               "   ld    $sp, 0x28(%[base])          \n" /* get SP via mailbox */
+               "   ld    $gp, 0x30(%[base])          \n" /* get GP via mailbox */
+               "   ld    $a1, 0x38(%[base])          \n"
+               "   jr    %[initfunc]                 \n" /* jump to initial PC */
+               "   nop                               \n"
+               "   .set pop                          \n"
+               : [core] "=&r" (core), [node] "=&r" (node),
+                 [base] "=&r" (base), [cpuid] "=&r" (cpuid),
+                 [count] "=&r" (count), [initfunc] "=&r" (initfunc)
+               : /* No Input */
+               : "a1");
+}
+
+void play_dead(void)
+{
+       int *state_addr;
+       unsigned int cpu = smp_processor_id();
+       void (*play_dead_at_ckseg1)(int *);
+
+       idle_task_exit();
+       play_dead_at_ckseg1 =
+               (void *)CKSEG1ADDR((unsigned long)loongson3_play_dead);
+       state_addr = &per_cpu(cpu_state, cpu);
+       mb();
+       play_dead_at_ckseg1(state_addr);
+}
+
+#define CPU_POST_DEAD_FROZEN   (CPU_POST_DEAD | CPU_TASKS_FROZEN)
+static int loongson3_cpu_callback(struct notifier_block *nfb,
+       unsigned long action, void *hcpu)
+{
+       unsigned int cpu = (unsigned long)hcpu;
+
+       switch (action) {
+       case CPU_POST_DEAD:
+       case CPU_POST_DEAD_FROZEN:
+               pr_info("Disable clock for CPU#%d\n", cpu);
+               LOONGSON_CHIPCFG0 &= ~(1 << (12 + cpu));
+               break;
+       case CPU_UP_PREPARE:
+       case CPU_UP_PREPARE_FROZEN:
+               pr_info("Enable clock for CPU#%d\n", cpu);
+               LOONGSON_CHIPCFG0 |= 1 << (12 + cpu);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
+static int register_loongson3_notifier(void)
+{
+       hotcpu_notifier(loongson3_cpu_callback, 0);
+       return 0;
+}
+early_initcall(register_loongson3_notifier);
+
+#endif
+
 struct plat_smp_ops loongson3_smp_ops = {
        .send_ipi_single = loongson3_send_ipi_single,
        .send_ipi_mask = loongson3_send_ipi_mask,
@@ -264,4 +436,8 @@ struct plat_smp_ops loongson3_smp_ops = {
        .boot_secondary = loongson3_boot_secondary,
        .smp_setup = loongson3_smp_setup,
        .prepare_cpus = loongson3_prepare_cpus,
+#ifdef CONFIG_HOTPLUG_CPU
+       .cpu_disable = loongson3_cpu_disable,
+       .cpu_die = loongson3_cpu_die,
+#endif
 };