Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/multikernel/device-tree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ only the boot handoff knows::
multikernel,ipi-pages = <65>;
multikernel,host-ipi-buffer = <...>; /* the host's ring */
multikernel,host-ipi-pages = <...>;
multikernel,host-ipi-cpu = <...>; /* physical doorbell CPU, u64 */
};

On x86 the ``SETUP_MULTIKERNEL`` setup_data entry points at the page and
Expand Down
34 changes: 34 additions & 0 deletions Documentation/multikernel/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,37 @@ Restrictions
- Instance files are read-only; an instance's resources change through
overlays targeting ``/instances/<name>``.
- Rollback (``rmdir`` on a transaction) cannot destroy a running instance.

RISC-V entry stub
=================

OpenSBI ``HART_START`` does not invalidate a stopped hart's instruction
cache, and RFENCE cannot target that hart. The host therefore first starts
every assigned hart at an immutable host-text trampoline. Every hart made
available to the pool has executed a local ``fence.i`` immediately before
``HART_STOP``, so the trampoline cannot be fetched from an older cache line.
The trampoline executes another ``fence.i`` and immediately calls
``HART_STOP``, making a newly copied per-instance stub visible before its
first fetch. The host confirms ``STOPPED`` before the real start. It repeats
the handshake before donating a hart to an active instance through CPU
hot-add.

The host copies one immutable entry stub into the instance control block.
That stub begins with ``fence.i`` before loading the current entry from the
preceding context page and jumping to it. It preserves the boot ABI
registers ``a0`` and ``a1``.

The multikernel manifest advertises the stub address to the spawn kernel.
Before starting a secondary hart, the spawn kernel changes the context entry
to ``secondary_start_sbi`` and passes the normal per-CPU boot data in ``a1``.
Thus every HSM start reaches the immutable stub before entering replaceable
Image code; the primary still receives its DTB and secondaries still receive
their SBI boot data.

Every local HSM stop path also executes ``fence.i`` immediately before the
hart enters firmware, keeping the immutable host trampoline safe to fetch on
the next start.

Respawns update only the host-owned entry data, never the copied instructions.
The priming handshake makes the immutable stub visible; the stub's own
``fence.i`` then makes the newly written Image visible before the jump.
2 changes: 2 additions & 0 deletions arch/riscv/Kbuild
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ obj-$(CONFIG_KVM) += kvm/

obj-$(CONFIG_ARCH_SUPPORTS_KEXEC_PURGATORY) += purgatory/

obj-$(CONFIG_MULTIKERNEL) += multikernel/

# for cleaning
subdir- += boot
11 changes: 11 additions & 0 deletions arch/riscv/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,17 @@ config ARCH_SELECTS_KEXEC_FILE
config ARCH_SUPPORTS_KEXEC_PURGATORY
def_bool ARCH_SUPPORTS_KEXEC_FILE

config ARCH_SUPPORTS_MULTIKERNEL
def_bool y
depends on 64BIT
depends on RISCV_SBI
depends on KEXEC_FILE
depends on HOTPLUG_CPU
depends on !RISCV_M_MODE

config ARCH_HAS_MK_POOL_STATE
def_bool ARCH_SUPPORTS_MULTIKERNEL

config ARCH_SUPPORTS_CRASH_DUMP
def_bool y

Expand Down
8 changes: 8 additions & 0 deletions arch/riscv/include/asm/cpu_ops_sbi.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@

extern const struct cpu_operations cpu_ops_sbi;

int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr,
unsigned long priv);

#ifdef CONFIG_HOTPLUG_CPU
int sbi_hsm_hart_stop(void);
int sbi_hsm_hart_get_status(unsigned long hartid);
#endif

/**
* struct sbi_hart_boot_data - Hart specific boot used during booting and
* cpu hotplug.
Expand Down
57 changes: 57 additions & 0 deletions arch/riscv/include/asm/multikernel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _ASM_RISCV_MULTIKERNEL_H
#define _ASM_RISCV_MULTIKERNEL_H

#ifndef __ASSEMBLY__

#include <linux/errno.h>
#include <linux/sizes.h>
#include <linux/types.h>

#include <asm/page.h>
#include <asm/smp.h>

/*
* Hart IDs are sparse firmware identifiers and may exceed NR_CPUS. Keep
* them as values and always translate through the architecture CPU maps.
*/
static inline u64 arch_cpu_physical_id(int cpu)
{
return cpuid_to_hartid_map(cpu);
}

static inline int arch_cpu_from_physical_id(u64 hartid)
{
if (hartid == INVALID_HARTID)
return -ENOENT;

return riscv_hartid_to_cpuid(hartid);
}

