]> git.ipfire.org Git - people/ms/linux.git/commitdiff
RISC-V: Support cpu hotplug
authorAtish Patra <atish.patra@wdc.com>
Wed, 18 Mar 2020 01:11:44 +0000 (18:11 -0700)
committerPalmer Dabbelt <palmerdabbelt@google.com>
Tue, 31 Mar 2020 18:28:30 +0000 (11:28 -0700)
This patch enable support for cpu hotplug in RISC-V. It uses SBI HSM
extension to online/offline any hart. As a result, the harts are
returned to firmware once they are offline. If the harts are brought
online afterwards, they re-enter Linux kernel as if a secondary hart
booted for the first time. All booting requirements are honored during
this process.

Tested both on QEMU and HighFive Unleashed board with. Test result follows.

---------------------------------------------------
Offline cpu 2
---------------------------------------------------
$ echo 0 > /sys/devices/system/cpu/cpu2/online
[   32.828684] CPU2: off
$ cat /proc/cpuinfo
processor       : 0
hart            : 0
isa             : rv64imafdcsu
mmu             : sv48

processor       : 1
hart            : 1
isa             : rv64imafdcsu
mmu             : sv48

processor       : 3
hart            : 3
isa             : rv64imafdcsu
mmu             : sv48

processor       : 4
hart            : 4
isa             : rv64imafdcsu
mmu             : sv48

processor       : 5
hart            : 5
isa             : rv64imafdcsu
mmu             : sv48

processor       : 6
hart            : 6
isa             : rv64imafdcsu
mmu             : sv48

processor       : 7
hart            : 7
isa             : rv64imafdcsu
mmu             : sv48

---------------------------------------------------
online cpu 2
---------------------------------------------------
$ echo 1 > /sys/devices/system/cpu/cpu2/online
$ cat /proc/cpuinfo
processor       : 0
hart            : 0
isa             : rv64imafdcsu
mmu             : sv48

processor       : 1
hart            : 1
isa             : rv64imafdcsu
mmu             : sv48

processor       : 2
hart            : 2
isa             : rv64imafdcsu
mmu             : sv48

processor       : 3
hart            : 3
isa             : rv64imafdcsu
mmu             : sv48

processor       : 4
hart            : 4
isa             : rv64imafdcsu
mmu             : sv48

processor       : 5
hart            : 5
isa             : rv64imafdcsu
mmu             : sv48

processor       : 6
hart            : 6
isa             : rv64imafdcsu
mmu             : sv48

processor       : 7
hart            : 7
isa             : rv64imafdcsu
mmu             : sv48

Signed-off-by: Atish Patra <atish.patra@wdc.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
arch/riscv/Kconfig
arch/riscv/include/asm/cpu_ops.h
arch/riscv/include/asm/smp.h
arch/riscv/kernel/Makefile
arch/riscv/kernel/cpu-hotplug.c [new file with mode: 0644]
arch/riscv/kernel/cpu_ops_sbi.c
arch/riscv/kernel/setup.c

index cac23a5c110495f57f101514fd820af4151fbdeb..bc713666f00a451a9939cc63f5a1e7b8113e4420 100644 (file)
@@ -20,7 +20,6 @@ config RISCV
        select CLONE_BACKWARDS
        select COMMON_CLK
        select GENERIC_CLOCKEVENTS
-       select GENERIC_CPU_DEVICES
        select GENERIC_IRQ_SHOW
        select GENERIC_PCI_IOMAP
        select GENERIC_SCHED_CLOCK
@@ -254,6 +253,17 @@ config NR_CPUS
        depends on SMP
        default "8"
 
+config HOTPLUG_CPU
+       bool "Support for hot-pluggable CPUs"
+       depends on SMP
+       select GENERIC_IRQ_MIGRATION
+       help
+
+         Say Y here to experiment with turning CPUs off and on.  CPUs
+         can be controlled through /sys/devices/system/cpu.
+
+         Say N if you want to disable CPU hotplug.
+
 choice
        prompt "CPU Tuning"
        default TUNE_GENERIC
index 5ce81a28e1d971faa94fd5c9bf56c4be4efa8c59..a8ec3c5c1bd265ddaff29d158d3c5ecf9e959309 100644 (file)
  *                     is a mechanism for doing so, tests whether it is
  *                     possible to boot the given HART.
  * @cpu_start:         Boots a cpu into the kernel.
