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/Documentation/multikernel/usage.rst b/Documentation/multikernel/usage.rst index 8ed85d8ead7e18..44fc82d6c6e6cf 100644 --- a/Documentation/multikernel/usage.rst +++ b/Documentation/multikernel/usage.rst @@ -83,3 +83,37 @@ 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 first starts +every assigned hart at an immutable host-text trampoline. Every hart made +available to the pool has executed a local ``fence.i`` immediately before +``HART_STOP``, so the trampoline cannot be fetched from an older cache line. +The trampoline executes another ``fence.i`` and immediately calls +``HART_STOP``, making a newly copied per-instance stub visible before its +first fetch. The host confirms ``STOPPED`` before the real start. It repeats +the handshake before donating a hart to an active instance through CPU +hot-add. + +The host copies one immutable entry stub into the instance control block. +That stub begins with ``fence.i`` before loading the current entry from the +preceding context page and jumping to it. It preserves the boot ABI +registers ``a0`` and ``a1``. + +The multikernel manifest advertises the stub address to the spawn kernel. +Before starting a secondary hart, the spawn kernel changes the context entry +to ``secondary_start_sbi`` and passes the normal per-CPU boot data in ``a1``. +Thus every HSM start reaches the immutable stub before entering replaceable +Image code; the primary still receives its DTB and secondaries still receive +their SBI boot data. + +Every local HSM stop path also executes ``fence.i`` immediately before the +hart enters firmware, keeping the immutable host trampoline safe to fetch on +the next start. + +Respawns update only the host-owned entry data, never the copied instructions. +The priming handshake makes the immutable stub visible; the stub's own +``fence.i`` then makes the newly written Image visible before the jump. 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..edf3186ce8c180 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -1087,6 +1087,17 @@ config ARCH_SELECTS_KEXEC_FILE config ARCH_SUPPORTS_KEXEC_PURGATORY def_bool ARCH_SUPPORTS_KEXEC_FILE +config ARCH_SUPPORTS_MULTIKERNEL + def_bool y + depends on 64BIT + depends on RISCV_SBI + depends on KEXEC_FILE + depends on HOTPLUG_CPU + depends on !RISCV_M_MODE + +config ARCH_HAS_MK_POOL_STATE + def_bool ARCH_SUPPORTS_MULTIKERNEL + config ARCH_SUPPORTS_CRASH_DUMP def_bool y 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 new file mode 100644 index 00000000000000..7d66e8ac1d09fb --- /dev/null +++ b/arch/riscv/include/asm/multikernel.h @@ -0,0 +1,57 @@ +/* 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) + +struct mk_riscv_spawn_context { + unsigned long image_entry; +}; + +/* Per-instance state, allocated from the control block on first spawn. */ +struct mk_instance_arch { + struct mk_riscv_spawn_context *ctx; + phys_addr_t ctx_phys; + const void *stub; + phys_addr_t stub_phys; +}; + +extern char mk_riscv_entry_stub_start[]; +extern char mk_riscv_entry_stub_end[]; +extern char mk_riscv_entry_fence_stop[]; + +#endif /* !__ASSEMBLY__ */ + +#endif /* _ASM_RISCV_MULTIKERNEL_H */ diff --git a/arch/riscv/kernel/cpu_ops_sbi.c b/arch/riscv/kernel/cpu_ops_sbi.c index 00aff669f5f2f5..1d34f795674ba6 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 @@ -23,45 +26,86 @@ const struct cpu_operations cpu_ops_sbi; */ static struct sbi_hart_boot_data boot_data[NR_CPUS]; -static int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr, - unsigned long priv) +static int sbi_hsm_err_map_linux_errno(long err) +{ + switch (err) { + case SBI_ERR_ALREADY_AVAILABLE: + case SBI_ERR_ALREADY_STARTED: + case SBI_ERR_ALREADY_STOPPED: + return -EALREADY; + case SBI_ERR_FAILURE: + return -EIO; + default: + return sbi_err_map_linux_errno(err); + } +} + +int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr, + unsigned long priv) { struct sbiret ret; ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_START, hartid, saddr, priv, 0, 0, 0); if (ret.error) - return sbi_err_map_linux_errno(ret.error); + return sbi_hsm_err_map_linux_errno(ret.error); else return 0; } #ifdef CONFIG_HOTPLUG_CPU -static int sbi_hsm_hart_stop(void) +int sbi_hsm_hart_stop(void) { struct sbiret ret; + /* A stopped hart cannot receive the remote fence for its next entry. */ + local_flush_icache_all(); ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_STOP, 0, 0, 0, 0, 0, 0); if (ret.error) - return sbi_err_map_linux_errno(ret.error); + return sbi_hsm_err_map_linux_errno(ret.error); else return 0; } -static int sbi_hsm_hart_get_status(unsigned long hartid) +int sbi_hsm_hart_get_status(unsigned long hartid) { struct sbiret ret; ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_STATUS, hartid, 0, 0, 0, 0, 0); if (ret.error) - return sbi_err_map_linux_errno(ret.error); + return sbi_hsm_err_map_linux_errno(ret.error); else return ret.value; } #endif +#ifdef CONFIG_MULTIKERNEL +static int sbi_spawn_cpu_entry(unsigned long *boot_addr) +{ + struct mk_riscv_spawn_context *ctx; + phys_addr_t stub_addr; + + if (!mk_is_spawn_kernel()) + return 0; + + stub_addr = mk_manifest_entry_stub_phys(); + if (stub_addr < PAGE_SIZE || !IS_ALIGNED(stub_addr, PAGE_SIZE) || + !pfn_valid(PHYS_PFN(stub_addr - PAGE_SIZE)) || + !pfn_valid(PHYS_PFN(stub_addr))) { + pr_err_once("SBI: invalid multikernel entry stub address %pa\n", + &stub_addr); + return -EINVAL; + } + + ctx = phys_to_virt(stub_addr - PAGE_SIZE); + WRITE_ONCE(ctx->image_entry, *boot_addr); + *boot_addr = stub_addr; + return 0; +} +#endif + static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle) { unsigned long boot_addr = __pa_symbol(secondary_start_sbi); @@ -73,6 +117,10 @@ static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle) smp_mb(); bdata->task_ptr = tidle; bdata->stack_ptr = task_pt_regs(tidle); +#ifdef CONFIG_MULTIKERNEL + if (sbi_spawn_cpu_entry(&boot_addr)) + return -EINVAL; +#endif /* Make sure boot data is updated */ smp_mb(); hsm_data = __pa(bdata); 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/arch/riscv/multikernel/Makefile b/arch/riscv/multikernel/Makefile new file mode 100644 index 00000000000000..cd2327badca70a --- /dev/null +++ b/arch/riscv/multikernel/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +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..19524ed42125be --- /dev/null +++ b/arch/riscv/multikernel/entry.S @@ -0,0 +1,40 @@ +/* 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) + +/* + * 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 new file mode 100644 index 00000000000000..3fead9a15e9f3e --- /dev/null +++ b/arch/riscv/multikernel/spawn.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only +#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; +} + +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; + + /* 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) +{ + /* RISC-V CPU topology already records possible harts. */ +} + +void __noreturn mk_enter_pool_state(void *info) +{ + 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) +{ + 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) +{ + 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_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, 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); + 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 mk_riscv_hart_stopped(phys_cpu); +} + +int mk_repark_instance_to_host(struct mk_instance *instance) +{ + return 0; +} + +int mk_repark_cpu_to_instance(struct mk_instance *instance, + mk_phys_cpu_t phys_cpu) +{ + if (!instance->arch.stub) + return -EINVAL; + + return mk_riscv_prime_icache(phys_cpu); +} + +int mk_repark_cpu_to_host(struct mk_instance *instance, + mk_phys_cpu_t phys_cpu) +{ + return 0; +} 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..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 @@ -861,6 +864,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 +924,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 +948,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 @@ -1012,6 +1039,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/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_core.c b/kernel/kexec_core.c index 01382f40aeb724..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); @@ -1756,10 +1774,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/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/Kconfig b/kernel/multikernel/Kconfig index ed4984e1942f50..512d045e10cc16 100644 --- a/kernel/multikernel/Kconfig +++ b/kernel/multikernel/Kconfig @@ -10,10 +10,19 @@ 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 + depends on OF select LIBFDT select OF_DYNAMIC if OF help diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index 4f466bf2f09a79..a15d153e244d0b 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", @@ -1435,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); @@ -1451,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 { @@ -1463,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 = @@ -1471,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", @@ -1595,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); @@ -1636,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; @@ -1679,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) @@ -1692,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/hotplug.c b/kernel/multikernel/hotplug.c index 5e7d22278896d4..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)"; @@ -192,6 +193,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); @@ -1216,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) { @@ -1298,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; } @@ -1327,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 */ @@ -1407,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; } @@ -1439,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) { @@ -1478,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; } @@ -1507,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) { @@ -1549,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; } @@ -1583,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 @@ -1592,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); @@ -1634,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; } @@ -1665,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); @@ -1711,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; } diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index 529a171e233d9f..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 @@ -339,6 +340,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 +348,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; defaulting to physical CPU 0\n"); + host_doorbell_cpu = 0; + } 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); @@ -493,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 3c1e6b85cdd703..43825d5bd8bfb0 100644 --- a/kernel/multikernel/manifest.c +++ b/kernel/multikernel/manifest.c @@ -21,17 +21,52 @@ #include #include #include +#include #include "internal.h" /* 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,9 +78,14 @@ 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; + /* 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); @@ -70,6 +110,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"); @@ -165,11 +215,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; }