/*
* The RISC-V spawn path will use one page for its context, up to 64 KiB
* for the generated DTB, and one page for the fence.i entry stub. SBI HSM
* starts a hart in the existing address space, so no trampoline page tables
* are needed.
*/
#define MK_CTRL_BLOCK_SIZE (SZ_64K + 2 * PAGE_SIZE)

struct mk_riscv_spawn_context {
unsigned long image_entry;
};

/* Per-instance state, allocated from the control block on first spawn. */
struct mk_instance_arch {
struct mk_riscv_spawn_context *ctx;
phys_addr_t ctx_phys;
const void *stub;
phys_addr_t stub_phys;
};

extern char mk_riscv_entry_stub_start[];
extern char mk_riscv_entry_stub_end[];
extern char mk_riscv_entry_fence_stop[];

#endif /* !__ASSEMBLY__ */

#endif /* _ASM_RISCV_MULTIKERNEL_H */
62 changes: 55 additions & 7 deletions arch/riscv/kernel/cpu_ops_sbi.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
* Copyright (c) 2020 Western Digital Corporation or its affiliates.
*/

#include <linux/cacheflush.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/multikernel.h>
#include <linux/sched/task_stack.h>
#include <asm/cpu_ops.h>
#include <asm/cpu_ops_sbi.h>
Expand All @@ -23,45 +26,86 @@ const struct cpu_operations cpu_ops_sbi;
*/
static struct sbi_hart_boot_data boot_data[NR_CPUS];

static int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr,
unsigned long priv)
static int sbi_hsm_err_map_linux_errno(long err)
{
switch (err) {
case SBI_ERR_ALREADY_AVAILABLE:
case SBI_ERR_ALREADY_STARTED:
case SBI_ERR_ALREADY_STOPPED:
return -EALREADY;
case SBI_ERR_FAILURE:
return -EIO;
default:
return sbi_err_map_linux_errno(err);
}
}

int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr,
unsigned long priv)
{
struct sbiret ret;

ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_START,
hartid, saddr, priv, 0, 0, 0);
if (ret.error)
return sbi_err_map_linux_errno(ret.error);
return sbi_hsm_err_map_linux_errno(ret.error);
else
return 0;
}

#ifdef CONFIG_HOTPLUG_CPU
static int sbi_hsm_hart_stop(void)
int sbi_hsm_hart_stop(void)
{
struct sbiret ret;

/* A stopped hart cannot receive the remote fence for its next entry. */
local_flush_icache_all();
ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_STOP, 0, 0, 0, 0, 0, 0);

if (ret.error)
return sbi_err_map_linux_errno(ret.error);
return sbi_hsm_err_map_linux_errno(ret.error);
else
return 0;
}

static int sbi_hsm_hart_get_status(unsigned long hartid)
int sbi_hsm_hart_get_status(unsigned long hartid)
{
struct sbiret ret;

ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_STATUS,
hartid, 0, 0, 0, 0, 0);
if (ret.error)
return sbi_err_map_linux_errno(ret.error);
return sbi_hsm_err_map_linux_errno(ret.error);
else
return ret.value;
}
#endif

#ifdef CONFIG_MULTIKERNEL
static int sbi_spawn_cpu_entry(unsigned long *boot_addr)
{
struct mk_riscv_spawn_context *ctx;
phys_addr_t stub_addr;

if (!mk_is_spawn_kernel())
return 0;

stub_addr = mk_manifest_entry_stub_phys();
if (stub_addr < PAGE_SIZE || !IS_ALIGNED(stub_addr, PAGE_SIZE) ||
!pfn_valid(PHYS_PFN(stub_addr - PAGE_SIZE)) ||
!pfn_valid(PHYS_PFN(stub_addr))) {
pr_err_once("SBI: invalid multikernel entry stub address %pa\n",
&stub_addr);
return -EINVAL;
}

ctx = phys_to_virt(stub_addr - PAGE_SIZE);
WRITE_ONCE(ctx->image_entry, *boot_addr);
*boot_addr = stub_addr;
return 0;
}
#endif

static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle)
{
unsigned long boot_addr = __pa_symbol(secondary_start_sbi);
Expand All @@ -73,6 +117,10 @@ static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle)
smp_mb();
bdata->task_ptr = tidle;
bdata->stack_ptr = task_pt_regs(tidle);
#ifdef CONFIG_MULTIKERNEL
if (sbi_spawn_cpu_entry(&boot_addr))
return -EINVAL;
#endif
/* Make sure boot data is updated */
smp_mb();
hsm_data = __pa(bdata);
Expand Down
13 changes: 13 additions & 0 deletions arch/riscv/kernel/reset.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Copyright (C) 2012 Regents of the University of California
*/