+ * @cpu_disable:       Prepares a cpu to die. May fail for some
+ *                     mechanism-specific reason, which will cause the hot
+ *                     unplug to be aborted. Called from the cpu to be killed.
+ * @cpu_stop:          Makes a cpu leave the kernel. Must not fail. Called from
+ *                     the cpu being stopped.
+ * @cpu_is_stopped:    Ensures a cpu has left the kernel. Called from another
+ *                     cpu.
  */
 struct cpu_operations {
        const char      *name;
        int             (*cpu_prepare)(unsigned int cpu);
        int             (*cpu_start)(unsigned int cpu,
                                     struct task_struct *tidle);
+#ifdef CONFIG_HOTPLUG_CPU
+       int             (*cpu_disable)(unsigned int cpu);
+       void            (*cpu_stop)(void);
+       int             (*cpu_is_stopped)(unsigned int cpu);
+#endif
 };
 
 extern const struct cpu_operations *cpu_ops[NR_CPUS];
index 023f74fb8b3bb6feac53bd288e03b3c867842f5a..f4c7cfda6b7fb4ddd663c098714a0e0aa7c59880 100644 (file)
@@ -43,6 +43,13 @@ void riscv_cpuid_to_hartid_mask(const struct cpumask *in, struct cpumask *out);
  */
 #define raw_smp_processor_id() (current_thread_info()->cpu)
 
+#if defined CONFIG_HOTPLUG_CPU
+int __cpu_disable(void);
+void __cpu_die(unsigned int cpu);
+void cpu_stop(void);
+#else
+#endif /* CONFIG_HOTPLUG_CPU */
+
 #else
 
 static inline void show_ipi_stats(struct seq_file *p, int prec)
@@ -69,4 +76,14 @@ static inline void riscv_cpuid_to_hartid_mask(const struct cpumask *in,
 }
 
 #endif /* CONFIG_SMP */
+
+#if defined(CONFIG_HOTPLUG_CPU) && (CONFIG_SMP)
+bool cpu_has_hotplug(unsigned int cpu);
+#else
+static inline bool cpu_has_hotplug(unsigned int cpu)
+{
+       return false;
+}
+#endif
+
 #endif /* _ASM_RISCV_SMP_H */
index 674a23cc4223158e126c39af9df75d57f7e8d639..c121cc491eb8884a18c3aa70e4abfefa61855c80 100644 (file)
@@ -49,5 +49,6 @@ obj-$(CONFIG_RISCV_SBI)               += sbi.o
 ifeq ($(CONFIG_RISCV_SBI), y)
 obj-$(CONFIG_SMP) += cpu_ops_sbi.o
 endif
+obj-$(CONFIG_HOTPLUG_CPU)      += cpu-hotplug.o
 
 clean:
