From 19cc8c6a6fb7a8328d01862d356b3ff7fac744fd Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Wed, 26 Aug 2026 13:15:05 +0300 Subject: [PATCH 1/8] riscv: add multikernel architecture skeleton Wire CONFIG_MULTIKERNEL into the 64-bit RISC-V build and add sparse hart ID translations. Reject the invalid hart sentinel before lookup so it cannot alias an unused logical CPU slot. Declare the generic contiguous-allocation and memory-hotplug dependencies so CONFIG_MULTIKERNEL cannot expose an unbuildable configuration. Reserve the architecture control block for the spawn context, DTB and entry stub. Provide safe stubs for the full architecture interface so the functional SBI HSM, Image loader and doorbell work can land incrementally. Signed-off-by: Nikolay Nikolaev --- arch/riscv/Kbuild | 2 + arch/riscv/Kconfig | 8 +++ arch/riscv/include/asm/multikernel.h | 48 ++++++++++++++++++ arch/riscv/multikernel/Makefile | 3 ++ arch/riscv/multikernel/spawn.c | 73 ++++++++++++++++++++++++++++ arch/x86/Kconfig | 3 ++ include/linux/multikernel.h | 2 + kernel/multikernel/Kconfig | 8 +++ kernel/multikernel/hotplug.c | 4 ++ 9 files changed, 151 insertions(+) create mode 100644 arch/riscv/include/asm/multikernel.h create mode 100644 arch/riscv/multikernel/Makefile create mode 100644 arch/riscv/multikernel/spawn.c diff --git a/arch/riscv/Kbuild b/arch/riscv/Kbuild index 126fb738fc4423..635b24b5af7053 100644 --- a/arch/riscv/Kbuild +++ b/arch/riscv/Kbuild @@ -7,5 +7,7 @@ obj-$(CONFIG_KVM) += kvm/ obj-$(CONFIG_ARCH_SUPPORTS_KEXEC_PURGATORY) += purgatory/ +obj-$(CONFIG_MULTIKERNEL) += multikernel/ + # for cleaning subdir- += boot diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 90c531e6abf5cf..458f2a6d4588c8 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -1087,6 +1087,14 @@ 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_SUPPORTS_CRASH_DUMP def_bool y diff --git a/arch/riscv/include/asm/multikernel.h b/arch/riscv/include/asm/multikernel.h new file mode 100644 index 00000000000000..f4d9685bf0abc4 --- /dev/null +++ b/arch/riscv/include/asm/multikernel.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASM_RISCV_MULTIKERNEL_H +#define _ASM_RISCV_MULTIKERNEL_H + +#ifndef __ASSEMBLY__ + +#include +#include +#include + +#include +#include + +/* + * 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) + +/* + * Architecture-private spawn state is added with the SBI HSM and Image + * loader support. The compile-only skeleton intentionally has none. + */ +struct mk_instance_arch { +}; + +#endif /* !__ASSEMBLY__ */ + +#endif /* _ASM_RISCV_MULTIKERNEL_H */ diff --git a/arch/riscv/multikernel/Makefile b/arch/riscv/multikernel/Makefile new file mode 100644 index 00000000000000..3ef62e5996af69 --- /dev/null +++ b/arch/riscv/multikernel/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += spawn.o diff --git a/arch/riscv/multikernel/spawn.c b/arch/riscv/multikernel/spawn.c new file mode 100644 index 00000000000000..a576346bc94f25 --- /dev/null +++ b/arch/riscv/multikernel/spawn.c @@ -0,0 +1,73 @@ +// 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 + +void mk_arch_send_ipi(mk_phys_cpu_t phys_cpu) +{ + pr_warn_once("RISC-V multikernel IPI support is not implemented\n"); +} + +void mk_arch_register_cpu(mk_phys_cpu_t phys_id) +{ + /* RISC-V CPU topology already records possible harts. */ +} + +void __noreturn mk_enter_pool_state(void *info) +{ + /* Unreachable while CONFIG_ARCH_HAS_MK_POOL_STATE is disabled. */ + panic("RISC-V multikernel pool parking is not implemented"); +} + +int mk_arch_register_force_stop(void) +{ + return -EOPNOTSUPP; +} + +void mk_force_stop_cpu(mk_phys_cpu_t phys_cpu) +{ + pr_warn_once("RISC-V multikernel force-stop is not implemented\n"); +} + +int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance, + int cpu) +{ + return -EOPNOTSUPP; +} + +int mk_arch_release_instance(struct mk_instance *instance) +{ + return 0; +} + +int mk_arch_confirm_parked(struct mk_instance *instance, + mk_phys_cpu_t phys_cpu) +{ + return -EOPNOTSUPP; +} + +int mk_repark_instance_to_host(struct mk_instance *instance) +{ + return -EOPNOTSUPP; +} + +int mk_repark_cpu_to_instance(struct mk_instance *instance, + mk_phys_cpu_t phys_cpu) +{ + return -EOPNOTSUPP; +} + +int mk_repark_cpu_to_host(struct mk_instance *instance, + mk_phys_cpu_t phys_cpu) +{ + return -EOPNOTSUPP; +} diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index bf5404b305d890..fb57348aef7e57 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2026,6 +2026,9 @@ config ARCH_SUPPORTS_MULTIKERNEL config ARCH_HAS_MK_HOST_PARK def_bool X86_64 +config ARCH_HAS_MK_POOL_STATE + def_bool X86_64 + config ARCH_SUPPORTS_CRASH_DUMP def_bool X86_64 || (X86_32 && HIGHMEM) diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 6493cb3eae6cb1..39635d4eb80344 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -1012,6 +1012,8 @@ void *mk_instance_ctrl_alloc(struct mk_instance *instance, size_t size, * - CONFIG_ARCH_HAS_MK_HOST_PARK and the host park functions declared * with the pool chunk API above, for architectures that park CPUs in * software rather than in firmware. + * - CONFIG_ARCH_HAS_MK_POOL_STATE, if the architecture can park an + * offlined CPU for reassignment to another kernel instance. */ /* Doorbell for the message ring: IPI a CPU owned by another kernel */ diff --git a/kernel/multikernel/Kconfig b/kernel/multikernel/Kconfig index ed4984e1942f50..a7f0847b452f7e 100644 --- a/kernel/multikernel/Kconfig +++ b/kernel/multikernel/Kconfig @@ -10,10 +10,18 @@ config ARCH_SUPPORTS_MULTIKERNEL config ARCH_HAS_MK_HOST_PARK bool +# The arch can park an offlined CPU in a state from which multikernel can +# reassign it to another kernel instance. +config ARCH_HAS_MK_POOL_STATE + bool + config MULTIKERNEL bool "Multikernel support" depends on ARCH_SUPPORTS_MULTIKERNEL + depends on CONTIG_ALLOC depends on KEXEC_CORE + depends on MEMORY_HOTPLUG + depends on MEMORY_HOTREMOVE select LIBFDT select OF_DYNAMIC if OF help diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index 5e7d22278896d4..0235e3965222c0 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -192,6 +192,10 @@ static int mk_do_cpu_remove(mk_phys_cpu_t cpu_id) int ret; struct mk_hotplug_op *op; + /* Reject unsupported pool transitions before offlining the CPU. */ + if (!IS_ENABLED(CONFIG_ARCH_HAS_MK_POOL_STATE)) + return -EOPNOTSUPP; + logical_cpu = mk_cpu_to_logical(cpu_id); if (logical_cpu < 0) { pr_err("Multikernel hotplug: Physical CPU %llu not found\n", cpu_id); From 927972ac7645f14a838f273bf5b52acc40730aa3 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Sun, 30 Aug 2026 21:55:57 +0530 Subject: [PATCH 2/8] 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 a7f0847b452f7e..512d045e10cc16 100644 --- a/kernel/multikernel/Kconfig +++ b/kernel/multikernel/Kconfig @@ -22,6 +22,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 32b7730f85980bde01ba8042c6884947a3792b04 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 21:53:51 +0530 Subject: [PATCH 3/8] 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 | 13 +++++++++---- arch/riscv/kernel/cpu_ops_sbi.c | 8 ++++---- 3 files changed, 21 insertions(+), 8 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 f4d9685bf0abc4..a96cba8504e234 100644 --- a/arch/riscv/include/asm/multikernel.h +++ b/arch/riscv/include/asm/multikernel.h @@ -36,11 +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 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; }; #endif /* !__ASSEMBLY__ */ 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 809d0028fa29a23b9c039feff718545359ab5bd0 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 22:20:09 +0530 Subject: [PATCH 4/8] 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 39635d4eb80344..d83aced161dd6c 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -861,6 +861,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 @@ -914,6 +921,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 /** @@ -922,6 +945,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 3c1e6b85cdd703..4d4b2bcac58282 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -26,12 +26,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 @@ -43,7 +77,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", @@ -70,6 +106,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 71cd4c09714e0f0344b256197d0072ae783d9024 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 22:44:32 +0530 Subject: [PATCH 5/8] 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 | 9 +- kernel/multikernel/manifest.c | 7 + 8 files changed, 178 insertions(+), 24 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 a96cba8504e234..1faf9fdefb0c7c 100644 --- a/arch/riscv/include/asm/multikernel.h +++ b/arch/riscv/include/asm/multikernel.h @@ -48,6 +48,9 @@ struct mk_instance_arch { phys_addr_t stub_phys; }; +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 a576346bc94f25..b24fb8427a3de0 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) { - /* Unreachable while CONFIG_ARCH_HAS_MK_POOL_STATE is disabled. */ - 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(); } int mk_arch_register_force_stop(void) @@ -41,33 +126,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 01382f40aeb724..9babd86a4895ec 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -1756,10 +1756,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 4f466bf2f09a79..791ca7d98545e3 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -296,12 +296,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 529a171e233d9f..52940aad66b2d0 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -339,6 +339,7 @@ static struct mk_instance * __init mk_restore_host_instance(void) { struct mk_instance *host_instance; phys_addr_t host_ipi_phys; + u64 host_doorbell_cpu; u32 host_ipi_pages; size_t host_ipi_size; @@ -346,14 +347,18 @@ static struct mk_instance * __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 NULL; } + 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 NULL; + } host_ipi_size = (size_t)host_ipi_pages << PAGE_SHIFT; host_instance = alloc_mk_instance(0, "", false); if (!host_instance) return NULL; - /* Set physical CPU 0 as default target for host IPIs */ - if (mk_cpu_set_add(host_instance->cpus, 0)) { + if (mk_cpu_set_add(host_instance->cpus, host_doorbell_cpu)) { kfree(host_instance->name); mk_cpu_set_free(host_instance->cpus); kfree(host_instance); diff --git a/kernel/multikernel/manifest.c b/kernel/multikernel/manifest.c index 4d4b2bcac58282..59aaf83a30802b 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "internal.h" @@ -211,11 +212,17 @@ static int mk_manifest_chosen(void *fdt, void *data) return ret; if (root_instance->ipi_data) { + mk_phys_cpu_t doorbell_cpu; + + doorbell_cpu = arch_cpu_physical_id(get_boot_cpu_id()); ret = fdt_property_u64(fdt, "multikernel,host-ipi-buffer", root_instance->ipi_phys); if (!ret) ret = fdt_property_u32(fdt, "multikernel,host-ipi-pages", root_instance->ipi_pages); + if (!ret) + ret = fdt_property_u64(fdt, "multikernel,host-ipi-cpu", + doorbell_cpu); if (ret) return ret; } From a452f3ab684a9d4e0242ea02e60474c500bef7d7 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Sun, 30 Aug 2026 16:15:27 +0530 Subject: [PATCH 6/8] 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 1faf9fdefb0c7c..7d66e8ac1d09fb 100644 --- a/arch/riscv/include/asm/multikernel.h +++ b/arch/riscv/include/asm/multikernel.h @@ -50,6 +50,7 @@ struct mk_instance_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 b24fb8427a3de0..3fead9a15e9f3e 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; @@ -127,16 +146,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; @@ -146,8 +163,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); @@ -192,7 +214,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 0646f02cdd321c74308f925c1d021e96cd66838e Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Sun, 30 Aug 2026 16:15:36 +0530 Subject: [PATCH 7/8] 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 9babd86a4895ec..aaf3532e1ddee1 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -1688,6 +1688,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; @@ -1702,6 +1703,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); @@ -1726,7 +1744,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 0235e3965222c0..cb9910467e2594 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)"; @@ -1220,16 +1221,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 == root_instance->id) { if (mk_cpu_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) { @@ -1302,6 +1310,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; } @@ -1331,18 +1341,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 == root_instance->id) { if (mk_cpu_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 */ @@ -1411,6 +1427,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; } @@ -1443,13 +1461,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 == root_instance->id) - return mk_do_mem_add(start_pfn, nr_pages, numa_node, mem_type); + if (instance_id == root_instance->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) { @@ -1482,6 +1507,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; } @@ -1511,13 +1538,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 == root_instance->id) - return mk_do_mem_remove(start_pfn, nr_pages); + if (instance_id == root_instance->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) { @@ -1553,6 +1587,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; } @@ -1587,6 +1623,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 @@ -1596,14 +1635,19 @@ int mk_send_device_add(int instance_id, u16 domain, u8 bus, u8 devfn, if (instance_id == root_instance->id) { if (mk_cpu_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); @@ -1638,6 +1682,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; } @@ -1669,18 +1715,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 == root_instance->id) { if (mk_cpu_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); @@ -1715,5 +1768,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 7fd9938de88f4c78307a4a58ab34fb9deb4f9f12 Mon Sep 17 00:00:00 2001 From: Utkarsh Maurya Date: Fri, 28 Aug 2026 22:53:58 +0530 Subject: [PATCH 8/8] 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 | 57 +++++++++++++++++++++++++++----- kernel/multikernel/instance_dt.c | 7 ++-- kernel/multikernel/internal.h | 3 ++ kernel/multikernel/ipi.c | 33 +++++++++++------- kernel/multikernel/manifest.c | 3 ++ 12 files changed, 143 insertions(+), 29 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 d83aced161dd6c..2dd31ec1dad084 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -764,6 +764,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 bc48b5512208e9..68ffe08265ad62 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -475,6 +475,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 791ca7d98545e3..a15d153e244d0b 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -1440,6 +1440,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 (mk_parent_instance && root_instance) { + 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 = root_instance->id; + + mk_send_ipi_data_to_instance(mk_parent_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); @@ -1456,11 +1478,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 { @@ -1468,6 +1500,8 @@ struct mk_halted_work { int instance_id; }; +static bool mk_force_stop_available; + static void mk_halted_work_fn(struct work_struct *work) { struct mk_halted_work *aw = @@ -1476,7 +1510,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", @@ -1600,12 +1636,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); @@ -1641,6 +1675,9 @@ int multikernel_force_halt_by_id(int mk_id) int cpu_count = 0; int ret; + if (!mk_force_stop_available) + return -EOPNOTSUPP; + instance = mk_instance_find(mk_id); if (!instance) return -ENOENT; @@ -1684,9 +1721,9 @@ int multikernel_force_halt_by_id(int mk_id) * for them to arrive before reporting the instance re-spawnable, * exactly as the graceful path does after its shutdown ACK. */ - mk_instance_settle_halted(instance); + ret = mk_instance_settle_halted(instance); mk_instance_put(instance); - return 0; + return ret; } static int __init multikernel_init(void) @@ -1697,6 +1734,8 @@ static int __init multikernel_init(void) if (ret < 0) { pr_warn("No force stop handler: %d (force halt unavailable)\n", ret); /* Continue anyway - graceful shutdown still works */ + } else { + mk_force_stop_available = true; } ret = mk_messaging_init(); diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index 52940aad66b2d0..81be367def5e19 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -37,6 +37,7 @@ */ struct mk_instance *root_instance = NULL; EXPORT_SYMBOL_GPL(root_instance); +struct mk_instance *mk_parent_instance; /** * mk_dt_extract_instance_info() - Extract instance ID and name from DTB @@ -349,8 +350,8 @@ static struct mk_instance * __init mk_restore_host_instance(void) } 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 NULL; + pr_warn("No host doorbell CPU; defaulting to physical CPU 0\n"); + host_doorbell_cpu = 0; } host_ipi_size = (size_t)host_ipi_pages << PAGE_SHIFT; @@ -498,6 +499,8 @@ int __init mk_instance_restore_from_manifest(void) host_instance = mk_restore_host_instance(); if (!host_instance) pr_warn("Failed to restore host instance (spawn→host communication unavailable)\n"); + else + mk_parent_instance = host_instance; pr_info("Successfully restored multikernel root instance %d ('%s') from the boot tree (%d bytes)\n", instance_id, instance_name, dtb_len); diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index da72f654002f04..a43ada0727d5c0 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -11,6 +11,7 @@ extern struct mutex mk_instance_mutex; extern struct idr mk_instance_idr; extern struct list_head mk_instance_list; extern struct mk_instance *root_instance; +extern struct mk_instance *mk_parent_instance; /* kernfs.c */ extern struct kernfs_node *mk_root_kn; @@ -37,6 +38,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 8a7b375bd886a3..f2f00a07c84200 100644 --- a/kernel/multikernel/ipi.c +++ b/kernel/multikernel/ipi.c @@ -176,30 +176,27 @@ 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 = mk_cpu_set_first(instance->cpus); if (target == MK_PHYS_CPU_INVALID) { - pr_err("Instance %d has no CPUs 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; } @@ -218,8 +215,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; } @@ -244,9 +240,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 59aaf83a30802b..43825d5bd8bfb0 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -83,6 +83,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);