#include <linux/multikernel.h>
#include <linux/panic.h>
#include <linux/reboot.h>
#include <linux/pm.h>

Expand All @@ -17,18 +19,29 @@ EXPORT_SYMBOL(pm_power_off);

void machine_restart(char *cmd)
{
if (mk_is_spawn_kernel() && panic_in_progress())
mk_panic_to_pool();
if (mk_is_spawn_kernel())
mk_halt_to_pool();

do_kernel_restart(cmd);
while (1);
}

void machine_halt(void)
{
if (mk_is_spawn_kernel())
mk_halt_to_pool();

do_kernel_power_off();
default_power_off();
}

void machine_power_off(void)
{
if (mk_is_spawn_kernel())
mk_halt_to_pool();

do_kernel_power_off();
default_power_off();
}
16 changes: 10 additions & 6 deletions arch/riscv/kernel/sbi.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <linux/bits.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/multikernel.h>
#include <linux/pm.h>
#include <linux/reboot.h>
#include <asm/sbi.h>
Expand Down Expand Up @@ -648,6 +649,7 @@ int sbi_debug_console_read(char *bytes, unsigned int num_bytes)

void __init sbi_init(void)
{
bool spawn_kernel = mk_is_spawn_kernel();
bool srst_power_off = false;
int ret;

Expand Down Expand Up @@ -682,11 +684,13 @@ void __init sbi_init(void)
if (sbi_spec_version >= sbi_mk_version(0, 3) &&
sbi_probe_extension(SBI_EXT_SRST)) {
pr_info("SBI SRST extension detected\n");
register_platform_power_off(sbi_srst_power_off);
srst_power_off = true;
sbi_srst_reboot_nb.notifier_call = sbi_srst_reboot;
sbi_srst_reboot_nb.priority = 192;
register_restart_handler(&sbi_srst_reboot_nb);
if (!spawn_kernel) {
register_platform_power_off(sbi_srst_power_off);
srst_power_off = true;
sbi_srst_reboot_nb.notifier_call = sbi_srst_reboot;
sbi_srst_reboot_nb.priority = 192;
register_restart_handler(&sbi_srst_reboot_nb);
}
}
if (sbi_spec_version >= sbi_mk_version(2, 0) &&
sbi_probe_extension(SBI_EXT_DBCN) > 0) {
Expand All @@ -704,6 +708,6 @@ void __init sbi_init(void)
__sbi_rfence = __sbi_rfence_v01;
}

if (!srst_power_off)
if (!spawn_kernel && !srst_power_off)
sbi_set_power_off();
}
4 changes: 4 additions & 0 deletions arch/riscv/kernel/setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/memblock.h>
#include <linux/multikernel.h>
#include <linux/panic.h>
#include <linux/sched.h>
#include <linux/console.h>
#include <linux/of_fdt.h>
Expand Down Expand Up @@ -316,6 +318,8 @@ extern void __init init_rt_signal_env(void);
void __init setup_arch(char **cmdline_p)
{
parse_dtb();
if (mk_is_spawn_kernel())
set_arch_panic_timeout(-1, 0);
setup_initial_init_mm(_stext, _etext, _edata, _end);

*cmdline_p = boot_command_line;
Expand Down
13 changes: 13 additions & 0 deletions arch/riscv/kernel/smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <linux/module.h>
#include <linux/kexec.h>
#include <linux/kgdb.h>
#include <linux/multikernel.h>
#include <linux/percpu.h>
#include <linux/profile.h>
#include <linux/smp.h>
Expand Down Expand Up @@ -81,11 +82,23 @@ int riscv_hartid_to_cpuid(unsigned long hartid)

static void ipi_stop(void)
{
if (mk_is_spawn_kernel())
mk_enter_pool_state(NULL);

set_cpu_online(smp_processor_id(), false);
while (1)
wait_for_interrupt();
}

void __noreturn panic_smp_self_stop(void)
{
if (mk_is_spawn_kernel())
mk_enter_pool_state(NULL);

for (;;)
cpu_relax();
}

#ifdef CONFIG_KEXEC_CORE
static atomic_t waiting_for_crash_ipi = ATOMIC_INIT(0);

Expand Down
3 changes: 3 additions & 0 deletions arch/riscv/multikernel/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only

obj-y += spawn.o entry.o
Loading