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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions Documentation/ABI/testing/multikernel
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
What: /sys/fs/multikernel/instances/<name>/stats
Date: August 2026
Contact: Nikolay Nikolaev <nicknickolaev@gmail.com>
Description:
Read-only reliability snapshot for one multikernel instance. The
format is one ``key value`` pair per line. The first line is always
``stats_version 1``. Versioned formats are append-only; readers must
ignore unknown keys.

The snapshot is observational, not atomic with concurrent producers.
Cumulative transport counters are unsigned 32-bit event counts and
wrap modulo 2^32. They reset when the shared buffer is initialized
for a new ``spawn_epoch`` and cannot be reset through this file.
Compare deltas only between samples with the same nonzero epoch.
Gauges are unsigned point-in-time counts and have no reset operation.

Metadata keys (class; unit; meaning):

``stats_version`` (metadata; version; this file format, currently 1),
``abi_version`` (metadata; version; exact shared transport ABI),
``snapshot_atomic`` (metadata; boolean; always 0),
``transport_counter_bits`` (metadata; bits; always 32),
``transport_available`` (gauge; boolean; shared buffer is mapped), and
``spawn_epoch`` (metadata; launch identifier; changes on each start).

``abi_version`` identifies the exact private kernel-to-kernel transport
contract used by the running instance. Exposing it here supports
diagnostics; it is not a stable userspace ABI or a compatibility promise
between transport versions.

Ordered IPI keys (class; unit; increment or sampled condition; reset):

``ipi.producer_contention`` (counter; sends; producer gate observed
busy or recursively owned; new epoch), ``ipi.full_failures`` (counter;
sends; head slot prevented publication; new epoch),
``ipi.invalid_state`` (counter; observations; impossible slot/cursor
state; new epoch), ``ipi.cancelled_writes`` (counter; writes; parked
producer recovered as CANCELLED; new epoch), ``ipi.gate_busy`` (gauge;
gates; producer gate nonzero; sampled), ``ipi.slot_writing`` (gauge;
slots; WRITING; sampled), ``ipi.slot_ready`` (gauge; slots; READY;
sampled), ``ipi.slot_consuming`` (gauge; slots; CONSUMING; sampled),
and ``ipi.slot_cancelled`` (gauge; slots; CANCELLED; sampled).

Direct reply keys (class; unit; increment or sampled condition; reset):

``reply.late_replies`` (counter; replies; stale exact-token claim or
completion was rejected; new epoch), ``reply.cancelled_slots``
(counter; slots; timeout
cancelled RESERVED/WRITING; new epoch), ``reply.atomic_timeouts``
(counter; waits; bounded atomic wait expired in cancellable
RESERVED/WRITING or indeterminate EXECUTING state; new epoch),
``reply.indeterminate_timeouts`` (counter; waits; subset where
EXECUTING won and changed to COMMITTED; new epoch),
``reply.occupied_failures`` (counter;
reservations; no FREE slot; new epoch), ``reply.slot_busy`` (gauge;
slots; state is not FREE; sampled), ``reply.slot_reserved`` (gauge;
slots; RESERVED; sampled), ``reply.slot_writing`` (gauge; slots;
WRITING; sampled), ``reply.slot_executing`` (gauge; slots; EXECUTING;
sampled), ``reply.slot_committed`` (gauge; slots; COMMITTED; sampled),
``reply.slot_ready`` (gauge; slots; READY; sampled), and
``reply.slot_abandoned`` (gauge; slots; ABANDONED; sampled).

Pending IRQ keys (class; unit; increment or sampled condition; reset):

``irq.recorded`` (counter; IRQs; pending count recorded; new epoch),
``irq.coalesced`` (counter; IRQs; recorded onto an already-pending
slot; new epoch), ``irq.masked_deferred`` (counter; IRQs; delivery
deferred while logically masked; new epoch), ``irq.stale`` (counter;
observations; epoch/generation/route validation rejected work; new
epoch), ``irq.dispatch_failed`` (counter; IRQs; validated local dispatch
failed; new epoch), ``irq.saturated`` (counter; IRQs; pending count hit
its representable limit; new epoch), ``irq.slot_active`` (gauge; slots;
nonzero generation; sampled), ``irq.slot_pending`` (gauge; slots;
nonzero pending count; sampled), ``irq.slot_masked`` (gauge; slots;
MASKED set; sampled), ``irq.slot_consuming`` (gauge; slots; CONSUMING
set; sampled), and ``irq.pending_total`` (gauge; IRQs; sum of pending
counts across active slots; sampled).
Users: multikernel management and diagnostic tools
33 changes: 33 additions & 0 deletions Documentation/multikernel/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ Phase 1: Instance Creation (Automatic from DTB)
cat /sys/fs/multikernel/instances/web-server/status
# Output: ready

