Skip to content

Commit 3330928

Browse files
committed
multikernel: make IPI publication ordered and recoverable
Serialize shared-ring producers with a bounded owner-aware gate. Preserve FIFO publication and recover a gate only after its producer CPU is known to be parked. The gate and slot-state protocol change the private shared transport layout. Add exact pre-launch ABI checks and require an initialization acknowledgment before activation. Prepare each downlink with its target identity so callers can queue messages while the receiver boots, then drain those entries when its handlers publish readiness. Keep console-path failure diagnostics deferred to avoid recursive printk locking. Mark a launched instance active before releasing the global kexec lock, and wait for readiness without blocking crash kexec. Validate nested-parent identity dynamically and fail initialization cleanly when manifest restoration leaves no root instance. Fail invalid manifests and missing acknowledgments closed. A spawn started by a host without the pre-launch check validates the boot-context anchor before using shifted fields and parks locally on mismatch. Signed-off-by: Nikolay Nikolaev <nicknickolaev@gmail.com>
1 parent 46e05cf commit 3330928

16 files changed

Lines changed: 918 additions & 219 deletions

File tree

arch/x86/boot/header.S

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,13 @@ xloadflags:
379379
#define XLF56 0
380380
#endif
381381

382-
.word XLF0 | XLF1 | XLF23 | XLF4 | XLF56
382+
#ifdef CONFIG_MULTIKERNEL
383+
# define XLF_MK XLF_MULTIKERNEL_IPI
384+
#else
385+
# define XLF_MK 0
386+
#endif
387+
388+
.word XLF0 | XLF1 | XLF23 | XLF4 | XLF56 | XLF_MK
383389

384390
cmdline_size: .long COMMAND_LINE_SIZE-1 #length of the command line,
385391
#added with boot protocol

arch/x86/include/asm/multikernel.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,14 @@ struct mk_spawn_context {
104104
unsigned long boot_cpu_khz; /* Host CPU frequency calibration */
105105
unsigned long boot_tsc_khz; /* Host TSC frequency calibration */
106106
unsigned long boot_apic_hz; /* Host local APIC timer frequency */
107+
u64 abi_magic; /* Validated context producer */
107108
} __aligned(PAGE_SIZE);
108109

109110
static_assert(offsetof(struct mk_spawn_context, bp) == 144);
110111
static_assert(offsetof(struct mk_spawn_context, boot_lps) ==
111112
144 + sizeof(struct boot_params));
113+
static_assert(offsetof(struct mk_spawn_context, abi_magic) ==
114+
144 + sizeof(struct boot_params) + 4 * sizeof(unsigned long));
112115
static_assert(sizeof(struct mk_spawn_context) == 2 * PAGE_SIZE);
113116