diff --git a/arch/riscv/kernel/cpu-hotplug.c b/arch/riscv/kernel/cpu-hotplug.c
new file mode 100644 (file)
index 0000000..df84e0c
--- /dev/null
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Western Digital Corporation or its affiliates.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/cpu.h>
+#include <linux/sched/hotplug.h>
+#include <asm/irq.h>
+#include <asm/cpu_ops.h>
+#include <asm/sbi.h>
+
+void cpu_stop(void);
+void arch_cpu_idle_dead(void)
+{
+       cpu_stop();
+}
+
+bool cpu_has_hotplug(unsigned int cpu)
+{
+       if (cpu_ops[cpu]->cpu_stop)
+               return true;
+
+       return false;
+}
+
+/*
+ * __cpu_disable runs on the processor to be shutdown.
+ */
+int __cpu_disable(void)
+{
+       int ret = 0;
+       unsigned int cpu = smp_processor_id();
+
+       if (!cpu_ops[cpu] || !cpu_ops[cpu]->cpu_stop)
+               return -EOPNOTSUPP;
+
+       if (cpu_ops[cpu]->cpu_disable)
+               ret = cpu_ops[cpu]->cpu_disable(cpu);
+
+       if (ret)
+               return ret;
+
+       remove_cpu_topology(cpu);
+       set_cpu_online(cpu, false);
+       irq_migrate_all_off_this_cpu();
+
+       return ret;
+}
+
+/*
+ * Called on the thread which is asking for a CPU to be shutdown.
+ */
+void __cpu_die(unsigned int cpu)
+{
+       int ret = 0;
+
+       if (!cpu_wait_death(cpu, 5)) {
+               pr_err("CPU %u: didn't die\n", cpu);
+               return;
+       }
+       pr_notice("CPU%u: off\n", cpu);
+
+       /* Verify from the firmware if the cpu is really stopped*/
+       if (cpu_ops[cpu]->cpu_is_stopped)
+               ret = cpu_ops[cpu]->cpu_is_stopped(cpu);
+       if (ret)
+               pr_warn("CPU%d may not have stopped: %d\n", cpu, ret);
+}
+
+/*
+ * Called from the idle thread for the CPU which has been shutdown.
+ */
+void cpu_stop(void)
+{
+       idle_task_exit();
+
+       (void)cpu_report_death();
+
+       cpu_ops[smp_processor_id()]->cpu_stop();
+       /* It should never reach here */
+       BUG();
+}
index 66f3cded91f5592d4b4c1d7131fe58a618a32a42..685fae72b7f5c758d83e1ebe4fdf40eff7235848 100644 (file)
@@ -74,8 +74,42 @@ static int sbi_cpu_prepare(unsigned int cpuid)
        return 0;
 }
 
+#ifdef CONFIG_HOTPLUG_CPU
+static int sbi_cpu_disable(unsigned int cpuid)
+{
+       if (!cpu_ops_sbi.cpu_stop)
+               return -EOPNOTSUPP;
+       return 0;
+}
+
+static void sbi_cpu_stop(void)
+{
+       int ret;
+
+       ret = sbi_hsm_hart_stop();
+       pr_crit("Unable to stop the cpu %u (%d)\n", smp_processor_id(), ret);
+}
+
+static int sbi_cpu_is_stopped(unsigned int cpuid)
+{
+       int rc;
+       int hartid = cpuid_to_hartid_map(cpuid);
+
+       rc = sbi_hsm_hart_get_status(hartid);
+
+       if (rc == SBI_HSM_HART_STATUS_STOPPED)
+               return 0;
+       return rc;
+}
+#endif
+
 const struct cpu_operations cpu_ops_sbi = {
        .name           = "sbi",
        .cpu_prepare    = sbi_cpu_prepare,
        .cpu_start      = sbi_cpu_start,
+#ifdef CONFIG_HOTPLUG_CPU
+       .cpu_disable    = sbi_cpu_disable,
+       .cpu_stop       = sbi_cpu_stop,
+       .cpu_is_stopped = sbi_cpu_is_stopped,
+#endif
 };
index 07f4e7503223c5466d38af98f0dd8a51677568ff..145128a7e560e4791fadb7d7683f4cc8c89c9303 100644 (file)
 #include <linux/of_platform.h>
 #include <linux/sched/task.h>
 #include <linux/swiotlb.h>
+#include <linux/smp.h>
 
 #include <asm/clint.h>
+#include <asm/cpu_ops.h>
 #include <asm/setup.h>
 #include <asm/sections.h>
 #include <asm/pgtable.h>
-#include <asm/smp.h>
 #include <asm/sbi.h>
 #include <asm/tlbflush.h>
 #include <asm/thread_info.h>
@@ -47,6 +48,7 @@ struct screen_info screen_info = {
  */
 atomic_t hart_lottery __section(.sdata);
 unsigned long boot_cpu_hartid;
+static DEFINE_PER_CPU(struct cpu, cpu_devices);
 
 void __init parse_dtb(void)
 {
@@ -94,3 +96,18 @@ void __init setup_arch(char **cmdline_p)
 
        riscv_fill_hwcap();
 }
+
+static int __init topology_init(void)
+{
+       int i;
+
+       for_each_possible_cpu(i) {
+               struct cpu *cpu = &per_cpu(cpu_devices, i);
+
+               cpu->hotpluggable = cpu_has_hotplug(i);
+               register_cpu(cpu, i);
+       }
+
+       return 0;
+}
+subsys_initcall(topology_init);