cat /sys/fs/multikernel/instances/web-server/stats

``stats`` is a read-only, append-only key/value interface. Version 1
starts with ``stats_version 1`` and reports the shared ordered-IPI, direct
reply, and pending-IRQ transport counters for the instance. New keys may
be appended; readers must ignore keys they do not understand.

The output is an observational snapshot rather than an atomic transaction.
Shared transport counters are unsigned 32-bit values and may wrap. They
are reinitialized for a new ``spawn_epoch``, are not writable or resettable
through this interface, and must only be compared as modulo-32-bit deltas
between samples carrying the same nonzero epoch.

# View instance device tree
cat /sys/fs/multikernel/instances/web-server/device_tree_source
# Output: DTS format showing the instance configuration
Expand Down Expand Up @@ -122,6 +135,23 @@ Phase 2: Kernel Loading (Kexec Integration)
- Restore the instance's DTB and recreate the instance structure
- Re-reserve the same memory and CPU resources

SR-IOV Assignment Boundary
==========================

SR-IOV assignment is intended for cooperative spawned kernels. Filtering
configuration-space access and enumerating only assigned BDFs prevents
accidental access by those kernels; it is not a security boundary against a
privileged kernel that deliberately issues configuration cycles or maps host
physical windows. The host-owned IOMMU domain is the boundary that constrains
device-initiated DMA.

The host fails assignment closed unless it can establish the complete device
lifecycle: the device is an SR-IOV VF in a singleton IOMMU group, reset is
available, MSI or MSI-X programming remains isolated and host-owned, and one
coherent host-owned IOMMU domain covers the assigned memory. Reset, interrupt,
or DMA teardown uncertainty leaves the instance failed rather than returning
the VF to use.

Device Tree Format
==================

Expand Down Expand Up @@ -213,3 +243,6 @@ The new kernfs interface has the following restrictions:
- **No direct DTB upload to instances**: Instances don't have writable ``device_tree`` files
- **Centralized DTB management**: All instances must be created via the root ``device_tree`` file
- **Read-only instance files**: All instance attributes are read-only for consistency
- **Host control CPU**: Logical CPU 0 is the PCI forwarding and control CPU and
must remain online while assigned devices are active. Selecting another
control CPU is a future policy extension.
8 changes: 7 additions & 1 deletion arch/x86/boot/header.S
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,13 @@ xloadflags:
#define XLF56 0
#endif

.word XLF0 | XLF1 | XLF23 | XLF4 | XLF56
#ifdef CONFIG_MULTIKERNEL
# define XLF13 XLF_MULTIKERNEL_IPI_V7
#else
# define XLF13 0
#endif

.word XLF0 | XLF1 | XLF23 | XLF4 | XLF56 | XLF13

cmdline_size: .long COMMAND_LINE_SIZE-1 #length of the command line,
#added with boot protocol
Expand Down
38 changes: 32 additions & 6 deletions arch/x86/include/asm/multikernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

#ifndef __ASSEMBLY__

#include <linux/build_bug.h>
#include <linux/init.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/cpumask.h>
#include <linux/sizes.h>
Expand Down Expand Up @@ -54,17 +57,21 @@ static inline int arch_cpu_from_physical_id(u64 phys_id)
* from a page written by the host while running on CPUs parked by (possibly
* differently built) spawn kernels.
*
* The fields fall into two classes that must not be mixed up:
* The fields fall into three classes that must not be mixed up:
*
* - Anchor fields (self_phys, park_phys, park_cr3, ctrl_phys,
* ctrl_size): the context's own identity, written once when the
* context is set up. The spawn kernel reads them whenever it parks a
* CPU on halt or offline, so they must stay valid for the context's
* whole lifetime.
*
* - Dispatch fields (everything else): the wake mailbox, rewritten for
* every publication and staged into registers by the CPU that claims
* it. Reparking gets its own repark_* dispatch fields precisely so a
* - Primary boot data (bp and the calibration values appended after it):
* written before the boot CPU is released and consumed while that kernel
* initializes. Secondary and repark publications do not rewrite it.
*
* - Dispatch fields (the remaining fixed-size fields): the wake mailbox,
* rewritten for every publication and staged into registers by the CPU
* that claims it. Reparking gets its own repark_* dispatch fields so a
* repark publication never overwrites the anchor: the two used to
* share fields, and a repark left the anchor pointing at another
* kernel's park area, which triple-faulted the next halt.
Expand All @@ -91,10 +98,23 @@ struct mk_spawn_context {
u32 flags; /* MK_SPAWN_F_* flags */
u32 ready; /* Signal flag */
u32 reserved; /* Padding for alignment */
/* Variable-size struct last - size depends on kernel config */
/* Keep all existing context offsets unchanged. */
struct boot_params bp; /* Standard x86 boot params */
/* Optional boot data belongs after boot_params, in the zeroed tail. */
unsigned long boot_lps; /* Host delay loops per second */
unsigned long boot_cpu_khz; /* Host CPU frequency calibration */
unsigned long boot_tsc_khz; /* Host TSC frequency calibration */
unsigned long boot_apic_hz; /* Host local APIC timer frequency */
u64 abi_magic; /* Validated context producer */
} __aligned(PAGE_SIZE);