114117
/* Pool park loop code, copied by the host into per-instance park pages */
@@ -163,7 +166,8 @@ void mk_set_spawn_context(struct mk_spawn_context *ctx,
163166
int mk_spawn_cpu(struct mk_instance *instance, int cpu,
164167
struct mk_spawn_context *ctx);
165168

166-
/* Initialize boot context tracking in spawn kernel */
169+
/* Validate and initialize boot context tracking in spawn kernel */
170+
struct mk_spawn_context *mk_validate_boot_context(phys_addr_t ctx_phys);
167171
void mk_init_boot_context(phys_addr_t ctx_phys);
168172

169173
/* Identity page table and trampoline setup */

arch/x86/include/uapi/asm/bootparam.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#define XLF_5LEVEL (1<<5)
2626
#define XLF_5LEVEL_ENABLED (1<<6)
2727
#define XLF_MEM_ENCRYPTION (1<<7)
28+
#define XLF_MULTIKERNEL_IPI 0x0100
2829

2930
#ifndef __ASSEMBLER__
3031

arch/x86/kernel/kexec-bzimage64.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,11 @@ static void *bzImage64_load(struct kimage *image, char *kernel,
554554
.buf_max = ULONG_MAX, .top_down = true };
555555

556556
header = (struct setup_header *)(kernel + setup_hdr_offset);
557+
if (image->type == KEXEC_TYPE_MULTIKERNEL &&
558+
!(header->xloadflags & XLF_MULTIKERNEL_IPI)) {
559+
pr_err("Loaded kernel lacks the required shared transport layout\n");
560+
return ERR_PTR(-EPROTONOSUPPORT);
561+
}
557562
setup_sects = header->setup_sects;
558563
if (setup_sects == 0)
559564
setup_sects = 4;

arch/x86/kernel/kexec-vmlinux.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ struct elf_kernel_info {
6060
unsigned long reloc_size; /* Size of relocation data */
6161
};
6262

63+
struct mk_elf_note_desc {
64+
u64 entry;
65+
u32 ipi_abi_version;
66+
u32 reserved;
67+
};
68+
6369
/*
6470
* Find multikernel entry point from PT_NOTE section.
6571
* Looks for note with name "Linux" and type 0x4d4b ('MK').
@@ -93,12 +99,20 @@ static unsigned long find_multikernel_entry_note(const void *buf, size_t len,
9399

94100
if (nhdr->n_type == 0x4d4b &&
95101
nhdr->n_namesz == 6 &&
96-
nhdr->n_descsz == sizeof(u64) &&
102+
nhdr->n_descsz == sizeof(struct mk_elf_note_desc) &&
97103
!memcmp(ptr + sizeof(*nhdr), "Linux", 6)) {
98-
u64 entry = *(u64 *)(ptr + sizeof(*nhdr) +
99-
ALIGN(nhdr->n_namesz, 4));
100-
pr_info("multikernel: entry=0x%llx\n", entry);
101-
return entry;
104+
const struct mk_elf_note_desc *desc;
105+
106+
desc = ptr + sizeof(*nhdr) +
107+
ALIGN(nhdr->n_namesz, 4);
108+
if (desc->ipi_abi_version != MK_IPI_ABI_VERSION) {
109+
pr_err("multikernel IPI ABI %u is not supported\n",
110+
desc->ipi_abi_version);
111+
return 0;
112+
}
113+
pr_info("multikernel: entry=0x%llx, IPI ABI=%u\n",
114+
desc->entry, desc->ipi_abi_version);
115+
return desc->entry;
102116
}
103117
ptr += note_size;
104118
}

arch/x86/kernel/platform-quirks.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ static void __init multikernel_setup_calibration(void)
4747
{
4848
phys_addr_t ctx_phys = orig_boot_params -
4949
offsetof(struct mk_spawn_context, bp);
50-
struct mk_spawn_context *ctx = __va(ctx_phys);
50+
struct mk_spawn_context *ctx = mk_validate_boot_context(ctx_phys);
5151

52-
if (ctx->self_phys != ctx_phys || !ctx->boot_tsc_khz)
52+
if (!ctx || !ctx->boot_tsc_khz)
5353
return;
5454

5555
multikernel_tsc_khz = ctx->boot_tsc_khz;

arch/x86/multikernel/head_64.S

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <linux/linkage.h>
1717
#include <linux/init.h>
1818
#include <linux/pgtable.h>
19+
#include <linux/multikernel_abi.h>
1920
#include <asm/segment.h>
2021
#include <asm/page.h>
2122
#include <asm/msr.h>
@@ -273,4 +274,6 @@ SYM_CODE_END(multikernel_secondary_startup)
273274
1: .asciz "Linux"
274275
2: .balign 4
275276
3: .quad multikernel_startup_64 - __START_KERNEL_map
277+
.long MK_IPI_ABI_VERSION
278+
.long 0
276279
4: .balign 4

arch/x86/multikernel/spawn.c

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171

7272
/* Set in spawn kernels: the context this kernel booted from */
7373
static struct mk_spawn_context *mk_boot_context;
74+
static phys_addr_t mk_boot_context_phys;
7475

7576
/*
7677
* Spawn kernel's own trampoline for secondary CPU wakeup.
@@ -84,6 +85,16 @@ static struct mk_spawn_context *mk_boot_context;
8485
*/
8586
static void *spawn_trampoline_va;
8687
static unsigned long spawn_trampoline_phys;
88+
static bool spawn_trampoline_prepared;
89+
static bool spawn_pool_park_prepared;
90+
static bool spawn_park_ready;
91+
static int spawn_park_error;
92+
93+
bool mk_arch_park_ready(void)
94+
{
95+
/* Pair with publication after both executable park mappings succeed. */
96+
return smp_load_acquire(&spawn_park_ready);
97+
}
8798

8899
extern char multikernel_relocate_kernel_start[];
89100
extern char multikernel_relocate_kernel_end[];
@@ -471,6 +482,7 @@ int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance,
471482
instance->spawn_ctx->boot_tsc_khz = tsc_khz;
472483
instance->spawn_ctx->boot_apic_hz =
473484
(unsigned long)lapic_timer_period * HZ;
485+
instance->spawn_ctx->abi_magic = MK_BOOT_CONTEXT_MAGIC;
474486

475487
return mk_spawn_cpu(instance, cpu, instance->spawn_ctx);
476488
}
@@ -693,18 +705,38 @@ void __init mk_arch_register_cpu(u64 phys_id)
693705
topology_register_apic((u32)phys_id, CPU_ACPIID_INVALID, true);
694706
}
695707

