From ced11cc78d38479ee2f52d3327cbc5afa20f9c08 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Sun, 30 Aug 2026 21:55:57 +0530 Subject: [PATCH 1/9] multikernel: require OF The instance restore and ring handoff paths consume the live OF tree. Without CONFIG_OF, x86 multikernel configurations compile references to OF globals that cannot link and cannot restore a spawn at runtime. Reject that unusable configuration in Kconfig. Signed-off-by: Utkarsh Maurya --- kernel/multikernel/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/multikernel/Kconfig b/kernel/multikernel/Kconfig index cec1ea32d83361..e7e242d0feac37 100644 --- a/kernel/multikernel/Kconfig +++ b/kernel/multikernel/Kconfig @@ -17,6 +17,7 @@ config MULTIKERNEL depends on KEXEC_CORE depends on MEMORY_HOTPLUG depends on MEMORY_HOTREMOVE + depends on OF select LIBFDT select OF_DYNAMIC if OF help From eed1126b7f23b234f1b21d69caec166b88954763 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 21:53:51 +0530 Subject: [PATCH 2/9] riscv: add multikernel SBI HSM helpers and spawn state RISC-V parks CPUs in firmware with SBI HSM, so multikernel must reuse the architecture's HART_START, HART_STOP and HART_STATUS wrappers. Make those helpers available to RISC-V architecture code while retaining the existing SBI-to-Linux errno mapping. Add only the per-instance context and entry-stub bookkeeping required by the lifecycle implementation. Hart IDs remain physical firmware identifiers and are never used as array indexes. Link: https://github.com/multikernel/linux/issues/23 Signed-off-by: Utkarsh Maurya --- arch/riscv/include/asm/cpu_ops_sbi.h | 8 ++++++++ arch/riscv/include/asm/multikernel.h | 14 +++++++++----- arch/riscv/kernel/cpu_ops_sbi.c | 8 ++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/arch/riscv/include/asm/cpu_ops_sbi.h b/arch/riscv/include/asm/cpu_ops_sbi.h index 776fa55fbaa456..dd477010d11048 100644 --- a/arch/riscv/include/asm/cpu_ops_sbi.h +++ b/arch/riscv/include/asm/cpu_ops_sbi.h @@ -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. diff --git a/arch/riscv/include/asm/multikernel.h b/arch/riscv/include/asm/multikernel.h index 5390332ccb3061..b367bcd5c3fd41 100644 --- a/arch/riscv/include/asm/multikernel.h +++ b/arch/riscv/include/asm/multikernel.h @@ -36,12 +36,16 @@ static inline int arch_cpu_from_physical_id(u64 hartid) */ #define MK_CTRL_BLOCK_SIZE (SZ_64K + 2 * PAGE_SIZE) -/* - * Architecture-private spawn and park state is added with the SBI HSM - * and Image loader support. The compile-only skeleton intentionally has - * none. - */ +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; }; struct mk_pool_arch { diff --git a/arch/riscv/kernel/cpu_ops_sbi.c b/arch/riscv/kernel/cpu_ops_sbi.c index 00aff669f5f2f5..ee75bd9eed0458 100644 --- a/arch/riscv/kernel/cpu_ops_sbi.c +++ b/arch/riscv/kernel/cpu_ops_sbi.c @@ -23,8 +23,8 @@ 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) +int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr, + unsigned long priv) { struct sbiret ret; @@ -37,7 +37,7 @@ static int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr, } #ifdef CONFIG_HOTPLUG_CPU -static int sbi_hsm_hart_stop(void) +int sbi_hsm_hart_stop(void) { struct sbiret ret; @@ -49,7 +49,7 @@ static int sbi_hsm_hart_stop(void) return 0; } -static int sbi_hsm_hart_get_status(unsigned long hartid) +int sbi_hsm_hart_get_status(unsigned long hartid) { struct sbiret ret; From d0b12c942b554e3976d92ac0008c38a29cb931f5 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 22:20:09 +0530 Subject: [PATCH 3/9] riscv: add multikernel fence.i entry stub OpenSBI does not invalidate a stopped hart's instruction cache when HART_START restarts it, and an SBI remote fence cannot target a stopped hart. Reusing an Image address can therefore execute stale instructions. Add an immutable, relocation-free entry stub whose first instruction is fence.i and whose target comes from the adjacent data page. Publish its address through the multikernel manifest and route spawn-kernel secondary starts through it as well as the primary start. The stub preserves a0 and a1, so the primary receives its DTB and secondaries receive their normal SBI boot data. Flush the local instruction cache before every HART_STOP so a later start cannot retain an older stub line. Link: https://github.com/multikernel/linux/issues/24 Signed-off-by: Utkarsh Maurya --- Documentation/multikernel/usage.rst | 23 +++++++++++++++ arch/riscv/kernel/cpu_ops_sbi.c | 34 +++++++++++++++++++++ arch/riscv/multikernel/Makefile | 2 +- arch/riscv/multikernel/entry.S | 24 +++++++++++++++ include/linux/multikernel.h | 24 +++++++++++++++ kernel/multikernel/manifest.c | 46 +++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 arch/riscv/multikernel/entry.S diff --git a/Documentation/multikernel/usage.rst b/Documentation/multikernel/usage.rst index 8ed85d8ead7e18..d1fca6fe0a7cf5 100644 --- a/Documentation/multikernel/usage.rst +++ b/Documentation/multikernel/usage.rst @@ -83,3 +83,26 @@ Restrictions - Instance files are read-only; an instance's resources change through overlays targeting ``/instances/``. - 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 copies one +immutable entry stub into its control block. The 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, so the stub's first fetch cannot reuse an older line. + +Respawns update only the host-owned entry data, never the copied instructions. +The immutable stub can safely execute from a stale cache long enough to run +``fence.i``, which makes the newly written Image visible before the jump. diff --git a/arch/riscv/kernel/cpu_ops_sbi.c b/arch/riscv/kernel/cpu_ops_sbi.c index ee75bd9eed0458..ec648df6abf53e 100644 --- a/arch/riscv/kernel/cpu_ops_sbi.c +++ b/arch/riscv/kernel/cpu_ops_sbi.c @@ -5,8 +5,11 @@ * Copyright (c) 2020 Western Digital Corporation or its affiliates. */ +#include #include +#include #include +#include #include #include #include @@ -41,6 +44,8 @@ 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) @@ -62,6 +67,31 @@ int sbi_hsm_hart_get_status(unsigned long hartid) } #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); @@ -73,6 +103,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); diff --git a/arch/riscv/multikernel/Makefile b/arch/riscv/multikernel/Makefile index 3ef62e5996af69..cd2327badca70a 100644 --- a/arch/riscv/multikernel/Makefile +++ b/arch/riscv/multikernel/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-y += spawn.o +obj-y += spawn.o entry.o diff --git a/arch/riscv/multikernel/entry.S b/arch/riscv/multikernel/entry.S new file mode 100644 index 00000000000000..4b77b417d8ad36 --- /dev/null +++ b/arch/riscv/multikernel/entry.S @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include + +.section ".text", "ax" + +.option push +.option norvc +.option norelax + +SYM_CODE_START(mk_riscv_entry_stub_start) + fence.i + auipc t0, 0 + li t1, -PAGE_SIZE + and t0, t0, t1 + add t0, t0, t1 + ld t0, 0(t0) + jr t0 +.globl mk_riscv_entry_stub_end +mk_riscv_entry_stub_end: +SYM_CODE_END(mk_riscv_entry_stub_start) + +.option pop diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index b470f10b4890d3..b12be567b2afc0 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -951,6 +951,13 @@ void mk_register_cpus_from_manifest(void); /* Accept the manifest handed over at boot (spawn kernels) */ void mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len); +/* True after early boot observed a multikernel manifest handoff. */ +bool mk_is_spawn_kernel(void); + +/* Host-owned entry stub recorded in the manifest, or 0 if absent. */ +phys_addr_t mk_manifest_entry_stub_phys(void); +int mk_manifest_set_entry_stub(struct kimage *image, phys_addr_t entry); + /* Build the manifest for a spawn (host, kexec path) */ int mk_manifest_finalize(struct kimage *image); #else @@ -1005,6 +1012,22 @@ static inline void mk_register_cpus_from_manifest(void) static inline void mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len) { } + +static inline bool mk_is_spawn_kernel(void) +{ + return false; +} + +static inline phys_addr_t mk_manifest_entry_stub_phys(void) +{ + return 0; +} + +static inline int mk_manifest_set_entry_stub(struct kimage *image, + phys_addr_t entry) +{ + return -ENODEV; +} #endif /** @@ -1013,6 +1036,7 @@ static inline void mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len) #define MK_DT_CONFIG_VERSION_1 1 #define MK_DT_CONFIG_CURRENT MK_DT_CONFIG_VERSION_1 #define MK_FDT_COMPATIBLE "multikernel-v1" +#define MK_FDT_ENTRY_STUB "entry-stub" /** * Property Names diff --git a/kernel/multikernel/manifest.c b/kernel/multikernel/manifest.c index c4afb1edbc4e31..ee38faf3578a73 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -28,12 +28,46 @@ /* Physical address of the manifest this kernel booted with, 0 if none */ static phys_addr_t mk_manifest_fdt_phys; +static phys_addr_t mk_manifest_entry_stub; +static bool mk_spawn_kernel; phys_addr_t mk_manifest_phys(void) { return mk_manifest_fdt_phys; } +bool mk_is_spawn_kernel(void) +{ + return READ_ONCE(mk_spawn_kernel); +} + +phys_addr_t mk_manifest_entry_stub_phys(void) +{ + return mk_manifest_entry_stub; +} + +int mk_manifest_set_entry_stub(struct kimage *image, phys_addr_t entry) +{ + void *fdt; + int ret; + + if (!image || !image->mk_manifest || !entry) + return -EINVAL; + + fdt = phys_to_virt(image->mk_manifest); + ret = fdt_open_into(fdt, fdt, PAGE_SIZE); + if (!ret) + ret = fdt_setprop_u64(fdt, 0, MK_FDT_ENTRY_STUB, entry); + if (!ret) + ret = fdt_pack(fdt); + if (!ret) + return 0; + + pr_err("multikernel: failed to publish entry stub: %s\n", + fdt_strerror(ret)); + return ret == -FDT_ERR_NOSPACE ? -E2BIG : -EINVAL; +} + /** * mk_manifest_populate() - Accept the manifest handed over at boot * @fdt_phys: Physical address of the manifest FDT @@ -45,7 +79,9 @@ phys_addr_t mk_manifest_phys(void) */ void __init mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len) { + const fdt64_t *entry_stub; void *fdt = NULL; + int len; int err = 0; pr_info("multikernel: processing manifest at 0x%llx (size: %llu)\n", @@ -72,6 +108,16 @@ void __init mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len) goto out; } + entry_stub = fdt_getprop(fdt, 0, MK_FDT_ENTRY_STUB, &len); + if (entry_stub) { + if (len != sizeof(*entry_stub)) { + err = -EINVAL; + pr_warn("multikernel: manifest has invalid entry stub\n"); + goto out; + } + mk_manifest_entry_stub = fdt64_to_cpu(*entry_stub); + } + mk_manifest_fdt_phys = fdt_phys; pr_info("multikernel: manifest accepted\n"); From eb89a3aaaa2406ef869d697fb015dfec23ddd31b Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 22:44:32 +0530 Subject: [PATCH 4/9] riscv: implement multikernel SBI HSM lifecycle Implement the RISC-V multikernel CPU lifecycle with direct SBI HSM calls. A spawn is allowed only after HART_STATUS reaches STOPPED; STARTED, START_PENDING and STOP_PENDING are polled with a bounded timeout. Allocate the immutable entry stub and mutable target context from the instance control block, publish the stub in the manifest, and start the physical hart with the Image entry and DTB boot ABI. Use physical hart IDs for host doorbells and confirm firmware stop state before release or memory reclaim. RISC-V deliberately reports force-stop as unsupported because HSM has no remote HART_STOP operation. Link: https://github.com/multikernel/linux/issues/23 Link: https://github.com/multikernel/linux/issues/24 Signed-off-by: Utkarsh Maurya --- Documentation/multikernel/device-tree.rst | 1 + arch/riscv/Kconfig | 3 + arch/riscv/include/asm/multikernel.h | 3 + arch/riscv/multikernel/spawn.c | 161 +++++++++++++++++++--- kernel/kexec_core.c | 9 +- kernel/multikernel/core.c | 9 +- kernel/multikernel/instance_dt.c | 13 +- kernel/multikernel/manifest.c | 6 + 8 files changed, 178 insertions(+), 27 deletions(-) diff --git a/Documentation/multikernel/device-tree.rst b/Documentation/multikernel/device-tree.rst index 9e811af7cefeda..b54a077b291112 100644 --- a/Documentation/multikernel/device-tree.rst +++ b/Documentation/multikernel/device-tree.rst @@ -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 diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 458f2a6d4588c8..edf3186ce8c180 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -1095,6 +1095,9 @@ config ARCH_SUPPORTS_MULTIKERNEL 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 diff --git a/arch/riscv/include/asm/multikernel.h b/arch/riscv/include/asm/multikernel.h index b367bcd5c3fd41..2a83fed924ffac 100644 --- a/arch/riscv/include/asm/multikernel.h +++ b/arch/riscv/include/asm/multikernel.h @@ -51,6 +51,9 @@ struct mk_instance_arch { struct mk_pool_arch { }; +extern char mk_riscv_entry_stub_start[]; +extern char mk_riscv_entry_stub_end[]; + #endif /* !__ASSEMBLY__ */ #endif /* _ASM_RISCV_MULTIKERNEL_H */ diff --git a/arch/riscv/multikernel/spawn.c b/arch/riscv/multikernel/spawn.c index ef51dabe6972b2..9bdf1c2300f988 100644 --- a/arch/riscv/multikernel/spawn.c +++ b/arch/riscv/multikernel/spawn.c @@ -1,20 +1,95 @@ // SPDX-License-Identifier: GPL-2.0-only -/* - * RISC-V multikernel architecture interface skeleton. - * - * The SBI HSM spawn and park implementation is added by the follow-up - * architecture patches. Until then, operations which would change CPU - * ownership fail explicitly instead of pretending that a hart moved. - */ - +#include #include +#include #include #include #include +#include +#include + +#include +#include + +#define MK_HSM_POLL_US 1000 +#define MK_HSM_TIMEOUT_US USEC_PER_SEC + +static int mk_riscv_setup_instance(struct mk_instance *instance) +{ + struct mk_riscv_spawn_context *ctx; + size_t stub_size; + void *block; + + if (instance->arch.ctx) + return 0; + + stub_size = mk_riscv_entry_stub_end - mk_riscv_entry_stub_start; + if (WARN_ON_ONCE(!stub_size || stub_size > PAGE_SIZE)) + return -E2BIG; + + block = mk_instance_ctrl_alloc(instance, 2 * PAGE_SIZE, PAGE_SIZE); + if (!block) + return -ENOMEM; + + ctx = block; + memcpy(block + PAGE_SIZE, mk_riscv_entry_stub_start, stub_size); + + instance->arch.ctx = ctx; + instance->arch.ctx_phys = virt_to_phys(ctx); + instance->arch.stub = block + PAGE_SIZE; + instance->arch.stub_phys = instance->arch.ctx_phys + PAGE_SIZE; + return 0; +} + +static int mk_riscv_hart_stopped(unsigned long hartid) +{ + int state, ret; + + state = sbi_hsm_hart_get_status(hartid); + if (state == SBI_HSM_STATE_STARTED || + state == SBI_HSM_STATE_START_PENDING || + state == SBI_HSM_STATE_STOP_PENDING) { + ret = read_poll_timeout(sbi_hsm_hart_get_status, state, + state != SBI_HSM_STATE_STARTED && + state != SBI_HSM_STATE_START_PENDING && + state != SBI_HSM_STATE_STOP_PENDING, + MK_HSM_POLL_US, MK_HSM_TIMEOUT_US, + false, hartid); + if (ret) { + pr_err("mk_spawn: hart %lu did not stop within %ld us\n", + hartid, MK_HSM_TIMEOUT_US); + return -EBUSY; + } + } + + if (state < 0) { + if (state == -EPERM) + pr_err("mk_spawn: SBI domain denied HART_STATUS for hart %lu\n", + hartid); + else + pr_err("mk_spawn: failed to query hart %lu status: %d\n", + hartid, state); + return state; + } + if (state != SBI_HSM_STATE_STOPPED) { + pr_err("mk_spawn: hart %lu is not stopped (state %d)\n", + hartid, state); + return -EBUSY; + } + + return 0; +} void mk_arch_send_ipi(mk_phys_cpu_t phys_cpu) { - pr_warn_once("RISC-V multikernel IPI support is not implemented\n"); + struct sbiret ret; + + /* The host doorbell hart is intentionally absent from a spawn's CPU map. */ + ret = sbi_ecall(SBI_EXT_IPI, SBI_EXT_IPI_SEND_IPI, + 1UL, phys_cpu, 0, 0, 0, 0); + if (ret.error) + pr_err("Multikernel: failed to send IPI to hart %llu: %d\n", + phys_cpu, sbi_err_map_linux_errno(ret.error)); } void mk_arch_register_cpu(mk_phys_cpu_t phys_id) @@ -24,8 +99,18 @@ void mk_arch_register_cpu(mk_phys_cpu_t phys_id) void __noreturn mk_enter_pool_state(void *info) { - /* Only a spawn kernel gets here, and none can be started yet. */ - panic("RISC-V multikernel pool parking is not implemented"); + int ret; + + local_irq_disable(); + set_cpu_online(smp_processor_id(), false); + /* Publish the offline state before firmware stops this hart. */ + smp_mb(); + + ret = sbi_hsm_hart_stop(); + pr_emerg("Multikernel: HART_STOP returned on CPU %u: %d\n", + smp_processor_id(), ret); + for (;;) + wait_for_interrupt(); } void mk_force_stop_cpu(mk_phys_cpu_t phys_cpu) @@ -36,33 +121,77 @@ void mk_force_stop_cpu(mk_phys_cpu_t phys_cpu) int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance, int cpu) { - return -EOPNOTSUPP; + unsigned long hartid = arch_cpu_physical_id(cpu); + int ret; + + if (hartid == INVALID_HARTID || !image->start || + !image->arch.fdt_addr) + return -EINVAL; + + ret = mk_riscv_hart_stopped(hartid); + if (ret) + return ret; + + ret = mk_riscv_setup_instance(instance); + if (ret) + return ret; + + ret = mk_manifest_set_entry_stub(image, instance->arch.stub_phys); + if (ret) + return ret; + + WRITE_ONCE(instance->arch.ctx->image_entry, image->start); + /* Publish all Image, DTB and context stores before firmware starts it. */ + smp_mb(); + + ret = sbi_hsm_hart_start(hartid, instance->arch.stub_phys, + image->arch.fdt_addr); + if (ret == -EPERM) + pr_err("mk_spawn: SBI domain denied HART_START for hart %lu\n", + hartid); + else if (ret == -EINVAL) + pr_err("mk_spawn: invalid HART_START parameters for hart %lu\n", + hartid); + else if (ret) + pr_err("mk_spawn: failed to start hart %lu: %d\n", hartid, ret); + + return ret; } int mk_arch_release_instance(struct mk_instance *instance) { + int ret; + + ret = mk_instance_confirm_parked(instance); + if (ret) + return ret; + + instance->arch.ctx = NULL; + instance->arch.ctx_phys = 0; + instance->arch.stub = NULL; + instance->arch.stub_phys = 0; return 0; } int mk_arch_confirm_parked(struct mk_instance *instance, mk_phys_cpu_t phys_cpu) { - return -EOPNOTSUPP; + return mk_riscv_hart_stopped(phys_cpu); } int mk_repark_instance_to_host(struct mk_instance *instance) { - return -EOPNOTSUPP; + return 0; } int mk_repark_cpu_to_instance(struct mk_instance *instance, mk_phys_cpu_t phys_cpu) { - return -EOPNOTSUPP; + return 0; } int mk_repark_cpu_to_host(struct mk_instance *instance, mk_phys_cpu_t phys_cpu) { - return -EOPNOTSUPP; + return 0; } diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index e1d0d55b1a0f46..20a1da55c4fe35 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -1755,10 +1755,11 @@ int multikernel_kexec_by_id(int mk_id) } rc = mk_manifest_finalize(mk_image); - if (rc) - pr_warn("Manifest finalization failed: %d\n", rc); - else - pr_info("Manifest finalized for multikernel instance\n"); + if (rc) { + pr_err("Manifest finalization failed: %d\n", rc); + goto unlock; + } + pr_info("Manifest finalized for multikernel instance\n"); /* * Point at the ring this image actually carries. Every load diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index 9de50f787d2737..d288295aa02e9a 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -423,12 +423,17 @@ bool multikernel_allow_emergency_restart(void) */ int mk_instance_confirm_parked(struct mk_instance *instance) { + const struct mk_cpu_set *cpus; mk_phys_cpu_t phys_cpu; unsigned int i; int ret, failed = 0; - /* Empty until the instance first ran, so nothing of it is executing */ - mk_cpu_set_for_each(i, phys_cpu, instance->cpus_on_slot) { + /* Firmware-park architectures never put CPUs on an instance slot. */ + cpus = IS_ENABLED(CONFIG_ARCH_HAS_MK_HOST_PARK) ? + instance->cpus_on_slot : instance->cpus; + + /* An empty set means no CPU can still be executing this image. */ + mk_cpu_set_for_each(i, phys_cpu, cpus) { ret = mk_arch_confirm_parked(instance, phys_cpu); if (ret) { pr_err("Instance %d (%s): CPU %llu is not parked: %d\n", diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index c0f6ca709c0298..c20798abda98ba 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -317,6 +317,7 @@ static int __init mk_restore_host_instance(void) { struct mk_instance *hi; phys_addr_t host_ipi_phys; + u64 host_doorbell_cpu; u32 host_ipi_pages; size_t host_ipi_size; @@ -324,17 +325,19 @@ static int __init mk_restore_host_instance(void) pr_warn("No host IPI buffer in the boot tree (spawn won't be able to send to host)\n"); return -ENOENT; } + if (of_property_read_u64(of_chosen, "multikernel,host-ipi-cpu", + &host_doorbell_cpu)) { + pr_warn("No host doorbell CPU in the boot tree\n"); + return -ENOENT; + } host_ipi_size = (size_t)host_ipi_pages << PAGE_SHIFT; hi = mk_instance_alloc(0, "host"); if (!hi) return -ENOMEM; - /* - * The host's owned CPU set is unknown here; ring its doorbell on - * physical CPU 0 without pretending we know what it owns. - */ - hi->ipi_target = 0; + /* The parent's owned CPU set is unknown; record only its doorbell. */ + hi->ipi_target = host_doorbell_cpu; hi->ipi_data = memremap(host_ipi_phys, host_ipi_size, MEMREMAP_WB); if (!hi->ipi_data) { diff --git a/kernel/multikernel/manifest.c b/kernel/multikernel/manifest.c index ee38faf3578a73..5e62760c918fd2 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "internal.h" @@ -319,6 +320,7 @@ static int mk_manifest_chosen(void *fdt, void *data) { struct mk_manifest_ctx *ctx = data; struct kimage *image = ctx->image; + mk_phys_cpu_t doorbell_cpu; phys_addr_t slot; int ret; @@ -327,11 +329,15 @@ static int mk_manifest_chosen(void *fdt, void *data) return ret; if (mk_self->ipi_data) { + doorbell_cpu = arch_cpu_physical_id(get_boot_cpu_id()); ret = fdt_property_u64(fdt, "multikernel,host-ipi-buffer", mk_self->ipi_phys); if (!ret) ret = fdt_property_u32(fdt, "multikernel,host-ipi-pages", mk_self->ipi_pages); + if (!ret) + ret = fdt_property_u64(fdt, "multikernel,host-ipi-cpu", + doorbell_cpu); if (ret) return ret; } From a684fa3c0e12141097b1761deb400d24b7612ce4 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Sun, 30 Aug 2026 16:15:27 +0530 Subject: [PATCH 5/9] riscv: harden multikernel HSM start handoff Prime every assigned hart through an immutable host-text fence.i trampoline before it fetches a rewritten instance stub or Image. Preserve distinct HSM state errors for bounded lifecycle handling. Link: https://github.com/multikernel/linux/issues/23 Link: https://github.com/multikernel/linux/issues/24 Signed-off-by: Utkarsh Maurya --- Documentation/multikernel/usage.rst | 25 +++++++++++++------ arch/riscv/include/asm/multikernel.h | 1 + arch/riscv/kernel/cpu_ops_sbi.c | 20 ++++++++++++--- arch/riscv/multikernel/entry.S | 16 ++++++++++++ arch/riscv/multikernel/spawn.c | 37 +++++++++++++++++++++++----- 5 files changed, 83 insertions(+), 16 deletions(-) diff --git a/Documentation/multikernel/usage.rst b/Documentation/multikernel/usage.rst index d1fca6fe0a7cf5..44fc82d6c6e6cf 100644 --- a/Documentation/multikernel/usage.rst +++ b/Documentation/multikernel/usage.rst @@ -88,10 +88,20 @@ 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 copies one -immutable entry stub into its control block. The 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``. +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 @@ -101,8 +111,9 @@ 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, so the stub's first fetch cannot reuse an older line. +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 immutable stub can safely execute from a stale cache long enough to run -``fence.i``, which makes the newly written Image visible before the jump. +The priming handshake makes the immutable stub visible; the stub's own +``fence.i`` then makes the newly written Image visible before the jump. diff --git a/arch/riscv/include/asm/multikernel.h b/arch/riscv/include/asm/multikernel.h index 2a83fed924ffac..674cda116db7fc 100644 --- a/arch/riscv/include/asm/multikernel.h +++ b/arch/riscv/include/asm/multikernel.h @@ -53,6 +53,7 @@ struct mk_pool_arch { extern char mk_riscv_entry_stub_start[]; extern char mk_riscv_entry_stub_end[]; +extern char mk_riscv_entry_fence_stop[]; #endif /* !__ASSEMBLY__ */ diff --git a/arch/riscv/kernel/cpu_ops_sbi.c b/arch/riscv/kernel/cpu_ops_sbi.c index ec648df6abf53e..1d34f795674ba6 100644 --- a/arch/riscv/kernel/cpu_ops_sbi.c +++ b/arch/riscv/kernel/cpu_ops_sbi.c @@ -26,6 +26,20 @@ const struct cpu_operations cpu_ops_sbi; */ static struct sbi_hart_boot_data boot_data[NR_CPUS]; +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) { @@ -34,7 +48,7 @@ int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr, 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; } @@ -49,7 +63,7 @@ int sbi_hsm_hart_stop(void) 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; } @@ -61,7 +75,7 @@ int sbi_hsm_hart_get_status(unsigned long hartid) 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; } diff --git a/arch/riscv/multikernel/entry.S b/arch/riscv/multikernel/entry.S index 4b77b417d8ad36..19524ed42125be 100644 --- a/arch/riscv/multikernel/entry.S +++ b/arch/riscv/multikernel/entry.S @@ -21,4 +21,20 @@ SYM_CODE_START(mk_riscv_entry_stub_start) mk_riscv_entry_stub_end: SYM_CODE_END(mk_riscv_entry_stub_start) +/* + * Every hart executes a local fence.i immediately before entering the CPU + * pool, so this immutable host-text entry cannot be fetched from an older + * cache line. Use it to make a newly copied per-instance stub visible before + * its first fetch. + */ +SYM_CODE_START(mk_riscv_entry_fence_stop) + fence.i + li a7, 0x48534d /* SBI_EXT_HSM */ + li a6, 1 /* SBI_EXT_HSM_HART_STOP */ + ecall +.Lmk_riscv_entry_stop_failed: + wfi + j .Lmk_riscv_entry_stop_failed +SYM_CODE_END(mk_riscv_entry_fence_stop) + .option pop diff --git a/arch/riscv/multikernel/spawn.c b/arch/riscv/multikernel/spawn.c index 9bdf1c2300f988..e5ac892d0d1ba2 100644 --- a/arch/riscv/multikernel/spawn.c +++ b/arch/riscv/multikernel/spawn.c @@ -80,6 +80,25 @@ static int mk_riscv_hart_stopped(unsigned long hartid) return 0; } +static int mk_riscv_prime_icache(unsigned long hartid) +{ + int ret; + + ret = mk_riscv_hart_stopped(hartid); + if (ret) + return ret; + + ret = sbi_hsm_hart_start(hartid, + __pa_symbol(mk_riscv_entry_fence_stop), 0); + if (ret) { + pr_err("mk_spawn: failed to prime hart %lu I-cache: %d\n", + hartid, ret); + return ret; + } + + return mk_riscv_hart_stopped(hartid); +} + void mk_arch_send_ipi(mk_phys_cpu_t phys_cpu) { struct sbiret ret; @@ -122,16 +141,14 @@ int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance, int cpu) { unsigned long hartid = arch_cpu_physical_id(cpu); + mk_phys_cpu_t phys_cpu; + unsigned int i; int ret; if (hartid == INVALID_HARTID || !image->start || !image->arch.fdt_addr) return -EINVAL; - ret = mk_riscv_hart_stopped(hartid); - if (ret) - return ret; - ret = mk_riscv_setup_instance(instance); if (ret) return ret; @@ -141,8 +158,13 @@ int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance, return ret; WRITE_ONCE(instance->arch.ctx->image_entry, image->start); - /* Publish all Image, DTB and context stores before firmware starts it. */ + /* Publish all Image, DTB, stub and context stores before starting it. */ smp_mb(); + mk_cpu_set_for_each(i, phys_cpu, instance->cpus) { + ret = mk_riscv_prime_icache(phys_cpu); + if (ret) + return ret; + } ret = sbi_hsm_hart_start(hartid, instance->arch.stub_phys, image->arch.fdt_addr); @@ -187,7 +209,10 @@ int mk_repark_instance_to_host(struct mk_instance *instance) int mk_repark_cpu_to_instance(struct mk_instance *instance, mk_phys_cpu_t phys_cpu) { - return 0; + if (!instance->arch.stub) + return -EINVAL; + + return mk_riscv_prime_icache(phys_cpu); } int mk_repark_cpu_to_host(struct mk_instance *instance, From 923987f0ebfcf5d67665ca206d1d02501a3aa44b Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Sun, 30 Aug 2026 16:15:36 +0530 Subject: [PATCH 6/9] multikernel: serialize instance resource changes Reuse the kexec lock for CPU, memory and device transfers so an Image rewrite cannot race with resource mutation. Recover an active instance only after every assigned hart is confirmed stopped. Link: https://github.com/multikernel/linux/issues/23 Link: https://github.com/multikernel/linux/issues/26 Signed-off-by: Utkarsh Maurya --- kernel/kexec_core.c | 20 ++++++- kernel/multikernel/hotplug.c | 103 +++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index 20a1da55c4fe35..e76ca0ea08007a 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -1687,6 +1687,7 @@ int multikernel_kexec_by_id(int mk_id) { struct kimage *mk_image; struct mk_instance *instance; + bool parked = false; int cpu = -1; int i, rc; @@ -1701,6 +1702,23 @@ int multikernel_kexec_by_id(int mk_id) } instance = mk_image->mk_instance; + if (instance->state == MK_STATE_ACTIVE) { + rc = mk_instance_confirm_parked(instance); + if (rc) { + pr_err("Multikernel instance %d is still active\n", mk_id); + goto unlock; + } + pr_warn("Multikernel instance %d stopped without a halt notification; recovering it\n", + mk_id); + mk_instance_set_state(instance, MK_STATE_LOADED); + parked = true; + } + if (instance->state != MK_STATE_LOADED) { + pr_err("Multikernel instance %d is not ready to spawn (state=%d)\n", + mk_id, instance->state); + rc = -EBUSY; + goto unlock; + } if (!mk_cpu_set_empty(instance->cpus)) { mk_phys_cpu_t phys_cpu = mk_cpu_set_first(instance->cpus); @@ -1725,7 +1743,7 @@ int multikernel_kexec_by_id(int mk_id) * when it is overwritten faults with interrupts disabled and takes * the machine down, console included. */ - rc = mk_instance_confirm_parked(instance); + rc = parked ? 0 : mk_instance_confirm_parked(instance); if (rc) { pr_err("Instance %d still has running CPUs, refusing to reload its image\n", mk_id); diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index 91ff72e0a168d6..99de644a0942c0 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -23,6 +23,7 @@ #include #include #include +#include "../kexec_internal.h" #include "internal.h" static const char mk_mem_resource_name[] = "System RAM (multikernel)"; @@ -1208,16 +1209,23 @@ int mk_send_cpu_remove(int instance_id, mk_phys_cpu_t cpu_id) struct mk_instance *target_instance; int ret; + if (!kexec_trylock()) + return -EBUSY; + /* For self-removal, execute directly (we're in process context) */ if (instance_id == mk_self->id) { if (mk_pool) - return mk_pool_cpu_add(cpu_id); - return mk_do_cpu_remove(cpu_id); + ret = mk_pool_cpu_add(cpu_id); + else + ret = mk_do_cpu_remove(cpu_id); + goto unlock; } target_instance = mk_instance_find(instance_id); - if (!target_instance) - return -ENODEV; + if (!target_instance) { + ret = -ENODEV; + goto unlock; + } /* For non-running instances, return CPU to root using existing API */ if (target_instance->state != MK_STATE_ACTIVE) { @@ -1290,6 +1298,8 @@ int mk_send_cpu_remove(int instance_id, mk_phys_cpu_t cpu_id) ret = 0; out: mk_instance_put(target_instance); +unlock: + kexec_unlock(); return ret; } @@ -1319,18 +1329,24 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl struct mk_instance *target_instance; int ret; + if (!kexec_trylock()) + return -EBUSY; + /* For self-addition, execute directly (we're in process context) */ if (instance_id == mk_self->id) { if (mk_pool) - return mk_pool_cpu_remove(cpu_id, numa_node, flags); - return mk_do_cpu_add(cpu_id, numa_node, flags); + ret = mk_pool_cpu_remove(cpu_id, numa_node, flags); + else + ret = mk_do_cpu_add(cpu_id, numa_node, flags); + goto unlock; } target_instance = mk_instance_find(instance_id); if (!target_instance) { pr_err("Multikernel hotplug: instance %d not found for CPU add\n", instance_id); - return -ENODEV; + ret = -ENODEV; + goto unlock; } /* For non-running instances, transfer CPU from root using existing API */ @@ -1399,6 +1415,8 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl ret = 0; out: mk_instance_put(target_instance); +unlock: + kexec_unlock(); return ret; } @@ -1431,13 +1449,20 @@ int mk_send_mem_add(int instance_id, u64 start_pfn, u64 nr_pages, struct mk_instance *target_instance; int ret; + if (!kexec_trylock()) + return -EBUSY; + /* For self-addition, execute directly (we're in process context) */ - if (instance_id == mk_self->id) - return mk_do_mem_add(start_pfn, nr_pages, numa_node, mem_type); + if (instance_id == mk_self->id) { + ret = mk_do_mem_add(start_pfn, nr_pages, numa_node, mem_type); + goto unlock; + } target_instance = mk_instance_find(instance_id); - if (!target_instance) - return -ENODEV; + if (!target_instance) { + ret = -ENODEV; + goto unlock; + } /* For non-running instances, allocate memory from pool and add to instance */ if (target_instance->state != MK_STATE_ACTIVE) { @@ -1470,6 +1495,8 @@ int mk_send_mem_add(int instance_id, u64 start_pfn, u64 nr_pages, (int)numa_node); out: mk_instance_put(target_instance); +unlock: + kexec_unlock(); return ret; } @@ -1499,13 +1526,20 @@ int mk_send_mem_remove(int instance_id, u64 start_pfn, u64 nr_pages) struct mk_instance *target_instance; int ret; + if (!kexec_trylock()) + return -EBUSY; + /* For self-removal, execute directly (we're in process context) */ - if (instance_id == mk_self->id) - return mk_do_mem_remove(start_pfn, nr_pages); + if (instance_id == mk_self->id) { + ret = mk_do_mem_remove(start_pfn, nr_pages); + goto unlock; + } target_instance = mk_instance_find(instance_id); - if (!target_instance) - return -ENODEV; + if (!target_instance) { + ret = -ENODEV; + goto unlock; + } /* For non-running instances, just remove the memory region from the instance */ if (target_instance->state != MK_STATE_ACTIVE) { @@ -1541,6 +1575,8 @@ int mk_send_mem_remove(int instance_id, u64 start_pfn, u64 nr_pages) PFN_PHYS(nr_pages)); out: mk_instance_put(target_instance); +unlock: + kexec_unlock(); return ret; } @@ -1575,6 +1611,9 @@ int mk_send_device_add(int instance_id, u16 domain, u8 bus, u8 devfn, int ret; u32 resource_id; + if (!kexec_trylock()) + return -EBUSY; + if (driver_override) strscpy(payload.driver_override, driver_override, sizeof(payload.driver_override)); else @@ -1584,14 +1623,19 @@ int mk_send_device_add(int instance_id, u16 domain, u8 bus, u8 devfn, if (instance_id == mk_self->id) { if (mk_pool) - return mk_pool_device_remove(domain, bus, devfn, - driver_override, flags); - return mk_do_device_add(domain, bus, devfn, driver_override, flags); + ret = mk_pool_device_remove(domain, bus, devfn, + driver_override, flags); + else + ret = mk_do_device_add(domain, bus, devfn, + driver_override, flags); + goto unlock; } target_instance = mk_instance_find(instance_id); - if (!target_instance) - return -ENODEV; + if (!target_instance) { + ret = -ENODEV; + goto unlock; + } if (target_instance->state != MK_STATE_ACTIVE) { ret = mk_instance_add_pci_device(target_instance, domain, bus, devfn); @@ -1626,6 +1670,8 @@ int mk_send_device_add(int instance_id, u16 domain, u8 bus, u8 devfn, ret = 0; out: mk_instance_put(target_instance); +unlock: + kexec_unlock(); return ret; } @@ -1657,18 +1703,25 @@ int mk_send_device_remove(int instance_id, u16 domain, u8 bus, u8 devfn) int ret; u32 resource_id; + if (!kexec_trylock()) + return -EBUSY; + payload.driver_override[0] = '\0'; resource_id = (domain << 16) | (bus << 8) | devfn; if (instance_id == mk_self->id) { if (mk_pool) - return mk_pool_device_add(domain, bus, devfn, NULL); - return mk_do_device_remove(domain, bus, devfn); + ret = mk_pool_device_add(domain, bus, devfn, NULL); + else + ret = mk_do_device_remove(domain, bus, devfn); + goto unlock; } target_instance = mk_instance_find(instance_id); - if (!target_instance) - return -ENODEV; + if (!target_instance) { + ret = -ENODEV; + goto unlock; + } if (target_instance->state != MK_STATE_ACTIVE) { ret = mk_instance_remove_pci_device(target_instance, domain, bus, devfn); @@ -1703,5 +1756,7 @@ int mk_send_device_remove(int instance_id, u16 domain, u8 bus, u8 devfn) ret = 0; out: mk_instance_put(target_instance); +unlock: + kexec_unlock(); return ret; } From a85f409363bbc00198c44aaccfe1eae1063f6fb8 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 22:53:58 +0530 Subject: [PATCH 7/9] riscv: contain multikernel spawn shutdown paths A spawned kernel must never invoke SBI SRST or a legacy shutdown because those operations can reset the host. Detect the spawn handoff before reset registration and suppress host-wide reset handlers. Route halt, poweroff, restart, SMP stop and panic shutdown through local HART_STOP. Cache the parent endpoint for a non-allocating panic notification, set a safe spawn panic default, and prevent memory reuse until every assigned hart is confirmed stopped. Link: https://github.com/multikernel/linux/issues/25 Signed-off-by: Utkarsh Maurya --- arch/riscv/kernel/reset.c | 13 ++++++++++ arch/riscv/kernel/sbi.c | 16 +++++++----- arch/riscv/kernel/setup.c | 4 +++ arch/riscv/kernel/smp.c | 13 ++++++++++ include/linux/multikernel.h | 3 +++ kernel/kexec.c | 12 +++++++++ kernel/kexec_file.c | 8 ++++++ kernel/multikernel/core.c | 48 +++++++++++++++++++++++++++++------ kernel/multikernel/internal.h | 2 ++ kernel/multikernel/ipi.c | 33 +++++++++++++++--------- kernel/multikernel/manifest.c | 3 +++ 11 files changed, 129 insertions(+), 26 deletions(-) diff --git a/arch/riscv/kernel/reset.c b/arch/riscv/kernel/reset.c index 9122885722265c..ead7399ce1c230 100644 --- a/arch/riscv/kernel/reset.c +++ b/arch/riscv/kernel/reset.c @@ -3,6 +3,8 @@ * Copyright (C) 2012 Regents of the University of California */ +#include +#include #include #include @@ -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(); } diff --git a/arch/riscv/kernel/sbi.c b/arch/riscv/kernel/sbi.c index c443337056ab7d..e1a4bdcce95bd4 100644 --- a/arch/riscv/kernel/sbi.c +++ b/arch/riscv/kernel/sbi.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -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) { @@ -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(); } diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c index b5bc5fc65cea65..e633be1a9314a7 100644 --- a/arch/riscv/kernel/setup.c +++ b/arch/riscv/kernel/setup.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -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; diff --git a/arch/riscv/kernel/smp.c b/arch/riscv/kernel/smp.c index 5ed5095320e66a..050e801f56b3d2 100644 --- a/arch/riscv/kernel/smp.c +++ b/arch/riscv/kernel/smp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index b12be567b2afc0..af704b74dcc470 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -855,6 +855,9 @@ struct mk_instance *mk_instance_get(struct mk_instance *instance); */ void __noreturn mk_halt_to_pool(void); +/* Panic-safe variant: best-effort parent notification, then local HART_STOP. */ +void __noreturn mk_panic_to_pool(void); + /** * mk_instance_reserve_resources() - Reserve CPU and memory resources for instance * @instance: Instance to reserve resources for diff --git a/kernel/kexec.c b/kernel/kexec.c index fff660fef63330..1af88d295619ec 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,17 @@ static int do_kexec_load(unsigned long entry, unsigned long nr_segments, int count, i; count = kimage_get_all_by_type(KEXEC_TYPE_MULTIKERNEL, images, 10); +#ifdef CONFIG_MULTIKERNEL + for (i = 0; i < count; i++) { + if (images[i]->mk_instance && + mk_instance_confirm_parked(images[i]->mk_instance)) { + pr_err("Multikernel instance %d still has running CPUs\n", + images[i]->mk_id); + ret = -EBUSY; + goto out_unlock; + } + } +#endif for (i = 0; i < count; i++) { kimage_remove_from_list(images[i]); kimage_free(images[i]); diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index 10e670a479d79b..5fa0089c89fd06 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -498,6 +498,14 @@ SYSCALL_DEFINE5(kexec_file_load, int, kernel_fd, int, initrd_fd, ret = -ENOENT; goto out; } +#ifdef CONFIG_MULTIKERNEL + ret = mk_instance_confirm_parked(mk_image->mk_instance); + if (ret) { + pr_err("Multikernel instance %d still has running CPUs\n", + mk_id); + goto out; + } +#endif pr_info("Unloading kernel from multikernel instance %d\n", mk_id); kimage_remove_from_list(mk_image); diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index d288295aa02e9a..8beff8c794b8c4 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -1550,6 +1550,28 @@ void __noreturn mk_halt_to_pool(void) mk_notify_down_and_park(0, MK_SYS_HALTED); } +void __noreturn mk_panic_to_pool(void) +{ + u8 buffer[sizeof(struct mk_message) + sizeof(struct mk_resource_ack)] + __aligned(__alignof__(struct mk_message)) = {}; + struct mk_message *msg = (struct mk_message *)buffer; + struct mk_resource_ack *ack = (struct mk_resource_ack *)msg->payload; + + if (host_instance && mk_self) { + msg->msg_type = MK_MSG_SYSTEM; + msg->msg_subtype = MK_SYS_HALTED; + msg->payload_len = sizeof(*ack); + ack->operation = MK_SYS_SHUTDOWN; + ack->resource_id = mk_self->id; + + mk_send_ipi_data_to_instance(host_instance, buffer, + sizeof(buffer), MK_MSG_SYSTEM); + } + + /* panic_other_cpus_shutdown() already sent every secondary to HART_STOP. */ + mk_enter_pool_state(NULL); +} + static void mk_shutdown_work_fn(struct work_struct *work) { struct mk_shutdown_work *sw = container_of(work, struct mk_shutdown_work, work); @@ -1566,11 +1588,21 @@ static void mk_shutdown_work_fn(struct work_struct *work) * it corrupts the single-producer mailbox. The kexec path confirms the * CPUs are parked before it rewrites the image. */ -static void mk_instance_settle_halted(struct mk_instance *instance) +static int mk_instance_settle_halted(struct mk_instance *instance) { + int ret; + + ret = mk_instance_confirm_parked(instance); + if (ret) { + pr_err("Instance %d (%s) reported halted before every CPU stopped\n", + instance->id, instance->name); + return ret; + } + pr_info("Instance %d (%s) halted, CPUs parking in pool\n", instance->id, instance->name); mk_instance_set_state(instance, MK_STATE_LOADED); + return 0; } struct mk_halted_work { @@ -1586,7 +1618,9 @@ static void mk_halted_work_fn(struct work_struct *work) instance = mk_instance_find(aw->instance_id); if (instance) { - mk_instance_settle_halted(instance); + if (mk_instance_settle_halted(instance)) + pr_err("Failed to settle halted instance %d\n", + aw->instance_id); mk_instance_put(instance); } else { pr_warn("Shutdown ACK from unknown instance %d\n", @@ -1710,12 +1744,10 @@ int multikernel_halt_by_id(int mk_id) ret = mk_msg_pending_wait(pending, 30000); if (ret == 0) { - if (mk_instance_confirm_parked(instance)) - pr_warn("Multikernel instance %d halted with CPUs unaccounted for\n", + ret = mk_instance_settle_halted(instance); + if (!ret) + pr_info("Multikernel instance %d halted (graceful)\n", mk_id); - - mk_instance_set_state(instance, MK_STATE_LOADED); - pr_info("Multikernel instance %d halted (graceful)\n", mk_id); } mk_instance_put(instance); @@ -1926,7 +1958,7 @@ int multikernel_force_halt_by_id(int mk_id) if (instance == host_instance) ret = mk_confirm_fenced(instance, targets); else - mk_instance_settle_halted(instance); + ret = mk_instance_settle_halted(instance); mk_cpu_set_free(targets); mk_instance_put(instance); diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index 563bad63aa0f7f..dc551b179ceb61 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -44,6 +44,8 @@ int mk_overlay_rmdir(struct kernfs_node *kn); /* ipi.c */ int mk_arm_force_halt(struct mk_instance *instance); +int mk_send_ipi_data_to_instance(struct mk_instance *instance, const void *data, + size_t data_size, unsigned long type); /* hotplug.c */ int mk_hotplug_init(void); diff --git a/kernel/multikernel/ipi.c b/kernel/multikernel/ipi.c index b9a2a74bbf1ca4..93a04b4983b590 100644 --- a/kernel/multikernel/ipi.c +++ b/kernel/multikernel/ipi.c @@ -176,32 +176,29 @@ int mk_arm_force_halt(struct mk_instance *instance) * * Returns 0 on success, negative error code on failure */ -int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, unsigned long type) +int mk_send_ipi_data_to_instance(struct mk_instance *instance, const void *data, + size_t data_size, unsigned long type) { struct mk_ipi_data *slot; - struct mk_instance *instance = mk_instance_find(instance_id); unsigned int head, next_head, tail; mk_phys_cpu_t target; if (!instance) return -EINVAL; - if (data_size > MK_MAX_DATA_SIZE) { - mk_instance_put(instance); + if (data_size > MK_MAX_DATA_SIZE) return -EINVAL; - } target = instance->ipi_target; if (target == MK_PHYS_CPU_INVALID) target = mk_cpu_set_first(instance->cpus); if (target == MK_PHYS_CPU_INVALID) { - pr_err("Instance %d has no CPU to receive the IPI\n", instance_id); - mk_instance_put(instance); + pr_err("Instance %d has no CPUs to receive the IPI\n", instance->id); return -ENODEV; } if (!mk_instance_ipi_area(instance)) { - pr_err("Multikernel IPI buffer not available for instance %d\n", instance_id); - mk_instance_put(instance); + pr_err("Multikernel IPI buffer not available for instance %d\n", + instance->id); return -ENODEV; } @@ -220,8 +217,7 @@ int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, uns */ printk_deferred(KERN_WARNING "multikernel: IPI ring full for instance %d (head=%u, tail=%u)\n", - instance_id, head, tail); - mk_instance_put(instance); + instance->id, head, tail); return -ENOSPC; } @@ -246,9 +242,22 @@ int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, uns smp_store_release(&slot->data_size, data_size); mk_arch_send_ipi(target); + return 0; +} + +int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, + unsigned long type) +{ + struct mk_instance *instance; + int ret; + instance = mk_instance_find(instance_id); + if (!instance) + return -EINVAL; + + ret = mk_send_ipi_data_to_instance(instance, data, data_size, type); mk_instance_put(instance); - return 0; + return ret; } static void mk_ipi_drain_ring(void) diff --git a/kernel/multikernel/manifest.c b/kernel/multikernel/manifest.c index 5e62760c918fd2..b7949ee108f358 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -85,6 +85,9 @@ void __init mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len) int len; int err = 0; + /* A malformed spawn handoff must still never gain host-wide reset. */ + WRITE_ONCE(mk_spawn_kernel, true); + pr_info("multikernel: processing manifest at 0x%llx (size: %llu)\n", fdt_phys, fdt_len); From e8499f5e94a806bce599532c18d531e8b5061173 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Mon, 7 Sep 2026 23:21:46 +0530 Subject: [PATCH 8/9] multikernel: retain CPUs after ambiguous hot-add Reserve instance tracking before handing a pool CPU to a running kernel. If the response is lost and firmware cannot confirm the hart stopped, conservatively keep the CPU assigned to that instance instead of exposing a possibly running hart through the free pool. Link: https://github.com/multikernel/linux/issues/23 Signed-off-by: Utkarsh Maurya --- arch/riscv/multikernel/spawn.c | 2 +- kernel/multikernel/hotplug.c | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/arch/riscv/multikernel/spawn.c b/arch/riscv/multikernel/spawn.c index e5ac892d0d1ba2..1c3f2e2121b57e 100644 --- a/arch/riscv/multikernel/spawn.c +++ b/arch/riscv/multikernel/spawn.c @@ -218,5 +218,5 @@ int mk_repark_cpu_to_instance(struct mk_instance *instance, int mk_repark_cpu_to_host(struct mk_instance *instance, mk_phys_cpu_t phys_cpu) { - return 0; + return mk_riscv_hart_stopped(phys_cpu); } diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index 99de644a0942c0..0aa80741a93ab9 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -1368,6 +1368,11 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl goto out; } + /* Reserve tracking before the remote kernel can start the CPU. */ + ret = mk_cpu_set_reserve(target_instance->cpus, 1); + if (ret) + goto out; + /* * The CPU is parked on the host slot, where the spawn kernel's * secondary wakeup cannot reach it. Point it at the instance's @@ -1397,19 +1402,20 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl ret = mk_msg_pending_wait(pending, 10000); if (ret < 0) { - /* - * Best effort: if the instance never picked the CPU up, it - * is still parked on the instance context and comes home; - * if the instance onlined it despite the error, nothing is - * watching the context and this times out harmlessly. - */ - mk_repark_cpu_to_host(target_instance, cpu_id); + int park_ret; + + park_ret = mk_repark_cpu_to_host(target_instance, cpu_id); + if (park_ret < 0) { + /* The request may have succeeded despite the lost ACK. */ + WARN_ON_ONCE(mk_cpu_set_add(target_instance->cpus, cpu_id)); + mk_cpu_set_del(mk_pool->cpus, cpu_id); + pr_err("Multikernel hotplug: CPU %llu ownership is uncertain; keeping it with instance %d\n", + cpu_id, instance_id); + } goto out; } - if (mk_cpu_set_add(target_instance->cpus, cpu_id)) - pr_warn("Multikernel hotplug: Failed to track CPU %llu in instance %d\n", - cpu_id, instance_id); + WARN_ON_ONCE(mk_cpu_set_add(target_instance->cpus, cpu_id)); mk_cpu_set_del(mk_pool->cpus, cpu_id); ret = 0; From eef3dcbad2346d33e4421ffb1e7365d7ac63218f Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Mon, 7 Sep 2026 23:21:57 +0530 Subject: [PATCH 9/9] multikernel: serialize halted instance settlement Take the kexec lock while confirming parked CPUs and moving an instance back to the loaded state. This keeps asynchronous halt notifications from racing CPU ownership changes or image teardown. Link: https://github.com/multikernel/linux/issues/23 Signed-off-by: Utkarsh Maurya --- kernel/multikernel/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index 8beff8c794b8c4..9dfd172cd13cdb 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -14,6 +14,7 @@ #include #include #include +#include "../kexec_internal.h" #include "internal.h" static void mk_instance_return_all_cpus(struct mk_instance *instance) @@ -1592,17 +1593,23 @@ static int mk_instance_settle_halted(struct mk_instance *instance) { int ret; + /* Serialize CPU-set inspection with resource moves and image teardown. */ + while (!kexec_trylock()) + msleep(20); + ret = mk_instance_confirm_parked(instance); if (ret) { pr_err("Instance %d (%s) reported halted before every CPU stopped\n", instance->id, instance->name); - return ret; + goto out; } pr_info("Instance %d (%s) halted, CPUs parking in pool\n", instance->id, instance->name); mk_instance_set_state(instance, MK_STATE_LOADED); - return 0; +out: + kexec_unlock(); + return ret; } struct mk_halted_work {