static_assert(offsetof(struct mk_spawn_context, bp) == 144);
static_assert(offsetof(struct mk_spawn_context, boot_lps) ==
144 + sizeof(struct boot_params));
static_assert(offsetof(struct mk_spawn_context, abi_magic) ==
144 + sizeof(struct boot_params) + 4 * sizeof(unsigned long));
static_assert(sizeof(struct mk_spawn_context) == 2 * PAGE_SIZE);

/* Pool park loop code, copied by the host into per-instance park pages */
extern char mk_pool_park_start[];
extern char mk_pool_park_end[];
Expand Down Expand Up @@ -139,7 +159,8 @@ void mk_set_spawn_context(struct mk_spawn_context *ctx,
int mk_spawn_cpu(struct mk_instance *instance, int cpu,
struct mk_spawn_context *ctx);

/* Initialize boot context tracking in spawn kernel */
/* Validate and initialize boot context tracking in spawn kernel */
struct mk_spawn_context *mk_validate_boot_context(phys_addr_t ctx_phys);
void mk_init_boot_context(phys_addr_t ctx_phys);

/* Identity page table and trampoline setup */
Expand All @@ -161,6 +182,11 @@ int multikernel_wakeup_secondary_cpu_64(u32 apicid, unsigned long start_eip,
int multikernel_restore_ap(unsigned int cpu, unsigned long cr3,
unsigned long gs_base, unsigned long stack,
unsigned long entry);
#if defined(CONFIG_MULTIKERNEL) && defined(CONFIG_PCI)
void __init x86_multikernel_pci_platform_init(void);
#else
static inline void x86_multikernel_pci_platform_init(void) { }
#endif

#endif /* __ASSEMBLY__ */

Expand Down
6 changes: 6 additions & 0 deletions arch/x86/include/uapi/asm/bootparam.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
#define XLF_5LEVEL (1<<5)
#define XLF_5LEVEL_ENABLED (1<<6)
#define XLF_MEM_ENCRYPTION (1<<7)
#define XLF_MULTIKERNEL_IPI_V2 0x0100
#define XLF_MULTIKERNEL_IPI_V3 0x0200
#define XLF_MULTIKERNEL_IPI_V4 0x0400
#define XLF_MULTIKERNEL_IPI_V5 0x0800
#define XLF_MULTIKERNEL_IPI_V6 0x1000
#define XLF_MULTIKERNEL_IPI_V7 0x2000

#ifndef __ASSEMBLER__

Expand Down
5 changes: 5 additions & 0 deletions arch/x86/kernel/kexec-bzimage64.c
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,11 @@ static void *bzImage64_load(struct kimage *image, char *kernel,
.buf_max = ULONG_MAX, .top_down = true };

header = (struct setup_header *)(kernel + setup_hdr_offset);
if (image->type == KEXEC_TYPE_MULTIKERNEL &&
!(header->xloadflags & XLF_MULTIKERNEL_IPI_V7)) {
pr_err("Loaded kernel lacks the required shared transport layout\n");
return ERR_PTR(-EPROTONOSUPPORT);
}
setup_sects = header->setup_sects;
if (setup_sects == 0)
setup_sects = 4;
Expand Down
24 changes: 19 additions & 5 deletions arch/x86/kernel/kexec-vmlinux.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ struct elf_kernel_info {
unsigned long reloc_size; /* Size of relocation data */
};

struct mk_elf_note_desc {
u64 entry;
u32 ipi_abi_version;
u32 reserved;
};

/*
* Find multikernel entry point from PT_NOTE section.
* Looks for note with name "Linux" and type 0x4d4b ('MK').
Expand Down Expand Up @@ -93,12 +99,20 @@ static unsigned long find_multikernel_entry_note(const void *buf, size_t len,

if (nhdr->n_type == 0x4d4b &&
nhdr->n_namesz == 6 &&
nhdr->n_descsz == sizeof(u64) &&
nhdr->n_descsz == sizeof(struct mk_elf_note_desc) &&
!memcmp(ptr + sizeof(*nhdr), "Linux", 6)) {
u64 entry = *(u64 *)(ptr + sizeof(*nhdr) +
ALIGN(nhdr->n_namesz, 4));
pr_info("multikernel: entry=0x%llx\n", entry);
return entry;
const struct mk_elf_note_desc *desc;

desc = ptr + sizeof(*nhdr) +
ALIGN(nhdr->n_namesz, 4);
if (desc->ipi_abi_version != MK_IPI_ABI_VERSION) {
pr_err("multikernel IPI ABI %u is not supported\n",
desc->ipi_abi_version);
return 0;
}
pr_info("multikernel: entry=0x%llx, IPI ABI=%u\n",
desc->entry, desc->ipi_abi_version);
return desc->entry;
}
ptr += note_size;
}
Expand Down
41 changes: 40 additions & 1 deletion arch/x86/kernel/platform-quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
#include <asm/e820/api.h>
#include <asm/apic.h>
#include <asm/apicdef.h>
#include <linux/cpufeature.h>
#include <asm/mpspec.h>
#include <asm/numa.h>
#include <asm/page.h>
#include <linux/pgtable.h>
#include <asm/pgtable.h>
#include <asm/pgtable_64_types.h>
Expand All @@ -28,6 +30,35 @@ extern pmd_t *populate_extra_pmd(unsigned long vaddr);
extern unsigned long orig_boot_params;

#ifdef CONFIG_MULTIKERNEL
static unsigned long multikernel_cpu_khz;
static unsigned long multikernel_tsc_khz;

static unsigned long multikernel_calibrate_cpu(void)
{
return multikernel_cpu_khz;
}

static unsigned long multikernel_calibrate_tsc(void)
{
return multikernel_tsc_khz;
}

static void __init multikernel_setup_calibration(void)
{
phys_addr_t ctx_phys = orig_boot_params -
offsetof(struct mk_spawn_context, bp);
struct mk_spawn_context *ctx = mk_validate_boot_context(ctx_phys);

if (!ctx || !ctx->boot_tsc_khz)
return;

multikernel_tsc_khz = ctx->boot_tsc_khz;
multikernel_cpu_khz = ctx->boot_cpu_khz ?: ctx->boot_tsc_khz;
x86_platform.calibrate_cpu = multikernel_calibrate_cpu;
x86_platform.calibrate_tsc = multikernel_calibrate_tsc;
setup_force_cpu_cap(X86_FEATURE_TSC_KNOWN_FREQ);
}

/*
* Custom wakeup for multikernel spawn kernels.
* Uses shared spawn table instead of realmode trampoline.
Expand Down Expand Up @@ -105,6 +136,10 @@ static void __init multikernel_parse_smp_config(void)
*/
apic_update_callback(wakeup_secondary_cpu_64, multikernel_wakeup_cpu);
}
#else
static inline void multikernel_setup_calibration(void)
{
}
#endif /* CONFIG_MULTIKERNEL */

void __init x86_early_init_platform_quirks(void)
Expand Down Expand Up @@ -135,6 +170,8 @@ void __init x86_early_init_platform_quirks(void)
x86_platform.legacy.i8042 = X86_LEGACY_I8042_PLATFORM_ABSENT;
break;
case X86_SUBARCH_MULTIKERNEL:
multikernel_setup_calibration();
x86_multikernel_pci_platform_init();
x86_platform.legacy.devices.pnpbios = 0;
x86_platform.legacy.i8042 = X86_LEGACY_I8042_PLATFORM_ABSENT;
x86_platform.legacy.rtc = 0;
Expand Down Expand Up @@ -175,7 +212,9 @@ void __init x86_early_init_platform_quirks(void)
* the PIT - which belongs to the host - and then request
* legacy IRQ0, which can never reach an instance CPU that
* has neither a PIC nor an IO-APIC. Ticks come from the
* local APIC timer via setup_percpu_clockev() instead.
* local APIC timer initialized by setup_percpu_clockev().
* Keeping global_clock_event unset bypasses LAPIC timer
* verification, whose fallback path requires legacy IRQ0.
*/
x86_init.timers.timer_init = x86_init_noop;
x86_init.timers.wallclock_init = x86_init_noop;
Expand Down
1 change: 1 addition & 0 deletions arch/x86/multikernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
#

obj-y += spawn.o direct_boot.o head_64.o
obj-$(CONFIG_PCI) += pci.o
3 changes: 3 additions & 0 deletions arch/x86/multikernel/head_64.S
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <linux/linkage.h>
#include <linux/init.h>
#include <linux/pgtable.h>
#include <linux/multikernel_abi.h>
#include <asm/segment.h>
#include <asm/page.h>
#include <asm/msr.h>
Expand Down Expand Up @@ -273,4 +274,6 @@ SYM_CODE_END(multikernel_secondary_startup)
1: .asciz "Linux"
2: .balign 4
3: .quad multikernel_startup_64 - __START_KERNEL_map
.long MK_IPI_ABI_VERSION
.long 0
4: .balign 4
Loading