696-
/*
697-
* Initialize boot context tracking in spawn kernel.
698-
* Called early during spawn kernel boot.
699-
*/
700-
void mk_init_boot_context(phys_addr_t ctx_phys)
708+
static __noreturn void mk_reject_spawn_context(void)
709+
{
710+
/*
711+
* The host context layout is unknown, so neither its park state nor any
712+
* shared context field is safe to use. Keep this CPU local and inert. An NMI
713+
* can wake HLT, but returns to this loop with maskable interrupts still
714+
* disabled; disable them again before every halt for defense in depth.
715+
*/
716+
for (;;) {
717+
native_irq_disable();
718+
native_halt();
719+
}
720+
}
721+
722+
struct mk_spawn_context *mk_validate_boot_context(phys_addr_t ctx_phys)
701723
{
702724
struct mk_spawn_context *ctx;
725+
phys_addr_t stamped_phys;
726+
u64 abi_magic;
703727

704728
if (!ctx_phys) {
705729
pr_err("mk_spawn: Boot context physical address is 0!\n");
706-
return;
730+
return NULL;
707731
}
732+
if (mk_boot_context) {
733+
if (ctx_phys != mk_boot_context_phys)
734+
mk_reject_spawn_context();
735+
return mk_boot_context;
736+
}
737+
/* Reject an invalid derived address before mapping or dereferencing it. */
738+
if (!IS_ALIGNED(ctx_phys, PAGE_SIZE))
739+
mk_reject_spawn_context();
708740

709741
/*
710742
* The spawn context is in the multikernel pool which is regular RAM,
@@ -721,14 +753,28 @@ void mk_init_boot_context(phys_addr_t ctx_phys)
721753
* work and then fails much later, when this kernel shuts down and
722754
* its CPUs park on nonsense addresses.
723755
*/
724-
if (ctx->self_phys != ctx_phys) {
725-
pr_err("mk_spawn: Boot context at %pa is stamped %pa\n",
726-
&ctx_phys, &ctx->self_phys);
727-
pr_err("mk_spawn: Spawn context layout mismatch - host and spawn kernels must be built from the same source\n");
728-
return;
729-
}
730-
756+
stamped_phys = READ_ONCE(ctx->self_phys);
757+
if (stamped_phys != ctx_phys)
758+
mk_reject_spawn_context();
759+
abi_magic = READ_ONCE(ctx->abi_magic);
760+
if (abi_magic != MK_BOOT_CONTEXT_MAGIC)
761+
mk_reject_spawn_context();
762+
763+
mk_boot_context_phys = ctx_phys;
731764
mk_boot_context = ctx;
765+
return ctx;
766+
}
767+
768+
/*
769+
* Initialize boot context tracking in spawn kernel.
770+
* Called early during spawn kernel boot.
771+
*/
772+
void mk_init_boot_context(phys_addr_t ctx_phys)
773+
{
774+
struct mk_spawn_context *ctx = mk_validate_boot_context(ctx_phys);
775+
776+
if (!ctx)
777+
return;
732778
/*
733779
* A spawn kernel cannot calibrate against legacy timers because they
734780
* belong to the host. Reuse the selected physical CPU's delay and local
@@ -764,52 +810,74 @@ void mk_init_boot_context(phys_addr_t ctx_phys)
764810
*
765811
* One physical page serves every wake path of this instance: the host
766812
* allocates it once in mk_setup_trampoline() and reuses it across
767-
* re-spawns, and mk_prepare_trampoline() places our own trampoline copy
813+
* re-spawns, and mk_arch_prepare_park() places our own trampoline copy
768814
* (including the secondary entry) in the same page.
769815
*/
770-
static int __init mk_prepare_trampoline(void)
816+
int __init mk_arch_prepare_park(void)
771817
{
772818
struct mk_spawn_context *ctx = mk_boot_context;
773819
unsigned long virt;
774820
int ret;
775821

822+
if (mk_arch_park_ready())
823+
return 0;
824+
if (spawn_park_error)
825+
return spawn_park_error;
776826
if (!ctx)
777827
return 0;
828+
if (!ctx->trampoline_phys || !ctx->park_phys || !ctx->park_cr3) {
829+
ret = -EINVAL;
830+
goto fail;
831+
}
778832

779833
/*
780834
* Put our own copy of the trampoline in the page the host set
781835
* aside, while the kernel is fully alive: after shutdown this page
782836
* is entered from an offline CPU, where changing page attributes
783837
* is not allowed.
784838
*/
785-
spawn_trampoline_phys = ctx->trampoline_phys;
786-
spawn_trampoline_va = __va(spawn_trampoline_phys);
787-
memcpy(spawn_trampoline_va, multikernel_relocate_kernel_start,
788-
multikernel_relocate_kernel_end - multikernel_relocate_kernel_start);
839+
if (!spawn_trampoline_prepared) {
840+
spawn_trampoline_phys = ctx->trampoline_phys;
841+
spawn_trampoline_va = __va(spawn_trampoline_phys);
842+
memcpy(spawn_trampoline_va, multikernel_relocate_kernel_start,
843+
multikernel_relocate_kernel_end -
844+
multikernel_relocate_kernel_start);
789845

790-
/*
791-
* Both pages are executed from the direct map, which is writable,
792-
* so drop write before adding execute. Leaving them writable and
793-
* executable trips the kernel's own W^X check.
794-
*/
795-
virt = (unsigned long)spawn_trampoline_va & PAGE_MASK;
796-
ret = set_memory_ro(virt, 1);
797-
if (!ret)
798-
ret = set_memory_x(virt, 1);
799-
if (ret)
800-
return ret;
846+
/*
847+
* Both pages are executed from the direct map, which is writable,
848+
* so drop write before adding execute. Leaving them writable and
849+
* executable trips the kernel's own W^X check.
850+
*/
851+
virt = (unsigned long)spawn_trampoline_va & PAGE_MASK;
852+
ret = set_memory_ro(virt, 1);
853+
if (!ret)
854+
ret = set_memory_x(virt, 1);
855+
if (ret)
856+
goto fail;
857+
spawn_trampoline_prepared = true;
858+
}
801859

802860
/* The pool park page is entered the same way when this kernel dies */
803-
if (ctx->park_phys) {
861+
if (!spawn_pool_park_prepared) {
804862
virt = (unsigned long)__va(ctx->park_phys) & PAGE_MASK;
805863
ret = set_memory_ro(virt, 1);
806864
if (!ret)
807865
ret = set_memory_x(virt, 1);
866+
if (ret)
867+
goto fail;
868+
spawn_pool_park_prepared = true;
808869
}
809870

871+
/* Publish executable mappings before any reject or abort can park. */
872+
smp_store_release(&spawn_park_ready, true);
873+
return 0;
874+
875+
fail:
876+
/* A partial W^X transition is not safe to retry. */
877+
spawn_park_error = ret;
810878
return ret;
811879
}
812-
early_initcall(mk_prepare_trampoline);
880+
early_initcall(mk_arch_prepare_park);
813881

814882
/*
815883
* Add a 2MB executable mapping to a page table.

0 commit comments

Comments
 (0)