From 37df9f8e736665cad0ee84baaee2546b07a42764 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Thu, 30 Jul 2026 08:23:56 +0300 Subject: [PATCH 01/13] x86/multikernel: inherit host timer calibration Spawn kernels cannot calibrate against host-owned PIT, PIC, or IO-APIC resources. Carry the host delay-loop, CPU, TSC, and LAPIC calibration in the immutable boot portion of the spawn context, install it before early clock setup, and retain LAPIC-only timer initialization for instance ticks. Signed-off-by: Nikolay Nikolaev --- arch/x86/include/asm/multikernel.h | 16 +++++++++--- arch/x86/kernel/platform-quirks.c | 40 +++++++++++++++++++++++++++++- arch/x86/multikernel/spawn.c | 22 ++++++++++++++++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/arch/x86/include/asm/multikernel.h b/arch/x86/include/asm/multikernel.h index da9d37264e410d..8288354b8d8bee 100644 --- a/arch/x86/include/asm/multikernel.h +++ b/arch/x86/include/asm/multikernel.h @@ -54,7 +54,7 @@ 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 @@ -62,9 +62,13 @@ static inline int arch_cpu_from_physical_id(u64 phys_id) * 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 fields (boot_* and bp): written before the boot CPU is + * released and consumed while that kernel initializes. Secondary and + * repark publications do not rewrite them. + * + * - 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. @@ -80,6 +84,10 @@ struct mk_spawn_context { unsigned long gs_base; /* Per-CPU GS base (for secondary) */ unsigned long stack; /* Stack pointer (for secondary) */ unsigned long spawn_cr3; /* Spawn kernel's CR3 (for secondary CPU final switch) */ + 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 */ unsigned long park_phys; /* Pool park code page (host owned, never reloaded) */ unsigned long park_cr3; /* Page table parked CPUs run on (host owned) */ unsigned long repark_park_phys; /* REPARK dispatch: park page to move to */ diff --git a/arch/x86/kernel/platform-quirks.c b/arch/x86/kernel/platform-quirks.c index 95d2cd2ccf74f5..466fa69d2495ba 100644 --- a/arch/x86/kernel/platform-quirks.c +++ b/arch/x86/kernel/platform-quirks.c @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -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 = __va(ctx_phys); + + if (ctx->self_phys != ctx_phys || !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. @@ -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) @@ -135,6 +170,7 @@ 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_platform.legacy.devices.pnpbios = 0; x86_platform.legacy.i8042 = X86_LEGACY_I8042_PLATFORM_ABSENT; x86_platform.legacy.rtc = 0; @@ -175,7 +211,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; diff --git a/arch/x86/multikernel/spawn.c b/arch/x86/multikernel/spawn.c index 55487d9ed5a1b2..293788aac5c02d 100644 --- a/arch/x86/multikernel/spawn.c +++ b/arch/x86/multikernel/spawn.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -44,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -447,6 +450,14 @@ int mk_arch_spawn_instance(struct kimage *image, struct mk_instance *instance, (unsigned long)instance->trampoline_va, virt_to_phys(instance->trampoline_va), virt_to_phys(instance->park_va)); + instance->spawn_ctx->boot_lps = cpu_data(cpu).loops_per_jiffy; + if (!instance->spawn_ctx->boot_lps) + instance->spawn_ctx->boot_lps = loops_per_jiffy; + instance->spawn_ctx->boot_lps *= HZ; + instance->spawn_ctx->boot_cpu_khz = cpu_khz; + instance->spawn_ctx->boot_tsc_khz = tsc_khz; + instance->spawn_ctx->boot_apic_hz = + (unsigned long)lapic_timer_period * HZ; return mk_spawn_cpu(instance, cpu, instance->spawn_ctx); } @@ -705,6 +716,17 @@ void mk_init_boot_context(phys_addr_t ctx_phys) } mk_boot_context = ctx; + /* + * A spawn kernel cannot calibrate against legacy timers because they + * belong to the host. Reuse the selected physical CPU's delay and local + * APIC timer calibration, while keeping explicit command-line values + * authoritative. + */ + if (!preset_lpj && ctx->boot_lps) + preset_lpj = DIV_ROUND_CLOSEST_ULL(ctx->boot_lps, HZ); + if (!lapic_timer_period && ctx->boot_apic_hz) + lapic_timer_period = + DIV_ROUND_CLOSEST_ULL(ctx->boot_apic_hz, HZ); /* * The host's control area (this context, the trampoline and park From 6d37c0d121bbd7552b2b93a3c8dfdcb6a8ac00c9 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Thu, 30 Jul 2026 08:16:33 +0300 Subject: [PATCH 02/13] multikernel: carry assigned PCI resource metadata A spawned kernel cannot safely rediscover the resource layout of a device that remains physically attached to the host. Make the instance description the owned source of PCI identity, BAR, and host-bridge metadata. Snapshot device resources and ECAM descriptors into the Multikernel device-tree transport. Parse, clone, and release that data with the instance lifecycle, and restore it from the manifest without retaining live host PCI objects. This establishes the transport and ownership model only; later patches perform the exclusive VF lease. Signed-off-by: Nikolay Nikolaev --- include/linux/multikernel.h | 44 ++++- kernel/multikernel/Makefile | 2 +- kernel/multikernel/baseline.c | 12 ++ kernel/multikernel/core.c | 26 +++ kernel/multikernel/dts.c | 316 ++++++++++++++++++++++++++++++- kernel/multikernel/instance_dt.c | 116 +++--------- kernel/multikernel/internal.h | 8 + kernel/multikernel/kernfs.c | 1 + kernel/multikernel/pci.c | 136 +++++++++++++ 9 files changed, 563 insertions(+), 98 deletions(-) create mode 100644 kernel/multikernel/pci.c diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 011374c92c8481..e98a0c1b78de2f 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -14,6 +14,8 @@ #include #include #include +struct pci_bus; +struct pci_dev; /** * Physical CPU identifiers @@ -403,6 +405,12 @@ struct mk_memory_region { * Represents a single PCI device that should be accessible to an instance. * Format: vendor:device@domain:bus:slot.func */ +#define MK_PCI_RESOURCE_COUNT 6 +struct mk_pci_resource { + u64 start; + u64 end; + u64 flags; +}; struct mk_pci_device { char name[64]; /* Device name from DTB (e.g., "enp9s0_dev") */ u16 vendor; /* PCI vendor ID */ @@ -411,9 +419,30 @@ struct mk_pci_device { u8 bus; /* PCI bus number */ u8 slot; /* PCI slot number */ u8 func; /* PCI function number */ + struct mk_pci_resource resources[MK_PCI_RESOURCE_COUNT]; + bool resources_valid; struct list_head list; /* Link to device list */ }; +/** + * PCI host bridge configuration-space descriptor + * + * Describes an ECAM window for a PCI segment and inclusive bus range. This is + * platform metadata shared with an instance, not an assignable device. + */ +struct mk_pci_host_bridge { + u16 segment; + u8 bus_start; + u8 bus_end; + u64 ecam_base; + struct list_head list; +}; + +#define MK_MAX_PCI_HOST_BRIDGES 16 +int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, + struct mk_pci_host_bridge *bridges, + size_t capacity); + /** * Platform device specification * @@ -454,13 +483,18 @@ struct mk_dt_config { int pci_device_count; /* Number of PCI devices */ bool pci_devices_valid; /* Whether PCI device list is valid */ + /* PCI host bridge metadata */ + struct list_head pci_host_bridges; + int pci_host_bridge_count; + bool pci_host_bridges_valid; + /* Platform device resources */ struct list_head platform_devices; /* List of struct mk_platform_device */ int platform_device_count; /* Number of platform devices */ bool platform_devices_valid; /* Whether platform device list is valid */ /* Extensibility: Reserved fields for future use */ - u32 reserved[7]; /* Reduced due to added fields */ + u32 reserved[4]; /* Raw device tree data */ void *dtb_data; @@ -493,6 +527,11 @@ struct mk_instance { int pci_device_count; /* Number of PCI devices */ bool pci_devices_valid; /* Whether PCI device list is valid */ + /* PCI host bridge metadata (descriptive, never transferred) */ + struct list_head pci_host_bridges; + int pci_host_bridge_count; + bool pci_host_bridges_valid; + /* Platform device resources */ struct list_head platform_devices; /* List of struct mk_platform_device */ int platform_device_count; /* Number of platform devices */ @@ -765,6 +804,9 @@ void mk_kimage_free(struct kimage *image, void *virt_addr, size_t size); /* Device probe filtering against the instance's allowlist */ bool mk_pci_should_probe(struct pci_bus *bus, int devfn); +bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, + u16 *vendor, u16 *device); +void mk_pci_restore_resources(struct pci_dev *dev); bool mk_platform_device_allowed(const char *name, const char *hid); /* Early CPU registration from the manifest (spawn kernels) */ diff --git a/kernel/multikernel/Makefile b/kernel/multikernel/Makefile index 359cccdd20353e..e6f79baf71f595 100644 --- a/kernel/multikernel/Makefile +++ b/kernel/multikernel/Makefile @@ -3,7 +3,7 @@ # Makefile for multikernel support # -obj-y += core.o cpuset.o mem.o kernfs.o dts.o instance_dt.o manifest.o ipi.o messaging.o overlay.o hotplug.o baseline.o +obj-y += core.o cpuset.o mem.o kernfs.o dts.o instance_dt.o manifest.o pci.o ipi.o messaging.o overlay.o hotplug.o baseline.o # DMA-BUF heap for multikernel memory allocation obj-$(CONFIG_DMABUF_HEAPS) += dma_heap.o diff --git a/kernel/multikernel/baseline.c b/kernel/multikernel/baseline.c index d4e2dd9be3a54c..671a1057fbf8d2 100644 --- a/kernel/multikernel/baseline.c +++ b/kernel/multikernel/baseline.c @@ -183,6 +183,9 @@ static void mk_baseline_clear_resources(struct mk_instance *instance) } instance->pci_device_count = 0; instance->pci_devices_valid = false; + mk_pci_host_bridges_free(&instance->pci_host_bridges, + &instance->pci_host_bridge_count, + &instance->pci_host_bridges_valid); list_for_each_entry_safe(plat_dev, plat_tmp, &instance->platform_devices, list) { list_del(&plat_dev->list); @@ -605,6 +608,15 @@ int mk_baseline_validate_and_initialize(const void *fdt, size_t fdt_size) return ret; } + ret = mk_dt_parse_pci_host_bridges(fdt, resources_node, + &root_instance->pci_host_bridges, + &root_instance->pci_host_bridge_count, + &root_instance->pci_host_bridges_valid); + if (ret) { + pr_err("Failed to parse baseline PCI host bridges: %d\n", ret); + return ret; + } + ret = mk_baseline_parse_devices(fdt, resources_node, root_instance); if (ret) { pr_err("Failed to parse baseline devices: %d\n", ret); diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index f91763107cf950..a36c7cd9bb19e1 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -143,6 +143,9 @@ static void mk_instance_release(struct kref *kref) mk_instance_return_all_cpus(instance); mk_instance_return_pci_devices(instance); mk_instance_return_platform_devices(instance); + mk_pci_host_bridges_free(&instance->pci_host_bridges, + &instance->pci_host_bridge_count, + &instance->pci_host_bridges_valid); mk_instance_free_memory(instance); mk_cpu_set_free(instance->cpus); @@ -988,6 +991,29 @@ int mk_instance_reserve_resources(struct mk_instance *instance, pr_warn("Continuing without PCI device assignment\n"); } + /* Copy descriptive PCI host bridge metadata; it is never transferred. */ + if (config->pci_host_bridges_valid && config->pci_host_bridge_count > 0) { + ret = mk_pci_host_bridges_clone(&instance->pci_host_bridges, + &instance->pci_host_bridge_count, + &instance->pci_host_bridges_valid, + &config->pci_host_bridges, + config->pci_host_bridge_count, true); + } else if (root_instance) { + ret = mk_pci_host_bridges_clone(&instance->pci_host_bridges, + &instance->pci_host_bridge_count, + &instance->pci_host_bridges_valid, + &root_instance->pci_host_bridges, + root_instance->pci_host_bridge_count, + root_instance->pci_host_bridges_valid); + } else { + ret = 0; + } + if (ret) { + pr_err("Failed to copy PCI host bridge metadata for instance %d (%s): %d\n", + instance->id, instance->name, ret); + return ret; + } + /* Reserve platform device resources */ ret = mk_instance_reserve_platform_devices(instance, config); if (ret) { diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index 51950eae735129..bc9cbbc5c579cb 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -17,12 +17,21 @@ #include #include #include +#include #include #include #include #include "internal.h" +int __weak +mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, + struct mk_pci_host_bridge *bridges, + size_t capacity) +{ + return 0; +} + static const void *mk_dt_get_base_fdt(void) { if (!root_instance || !root_instance->dtb_data) { @@ -54,6 +63,9 @@ void mk_dt_config_init(struct mk_dt_config *config) INIT_LIST_HEAD(&config->pci_devices); config->pci_device_count = 0; config->pci_devices_valid = true; + INIT_LIST_HEAD(&config->pci_host_bridges); + config->pci_host_bridge_count = 0; + config->pci_host_bridges_valid = false; INIT_LIST_HEAD(&config->platform_devices); config->platform_device_count = 0; @@ -63,6 +75,7 @@ void mk_dt_config_init(struct mk_dt_config *config) void mk_dt_config_free(struct mk_dt_config *config) { struct mk_pci_device *pci_dev, *tmp_pci; + struct mk_pci_host_bridge *host_bridge, *tmp_bridge; struct mk_platform_device *plat_dev, *tmp_plat; if (!config) @@ -81,6 +94,16 @@ void mk_dt_config_free(struct mk_dt_config *config) config->pci_devices_valid = false; } + if (config->pci_host_bridges_valid) { + list_for_each_entry_safe(host_bridge, tmp_bridge, + &config->pci_host_bridges, list) { + list_del(&host_bridge->list); + kfree(host_bridge); + } + config->pci_host_bridge_count = 0; + config->pci_host_bridges_valid = false; + } + /* Free platform device list */ if (config->platform_devices_valid) { list_for_each_entry_safe(plat_dev, tmp_plat, &config->platform_devices, list) { @@ -97,6 +120,119 @@ void mk_dt_config_free(struct mk_dt_config *config) /* Note: We don't free dtb_data here as it's managed by the caller */ } +void mk_pci_host_bridges_free(struct list_head *bridges, int *count, + bool *valid) +{ + struct mk_pci_host_bridge *bridge, *tmp; + + list_for_each_entry_safe(bridge, tmp, bridges, list) { + list_del(&bridge->list); + kfree(bridge); + } + *count = 0; + *valid = false; +} + +int mk_pci_host_bridges_clone(struct list_head *dst, int *dst_count, + bool *dst_valid, const struct list_head *src, + int src_count, bool src_valid) +{ + const struct mk_pci_host_bridge *src_bridge; + struct mk_pci_host_bridge *dst_bridge; + + mk_pci_host_bridges_free(dst, dst_count, dst_valid); + if (!src_valid || src_count == 0) + return 0; + + *dst_valid = true; + list_for_each_entry(src_bridge, src, list) { + dst_bridge = kmemdup(src_bridge, sizeof(*dst_bridge), GFP_KERNEL); + if (!dst_bridge) { + mk_pci_host_bridges_free(dst, dst_count, dst_valid); + return -ENOMEM; + } + INIT_LIST_HEAD(&dst_bridge->list); + list_add_tail(&dst_bridge->list, dst); + (*dst_count)++; + } + + return 0; +} + +int mk_dt_parse_pci_host_bridges(const void *fdt, int resources_node, + struct list_head *bridges, int *count, + bool *valid) +{ + struct mk_pci_host_bridge *bridge, *existing; + const fdt32_t *segment_prop, *bus_range; + const fdt64_t *ecam_prop; + int bridges_node, bridge_node, len, ret = -EINVAL; + u32 segment, bus_start, bus_end; + u64 ecam_base; + + bridges_node = fdt_subnode_offset(fdt, resources_node, + "pci-host-bridges"); + if (bridges_node < 0) + return 0; + + *valid = true; + fdt_for_each_subnode(bridge_node, fdt, bridges_node) { + segment_prop = fdt_getprop(fdt, bridge_node, "segment", &len); + if (!segment_prop || len != sizeof(*segment_prop)) + goto invalid; + segment = fdt32_to_cpu(*segment_prop); + + bus_range = fdt_getprop(fdt, bridge_node, "bus-range", &len); + if (!bus_range || len != 2 * sizeof(*bus_range)) + goto invalid; + bus_start = fdt32_to_cpu(bus_range[0]); + bus_end = fdt32_to_cpu(bus_range[1]); + + ecam_prop = fdt_getprop(fdt, bridge_node, "ecam-base", &len); + if (!ecam_prop || len != sizeof(*ecam_prop)) + goto invalid; + ecam_base = fdt64_to_cpu(*ecam_prop); + + if (segment > U16_MAX || bus_start > U8_MAX || bus_end > U8_MAX || + bus_start > bus_end || !ecam_base || !IS_ALIGNED(ecam_base, SZ_1M)) + goto invalid; + + list_for_each_entry(existing, bridges, list) { + if (existing->segment == segment && + bus_start <= existing->bus_end && + bus_end >= existing->bus_start) { + pr_err("Overlapping PCI host bridge bus ranges in segment %04x\n", + segment); + goto error; + } + } + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + ret = -ENOMEM; + goto error; + } + bridge->segment = segment; + bridge->bus_start = bus_start; + bridge->bus_end = bus_end; + bridge->ecam_base = ecam_base; + list_add_tail(&bridge->list, bridges); + (*count)++; + pr_info("Added PCI host bridge: segment %04x [bus %02x-%02x] ECAM %#llx\n", + bridge->segment, bridge->bus_start, bridge->bus_end, + (unsigned long long)bridge->ecam_base); + } + + return 0; + +invalid: + pr_err("Invalid PCI host bridge metadata in node '%s'\n", + fdt_get_name(fdt, bridge_node, NULL)); +error: + mk_pci_host_bridges_free(bridges, count, valid); + return ret; +} + /** * Function prototypes */ @@ -208,10 +344,11 @@ static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, { const char *pci_id_str; const fdt32_t *vendor_prop, *device_prop; + const fdt64_t *resources_prop; struct mk_pci_device *pci_dev; unsigned int domain, bus, slot, func; const char *node_name; - int len; + int len, i; node_name = fdt_get_name(source_fdt, dev_node, NULL); @@ -248,12 +385,38 @@ static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, return -ENOMEM; } + strscpy(pci_dev->name, device_name, sizeof(pci_dev->name)); pci_dev->vendor = (u16)fdt32_to_cpu(*vendor_prop); pci_dev->device = (u16)fdt32_to_cpu(*device_prop); pci_dev->domain = (u16)domain; pci_dev->bus = (u8)bus; pci_dev->slot = (u8)slot; pci_dev->func = (u8)func; + resources_prop = fdt_getprop(source_fdt, dev_node, "bar-resources", &len); + if (resources_prop) { + if (len != MK_PCI_RESOURCE_COUNT * 3 * sizeof(*resources_prop)) { + pr_err("Invalid bar-resources in device '%s'\n", device_name); + kfree(pci_dev); + return -EINVAL; + } + for (i = 0; i < MK_PCI_RESOURCE_COUNT; i++) { + u64 start = fdt64_to_cpu(resources_prop[i * 3]); + u64 end = fdt64_to_cpu(resources_prop[i * 3 + 1]); + u64 flags = fdt64_to_cpu(resources_prop[i * 3 + 2]); + + if ((start || end) && + (end < start || !(flags & (IORESOURCE_IO | IORESOURCE_MEM)))) { + pr_err("Invalid PCI BAR %d range in device '%s'\n", + i, device_name); + kfree(pci_dev); + return -EINVAL; + } + pci_dev->resources[i].start = start; + pci_dev->resources[i].end = end; + pci_dev->resources[i].flags = flags; + } + pci_dev->resources_valid = true; + } list_add_tail(&pci_dev->list, &config->pci_devices); config->pci_device_count++; @@ -557,6 +720,16 @@ int mk_dt_parse(const void *dtb_data, size_t dtb_size, return ret; } + ret = mk_dt_parse_pci_host_bridges(fdt, resources_node, + &config->pci_host_bridges, + &config->pci_host_bridge_count, + &config->pci_host_bridges_valid); + if (ret) { + pr_err("Failed to parse PCI host bridge metadata: %d\n", ret); + mk_dt_config_free(config); + return ret; + } + ret = mk_dt_parse_devices(fdt, resources_node, config); if (ret) { pr_err("Failed to parse device resources: %d\n", ret); @@ -564,9 +737,10 @@ int mk_dt_parse(const void *dtb_data, size_t dtb_size, return ret; } - pr_info("Successfully parsed multikernel device tree with %zu bytes memory, %u CPUs, %d PCI devices, and %d platform devices\n", + pr_info("Successfully parsed multikernel device tree with %zu bytes memory, %d CPUs, %d PCI host bridges, %d PCI devices, and %d platform devices\n", config->memory_size, mk_cpu_set_count(config->cpus), - config->pci_device_count, config->platform_device_count); + config->pci_host_bridge_count, config->pci_device_count, + config->platform_device_count); return 0; } @@ -607,6 +781,17 @@ int mk_dt_parse_resources(const void *fdt, int resources_node, return ret; } + ret = mk_dt_parse_pci_host_bridges(fdt, resources_node, + &config->pci_host_bridges, + &config->pci_host_bridge_count, + &config->pci_host_bridges_valid); + if (ret) { + pr_err("Failed to parse PCI host bridge metadata for '%s': %d\n", + instance_name, ret); + mk_dt_config_free(config); + return ret; + } + ret = mk_dt_parse_devices(fdt, resources_node, config); if (ret) { pr_err("Failed to parse device resources for '%s': %d\n", instance_name, ret); @@ -614,10 +799,11 @@ int mk_dt_parse_resources(const void *fdt, int resources_node, return ret; } - pr_info("Successfully parsed instance '%s': %zu bytes memory, %u CPUs, %d PCI devices, %d platform devices\n", + pr_info("Successfully parsed instance '%s': %zu bytes memory, %d CPUs, %d PCI host bridges, %d PCI devices, %d platform devices\n", instance_name, config->memory_size, mk_cpu_set_count(config->cpus), - config->pci_device_count, config->platform_device_count); + config->pci_host_bridge_count, config->pci_device_count, + config->platform_device_count); return 0; } @@ -816,6 +1002,7 @@ int mk_dt_get_property_size(const void *dtb_data, size_t dtb_size, void mk_dt_print_config(const struct mk_dt_config *config) { struct mk_pci_device *pci_dev; + struct mk_pci_host_bridge *host_bridge; struct mk_platform_device *plat_dev; if (!config) { @@ -846,6 +1033,17 @@ void mk_dt_print_config(const struct mk_dt_config *config) pr_info(" CPU assignment: unavailable (allocation failed)\n"); } + if (config->pci_host_bridges_valid) { + pr_info(" PCI host bridges: %d\n", config->pci_host_bridge_count); + list_for_each_entry(host_bridge, &config->pci_host_bridges, list) + pr_info(" - segment %04x [bus %02x-%02x] ECAM %#llx\n", + host_bridge->segment, host_bridge->bus_start, + host_bridge->bus_end, + (unsigned long long)host_bridge->ecam_base); + } else { + pr_info(" PCI host bridges: none specified\n"); + } + if (config->pci_devices_valid) { if (config->pci_device_count == 0) { pr_info(" PCI devices: none specified\n"); @@ -880,6 +1078,68 @@ void mk_dt_print_config(const struct mk_dt_config *config) pr_info(" DTB: %zu bytes\n", config->dtb_size); } +static int mk_dt_emit_pci_host_bridge(void *fdt, + const struct mk_pci_host_bridge *bridge) +{ + char node_name[32]; + fdt32_t bus_range[2]; + int ret; + + snprintf(node_name, sizeof(node_name), "host@%04x,%02x", + bridge->segment, bridge->bus_start); + ret = fdt_begin_node(fdt, node_name); + if (ret) + return ret; + ret = fdt_property_u32(fdt, "segment", bridge->segment); + if (ret) + return ret; + bus_range[0] = cpu_to_fdt32(bridge->bus_start); + bus_range[1] = cpu_to_fdt32(bridge->bus_end); + ret = fdt_property(fdt, "bus-range", bus_range, sizeof(bus_range)); + if (ret) + return ret; + ret = fdt_property_u64(fdt, "ecam-base", bridge->ecam_base); + if (ret) + return ret; + return fdt_end_node(fdt); +} + +static int mk_dt_emit_pci_host_bridges(void *fdt, + const struct mk_instance *instance) +{ + struct mk_pci_host_bridge discovered[MK_MAX_PCI_HOST_BRIDGES]; + const struct mk_pci_host_bridge *bridge; + int discovered_count, index, ret; + + discovered_count = + mk_arch_snapshot_pci_host_bridges(instance, discovered, + ARRAY_SIZE(discovered)); + if (discovered_count < 0) + return discovered_count; + if (!discovered_count && + (!instance->pci_host_bridges_valid || + !instance->pci_host_bridge_count)) + return 0; + + ret = fdt_begin_node(fdt, "pci-host-bridges"); + if (ret) + return ret; + if (discovered_count) { + for (index = 0; index < discovered_count; index++) { + ret = mk_dt_emit_pci_host_bridge(fdt, &discovered[index]); + if (ret) + return ret; + } + } else { + list_for_each_entry(bridge, &instance->pci_host_bridges, list) { + ret = mk_dt_emit_pci_host_bridge(fdt, bridge); + if (ret) + return ret; + } + } + return fdt_end_node(fdt); +} + /** * mk_dt_generate_instance_dtb() - Generate instance DTB from kernel data structures * @instance: Instance with transferred resources (CPUs, memory, devices) @@ -983,6 +1243,10 @@ int mk_dt_generate_instance_dtb(struct mk_instance *instance, if (ret) goto err_free; } + ret = mk_dt_emit_pci_host_bridges(fdt, instance); + if (ret) + goto err_free; + if ((instance->pci_devices_valid && instance->pci_device_count > 0) || (instance->platform_devices_valid && instance->platform_device_count > 0)) { ret = fdt_begin_node(fdt, "devices"); @@ -994,6 +1258,43 @@ int mk_dt_generate_instance_dtb(struct mk_instance *instance, list_for_each_entry(pci_dev, &instance->pci_devices, list) { char node_name[64]; char pci_id_str[32]; + fdt64_t resources[MK_PCI_RESOURCE_COUNT * 3]; + struct pci_dev *live_dev = NULL; + unsigned int devfn; + int i; + + if (!pci_dev->resources_valid) { + devfn = PCI_DEVFN(pci_dev->slot, + pci_dev->func); + live_dev = + pci_get_domain_bus_and_slot(pci_dev->domain, + pci_dev->bus, devfn); + if (!live_dev) { + pr_err("PCI device %04x:%02x:%02x.%x disappeared before resource snapshot\n", + pci_dev->domain, pci_dev->bus, + pci_dev->slot, pci_dev->func); + ret = -ENODEV; + goto err_free; + } + } + for (i = 0; i < MK_PCI_RESOURCE_COUNT; i++) { + u64 start, end, flags; + + if (live_dev) { + start = pci_resource_start(live_dev, i); + end = pci_resource_end(live_dev, i); + flags = pci_resource_flags(live_dev, i); + } else { + start = pci_dev->resources[i].start; + end = pci_dev->resources[i].end; + flags = pci_dev->resources[i].flags; + } + resources[i * 3] = cpu_to_fdt64(start); + resources[i * 3 + 1] = cpu_to_fdt64(end); + resources[i * 3 + 2] = cpu_to_fdt64(flags); + } + if (live_dev) + pci_dev_put(live_dev); snprintf(node_name, sizeof(node_name), "%s", pci_dev->name[0] ? pci_dev->name : "unnamed_pci"); @@ -1015,6 +1316,11 @@ int mk_dt_generate_instance_dtb(struct mk_instance *instance, ret = fdt_property_u32(fdt, "device-id", pci_dev->device); if (ret) goto err_free; + ret = fdt_property(fdt, "bar-resources", resources, + sizeof(resources)); + if (ret) + goto err_free; + ret = fdt_end_node(fdt); if (ret) goto err_free; } diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index f21cc947149f10..0d68d4c383e636 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -405,6 +405,9 @@ static struct mk_instance * __init alloc_mk_instance(int instance_id, const char INIT_LIST_HEAD(&instance->pci_devices); instance->pci_devices_valid = false; instance->pci_device_count = 0; + INIT_LIST_HEAD(&instance->pci_host_bridges); + instance->pci_host_bridges_valid = false; + instance->pci_host_bridge_count = 0; INIT_LIST_HEAD(&instance->platform_devices); instance->platform_devices_valid = false; instance->platform_device_count = 0; @@ -452,19 +455,13 @@ static int __init mk_copy_pci_devices(const struct mk_dt_config *config, instance->pci_devices_valid = true; list_for_each_entry(src_dev, &config->pci_devices, list) { - dst_dev = kzalloc(sizeof(*dst_dev), GFP_KERNEL); + dst_dev = kmemdup(src_dev, sizeof(*dst_dev), GFP_KERNEL); if (!dst_dev) { pr_err("Failed to allocate PCI device entry\n"); return -ENOMEM; } - dst_dev->vendor = src_dev->vendor; - dst_dev->device = src_dev->device; - dst_dev->domain = src_dev->domain; - dst_dev->bus = src_dev->bus; - dst_dev->slot = src_dev->slot; - dst_dev->func = src_dev->func; - + INIT_LIST_HEAD(&dst_dev->list); list_add_tail(&dst_dev->list, &instance->pci_devices); instance->pci_device_count++; } @@ -473,6 +470,17 @@ static int __init mk_copy_pci_devices(const struct mk_dt_config *config, return 0; } +static int __init mk_copy_pci_host_bridges(const struct mk_dt_config *config, + struct mk_instance *instance) +{ + return mk_pci_host_bridges_clone(&instance->pci_host_bridges, + &instance->pci_host_bridge_count, + &instance->pci_host_bridges_valid, + &config->pci_host_bridges, + config->pci_host_bridge_count, + config->pci_host_bridges_valid); +} + static int __init mk_copy_platform_devices(const struct mk_dt_config *config, struct mk_instance *instance) { @@ -771,6 +779,12 @@ int __init mk_instance_restore_from_manifest(void) goto cleanup_devices; } + ret = mk_copy_pci_host_bridges(&config, instance); + if (ret) { + pr_err("Failed to copy PCI host bridge metadata: %d\n", ret); + goto cleanup_devices; + } + ret = mk_copy_platform_devices(&config, instance); if (ret) { pr_err("Failed to copy platform devices: %d\n", ret); @@ -797,6 +811,9 @@ int __init mk_instance_restore_from_manifest(void) return 0; cleanup_devices: + mk_pci_host_bridges_free(&instance->pci_host_bridges, + &instance->pci_host_bridge_count, + &instance->pci_host_bridges_valid); if (instance->pci_devices_valid) { struct mk_pci_device *pci_dev, *tmp_pci; list_for_each_entry_safe(pci_dev, tmp_pci, &instance->pci_devices, list) { @@ -828,89 +845,6 @@ int __init mk_instance_restore_from_manifest(void) /* Run at early_initcall to enforce CPU restrictions before per-CPU allocations */ early_initcall(mk_instance_restore_from_manifest); -/** - * mk_pci_should_probe - Check if PCI probing should occur at all - * @bus: PCI bus - * @devfn: device/function number - * - * Called BEFORE any PCI config space reads to determine if probing - * should proceed. This prevents config space accesses to devices - * that are not in the whitelist. - * - * Returns: true if probing should proceed, false to skip entirely - */ -bool mk_pci_should_probe(struct pci_bus *bus, int devfn) -{ - struct mk_pci_device *pci_dev; - u16 domain = pci_domain_nr(bus); - u8 bus_num = bus->number; - u8 slot = PCI_SLOT(devfn); - u8 func = PCI_FUNC(devfn); - u8 hdr_type; - - if (!root_instance) - return true; - - if (!root_instance->dtb_data) - return true; - - if (!root_instance->pci_devices_valid || root_instance->pci_device_count == 0) - return false; - - list_for_each_entry(pci_dev, &root_instance->pci_devices, list) { - if (pci_dev->domain != domain) - continue; - - /* Exact location match - always allow */ - if (pci_dev->bus == bus_num && - pci_dev->slot == slot && - pci_dev->func == func) - return true; - } - - /* - * Check if any whitelisted device is on a downstream bus. - * If so, this might be a bridge in the path to that device. - */ - list_for_each_entry(pci_dev, &root_instance->pci_devices, list) { - if (pci_dev->domain == domain && pci_dev->bus > bus_num) - goto check_bridge; - } - return false; - -check_bridge: - /* - * There's a whitelisted device on a downstream bus. Check if this - * is a bridge that serves it. - */ - if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type) == 0) { - bool is_bridge = ((hdr_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE); - - if (is_bridge) { - u8 secondary_bus = 0, subordinate_bus = 0; - - pci_bus_read_config_byte(bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); - pci_bus_read_config_byte(bus, devfn, PCI_SUBORDINATE_BUS, &subordinate_bus); - - /* - * Allow bridge if there's a whitelisted device on any bus - * between secondary and subordinate (inclusive). - */ - if (secondary_bus > 0 && subordinate_bus >= secondary_bus) { - list_for_each_entry(pci_dev, &root_instance->pci_devices, list) { - if (pci_dev->domain == domain && - pci_dev->bus >= secondary_bus && - pci_dev->bus <= subordinate_bus) - return true; - } - } - } - } - - return false; -} -EXPORT_SYMBOL_GPL(mk_pci_should_probe); - bool mk_platform_device_allowed(const char *name, const char *hid) { struct mk_platform_device *plat_dev; diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index 75e4b8d25eb939..ad5da764883f26 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -18,6 +18,14 @@ int mk_dt_parse_resources(const void *fdt, int resources_node, const char *instance_name, struct mk_dt_config *config); int mk_dt_generate_instance_dtb(struct mk_instance *instance, void **out_dtb, size_t *out_size); +int mk_dt_parse_pci_host_bridges(const void *fdt, int resources_node, + struct list_head *bridges, int *count, + bool *valid); +int mk_pci_host_bridges_clone(struct list_head *dst, int *dst_count, + bool *dst_valid, const struct list_head *src, + int src_count, bool src_valid); +void mk_pci_host_bridges_free(struct list_head *bridges, int *count, + bool *valid); /* overlay.c */ extern struct kernfs_node *mk_overlay_root_kn; diff --git a/kernel/multikernel/kernfs.c b/kernel/multikernel/kernfs.c index a0573fc23a1c99..9e7cae53585b4c 100644 --- a/kernel/multikernel/kernfs.c +++ b/kernel/multikernel/kernfs.c @@ -269,6 +269,7 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, INIT_LIST_HEAD(&instance->memory_regions); INIT_LIST_HEAD(&instance->list); INIT_LIST_HEAD(&instance->pci_devices); + INIT_LIST_HEAD(&instance->pci_host_bridges); INIT_LIST_HEAD(&instance->platform_devices); kref_init(&instance->refcount); diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c new file mode 100644 index 00000000000000..baf1f48a445909 --- /dev/null +++ b/kernel/multikernel/pci.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Multikernel PCI assignment policy + * + * Keeps assigned-device discovery, identity presentation, bridge traversal, + * and resource restoration independent from the manifest that populated + * the current instance. + */ + +#include +#include + +#include "internal.h" + +static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, + int devfn) +{ + struct mk_pci_device *device; + u16 domain = pci_domain_nr(bus); + u8 slot = PCI_SLOT(devfn); + u8 func = PCI_FUNC(devfn); + + if (!root_instance || !root_instance->dtb_data || + !root_instance->pci_devices_valid) + return NULL; + + list_for_each_entry(device, &root_instance->pci_devices, list) { + if (device->domain == domain && device->bus == bus->number && + device->slot == slot && device->func == func) + return device; + } + + return NULL; +} + +/** + * mk_pci_get_assigned_identity - Get the identity presented to an instance + * @bus: PCI bus + * @devfn: device/function number + * @vendor: assigned Vendor ID + * @device_id: assigned Device ID + * + * Returns: true when assignment metadata contains an exact location match. + */ +bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, + u16 *vendor, u16 *device_id) +{ + struct mk_pci_device *device = mk_pci_find_assigned(bus, devfn); + + if (!device) + return false; + + *vendor = device->vendor; + *device_id = device->device; + pr_notice("MK_SECONDARY_ASSIGNED_PCI_IDENTITY bdf=%04x:%02x:%02x.%x vendor=%04x device=%04x\n", + device->domain, device->bus, device->slot, device->func, + *vendor, *device_id); + return true; +} + +void mk_pci_restore_resources(struct pci_dev *dev) +{ + struct mk_pci_device *device; + int i; + + device = mk_pci_find_assigned(dev->bus, dev->devfn); + if (!device || !device->resources_valid) + return; + + dev->non_compliant_bars = true; + for (i = 0; i < MK_PCI_RESOURCE_COUNT; i++) { + dev->resource[i].start = device->resources[i].start; + dev->resource[i].end = device->resources[i].end; + dev->resource[i].flags = device->resources[i].flags; + } + pr_info("Restored PCI BAR resources for %s\n", pci_name(dev)); +} +EXPORT_SYMBOL_GPL(mk_pci_restore_resources); + +static bool mk_pci_bridge_reaches_assigned(struct pci_bus *bus, int devfn) +{ + struct mk_pci_device *device; + u16 domain = pci_domain_nr(bus); + u8 secondary_bus = 0; + u8 subordinate_bus = 0; + u8 hdr_type; + + if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type) || + (hdr_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE) + return false; + + pci_bus_read_config_byte(bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); + pci_bus_read_config_byte(bus, devfn, PCI_SUBORDINATE_BUS, + &subordinate_bus); + if (!secondary_bus || subordinate_bus < secondary_bus) + return false; + + list_for_each_entry(device, &root_instance->pci_devices, list) { + if (device->domain == domain && device->bus >= secondary_bus && + device->bus <= subordinate_bus) + return true; + } + + return false; +} + +/** + * mk_pci_should_probe - Check whether PCI probing may access a location + * @bus: PCI bus + * @devfn: device/function number + * + * Exact assigned functions and bridges leading to downstream assignments are + * visible. Other functions are rejected before their config space is read. + * + * Returns: true if probing should proceed, false to skip entirely. + */ +bool mk_pci_should_probe(struct pci_bus *bus, int devfn) +{ + struct mk_pci_device *device; + u16 domain = pci_domain_nr(bus); + + if (!root_instance || !root_instance->dtb_data) + return true; + if (!root_instance->pci_devices_valid || + !root_instance->pci_device_count) + return false; + if (mk_pci_find_assigned(bus, devfn)) + return true; + + list_for_each_entry(device, &root_instance->pci_devices, list) { + if (device->domain == domain && device->bus > bus->number) + return mk_pci_bridge_reaches_assigned(bus, devfn); + } + + return false; +} From 5295e1020c0285aa6f5e8bd16e2549a08e20b6af Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Thu, 30 Jul 2026 08:16:33 +0300 Subject: [PATCH 03/13] x86/multikernel: own PCI ECAM state transfer PCI ECAM discovery and restoration are architecture policy. Keeping that policy in generic Multikernel or PCI probe code makes the transport depend on x86 implementation details. Provide a locked MMCONFIG region iterator, snapshot only windows that cover assigned devices, and restore those windows from the instance description during x86 platform setup. Select the restored ECAM operations before the spawned kernel scans PCI. Generic MMCONFIG code remains unaware of Multikernel instances. Signed-off-by: Nikolay Nikolaev --- arch/x86/include/asm/multikernel.h | 11 +++ arch/x86/include/asm/pci_x86.h | 4 + arch/x86/kernel/platform-quirks.c | 1 + arch/x86/multikernel/Makefile | 2 +- arch/x86/multikernel/pci.c | 125 +++++++++++++++++++++++++++++ arch/x86/pci/mmconfig-shared.c | 19 +++++ include/linux/multikernel.h | 3 - kernel/multikernel/dts.c | 1 + 8 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 arch/x86/multikernel/pci.c diff --git a/arch/x86/include/asm/multikernel.h b/arch/x86/include/asm/multikernel.h index 8288354b8d8bee..7637245b51fc34 100644 --- a/arch/x86/include/asm/multikernel.h +++ b/arch/x86/include/asm/multikernel.h @@ -10,6 +10,7 @@ #ifndef __ASSEMBLY__ +#include #include #include #include @@ -169,6 +170,16 @@ 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); +struct mk_pci_host_bridge; + +#ifdef CONFIG_MULTIKERNEL +void __init x86_multikernel_pci_platform_init(void); +int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, + struct mk_pci_host_bridge *bridges, + size_t capacity); +#else +static inline void x86_multikernel_pci_platform_init(void) { } +#endif #endif /* __ASSEMBLY__ */ diff --git a/arch/x86/include/asm/pci_x86.h b/arch/x86/include/asm/pci_x86.h index 70533fdcbf02c9..668bfead038643 100644 --- a/arch/x86/include/asm/pci_x86.h +++ b/arch/x86/include/asm/pci_x86.h @@ -190,6 +190,10 @@ extern struct pci_mmcfg_region *__init pci_mmconfig_add(int segment, int start, extern struct list_head pci_mmcfg_list; +typedef int (*pci_mmcfg_region_cb)(const struct pci_mmcfg_region *region, + void *data); +int pci_mmcfg_walk_regions(pci_mmcfg_region_cb callback, void *data); + #define PCI_MMCFG_BUS_OFFSET(bus) ((bus) << 20) /* diff --git a/arch/x86/kernel/platform-quirks.c b/arch/x86/kernel/platform-quirks.c index 466fa69d2495ba..7765d7613c06c4 100644 --- a/arch/x86/kernel/platform-quirks.c +++ b/arch/x86/kernel/platform-quirks.c @@ -171,6 +171,7 @@ void __init x86_early_init_platform_quirks(void) 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; diff --git a/arch/x86/multikernel/Makefile b/arch/x86/multikernel/Makefile index 331bd895af1f7b..f4d399154f4ce6 100644 --- a/arch/x86/multikernel/Makefile +++ b/arch/x86/multikernel/Makefile @@ -3,4 +3,4 @@ # Makefile for multikernel spawn support # -obj-y += spawn.o direct_boot.o head_64.o +obj-y += spawn.o pci.o direct_boot.o head_64.o diff --git a/arch/x86/multikernel/pci.c b/arch/x86/multikernel/pci.c new file mode 100644 index 00000000000000..80bbc4344eee84 --- /dev/null +++ b/arch/x86/multikernel/pci.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * x86 PCI support for multikernel spawn kernels. + * + * This file owns the x86-specific transport of PCI ECAM windows. Generic + * MMCONFIG code only provides an iterator over its region list. + */ + +#include +#include +#include + +#include +#include +#include + +struct mk_mmcfg_snapshot { + const struct mk_instance *instance; + struct mk_pci_host_bridge *bridges; + size_t capacity; + size_t count; +}; + +#ifdef CONFIG_PCI_MMCONFIG +static int mk_mmcfg_snapshot_region(const struct pci_mmcfg_region *region, + void *data) +{ + struct mk_mmcfg_snapshot *snapshot = data; + const struct mk_pci_device *device; + + list_for_each_entry(device, &snapshot->instance->pci_devices, list) { + if (device->domain != region->segment || + device->bus < region->start_bus || + device->bus > region->end_bus) + continue; + if (snapshot->count == snapshot->capacity) + return -ENOSPC; + + snapshot->bridges[snapshot->count].segment = region->segment; + snapshot->bridges[snapshot->count].bus_start = region->start_bus; + snapshot->bridges[snapshot->count].bus_end = region->end_bus; + snapshot->bridges[snapshot->count].ecam_base = region->address; + pr_info("Multikernel publishing ECAM segment %04x [bus %02x-%02x] base %#llx\n", + region->segment, region->start_bus, region->end_bus, + (unsigned long long)region->address); + snapshot->count++; + break; + } + + return 0; +} + +int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, + struct mk_pci_host_bridge *bridges, + size_t capacity) +{ + struct mk_mmcfg_snapshot snapshot = { + .instance = instance, + .bridges = bridges, + .capacity = capacity, + }; + int ret; + + if (!instance || !bridges || !capacity || + !instance->pci_devices_valid || !instance->pci_device_count) + return 0; + + ret = pci_mmcfg_walk_regions(mk_mmcfg_snapshot_region, &snapshot); + return ret ?: snapshot.count; +} + +static int __init x86_multikernel_pci_arch_init(void) +{ + const struct mk_pci_host_bridge *bridge; + + if (!root_instance || !root_instance->pci_host_bridges_valid || + !root_instance->pci_host_bridge_count) { + pr_err("Multikernel has no restored PCI host bridge metadata\n"); + return 1; + } + if (!list_empty(&pci_mmcfg_list)) { + pr_err("Multikernel PCI host bridge list was not empty before restore\n"); + return 1; + } + + list_for_each_entry(bridge, &root_instance->pci_host_bridges, list) { + if (!pci_mmconfig_add(bridge->segment, bridge->bus_start, + bridge->bus_end, bridge->ecam_base)) + return 1; + pr_notice("Multikernel restored ECAM segment %04x [bus %02x-%02x] base %#llx\n", + bridge->segment, bridge->bus_start, bridge->bus_end, + (unsigned long long)bridge->ecam_base); + } + if (!pci_mmcfg_arch_init()) { + pr_err("Multikernel failed to map restored PCI ECAM windows\n"); + return 1; + } + + raw_pci_ops = &pci_mmcfg; + raw_pci_ext_ops = &pci_mmcfg; + pr_notice("Multikernel selected ECAM for PCI config access\n"); + + return 0; +} +#else +int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, + struct mk_pci_host_bridge *bridges, + size_t capacity) +{ + return 0; +} + +static int __init x86_multikernel_pci_arch_init(void) +{ + pr_err("Multikernel PCI requires CONFIG_PCI_MMCONFIG\n"); + return 1; +} +#endif + +void __init x86_multikernel_pci_platform_init(void) +{ + pci_probe = PCI_PROBE_MMCONF | PCI_PROBE_NOEARLY; + x86_init.pci.arch_init = x86_multikernel_pci_arch_init; + x86_init.pci.init = pci_legacy_init; +} diff --git a/arch/x86/pci/mmconfig-shared.c b/arch/x86/pci/mmconfig-shared.c index 1f45223259204d..c22666789167ca 100644 --- a/arch/x86/pci/mmconfig-shared.c +++ b/arch/x86/pci/mmconfig-shared.c @@ -35,6 +35,25 @@ static DEFINE_MUTEX(pci_mmcfg_lock); LIST_HEAD(pci_mmcfg_list); +int pci_mmcfg_walk_regions(pci_mmcfg_region_cb callback, void *data) +{ + const struct pci_mmcfg_region *region; + int ret = 0; + + if (!callback) + return -EINVAL; + + mutex_lock(&pci_mmcfg_lock); + list_for_each_entry(region, &pci_mmcfg_list, list) { + ret = callback(region, data); + if (ret) + break; + } + mutex_unlock(&pci_mmcfg_lock); + + return ret; +} + static void __init pci_mmconfig_remove(struct pci_mmcfg_region *cfg) { if (cfg->res.parent) diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index e98a0c1b78de2f..1a79d8cd58cac3 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -439,9 +439,6 @@ struct mk_pci_host_bridge { }; #define MK_MAX_PCI_HOST_BRIDGES 16 -int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, - struct mk_pci_host_bridge *bridges, - size_t capacity); /** * Platform device specification diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index bc9cbbc5c579cb..352b91aa9e5995 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "internal.h" From 62b31bec2bf1681fe3ee475ae6b190ebbabacca9 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Thu, 30 Jul 2026 08:16:33 +0300 Subject: [PATCH 04/13] pci/multikernel: filter config access to assigned devices Normal PCI enumeration in a cooperative spawn kernel should not probe or mutate functions retained by the primary kernel. Filtering in the generic PCI probe path is too late and puts Multikernel policy in shared PCI code. Wrap the x86 root configuration operations used by synthetic roots. Normal PCI accesses then reject functions without assignment metadata. Present assigned identity from instance metadata, retain only the bridge traversal needed to reach an assignment, and restore recorded BAR resources during early fixup. This constrains normal in-kernel PCI paths, not a privileged secondary kernel that deliberately accesses physical configuration mechanisms. Keep assignment policy in Multikernel and remove the special case from drivers/pci/probe.c. Signed-off-by: Nikolay Nikolaev --- arch/x86/multikernel/pci.c | 44 +++++++++++++++++++++++++++++++++++++ drivers/pci/probe.c | 9 -------- include/linux/multikernel.h | 10 +++++---- kernel/multikernel/pci.c | 40 +++++++++++++++++++-------------- 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/arch/x86/multikernel/pci.c b/arch/x86/multikernel/pci.c index 80bbc4344eee84..9121921b10a158 100644 --- a/arch/x86/multikernel/pci.c +++ b/arch/x86/multikernel/pci.c @@ -21,6 +21,47 @@ struct mk_mmcfg_snapshot { size_t count; }; +static struct pci_ops mk_pci_native_ops; + +static bool mk_pci_identity_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *value) +{ + u16 vendor, device; + u32 identity; + u32 mask; + + if (where < PCI_VENDOR_ID || where + size > PCI_COMMAND || + !mk_pci_get_assigned_identity(bus, devfn, &vendor, &device)) + return false; + + identity = vendor | (u32)device << 16; + mask = size == sizeof(identity) ? ~0U : (1U << (size * 8)) - 1; + *value = (identity >> (where * 8)) & mask; + return true; +} + +static int mk_pci_read(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 *value) +{ + if (!mk_pci_should_probe(bus, devfn, &mk_pci_native_ops)) { + *value = ~0U; + return PCIBIOS_DEVICE_NOT_FOUND; + } + if (mk_pci_identity_read(bus, devfn, where, size, value)) + return PCIBIOS_SUCCESSFUL; + + return mk_pci_native_ops.read(bus, devfn, where, size, value); +} + +static int mk_pci_write(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 value) +{ + if (!mk_pci_should_probe(bus, devfn, &mk_pci_native_ops)) + return PCIBIOS_DEVICE_NOT_FOUND; + + return mk_pci_native_ops.write(bus, devfn, where, size, value); +} + #ifdef CONFIG_PCI_MMCONFIG static int mk_mmcfg_snapshot_region(const struct pci_mmcfg_region *region, void *data) @@ -98,6 +139,9 @@ static int __init x86_multikernel_pci_arch_init(void) raw_pci_ops = &pci_mmcfg; raw_pci_ext_ops = &pci_mmcfg; + mk_pci_native_ops = pci_root_ops; + pci_root_ops.read = mk_pci_read; + pci_root_ops.write = mk_pci_write; pr_notice("Multikernel selected ECAM for PCI config access\n"); return 0; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 0608ae159f41ee..41183aed8f5d94 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -22,7 +22,6 @@ #include #include #include -#include #include "pci.h" #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ @@ -2623,14 +2622,6 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) struct pci_dev *dev; u32 l; - /* - * For multikernel spawns, check if we should even probe this location - * BEFORE any config space access. This prevents hardware conflicts - * when the host kernel is also using PCI devices. - */ - if (IS_ENABLED(CONFIG_MULTIKERNEL) && !mk_pci_should_probe(bus, devfn)) - return NULL; - /* * Create pwrctrl device (if required) for the PCI device to handle the * power state. If the pwrctrl device is created, then skip scanning diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 1a79d8cd58cac3..30219aeb8a268f 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -15,7 +15,7 @@ #include #include struct pci_bus; -struct pci_dev; +struct pci_ops; /** * Physical CPU identifiers @@ -800,10 +800,10 @@ void *mk_kimage_alloc(struct kimage *image, size_t size, size_t align); void mk_kimage_free(struct kimage *image, void *virt_addr, size_t size); /* Device probe filtering against the instance's allowlist */ -bool mk_pci_should_probe(struct pci_bus *bus, int devfn); +bool mk_pci_should_probe(struct pci_bus *bus, int devfn, + const struct pci_ops *ops); bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, u16 *vendor, u16 *device); -void mk_pci_restore_resources(struct pci_dev *dev); bool mk_platform_device_allowed(const char *name, const char *hid); /* Early CPU registration from the manifest (spawn kernels) */ @@ -851,7 +851,9 @@ static inline void mk_kimage_free(struct kimage *image, void *virt_addr, size_t size) { } -static inline bool mk_pci_should_probe(struct pci_bus *bus, int devfn) + +static inline bool mk_pci_should_probe(struct pci_bus *bus, int devfn, + const struct pci_ops *ops) { return true; } diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index baf1f48a445909..369a94ca145664 100644 --- a/kernel/multikernel/pci.c +++ b/kernel/multikernel/pci.c @@ -12,8 +12,7 @@ #include "internal.h" -static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, - int devfn) +static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, int devfn) { struct mk_pci_device *device; u16 domain = pci_domain_nr(bus); @@ -52,13 +51,10 @@ bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, *vendor = device->vendor; *device_id = device->device; - pr_notice("MK_SECONDARY_ASSIGNED_PCI_IDENTITY bdf=%04x:%02x:%02x.%x vendor=%04x device=%04x\n", - device->domain, device->bus, device->slot, device->func, - *vendor, *device_id); return true; } -void mk_pci_restore_resources(struct pci_dev *dev) +static void mk_pci_restore_resources(struct pci_dev *dev) { struct mk_pci_device *device; int i; @@ -75,23 +71,33 @@ void mk_pci_restore_resources(struct pci_dev *dev) } pr_info("Restored PCI BAR resources for %s\n", pci_name(dev)); } -EXPORT_SYMBOL_GPL(mk_pci_restore_resources); -static bool mk_pci_bridge_reaches_assigned(struct pci_bus *bus, int devfn) +DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, mk_pci_restore_resources); + +static bool mk_pci_bridge_reaches_assigned(struct pci_bus *bus, int devfn, + const struct pci_ops *ops) { struct mk_pci_device *device; u16 domain = pci_domain_nr(bus); u8 secondary_bus = 0; u8 subordinate_bus = 0; u8 hdr_type; + u32 value; - if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type) || - (hdr_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE) + if (ops->read(bus, devfn, PCI_HEADER_TYPE, sizeof(hdr_type), &value)) + return false; + hdr_type = value; + if ((hdr_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE) return false; - pci_bus_read_config_byte(bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); - pci_bus_read_config_byte(bus, devfn, PCI_SUBORDINATE_BUS, - &subordinate_bus); + if (ops->read(bus, devfn, PCI_SECONDARY_BUS, sizeof(secondary_bus), + &value)) + return false; + secondary_bus = value; + if (ops->read(bus, devfn, PCI_SUBORDINATE_BUS, + sizeof(subordinate_bus), &value)) + return false; + subordinate_bus = value; if (!secondary_bus || subordinate_bus < secondary_bus) return false; @@ -108,18 +114,20 @@ static bool mk_pci_bridge_reaches_assigned(struct pci_bus *bus, int devfn) * mk_pci_should_probe - Check whether PCI probing may access a location * @bus: PCI bus * @devfn: device/function number + * @ops: unfiltered config-space operations used to identify bridge paths * * Exact assigned functions and bridges leading to downstream assignments are * visible. Other functions are rejected before their config space is read. * * Returns: true if probing should proceed, false to skip entirely. */ -bool mk_pci_should_probe(struct pci_bus *bus, int devfn) +bool mk_pci_should_probe(struct pci_bus *bus, int devfn, + const struct pci_ops *ops) { struct mk_pci_device *device; u16 domain = pci_domain_nr(bus); - if (!root_instance || !root_instance->dtb_data) + if (!ops || !root_instance || !root_instance->dtb_data) return true; if (!root_instance->pci_devices_valid || !root_instance->pci_device_count) @@ -129,7 +137,7 @@ bool mk_pci_should_probe(struct pci_bus *bus, int devfn) list_for_each_entry(device, &root_instance->pci_devices, list) { if (device->domain == domain && device->bus > bus->number) - return mk_pci_bridge_reaches_assigned(bus, devfn); + return mk_pci_bridge_reaches_assigned(bus, devfn, ops); } return false; From 5a1a6481ca0c099d8cc1dc5a6eb99b7a598a2238 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Thu, 30 Jul 2026 08:21:49 +0300 Subject: [PATCH 05/13] x86/multikernel: build synthetic PCI roots for assignments Passing physical host bridges to a spawned kernel leaves bridge configuration shared and mutable even when endpoint probing is filtered. The primary and secondary could then program the same routing state independently. Treat ECAM descriptors as discovery metadata only. Create a synthetic root for each required segment and bus range, scan assigned endpoints through the filtered configuration operations, and never enumerate the physical bridge functions in the spawned kernel. Fail the spawned-kernel PCI initialization when root metadata is missing, overlapping, or cannot be mapped so a partial topology is never exposed. Signed-off-by: Nikolay Nikolaev --- arch/x86/multikernel/pci.c | 165 ++++++++++++++++++++++++++++++------ include/linux/multikernel.h | 14 +-- kernel/multikernel/dts.c | 7 +- kernel/multikernel/pci.c | 75 +--------------- 4 files changed, 158 insertions(+), 103 deletions(-) diff --git a/arch/x86/multikernel/pci.c b/arch/x86/multikernel/pci.c index 9121921b10a158..bb4f97fe039c7c 100644 --- a/arch/x86/multikernel/pci.c +++ b/arch/x86/multikernel/pci.c @@ -8,12 +8,16 @@ #include #include +#include #include #include +#include #include +#include #include +#ifdef CONFIG_PCI_MMCONFIG struct mk_mmcfg_snapshot { const struct mk_instance *instance; struct mk_pci_host_bridge *bridges; @@ -22,16 +26,29 @@ struct mk_mmcfg_snapshot { }; static struct pci_ops mk_pci_native_ops; +static bool mk_pci_roots_ready; -static bool mk_pci_identity_read(struct pci_bus *bus, unsigned int devfn, - int where, int size, u32 *value) +static bool mk_mmcfg_snapshot_contains(const struct mk_mmcfg_snapshot *snapshot, + u16 segment, u8 bus) +{ + size_t index; + + for (index = 0; index < snapshot->count; index++) { + if (snapshot->bridges[index].segment == segment && + snapshot->bridges[index].bus_start == bus) + return true; + } + + return false; +} + +static bool mk_pci_identity_read(u16 vendor, u16 device, int where, int size, + u32 *value) { - u16 vendor, device; u32 identity; u32 mask; - if (where < PCI_VENDOR_ID || where + size > PCI_COMMAND || - !mk_pci_get_assigned_identity(bus, devfn, &vendor, &device)) + if (where < PCI_VENDOR_ID || where + size > PCI_COMMAND) return false; identity = vendor | (u32)device << 16; @@ -43,11 +60,13 @@ static bool mk_pci_identity_read(struct pci_bus *bus, unsigned int devfn, static int mk_pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value) { - if (!mk_pci_should_probe(bus, devfn, &mk_pci_native_ops)) { + u16 vendor, device; + + if (!mk_pci_get_assigned_identity(bus, devfn, &vendor, &device)) { *value = ~0U; return PCIBIOS_DEVICE_NOT_FOUND; } - if (mk_pci_identity_read(bus, devfn, where, size, value)) + if (mk_pci_identity_read(vendor, device, where, size, value)) return PCIBIOS_SUCCESSFUL; return mk_pci_native_ops.read(bus, devfn, where, size, value); @@ -56,13 +75,12 @@ static int mk_pci_read(struct pci_bus *bus, unsigned int devfn, int where, static int mk_pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value) { - if (!mk_pci_should_probe(bus, devfn, &mk_pci_native_ops)) + if (!mk_pci_get_assigned_identity(bus, devfn, NULL, NULL)) return PCIBIOS_DEVICE_NOT_FOUND; return mk_pci_native_ops.write(bus, devfn, where, size, value); } -#ifdef CONFIG_PCI_MMCONFIG static int mk_mmcfg_snapshot_region(const struct pci_mmcfg_region *region, void *data) { @@ -74,18 +92,20 @@ static int mk_mmcfg_snapshot_region(const struct pci_mmcfg_region *region, device->bus < region->start_bus || device->bus > region->end_bus) continue; + if (mk_mmcfg_snapshot_contains(snapshot, device->domain, + device->bus)) + continue; if (snapshot->count == snapshot->capacity) return -ENOSPC; snapshot->bridges[snapshot->count].segment = region->segment; - snapshot->bridges[snapshot->count].bus_start = region->start_bus; - snapshot->bridges[snapshot->count].bus_end = region->end_bus; + snapshot->bridges[snapshot->count].bus_start = device->bus; + snapshot->bridges[snapshot->count].bus_end = device->bus; snapshot->bridges[snapshot->count].ecam_base = region->address; - pr_info("Multikernel publishing ECAM segment %04x [bus %02x-%02x] base %#llx\n", - region->segment, region->start_bus, region->end_bus, + pr_info("Multikernel publishing synthetic PCI root %04x:%02x ECAM base %#llx\n", + region->segment, device->bus, (unsigned long long)region->address); snapshot->count++; - break; } return 0; @@ -95,6 +115,7 @@ int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, struct mk_pci_host_bridge *bridges, size_t capacity) { + const struct mk_pci_device *device; struct mk_mmcfg_snapshot snapshot = { .instance = instance, .bridges = bridges, @@ -107,34 +128,65 @@ int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, return 0; ret = pci_mmcfg_walk_regions(mk_mmcfg_snapshot_region, &snapshot); - return ret ?: snapshot.count; + if (ret) + return ret; + + list_for_each_entry(device, &instance->pci_devices, list) { + if (mk_mmcfg_snapshot_contains(&snapshot, device->domain, + device->bus)) + continue; + pr_err("Multikernel has no ECAM window for assigned PCI bus %04x:%02x\n", + device->domain, device->bus); + return -ENOENT; + } + + return snapshot.count; } static int __init x86_multikernel_pci_arch_init(void) { const struct mk_pci_host_bridge *bridge; + int bridge_count = 0; if (!root_instance || !root_instance->pci_host_bridges_valid || !root_instance->pci_host_bridge_count) { pr_err("Multikernel has no restored PCI host bridge metadata\n"); - return 1; + return 0; } if (!list_empty(&pci_mmcfg_list)) { pr_err("Multikernel PCI host bridge list was not empty before restore\n"); - return 1; + return 0; + } + + list_for_each_entry(bridge, &root_instance->pci_host_bridges, list) { + if (bridge->bus_start != bridge->bus_end) { + pr_err("Multikernel PCI root %04x:[%02x-%02x] exposes shared bridge topology\n", + bridge->segment, bridge->bus_start, + bridge->bus_end); + return 0; + } + bridge_count++; + } + if (bridge_count != root_instance->pci_host_bridge_count) { + pr_err("Multikernel PCI root count mismatch: expected %d, restored %d\n", + root_instance->pci_host_bridge_count, bridge_count); + return 0; } list_for_each_entry(bridge, &root_instance->pci_host_bridges, list) { if (!pci_mmconfig_add(bridge->segment, bridge->bus_start, - bridge->bus_end, bridge->ecam_base)) - return 1; - pr_notice("Multikernel restored ECAM segment %04x [bus %02x-%02x] base %#llx\n", - bridge->segment, bridge->bus_start, bridge->bus_end, + bridge->bus_end, bridge->ecam_base)) { + pr_err("Multikernel failed to register synthetic PCI root %04x:%02x\n", + bridge->segment, bridge->bus_start); + return 0; + } + pr_notice("Multikernel restored synthetic PCI root %04x:%02x ECAM base %#llx\n", + bridge->segment, bridge->bus_start, (unsigned long long)bridge->ecam_base); } if (!pci_mmcfg_arch_init()) { pr_err("Multikernel failed to map restored PCI ECAM windows\n"); - return 1; + return 0; } raw_pci_ops = &pci_mmcfg; @@ -142,10 +194,70 @@ static int __init x86_multikernel_pci_arch_init(void) mk_pci_native_ops = pci_root_ops; pci_root_ops.read = mk_pci_read; pci_root_ops.write = mk_pci_write; + mk_pci_roots_ready = true; pr_notice("Multikernel selected ECAM for PCI config access\n"); return 0; } + +static int __init mk_pci_scan_root(const struct mk_pci_host_bridge *bridge) +{ + struct pci_sysdata *sd; + struct pci_bus *bus; + LIST_HEAD(resources); + + if (bridge->segment && !pci_domains_supported) { + pr_err("Multikernel cannot scan PCI root %04x:%02x without domain support\n", + bridge->segment, bridge->bus_start); + return -EOPNOTSUPP; + } + if (pci_find_bus(bridge->segment, bridge->bus_start)) { + pr_err("Multikernel PCI root %04x:%02x already exists\n", + bridge->segment, bridge->bus_start); + return -EEXIST; + } + + sd = kzalloc(sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + sd->domain = bridge->segment; + sd->node = x86_pci_root_bus_node(bridge->bus_start); + x86_pci_root_bus_resources(bridge->bus_start, &resources); + bus = pci_scan_root_bus(NULL, bridge->bus_start, &pci_root_ops, sd, + &resources); + if (!bus) { + pci_free_resource_list(&resources); + kfree(sd); + return -ENOMEM; + } + pci_bus_add_devices(bus); + pr_notice("Multikernel scanned synthetic PCI root %04x:%02x\n", + bridge->segment, bridge->bus_start); + return 0; +} + +static int __init x86_multikernel_pci_init(void) +{ + const struct mk_pci_host_bridge *bridge; + int ret; + + if (!root_instance) + panic("Multikernel lost restored instance metadata"); + if (!root_instance->pci_device_count) + return 0; + if (!root_instance->pci_devices_valid || !mk_pci_roots_ready) + panic("Multikernel synthetic PCI roots are unavailable"); + + list_for_each_entry(bridge, &root_instance->pci_host_bridges, list) { + ret = mk_pci_scan_root(bridge); + if (ret) + panic("Multikernel failed to scan synthetic PCI root %04x:%02x: %d", + bridge->segment, bridge->bus_start, ret); + } + + /* Suppress legacy bus 0 probing after every assigned root is present. */ + return 0; +} #else int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, struct mk_pci_host_bridge *bridges, @@ -157,7 +269,12 @@ int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, static int __init x86_multikernel_pci_arch_init(void) { pr_err("Multikernel PCI requires CONFIG_PCI_MMCONFIG\n"); - return 1; + return 0; +} + +static int __init x86_multikernel_pci_init(void) +{ + return 0; } #endif @@ -165,5 +282,5 @@ void __init x86_multikernel_pci_platform_init(void) { pci_probe = PCI_PROBE_MMCONF | PCI_PROBE_NOEARLY; x86_init.pci.arch_init = x86_multikernel_pci_arch_init; - x86_init.pci.init = pci_legacy_init; + x86_init.pci.init = x86_multikernel_pci_init; } diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 30219aeb8a268f..7c7a0ef78e80e9 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -954,17 +954,17 @@ int __init mk_instance_restore_from_manifest(void); */ /** - * mk_pci_should_probe() - Check if PCI probing should occur at a location + * mk_pci_get_assigned_identity() - Check and identify an assigned PCI function * @bus: PCI bus * @devfn: PCI device/function number + * @vendor: optional assigned vendor ID output + * @device: optional assigned device ID output * - * Called BEFORE any PCI config space reads to determine if probing - * should proceed. This prevents config space accesses to devices - * that are not in the whitelist, avoiding hardware conflicts on bare metal. + * Synthetic roots make assigned functions directly discoverable. Only exact + * assignment metadata matches may access config space; physical bridges and + * all other functions remain inaccessible. * - * Returns: true if probing should proceed, false to skip entirely - * - * Declared above with the CONFIG_MULTIKERNEL stubs. + * Returns: true for an exact assignment metadata match, false otherwise */ /** diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index 352b91aa9e5995..d212894a6a3f18 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -135,7 +135,7 @@ void mk_pci_host_bridges_free(struct list_head *bridges, int *count, } int mk_pci_host_bridges_clone(struct list_head *dst, int *dst_count, - bool *dst_valid, const struct list_head *src, + bool *dst_valid, const struct list_head *src, int src_count, bool src_valid) { const struct mk_pci_host_bridge *src_bridge; @@ -1112,6 +1112,11 @@ static int mk_dt_emit_pci_host_bridges(void *fdt, const struct mk_pci_host_bridge *bridge; int discovered_count, index, ret; + if (!instance->pci_devices_valid) + return -EINVAL; + if (!instance->pci_device_count) + return 0; + discovered_count = mk_arch_snapshot_pci_host_bridges(instance, discovered, ARRAY_SIZE(discovered)); diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index 369a94ca145664..61c5f9cbe15e10 100644 --- a/kernel/multikernel/pci.c +++ b/kernel/multikernel/pci.c @@ -49,8 +49,10 @@ bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, if (!device) return false; - *vendor = device->vendor; - *device_id = device->device; + if (vendor) + *vendor = device->vendor; + if (device_id) + *device_id = device->device; return true; } @@ -73,72 +75,3 @@ static void mk_pci_restore_resources(struct pci_dev *dev) } DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, mk_pci_restore_resources); - -static bool mk_pci_bridge_reaches_assigned(struct pci_bus *bus, int devfn, - const struct pci_ops *ops) -{ - struct mk_pci_device *device; - u16 domain = pci_domain_nr(bus); - u8 secondary_bus = 0; - u8 subordinate_bus = 0; - u8 hdr_type; - u32 value; - - if (ops->read(bus, devfn, PCI_HEADER_TYPE, sizeof(hdr_type), &value)) - return false; - hdr_type = value; - if ((hdr_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE) - return false; - - if (ops->read(bus, devfn, PCI_SECONDARY_BUS, sizeof(secondary_bus), - &value)) - return false; - secondary_bus = value; - if (ops->read(bus, devfn, PCI_SUBORDINATE_BUS, - sizeof(subordinate_bus), &value)) - return false; - subordinate_bus = value; - if (!secondary_bus || subordinate_bus < secondary_bus) - return false; - - list_for_each_entry(device, &root_instance->pci_devices, list) { - if (device->domain == domain && device->bus >= secondary_bus && - device->bus <= subordinate_bus) - return true; - } - - return false; -} - -/** - * mk_pci_should_probe - Check whether PCI probing may access a location - * @bus: PCI bus - * @devfn: device/function number - * @ops: unfiltered config-space operations used to identify bridge paths - * - * Exact assigned functions and bridges leading to downstream assignments are - * visible. Other functions are rejected before their config space is read. - * - * Returns: true if probing should proceed, false to skip entirely. - */ -bool mk_pci_should_probe(struct pci_bus *bus, int devfn, - const struct pci_ops *ops) -{ - struct mk_pci_device *device; - u16 domain = pci_domain_nr(bus); - - if (!ops || !root_instance || !root_instance->dtb_data) - return true; - if (!root_instance->pci_devices_valid || - !root_instance->pci_device_count) - return false; - if (mk_pci_find_assigned(bus, devfn)) - return true; - - list_for_each_entry(device, &root_instance->pci_devices, list) { - if (device->domain == domain && device->bus > bus->number) - return mk_pci_bridge_reaches_assigned(bus, devfn, ops); - } - - return false; -} From f0dbebb3739a4eb71f622ab09b052dc480e779b3 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Wed, 5 Aug 2026 10:36:26 +0300 Subject: [PATCH 06/13] multikernel: harden IPI publication and host routing Use per-slot ring states so an interrupted producer cannot block other ready messages. Publish emergency shutdown through a dedicated flag, reset stale slots before respawn, preserve the physical host-control endpoint in the manifest, and re-kick a full destination ring so published entries cannot remain stranded. Signed-off-by: Nikolay Nikolaev --- include/linux/multikernel.h | 32 ++++- kernel/kexec_core.c | 4 +- kernel/multikernel/core.c | 3 +- kernel/multikernel/hotplug.c | 3 +- kernel/multikernel/instance_dt.c | 29 +++-- kernel/multikernel/ipi.c | 205 +++++++++++++------------------ kernel/multikernel/messaging.c | 3 +- net/vmw_vsock/mk_transport.c | 3 +- 8 files changed, 147 insertions(+), 135 deletions(-) diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 7c7a0ef78e80e9..c3967605140d5b 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -77,8 +77,14 @@ static inline mk_phys_cpu_t mk_cpu_set_first(const struct mk_cpu_set *set) /* IPI ring buffer size - must be power of 2 for efficient modulo */ #define MK_IPI_RING_SIZE 64 +#define MK_IPI_SLOT_EMPTY 0 +#define MK_IPI_SLOT_WRITING 1 +#define MK_IPI_SLOT_READY 2 +#define MK_IPI_SLOT_CONSUMING 3 + /* Data structure for passing parameters via IPI */ struct mk_ipi_data { + atomic_t state; u64 sender_cpu; /* Physical ID of the CPU that sent this IPI */ unsigned int type; /* User-defined type identifier */ size_t data_size; /* Size of the data */ @@ -87,16 +93,35 @@ struct mk_ipi_data { /* IPI ring buffer for queuing messages */ struct mk_ipi_ring { - atomic_t head; /* Producer index */ - atomic_t tail; /* Consumer index */ + atomic_t head; /* Producer allocation cursor */ + atomic_t tail; /* Consumer scan cursor */ struct mk_ipi_data entries[MK_IPI_RING_SIZE]; /* Ring buffer entries */ }; /* Shared memory structures - per-instance design */ struct mk_shared_data { + atomic_t emergency_shutdown; struct mk_ipi_ring ring; /* IPI message ring buffer */ }; +static inline void mk_ipi_ring_reset(struct mk_ipi_ring *ring) +{ + unsigned int i; + + atomic_set(&ring->head, 0); + atomic_set(&ring->tail, 0); + for (i = 0; i < MK_IPI_RING_SIZE; i++) { + WRITE_ONCE(ring->entries[i].data_size, 0); + atomic_set(&ring->entries[i].state, MK_IPI_SLOT_EMPTY); + } +} + +static inline void mk_shared_data_reset(struct mk_shared_data *shared) +{ + atomic_set(&shared->emergency_shutdown, 0); + mk_ipi_ring_reset(&shared->ring); +} + /* Function pointer type for IPI callbacks */ typedef void (*mk_ipi_callback_t)(struct mk_ipi_data *data, void *ctx); @@ -274,7 +299,8 @@ struct mk_shutdown_payload { * Message handler callback type */ typedef void (*mk_msg_handler_t)(u32 msg_type, u32 subtype, - void *payload, u32 payload_len, void *ctx); + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx); /* Opaque type for pending message tracking */ struct mk_pending_msg; diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index 1ee2cffa059576..b3b6213ff4ef6e 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -1782,8 +1782,10 @@ int multikernel_kexec_by_id(int mk_id) * filling this one". Anything left in there was addressed to a * kernel that is gone. */ - if (instance->ipi_data) + if (instance->ipi_data) { memset(instance->ipi_data, 0, sizeof(*instance->ipi_data)); + mk_shared_data_reset(instance->ipi_data); + } /* * Same for the other direction: whatever the halted instance left diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index a36c7cd9bb19e1..48d35b698881ce 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -1287,7 +1287,8 @@ static void mk_halted_work_fn(struct work_struct *work) } static void mk_system_msg_handler(u32 msg_type, u32 subtype, - void *payload, u32 payload_len, void *ctx) + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx) { if (msg_type != MK_MSG_SYSTEM) return; diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index 8ad4a9411ecbc3..25e2057c56a98a 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -891,7 +891,8 @@ static int mk_handle_device_remove(struct mk_device_resource_payload *payload, u * message subtype. */ static void mk_resource_msg_handler(u32 msg_type, u32 subtype, - void *payload, u32 payload_len, void *ctx) + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx) { int ret = 0; diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index 0d68d4c383e636..81da05bfe2b48d 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -168,6 +168,7 @@ int mk_manifest_add_instance_dtb(struct kimage *image, void *fdt, int mk_id) */ int mk_manifest_add_host_ipi(struct kimage *image, void *fdt) { + mk_phys_cpu_t target_cpu = arch_cpu_physical_id(0); int ret = 0; if (!root_instance->ipi_data) { @@ -175,12 +176,15 @@ int mk_manifest_add_host_ipi(struct kimage *image, void *fdt) return 0; } - pr_info("Preserving host IPI buffer: phys=0x%llx, pages=%u\n", - (unsigned long long)root_instance->ipi_phys, root_instance->ipi_pages); + pr_info("Preserving host IPI buffer: phys=0x%llx, pages=%u, target CPU=%llu\n", + (unsigned long long)root_instance->ipi_phys, + root_instance->ipi_pages, + (unsigned long long)target_cpu); ret |= fdt_begin_node(fdt, "host-ipi-buffer"); ret |= fdt_property_u64(fdt, "phys-addr", root_instance->ipi_phys); ret |= fdt_property_u32(fdt, "pages", root_instance->ipi_pages); + ret |= fdt_property_u64(fdt, "target-cpu", target_cpu); ret |= fdt_end_node(fdt); if (ret) { @@ -386,6 +390,7 @@ static struct mk_instance * __init alloc_mk_instance(int instance_id, const char pr_err("Failed to allocate IPI buffer for instance %d\n", instance_id); goto err_free_name; } + mk_shared_data_reset(instance->ipi_data); instance->ipi_phys = virt_to_phys(instance->ipi_data); instance->ipi_pages = (sizeof(struct mk_shared_data) + PAGE_SIZE - 1) / PAGE_SIZE; @@ -572,8 +577,10 @@ static struct mk_instance * __init mk_restore_host_instance(const void *manifest struct mk_instance *host_instance; int host_ipi_node; const fdt64_t *phys_prop; + const fdt64_t *cpu_prop; const fdt32_t *pages_prop; phys_addr_t host_ipi_phys = 0; + mk_phys_cpu_t host_ipi_cpu = MK_PHYS_CPU_INVALID; u32 host_ipi_pages = 0; size_t host_ipi_size = 0; int len; @@ -593,10 +600,15 @@ static struct mk_instance * __init mk_restore_host_instance(const void *manifest host_ipi_pages = fdt32_to_cpu(*pages_prop); host_ipi_size = (size_t)host_ipi_pages << PAGE_SHIFT; } + cpu_prop = fdt_getprop(manifest, host_ipi_node, "target-cpu", &len); + if (cpu_prop && len == sizeof(*cpu_prop)) + host_ipi_cpu = fdt64_to_cpu(*cpu_prop); - if (!host_ipi_phys || !host_ipi_pages) { - pr_warn("Incomplete host IPI buffer info (phys=0x%llx, pages=%u)\n", - (unsigned long long)host_ipi_phys, host_ipi_pages); + if (!host_ipi_phys || !host_ipi_pages || + host_ipi_cpu == MK_PHYS_CPU_INVALID) { + pr_warn("Incomplete host IPI buffer info (phys=0x%llx, pages=%u, target CPU=%llu)\n", + (unsigned long long)host_ipi_phys, host_ipi_pages, + (unsigned long long)host_ipi_cpu); return NULL; } @@ -604,8 +616,7 @@ static struct mk_instance * __init mk_restore_host_instance(const void *manifest 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_ipi_cpu)) { kfree(host_instance->name); mk_cpu_set_free(host_instance->cpus); kfree(host_instance); @@ -623,9 +634,9 @@ static struct mk_instance * __init mk_restore_host_instance(const void *manifest } host_instance->ipi_phys = host_ipi_phys; host_instance->ipi_pages = host_ipi_pages; - pr_info("Restored host IPI buffer: phys=0x%llx, virt=%px, pages=%u\n", + pr_info("Restored host IPI buffer: phys=0x%llx, virt=%p, pages=%u, target CPU=%llu\n", (unsigned long long)host_ipi_phys, host_instance->ipi_data, - host_ipi_pages); + host_ipi_pages, (unsigned long long)host_ipi_cpu); pr_info("Registered host instance (ID 0) for spawn→host communication\n"); return host_instance; diff --git a/kernel/multikernel/ipi.c b/kernel/multikernel/ipi.c index 7f4ba78b39b06a..0325ab3e3bf933 100644 --- a/kernel/multikernel/ipi.c +++ b/kernel/multikernel/ipi.c @@ -21,40 +21,70 @@ static raw_spinlock_t mk_handlers_lock = __RAW_SPIN_LOCK_UNLOCKED(mk_handlers_lo static void mk_ipi_drain_ring(void); /* - * Ring indices live in memory another kernel instance can write, so every - * read is masked before it indexes the entry array. An instance that dies - * mid-update must not be able to walk this kernel off the end of its ring. + * head is an allocation cursor, not the consumer-visible publication point. + * A producer killed while WRITING can strand only its claimed slot; READY + * slots elsewhere remain visible to the consumer and to the NMI shutdown scan. */ -static inline unsigned int mk_ring_idx(unsigned int i) +static int mk_ipi_ring_claim_slot(struct mk_ipi_ring *ring, + struct mk_ipi_data **slot_out) { - return i & (MK_IPI_RING_SIZE - 1); + struct mk_ipi_data *slot; + unsigned int scanned = 0; + unsigned int idx; + int head; + int next; + + while (scanned < MK_IPI_RING_SIZE) { + head = atomic_read(&ring->head); + idx = head & (MK_IPI_RING_SIZE - 1); + next = (idx + 1) & (MK_IPI_RING_SIZE - 1); + + if (atomic_cmpxchg(&ring->head, head, next) != head) { + cpu_relax(); + continue; + } + + slot = &ring->entries[idx]; + if (atomic_cmpxchg(&slot->state, MK_IPI_SLOT_EMPTY, + MK_IPI_SLOT_WRITING) == MK_IPI_SLOT_EMPTY) { + *slot_out = slot; + return 0; + } + + scanned++; + } + + return -ENOSPC; +} + +static void mk_ipi_slot_release(struct mk_ipi_data *slot) +{ + WRITE_ONCE(slot->data_size, 0); + atomic_set_release(&slot->state, MK_IPI_SLOT_EMPTY); } /** * mk_ipi_ring_drop_pending - Discard everything queued in this kernel's ring * - * Called when an instance is re-spawned. A halting instance parks its CPUs - * wherever they were, including between claiming a ring slot and publishing - * it, and the drain stops at such a slot forever. Anything still queued was - * sent by a kernel that is gone, so drop it all rather than let one - * abandoned slot wedge the ring. + * Called after the previous instance has halted and immediately before this + * kernel is re-spawned, when no producers can still access the ring. */ void mk_ipi_ring_drop_pending(void) { struct mk_ipi_ring *ring; - unsigned int head, tail; + unsigned int i; if (!root_instance || !root_instance->ipi_data) return; ring = &root_instance->ipi_data->ring; - head = mk_ring_idx(atomic_read(&ring->head)); - - for (tail = mk_ring_idx(atomic_read(&ring->tail)); tail != head; - tail = mk_ring_idx(tail + 1)) - ring->entries[tail].data_size = 0; + for (i = 0; i < MK_IPI_RING_SIZE; i++) { + WRITE_ONCE(ring->entries[i].data_size, 0); + atomic_set(&ring->entries[i].state, MK_IPI_SLOT_EMPTY); + } - atomic_set(&ring->tail, head); + atomic_set(&ring->head, 0); + atomic_set(&ring->tail, 0); } /** @@ -133,8 +163,9 @@ int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, uns { struct mk_ipi_data *slot; struct mk_instance *instance = mk_instance_find(instance_id); - unsigned int head, next_head, tail; + struct mk_ipi_ring *ring; mk_phys_cpu_t target; + int ret; if (!instance) return -EINVAL; @@ -169,46 +200,31 @@ int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, uns return -ENODEV; } - /* Try to enqueue the message in the ring buffer */ - do { - head = mk_ring_idx(atomic_read(&instance->ipi_data->ring.head)); - next_head = mk_ring_idx(head + 1); - tail = mk_ring_idx(atomic_read(&instance->ipi_data->ring.tail)); - - /* Check if ring buffer is full */ - if (next_head == tail) { - /* - * Console output reaches this path, so a plain printk - * here re-enters the console write that called us and - * deadlocks on its lock with interrupts already off. - */ - printk_deferred(KERN_WARNING - "multikernel: IPI ring full for instance %d (head=%u, tail=%u)\n", - instance_id, head, tail); - mk_instance_put(instance); - return -ENOSPC; - } - - /* Try to claim this slot atomically */ - } while (atomic_cmpxchg(&instance->ipi_data->ring.head, head, next_head) != head); - - /* We've claimed slot 'head', now fill it */ - slot = &instance->ipi_data->ring.entries[head]; + ring = &instance->ipi_data->ring; + ret = mk_ipi_ring_claim_slot(ring, &slot); + if (ret) { + /* + * A doorbell can be coalesced while the target is draining this + * ring. Kick it again before reporting backpressure so READY + * entries cannot remain stranded without another notification. + */ + mk_arch_send_ipi(target); + printk_deferred(KERN_WARNING + "multikernel: IPI ring full for instance %d\n", + instance_id); + mk_instance_put(instance); + return ret; + } + WRITE_ONCE(slot->data_size, 0); slot->sender_cpu = arch_cpu_physical_id(smp_processor_id()); slot->type = type; if (data && data_size > 0) memcpy(slot->buffer, data, data_size); - /* - * data_size publishes the slot: the reader treats a zero as "the - * producer has claimed this slot but has not filled it yet" and - * waits. Claiming the slot advanced head, so a reader can already - * be looking at it; everything above must be visible first. - */ - smp_store_release(&slot->data_size, data_size); - + WRITE_ONCE(slot->data_size, data_size); + atomic_set_release(&slot->state, MK_IPI_SLOT_READY); mk_arch_send_ipi(target); mk_instance_put(instance); @@ -219,44 +235,31 @@ static void mk_ipi_drain_ring(void) { struct mk_ipi_data *slot; struct mk_ipi_handler *handler; - unsigned int head, tail, next_tail; + struct mk_ipi_ring *ring; + unsigned int tail, idx, scanned; size_t data_size; int messages_processed = 0; if (!root_instance || !root_instance->ipi_data) return; - while (1) { - tail = mk_ring_idx(atomic_read(&root_instance->ipi_data->ring.tail)); - head = mk_ring_idx(atomic_read(&root_instance->ipi_data->ring.head)); - - if (tail == head) - break; + ring = &root_instance->ipi_data->ring; + tail = atomic_read(&ring->tail); - slot = &root_instance->ipi_data->ring.entries[tail]; + for (scanned = 0; scanned < MK_IPI_RING_SIZE; scanned++) { + idx = (tail + scanned) & (MK_IPI_RING_SIZE - 1); + slot = &ring->entries[idx]; - /* - * Pairs with the store_release in multikernel_send_ipi_data(). - * Zero means the sender claimed this slot but has not - * finished writing it. Leave it alone: skipping it would - * drop the message it is about to publish. Its own IPI, or - * the next one, brings us back here. - * - * A sender stopped before publishing leaves its slot zero - * forever; mk_ipi_ring_drop_pending() clears those out when - * the instance is re-spawned. - */ - data_size = smp_load_acquire(&slot->data_size); - if (data_size == 0) - break; + if (atomic_cmpxchg_acquire(&slot->state, MK_IPI_SLOT_READY, + MK_IPI_SLOT_CONSUMING) != + MK_IPI_SLOT_READY) + continue; + data_size = READ_ONCE(slot->data_size); if (data_size > MK_MAX_DATA_SIZE) { pr_warn_once("Multikernel IPI slot %u has bad size %zu\n", - tail, data_size); - slot->data_size = 0; - next_tail = mk_ring_idx(tail + 1); - atomic_set(&root_instance->ipi_data->ring.tail, next_tail); - continue; + idx, data_size); + goto advance_tail; } /* Dispatch to registered handler */ @@ -274,10 +277,8 @@ static void mk_ipi_drain_ring(void) raw_spin_unlock(&mk_handlers_lock); advance_tail: - /* Mark consumed so the slot reads as unpublished again */ - slot->data_size = 0; - next_tail = mk_ring_idx(tail + 1); - atomic_set(&root_instance->ipi_data->ring.tail, next_tail); + mk_ipi_slot_release(slot); + atomic_set(&ring->tail, (idx + 1) & (MK_IPI_RING_SIZE - 1)); messages_processed++; if (messages_processed >= MK_IPI_RING_SIZE) @@ -320,8 +321,9 @@ void generic_multikernel_interrupt(void) /** * mk_has_pending_shutdown - Check if there's a pending shutdown message * - * Peeks at the IPI ring buffer to check for a MK_SYS_SHUTDOWN message - * with MK_SHUTDOWN_IMMEDIATE flag. Used by NMI handler for force halt. + * Checks the dedicated emergency flag published before force-halt NMIs. + * This path must not depend on claiming a normal IPI ring slot because a + * crashed producer can leave slots unavailable. * * Safe to call from NMI context (no locks, read-only peek). * @@ -329,41 +331,8 @@ void generic_multikernel_interrupt(void) */ bool mk_has_pending_shutdown(void) { - struct mk_ipi_data *slot; - struct mk_message *msg; - struct mk_shutdown_payload *payload; - unsigned int head, tail, idx, scanned; - if (!root_instance || !root_instance->ipi_data) return false; - tail = mk_ring_idx(atomic_read(&root_instance->ipi_data->ring.tail)); - head = mk_ring_idx(atomic_read(&root_instance->ipi_data->ring.head)); - - /* - * Scan pending messages without consuming them. The trip count is - * bounded by the ring size rather than by the indices alone: this - * runs in NMI context, where a never-terminating loop takes the CPU - * out permanently with NMIs latched. - */ - for (scanned = 0, idx = tail; - idx != head && scanned < MK_IPI_RING_SIZE; - idx = mk_ring_idx(idx + 1), scanned++) { - slot = &root_instance->ipi_data->ring.entries[idx]; - - if (slot->data_size < sizeof(struct mk_message)) - continue; - - msg = (struct mk_message *)slot->buffer; - if (msg->msg_type != MK_MSG_SYSTEM || msg->msg_subtype != MK_SYS_SHUTDOWN) - continue; - - if (msg->payload_len >= sizeof(struct mk_shutdown_payload)) { - payload = (struct mk_shutdown_payload *)msg->payload; - if (payload->flags & MK_SHUTDOWN_IMMEDIATE) - return true; - } - } - - return false; + return atomic_read_acquire(&root_instance->ipi_data->emergency_shutdown); } diff --git a/kernel/multikernel/messaging.c b/kernel/multikernel/messaging.c index 2cd2b7682f8d3e..e2674950b33fc0 100644 --- a/kernel/multikernel/messaging.c +++ b/kernel/multikernel/messaging.c @@ -86,7 +86,8 @@ static void mk_message_type_ipi_callback(struct mk_ipi_data *data, void *ctx) msg_type, msg_subtype, payload_len, data->sender_cpu); /* Call the registered handler for this message type */ - type_handler->msg_handler(msg_type, msg_subtype, payload, payload_len, type_handler->context); + type_handler->msg_handler(msg_type, msg_subtype, payload, payload_len, + data->sender_cpu, type_handler->context); } /* diff --git a/net/vmw_vsock/mk_transport.c b/net/vmw_vsock/mk_transport.c index f75dbf3f2c8209..e7594eaee1393f 100644 --- a/net/vmw_vsock/mk_transport.c +++ b/net/vmw_vsock/mk_transport.c @@ -279,7 +279,8 @@ static void mk_vsock_rx_work(struct work_struct *work) } static void mk_vsock_ipi_handler(u32 msg_type, u32 subtype, - void *payload, u32 payload_len, void *ctx) + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx) { struct sk_buff *skb; struct virtio_vsock_hdr *hdr; From 1b7e5e2d1205528f6efc240e70f5647189fc2947 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Thu, 30 Jul 2026 08:17:44 +0300 Subject: [PATCH 07/13] pci/multikernel: add isolated SR-IOV VF leases Introduce fail-closed runtime leases for live SR-IOV VFs as one complete ownership transition. Reserve instance resources atomically, require an isolated IOMMU group and host-owned paging domain before publishing the lease, preserve host driver state, and unwind partial setup without exposing the VF. Quiesce interrupt delivery and reset assigned VFs across halt, respawn, removal, and teardown so a lease is never reusable with stale DMA or device state. Intel VT-d faults remain observable through the host IOMMU path but are not claimed as portable automatic instance-failure notifications. Signed-off-by: Nikolay Nikolaev --- include/linux/multikernel.h | 18 +- kernel/kexec_core.c | 12 + kernel/multikernel/Kconfig | 3 + kernel/multikernel/baseline.c | 60 +- kernel/multikernel/core.c | 735 +++++++++--------- kernel/multikernel/dts.c | 29 +- kernel/multikernel/hotplug.c | 156 ++-- kernel/multikernel/instance_dt.c | 1 + kernel/multikernel/internal.h | 33 + kernel/multikernel/ipi.c | 30 +- kernel/multikernel/kernfs.c | 23 +- kernel/multikernel/mem.c | 44 +- kernel/multikernel/messaging.c | 28 +- kernel/multikernel/pci.c | 1244 +++++++++++++++++++++++++++++- 14 files changed, 1894 insertions(+), 522 deletions(-) diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index c3967605140d5b..cc1e419d40f4fc 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -534,6 +535,8 @@ struct mk_instance { int id; /* Kernel-assigned instance ID */ char *name; /* User-provided instance name */ enum mk_instance_state state; /* Current state */ + /* Serializes memory topology changes with PCI assignment leases. */ + struct mutex resource_mutex; /* Resource management - list of reserved memory regions */ struct list_head memory_regions; /* List of struct mk_memory_region */ @@ -549,6 +552,8 @@ struct mk_instance { struct list_head pci_devices; /* List of struct mk_pci_device */ int pci_device_count; /* Number of PCI devices */ bool pci_devices_valid; /* Whether PCI device list is valid */ + /* Host-only live PCI assignment leases (private elements). */ + struct list_head pci_assignments; /* PCI host bridge metadata (descriptive, never transferred) */ struct list_head pci_host_bridges; @@ -742,14 +747,14 @@ struct mk_instance *mk_instance_get(struct mk_instance *instance); void __noreturn mk_halt_to_pool(void); /** - * mk_instance_reserve_resources() - Reserve CPU and memory resources for instance - * @instance: Instance to reserve resources for - * @config: Device tree configuration with memory size and CPU assignment + * mk_instance_reserve_resources() - Atomically reserve instance resources + * @instance: Empty instance to populate + * @config: Parsed memory, CPU, PCI, and platform resource configuration * - * Allocates the specified memory size from the multikernel pool, creates - * memory regions, and copies CPU assignment. + * Reserves every configured resource class or returns all resources acquired + * by the attempt. A failure never leaves a partially populated instance. * - * Returns 0 on success, negative error code on failure. + * Returns: 0 on success, negative error code on failure */ int mk_instance_reserve_resources(struct mk_instance *instance, const struct mk_dt_config *config); @@ -840,6 +845,7 @@ void mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len); /* Build the manifest for a spawn (host, kexec path) */ int mk_manifest_finalize(struct kimage *image); +int mk_pci_prepare_instance_start(struct mk_instance *instance); #else static inline bool multikernel_allow_emergency_restart(void) { diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index b3b6213ff4ef6e..e350d391f75a00 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -1733,6 +1733,18 @@ int multikernel_kexec_by_id(int mk_id) goto unlock; } + /* + * Stop and reset every leased VF before rewriting instance memory. A + * force-halted kernel may have left bus mastering enabled and DMA in + * flight into the image that is about to be reused. + */ + rc = mk_pci_prepare_instance_start(instance); + if (rc) { + pr_err("Failed to prepare PCI assignments for instance %d restart: %d\n", + mk_id, rc); + goto unlock; + } + /* * Booting consumes the image: the spawn kernel writes its .data and * patches its own text, so the copy in instance memory is spent once diff --git a/kernel/multikernel/Kconfig b/kernel/multikernel/Kconfig index d704be265ae70c..4f4669d041f41e 100644 --- a/kernel/multikernel/Kconfig +++ b/kernel/multikernel/Kconfig @@ -24,4 +24,7 @@ config MULTIKERNEL - A manifest handed to each spawn kernel on multikernel's own boot channel, describing its resources and message rings + SR-IOV VF assignment additionally requires PCI_IOV and an active + hardware IOMMU with isolated interrupt delivery. Assignment fails + closed when those isolation facilities are unavailable. If unsure, say N. diff --git a/kernel/multikernel/baseline.c b/kernel/multikernel/baseline.c index 671a1057fbf8d2..02c6f1224f45a5 100644 --- a/kernel/multikernel/baseline.c +++ b/kernel/multikernel/baseline.c @@ -505,54 +505,54 @@ static int mk_baseline_initialize_devices(const struct mk_instance *instance) { struct mk_pci_device *pci_dev; struct pci_dev *dev; - int failed = 0, unbound = 0; + int failed = 0; + int available = 0; - if (instance->pci_device_count == 0) { - pr_debug("No PCI devices in baseline to unbind\n"); + if (!instance->pci_device_count) { + pr_debug("No PCI devices in the multikernel pool\n"); return 0; } - pr_info("Unbinding %d PCI devices for multikernel pool\n", + pr_info("Validating %d PCI devices for the multikernel pool\n", instance->pci_device_count); + pci_lock_rescan_remove(); list_for_each_entry(pci_dev, &instance->pci_devices, list) { - dev = pci_get_domain_bus_and_slot(pci_dev->domain, pci_dev->bus, - PCI_DEVFN(pci_dev->slot, pci_dev->func)); + dev = pci_get_domain_bus_and_slot(pci_dev->domain, + pci_dev->bus, + PCI_DEVFN(pci_dev->slot, + pci_dev->func)); if (!dev) { pr_warn("PCI device %04x:%04x@%04x:%02x:%02x.%x not found in system\n", - pci_dev->vendor, pci_dev->device, pci_dev->domain, - pci_dev->bus, pci_dev->slot, pci_dev->func); + pci_dev->vendor, pci_dev->device, + pci_dev->domain, pci_dev->bus, + pci_dev->slot, pci_dev->func); failed++; continue; } - if (!dev->driver) { - pr_debug("PCI device %04x:%04x@%04x:%02x:%02x.%x already unbound\n", - pci_dev->vendor, pci_dev->device, pci_dev->domain, - pci_dev->bus, pci_dev->slot, pci_dev->func); - pci_dev_put(dev); - unbound++; - continue; + if (dev->vendor != pci_dev->vendor || + dev->device != pci_dev->device || + pci_dev_is_disconnected(dev) || + !pci_device_is_present(dev)) { + pr_warn("PCI device %04x:%04x@%04x:%02x:%02x.%x is not available\n", + pci_dev->vendor, pci_dev->device, + pci_dev->domain, pci_dev->bus, + pci_dev->slot, pci_dev->func); + failed++; + } else { + available++; } - - const char *driver_name = dev->driver->name; - - device_release_driver(&dev->dev); - - pr_info("Unbound PCI device %04x:%04x@%04x:%02x:%02x.%x (was: %s) for multikernel pool\n", - pci_dev->vendor, pci_dev->device, pci_dev->domain, - pci_dev->bus, pci_dev->slot, pci_dev->func, - driver_name); - pci_dev_put(dev); - unbound++; } + pci_unlock_rescan_remove(); - if (failed > 0) { - pr_warn("Failed to find %d PCI devices in system\n", failed); - } + if (failed) + pr_warn("%d PCI devices in the multikernel pool are unavailable\n", + failed); - pr_info("Successfully unbound %d PCI devices for multikernel pool\n", unbound); + pr_info("Validated %d PCI devices; host drivers remain bound until assignment\n", + available); return 0; } diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index 48d35b698881ce..befcdfb56e31f3 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -15,6 +15,39 @@ #include #include "internal.h" +/* + * CPU moves take transaction first and ownership only while inspecting or + * publishing the paired CPU sets. PCI request work takes ownership alone, + * so spawn RPCs can complete while a remote hotplug ACK is pending. + */ +static DEFINE_MUTEX(mk_cpu_transaction_mutex); +static DEFINE_MUTEX(mk_cpu_ownership_mutex); + +void mk_cpu_transaction_lock(void) +{ + mutex_lock(&mk_cpu_transaction_mutex); +} + +void mk_cpu_transaction_unlock(void) +{ + mutex_unlock(&mk_cpu_transaction_mutex); +} + +void mk_cpu_ownership_lock(void) +{ + mutex_lock(&mk_cpu_ownership_mutex); +} + +void mk_cpu_ownership_unlock(void) +{ + mutex_unlock(&mk_cpu_ownership_mutex); +} + +void mk_cpu_ownership_assert_held(void) +{ + lockdep_assert_held(&mk_cpu_ownership_mutex); +} + static void mk_instance_return_all_cpus(struct mk_instance *instance) { if (!instance || mk_cpu_set_empty(instance->cpus)) @@ -26,16 +59,20 @@ static void mk_instance_return_all_cpus(struct mk_instance *instance) mk_instance_return_cpus(instance, instance->cpus); } -static void mk_instance_return_pci_devices(struct mk_instance *instance) +static int mk_instance_return_pci_devices(struct mk_instance *instance) { struct mk_pci_device *pci_dev, *pci_tmp; int returned_count = 0; + int ret; - if (!instance || !instance->pci_devices_valid) - return; + if (!instance || instance == root_instance || instance->id == 0) + return 0; - if (instance == root_instance || instance->id == 0) - return; + ret = mk_pci_release_assignments(instance); + if (ret) + return ret; + if (!instance->pci_devices_valid) + return 0; if (!root_instance) { pr_warn("Cannot return PCI devices from instance %d (%s): no root instance\n", @@ -43,52 +80,37 @@ static void mk_instance_return_pci_devices(struct mk_instance *instance) goto cleanup; } - list_for_each_entry_safe(pci_dev, pci_tmp, &instance->pci_devices, list) { - struct mk_pci_device *root_dev; - - root_dev = kzalloc(sizeof(*root_dev), GFP_KERNEL); - if (!root_dev) { - pr_warn("Failed to allocate PCI device entry for root instance\n"); - continue; - } - - *root_dev = *pci_dev; - INIT_LIST_HEAD(&root_dev->list); - - list_add_tail(&root_dev->list, &root_instance->pci_devices); + list_for_each_entry_safe(pci_dev, pci_tmp, &instance->pci_devices, + list) { + list_move_tail(&pci_dev->list, &root_instance->pci_devices); root_instance->pci_device_count++; root_instance->pci_devices_valid = true; - - pr_debug("Returned PCI device %04x:%02x:%02x.%d from instance %d to root\n", - root_dev->domain, root_dev->bus, root_dev->slot, - root_dev->func, instance->id); - returned_count++; } - if (returned_count > 0) { + if (returned_count) pr_info("Returned %d PCI devices from instance %d (%s) to root instance\n", returned_count, instance->id, instance->name); - } cleanup: - list_for_each_entry_safe(pci_dev, pci_tmp, &instance->pci_devices, list) { + list_for_each_entry_safe(pci_dev, pci_tmp, &instance->pci_devices, + list) { list_del(&pci_dev->list); kfree(pci_dev); } instance->pci_device_count = 0; instance->pci_devices_valid = false; + return 0; } - static void mk_instance_return_platform_devices(struct mk_instance *instance) { - struct mk_platform_device *plat_dev, *plat_tmp; - int returned_count = 0; + struct mk_platform_device *device, *tmp; + int returned = 0; - if (!instance || !instance->platform_devices_valid) + if (!instance || instance == root_instance || instance->id == 0) return; - - if (instance == root_instance || instance->id == 0) + if (!instance->platform_devices_valid && + list_empty(&instance->platform_devices)) return; if (!root_instance) { @@ -97,63 +119,63 @@ static void mk_instance_return_platform_devices(struct mk_instance *instance) goto cleanup; } - list_for_each_entry_safe(plat_dev, plat_tmp, &instance->platform_devices, list) { - struct mk_platform_device *root_dev; - - root_dev = kzalloc(sizeof(*root_dev), GFP_KERNEL); - if (!root_dev) { - pr_warn("Failed to allocate platform device entry for root instance\n"); - continue; - } - - *root_dev = *plat_dev; - INIT_LIST_HEAD(&root_dev->list); - - list_add_tail(&root_dev->list, &root_instance->platform_devices); + list_for_each_entry_safe(device, tmp, &instance->platform_devices, + list) { + list_move_tail(&device->list, + &root_instance->platform_devices); root_instance->platform_device_count++; root_instance->platform_devices_valid = true; - - pr_debug("Returned platform device '%s' from instance %d to root\n", - root_dev->name, instance->id); - - returned_count++; + returned++; } - if (returned_count > 0) { + if (returned) pr_info("Returned %d platform devices from instance %d (%s) to root instance\n", - returned_count, instance->id, instance->name); - } + returned, instance->id, instance->name); cleanup: - list_for_each_entry_safe(plat_dev, plat_tmp, &instance->platform_devices, list) { - list_del(&plat_dev->list); - kfree(plat_dev); + list_for_each_entry_safe(device, tmp, &instance->platform_devices, + list) { + list_del(&device->list); + kfree(device); } instance->platform_device_count = 0; instance->platform_devices_valid = false; } -static void mk_instance_release(struct kref *kref) +int mk_instance_release_resources(struct mk_instance *instance) { - struct mk_instance *instance = container_of(kref, struct mk_instance, refcount); + int ret; - pr_info("Releasing multikernel instance %d (%s), returning resources to root\n", - instance->id, instance->name); + if (!instance || instance == root_instance || instance->id == 0) + return 0; - mk_instance_return_all_cpus(instance); - mk_instance_return_pci_devices(instance); + ret = mk_instance_return_pci_devices(instance); + if (ret) + return ret; mk_instance_return_platform_devices(instance); mk_pci_host_bridges_free(&instance->pci_host_bridges, &instance->pci_host_bridge_count, &instance->pci_host_bridges_valid); + mk_instance_return_all_cpus(instance); mk_instance_free_memory(instance); + return 0; +} + +static void mk_instance_release(struct kref *kref) +{ + struct mk_instance *instance = + container_of(kref, struct mk_instance, refcount); + int ret; + pr_info("Releasing multikernel instance %d (%s), returning resources to root\n", + instance->id, instance->name); + ret = mk_instance_release_resources(instance); + WARN_ON_ONCE(ret); mk_cpu_set_free(instance->cpus); kfree(instance->dtb_data); kfree(instance->name); kfree(instance); } - /** * Instance reference counting */ @@ -289,6 +311,7 @@ bool multikernel_allow_emergency_restart(void) */ int mk_instance_confirm_parked(struct mk_instance *instance) { + struct mk_cpu_set *snapshot; mk_phys_cpu_t phys_cpu; unsigned int i; int ret, failed = 0; @@ -296,8 +319,19 @@ int mk_instance_confirm_parked(struct mk_instance *instance) /* Never started, so nothing of it is running */ if (!instance->spawn_ctx) return 0; + if (!instance->cpus_on_slot) + return 0; + + snapshot = mk_cpu_set_alloc(); + if (!snapshot) + return -ENOMEM; + ret = mk_cpu_set_copy(snapshot, instance->cpus_on_slot); + if (ret) { + mk_cpu_set_free(snapshot); + return ret; + } - mk_cpu_set_for_each(i, phys_cpu, instance->cpus_on_slot) { + mk_cpu_set_for_each(i, phys_cpu, snapshot) { ret = mk_arch_confirm_parked(instance, phys_cpu); if (ret) { pr_err("Instance %d (%s): CPU %llu is not parked: %d\n", @@ -306,6 +340,7 @@ int mk_instance_confirm_parked(struct mk_instance *instance) } } + mk_cpu_set_free(snapshot); return failed ? -EBUSY : 0; } @@ -322,8 +357,10 @@ int mk_instance_confirm_parked(struct mk_instance *instance) int mk_instance_transfer_cpus(struct mk_instance *instance, const struct mk_cpu_set *cpus) { + struct mk_cpu_set *snapshot; unsigned int i, requested_count; mk_phys_cpu_t phys_cpu; + int logical_cpu; int unavailable = 0; char buf[256]; int ret; @@ -333,14 +370,25 @@ int mk_instance_transfer_cpus(struct mk_instance *instance, return -EINVAL; } - requested_count = mk_cpu_set_count(cpus); + snapshot = mk_cpu_set_alloc(); + if (!snapshot) + return -ENOMEM; + + mk_cpu_transaction_lock(); + mk_cpu_ownership_lock(); + ret = mk_cpu_set_copy(snapshot, cpus); + if (ret) + goto unlock; + + requested_count = mk_cpu_set_count(snapshot); if (requested_count == 0) { pr_info("No CPUs requested for instance %d (%s)\n", instance->id, instance->name); - return 0; + ret = 0; + goto unlock; } - mk_cpu_set_for_each(i, phys_cpu, cpus) { + mk_cpu_set_for_each(i, phys_cpu, snapshot) { if (!mk_cpu_set_contains(mk_cpu_pool, phys_cpu)) { pr_err("CPU %llu not available in the pool\n", phys_cpu); @@ -348,24 +396,30 @@ int mk_instance_transfer_cpus(struct mk_instance *instance, continue; } - if (arch_cpu_from_physical_id(phys_cpu) < 0) { + logical_cpu = arch_cpu_from_physical_id(phys_cpu); + if (logical_cpu < 0) { pr_err("Physical CPU %llu not found in logical CPU map\n", phys_cpu); unavailable++; + } else if (logical_cpu == 0) { + pr_err("Physical CPU %llu is reserved for host control\n", + phys_cpu); + unavailable++; } } if (unavailable > 0) { pr_err("Instance %d (%s): %d CPUs are not available\n", instance->id, instance->name, unavailable); - return -EBUSY; + ret = -EBUSY; + goto unlock; } ret = mk_cpu_set_reserve(instance->cpus, requested_count); if (ret) - return ret; + goto unlock; - mk_cpu_set_for_each(i, phys_cpu, cpus) { + mk_cpu_set_for_each(i, phys_cpu, snapshot) { mk_cpu_set_del(mk_cpu_pool, phys_cpu); mk_cpu_set_add(instance->cpus, phys_cpu); } @@ -374,7 +428,11 @@ int mk_instance_transfer_cpus(struct mk_instance *instance, pr_info("Transferred %u CPUs from pool to instance %d (%s): %s\n", requested_count, instance->id, instance->name, buf); - return 0; +unlock: + mk_cpu_ownership_unlock(); + mk_cpu_transaction_unlock(); + mk_cpu_set_free(snapshot); + return ret; } /** @@ -390,6 +448,7 @@ int mk_instance_transfer_cpus(struct mk_instance *instance, int mk_instance_return_cpus(struct mk_instance *instance, const struct mk_cpu_set *cpus) { + struct mk_cpu_set *snapshot; unsigned int i, requested_count; mk_phys_cpu_t phys_cpu; int not_found = 0; @@ -401,15 +460,26 @@ int mk_instance_return_cpus(struct mk_instance *instance, return -EINVAL; } - requested_count = mk_cpu_set_count(cpus); + snapshot = mk_cpu_set_alloc(); + if (!snapshot) + return -ENOMEM; + + mk_cpu_transaction_lock(); + mk_cpu_ownership_lock(); + ret = mk_cpu_set_copy(snapshot, cpus); + if (ret) + goto unlock; + + requested_count = mk_cpu_set_count(snapshot); if (requested_count == 0) { pr_info("No CPUs requested to return from instance %d (%s)\n", instance->id, instance->name); - return 0; + ret = 0; + goto unlock; } /* Validate all CPUs are assigned to this instance */ - mk_cpu_set_for_each(i, phys_cpu, cpus) { + mk_cpu_set_for_each(i, phys_cpu, snapshot) { if (!mk_cpu_set_contains(instance->cpus, phys_cpu)) { pr_err("CPU %llu not assigned to instance %d (%s)\n", phys_cpu, instance->id, instance->name); @@ -420,22 +490,17 @@ int mk_instance_return_cpus(struct mk_instance *instance, if (not_found > 0) { pr_err("Instance %d (%s): %d CPUs are not assigned to this instance\n", instance->id, instance->name, not_found); - return -EINVAL; + ret = -EINVAL; + goto unlock; } ret = mk_cpu_set_reserve(mk_cpu_pool, requested_count); if (ret) - return ret; + goto unlock; - mk_cpu_set_format(buf, sizeof(buf), cpus); + mk_cpu_set_format(buf, sizeof(buf), snapshot); - /* - * @cpus may alias instance->cpus (returning everything on - * teardown), so walk it back-to-front: a deletion then never - * shifts entries the walk has yet to visit. - */ - for (i = requested_count; i-- > 0; ) { - phys_cpu = cpus->ids[i]; + mk_cpu_set_for_each(i, phys_cpu, snapshot) { mk_cpu_set_add(mk_cpu_pool, phys_cpu); mk_cpu_set_del(instance->cpus, phys_cpu); } @@ -443,16 +508,20 @@ int mk_instance_return_cpus(struct mk_instance *instance, pr_info("Returned %u CPUs from instance %d (%s) to the pool: %s\n", requested_count, instance->id, instance->name, buf); - return 0; +unlock: + mk_cpu_ownership_unlock(); + mk_cpu_transaction_unlock(); + mk_cpu_set_free(snapshot); + return ret; } static int mk_instance_reserve_cpus(struct mk_instance *instance, const struct mk_dt_config *config) { if (!config->cpus) { - pr_warn("No CPU configuration for instance %d (%s)\n", - instance->id, instance->name); - return 0; + pr_err("No CPU configuration for instance %d (%s)\n", + instance->id, instance->name); + return -ENOMEM; } return mk_instance_transfer_cpus(instance, config->cpus); @@ -462,91 +531,43 @@ static int mk_instance_transfer_pci_devices(struct mk_instance *instance, const struct list_head *requested_devices, int requested_count) { - struct mk_pci_device *req_dev, *root_dev, *tmp; - int transferred = 0; - int not_found = 0; - bool found; - if (!root_instance || !root_instance->pci_devices_valid) { pr_err("No root instance or PCI devices not initialized\n"); return -EINVAL; } - if (requested_count == 0 || list_empty(requested_devices)) { + if (!requested_count || list_empty(requested_devices)) { pr_info("No PCI devices requested for instance %d (%s)\n", instance->id, instance->name); instance->pci_devices_valid = true; return 0; } - list_for_each_entry(req_dev, requested_devices, list) { - found = false; - list_for_each_entry(root_dev, &root_instance->pci_devices, list) { - if (root_dev->vendor == req_dev->vendor && - root_dev->device == req_dev->device && - root_dev->domain == req_dev->domain && - root_dev->bus == req_dev->bus && - root_dev->slot == req_dev->slot && - root_dev->func == req_dev->func) { - found = true; - break; - } - } - if (!found) { - pr_err("PCI device %04x:%04x@%04x:%02x:%02x.%x not available in root pool\n", - req_dev->vendor, req_dev->device, req_dev->domain, - req_dev->bus, req_dev->slot, req_dev->func); - not_found++; - } - } - - if (not_found > 0) { - pr_err("Instance %d (%s): %d PCI devices not available\n", - instance->id, instance->name, not_found); - return -ENOENT; - } - - list_for_each_entry(req_dev, requested_devices, list) { - list_for_each_entry_safe(root_dev, tmp, &root_instance->pci_devices, list) { - if (root_dev->vendor == req_dev->vendor && - root_dev->device == req_dev->device && - root_dev->domain == req_dev->domain && - root_dev->bus == req_dev->bus && - root_dev->slot == req_dev->slot && - root_dev->func == req_dev->func) { - - list_del(&root_dev->list); - list_add_tail(&root_dev->list, &instance->pci_devices); - root_instance->pci_device_count--; - instance->pci_device_count++; - transferred++; - - pr_debug("Transferred PCI device %04x:%04x@%04x:%02x:%02x.%x to instance %d\n", - root_dev->vendor, root_dev->device, root_dev->domain, - root_dev->bus, root_dev->slot, root_dev->func, - instance->id); - break; - } - } - } - - instance->pci_devices_valid = true; - pr_info("Transferred %d PCI devices from root to instance %d (%s), root pool remaining: %d devices\n", - transferred, instance->id, instance->name, root_instance->pci_device_count); - - return 0; + return mk_pci_assign_devices(instance, requested_devices, + requested_count); } - static int mk_instance_reserve_pci_devices(struct mk_instance *instance, const struct mk_dt_config *config) { - if (!config->pci_devices_valid || config->pci_device_count == 0) { + if (!config->pci_devices_valid) { + if (config->pci_device_count || + !list_empty(&config->pci_devices)) + return -EINVAL; + instance->pci_devices_valid = true; + return 0; + } + + if (!config->pci_device_count) { + if (!list_empty(&config->pci_devices)) + return -EINVAL; instance->pci_devices_valid = true; instance->pci_device_count = 0; pr_debug("No PCI devices to reserve for instance %d (%s)\n", instance->id, instance->name); return 0; } + if (list_empty(&config->pci_devices)) + return -EINVAL; return mk_instance_transfer_pci_devices(instance, &config->pci_devices, @@ -554,86 +575,111 @@ static int mk_instance_reserve_pci_devices(struct mk_instance *instance, } static int mk_instance_transfer_platform_devices(struct mk_instance *instance, - const struct list_head *requested_devices, - int requested_count) + const struct list_head *requested_devices, + int requested_count) { - struct mk_platform_device *req_dev, *root_dev, *tmp; + struct mk_platform_device *requested, *other, *root_device; + int actual_count = 0; int transferred = 0; - int not_found = 0; - bool found; if (!root_instance || !root_instance->platform_devices_valid) { pr_err("No root instance or platform devices not initialized\n"); return -EINVAL; } + if (requested_count <= 0 || list_empty(requested_devices)) + return -EINVAL; - if (requested_count == 0 || list_empty(requested_devices)) { - pr_info("No platform devices requested for instance %d (%s)\n", - instance->id, instance->name); - instance->platform_devices_valid = true; - return 0; - } + list_for_each_entry(requested, requested_devices, list) { + actual_count++; + list_for_each_entry(other, requested_devices, list) { + if (other == requested) + break; + if (!strcmp(other->name, requested->name)) { + pr_err("Platform device %s is requested more than once\n", + requested->name); + return -EINVAL; + } + } - list_for_each_entry(req_dev, requested_devices, list) { - found = false; - list_for_each_entry(root_dev, &root_instance->platform_devices, list) { - if (strcmp(root_dev->name, req_dev->name) == 0) { - found = true; + root_device = NULL; + list_for_each_entry(other, &root_instance->platform_devices, + list) { + if (!strcmp(other->name, requested->name)) { + root_device = other; break; } } - if (!found) { - pr_err("Platform device '%s' not available in root pool\n", - req_dev->name); - not_found++; + if (!root_device) { + pr_err("Platform device %s not available in root pool\n", + requested->name); + return -ENOENT; } } - if (not_found > 0) { - pr_err("Instance %d (%s): %d platform devices not available\n", - instance->id, instance->name, not_found); - return -ENOENT; + if (actual_count != requested_count) { + pr_err("Platform device count mismatch: metadata=%d list=%d\n", + requested_count, actual_count); + return -EINVAL; } - list_for_each_entry(req_dev, requested_devices, list) { - list_for_each_entry_safe(root_dev, tmp, &root_instance->platform_devices, list) { - if (strcmp(root_dev->name, req_dev->name) == 0) { - list_del(&root_dev->list); - list_add_tail(&root_dev->list, &instance->platform_devices); - root_instance->platform_device_count--; - instance->platform_device_count++; - transferred++; - - pr_debug("Transferred platform device '%s' to instance %d\n", - root_dev->name, instance->id); + list_for_each_entry(requested, requested_devices, list) { + root_device = NULL; + list_for_each_entry(other, &root_instance->platform_devices, + list) { + if (!strcmp(other->name, requested->name)) { + root_device = other; break; } } + if (!root_device) + goto rollback; + + list_move_tail(&root_device->list, + &instance->platform_devices); + root_instance->platform_device_count--; + instance->platform_device_count++; + transferred++; } instance->platform_devices_valid = true; - pr_info("Transferred %d platform devices from root to instance %d (%s), root pool remaining: %d devices\n", - transferred, instance->id, instance->name, root_instance->platform_device_count); - + pr_info("Transferred %d platform devices from root to instance %d (%s)\n", + transferred, instance->id, instance->name); return 0; + +rollback: + pr_err("Platform inventory changed during reservation for instance %d\n", + instance->id); + mk_instance_return_platform_devices(instance); + return -EIO; } static int mk_instance_reserve_platform_devices(struct mk_instance *instance, - const struct mk_dt_config *config) + const struct mk_dt_config *config) { - if (!config->platform_devices_valid || config->platform_device_count == 0) { + if (!config->platform_devices_valid) { + if (config->platform_device_count || + !list_empty(&config->platform_devices)) + return -EINVAL; + instance->platform_devices_valid = true; + return 0; + } + + if (!config->platform_device_count) { + if (!list_empty(&config->platform_devices)) + return -EINVAL; instance->platform_devices_valid = true; instance->platform_device_count = 0; pr_debug("No platform devices to reserve for instance %d (%s)\n", instance->id, instance->name); return 0; } + if (list_empty(&config->platform_devices)) + return -EINVAL; return mk_instance_transfer_platform_devices(instance, &config->platform_devices, config->platform_device_count); } - /** * mk_instance_add_pci_device - Add a single PCI device to an instance * @instance: Target instance @@ -649,37 +695,14 @@ static int mk_instance_reserve_platform_devices(struct mk_instance *instance, int mk_instance_add_pci_device(struct mk_instance *instance, u16 domain, u8 bus, u8 devfn) { - struct mk_pci_device *root_dev, *tmp; - u8 slot = PCI_SLOT(devfn); - u8 func = PCI_FUNC(devfn); - - if (!root_instance || !root_instance->pci_devices_valid) { - pr_err("No root instance or PCI devices not initialized\n"); - return -EINVAL; - } - - list_for_each_entry_safe(root_dev, tmp, &root_instance->pci_devices, list) { - if (root_dev->domain == domain && - root_dev->bus == bus && - root_dev->slot == slot && - root_dev->func == func) { - - list_del(&root_dev->list); - list_add_tail(&root_dev->list, &instance->pci_devices); - root_instance->pci_device_count--; - instance->pci_device_count++; - instance->pci_devices_valid = true; - - pr_info("Transferred PCI device %04x:%04x@%04x:%02x:%02x.%x to instance %d\n", - root_dev->vendor, root_dev->device, domain, bus, slot, func, - instance->id); - return 0; - } - } + int ret; - pr_err("PCI device %04x:%02x:%02x.%x not found in root pool\n", - domain, bus, slot, func); - return -ENOENT; + ret = mk_pci_assign_device(instance, domain, bus, devfn); + if (!ret) + pr_info("Leased PCI VF %04x:%02x:%02x.%x to instance %d\n", + domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), + instance->id); + return ret; } /** @@ -690,63 +713,22 @@ int mk_instance_add_pci_device(struct mk_instance *instance, * @devfn: PCI device and function (combined) * * Returns a single PCI device from the specified instance back to root instance. - * Used for dynamic PCI device hotplug from non-running instances. + * Dynamic assignment changes are accepted only while the instance is ready. * * Returns: 0 on success, negative error code on failure */ int mk_instance_remove_pci_device(struct mk_instance *instance, u16 domain, u8 bus, u8 devfn) { - struct mk_pci_device *inst_dev, *tmp; - struct mk_pci_device *root_dev; - u8 slot = PCI_SLOT(devfn); - u8 func = PCI_FUNC(devfn); - - if (!instance->pci_devices_valid) { - pr_err("Instance %d PCI devices not initialized\n", instance->id); - return -EINVAL; - } - - if (!root_instance) { - pr_err("Cannot return PCI device: no root instance\n"); - return -EINVAL; - } - - list_for_each_entry_safe(inst_dev, tmp, &instance->pci_devices, list) { - if (inst_dev->domain == domain && - inst_dev->bus == bus && - inst_dev->slot == slot && - inst_dev->func == func) { - - root_dev = kzalloc(sizeof(*root_dev), GFP_KERNEL); - if (!root_dev) { - pr_err("Failed to allocate PCI device entry for root instance\n"); - return -ENOMEM; - } - - *root_dev = *inst_dev; - INIT_LIST_HEAD(&root_dev->list); - - list_add_tail(&root_dev->list, &root_instance->pci_devices); - root_instance->pci_device_count++; - root_instance->pci_devices_valid = true; - - list_del(&inst_dev->list); - kfree(inst_dev); - instance->pci_device_count--; - - pr_info("Returned PCI device %04x:%04x@%04x:%02x:%02x.%x from instance %d to root\n", - root_dev->vendor, root_dev->device, domain, bus, slot, func, - instance->id); - return 0; - } - } + int ret; - pr_err("PCI device %04x:%02x:%02x.%x not found in instance %d\n", - domain, bus, slot, func, instance->id); - return -ENOENT; + ret = mk_pci_unassign_device(instance, domain, bus, devfn); + if (!ret) + pr_info("Released PCI VF %04x:%02x:%02x.%x from instance %d\n", + domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), + instance->id); + return ret; } - /** * Memory management functions for instances */ @@ -942,90 +924,106 @@ void mk_instance_free_memory(struct mk_instance *instance) instance->id, instance->name); } +static bool mk_instance_resources_empty(const struct mk_instance *instance) +{ + return list_empty(&instance->memory_regions) && + !instance->instance_pool && !instance->region_count && + mk_cpu_set_empty(instance->cpus) && + list_empty(&instance->pci_devices) && + list_empty(&instance->pci_assignments) && + !instance->pci_device_count && + list_empty(&instance->pci_host_bridges) && + !instance->pci_host_bridge_count && + list_empty(&instance->platform_devices) && + !instance->platform_device_count; +} + /** - * mk_instance_reserve_resources() - Reserve memory and CPU resources for an instance + * mk_instance_reserve_resources() - Atomically reserve instance resources * @instance: Instance to reserve resources for - * @config: Device tree configuration with memory regions and CPU assignment + * @config: Parsed resource configuration * - * Reserves all memory regions specified in the device tree configuration, - * makes them children of the main multikernel_res, and copies CPU assignment. + * Each resource class is acquired only after the preceding class succeeds. + * Any error returns every acquired resource to the root instance in reverse + * order, so callers never observe a partially populated instance. * - * Returns 0 on success, negative error code on failure. + * Returns: 0 on success, negative error code on failure */ int mk_instance_reserve_resources(struct mk_instance *instance, const struct mk_dt_config *config) { + const char *failed_resource; + int release_ret; int ret; - if (!config || !instance) { + if (!config || !instance || !instance->cpus) { pr_err("Invalid parameters to mk_instance_reserve_resources\n"); return -EINVAL; } + if (!mk_instance_resources_empty(instance)) { + pr_err("Instance %d (%s) already owns resources\n", + instance->id, instance->name); + return -EBUSY; + } - /* Free any existing memory regions first */ - mk_instance_free_memory(instance); - - /* Reserve memory regions */ + failed_resource = "memory"; ret = mk_instance_reserve_memory(instance, config); - if (ret) { - pr_err("Failed to reserve memory regions for instance %d (%s): %d\n", - instance->id, instance->name, ret); - return ret; - } + if (ret) + goto rollback; - /* Reserve CPU resources */ + failed_resource = "CPU"; ret = mk_instance_reserve_cpus(instance, config); - if (ret) { - pr_err("Failed to reserve CPU resources for instance %d (%s): %d\n", - instance->id, instance->name, ret); - /* Don't fail the whole operation for CPU reservation failure */ - pr_warn("Continuing without CPU assignment\n"); - } - - /* Reserve PCI device resources */ - ret = mk_instance_reserve_pci_devices(instance, config); - if (ret) { - pr_err("Failed to reserve PCI device resources for instance %d (%s): %d\n", - instance->id, instance->name, ret); - /* Don't fail the whole operation for PCI reservation failure */ - pr_warn("Continuing without PCI device assignment\n"); - } - - /* Copy descriptive PCI host bridge metadata; it is never transferred. */ - if (config->pci_host_bridges_valid && config->pci_host_bridge_count > 0) { + if (ret) + goto rollback; + + failed_resource = "PCI host bridge metadata"; + if ((!config->pci_host_bridges_valid && + (config->pci_host_bridge_count || + !list_empty(&config->pci_host_bridges))) || + (config->pci_host_bridges_valid && + !config->pci_host_bridge_count && + !list_empty(&config->pci_host_bridges))) { + ret = -EINVAL; + } else if (config->pci_host_bridges_valid && + config->pci_host_bridge_count > 0) { ret = mk_pci_host_bridges_clone(&instance->pci_host_bridges, &instance->pci_host_bridge_count, &instance->pci_host_bridges_valid, - &config->pci_host_bridges, - config->pci_host_bridge_count, true); + &config->pci_host_bridges, + config->pci_host_bridge_count, true); } else if (root_instance) { ret = mk_pci_host_bridges_clone(&instance->pci_host_bridges, &instance->pci_host_bridge_count, &instance->pci_host_bridges_valid, - &root_instance->pci_host_bridges, - root_instance->pci_host_bridge_count, - root_instance->pci_host_bridges_valid); + &root_instance->pci_host_bridges, + root_instance->pci_host_bridge_count, + root_instance->pci_host_bridges_valid); } else { - ret = 0; - } - if (ret) { - pr_err("Failed to copy PCI host bridge metadata for instance %d (%s): %d\n", - instance->id, instance->name, ret); - return ret; + ret = -EINVAL; } + if (ret) + goto rollback; - /* Reserve platform device resources */ + failed_resource = "platform device"; ret = mk_instance_reserve_platform_devices(instance, config); - if (ret) { - pr_err("Failed to reserve platform device resources for instance %d (%s): %d\n", - instance->id, instance->name, ret); - /* Don't fail the whole operation for platform reservation failure */ - pr_warn("Continuing without platform device assignment\n"); - } + if (ret) + goto rollback; + + failed_resource = "PCI device"; + ret = mk_instance_reserve_pci_devices(instance, config); + if (ret) + goto rollback; return 0; -} +rollback: + pr_err("Failed to reserve %s resources for instance %d (%s): %d\n", + failed_resource, instance->id, instance->name, ret); + release_ret = mk_instance_release_resources(instance); + if (release_ret) + return release_ret; + return ret; +} /** * Per-instance memory pool management */ @@ -1414,8 +1412,8 @@ int multikernel_halt_by_id(int mk_id) } /** - * multikernel_force_halt_by_id - Forcible shutdown of a multikernel instance via NMI - * @mk_id: Instance ID to halt + * mk_instance_force_halt - Forcibly stop an instance via NMI + * @instance: Instance to stop * * Forces a spawn kernel's CPUs to stop by queuing a shutdown message in the * IPI ring buffer and sending NMIs directly to each CPU. The NMI handler @@ -1426,49 +1424,68 @@ int multikernel_halt_by_id(int mk_id) * * Returns: 0 on success, negative error code on failure */ -int multikernel_force_halt_by_id(int mk_id) +int mk_instance_force_halt(struct mk_instance *instance) { - struct mk_instance *instance; struct mk_shutdown_payload payload; + struct mk_cpu_set *snapshot; mk_phys_cpu_t phys_cpu; unsigned int i; int cpu_count = 0; int ret; - instance = mk_instance_find(mk_id); if (!instance) - return -ENOENT; - + return -EINVAL; if (instance->state != MK_STATE_ACTIVE) { pr_err("Instance %d not active (state=%d), nothing to force halt\n", - mk_id, instance->state); - mk_instance_put(instance); + instance->id, instance->state); return -EINVAL; } - if (mk_cpu_set_empty(instance->cpus)) { - pr_err("Instance %d has no CPUs assigned\n", mk_id); - mk_instance_put(instance); - return -EINVAL; + snapshot = mk_cpu_set_alloc(); + if (!snapshot) + return -ENOMEM; + mk_cpu_transaction_lock(); + mk_cpu_ownership_lock(); + ret = mk_cpu_set_copy(snapshot, instance->cpus); + mk_cpu_ownership_unlock(); + if (ret) { + mk_cpu_transaction_unlock(); + mk_cpu_set_free(snapshot); + return ret; } - pr_info("Force halting multikernel instance %d via NMI\n", mk_id); + if (mk_cpu_set_empty(snapshot)) { + pr_err("Instance %d has no CPUs assigned\n", instance->id); + mk_cpu_transaction_unlock(); + mk_cpu_set_free(snapshot); + return -EINVAL; + } - /* Queue shutdown message - NMI handler will check for this */ + pr_info("Force halting multikernel instance %d via NMI\n", instance->id); + if (!instance->ipi_data) { + mk_cpu_transaction_unlock(); + mk_cpu_set_free(snapshot); + return -ENODEV; + } + atomic_set_release(&instance->ipi_data->emergency_shutdown, 1); payload.flags = MK_SHUTDOWN_IMMEDIATE; - payload.sender_instance_id = root_instance->id; - ret = mk_send_message(mk_id, MK_MSG_SYSTEM, MK_SYS_SHUTDOWN, - &payload, sizeof(payload)); + payload.sender_instance_id = root_instance ? root_instance->id : 0; + ret = mk_send_message_to_instance(instance, MK_MSG_SYSTEM, + MK_SYS_SHUTDOWN, &payload, + sizeof(payload)); if (ret < 0) - pr_err("Failed to queue shutdown message: %d (sending NMI anyway)\n", ret); + pr_err("Failed to queue shutdown message: %d (sending NMI anyway)\n", + ret); /* Send NMI to each CPU in the instance */ - mk_cpu_set_for_each(i, phys_cpu, instance->cpus) { + mk_cpu_set_for_each(i, phys_cpu, snapshot) { mk_force_stop_cpu(phys_cpu); cpu_count++; } - pr_info("Sent NMI to %d CPUs in instance %d\n", cpu_count, mk_id); + mk_cpu_set_free(snapshot); + pr_info("Sent NMI to %d CPUs in instance %d\n", + cpu_count, instance->id); /* * The NMI handler parks each CPU on the instance's context. Wait @@ -1476,14 +1493,32 @@ int multikernel_force_halt_by_id(int mk_id) * exactly as the graceful path does after its shutdown ACK. */ mk_instance_settle_halted(instance); - mk_instance_put(instance); + mk_cpu_transaction_unlock(); return 0; } +int multikernel_force_halt_by_id(int mk_id) +{ + struct mk_instance *instance; + int ret; + + instance = mk_instance_find(mk_id); + if (!instance) + return -ENOENT; + ret = mk_instance_force_halt(instance); + mk_instance_put(instance); + return ret; +} static int __init multikernel_init(void) { int ret; + ret = mk_pci_lease_system_init(); + if (ret) { + pr_err("Failed to initialize PCI assignment leases: %d\n", ret); + return ret; + } + /* Register NMI handler for forcible shutdown */ ret = mk_register_stop_nmi_handler(); if (ret < 0) { @@ -1494,6 +1529,7 @@ static int __init multikernel_init(void) ret = mk_messaging_init(); if (ret < 0) { pr_err("Failed to initialize multikernel messaging: %d\n", ret); + mk_pci_lease_system_cleanup(); return ret; } @@ -1501,6 +1537,7 @@ static int __init multikernel_init(void) if (ret < 0) { pr_err("Failed to register system message handler: %d\n", ret); mk_messaging_cleanup(); + mk_pci_lease_system_cleanup(); return ret; } @@ -1509,6 +1546,7 @@ static int __init multikernel_init(void) pr_err("Failed to initialize multikernel hotplug: %d\n", ret); mk_unregister_msg_handler(MK_MSG_SYSTEM, mk_system_msg_handler); mk_messaging_cleanup(); + mk_pci_lease_system_cleanup(); return ret; } @@ -1518,6 +1556,7 @@ static int __init multikernel_init(void) mk_hotplug_cleanup(); mk_unregister_msg_handler(MK_MSG_SYSTEM, mk_system_msg_handler); mk_messaging_cleanup(); + mk_pci_lease_system_cleanup(); return ret; } diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index d212894a6a3f18..0047531cfebbb6 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -140,26 +140,45 @@ int mk_pci_host_bridges_clone(struct list_head *dst, int *dst_count, { const struct mk_pci_host_bridge *src_bridge; struct mk_pci_host_bridge *dst_bridge; + int ret = -EINVAL; mk_pci_host_bridges_free(dst, dst_count, dst_valid); - if (!src_valid || src_count == 0) + if (src_count < 0) + return -EINVAL; + if (!src_valid) { + if (src_count || !list_empty(src)) + return -EINVAL; return 0; + } + if (!src_count) { + if (!list_empty(src)) + return -EINVAL; + return 0; + } + if (list_empty(src)) + return -EINVAL; *dst_valid = true; list_for_each_entry(src_bridge, src, list) { - dst_bridge = kmemdup(src_bridge, sizeof(*dst_bridge), GFP_KERNEL); + dst_bridge = kmemdup(src_bridge, sizeof(*dst_bridge), + GFP_KERNEL); if (!dst_bridge) { - mk_pci_host_bridges_free(dst, dst_count, dst_valid); - return -ENOMEM; + ret = -ENOMEM; + goto error; } INIT_LIST_HEAD(&dst_bridge->list); list_add_tail(&dst_bridge->list, dst); (*dst_count)++; } + if (*dst_count != src_count) + goto error; return 0; -} +error: + mk_pci_host_bridges_free(dst, dst_count, dst_valid); + return ret; +} int mk_dt_parse_pci_host_bridges(const void *fdt, int resources_node, struct list_head *bridges, int *count, bool *valid) diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index 25e2057c56a98a..c834c338c06f78 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -1230,6 +1230,21 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl return ret; } +static int mk_memory_change_allowed(struct mk_instance *instance) +{ + bool iommu_active; + + mutex_lock(&instance->resource_mutex); + iommu_active = mk_pci_iommu_lease_active_locked(instance); + mutex_unlock(&instance->resource_mutex); + if (!iommu_active) + return 0; + + pr_err("Cannot change memory for instance %d while an IOMMU lease is active\n", + instance->id); + return -EBUSY; +} + /** * mk_send_mem_add - Add memory to instance * @instance_id: Target instance ID @@ -1266,6 +1281,11 @@ int mk_send_mem_add(int instance_id, u64 start_pfn, u64 nr_pages, target_instance = mk_instance_find(instance_id); if (!target_instance) return -ENODEV; + ret = mk_memory_change_allowed(target_instance); + if (ret) { + mk_instance_put(target_instance); + return ret; + } /* For non-running instances, allocate memory from pool and add to instance */ if (target_instance->state != MK_STATE_ACTIVE) { @@ -1332,6 +1352,11 @@ int mk_send_mem_remove(int instance_id, u64 start_pfn, u64 nr_pages) target_instance = mk_instance_find(instance_id); if (!target_instance) return -ENODEV; + ret = mk_memory_change_allowed(target_instance); + if (ret) { + mk_instance_put(target_instance); + return ret; + } /* For non-running instances, just remove the memory region from the instance */ if (target_instance->state != MK_STATE_ACTIVE) { @@ -1371,117 +1396,66 @@ int mk_send_mem_remove(int instance_id, u64 start_pfn, u64 nr_pages) } /** - * mk_send_device_add - Add PCI device to instance and wait for completion + * mk_send_device_add - Assign a PCI device to an instance * @instance_id: Target instance ID * @domain: PCI domain * @bus: PCI bus * @devfn: PCI device and function (combined) - * @driver_override: Target driver name for binding (can be NULL) - * @flags: Additional flags + * @driver_override: Target driver name for a root-kernel add + * @flags: Additional root-kernel add flags * - * For local instance, executes addition synchronously. - * For remote instance, sends IPI and waits for ACK response. - * For instances that are not yet running (MK_STATE_READY/LOADED), - * adds device to instance's device list. + * Remote assignment changes are permitted only while the target instance is + * ready. Active instances must be stopped and returned to ready state first. * * Returns: 0 on success, negative error code on failure */ int mk_send_device_add(int instance_id, u16 domain, u8 bus, u8 devfn, const char *driver_override, u32 flags) { - struct mk_device_resource_payload payload = { - .domain = domain, - .bus = bus, - .devfn = devfn, - .flags = flags, - .sender_instance_id = root_instance->id - }; - struct mk_pending_msg *pending; struct mk_instance *target_instance; int ret; - u32 resource_id; - - if (driver_override) - strscpy(payload.driver_override, driver_override, sizeof(payload.driver_override)); - else - payload.driver_override[0] = '\0'; - - resource_id = (domain << 16) | (bus << 8) | devfn; + if (!root_instance) + return -ENODEV; if (instance_id == root_instance->id) - return mk_do_device_add(domain, bus, devfn, driver_override, flags); + return mk_do_device_add(domain, bus, devfn, driver_override, + flags); target_instance = mk_instance_find(instance_id); if (!target_instance) return -ENODEV; - if (target_instance->state != MK_STATE_ACTIVE) { - ret = mk_instance_add_pci_device(target_instance, domain, bus, devfn); - goto out; - } - - pending = mk_msg_pending_add(MK_MSG_RESOURCE, MK_RES_DEVICE_ADD, resource_id); - if (!pending) { - ret = -ENOMEM; - goto out; - } - - ret = mk_send_message(instance_id, MK_MSG_RESOURCE, MK_RES_DEVICE_ADD, - &payload, sizeof(payload)); - if (ret < 0) { - mk_msg_pending_wait(pending, 0); - goto out; - } - - ret = mk_msg_pending_wait(pending, 10000); - if (ret < 0) - goto out; - - ret = mk_instance_add_pci_device(target_instance, domain, bus, devfn); - if (ret < 0) { - pr_warn("Device added to target but failed to update tracking: %d\n", ret); + if (target_instance->state != MK_STATE_READY) { + pr_err("PCI assignment changes require instance %d to be ready\n", + instance_id); + ret = -EBUSY; + } else { + ret = mk_instance_add_pci_device(target_instance, domain, bus, + devfn); } - - pr_info("Multikernel hotplug: Device %04x:%02x:%02x.%x successfully added to instance %d\n", - domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), instance_id); - - ret = 0; -out: mk_instance_put(target_instance); return ret; } /** - * mk_send_device_remove - Remove PCI device from instance and wait for completion + * mk_send_device_remove - Release a PCI device from an instance * @instance_id: Target instance ID * @domain: PCI domain * @bus: PCI bus * @devfn: PCI device and function (combined) * - * For local instance, executes removal synchronously. - * For remote instance, sends IPI and waits for ACK response. - * For instances that are not yet running (MK_STATE_READY/LOADED), - * removes device from instance's device list. + * Remote assignment changes are permitted only while the target instance is + * ready. Active instances must be stopped and returned to ready state first. * * Returns: 0 on success, negative error code on failure */ int mk_send_device_remove(int instance_id, u16 domain, u8 bus, u8 devfn) { - struct mk_device_resource_payload payload = { - .domain = domain, - .bus = bus, - .devfn = devfn, - .flags = 0, - .sender_instance_id = root_instance->id - }; - struct mk_pending_msg *pending; struct mk_instance *target_instance; int ret; - u32 resource_id; - - payload.driver_override[0] = '\0'; - resource_id = (domain << 16) | (bus << 8) | devfn; + if (!root_instance) + return -ENODEV; if (instance_id == root_instance->id) return mk_do_device_remove(domain, bus, devfn); @@ -1489,38 +1463,14 @@ int mk_send_device_remove(int instance_id, u16 domain, u8 bus, u8 devfn) if (!target_instance) return -ENODEV; - if (target_instance->state != MK_STATE_ACTIVE) { - ret = mk_instance_remove_pci_device(target_instance, domain, bus, devfn); - goto out; - } - - pending = mk_msg_pending_add(MK_MSG_RESOURCE, MK_RES_DEVICE_REMOVE, resource_id); - if (!pending) { - ret = -ENOMEM; - goto out; - } - - ret = mk_send_message(instance_id, MK_MSG_RESOURCE, MK_RES_DEVICE_REMOVE, - &payload, sizeof(payload)); - if (ret < 0) { - mk_msg_pending_wait(pending, 0); - goto out; - } - - ret = mk_msg_pending_wait(pending, 10000); - if (ret < 0) - goto out; - - ret = mk_instance_remove_pci_device(target_instance, domain, bus, devfn); - if (ret < 0) { - pr_warn("Device removed from target but failed to update tracking: %d\n", ret); + if (target_instance->state != MK_STATE_READY) { + pr_err("PCI assignment changes require instance %d to be ready\n", + instance_id); + ret = -EBUSY; + } else { + ret = mk_instance_remove_pci_device(target_instance, domain, bus, + devfn); } - - pr_info("Multikernel hotplug: Device %04x:%02x:%02x.%x successfully removed from instance %d\n", - domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), instance_id); - - ret = 0; -out: mk_instance_put(target_instance); return ret; } diff --git a/kernel/multikernel/instance_dt.c b/kernel/multikernel/instance_dt.c index 81da05bfe2b48d..1f47688fe1a689 100644 --- a/kernel/multikernel/instance_dt.c +++ b/kernel/multikernel/instance_dt.c @@ -408,6 +408,7 @@ static struct mk_instance * __init alloc_mk_instance(int instance_id, const char INIT_LIST_HEAD(&instance->list); kref_init(&instance->refcount); INIT_LIST_HEAD(&instance->pci_devices); + mk_pci_lease_instance_init(instance); instance->pci_devices_valid = false; instance->pci_device_count = 0; INIT_LIST_HEAD(&instance->pci_host_bridges); diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index ad5da764883f26..9830a68ca1f860 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -5,6 +5,13 @@ extern struct idr mk_instance_idr; extern struct list_head mk_instance_list; extern struct mk_instance *root_instance; +/* ipi.c */ +int mk_send_ipi_data(struct mk_instance *instance, void *data, + size_t data_size, unsigned long type); +/* messaging.c */ +int mk_send_message_to_instance(struct mk_instance *instance, u32 msg_type, + u32 subtype, void *payload, u32 payload_len); + /* kernfs.c */ extern struct kernfs_node *mk_root_kn; extern struct kernfs_node *mk_instances_kn; @@ -12,6 +19,7 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, int resources_node, size_t dtb_size); struct mk_instance *mk_instance_find_by_name(const char *name); int mk_instance_destroy(struct mk_instance *instance); +int mk_instance_release_resources(struct mk_instance *instance); /* dts.c */ int mk_dt_parse_resources(const void *fdt, int resources_node, @@ -27,6 +35,31 @@ int mk_pci_host_bridges_clone(struct list_head *dst, int *dst_count, void mk_pci_host_bridges_free(struct list_head *bridges, int *count, bool *valid); +/* CPU ownership serialization: transaction must be acquired before ownership. */ +void mk_cpu_transaction_lock(void); +void mk_cpu_transaction_unlock(void); +void mk_cpu_ownership_lock(void); +void mk_cpu_ownership_unlock(void); +void mk_cpu_ownership_assert_held(void); + +/* pci.c */ +int mk_pci_lease_system_init(void); +void mk_pci_lease_system_cleanup(void); +void mk_pci_lease_instance_init(struct mk_instance *instance); +bool mk_pci_iommu_lease_active_locked(struct mk_instance *instance); +int mk_pci_assign_devices(struct mk_instance *instance, + const struct list_head *requested_devices, + int requested_count); +int mk_pci_assign_device(struct mk_instance *instance, u16 domain, u8 bus, + u8 devfn); +int mk_pci_unassign_device(struct mk_instance *instance, u16 domain, u8 bus, + u8 devfn); +int mk_pci_release_assignments(struct mk_instance *instance); + +/* Caller must hold instance->resource_mutex. */ +void mk_pci_quiesce_instance_irqs(struct mk_instance *instance); +int mk_instance_force_halt(struct mk_instance *instance); + /* overlay.c */ extern struct kernfs_node *mk_overlay_root_kn; int mk_overlay_init(void); diff --git a/kernel/multikernel/ipi.c b/kernel/multikernel/ipi.c index 0325ab3e3bf933..51997c31466fd2 100644 --- a/kernel/multikernel/ipi.c +++ b/kernel/multikernel/ipi.c @@ -149,7 +149,7 @@ EXPORT_SYMBOL(multikernel_unregister_handler); /** * multikernel_send_ipi_data - Send data to another CPU via IPI - * @instance_id: Target multikernel instance ID + * @instance: Target multikernel instance * @data: Pointer to data to send * @data_size: Size of data * @type: User-defined type identifier @@ -159,25 +159,24 @@ EXPORT_SYMBOL(multikernel_unregister_handler); * * 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(struct mk_instance *instance, void *data, + size_t data_size, unsigned long type) { struct mk_ipi_data *slot; - struct mk_instance *instance = mk_instance_find(instance_id); struct mk_ipi_ring *ring; mk_phys_cpu_t target; + int instance_id; int ret; if (!instance) return -EINVAL; - if (data_size > MK_MAX_DATA_SIZE) { - mk_instance_put(instance); + instance_id = instance->id; + 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); return -ENODEV; } @@ -196,7 +195,6 @@ int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, uns if (!instance->ipi_data) { pr_err("Multikernel IPI buffer not available for instance %d\n", instance_id); - mk_instance_put(instance); return -ENODEV; } @@ -212,7 +210,6 @@ 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\n", instance_id); - mk_instance_put(instance); return ret; } @@ -227,10 +224,23 @@ int multikernel_send_ipi_data(int instance_id, void *data, size_t data_size, uns atomic_set_release(&slot->state, MK_IPI_SLOT_READY); mk_arch_send_ipi(target); - mk_instance_put(instance); 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(instance, data, data_size, type); + mk_instance_put(instance); + return ret; +} + static void mk_ipi_drain_ring(void) { struct mk_ipi_data *slot; diff --git a/kernel/multikernel/kernfs.c b/kernel/multikernel/kernfs.c index 9e7cae53585b4c..5c0ef9f757ccf5 100644 --- a/kernel/multikernel/kernfs.c +++ b/kernel/multikernel/kernfs.c @@ -239,6 +239,7 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, struct kernfs_node *kn; struct mk_dt_config config; void *dtb_copy; + int release_ret; int ret; int allocated_id; @@ -269,6 +270,7 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, INIT_LIST_HEAD(&instance->memory_regions); INIT_LIST_HEAD(&instance->list); INIT_LIST_HEAD(&instance->pci_devices); + mk_pci_lease_instance_init(instance); INIT_LIST_HEAD(&instance->pci_host_bridges); INIT_LIST_HEAD(&instance->platform_devices); kref_init(&instance->refcount); @@ -334,7 +336,16 @@ int mk_create_instance_from_dtb(const char *name, int id, const void *fdt, kfree(instance->dtb_data); instance->dtb_data = NULL; err_free_resources: - mk_instance_free_memory(instance); + release_ret = mk_instance_release_resources(instance); + if (release_ret) { + pr_crit("Retaining failed instance '%s' because PCI cleanup failed: %d\n", + name, release_ret); + list_add_tail(&instance->list, &mk_instance_list); + kernfs_activate(kn); + mk_instance_set_state(instance, MK_STATE_FAILED); + mk_dt_config_free(&config); + return release_ret; + } err_free_idr: idr_remove(&mk_instance_idr, instance->id); err_remove_dir: @@ -436,6 +447,8 @@ static int mk_kernfs_rmdir(struct kernfs_node *kn) */ int mk_instance_destroy(struct mk_instance *instance) { + int ret; + lockdep_assert_held(&mk_instance_mutex); if (!instance) { @@ -455,6 +468,14 @@ int mk_instance_destroy(struct mk_instance *instance) return -EBUSY; } + ret = mk_instance_release_resources(instance); + if (ret) { + pr_crit("Cannot remove instance '%s' while PCI cleanup is unsafe: %d\n", + instance->name, ret); + mk_instance_set_state(instance, MK_STATE_FAILED); + return ret; + } + list_del(&instance->list); idr_remove(&mk_instance_idr, instance->id); if (instance->kn) { diff --git a/kernel/multikernel/mem.c b/kernel/multikernel/mem.c index 5c2da8dadb12ed..001d75bcc0368d 100644 --- a/kernel/multikernel/mem.c +++ b/kernel/multikernel/mem.c @@ -285,17 +285,30 @@ int mk_instance_add_memory_region(struct mk_instance *instance, size_t size) phys_addr_t phys_addr; int ret; + if (!instance) + return -EINVAL; + + mutex_lock(&instance->resource_mutex); + if (mk_pci_iommu_lease_active_locked(instance)) { + pr_err("Cannot add memory to instance %d while an IOMMU lease is active\n", + instance->id); + ret = -EBUSY; + goto out_unlock; + } + phys_addr = multikernel_alloc(size); if (!phys_addr) { pr_err("Failed to allocate %zu bytes from multikernel pool for instance %d\n", size, instance->id); - return -ENOMEM; + ret = -ENOMEM; + goto out_unlock; } region = kzalloc(sizeof(*region), GFP_KERNEL); if (!region) { multikernel_free(phys_addr, size); - return -ENOMEM; + ret = -ENOMEM; + goto out_unlock; } region->res.name = kasprintf(GFP_KERNEL, "mk-instance-%d-%s-region-%d", @@ -303,7 +316,8 @@ int mk_instance_add_memory_region(struct mk_instance *instance, size_t size) if (!region->res.name) { kfree(region); multikernel_free(phys_addr, size); - return -ENOMEM; + ret = -ENOMEM; + goto out_unlock; } region->res.start = phys_addr; @@ -318,7 +332,7 @@ int mk_instance_add_memory_region(struct mk_instance *instance, size_t size) kfree(region->res.name); kfree(region); multikernel_free(phys_addr, size); - return ret; + goto out_unlock; } INIT_LIST_HEAD(®ion->list); @@ -329,7 +343,10 @@ int mk_instance_add_memory_region(struct mk_instance *instance, size_t size) (unsigned long long)phys_addr, (unsigned long long)(phys_addr + size - 1), size >> 20, instance->id, instance->name); - return 0; + ret = 0; +out_unlock: + mutex_unlock(&instance->resource_mutex); + return ret; } /** @@ -350,10 +367,19 @@ int mk_instance_remove_memory_region(struct mk_instance *instance, { struct mk_memory_region *region, *tmp; bool found = false; + int ret; if (!instance) return -EINVAL; + mutex_lock(&instance->resource_mutex); + if (mk_pci_iommu_lease_active_locked(instance)) { + pr_err("Cannot remove memory from instance %d while an IOMMU lease is active\n", + instance->id); + ret = -EBUSY; + goto out_unlock; + } + list_for_each_entry_safe(region, tmp, &instance->memory_regions, list) { if (region->res.start == phys_addr && resource_size(®ion->res) == size) { @@ -381,10 +407,14 @@ int mk_instance_remove_memory_region(struct mk_instance *instance, (unsigned long long)phys_addr, (unsigned long long)(phys_addr + size - 1), instance->id, instance->name); - return -ENOENT; + ret = -ENOENT; + } else { + ret = 0; } - return 0; +out_unlock: + mutex_unlock(&instance->resource_mutex); + return ret; } /** diff --git a/kernel/multikernel/messaging.c b/kernel/multikernel/messaging.c index e2674950b33fc0..2739d33668cca5 100644 --- a/kernel/multikernel/messaging.c +++ b/kernel/multikernel/messaging.c @@ -11,6 +11,7 @@ #include #include #include +#include "internal.h" /* Pending message tracking for request-response pattern */ struct mk_pending_msg { @@ -191,8 +192,9 @@ int mk_msg_pending_wait(struct mk_pending_msg *pending, unsigned long timeout_ms * * Returns 0 on success, negative error code on failure */ -int mk_send_message(int instance_id, u32 msg_type, u32 subtype, - void *payload, u32 payload_len) +static int __mk_send_message(struct mk_instance *instance, int instance_id, + u32 msg_type, u32 subtype, void *payload, + u32 payload_len) { struct mk_message *msg; size_t total_size; @@ -224,7 +226,11 @@ int mk_send_message(int instance_id, u32 msg_type, u32 subtype, memcpy(msg->payload, payload, payload_len); /* Send via IPI using the message type as IPI type */ - ret = multikernel_send_ipi_data(instance_id, msg, total_size, msg_type); + if (instance) + ret = mk_send_ipi_data(instance, msg, total_size, msg_type); + else + ret = multikernel_send_ipi_data(instance_id, msg, total_size, + msg_type); /* Clean up temporary buffer */ kfree(msg); @@ -239,6 +245,22 @@ int mk_send_message(int instance_id, u32 msg_type, u32 subtype, return 0; } + +int mk_send_message_to_instance(struct mk_instance *instance, u32 msg_type, + u32 subtype, void *payload, u32 payload_len) +{ + if (!instance) + return -EINVAL; + return __mk_send_message(instance, instance->id, msg_type, subtype, + payload, payload_len); +} + +int mk_send_message(int instance_id, u32 msg_type, u32 subtype, + void *payload, u32 payload_len) +{ + return __mk_send_message(NULL, instance_id, msg_type, subtype, payload, + payload_len); +} EXPORT_SYMBOL(mk_send_message); /** diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index 61c5f9cbe15e10..727953237e5e7a 100644 --- a/kernel/multikernel/pci.c +++ b/kernel/multikernel/pci.c @@ -5,33 +5,1259 @@ * Keeps assigned-device discovery, identity presentation, bridge traversal, * and resource restoration independent from the manifest that populated * the current instance. + * + * Configuration-space filtering constrains normal PCI access by a cooperative + * spawn kernel. It is not a security boundary: a privileged kernel can issue + * configuration cycles or map physical configuration windows directly. The + * host-owned IOMMU domain separately constrains DMA initiated by an assigned + * device. */ +#include +#include +#include +#include #include +#include #include +#include +#include #include "internal.h" -static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, int devfn) +struct mk_pci_assignment { + struct list_head instance_node; + struct list_head active_node; + struct list_head transaction_node; + struct mk_instance *instance; + struct mk_pci_device *inventory; + struct pci_dev *vf; + struct pci_dev *pf; + const struct device_driver *host_driver; + struct iommu_group *iommu_group; + struct iommu_domain *iommu_domain; + char *host_driver_override; + struct mutex iommu_mutex; /* Serializes IOMMU activation and teardown. */ + unsigned int iommu_mapped_regions; + bool iommu_dma_owner; + bool iommu_attached; + bool iommu_override_active; + struct work_struct failure_work; + atomic_t failure_pending; + bool assigned; + bool inventory_moved; + bool expected_unbind; +}; + +static DEFINE_MUTEX(mk_pci_lease_mutex); +static DEFINE_SPINLOCK(mk_pci_active_lock); +static LIST_HEAD(mk_pci_active_assignments); +static bool mk_pci_notifier_registered; + +static bool mk_pci_device_live(struct pci_dev *pdev) +{ + return device_is_registered(&pdev->dev) && + !pci_dev_is_disconnected(pdev) && + pci_device_is_present(pdev); +} + +static bool mk_pci_device_matches_bdf(const struct mk_pci_device *device, + u16 domain, u8 bus, u8 devfn) +{ + return device->domain == domain && device->bus == bus && + device->slot == PCI_SLOT(devfn) && + device->func == PCI_FUNC(devfn); +} + +static struct mk_pci_device * +mk_pci_find_device_bdf(struct list_head *devices, u16 domain, u8 bus, u8 devfn) { struct mk_pci_device *device; - u16 domain = pci_domain_nr(bus); - u8 slot = PCI_SLOT(devfn); - u8 func = PCI_FUNC(devfn); - if (!root_instance || !root_instance->dtb_data || - !root_instance->pci_devices_valid) - return NULL; + list_for_each_entry(device, devices, list) { + if (mk_pci_device_matches_bdf(device, domain, bus, devfn)) + return device; + } + + return NULL; +} + +static bool mk_pci_inventory_matches(const struct mk_pci_device *left, + const struct mk_pci_device *right) +{ + return left->vendor == right->vendor && + left->device == right->device && + mk_pci_device_matches_bdf(left, right->domain, right->bus, + PCI_DEVFN(right->slot, right->func)); +} + +static struct mk_pci_device * +mk_pci_find_root_inventory(const struct mk_pci_device *requested) +{ + struct mk_pci_device *device; list_for_each_entry(device, &root_instance->pci_devices, list) { - if (device->domain == domain && device->bus == bus->number && - device->slot == slot && device->func == func) + if (mk_pci_inventory_matches(device, requested)) return device; } return NULL; } +static struct mk_pci_device * +mk_pci_find_root_bdf(u16 domain, u8 bus, u8 devfn) +{ + return mk_pci_find_device_bdf(&root_instance->pci_devices, domain, bus, + devfn); +} + +static struct mk_pci_assignment * +mk_pci_find_assignment(struct mk_instance *instance, u16 domain, u8 bus, + u8 devfn) +{ + struct mk_pci_assignment *assignment; + + list_for_each_entry(assignment, &instance->pci_assignments, + instance_node) { + if (mk_pci_device_matches_bdf(assignment->inventory, domain, bus, + devfn)) + return assignment; + } + + return NULL; +} + +static void mk_pci_assignment_failure_work(struct work_struct *work) +{ + struct mk_pci_assignment *assignment = + container_of(work, struct mk_pci_assignment, failure_work); + struct mk_instance *instance = assignment->instance; + int ret; + + pr_err("PCI assignment lease for %s was lost by instance %d (%s)\n", + pci_name(assignment->vf), instance->id, instance->name); + + if (READ_ONCE(instance->state) == MK_STATE_ACTIVE) { + ret = mk_instance_force_halt(instance); + if (ret) + pr_err("Failed to force halt instance %d after PCI lease loss: %d\n", + instance->id, ret); + } + + mk_instance_set_state(instance, MK_STATE_FAILED); +} + +static void mk_pci_schedule_failure(struct mk_pci_assignment *assignment) +{ + if (atomic_cmpxchg(&assignment->failure_pending, 0, 1)) + return; + + if (!schedule_work(&assignment->failure_work)) + atomic_set(&assignment->failure_pending, 0); +} + +static int mk_pci_bus_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct pci_dev *pdev = to_pci_dev(data); + struct mk_pci_assignment *assignment; + unsigned long flags; + + if (action != BUS_NOTIFY_DEL_DEVICE && + action != BUS_NOTIFY_REMOVED_DEVICE && + action != BUS_NOTIFY_UNBOUND_DRIVER) + return NOTIFY_DONE; + + spin_lock_irqsave(&mk_pci_active_lock, flags); + list_for_each_entry(assignment, &mk_pci_active_assignments, + active_node) { + if (pdev != assignment->vf && pdev != assignment->pf) + continue; + if (pdev == assignment->vf && + action == BUS_NOTIFY_UNBOUND_DRIVER && + assignment->expected_unbind) + continue; + mk_pci_schedule_failure(assignment); + } + spin_unlock_irqrestore(&mk_pci_active_lock, flags); + + return NOTIFY_OK; +} + +static struct notifier_block mk_pci_bus_notifier = { + .notifier_call = mk_pci_bus_notify, +}; + +static void +mk_pci_release_bound_driver(struct mk_pci_assignment *assignment) +{ + unsigned long flags; + + spin_lock_irqsave(&mk_pci_active_lock, flags); + assignment->expected_unbind = true; + spin_unlock_irqrestore(&mk_pci_active_lock, flags); + device_release_driver(&assignment->vf->dev); + spin_lock_irqsave(&mk_pci_active_lock, flags); + assignment->expected_unbind = false; + spin_unlock_irqrestore(&mk_pci_active_lock, flags); +} + +#if IS_ENABLED(CONFIG_IOMMU_API) +#define MK_PCI_ASSIGNMENT_DRIVER_NAME "multikernel-pci-assignment" + +static int mk_pci_iommu_assignment_probe(struct pci_dev *pdev, + const struct pci_device_id *id); +static void mk_pci_iommu_assignment_remove(struct pci_dev *pdev); + +static struct pci_driver mk_pci_assignment_driver = { + .name = MK_PCI_ASSIGNMENT_DRIVER_NAME, + .probe = mk_pci_iommu_assignment_probe, + .remove = mk_pci_iommu_assignment_remove, + .driver_managed_dma = true, +}; + +struct mk_pci_iommu_group_check { + struct device *vf; + unsigned int count; +}; + +static int mk_pci_iommu_check_group_device(struct device *dev, void *data) +{ + struct mk_pci_iommu_group_check *check = data; + + check->count++; + return dev == check->vf ? 0 : -EXDEV; +} + +static void mk_pci_iommu_free_resv_regions(struct list_head *regions) +{ + struct iommu_resv_region *region, *tmp; + + list_for_each_entry_safe(region, tmp, regions, list) { + list_del(®ion->list); + kfree(region); + } +} + +static int mk_pci_iommu_validate_group(struct mk_pci_assignment *assignment) +{ + struct mk_pci_iommu_group_check check = { + .vf = &assignment->vf->dev, + }; + int ret; + + ret = iommu_group_for_each_dev(assignment->iommu_group, &check, + mk_pci_iommu_check_group_device); + if (ret || check.count != 1) { + pr_err("IOMMU group %d for %s is not an isolated singleton group\n", + iommu_group_id(assignment->iommu_group), + pci_name(assignment->vf)); + return ret ?: -EXDEV; + } + + if (!iommu_group_has_isolated_msi(assignment->iommu_group)) { + pr_err("IOMMU group %d for %s lacks isolated MSI delivery\n", + iommu_group_id(assignment->iommu_group), + pci_name(assignment->vf)); + return -EPERM; + } + + return 0; +} + +static int +mk_pci_iommu_validate_resv_regions(struct mk_pci_assignment *assignment) +{ + struct iommu_resv_region *region; + struct mk_memory_region *memory; + LIST_HEAD(resv_regions); + u64 memory_end, resv_end; + int ret; + + ret = iommu_get_group_resv_regions(assignment->iommu_group, + &resv_regions); + if (ret) + goto out; + + list_for_each_entry(region, &resv_regions, list) { + if (region->type == IOMMU_RESV_DIRECT_RELAXABLE) + continue; + if (!region->length || + check_add_overflow((u64)region->start, + (u64)region->length - 1, &resv_end)) { + pr_err("IOMMU group %d for %s has invalid reserved region at %#llx\n", + iommu_group_id(assignment->iommu_group), + pci_name(assignment->vf), + (unsigned long long)region->start); + ret = -EOVERFLOW; + goto out; + } + + list_for_each_entry(memory, + &assignment->instance->memory_regions, list) { + resource_size_t size = resource_size(&memory->res); + + if (!size || + check_add_overflow((u64)memory->res.start, + (u64)size - 1, &memory_end)) { + ret = -EOVERFLOW; + goto out; + } + if ((u64)memory->res.start > resv_end || + memory_end < (u64)region->start) + continue; + + pr_err("Instance %d IOVA %#llx-%#llx overlaps IOMMU reserved region %#llx-%#llx type %u for %s\n", + assignment->instance->id, + (unsigned long long)memory->res.start, + (unsigned long long)memory_end, + (unsigned long long)region->start, + (unsigned long long)resv_end, region->type, + pci_name(assignment->vf)); + ret = -EPERM; + goto out; + } + } + + ret = 0; +out: + mk_pci_iommu_free_resv_regions(&resv_regions); + return ret; +} + +static int +mk_pci_iommu_validate_region(struct mk_pci_assignment *assignment, + const struct mk_memory_region *region) +{ + struct iommu_domain *domain = assignment->iommu_domain; + resource_size_t start = region->res.start; + resource_size_t size = resource_size(®ion->res); + u64 dma_mask = dma_get_mask(&assignment->vf->dev); + u64 end; + unsigned long min_page_size; + + if (!size || check_add_overflow((u64)start, (u64)size - 1, &end)) + return -EOVERFLOW; + if (!domain->pgsize_bitmap) + return -EOPNOTSUPP; + + min_page_size = 1UL << __ffs(domain->pgsize_bitmap); + if (!IS_ALIGNED(start, min_page_size) || + !IS_ALIGNED(size, min_page_size)) { + pr_err("Instance %d memory %#llx-%#llx is not aligned to IOMMU page size %#lx\n", + assignment->instance->id, (unsigned long long)start, + (unsigned long long)end, min_page_size); + return -EINVAL; + } + if (start > ULONG_MAX || end > ULONG_MAX || end > dma_mask) { + pr_err("Instance %d memory %#llx-%#llx exceeds DMA addressability of %s\n", + assignment->instance->id, (unsigned long long)start, + (unsigned long long)end, pci_name(assignment->vf)); + return -ERANGE; + } + if (domain->geometry.force_aperture && + (start < domain->geometry.aperture_start || + end > domain->geometry.aperture_end)) { + pr_err("Instance %d memory %#llx-%#llx is outside the IOMMU aperture for %s\n", + assignment->instance->id, (unsigned long long)start, + (unsigned long long)end, pci_name(assignment->vf)); + return -ERANGE; + } + + return 0; +} + +static void mk_pci_iommu_unmap_regions(struct mk_pci_assignment *assignment) +{ + struct mk_memory_region *region; + unsigned int remaining = assignment->iommu_mapped_regions; + + list_for_each_entry(region, &assignment->instance->memory_regions, list) { + resource_size_t size; + size_t unmapped; + + if (!remaining) + break; + size = resource_size(®ion->res); + unmapped = iommu_unmap(assignment->iommu_domain, + region->res.start, size); + if (unmapped != size) + pr_err("IOMMU unmapped only %#zx of %#llx bytes for instance %d at %#llx\n", + unmapped, (unsigned long long)size, + assignment->instance->id, + (unsigned long long)region->res.start); + remaining--; + } + if (remaining) + pr_err("IOMMU lease for %s lost %u mapped instance regions\n", + pci_name(assignment->vf), remaining); + assignment->iommu_mapped_regions = 0; +} + +static int +mk_pci_quiesce_assignment(struct mk_pci_assignment *assignment) +{ + struct pci_dev *vf = assignment->vf; + bool transactions_drained; + int ret; + + if (!mk_pci_device_live(vf)) + return 0; + + /* + * Releasing DMA ownership restores the group's default domain. Stop new + * DMA first, drain requests already issued, and reset the VF while the + * assignment domain still contains any stragglers. + */ + pci_clear_master(vf); + transactions_drained = pci_wait_for_pending_transaction(vf); + ret = pcie_reset_flr(vf, false); + if (!ret) + return 0; + if (ret == -ENOTTY && transactions_drained) + return 0; + + if (!transactions_drained) + pr_err("Timed out draining DMA from assigned VF %s\n", + pci_name(vf)); + if (ret != -ENOTTY) + pr_err("Failed to reset assigned VF %s: %d\n", + pci_name(vf), ret); + + /* + * Keep the assignment domain attached when the device cannot be made + * safe. The lease owner can retry teardown after the instance halts. + */ + return ret == -ENOTTY ? -ETIMEDOUT : ret; +} + +static int +mk_pci_reset_assignment_for_start(struct mk_pci_assignment *assignment) +{ + struct pci_dev *vf = assignment->vf; + int ret; + + if (!assignment->assigned || !assignment->iommu_attached) + return -EINVAL; + if (!mk_pci_device_live(vf)) + return -ENODEV; + + /* + * A stopped instance may have left DMA active. Keep its restrictive + * domain attached while stopping new requests, draining old ones, and + * resetting device state before the instance image is reused. + */ + pci_clear_master(vf); + if (!pci_wait_for_pending_transaction(vf)) { + pr_err("Timed out draining assigned VF %s before instance restart\n", + pci_name(vf)); + return -ETIMEDOUT; + } + + ret = pcie_reset_flr(vf, false); + if (ret) { + pr_err("Failed to reset assigned VF %s before instance restart: %d\n", + pci_name(vf), ret); + return ret == -ENOTTY ? -EOPNOTSUPP : ret; + } + return 0; +} + +static void +__mk_pci_iommu_deactivate_assignment(struct mk_pci_assignment *assignment) +{ + if (assignment->iommu_attached) { + iommu_detach_group(assignment->iommu_domain, + assignment->iommu_group); + assignment->iommu_attached = false; + } + if (assignment->iommu_dma_owner) { + iommu_device_release_dma_owner(&assignment->vf->dev); + assignment->iommu_dma_owner = false; + } +} + +static void +mk_pci_iommu_deactivate_assignment(struct mk_pci_assignment *assignment) +{ + mutex_lock(&assignment->iommu_mutex); + __mk_pci_iommu_deactivate_assignment(assignment); + mutex_unlock(&assignment->iommu_mutex); +} + +static void mk_pci_iommu_release_assignment(struct mk_pci_assignment *assignment) +{ + mutex_lock(&assignment->iommu_mutex); + __mk_pci_iommu_deactivate_assignment(assignment); + + if (assignment->iommu_domain) { + mk_pci_iommu_unmap_regions(assignment); + iommu_domain_free(assignment->iommu_domain); + assignment->iommu_domain = NULL; + } + if (assignment->iommu_group) { + iommu_group_put(assignment->iommu_group); + assignment->iommu_group = NULL; + } + mutex_unlock(&assignment->iommu_mutex); +} + +static int mk_pci_iommu_prepare_assignment(struct mk_pci_assignment *assignment) +{ + struct mk_memory_region *region; + int ret; + + if (!device_iommu_mapped(&assignment->vf->dev)) { + pr_err("Cannot assign %s without an active hardware IOMMU\n", + pci_name(assignment->vf)); + return -EOPNOTSUPP; + } + if (!device_iommu_capable(&assignment->vf->dev, + IOMMU_CAP_CACHE_COHERENCY)) { + pr_err("Cannot assign %s without coherent IOMMU mappings\n", + pci_name(assignment->vf)); + return -EOPNOTSUPP; + } + if (!assignment->instance->region_count || + list_empty(&assignment->instance->memory_regions)) + return -EINVAL; + + assignment->iommu_group = iommu_group_get(&assignment->vf->dev); + if (!assignment->iommu_group) + return -ENODEV; + + ret = mk_pci_iommu_validate_group(assignment); + if (ret) + goto err_release; + ret = mk_pci_iommu_validate_resv_regions(assignment); + if (ret) + goto err_release; + + assignment->iommu_domain = + iommu_paging_domain_alloc(&assignment->vf->dev); + if (IS_ERR(assignment->iommu_domain)) { + ret = PTR_ERR(assignment->iommu_domain); + assignment->iommu_domain = NULL; + goto err_release; + } + + list_for_each_entry(region, &assignment->instance->memory_regions, list) { + resource_size_t size = resource_size(®ion->res); + + ret = mk_pci_iommu_validate_region(assignment, region); + if (ret) + goto err_release; + ret = iommu_map(assignment->iommu_domain, region->res.start, + region->res.start, size, + IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE, GFP_KERNEL); + if (ret) + goto err_release; + assignment->iommu_mapped_regions++; + } + + /* + * The domain blocks DMA outside these mappings, but translation-fault + * notification is not portable. In particular, Intel VT-d reports primary + * faults through dmar_fault() without invoking a legacy domain handler. + * Do not claim automatic instance failure on an IOMMU fault here. + */ + pr_info("Prepared host IOMMU domain for %s with %u instance regions\n", + pci_name(assignment->vf), assignment->iommu_mapped_regions); + return 0; + +err_release: + mk_pci_iommu_release_assignment(assignment); + return ret; +} + +static int mk_pci_iommu_commit_assignment(struct mk_pci_assignment *assignment) +{ + int ret; + + if (!assignment->iommu_domain) + return 0; + + if (assignment->vf->driver_override) { + assignment->host_driver_override = + kstrdup(assignment->vf->driver_override, GFP_KERNEL); + if (!assignment->host_driver_override) + return -ENOMEM; + } + + ret = driver_set_override(&assignment->vf->dev, + &assignment->vf->driver_override, + MK_PCI_ASSIGNMENT_DRIVER_NAME, + strlen(MK_PCI_ASSIGNMENT_DRIVER_NAME)); + if (ret) + return ret; + assignment->iommu_override_active = true; + pci_set_drvdata(assignment->vf, assignment); + ret = device_driver_attach(&mk_pci_assignment_driver.driver, + &assignment->vf->dev); + if (ret) + return ret; + if (assignment->vf->dev.driver != &mk_pci_assignment_driver.driver) + return -ENODEV; + return 0; +} + +static int mk_pci_iommu_assignment_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct mk_pci_assignment *assignment = pci_get_drvdata(pdev); + int ret; + + if (!assignment || assignment->vf != pdev || !assignment->iommu_domain) + return -ENODEV; + ret = iommu_device_claim_dma_owner(&assignment->vf->dev, assignment); + if (ret) + return ret; + assignment->iommu_dma_owner = true; + + ret = iommu_attach_group(assignment->iommu_domain, + assignment->iommu_group); + if (ret) + return ret; + assignment->iommu_attached = true; + pr_info("Attached %s to host-owned IOMMU domain for instance %d\n", + pci_name(assignment->vf), assignment->instance->id); + return 0; +} + +static void mk_pci_iommu_assignment_remove(struct pci_dev *pdev) +{ + struct mk_pci_assignment *assignment = pci_get_drvdata(pdev); + int ret; + + if (!assignment || assignment->vf != pdev) + return; + + if (READ_ONCE(assignment->expected_unbind)) { + pci_set_drvdata(pdev, NULL); + return; + } + + ret = mk_pci_quiesce_assignment(assignment); + if (ret) { + pr_crit("Keeping IOMMU containment for %s after unsafe driver removal: %d\n", + pci_name(pdev), ret); + mk_pci_schedule_failure(assignment); + } else { + mk_pci_iommu_deactivate_assignment(assignment); + } + pci_set_drvdata(pdev, NULL); +} + +static int mk_pci_restore_host_binding(struct mk_pci_assignment *assignment) +{ + struct pci_dev *vf = assignment->vf; + const char *override = assignment->host_driver_override ?: ""; + int ret = 0; + + if (vf->dev.driver == &mk_pci_assignment_driver.driver) { + mk_pci_release_bound_driver(assignment); + } else if (vf->dev.driver) { + pr_err("Cannot release assignment driver from %s: device is bound to %s\n", + pci_name(vf), vf->dev.driver->name); + return -EBUSY; + } + + pci_set_drvdata(vf, NULL); + if (assignment->iommu_override_active) { + ret = driver_set_override(&vf->dev, &vf->driver_override, + override, strlen(override)); + if (ret) + return ret; + assignment->iommu_override_active = false; + } + + mk_pci_iommu_deactivate_assignment(assignment); + if (assignment->host_driver && mk_pci_device_live(vf)) { + if (!vf->dev.driver) { + ret = device_driver_attach(assignment->host_driver, &vf->dev); + if (ret) { + pr_err("Failed to restore driver %s to %s: %d\n", + assignment->host_driver->name, + pci_name(vf), ret); + return ret; + } + } else if (vf->dev.driver != assignment->host_driver) { + pr_err("Cannot restore driver %s to %s: device is bound to %s\n", + assignment->host_driver->name, pci_name(vf), + vf->dev.driver->name); + return -EBUSY; + } + } + return 0; +} + +static int mk_pci_iommu_system_init(void) +{ + return pci_register_driver(&mk_pci_assignment_driver); +} + +static void mk_pci_iommu_system_cleanup(void) +{ + pci_unregister_driver(&mk_pci_assignment_driver); +} +#else +static int +mk_pci_quiesce_assignment(struct mk_pci_assignment *assignment) +{ + return 0; +} + +static void +mk_pci_iommu_deactivate_assignment(struct mk_pci_assignment *assignment) +{ +} + +static int mk_pci_iommu_prepare_assignment(struct mk_pci_assignment *assignment) +{ + pr_err("Cannot assign %s without CONFIG_IOMMU_API\n", + pci_name(assignment->vf)); + return -EOPNOTSUPP; +} + +static int mk_pci_iommu_commit_assignment(struct mk_pci_assignment *assignment) +{ + return 0; +} + +static void mk_pci_iommu_release_assignment(struct mk_pci_assignment *assignment) +{ +} + +static int mk_pci_restore_host_binding(struct mk_pci_assignment *assignment) +{ + return 0; +} + +static int mk_pci_iommu_system_init(void) +{ + return 0; +} + +static void mk_pci_iommu_system_cleanup(void) +{ +} +#endif + +static int +mk_pci_prepare_assignment(struct mk_instance *instance, + const struct mk_pci_device *requested, + struct list_head *transaction) +{ + struct mk_pci_assignment *assignment; + struct mk_pci_device *inventory; + struct pci_dev *vf; + struct pci_dev *pf; + struct pci_dev *physfn; + int ret; + + inventory = mk_pci_find_root_inventory(requested); + if (!inventory) { + pr_err("PCI device %04x:%04x@%04x:%02x:%02x.%x is not available in the root pool\n", + requested->vendor, requested->device, requested->domain, + requested->bus, requested->slot, requested->func); + return -ENOENT; + } + + vf = pci_get_domain_bus_and_slot(inventory->domain, + inventory->bus, + PCI_DEVFN(inventory->slot, + inventory->func)); + if (!vf) + return -ENODEV; + + if (vf->vendor != inventory->vendor || + vf->device != inventory->device) { + pr_err("PCI identity changed for %s: expected %04x:%04x, found %04x:%04x\n", + pci_name(vf), inventory->vendor, inventory->device, + vf->vendor, vf->device); + pci_dev_put(vf); + return -ENODEV; + } + + physfn = pci_physfn(vf); + if (!vf->is_virtfn || physfn == vf) { + pr_err("PCI assignment only supports SR-IOV VFs, rejecting %s\n", + pci_name(vf)); + pci_dev_put(vf); + return -EOPNOTSUPP; + } + + if (!mk_pci_device_live(vf) || !mk_pci_device_live(physfn)) { + pci_dev_put(vf); + return -ENODEV; + } + ret = pcie_reset_flr(vf, true); + if (ret) { + pr_err("PCI assignment requires FLR for safe instance restart, rejecting %s\n", + pci_name(vf)); + pci_dev_put(vf); + return -EOPNOTSUPP; + } + + if (pci_is_dev_assigned(vf) || + mk_pci_find_assignment(instance, inventory->domain, + inventory->bus, + PCI_DEVFN(inventory->slot, + inventory->func))) { + pci_dev_put(vf); + return -EBUSY; + } + + pf = pci_dev_get(physfn); + assignment = kzalloc(sizeof(*assignment), GFP_KERNEL); + if (!assignment) { + pci_dev_put(pf); + pci_dev_put(vf); + return -ENOMEM; + } + + assignment->instance = instance; + assignment->inventory = inventory; + assignment->vf = vf; + assignment->pf = pf; + assignment->host_driver = vf->dev.driver; + if (assignment->host_driver && assignment->host_driver->owner && + !try_module_get(assignment->host_driver->owner)) { + kfree(assignment); + pci_dev_put(pf); + pci_dev_put(vf); + return -ENODEV; + } + + INIT_LIST_HEAD(&assignment->instance_node); + INIT_LIST_HEAD(&assignment->active_node); + INIT_LIST_HEAD(&assignment->transaction_node); + mutex_init(&assignment->iommu_mutex); + INIT_WORK(&assignment->failure_work, mk_pci_assignment_failure_work); + atomic_set(&assignment->failure_pending, 0); + + ret = mk_pci_iommu_prepare_assignment(assignment); + if (ret) + goto err_module; + + list_add_tail(&assignment->instance_node, &instance->pci_assignments); + list_add_tail(&assignment->transaction_node, transaction); + + return 0; + +err_module: + if (assignment->host_driver && assignment->host_driver->owner) + module_put(assignment->host_driver->owner); + kfree(assignment); + pci_dev_put(pf); + pci_dev_put(vf); + return ret; +} + +static int mk_pci_commit_assignment(struct mk_pci_assignment *assignment) +{ + struct pci_dev *vf = assignment->vf; + unsigned long flags; + int ret; + int i; + + if (!mk_pci_device_live(vf) || !mk_pci_device_live(assignment->pf)) + return -ENODEV; + + if (vf->dev.driver != assignment->host_driver || + pci_is_dev_assigned(vf)) + return -EBUSY; + + pci_set_dev_assigned(vf); + assignment->assigned = true; + + spin_lock_irqsave(&mk_pci_active_lock, flags); + list_add_tail(&assignment->active_node, &mk_pci_active_assignments); + spin_unlock_irqrestore(&mk_pci_active_lock, flags); + + if (assignment->host_driver) + mk_pci_release_bound_driver(assignment); + + if (vf->dev.driver) + return -EBUSY; + + ret = mk_pci_iommu_commit_assignment(assignment); + if (ret) + return ret; + + for (i = 0; i < MK_PCI_RESOURCE_COUNT; i++) { + assignment->inventory->resources[i].start = + vf->resource[i].start; + assignment->inventory->resources[i].end = + vf->resource[i].end; + assignment->inventory->resources[i].flags = + vf->resource[i].flags; + } + assignment->inventory->resources_valid = true; + + list_move_tail(&assignment->inventory->list, + &assignment->instance->pci_devices); + root_instance->pci_device_count--; + assignment->instance->pci_device_count++; + assignment->instance->pci_devices_valid = true; + assignment->inventory_moved = true; + + pr_info("Leased SR-IOV VF %s to instance %d (%s)\n", + pci_name(vf), assignment->instance->id, + assignment->instance->name); + return 0; +} + +static int mk_pci_release_assignment(struct mk_pci_assignment *assignment, + struct list_head *released) +{ + struct mk_instance *instance = assignment->instance; + struct pci_dev *vf = assignment->vf; + unsigned long flags; + int ret; + + if (!assignment->assigned) + goto release_resources; + + ret = mk_pci_quiesce_assignment(assignment); + if (ret) + return ret; + + ret = mk_pci_restore_host_binding(assignment); + if (ret) + return ret; + + spin_lock_irqsave(&mk_pci_active_lock, flags); + if (!list_empty(&assignment->active_node)) + list_del_init(&assignment->active_node); + assignment->expected_unbind = false; + spin_unlock_irqrestore(&mk_pci_active_lock, flags); + + if (assignment->assigned) { + pci_clear_dev_assigned(vf); + assignment->assigned = false; + } + +release_resources: + mk_pci_iommu_release_assignment(assignment); + + if (assignment->inventory_moved && root_instance) { + assignment->inventory->resources_valid = false; + list_move_tail(&assignment->inventory->list, + &root_instance->pci_devices); + instance->pci_device_count--; + root_instance->pci_device_count++; + root_instance->pci_devices_valid = true; + assignment->inventory_moved = false; + } + + if (!list_empty(&assignment->transaction_node)) + list_del_init(&assignment->transaction_node); + list_del_init(&assignment->instance_node); + list_add_tail(&assignment->transaction_node, released); + + return 0; +} + +static void mk_pci_finalize_releases(struct list_head *released) +{ + struct mk_pci_assignment *assignment, *tmp; + + list_for_each_entry_safe(assignment, tmp, released, transaction_node) { + list_del_init(&assignment->transaction_node); + cancel_work_sync(&assignment->failure_work); + kfree(assignment->host_driver_override); + if (assignment->host_driver && assignment->host_driver->owner) + module_put(assignment->host_driver->owner); + pci_dev_put(assignment->pf); + pci_dev_put(assignment->vf); + kfree(assignment); + } +} + +static int mk_pci_rollback_transaction(struct list_head *transaction, + struct list_head *released) +{ + struct mk_pci_assignment *assignment, *tmp; + int rollback_ret = 0; + int ret; + + list_for_each_entry_safe_reverse(assignment, tmp, transaction, + transaction_node) { + ret = mk_pci_release_assignment(assignment, released); + if (!ret) + continue; + pr_crit("Failed to roll back PCI assignment for %s: %d\n", + pci_name(assignment->vf), ret); + list_del_init(&assignment->transaction_node); + if (!rollback_ret) + rollback_ret = ret; + } + + return rollback_ret; +} + +static int mk_pci_commit_transaction(struct list_head *transaction) +{ + struct mk_pci_assignment *assignment; + int ret; + + list_for_each_entry(assignment, transaction, transaction_node) { + ret = mk_pci_commit_assignment(assignment); + if (ret) + return ret; + } + + while (!list_empty(transaction)) { + assignment = list_first_entry(transaction, + struct mk_pci_assignment, + transaction_node); + list_del_init(&assignment->transaction_node); + } + + return 0; +} + +void mk_pci_lease_instance_init(struct mk_instance *instance) +{ + mutex_init(&instance->resource_mutex); + INIT_LIST_HEAD(&instance->pci_assignments); +} + +bool mk_pci_iommu_lease_active_locked(struct mk_instance *instance) +{ + if (!instance) + return false; + + lockdep_assert_held(&instance->resource_mutex); + return !list_empty(&instance->pci_assignments); +} + +int mk_pci_assign_devices(struct mk_instance *instance, + const struct list_head *requested_devices, + int requested_count) +{ + struct mk_pci_device *requested; + LIST_HEAD(released); + LIST_HEAD(transaction); + int prepared = 0; + int ret = 0; + int rollback_ret; + + if (!instance || instance == root_instance || !requested_devices || + requested_count < 0) + return -EINVAL; + if (!root_instance || !root_instance->pci_devices_valid) + return -EINVAL; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + + list_for_each_entry(requested, requested_devices, list) { + ret = mk_pci_prepare_assignment(instance, requested, + &transaction); + if (ret) + goto rollback; + prepared++; + } + + if (prepared != requested_count) { + ret = -EINVAL; + goto rollback; + } + + ret = mk_pci_commit_transaction(&transaction); + if (ret) + goto rollback; + goto out; + +rollback: + rollback_ret = mk_pci_rollback_transaction(&transaction, &released); + if (rollback_ret) + ret = rollback_ret; +out: + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + mk_pci_finalize_releases(&released); + return ret; +} + +int mk_pci_assign_device(struct mk_instance *instance, u16 domain, u8 bus, + u8 devfn) +{ + struct mk_pci_device *inventory; + LIST_HEAD(released); + LIST_HEAD(transaction); + int ret; + int rollback_ret; + + if (!instance || instance == root_instance) + return -EINVAL; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + + if (instance->state != MK_STATE_READY) { + ret = -EBUSY; + goto out; + } + if (!root_instance || !root_instance->pci_devices_valid) { + ret = -EINVAL; + goto out; + } + + inventory = mk_pci_find_root_bdf(domain, bus, devfn); + if (!inventory) { + ret = -ENOENT; + goto out; + } + + ret = mk_pci_prepare_assignment(instance, inventory, &transaction); + if (ret) + goto rollback; + ret = mk_pci_commit_transaction(&transaction); + if (ret) + goto rollback; + goto out; + +rollback: + rollback_ret = mk_pci_rollback_transaction(&transaction, &released); + if (rollback_ret) + ret = rollback_ret; +out: + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + mk_pci_finalize_releases(&released); + return ret; +} + +int mk_pci_unassign_device(struct mk_instance *instance, u16 domain, u8 bus, + u8 devfn) +{ + struct mk_pci_assignment *assignment; + LIST_HEAD(released); + int ret; + + if (!instance || instance == root_instance) + return -EINVAL; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + + if (instance->state != MK_STATE_READY) { + ret = -EBUSY; + goto out; + } + + assignment = mk_pci_find_assignment(instance, domain, bus, devfn); + if (!assignment) { + ret = -ENOENT; + goto out; + } + + ret = mk_pci_release_assignment(assignment, &released); +out: + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + mk_pci_finalize_releases(&released); + return ret; +} + +int mk_pci_release_assignments(struct mk_instance *instance) +{ + struct mk_pci_assignment *assignment; + LIST_HEAD(released); + int ret = 0; + + if (!instance || instance == root_instance) + return 0; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + while (!list_empty(&instance->pci_assignments)) { + assignment = list_last_entry(&instance->pci_assignments, + struct mk_pci_assignment, + instance_node); + ret = mk_pci_release_assignment(assignment, &released); + if (ret) { + pr_crit("Instance %d retains unsafe PCI lease for %s: %d\n", + instance->id, pci_name(assignment->vf), ret); + break; + } + } + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + mk_pci_finalize_releases(&released); + return ret; +} + +int mk_pci_prepare_instance_start(struct mk_instance *instance) +{ + struct mk_pci_assignment *assignment; + int ret = 0; + + if (!instance || instance == root_instance) + return -EINVAL; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + list_for_each_entry(assignment, &instance->pci_assignments, + instance_node) { + ret = mk_pci_reset_assignment_for_start(assignment); + if (ret) + break; + } + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + return ret; +} + +int mk_pci_lease_system_init(void) +{ + int ret; + + ret = mk_pci_iommu_system_init(); + if (ret) + return ret; + ret = bus_register_notifier(&pci_bus_type, &mk_pci_bus_notifier); + if (ret) { + mk_pci_iommu_system_cleanup(); + return ret; + } + mk_pci_notifier_registered = true; + return 0; +} + +void mk_pci_lease_system_cleanup(void) +{ + if (mk_pci_notifier_registered) { + bus_unregister_notifier(&pci_bus_type, &mk_pci_bus_notifier); + mk_pci_notifier_registered = false; + } + mk_pci_iommu_system_cleanup(); +} + +static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, int devfn) +{ + if (!root_instance || root_instance->id == 0 || + !root_instance->dtb_data || + !root_instance->pci_devices_valid) + return NULL; + + return mk_pci_find_device_bdf(&root_instance->pci_devices, + pci_domain_nr(bus), bus->number, devfn); +} + /** * mk_pci_get_assigned_identity - Get the identity presented to an instance * @bus: PCI bus From d723ef0e861d03a89a69a27cb01a11e1dd4ee7b7 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Tue, 4 Aug 2026 08:26:40 +0300 Subject: [PATCH 08/13] x86/multikernel: filter raw PCI config operations Move assignment enforcement from the synthetic root operations to both raw x86 PCI configuration backends. Reject BDFs absent from spawn assignment metadata, synthesize assigned identity where required, and forward accepted accesses to the saved backend. This constrains normal cooperative-kernel PCI paths; it is not a security boundary against privileged physical access. Signed-off-by: Nikolay Nikolaev --- arch/x86/multikernel/pci.c | 71 ++++++++++++++++++++++++++++++------- include/linux/multikernel.h | 30 +++++++--------- kernel/multikernel/pci.c | 26 ++++++++++---- 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/arch/x86/multikernel/pci.c b/arch/x86/multikernel/pci.c index bb4f97fe039c7c..b20a02e53ca176 100644 --- a/arch/x86/multikernel/pci.c +++ b/arch/x86/multikernel/pci.c @@ -25,7 +25,8 @@ struct mk_mmcfg_snapshot { size_t count; }; -static struct pci_ops mk_pci_native_ops; +static const struct pci_raw_ops *mk_pci_native_raw_ops; +static const struct pci_raw_ops *mk_pci_native_raw_ext_ops; static bool mk_pci_roots_ready; static bool mk_mmcfg_snapshot_contains(const struct mk_mmcfg_snapshot *snapshot, @@ -57,30 +58,73 @@ static bool mk_pci_identity_read(u16 vendor, u16 device, int where, int size, return true; } -static int mk_pci_read(struct pci_bus *bus, unsigned int devfn, int where, - int size, u32 *value) +static int mk_pci_raw_read(const struct pci_raw_ops *native, + unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, + u32 *value) { u16 vendor, device; - if (!mk_pci_get_assigned_identity(bus, devfn, &vendor, &device)) { + if (!mk_pci_get_assigned_identity_bdf(domain, bus, devfn, &vendor, + &device)) { *value = ~0U; return PCIBIOS_DEVICE_NOT_FOUND; } if (mk_pci_identity_read(vendor, device, where, size, value)) return PCIBIOS_SUCCESSFUL; - return mk_pci_native_ops.read(bus, devfn, where, size, value); + return native->read(domain, bus, devfn, where, size, value); } -static int mk_pci_write(struct pci_bus *bus, unsigned int devfn, int where, - int size, u32 value) +static int mk_pci_raw_write(const struct pci_raw_ops *native, + unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, + u32 value) { - if (!mk_pci_get_assigned_identity(bus, devfn, NULL, NULL)) + if (!mk_pci_get_assigned_identity_bdf(domain, bus, devfn, NULL, NULL)) return PCIBIOS_DEVICE_NOT_FOUND; - return mk_pci_native_ops.write(bus, devfn, where, size, value); + return native->write(domain, bus, devfn, where, size, value); } +static int mk_pci_read(unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, u32 *value) +{ + return mk_pci_raw_read(mk_pci_native_raw_ops, domain, bus, devfn, + where, size, value); +} + +static int mk_pci_write(unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, u32 value) +{ + return mk_pci_raw_write(mk_pci_native_raw_ops, domain, bus, devfn, + where, size, value); +} + +static int mk_pci_ext_read(unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, u32 *value) +{ + return mk_pci_raw_read(mk_pci_native_raw_ext_ops, domain, bus, devfn, + where, size, value); +} + +static int mk_pci_ext_write(unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, u32 value) +{ + return mk_pci_raw_write(mk_pci_native_raw_ext_ops, domain, bus, devfn, + where, size, value); +} + +static const struct pci_raw_ops mk_pci_filtered_raw_ops = { + .read = mk_pci_read, + .write = mk_pci_write, +}; + +static const struct pci_raw_ops mk_pci_filtered_raw_ext_ops = { + .read = mk_pci_ext_read, + .write = mk_pci_ext_write, +}; + static int mk_mmcfg_snapshot_region(const struct pci_mmcfg_region *region, void *data) { @@ -191,11 +235,12 @@ static int __init x86_multikernel_pci_arch_init(void) raw_pci_ops = &pci_mmcfg; raw_pci_ext_ops = &pci_mmcfg; - mk_pci_native_ops = pci_root_ops; - pci_root_ops.read = mk_pci_read; - pci_root_ops.write = mk_pci_write; + mk_pci_native_raw_ops = raw_pci_ops; + mk_pci_native_raw_ext_ops = raw_pci_ext_ops; + raw_pci_ops = &mk_pci_filtered_raw_ops; + raw_pci_ext_ops = &mk_pci_filtered_raw_ext_ops; mk_pci_roots_ready = true; - pr_notice("Multikernel selected ECAM for PCI config access\n"); + pr_notice("Multikernel selected filtered ECAM for PCI config access\n"); return 0; } diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index cc1e419d40f4fc..0e595d6b9ece4f 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -16,7 +16,6 @@ #include #include struct pci_bus; -struct pci_ops; /** * Physical CPU identifiers @@ -830,11 +829,10 @@ void mk_instance_set_state(struct mk_instance *instance, void *mk_kimage_alloc(struct kimage *image, size_t size, size_t align); void mk_kimage_free(struct kimage *image, void *virt_addr, size_t size); -/* Device probe filtering against the instance's allowlist */ -bool mk_pci_should_probe(struct pci_bus *bus, int devfn, - const struct pci_ops *ops); -bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, - u16 *vendor, u16 *device); +/* Device filtering against the instance metadata */ +bool mk_pci_get_assigned_identity_bdf(unsigned int domain, unsigned int bus, + unsigned int devfn, u16 *vendor, + u16 *device); bool mk_platform_device_allowed(const char *name, const char *hid); /* Early CPU registration from the manifest (spawn kernels) */ @@ -884,11 +882,6 @@ static inline void mk_kimage_free(struct kimage *image, void *virt_addr, { } -static inline bool mk_pci_should_probe(struct pci_bus *bus, int devfn, - const struct pci_ops *ops) -{ - return true; -} static inline bool mk_platform_device_allowed(const char *name, const char *hid) { return true; @@ -982,19 +975,22 @@ int __init mk_instance_restore_from_manifest(void); */ /** - * PCI Device Enforcement Functions + * PCI Device Filtering Functions */ /** - * mk_pci_get_assigned_identity() - Check and identify an assigned PCI function - * @bus: PCI bus + * mk_pci_get_assigned_identity_bdf() - Identify an assigned PCI function + * @domain: PCI domain number + * @bus: PCI bus number * @devfn: PCI device/function number * @vendor: optional assigned vendor ID output * @device: optional assigned device ID output * - * Synthetic roots make assigned functions directly discoverable. Only exact - * assignment metadata matches may access config space; physical bridges and - * all other functions remain inaccessible. + * The raw x86 PCI configuration wrappers use this BDF-only lookup before + * reaching their hardware backend. Synthetic roots make assigned functions + * directly discoverable. A privileged spawn kernel can bypass those wrappers, + * so this check prevents accidental access rather than isolating a hostile + * kernel. * * Returns: true for an exact assignment metadata match, false otherwise */ diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index 727953237e5e7a..b0025bb1e1fec1 100644 --- a/kernel/multikernel/pci.c +++ b/kernel/multikernel/pci.c @@ -1247,7 +1247,8 @@ void mk_pci_lease_system_cleanup(void) mk_pci_iommu_system_cleanup(); } -static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, int devfn) +static struct mk_pci_device * +mk_pci_find_assigned_bdf(u16 domain, u8 bus, u8 devfn) { if (!root_instance || root_instance->id == 0 || !root_instance->dtb_data || @@ -1255,22 +1256,33 @@ static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, int devfn return NULL; return mk_pci_find_device_bdf(&root_instance->pci_devices, - pci_domain_nr(bus), bus->number, devfn); + domain, bus, devfn); +} + +static struct mk_pci_device *mk_pci_find_assigned(struct pci_bus *bus, int devfn) +{ + return mk_pci_find_assigned_bdf(pci_domain_nr(bus), bus->number, devfn); } /** - * mk_pci_get_assigned_identity - Get the identity presented to an instance - * @bus: PCI bus + * mk_pci_get_assigned_identity_bdf - Get an assigned function's identity + * @domain: PCI domain number + * @bus: PCI bus number * @devfn: device/function number * @vendor: assigned Vendor ID * @device_id: assigned Device ID * * Returns: true when assignment metadata contains an exact location match. */ -bool mk_pci_get_assigned_identity(struct pci_bus *bus, int devfn, - u16 *vendor, u16 *device_id) +bool mk_pci_get_assigned_identity_bdf(unsigned int domain, unsigned int bus, + unsigned int devfn, u16 *vendor, + u16 *device_id) { - struct mk_pci_device *device = mk_pci_find_assigned(bus, devfn); + struct mk_pci_device *device; + + if (domain != (u16)domain || bus != (u8)bus || devfn != (u8)devfn) + return false; + device = mk_pci_find_assigned_bdf(domain, bus, devfn); if (!device) return false; From 630f9351237bdb7b2b125f5f0e4b09b8a7b24290 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Wed, 5 Aug 2026 10:35:52 +0300 Subject: [PATCH 09/13] multikernel: serialize CPU ownership transfers Protect CPU-set storage and serialize ownership transactions across instance creation and runtime hotplug. Keep the ownership lock short around bookkeeping and use the transaction mutex across remote waits. Reserve logical CPU 0 for host control so assignment and message forwarding always retain a live host endpoint. Signed-off-by: Nikolay Nikolaev --- include/linux/multikernel.h | 25 ++-- kernel/multikernel/cpuset.c | 257 ++++++++++++++++++++++++++++------- kernel/multikernel/hotplug.c | 201 +++++++++++++++++++-------- 3 files changed, 362 insertions(+), 121 deletions(-) diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index 0e595d6b9ece4f..eae49b3a3f32f3 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -15,6 +15,7 @@ #include #include #include +#include struct pci_bus; /** @@ -31,6 +32,7 @@ typedef u64 mk_phys_cpu_t; #define MK_PHYS_CPU_INVALID (~(mk_phys_cpu_t)0) struct mk_cpu_set { + raw_spinlock_t lock; unsigned int nr; /* Entries in use */ unsigned int cap; /* Allocated capacity */ mk_phys_cpu_t *ids; @@ -45,26 +47,15 @@ bool mk_cpu_set_del(struct mk_cpu_set *set, mk_phys_cpu_t id); bool mk_cpu_set_contains(const struct mk_cpu_set *set, mk_phys_cpu_t id); int mk_cpu_set_copy(struct mk_cpu_set *dst, const struct mk_cpu_set *src); int mk_cpu_set_format(char *buf, size_t size, const struct mk_cpu_set *set); - -static inline unsigned int mk_cpu_set_count(const struct mk_cpu_set *set) -{ - return set ? set->nr : 0; -} - -static inline bool mk_cpu_set_empty(const struct mk_cpu_set *set) -{ - return mk_cpu_set_count(set) == 0; -} - -static inline mk_phys_cpu_t mk_cpu_set_first(const struct mk_cpu_set *set) -{ - return mk_cpu_set_empty(set) ? MK_PHYS_CPU_INVALID : set->ids[0]; -} +unsigned int mk_cpu_set_count(const struct mk_cpu_set *set); +bool mk_cpu_set_empty(const struct mk_cpu_set *set); +mk_phys_cpu_t mk_cpu_set_first(const struct mk_cpu_set *set); +bool mk_cpu_set_get(const struct mk_cpu_set *set, unsigned int index, + mk_phys_cpu_t *id); #define mk_cpu_set_for_each(i, id, set) \ for ((i) = 0; \ - (set) && (i) < (set)->nr && \ - (((id) = (set)->ids[(i)]), true); \ + mk_cpu_set_get((set), (i), &(id)); \ (i)++) /** diff --git a/kernel/multikernel/cpuset.c b/kernel/multikernel/cpuset.c index ad36a4f94fc65a..464f4a728051dc 100644 --- a/kernel/multikernel/cpuset.c +++ b/kernel/multikernel/cpuset.c @@ -13,31 +13,22 @@ #include #include -struct mk_cpu_set *mk_cpu_set_alloc(void) -{ - return kzalloc(sizeof(struct mk_cpu_set), GFP_KERNEL); -} - -void mk_cpu_set_free(struct mk_cpu_set *set) +static void mk_cpu_set_lock(const struct mk_cpu_set *set, unsigned long *flags) { - if (!set) - return; - - kfree(set->ids); - kfree(set); + raw_spin_lock_irqsave((raw_spinlock_t *)&set->lock, *flags); } -void mk_cpu_set_clear(struct mk_cpu_set *set) +static void mk_cpu_set_unlock(const struct mk_cpu_set *set, unsigned long flags) { - if (set) - set->nr = 0; + raw_spin_unlock_irqrestore((raw_spinlock_t *)&set->lock, flags); } -static int mk_cpu_set_index(const struct mk_cpu_set *set, mk_phys_cpu_t id) +static int mk_cpu_set_index_locked(const struct mk_cpu_set *set, + mk_phys_cpu_t id) { unsigned int i; - for (i = 0; set && i < set->nr; i++) { + for (i = 0; i < set->nr; i++) { if (set->ids[i] == id) return i; } @@ -45,9 +36,35 @@ static int mk_cpu_set_index(const struct mk_cpu_set *set, mk_phys_cpu_t id) return -1; } -bool mk_cpu_set_contains(const struct mk_cpu_set *set, mk_phys_cpu_t id) +struct mk_cpu_set *mk_cpu_set_alloc(void) { - return mk_cpu_set_index(set, id) >= 0; + struct mk_cpu_set *set; + + set = kzalloc(sizeof(*set), GFP_KERNEL); + if (set) + raw_spin_lock_init(&set->lock); + return set; +} + +void mk_cpu_set_free(struct mk_cpu_set *set) +{ + if (!set) + return; + + kfree(set->ids); + kfree(set); +} + +void mk_cpu_set_clear(struct mk_cpu_set *set) +{ + unsigned long flags; + + if (!set) + return; + + mk_cpu_set_lock(set, &flags); + set->nr = 0; + mk_cpu_set_unlock(set, flags); } /** @@ -61,64 +78,195 @@ bool mk_cpu_set_contains(const struct mk_cpu_set *set, mk_phys_cpu_t id) */ int mk_cpu_set_reserve(struct mk_cpu_set *set, unsigned int extra) { - unsigned int cap = set->nr + extra; - mk_phys_cpu_t *ids; + mk_phys_cpu_t *ids = NULL; + mk_phys_cpu_t *old_ids; + unsigned int cap; + unsigned long flags; - if (cap <= set->cap) - return 0; + if (!set) + return -EINVAL; + + for (;;) { + mk_cpu_set_lock(set, &flags); + cap = set->nr + extra; + if (cap <= set->cap) { + mk_cpu_set_unlock(set, flags); + kfree(ids); + return 0; + } + mk_cpu_set_unlock(set, flags); - cap = max_t(unsigned int, cap, 8); - ids = krealloc_array(set->ids, cap, sizeof(*ids), GFP_KERNEL); - if (!ids) - return -ENOMEM; + cap = max_t(unsigned int, cap, 8); + kfree(ids); + ids = kcalloc(cap, sizeof(*ids), GFP_KERNEL); + if (!ids) + return -ENOMEM; - set->ids = ids; - set->cap = cap; - return 0; + mk_cpu_set_lock(set, &flags); + if (set->nr + extra > cap) { + mk_cpu_set_unlock(set, flags); + continue; + } + if (cap <= set->cap) { + mk_cpu_set_unlock(set, flags); + kfree(ids); + return 0; + } + + memcpy(ids, set->ids, set->nr * sizeof(*ids)); + old_ids = set->ids; + set->ids = ids; + set->cap = cap; + mk_cpu_set_unlock(set, flags); + kfree(old_ids); + return 0; + } } /* Idempotent: adding an ID already in the set succeeds without effect */ int mk_cpu_set_add(struct mk_cpu_set *set, mk_phys_cpu_t id) { + unsigned long flags; int ret; - if (mk_cpu_set_contains(set, id)) - return 0; + if (!set) + return -EINVAL; - ret = mk_cpu_set_reserve(set, 1); - if (ret) - return ret; + for (;;) { + mk_cpu_set_lock(set, &flags); + if (mk_cpu_set_index_locked(set, id) >= 0) { + mk_cpu_set_unlock(set, flags); + return 0; + } + if (set->nr < set->cap) { + set->ids[set->nr++] = id; + mk_cpu_set_unlock(set, flags); + return 0; + } + mk_cpu_set_unlock(set, flags); - set->ids[set->nr++] = id; - return 0; + ret = mk_cpu_set_reserve(set, 1); + if (ret) + return ret; + } } bool mk_cpu_set_del(struct mk_cpu_set *set, mk_phys_cpu_t id) { - int idx = mk_cpu_set_index(set, id); + unsigned long flags; + int idx; - if (idx < 0) + if (!set) return false; + mk_cpu_set_lock(set, &flags); + idx = mk_cpu_set_index_locked(set, id); + if (idx < 0) { + mk_cpu_set_unlock(set, flags); + return false; + } + memmove(&set->ids[idx], &set->ids[idx + 1], (set->nr - idx - 1) * sizeof(set->ids[0])); set->nr--; + mk_cpu_set_unlock(set, flags); return true; } +bool mk_cpu_set_contains(const struct mk_cpu_set *set, mk_phys_cpu_t id) +{ + unsigned long flags; + bool found; + + if (!set) + return false; + + mk_cpu_set_lock(set, &flags); + found = mk_cpu_set_index_locked(set, id) >= 0; + mk_cpu_set_unlock(set, flags); + return found; +} + +unsigned int mk_cpu_set_count(const struct mk_cpu_set *set) +{ + unsigned long flags; + unsigned int nr; + + if (!set) + return 0; + + mk_cpu_set_lock(set, &flags); + nr = set->nr; + mk_cpu_set_unlock(set, flags); + return nr; +} + +bool mk_cpu_set_empty(const struct mk_cpu_set *set) +{ + return mk_cpu_set_count(set) == 0; +} + +mk_phys_cpu_t mk_cpu_set_first(const struct mk_cpu_set *set) +{ + unsigned long flags; + mk_phys_cpu_t id; + + if (!set) + return MK_PHYS_CPU_INVALID; + + mk_cpu_set_lock(set, &flags); + id = set->nr ? set->ids[0] : MK_PHYS_CPU_INVALID; + mk_cpu_set_unlock(set, flags); + return id; +} + +bool mk_cpu_set_get(const struct mk_cpu_set *set, unsigned int index, + mk_phys_cpu_t *id) +{ + unsigned long flags; + bool found = false; + + if (!set || !id) + return false; + + mk_cpu_set_lock(set, &flags); + if (index < set->nr) { + *id = set->ids[index]; + found = true; + } + mk_cpu_set_unlock(set, flags); + return found; +} + int mk_cpu_set_copy(struct mk_cpu_set *dst, const struct mk_cpu_set *src) { - unsigned int nr = mk_cpu_set_count(src); + unsigned long src_flags; + unsigned long dst_flags; + unsigned int nr; int ret; - dst->nr = 0; - ret = mk_cpu_set_reserve(dst, nr); - if (ret) - return ret; + if (!dst || !src) + return -EINVAL; - memcpy(dst->ids, src->ids, nr * sizeof(dst->ids[0])); - dst->nr = nr; - return 0; + for (;;) { + nr = mk_cpu_set_count(src); + ret = mk_cpu_set_reserve(dst, nr); + if (ret) + return ret; + + mk_cpu_set_lock(src, &src_flags); + if (src->nr > dst->cap) { + mk_cpu_set_unlock(src, src_flags); + continue; + } + + mk_cpu_set_lock(dst, &dst_flags); + memcpy(dst->ids, src->ids, src->nr * sizeof(dst->ids[0])); + dst->nr = src->nr; + mk_cpu_set_unlock(dst, dst_flags); + mk_cpu_set_unlock(src, src_flags); + return 0; + } } /** @@ -127,22 +275,31 @@ int mk_cpu_set_copy(struct mk_cpu_set *dst, const struct mk_cpu_set *src) * @size: Buffer size * @set: Set to format * - * Writes "none" for an empty set, a comma-separated list of physical + * Writes none for an empty set, a comma-separated list of physical * IDs otherwise. Output is truncated to @size. Returns the number of * characters written. */ int mk_cpu_set_format(char *buf, size_t size, const struct mk_cpu_set *set) { + unsigned long flags; unsigned int i; int len = 0; - if (mk_cpu_set_empty(set)) + if (!buf || !size) + return 0; + if (!set) return scnprintf(buf, size, "none"); - for (i = 0; i < set->nr; i++) { + mk_cpu_set_lock(set, &flags); + if (!set->nr) { + mk_cpu_set_unlock(set, flags); + return scnprintf(buf, size, "none"); + } + + for (i = 0; i < set->nr && len < size; i++) { len += scnprintf(buf + len, size - len, "%s%llu", i ? "," : "", set->ids[i]); } - + mk_cpu_set_unlock(set, flags); return len; } diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index c834c338c06f78..24fca1d855e17c 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -77,25 +77,6 @@ struct mk_cpu_hotplug_work { u32 operation; /* MK_RES_CPU_ADD or MK_RES_CPU_REMOVE */ }; -/* - * Ownership tracking for this kernel's own hotplug: root_instance->cpus - * is the set of CPUs this kernel owns, in the host and in spawn kernels - * alike. The assignable-pool bookkeeping (mk_cpu_pool) is not done here; - * it belongs to the mk_send_cpu_* initiator paths of the kernel that - * manages the pool. - */ -static void mk_account_cpu_online(mk_phys_cpu_t cpu_id) -{ - if (root_instance->cpus && mk_cpu_set_add(root_instance->cpus, cpu_id)) - pr_warn("Multikernel hotplug: Failed to track CPU %llu\n", - cpu_id); -} - -static void mk_account_cpu_offline(mk_phys_cpu_t cpu_id) -{ - mk_cpu_set_del(root_instance->cpus, cpu_id); -} - /** * Search present CPUs (not possible CPUs) to find the logical CPU with matching * physical ID. Using present CPUs is important because topology can change during @@ -124,6 +105,7 @@ static int mk_do_cpu_add(mk_phys_cpu_t cpu_id, u32 numa_node, u32 flags) pr_info("Multikernel hotplug: Adding CPU %llu (numa=%u, flags=0x%x)\n", cpu_id, numa_node, flags); + mk_cpu_transaction_lock(); logical_cpu = mk_cpu_to_logical(cpu_id); if (logical_cpu < 0) { /* @@ -136,7 +118,8 @@ static int mk_do_cpu_add(mk_phys_cpu_t cpu_id, u32 numa_node, u32 flags) if (logical_cpu < 0) { pr_err("Multikernel hotplug: CPU %llu is not in this kernel's pool\n", cpu_id); - return -ENODEV; + ret = -ENODEV; + goto unlock_transaction; } set_cpu_present(logical_cpu, true); } @@ -144,18 +127,37 @@ static int mk_do_cpu_add(mk_phys_cpu_t cpu_id, u32 numa_node, u32 flags) if (cpu_online(logical_cpu)) { pr_warn("Multikernel hotplug: CPU %d (phys %llu) already online\n", logical_cpu, cpu_id); - mk_account_cpu_online(cpu_id); - return 0; + mk_cpu_ownership_lock(); + if (root_instance->cpus) { + ret = mk_cpu_set_add(root_instance->cpus, cpu_id); + if (ret) + pr_warn("Multikernel hotplug: Failed to track CPU %llu in root pool\n", + cpu_id); + } + mk_cpu_ownership_unlock(); + ret = 0; + goto unlock_transaction; } + mk_cpu_ownership_lock(); + if (root_instance->cpus) { + ret = mk_cpu_set_reserve(root_instance->cpus, 1); + if (ret) { + mk_cpu_ownership_unlock(); + goto unlock_transaction; + } + } + mk_cpu_ownership_unlock(); + if (!get_cpu_device(logical_cpu)) { struct cpu *c = &per_cpu(cpu_devices, logical_cpu); + c->hotpluggable = true; ret = register_cpu(c, logical_cpu); if (ret) { pr_err("Multikernel hotplug: Failed to register CPU %d: %d\n", logical_cpu, ret); - return ret; + goto unlock_transaction; } } @@ -163,10 +165,19 @@ static int mk_do_cpu_add(mk_phys_cpu_t cpu_id, u32 numa_node, u32 flags) if (ret < 0) { pr_err("Multikernel hotplug: Failed to add CPU %d (phys %llu): %d\n", logical_cpu, cpu_id, ret); - return ret; + goto unlock_transaction; } - mk_account_cpu_online(cpu_id); + mk_cpu_ownership_lock(); + if (root_instance->cpus) { + ret = mk_cpu_set_add(root_instance->cpus, cpu_id); + if (ret) + pr_warn("Multikernel hotplug: Failed to track CPU %llu in root pool\n", + cpu_id); + } + mk_cpu_ownership_unlock(); + if (ret) + goto unlock_transaction; /* Track the operation for potential rollback */ op = kzalloc(sizeof(*op), GFP_KERNEL); @@ -183,7 +194,10 @@ static int mk_do_cpu_add(mk_phys_cpu_t cpu_id, u32 numa_node, u32 flags) pr_info("Multikernel hotplug: Successfully added CPU %d (phys %llu)\n", logical_cpu, cpu_id); - return 0; + ret = 0; +unlock_transaction: + mk_cpu_transaction_unlock(); + return ret; } static int mk_do_cpu_remove(mk_phys_cpu_t cpu_id) @@ -191,25 +205,45 @@ static int mk_do_cpu_remove(mk_phys_cpu_t cpu_id) int logical_cpu; int ret; struct mk_hotplug_op *op; + bool tracked; + mk_cpu_transaction_lock(); logical_cpu = mk_cpu_to_logical(cpu_id); if (logical_cpu < 0) { pr_err("Multikernel hotplug: Physical CPU %llu not found\n", cpu_id); - return -ENODEV; + ret = -ENODEV; + goto unlock_transaction; } + mk_cpu_ownership_lock(); + tracked = root_instance->cpus && + mk_cpu_set_contains(root_instance->cpus, cpu_id); + if (!cpu_online(logical_cpu)) { pr_warn("Multikernel hotplug: CPU %d (phys %llu) already offline\n", logical_cpu, cpu_id); - mk_account_cpu_offline(cpu_id); - return 0; + mk_cpu_set_del(root_instance->cpus, cpu_id); + mk_cpu_ownership_unlock(); + ret = 0; + goto unlock_transaction; + } + + if (!tracked) { + pr_err("Multikernel hotplug: CPU %llu is not tracked in root pool\n", + cpu_id); + mk_cpu_ownership_unlock(); + ret = -EINVAL; + goto unlock_transaction; } /* Don't allow removing CPU 0 (boot processor) */ if (logical_cpu == 0) { pr_err("Multikernel hotplug: Cannot remove boot CPU\n"); - return -EINVAL; + mk_cpu_ownership_unlock(); + ret = -EINVAL; + goto unlock_transaction; } + mk_cpu_ownership_unlock(); mk_set_pool_cpu(logical_cpu, true); @@ -218,10 +252,12 @@ static int mk_do_cpu_remove(mk_phys_cpu_t cpu_id) pr_err("Multikernel hotplug: Failed to remove CPU %d (phys %llu): %d\n", logical_cpu, cpu_id, ret); mk_set_pool_cpu(logical_cpu, false); - return ret; + goto unlock_transaction; } - mk_account_cpu_offline(cpu_id); + mk_cpu_ownership_lock(); + mk_cpu_set_del(root_instance->cpus, cpu_id); + mk_cpu_ownership_unlock(); /* * Clear CPU from present mask to prevent host kernel from trying @@ -244,7 +280,10 @@ static int mk_do_cpu_remove(mk_phys_cpu_t cpu_id) pr_info("Multikernel hotplug: Successfully removed CPU %d (phys %llu)\n", logical_cpu, cpu_id); - return 0; + ret = 0; +unlock_transaction: + mk_cpu_transaction_unlock(); + return ret; } static void mk_cpu_add_work_fn(struct work_struct *work) @@ -1050,6 +1089,7 @@ int mk_send_cpu_remove(int instance_id, mk_phys_cpu_t cpu_id) if (target_instance->state != MK_STATE_ACTIVE) { struct mk_cpu_set cpus = { .nr = 1, .cap = 1, .ids = &cpu_id }; + raw_spin_lock_init(&cpus.lock); /* * A CPU the instance has already run on is parked on that * instance's context. Bring it back to the host slot before @@ -1069,22 +1109,40 @@ int mk_send_cpu_remove(int instance_id, mk_phys_cpu_t cpu_id) goto out; } + mk_cpu_transaction_lock(); pending = mk_msg_pending_add(MK_MSG_RESOURCE, MK_RES_CPU_REMOVE, cpu_id); if (!pending) { ret = -ENOMEM; - goto out; + goto unlock_transaction; } - ret = mk_send_message(instance_id, MK_MSG_RESOURCE, MK_RES_CPU_REMOVE, - &payload, sizeof(payload)); + mk_cpu_ownership_lock(); + if (!mk_cpu_set_contains(target_instance->cpus, cpu_id)) { + pr_err("Multikernel hotplug: CPU %llu not assigned to instance %d\n", + cpu_id, instance_id); + mk_msg_pending_wait(pending, 0); + ret = -EINVAL; + goto unlock_ownership; + } + + ret = mk_cpu_set_reserve(mk_cpu_pool, 1); + if (ret) { + mk_msg_pending_wait(pending, 0); + goto unlock_ownership; + } + mk_cpu_ownership_unlock(); + + ret = mk_send_message_to_instance(target_instance, MK_MSG_RESOURCE, + MK_RES_CPU_REMOVE, &payload, + sizeof(payload)); if (ret < 0) { mk_msg_pending_wait(pending, 0); /* Immediate cleanup */ - goto out; + goto unlock_transaction; } ret = mk_msg_pending_wait(pending, 10000); if (ret < 0) - goto out; + goto unlock_transaction; /* * The spawn kernel parked the CPU on its own context when it went @@ -1098,15 +1156,27 @@ int mk_send_cpu_remove(int instance_id, mk_phys_cpu_t cpu_id) if (ret < 0) { pr_err("Multikernel hotplug: CPU %llu offline in instance %d but not reparked to host: %d\n", cpu_id, instance_id, ret); - goto out; + goto unlock_transaction; } - mk_cpu_set_del(target_instance->cpus, cpu_id); - if (!mk_cpu_pool || mk_cpu_set_add(mk_cpu_pool, cpu_id)) + mk_cpu_ownership_lock(); + if (!mk_cpu_set_contains(target_instance->cpus, cpu_id)) { + ret = -ESTALE; + goto unlock_ownership; + } + ret = mk_cpu_set_add(mk_cpu_pool, cpu_id); + if (ret) { pr_warn("Multikernel hotplug: Failed to track CPU %llu in pool\n", cpu_id); + goto unlock_ownership; + } + mk_cpu_set_del(target_instance->cpus, cpu_id); ret = 0; +unlock_ownership: + mk_cpu_ownership_unlock(); +unlock_transaction: + mk_cpu_transaction_unlock(); out: mk_instance_put(target_instance); return ret; @@ -1160,26 +1230,36 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl instance_id); return -ENODEV; } + if (arch_cpu_from_physical_id(cpu_id) == 0) { + pr_err("Multikernel hotplug: CPU %llu is reserved for host control\n", + cpu_id); + ret = -EINVAL; + goto out; + } /* For non-running instances, transfer CPU from root using existing API */ if (target_instance->state != MK_STATE_ACTIVE) { struct mk_cpu_set cpus = { .nr = 1, .cap = 1, .ids = &cpu_id }; + raw_spin_lock_init(&cpus.lock); ret = mk_instance_transfer_cpus(target_instance, &cpus); goto out; } - /* - * Only a CPU from the assignable pool is parked on the host slot; - * publishing a wakeup for any other CPU can only time out. - */ + mk_cpu_transaction_lock(); + mk_cpu_ownership_lock(); if (!mk_cpu_set_contains(mk_cpu_pool, cpu_id)) { - pr_err("Multikernel hotplug: CPU %llu is not in this kernel's pool\n", + pr_err("Multikernel hotplug: CPU %llu not available in the pool\n", cpu_id); ret = -EBUSY; - goto out; + goto unlock_ownership; } + ret = mk_cpu_set_reserve(target_instance->cpus, 1); + if (ret) + goto unlock_ownership; + mk_cpu_ownership_unlock(); + /* * The CPU is parked on the host slot, where the spawn kernel's * secondary wakeup cannot reach it. Point it at the instance's @@ -1189,22 +1269,23 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl if (ret < 0) { pr_err("Multikernel hotplug: Failed to repark CPU %llu to instance %d: %d\n", cpu_id, instance_id, ret); - goto out; + goto unlock_transaction; } pending = mk_msg_pending_add(MK_MSG_RESOURCE, MK_RES_CPU_ADD, cpu_id); if (!pending) { mk_repark_cpu_to_host(target_instance, cpu_id); ret = -ENOMEM; - goto out; + goto unlock_transaction; } - ret = mk_send_message(instance_id, MK_MSG_RESOURCE, MK_RES_CPU_ADD, - &payload, sizeof(payload)); + ret = mk_send_message_to_instance(target_instance, MK_MSG_RESOURCE, + MK_RES_CPU_ADD, &payload, + sizeof(payload)); if (ret < 0) { mk_msg_pending_wait(pending, 0); /* Immediate cleanup */ mk_repark_cpu_to_host(target_instance, cpu_id); - goto out; + goto unlock_transaction; } ret = mk_msg_pending_wait(pending, 10000); @@ -1216,15 +1297,27 @@ int mk_send_cpu_add(int instance_id, mk_phys_cpu_t cpu_id, u32 numa_node, u32 fl * watching the context and this times out harmlessly. */ mk_repark_cpu_to_host(target_instance, cpu_id); - goto out; + goto unlock_transaction; } - if (mk_cpu_set_add(target_instance->cpus, cpu_id)) + mk_cpu_ownership_lock(); + if (!mk_cpu_set_contains(mk_cpu_pool, cpu_id)) { + ret = -ESTALE; + goto unlock_ownership; + } + ret = mk_cpu_set_add(target_instance->cpus, cpu_id); + if (ret) { pr_warn("Multikernel hotplug: Failed to track CPU %llu in instance %d\n", cpu_id, instance_id); + goto unlock_ownership; + } mk_cpu_set_del(mk_cpu_pool, cpu_id); ret = 0; +unlock_ownership: + mk_cpu_ownership_unlock(); +unlock_transaction: + mk_cpu_transaction_unlock(); out: mk_instance_put(target_instance); return ret; From d53ae50962c988f38741756363603584c2129ff8 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Tue, 4 Aug 2026 11:04:33 +0300 Subject: [PATCH 10/13] pci/multikernel: mediate config access through host Keep PCI configuration ownership in the host by translating spawn reads and writes into request/response messages. Tag each request with the sender instance and authorize queued work against the actual sending CPU under the ownership lock, without global route generations. Signed-off-by: Nikolay Nikolaev --- arch/x86/multikernel/pci.c | 169 ++++++++++++++++++++++++++++++++---- include/linux/multikernel.h | 23 +++++ kernel/multikernel/ipi.c | 5 ++ kernel/multikernel/pci.c | 154 ++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+), 18 deletions(-) diff --git a/arch/x86/multikernel/pci.c b/arch/x86/multikernel/pci.c index b20a02e53ca176..886eb44bbe4f82 100644 --- a/arch/x86/multikernel/pci.c +++ b/arch/x86/multikernel/pci.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -25,9 +26,130 @@ struct mk_mmcfg_snapshot { size_t count; }; -static const struct pci_raw_ops *mk_pci_native_raw_ops; -static const struct pci_raw_ops *mk_pci_native_raw_ext_ops; static bool mk_pci_roots_ready; +static atomic64_t mk_pci_request_id = ATOMIC64_INIT(0); +static atomic64_t mk_pci_cfg_count = ATOMIC64_INIT(0); +static atomic64_t mk_pci_cfg_total_ns = ATOMIC64_INIT(0); +static atomic64_t mk_pci_cfg_max_ns = ATOMIC64_INIT(0); +static LIST_HEAD(mk_pci_cfg_pending); +static DEFINE_RAW_SPINLOCK(mk_pci_cfg_pending_lock); +static struct mk_instance *mk_pci_host_instance; + +struct mk_pci_cfg_pending { + struct list_head node; + u64 request_id; + s32 status; + u32 value; + bool done; +}; + +static bool mk_pci_message_from_host(mk_phys_cpu_t sender_cpu) +{ + return mk_pci_host_instance && + mk_cpu_set_contains(mk_pci_host_instance->cpus, sender_cpu); +} + +static void mk_pci_cfg_response_handler(u32 msg_type, u32 subtype, + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx) +{ + struct mk_pci_cfg_response *response = payload; + struct mk_pci_cfg_pending *pending; + unsigned long flags; + + if (msg_type != MK_MSG_PCI || subtype != MK_PCI_CFG_RESPONSE || + !mk_pci_message_from_host(sender_cpu) || + payload_len != sizeof(*response)) + return; + + raw_spin_lock_irqsave(&mk_pci_cfg_pending_lock, flags); + list_for_each_entry(pending, &mk_pci_cfg_pending, node) { + if (pending->request_id != response->request_id) + continue; + pending->status = response->status; + pending->value = response->value; + /* Publish the response fields before waking the polling CPU. */ + smp_store_release(&pending->done, true); + break; + } + raw_spin_unlock_irqrestore(&mk_pci_cfg_pending_lock, flags); +} + +static void mk_pci_record_latency(u64 start) +{ + u64 elapsed = ktime_get_mono_fast_ns() - start; + u64 old_max = atomic64_read(&mk_pci_cfg_max_ns); + + atomic64_inc(&mk_pci_cfg_count); + atomic64_add(elapsed, &mk_pci_cfg_total_ns); + while (elapsed > old_max) { + u64 previous = atomic64_cmpxchg(&mk_pci_cfg_max_ns, old_max, + elapsed); + + if (previous == old_max) + break; + old_max = previous; + } +} + +static int mk_pci_remote_config(unsigned int domain, unsigned int bus, + unsigned int devfn, int where, int size, + bool write, u32 *value) +{ + struct mk_pci_cfg_request request = { + .request_id = atomic64_inc_return(&mk_pci_request_id), + .sender_instance_id = root_instance ? root_instance->id : -1, + .domain = domain, + .bus = bus, + .devfn = devfn, + .reg = where, + .len = size, + .write = write, + .value = *value, + }; + struct mk_pci_cfg_pending pending = { + .request_id = request.request_id, + .status = PCIBIOS_SET_FAILED, + .value = ~0U, + }; + unsigned long flags; + u64 start = ktime_get_mono_fast_ns(); + u64 deadline = start + NSEC_PER_SEC; + int ret; + + raw_spin_lock_irqsave(&mk_pci_cfg_pending_lock, flags); + list_add_tail(&pending.node, &mk_pci_cfg_pending); + raw_spin_unlock_irqrestore(&mk_pci_cfg_pending_lock, flags); + + ret = mk_send_message(0, MK_MSG_PCI, MK_PCI_CFG_REQUEST, + &request, sizeof(request)); + if (ret) + goto out; + + /* Pairs with the response handler's publication of status and value. */ + while (!smp_load_acquire(&pending.done)) { + mk_poll_ipi_messages(); + if (ktime_get_mono_fast_ns() >= deadline) { + ret = -ETIMEDOUT; + goto out; + } + cpu_relax(); + } + ret = pending.status; + if (!write) + *value = pending.value; + mk_pci_record_latency(start); +out: + raw_spin_lock_irqsave(&mk_pci_cfg_pending_lock, flags); + list_del(&pending.node); + raw_spin_unlock_irqrestore(&mk_pci_cfg_pending_lock, flags); + if (ret < 0) { + pr_err_ratelimited("Multikernel PCI config request timed out or failed to send: %d\n", + ret); + return PCIBIOS_SET_FAILED; + } + return ret; +} static bool mk_mmcfg_snapshot_contains(const struct mk_mmcfg_snapshot *snapshot, u16 segment, u8 bus) @@ -58,8 +180,7 @@ static bool mk_pci_identity_read(u16 vendor, u16 device, int where, int size, return true; } -static int mk_pci_raw_read(const struct pci_raw_ops *native, - unsigned int domain, unsigned int bus, +static int mk_pci_raw_read(unsigned int domain, unsigned int bus, unsigned int devfn, int where, int size, u32 *value) { @@ -73,46 +194,43 @@ static int mk_pci_raw_read(const struct pci_raw_ops *native, if (mk_pci_identity_read(vendor, device, where, size, value)) return PCIBIOS_SUCCESSFUL; - return native->read(domain, bus, devfn, where, size, value); + return mk_pci_remote_config(domain, bus, devfn, where, size, false, + value); } -static int mk_pci_raw_write(const struct pci_raw_ops *native, - unsigned int domain, unsigned int bus, +static int mk_pci_raw_write(unsigned int domain, unsigned int bus, unsigned int devfn, int where, int size, u32 value) { if (!mk_pci_get_assigned_identity_bdf(domain, bus, devfn, NULL, NULL)) return PCIBIOS_DEVICE_NOT_FOUND; - return native->write(domain, bus, devfn, where, size, value); + return mk_pci_remote_config(domain, bus, devfn, where, size, true, + &value); } static int mk_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn, int where, int size, u32 *value) { - return mk_pci_raw_read(mk_pci_native_raw_ops, domain, bus, devfn, - where, size, value); + return mk_pci_raw_read(domain, bus, devfn, where, size, value); } static int mk_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn, int where, int size, u32 value) { - return mk_pci_raw_write(mk_pci_native_raw_ops, domain, bus, devfn, - where, size, value); + return mk_pci_raw_write(domain, bus, devfn, where, size, value); } static int mk_pci_ext_read(unsigned int domain, unsigned int bus, unsigned int devfn, int where, int size, u32 *value) { - return mk_pci_raw_read(mk_pci_native_raw_ext_ops, domain, bus, devfn, - where, size, value); + return mk_pci_raw_read(domain, bus, devfn, where, size, value); } static int mk_pci_ext_write(unsigned int domain, unsigned int bus, unsigned int devfn, int where, int size, u32 value) { - return mk_pci_raw_write(mk_pci_native_raw_ext_ops, domain, bus, devfn, - where, size, value); + return mk_pci_raw_write(domain, bus, devfn, where, size, value); } static const struct pci_raw_ops mk_pci_filtered_raw_ops = { @@ -197,6 +315,11 @@ static int __init x86_multikernel_pci_arch_init(void) pr_err("Multikernel has no restored PCI host bridge metadata\n"); return 0; } + mk_pci_host_instance = mk_instance_find(0); + if (!mk_pci_host_instance) { + pr_err("Multikernel has no restored host instance for PCI control\n"); + return 0; + } if (!list_empty(&pci_mmcfg_list)) { pr_err("Multikernel PCI host bridge list was not empty before restore\n"); return 0; @@ -232,11 +355,14 @@ static int __init x86_multikernel_pci_arch_init(void) pr_err("Multikernel failed to map restored PCI ECAM windows\n"); return 0; } + if (mk_register_msg_handler(MK_MSG_PCI, mk_pci_cfg_response_handler, + NULL)) { + pr_err("Multikernel failed to register PCI control-plane response handler\n"); + return 0; + } raw_pci_ops = &pci_mmcfg; raw_pci_ext_ops = &pci_mmcfg; - mk_pci_native_raw_ops = raw_pci_ops; - mk_pci_native_raw_ext_ops = raw_pci_ext_ops; raw_pci_ops = &mk_pci_filtered_raw_ops; raw_pci_ext_ops = &mk_pci_filtered_raw_ext_ops; mk_pci_roots_ready = true; @@ -299,6 +425,13 @@ static int __init x86_multikernel_pci_init(void) panic("Multikernel failed to scan synthetic PCI root %04x:%02x: %d", bridge->segment, bridge->bus_start, ret); } + if (atomic64_read(&mk_pci_cfg_count)) { + u64 count = atomic64_read(&mk_pci_cfg_count); + + pr_notice("Multikernel PCI control plane: %llu config requests, average %llu ns, max %llu ns\n", + count, atomic64_read(&mk_pci_cfg_total_ns) / count, + atomic64_read(&mk_pci_cfg_max_ns)); + } /* Suppress legacy bus 0 probing after every assigned root is present. */ return 0; diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index eae49b3a3f32f3..af9103426e9406 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -172,6 +172,7 @@ void mk_ipi_ring_drop_pending(void); #define MK_MSG_SYSTEM 0x3000 #define MK_MSG_USER 0x4000 #define MK_MSG_NETWORK 0x5000 +#define MK_MSG_PCI 0x6000 /* I/O interrupt forwarding subtypes */ #define MK_IO_IRQ_FORWARD (MK_MSG_IO + 1) @@ -198,6 +199,9 @@ void mk_ipi_ring_drop_pending(void); /* Network/vsock subtypes */ #define MK_NET_VSOCK_PKT (MK_MSG_NETWORK + 1) /* vsock packet */ #define MK_NET_DATA_READY (MK_MSG_NETWORK + 2) /* Data available notification */ +/* Host-mediated PCI control-plane subtypes */ +#define MK_PCI_CFG_REQUEST (MK_MSG_PCI + 1) +#define MK_PCI_CFG_RESPONSE (MK_MSG_PCI + 2) /** * Core message structure @@ -222,6 +226,24 @@ struct mk_io_irq_payload { u32 flags; /* Control flags (priority, etc.) */ }; +struct mk_pci_cfg_request { + u64 request_id; + s32 sender_instance_id; + u16 domain; + u8 bus; + u8 devfn; + u16 reg; + u8 len; + u8 write; + u32 value; +}; + +struct mk_pci_cfg_response { + u64 request_id; + s32 status; + u32 value; +}; + /* IRQ control flags */ #define MK_IRQ_HIGH_PRIORITY 0x01 #define MK_IRQ_LOW_LATENCY 0x02 @@ -331,6 +353,7 @@ int mk_register_msg_handler(u32 msg_type, mk_msg_handler_t handler, void *ctx); * Returns 0 on success, negative error code on failure */ int mk_unregister_msg_handler(u32 msg_type, mk_msg_handler_t handler); +void mk_poll_ipi_messages(void); /* Pending message tracking for request-response pattern */ struct mk_pending_msg *mk_msg_pending_add(u32 msg_type, u32 operation, u64 resource_id); diff --git a/kernel/multikernel/ipi.c b/kernel/multikernel/ipi.c index 51997c31466fd2..dfc0740a65b6d2 100644 --- a/kernel/multikernel/ipi.c +++ b/kernel/multikernel/ipi.c @@ -296,6 +296,11 @@ static void mk_ipi_drain_ring(void) } } +void mk_poll_ipi_messages(void) +{ + mk_ipi_drain_ring(); +} + /** * multikernel_interrupt_handler - Handle the multikernel IPI * diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index b0025bb1e1fec1..d18868e3713419 100644 --- a/kernel/multikernel/pci.c +++ b/kernel/multikernel/pci.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,13 @@ static DEFINE_MUTEX(mk_pci_lease_mutex); static DEFINE_SPINLOCK(mk_pci_active_lock); static LIST_HEAD(mk_pci_active_assignments); static bool mk_pci_notifier_registered; +static bool mk_pci_control_registered; + +struct mk_pci_cfg_work { + struct work_struct work; + struct mk_pci_cfg_request request; + mk_phys_cpu_t sender_cpu; +}; static bool mk_pci_device_live(struct pci_dev *pdev) { @@ -127,6 +135,135 @@ mk_pci_find_assignment(struct mk_instance *instance, u16 domain, u8 bus, return NULL; } +static bool mk_pci_request_route_stale(struct mk_instance *instance, + mk_phys_cpu_t sender_cpu) +{ + if (!instance || instance == root_instance) + return true; + if (READ_ONCE(instance->state) != MK_STATE_ACTIVE) + return true; + + mk_cpu_ownership_assert_held(); + return !mk_cpu_set_contains(instance->cpus, sender_cpu); +} + +static int mk_pci_config_access(struct mk_instance *instance, + const struct mk_pci_cfg_request *request, + u32 *value) +{ + struct mk_pci_assignment *assignment; + struct pci_dev *vf; + int ret; + + if (request->len != 1 && request->len != 2 && request->len != 4) + return PCIBIOS_BAD_REGISTER_NUMBER; + if (request->reg > PCI_CFG_SPACE_EXP_SIZE - request->len) + return PCIBIOS_BAD_REGISTER_NUMBER; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + assignment = mk_pci_find_assignment(instance, request->domain, + request->bus, request->devfn); + if (!assignment || !assignment->assigned || + !mk_pci_device_live(assignment->vf)) { + ret = PCIBIOS_DEVICE_NOT_FOUND; + goto out; + } + + vf = assignment->vf; + if (request->write) { + switch (request->len) { + case 1: + ret = pci_write_config_byte(vf, request->reg, + request->value); + break; + case 2: + ret = pci_write_config_word(vf, request->reg, + request->value); + break; + default: + ret = pci_write_config_dword(vf, request->reg, + request->value); + break; + } + } else { + switch (request->len) { + case 1: { + u8 data; + + ret = pci_read_config_byte(vf, request->reg, &data); + *value = data; + break; + } + case 2: { + u16 data; + + ret = pci_read_config_word(vf, request->reg, &data); + *value = data; + break; + } + default: + ret = pci_read_config_dword(vf, request->reg, value); + break; + } + } +out: + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + return ret; +} + +static void mk_pci_cfg_work_fn(struct work_struct *work) +{ + struct mk_pci_cfg_work *cfg_work = + container_of(work, struct mk_pci_cfg_work, work); + struct mk_pci_cfg_response response = { + .request_id = cfg_work->request.request_id, + .value = ~0U, + }; + struct mk_instance *instance; + + instance = mk_instance_find(cfg_work->request.sender_instance_id); + if (!instance) + goto out; + + mk_cpu_ownership_lock(); + response.status = mk_pci_request_route_stale(instance, + cfg_work->sender_cpu) ? -ESTALE : + mk_pci_config_access(instance, &cfg_work->request, + &response.value); + mk_cpu_ownership_unlock(); + if (mk_send_message_to_instance(instance, MK_MSG_PCI, + MK_PCI_CFG_RESPONSE, &response, + sizeof(response))) + pr_warn_ratelimited("Failed to return PCI config response to instance %d\n", + instance->id); + mk_instance_put(instance); +out: + kfree(cfg_work); +} + +static void mk_pci_control_msg_handler(u32 msg_type, u32 subtype, + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx) +{ + struct mk_pci_cfg_work *work; + + if (msg_type != MK_MSG_PCI || subtype != MK_PCI_CFG_REQUEST || + payload_len != sizeof(work->request)) + return; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (!work) + return; + INIT_WORK(&work->work, mk_pci_cfg_work_fn); + memcpy(&work->request, payload, sizeof(work->request)); + work->sender_cpu = sender_cpu; + schedule_work(&work->work); +} + static void mk_pci_assignment_failure_work(struct work_struct *work) { struct mk_pci_assignment *assignment = @@ -1235,11 +1372,28 @@ int mk_pci_lease_system_init(void) return ret; } mk_pci_notifier_registered = true; + if (root_instance && root_instance->id == 0) { + ret = mk_register_msg_handler(MK_MSG_PCI, + mk_pci_control_msg_handler, NULL); + if (ret) { + bus_unregister_notifier(&pci_bus_type, + &mk_pci_bus_notifier); + mk_pci_notifier_registered = false; + mk_pci_iommu_system_cleanup(); + return ret; + } + mk_pci_control_registered = true; + } return 0; } void mk_pci_lease_system_cleanup(void) { + if (mk_pci_control_registered) { + mk_unregister_msg_handler(MK_MSG_PCI, + mk_pci_control_msg_handler); + mk_pci_control_registered = false; + } if (mk_pci_notifier_registered) { bus_unregister_notifier(&pci_bus_type, &mk_pci_bus_notifier); mk_pci_notifier_registered = false; From 1f56a40b0b299ff65acac40b018558b576632991 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Tue, 4 Aug 2026 11:14:41 +0300 Subject: [PATCH 11/13] pci/multikernel: keep MSI ownership in host Allocate and program assigned VF MSI and MSI-X vectors in the host and forward events over the Multikernel message path. Register vectors disabled, pin forwarding to the host-control CPU, bind local descriptors before host activation, reject pre-bind delivery, and unwind every partial allocation. Signed-off-by: Nikolay Nikolaev --- arch/x86/multikernel/pci.c | 283 +++++++++++++++++++++++-- drivers/pci/msi/irqdomain.c | 25 ++- drivers/pci/msi/msi.c | 10 + include/linux/multikernel.h | 99 +++++++++ kernel/multikernel/ipi.c | 12 ++ kernel/multikernel/pci.c | 410 +++++++++++++++++++++++++++++++++++- 6 files changed, 810 insertions(+), 29 deletions(-) diff --git a/arch/x86/multikernel/pci.c b/arch/x86/multikernel/pci.c index 886eb44bbe4f82..201b5cdf741b93 100644 --- a/arch/x86/multikernel/pci.c +++ b/arch/x86/multikernel/pci.c @@ -7,7 +7,11 @@ */ #include +#include +#include +#include #include +#include #include #include #include @@ -33,6 +37,8 @@ static atomic64_t mk_pci_cfg_total_ns = ATOMIC64_INIT(0); static atomic64_t mk_pci_cfg_max_ns = ATOMIC64_INIT(0); static LIST_HEAD(mk_pci_cfg_pending); static DEFINE_RAW_SPINLOCK(mk_pci_cfg_pending_lock); +static LIST_HEAD(mk_pci_irq_pending); +static DEFINE_RAW_SPINLOCK(mk_pci_irq_pending_lock); static struct mk_instance *mk_pci_host_instance; struct mk_pci_cfg_pending { @@ -43,36 +49,276 @@ struct mk_pci_cfg_pending { bool done; }; +struct mk_pci_irq_pending { + struct list_head node; + u64 request_id; + s32 status; + bool done; +}; + static bool mk_pci_message_from_host(mk_phys_cpu_t sender_cpu) { return mk_pci_host_instance && mk_cpu_set_contains(mk_pci_host_instance->cpus, sender_cpu); } +static void mk_pci_forward_irq_noop(struct irq_data *data) +{ +} + +static void mk_pci_forward_irq_write_msg(struct irq_data *data, + struct msi_msg *msg) +{ +} + +static struct irq_chip mk_pci_forward_irq_chip = { + .name = "multikernel-pci-forward", + .irq_ack = mk_pci_forward_irq_noop, + .irq_mask = pci_msi_mask_irq, + .irq_unmask = pci_msi_unmask_irq, + .irq_write_msi_msg = mk_pci_forward_irq_write_msg, +}; + +static void mk_pci_bind_local_irqs(unsigned int irq, unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) + irq_set_chip_and_handler(irq + i, &mk_pci_forward_irq_chip, + handle_edge_irq); +} + +static bool mk_pci_forward_irq_matches(const struct mk_io_irq_payload *irq, + struct irq_data **irq_data) +{ + struct irq_data *data = irq_get_irq_data(irq->irq_number); + struct msi_desc *desc; + struct pci_dev *dev; + unsigned int offset; + + if (!data) + return false; + desc = irq_data_get_msi_desc(data); + if (!desc || irq->vector < desc->msi_index) + return false; + + dev = msi_desc_to_pci_dev(desc); + offset = irq->vector - desc->msi_index; + if (offset >= desc->nvec_used || desc->irq + offset != irq->irq_number || + pci_domain_nr(dev->bus) != MK_PCI_IRQ_ID_DOMAIN(irq->device_id) || + dev->bus->number != MK_PCI_IRQ_ID_BUS(irq->device_id) || + dev->devfn != MK_PCI_IRQ_ID_DEVFN(irq->device_id)) + return false; + + *irq_data = data; + return true; +} + static void mk_pci_cfg_response_handler(u32 msg_type, u32 subtype, void *payload, u32 payload_len, mk_phys_cpu_t sender_cpu, void *ctx) { - struct mk_pci_cfg_response *response = payload; - struct mk_pci_cfg_pending *pending; + struct mk_pci_cfg_response *cfg_response = payload; unsigned long flags; - if (msg_type != MK_MSG_PCI || subtype != MK_PCI_CFG_RESPONSE || - !mk_pci_message_from_host(sender_cpu) || - payload_len != sizeof(*response)) + if (msg_type != MK_MSG_PCI || !mk_pci_message_from_host(sender_cpu)) return; - raw_spin_lock_irqsave(&mk_pci_cfg_pending_lock, flags); - list_for_each_entry(pending, &mk_pci_cfg_pending, node) { - if (pending->request_id != response->request_id) - continue; - pending->status = response->status; - pending->value = response->value; - /* Publish the response fields before waking the polling CPU. */ - smp_store_release(&pending->done, true); - break; + if (subtype == MK_PCI_CFG_RESPONSE && + payload_len == sizeof(*cfg_response)) { + struct mk_pci_cfg_pending *pending; + + raw_spin_lock_irqsave(&mk_pci_cfg_pending_lock, flags); + list_for_each_entry(pending, &mk_pci_cfg_pending, node) { + if (pending->request_id != cfg_response->request_id) + continue; + pending->status = cfg_response->status; + pending->value = cfg_response->value; + /* Publish response fields before waking the polling CPU. */ + smp_store_release(&pending->done, true); + break; + } + raw_spin_unlock_irqrestore(&mk_pci_cfg_pending_lock, flags); + } else if (subtype == MK_PCI_IRQ_RESPONSE && + payload_len == sizeof(struct mk_pci_irq_response)) { + struct mk_pci_irq_response *response = payload; + struct mk_pci_irq_pending *pending; + + raw_spin_lock_irqsave(&mk_pci_irq_pending_lock, flags); + list_for_each_entry(pending, &mk_pci_irq_pending, node) { + if (pending->request_id != response->request_id) + continue; + pending->status = response->status; + /* Publish status before waking the polling CPU. */ + smp_store_release(&pending->done, true); + break; + } + raw_spin_unlock_irqrestore(&mk_pci_irq_pending_lock, flags); } - raw_spin_unlock_irqrestore(&mk_pci_cfg_pending_lock, flags); +} + +static void mk_pci_irq_forward_handler(u32 msg_type, u32 subtype, + void *payload, u32 payload_len, + mk_phys_cpu_t sender_cpu, void *ctx) +{ + struct mk_io_irq_payload *irq = payload; + struct irq_data *irq_data; + struct pci_dev *dev; + + if (msg_type != MK_MSG_IO || subtype != MK_IO_IRQ_FORWARD || + payload_len != sizeof(*irq) || + !mk_pci_message_from_host(sender_cpu)) + return; + if (!mk_pci_forward_irq_matches(irq, &irq_data)) { + pr_warn_ratelimited("Rejected host-forwarded PCI IRQ %u with unmatched identity %#x vector %u\n", + irq->irq_number, irq->device_id, + irq->vector); + return; + } + + if (irq_data_get_irq_chip(irq_data) != &mk_pci_forward_irq_chip) { + dev = msi_desc_to_pci_dev(irq_data_get_msi_desc(irq_data)); + pr_warn_ratelimited("Rejected host-forwarded PCI IRQ %u for %s vector %u before local binding\n", + irq->irq_number, pci_name(dev), + irq->vector); + return; + } + + if (generic_handle_irq_safe(irq->irq_number)) + pr_warn_ratelimited("Failed to dispatch host-forwarded PCI IRQ %u\n", + irq->irq_number); +} + +static int mk_pci_send_irq_request(struct mk_pci_irq_request *request) +{ + struct mk_pci_irq_pending pending = { + .request_id = atomic64_inc_return(&mk_pci_request_id), + .status = -ETIMEDOUT, + }; + unsigned long flags; + u64 deadline = ktime_get_mono_fast_ns() + NSEC_PER_SEC; + int ret; + + request->request_id = pending.request_id; + request->sender_instance_id = root_instance ? root_instance->id : -1; + raw_spin_lock_irqsave(&mk_pci_irq_pending_lock, flags); + list_add_tail(&pending.node, &mk_pci_irq_pending); + raw_spin_unlock_irqrestore(&mk_pci_irq_pending_lock, flags); + + ret = mk_send_message(0, MK_MSG_PCI, MK_PCI_IRQ_REQUEST, + request, sizeof(*request)); + if (ret) + goto out; + + /* Pairs with the response handler's publication of status. */ + while (!smp_load_acquire(&pending.done)) { + mk_poll_ipi_messages(); + if (ktime_get_mono_fast_ns() >= deadline) { + ret = -ETIMEDOUT; + goto out; + } + cpu_relax(); + } + ret = pending.status; +out: + raw_spin_lock_irqsave(&mk_pci_irq_pending_lock, flags); + list_del(&pending.node); + raw_spin_unlock_irqrestore(&mk_pci_irq_pending_lock, flags); + return ret; +} + +bool mk_pci_msi_controlled(struct pci_dev *dev) +{ + return root_instance && root_instance->id != 0 && + mk_pci_get_assigned_identity_bdf(pci_domain_nr(dev->bus), + dev->bus->number, dev->devfn, + NULL, NULL); +} + +int mk_pci_msi_prepare(struct pci_dev *dev, int nvec, int type) +{ + struct mk_pci_irq_request request = { + .domain = pci_domain_nr(dev->bus), + .bus = dev->bus->number, + .devfn = dev->devfn, + .operation = MK_PCI_IRQ_SETUP, + .nr_vectors = nvec, + .msix = type == PCI_CAP_ID_MSIX, + }; + + return mk_pci_send_irq_request(&request); +} + +static int mk_pci_msi_bind(struct pci_dev *dev, unsigned int index, + unsigned int irq, unsigned int nvec, bool msix) +{ + struct mk_pci_irq_request request = { + .domain = pci_domain_nr(dev->bus), + .bus = dev->bus->number, + .devfn = dev->devfn, + .operation = MK_PCI_IRQ_BIND, + .vector = index, + .nr_vectors = nvec, + .msix = msix, + .local_irq = irq, + }; + + /* The local descriptor must be dispatchable before the host unmasks. */ + mk_pci_bind_local_irqs(irq, msix ? 1 : nvec); + return mk_pci_send_irq_request(&request); +} + +bool mk_pci_msi_write_msg(struct pci_dev *dev, unsigned int index, + unsigned int irq, unsigned int nvec, bool msix) +{ + int ret; + + if (!mk_pci_msi_controlled(dev)) + return false; + ret = mk_pci_msi_bind(dev, index, irq, nvec, msix); + if (ret) + pr_err_ratelimited("Failed to bind host-owned MSI vector for %s: %d\n", + pci_name(dev), ret); + return true; +} + +int mk_pci_msi_activate(struct pci_dev *dev) +{ + struct msi_desc *desc; + int ret; + + if (!mk_pci_msi_controlled(dev)) + return 0; + msi_for_each_desc(desc, &dev->dev, MSI_DESC_ALL) { + ret = mk_pci_msi_bind(dev, desc->msi_index, desc->irq, + desc->nvec_used, + desc->pci.msi_attrib.is_msix); + if (ret) { + pr_err("Failed to activate host-owned MSI vector %u for %s: %d\n", + desc->msi_index, pci_name(dev), ret); + return ret; + } + } + + return 0; +} + +void mk_pci_msi_teardown(struct pci_dev *dev) +{ + struct mk_pci_irq_request request = { + .domain = pci_domain_nr(dev->bus), + .bus = dev->bus->number, + .devfn = dev->devfn, + .operation = MK_PCI_IRQ_TEARDOWN, + }; + int ret; + + if (!mk_pci_msi_controlled(dev)) + return; + ret = mk_pci_send_irq_request(&request); + if (ret) + pr_warn("Failed to tear down host-owned MSI vectors for %s: %d\n", + pci_name(dev), ret); } static void mk_pci_record_latency(u64 start) @@ -360,6 +606,13 @@ static int __init x86_multikernel_pci_arch_init(void) pr_err("Multikernel failed to register PCI control-plane response handler\n"); return 0; } + if (mk_register_msg_handler(MK_MSG_IO, mk_pci_irq_forward_handler, + NULL)) { + mk_unregister_msg_handler(MK_MSG_PCI, + mk_pci_cfg_response_handler); + pr_err("Multikernel failed to register PCI IRQ forwarding handler\n"); + return 0; + } raw_pci_ops = &pci_mmcfg; raw_pci_ext_ops = &pci_mmcfg; diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c index a329060287b5b7..38a2c3a97db411 100644 --- a/drivers/pci/msi/irqdomain.c +++ b/drivers/pci/msi/irqdomain.c @@ -4,6 +4,7 @@ */ #include #include +#include #include #include "msi.h" @@ -11,18 +12,36 @@ int pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) { struct irq_domain *domain; + bool controlled = mk_pci_msi_controlled(dev); + int ret; + + if (controlled) { + ret = mk_pci_msi_prepare(dev, nvec, type); + if (ret) + return ret; + } domain = dev_get_msi_domain(&dev->dev); if (domain && irq_domain_is_hierarchy(domain)) - return msi_domain_alloc_irqs_all_locked(&dev->dev, MSI_DEFAULT_DOMAIN, nvec); - - return pci_msi_legacy_setup_msi_irqs(dev, nvec, type); + ret = msi_domain_alloc_irqs_all_locked(&dev->dev, + MSI_DEFAULT_DOMAIN, nvec); + else + ret = pci_msi_legacy_setup_msi_irqs(dev, nvec, type); + if (!ret && controlled) + ret = mk_pci_msi_activate(dev); + + if (ret && controlled) + mk_pci_msi_teardown(dev); + return ret; } void pci_msi_teardown_msi_irqs(struct pci_dev *dev) { struct irq_domain *domain; + if (mk_pci_msi_controlled(dev)) + mk_pci_msi_teardown(dev); + domain = dev_get_msi_domain(&dev->dev); if (domain && irq_domain_is_hierarchy(domain)) { msi_domain_free_irqs_all_locked(&dev->dev, MSI_DEFAULT_DOMAIN); diff --git a/drivers/pci/msi/msi.c b/drivers/pci/msi/msi.c index 34d664139f48fc..04c15781052407 100644 --- a/drivers/pci/msi/msi.c +++ b/drivers/pci/msi/msi.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "../pci.h" #include "msi.h" @@ -240,6 +241,15 @@ void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg) { struct pci_dev *dev = msi_desc_to_pci_dev(entry); + if (mk_pci_msi_write_msg(dev, entry->msi_index, entry->irq, + entry->nvec_used, + entry->pci.msi_attrib.is_msix)) { + entry->msg = *msg; + if (entry->write_msi_msg) + entry->write_msi_msg(entry, entry->write_msi_msg_data); + return; + } + if (dev->current_state != PCI_D0 || pci_dev_is_disconnected(dev)) { /* Don't touch the hardware now */ } else if (entry->pci.msi_attrib.is_msix) { diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index af9103426e9406..a7908fafd4074d 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -17,6 +17,7 @@ #include #include struct pci_bus; +struct pci_dev; /** * Physical CPU identifiers @@ -202,6 +203,8 @@ void mk_ipi_ring_drop_pending(void); /* Host-mediated PCI control-plane subtypes */ #define MK_PCI_CFG_REQUEST (MK_MSG_PCI + 1) #define MK_PCI_CFG_RESPONSE (MK_MSG_PCI + 2) +#define MK_PCI_IRQ_REQUEST (MK_MSG_PCI + 3) +#define MK_PCI_IRQ_RESPONSE (MK_MSG_PCI + 4) /** * Core message structure @@ -226,6 +229,13 @@ struct mk_io_irq_payload { u32 flags; /* Control flags (priority, etc.) */ }; +/* Pack a PCI segment and BDF into mk_io_irq_payload::device_id. */ +#define MK_PCI_IRQ_ID(domain, bus, devfn) \ + (((u32)(domain) << 16) | ((u32)(bus) << 8) | (u32)(devfn)) +#define MK_PCI_IRQ_ID_DOMAIN(id) ((u16)((id) >> 16)) +#define MK_PCI_IRQ_ID_BUS(id) ((u8)((id) >> 8)) +#define MK_PCI_IRQ_ID_DEVFN(id) ((u8)(id)) + struct mk_pci_cfg_request { u64 request_id; s32 sender_instance_id; @@ -244,6 +254,31 @@ struct mk_pci_cfg_response { u32 value; }; +enum mk_pci_irq_operation { + MK_PCI_IRQ_SETUP = 1, + MK_PCI_IRQ_BIND, + MK_PCI_IRQ_TEARDOWN, +}; + +struct mk_pci_irq_request { + u64 request_id; + s32 sender_instance_id; + u16 domain; + u8 bus; + u8 devfn; + u16 operation; + u16 vector; + u16 nr_vectors; + u8 msix; + u8 reserved; + u32 local_irq; +}; + +struct mk_pci_irq_response { + u64 request_id; + s32 status; +}; + /* IRQ control flags */ #define MK_IRQ_HIGH_PRIORITY 0x01 #define MK_IRQ_LOW_LATENCY 0x02 @@ -847,6 +882,42 @@ void mk_kimage_free(struct kimage *image, void *virt_addr, size_t size); bool mk_pci_get_assigned_identity_bdf(unsigned int domain, unsigned int bus, unsigned int devfn, u16 *vendor, u16 *device); +#ifdef CONFIG_X86 +bool mk_pci_msi_controlled(struct pci_dev *dev); +int mk_pci_msi_prepare(struct pci_dev *dev, int nvec, int type); +int mk_pci_msi_activate(struct pci_dev *dev); +bool mk_pci_msi_write_msg(struct pci_dev *dev, unsigned int index, + unsigned int irq, unsigned int nvec, bool msix); +void mk_pci_msi_teardown(struct pci_dev *dev); +#else +static inline bool mk_pci_msi_controlled(struct pci_dev *dev) +{ + return false; +} + +static inline int mk_pci_msi_prepare(struct pci_dev *dev, int nvec, int type) +{ + return 0; +} + +static inline int mk_pci_msi_activate(struct pci_dev *dev) +{ + return 0; +} + +static inline bool mk_pci_msi_write_msg(struct pci_dev *dev, + unsigned int index, + unsigned int irq, + unsigned int nvec, bool msix) +{ + return false; +} + +static inline void mk_pci_msi_teardown(struct pci_dev *dev) +{ +} +#endif + bool mk_platform_device_allowed(const char *name, const char *hid); /* Early CPU registration from the manifest (spawn kernels) */ @@ -900,6 +971,34 @@ static inline bool mk_platform_device_allowed(const char *name, const char *hid) { return true; } + +static inline bool mk_pci_msi_controlled(struct pci_dev *dev) +{ + return false; +} + +static inline int mk_pci_msi_prepare(struct pci_dev *dev, int nvec, int type) +{ + return 0; +} + +static inline int mk_pci_msi_activate(struct pci_dev *dev) +{ + return 0; +} + +static inline bool mk_pci_msi_write_msg(struct pci_dev *dev, + unsigned int index, + unsigned int irq, + unsigned int nvec, bool msix) +{ + return false; +} + +static inline void mk_pci_msi_teardown(struct pci_dev *dev) +{ +} + static inline void mk_register_cpus_from_manifest(void) { } diff --git a/kernel/multikernel/ipi.c b/kernel/multikernel/ipi.c index dfc0740a65b6d2..9718af3913faa5 100644 --- a/kernel/multikernel/ipi.c +++ b/kernel/multikernel/ipi.c @@ -298,7 +298,19 @@ static void mk_ipi_drain_ring(void) void mk_poll_ipi_messages(void) { + unsigned long flags; + mk_phys_cpu_t target; + + if (!root_instance) + return; + target = mk_cpu_set_first(root_instance->cpus); + if (target == MK_PHYS_CPU_INVALID || + target != arch_cpu_physical_id(smp_processor_id())) + return; + + local_irq_save(flags); mk_ipi_drain_ring(); + local_irq_restore(flags); } /** diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index d18868e3713419..4782c3264bd269 100644 --- a/kernel/multikernel/pci.c +++ b/kernel/multikernel/pci.c @@ -13,6 +13,7 @@ * device. */ +#include #include #include #include @@ -26,6 +27,19 @@ #include "internal.h" +struct mk_pci_assignment; + +#define MK_PCI_FLR_SETTLE_MS 100 + +struct mk_pci_irq_vector { + struct mk_pci_assignment *assignment; + unsigned int host_irq; + u32 local_irq; + atomic64_t forwarded; + bool requested; + bool disabled; +}; + struct mk_pci_assignment { struct list_head instance_node; struct list_head active_node; @@ -43,6 +57,12 @@ struct mk_pci_assignment { bool iommu_dma_owner; bool iommu_attached; bool iommu_override_active; + struct mk_pci_irq_vector *irq_vectors; + unsigned int nr_irq_vectors; + bool irq_msix; + bool irq_needs_reprogram; + unsigned long irq_flr_deadline; + bool device_enabled; struct work_struct failure_work; atomic_t failure_pending; bool assigned; @@ -62,6 +82,12 @@ struct mk_pci_cfg_work { mk_phys_cpu_t sender_cpu; }; +struct mk_pci_irq_work { + struct work_struct work; + struct mk_pci_irq_request request; + mk_phys_cpu_t sender_cpu; +}; + static bool mk_pci_device_live(struct pci_dev *pdev) { return device_is_registered(&pdev->dev) && @@ -69,6 +95,15 @@ static bool mk_pci_device_live(struct pci_dev *pdev) pci_device_is_present(pdev); } +static int mk_pci_forwarding_cpu(void) +{ + /* The host IPI manifest publishes logical CPU 0's physical ID. */ + if (!cpu_online(0)) + return -ENODEV; + + return 0; +} + static bool mk_pci_device_matches_bdf(const struct mk_pci_device *device, u16 domain, u8 bus, u8 devfn) { @@ -135,6 +170,234 @@ mk_pci_find_assignment(struct mk_instance *instance, u16 domain, u8 bus, return NULL; } +static irqreturn_t mk_pci_forward_irq(int irq, void *data) +{ + struct mk_pci_irq_vector *vector = data; + struct mk_pci_assignment *assignment = vector->assignment; + struct mk_io_irq_payload payload = { + .vector = vector - assignment->irq_vectors, + .device_id = MK_PCI_IRQ_ID(pci_domain_nr(assignment->vf->bus), + assignment->vf->bus->number, + assignment->vf->devfn), + .flags = MK_IRQ_LOW_LATENCY | MK_IRQ_EDGE_TRIGGERED, + }; + u32 local_irq = READ_ONCE(vector->local_irq); + + if (READ_ONCE(assignment->instance->state) != MK_STATE_ACTIVE || + !local_irq) + return IRQ_HANDLED; + + payload.irq_number = local_irq; + if (atomic64_inc_return(&vector->forwarded) == 1) + pr_info("Forwarding host IRQ %u as instance IRQ %u for %s vector %u\n", + irq, local_irq, pci_name(assignment->vf), + payload.vector); + if (mk_send_message_to_instance(assignment->instance, MK_MSG_IO, + MK_IO_IRQ_FORWARD, &payload, + sizeof(payload))) + pr_warn_ratelimited("Failed to forward IRQ %u for %s to instance %d\n", + irq, pci_name(assignment->vf), + assignment->instance->id); + return IRQ_HANDLED; +} + +static unsigned int mk_pci_quiesce_irqs(struct mk_pci_assignment *assignment) +{ + unsigned int disabled = 0; + unsigned int i; + + for (i = 0; i < assignment->nr_irq_vectors; i++) + WRITE_ONCE(assignment->irq_vectors[i].local_irq, 0); + + for (i = 0; i < assignment->nr_irq_vectors; i++) { + struct mk_pci_irq_vector *vector = &assignment->irq_vectors[i]; + + if (vector->requested && !vector->disabled) { + disable_irq(vector->host_irq); + vector->disabled = true; + disabled++; + } + } + + return disabled; +} + +static void mk_pci_release_irqs(struct mk_pci_assignment *assignment) +{ + unsigned int i; + + mk_pci_quiesce_irqs(assignment); + for (i = 0; i < assignment->nr_irq_vectors; i++) { + struct mk_pci_irq_vector *vector = &assignment->irq_vectors[i]; + + if (vector->requested) + free_irq(vector->host_irq, vector); + } + if (assignment->nr_irq_vectors) + pci_free_irq_vectors(assignment->vf); + kfree(assignment->irq_vectors); + assignment->irq_vectors = NULL; + assignment->nr_irq_vectors = 0; + assignment->irq_msix = false; + assignment->irq_needs_reprogram = false; + assignment->irq_flr_deadline = 0; +} + +void mk_pci_quiesce_instance_irqs(struct mk_instance *instance) +{ + struct mk_pci_assignment *assignment; + unsigned int disabled = 0; + + if (!instance || instance == root_instance) + return; + + lockdep_assert_held(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + list_for_each_entry(assignment, &instance->pci_assignments, + instance_node) + disabled += mk_pci_quiesce_irqs(assignment); + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + if (disabled) + pr_info("Quiesced %u host-owned PCI IRQ vectors for instance %d\n", + disabled, instance->id); +} + +static int mk_pci_setup_irqs(struct mk_pci_assignment *assignment, + const struct mk_pci_irq_request *request) +{ + struct mk_pci_irq_vector *vectors; + unsigned int flags; + int forwarding_cpu; + int i; + int nvec; + int ret; + + if (!request->nr_vectors) + return -EINVAL; + forwarding_cpu = mk_pci_forwarding_cpu(); + if (forwarding_cpu < 0) { + pr_err("Host control CPU is unavailable for %s MSI forwarding\n", + pci_name(assignment->vf)); + return forwarding_cpu; + } + if (assignment->nr_irq_vectors) { + if (assignment->nr_irq_vectors == request->nr_vectors && + assignment->irq_msix == request->msix) + return 0; + return -EBUSY; + } + + vectors = kcalloc(request->nr_vectors, sizeof(*vectors), GFP_KERNEL); + if (!vectors) + return -ENOMEM; + flags = request->msix ? PCI_IRQ_MSIX : PCI_IRQ_MSI; + nvec = pci_alloc_irq_vectors(assignment->vf, request->nr_vectors, + request->nr_vectors, flags); + if (nvec < 0) { + kfree(vectors); + return nvec; + } + + assignment->irq_vectors = vectors; + assignment->nr_irq_vectors = nvec; + assignment->irq_msix = request->msix; + for (i = 0; i < nvec; i++) { + vectors[i].assignment = assignment; + vectors[i].host_irq = pci_irq_vector(assignment->vf, i); + } + for (i = 0; i < nvec; i++) { + ret = request_irq(vectors[i].host_irq, mk_pci_forward_irq, + IRQF_NO_AUTOEN | IRQF_NOBALANCING, + "multikernel-pci-forward", &vectors[i]); + if (ret) + goto err_release; + vectors[i].requested = true; + vectors[i].disabled = true; + ret = irq_set_affinity(vectors[i].host_irq, + cpumask_of(forwarding_cpu)); + if (ret) + goto err_release; + } + pr_info("Allocated %d host-owned %s vectors for %s (instance %d, CPU %d)\n", + nvec, request->msix ? "MSI-X" : "MSI", + pci_name(assignment->vf), assignment->instance->id, + forwarding_cpu); + return 0; + +err_release: + pr_err("Failed to configure host-owned IRQ for %s vector %d: %d\n", + pci_name(assignment->vf), i, ret); + mk_pci_release_irqs(assignment); + return ret; +} + +static int mk_pci_bind_irq(struct mk_pci_assignment *assignment, + const struct mk_pci_irq_request *request) +{ + struct mk_pci_irq_vector *vector; + unsigned long delay; + unsigned int count; + unsigned int i; + u32 last_irq; + u32 local_irq; + + if (!assignment->irq_vectors || + assignment->irq_msix != request->msix) + return -EINVAL; + count = request->msix ? 1 : request->nr_vectors; + if (!count || request->vector >= assignment->nr_irq_vectors || + count > assignment->nr_irq_vectors - request->vector) + return -EINVAL; + last_irq = request->local_irq + count - 1; + if (!request->local_irq || last_irq < request->local_irq) + return -EINVAL; + + for (i = 0; i < count; i++) { + vector = &assignment->irq_vectors[request->vector + i]; + local_irq = READ_ONCE(vector->local_irq); + if (!vector->requested) + return -EINVAL; + if (local_irq && local_irq != request->local_irq + i) + return -EBUSY; + } + + if (assignment->irq_needs_reprogram) { + if (time_before(jiffies, assignment->irq_flr_deadline)) { + delay = assignment->irq_flr_deadline - jiffies; + msleep(jiffies_to_msecs(delay) + 1); + } + pci_restore_msi_state(assignment->vf); + assignment->irq_needs_reprogram = false; + assignment->irq_flr_deadline = 0; + pr_info("Reprogrammed host-owned MSI state for %s after FLR\n", + pci_name(assignment->vf)); + } + + for (i = 0; i < count; i++) { + vector = &assignment->irq_vectors[request->vector + i]; + WRITE_ONCE(vector->local_irq, request->local_irq + i); + } + for (i = 0; i < count; i++) { + vector = &assignment->irq_vectors[request->vector + i]; + if (vector->disabled) { + enable_irq(vector->host_irq); + vector->disabled = false; + } + } + return 0; +} + +static bool mk_pci_is_flr_write(struct pci_dev *vf, + const struct mk_pci_cfg_request *request) +{ + return request->write && request->len == sizeof(u16) && + pci_is_pcie(vf) && + request->reg == pci_pcie_cap(vf) + PCI_EXP_DEVCTL && + request->value & PCI_EXP_DEVCTL_BCR_FLR; +} + static bool mk_pci_request_route_stale(struct mk_instance *instance, mk_phys_cpu_t sender_cpu) { @@ -153,6 +416,7 @@ static int mk_pci_config_access(struct mk_instance *instance, { struct mk_pci_assignment *assignment; struct pci_dev *vf; + bool flr; int ret; if (request->len != 1 && request->len != 2 && request->len != 4) @@ -172,6 +436,9 @@ static int mk_pci_config_access(struct mk_instance *instance, } vf = assignment->vf; + flr = mk_pci_is_flr_write(vf, request); + if (flr) + mk_pci_quiesce_irqs(assignment); if (request->write) { switch (request->len) { case 1: @@ -187,6 +454,13 @@ static int mk_pci_config_access(struct mk_instance *instance, request->value); break; } + if (!ret && flr && assignment->nr_irq_vectors) { + assignment->irq_needs_reprogram = true; + assignment->irq_flr_deadline = + jiffies + msecs_to_jiffies(MK_PCI_FLR_SETTLE_MS); + pr_info("Invalidated host-owned MSI bindings for spawn-triggered FLR of %s\n", + pci_name(vf)); + } } else { switch (request->len) { case 1: { @@ -228,7 +502,6 @@ static void mk_pci_cfg_work_fn(struct work_struct *work) instance = mk_instance_find(cfg_work->request.sender_instance_id); if (!instance) goto out; - mk_cpu_ownership_lock(); response.status = mk_pci_request_route_stale(instance, cfg_work->sender_cpu) ? -ESTALE : @@ -245,23 +518,115 @@ static void mk_pci_cfg_work_fn(struct work_struct *work) kfree(cfg_work); } +static int mk_pci_irq_access(struct mk_instance *instance, + const struct mk_pci_irq_request *request) +{ + struct mk_pci_assignment *assignment; + int ret; + + mutex_lock(&instance->resource_mutex); + mutex_lock(&mk_pci_lease_mutex); + pci_lock_rescan_remove(); + if (request->operation != MK_PCI_IRQ_TEARDOWN && + READ_ONCE(instance->state) != MK_STATE_ACTIVE) { + ret = -ESHUTDOWN; + goto out; + } + assignment = mk_pci_find_assignment(instance, request->domain, + request->bus, request->devfn); + if (!assignment || !assignment->assigned || + !mk_pci_device_live(assignment->vf)) { + ret = -ENODEV; + goto out; + } + + switch (request->operation) { + case MK_PCI_IRQ_SETUP: + ret = mk_pci_setup_irqs(assignment, request); + break; + case MK_PCI_IRQ_BIND: + ret = mk_pci_bind_irq(assignment, request); + break; + case MK_PCI_IRQ_TEARDOWN: + mk_pci_release_irqs(assignment); + ret = 0; + break; + default: + ret = -EINVAL; + break; + } +out: + pci_unlock_rescan_remove(); + mutex_unlock(&mk_pci_lease_mutex); + mutex_unlock(&instance->resource_mutex); + return ret; +} + +static void mk_pci_irq_work_fn(struct work_struct *work) +{ + struct mk_pci_irq_work *irq_work = + container_of(work, struct mk_pci_irq_work, work); + struct mk_pci_irq_response response = { + .request_id = irq_work->request.request_id, + }; + struct mk_instance *instance; + + instance = mk_instance_find(irq_work->request.sender_instance_id); + if (!instance) + goto out; + mk_cpu_ownership_lock(); + response.status = mk_pci_request_route_stale(instance, + irq_work->sender_cpu) ? -ESTALE : + mk_pci_irq_access(instance, &irq_work->request); + mk_cpu_ownership_unlock(); + if (mk_send_message_to_instance(instance, MK_MSG_PCI, + MK_PCI_IRQ_RESPONSE, &response, + sizeof(response))) + pr_warn_ratelimited("Failed to return PCI IRQ response to instance %d\n", + instance->id); + mk_instance_put(instance); +out: + kfree(irq_work); +} + static void mk_pci_control_msg_handler(u32 msg_type, u32 subtype, void *payload, u32 payload_len, mk_phys_cpu_t sender_cpu, void *ctx) { - struct mk_pci_cfg_work *work; + struct mk_pci_cfg_work *cfg_work; + struct mk_pci_irq_work *irq_work; - if (msg_type != MK_MSG_PCI || subtype != MK_PCI_CFG_REQUEST || - payload_len != sizeof(work->request)) + if (msg_type != MK_MSG_PCI) return; - work = kmalloc(sizeof(*work), GFP_ATOMIC); - if (!work) + switch (subtype) { + case MK_PCI_CFG_REQUEST: + if (payload_len != sizeof(cfg_work->request)) + return; + cfg_work = kmalloc(sizeof(*cfg_work), GFP_ATOMIC); + if (!cfg_work) + return; + INIT_WORK(&cfg_work->work, mk_pci_cfg_work_fn); + memcpy(&cfg_work->request, payload, + sizeof(cfg_work->request)); + cfg_work->sender_cpu = sender_cpu; + schedule_work(&cfg_work->work); + break; + case MK_PCI_IRQ_REQUEST: + if (payload_len != sizeof(irq_work->request)) + return; + irq_work = kmalloc(sizeof(*irq_work), GFP_ATOMIC); + if (!irq_work) + return; + INIT_WORK(&irq_work->work, mk_pci_irq_work_fn); + memcpy(&irq_work->request, payload, + sizeof(irq_work->request)); + irq_work->sender_cpu = sender_cpu; + schedule_work(&irq_work->work); + break; + default: return; - INIT_WORK(&work->work, mk_pci_cfg_work_fn); - memcpy(&work->request, payload, sizeof(work->request)); - work->sender_cpu = sender_cpu; - schedule_work(&work->work); + } } static void mk_pci_assignment_failure_work(struct work_struct *work) @@ -538,6 +903,7 @@ mk_pci_quiesce_assignment(struct mk_pci_assignment *assignment) bool transactions_drained; int ret; + mk_pci_release_irqs(assignment); if (!mk_pci_device_live(vf)) return 0; @@ -574,6 +940,7 @@ mk_pci_reset_assignment_for_start(struct mk_pci_assignment *assignment) struct pci_dev *vf = assignment->vf; int ret; + mk_pci_release_irqs(assignment); if (!assignment->assigned || !assignment->iommu_attached) return -EINVAL; if (!mk_pci_device_live(vf)) @@ -753,9 +1120,22 @@ static int mk_pci_iommu_assignment_probe(struct pci_dev *pdev, ret = iommu_attach_group(assignment->iommu_domain, assignment->iommu_group); - if (ret) + if (ret) { + iommu_device_release_dma_owner(&assignment->vf->dev); + assignment->iommu_dma_owner = false; return ret; + } assignment->iommu_attached = true; + ret = pci_enable_device(pdev); + if (ret) { + iommu_detach_group(assignment->iommu_domain, + assignment->iommu_group); + assignment->iommu_attached = false; + iommu_device_release_dma_owner(&assignment->vf->dev); + assignment->iommu_dma_owner = false; + return ret; + } + assignment->device_enabled = true; pr_info("Attached %s to host-owned IOMMU domain for instance %d\n", pci_name(assignment->vf), assignment->instance->id); return 0; @@ -770,6 +1150,10 @@ static void mk_pci_iommu_assignment_remove(struct pci_dev *pdev) return; if (READ_ONCE(assignment->expected_unbind)) { + if (assignment->device_enabled) { + pci_disable_device(pdev); + assignment->device_enabled = false; + } pci_set_drvdata(pdev, NULL); return; } @@ -782,6 +1166,10 @@ static void mk_pci_iommu_assignment_remove(struct pci_dev *pdev) } else { mk_pci_iommu_deactivate_assignment(assignment); } + if (assignment->device_enabled) { + pci_disable_device(pdev); + assignment->device_enabled = false; + } pci_set_drvdata(pdev, NULL); } From 981aa2b82b746b185650fbdfb90be306c946453a Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Tue, 4 Aug 2026 17:10:07 +0300 Subject: [PATCH 12/13] multikernel: harden optional PCI resource handling Build PCI lease and x86 PCI code only when CONFIG_PCI is enabled. Add fail-closed stubs and guard baseline and hotplug PCI calls. Require exact BDF properties and reject out-of-range identifiers. Signed-off-by: Nikolay Nikolaev --- arch/x86/include/asm/multikernel.h | 5 +- arch/x86/multikernel/Makefile | 3 +- include/linux/multikernel.h | 18 ++++++- kernel/multikernel/Makefile | 4 +- kernel/multikernel/baseline.c | 47 +++++++++++++----- kernel/multikernel/dts.c | 77 +++++++++++++++++++++++++----- kernel/multikernel/hotplug.c | 13 +++++ kernel/multikernel/internal.h | 54 ++++++++++++++++++++- kernel/multikernel/overlay.c | 48 ++++++++++++------- 9 files changed, 225 insertions(+), 44 deletions(-) diff --git a/arch/x86/include/asm/multikernel.h b/arch/x86/include/asm/multikernel.h index 7637245b51fc34..e849e358babda4 100644 --- a/arch/x86/include/asm/multikernel.h +++ b/arch/x86/include/asm/multikernel.h @@ -173,10 +173,13 @@ int multikernel_restore_ap(unsigned int cpu, unsigned long cr3, struct mk_pci_host_bridge; #ifdef CONFIG_MULTIKERNEL -void __init x86_multikernel_pci_platform_init(void); int mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, struct mk_pci_host_bridge *bridges, size_t capacity); +#endif + +#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 diff --git a/arch/x86/multikernel/Makefile b/arch/x86/multikernel/Makefile index f4d399154f4ce6..1fcee985bcbfe8 100644 --- a/arch/x86/multikernel/Makefile +++ b/arch/x86/multikernel/Makefile @@ -3,4 +3,5 @@ # Makefile for multikernel spawn support # -obj-y += spawn.o pci.o direct_boot.o head_64.o +obj-y += spawn.o direct_boot.o head_64.o +obj-$(CONFIG_PCI) += pci.o diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index a7908fafd4074d..56f3357b4976d6 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -879,10 +879,19 @@ void *mk_kimage_alloc(struct kimage *image, size_t size, size_t align); void mk_kimage_free(struct kimage *image, void *virt_addr, size_t size); /* Device filtering against the instance metadata */ +#ifdef CONFIG_PCI bool mk_pci_get_assigned_identity_bdf(unsigned int domain, unsigned int bus, unsigned int devfn, u16 *vendor, u16 *device); -#ifdef CONFIG_X86 +#else +static inline bool +mk_pci_get_assigned_identity_bdf(unsigned int domain, unsigned int bus, + unsigned int devfn, u16 *vendor, u16 *device) +{ + return false; +} +#endif +#if defined(CONFIG_X86) && defined(CONFIG_PCI) bool mk_pci_msi_controlled(struct pci_dev *dev); int mk_pci_msi_prepare(struct pci_dev *dev, int nvec, int type); int mk_pci_msi_activate(struct pci_dev *dev); @@ -928,8 +937,15 @@ void mk_manifest_populate(phys_addr_t fdt_phys, u64 fdt_len); /* Build the manifest for a spawn (host, kexec path) */ int mk_manifest_finalize(struct kimage *image); +#ifdef CONFIG_PCI int mk_pci_prepare_instance_start(struct mk_instance *instance); #else +static inline int mk_pci_prepare_instance_start(struct mk_instance *instance) +{ + return 0; +} +#endif +#else static inline bool multikernel_allow_emergency_restart(void) { return true; diff --git a/kernel/multikernel/Makefile b/kernel/multikernel/Makefile index e6f79baf71f595..8e5e9497b51474 100644 --- a/kernel/multikernel/Makefile +++ b/kernel/multikernel/Makefile @@ -3,7 +3,9 @@ # Makefile for multikernel support # -obj-y += core.o cpuset.o mem.o kernfs.o dts.o instance_dt.o manifest.o pci.o ipi.o messaging.o overlay.o hotplug.o baseline.o +obj-y += core.o cpuset.o mem.o kernfs.o dts.o instance_dt.o manifest.o +obj-y += ipi.o messaging.o overlay.o hotplug.o baseline.o +obj-$(CONFIG_PCI) += pci.o # DMA-BUF heap for multikernel memory allocation obj-$(CONFIG_DMABUF_HEAPS) += dma_heap.o diff --git a/kernel/multikernel/baseline.c b/kernel/multikernel/baseline.c index 02c6f1224f45a5..fc3df967ddacb4 100644 --- a/kernel/multikernel/baseline.c +++ b/kernel/multikernel/baseline.c @@ -270,7 +270,10 @@ static int mk_baseline_parse_devices(const void *fdt, int resources_node, struct mk_pci_device *pci_dev; const char *pci_id_str; const fdt32_t *vendor_prop, *device_prop; - unsigned int domain, bus, slot, func; + u32 vendor, device; + u16 domain; + u8 bus, slot, func; + int ret; pci_id_str = fdt_getprop(fdt, dev_node, "pci-id", &len); if (!pci_id_str) { @@ -279,10 +282,12 @@ static int mk_baseline_parse_devices(const void *fdt, int resources_node, return -EINVAL; } - if (sscanf(pci_id_str, "%x:%x:%x.%x", &domain, &bus, &slot, &func) != 4) { - pr_err("Invalid pci-id format '%s' for device '%s'\n", - pci_id_str, dev_name); - return -EINVAL; + ret = mk_pci_parse_bdf(pci_id_str, len, &domain, &bus, + &slot, &func); + if (ret) { + pr_err("Invalid or out-of-range pci-id '%.*s' for device '%s'\n", + len, pci_id_str, dev_name); + return ret; } vendor_prop = fdt_getprop(fdt, dev_node, "vendor-id", &len); @@ -298,6 +303,13 @@ static int mk_baseline_parse_devices(const void *fdt, int resources_node, dev_name); return -EINVAL; } + vendor = fdt32_to_cpu(*vendor_prop); + device = fdt32_to_cpu(*device_prop); + if (vendor > U16_MAX || device > U16_MAX) { + pr_err("Out-of-range vendor-id or device-id for device '%s'\n", + dev_name); + return -ERANGE; + } pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); if (!pci_dev) { @@ -307,12 +319,12 @@ static int mk_baseline_parse_devices(const void *fdt, int resources_node, strncpy(pci_dev->name, dev_name, sizeof(pci_dev->name) - 1); pci_dev->name[sizeof(pci_dev->name) - 1] = '\0'; - pci_dev->vendor = (u16)fdt32_to_cpu(*vendor_prop); - pci_dev->device = (u16)fdt32_to_cpu(*device_prop); - pci_dev->domain = (u16)domain; - pci_dev->bus = (u8)bus; - pci_dev->slot = (u8)slot; - pci_dev->func = (u8)func; + pci_dev->vendor = (u16)vendor; + pci_dev->device = (u16)device; + pci_dev->domain = domain; + pci_dev->bus = bus; + pci_dev->slot = slot; + pci_dev->func = func; list_add_tail(&pci_dev->list, &instance->pci_devices); instance->pci_device_count++; @@ -501,6 +513,7 @@ static int mk_baseline_initialize_cpus(void) return 0; } +#ifdef CONFIG_PCI static int mk_baseline_initialize_devices(const struct mk_instance *instance) { struct mk_pci_device *pci_dev; @@ -555,6 +568,18 @@ static int mk_baseline_initialize_devices(const struct mk_instance *instance) available); return 0; } +#else +static int mk_baseline_initialize_devices(const struct mk_instance *instance) +{ + if (!instance->pci_device_count) { + pr_debug("No PCI devices in the multikernel pool\n"); + return 0; + } + + pr_err("Cannot initialize PCI devices without CONFIG_PCI\n"); + return -EOPNOTSUPP; +} +#endif int mk_baseline_validate_and_initialize(const void *fdt, size_t fdt_size) { diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index 0047531cfebbb6..0ec4fb899fbec9 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,48 @@ mk_arch_snapshot_pci_host_bridges(const struct mk_instance *instance, return 0; } +static int mk_pci_parse_hex(const char *str, size_t digits, u32 *value) +{ + size_t i; + u32 parsed = 0; + + for (i = 0; i < digits; i++) { + int digit = hex_to_bin(str[i]); + + if (digit < 0) + return -EINVAL; + parsed = (parsed << 4) | digit; + } + + *value = parsed; + return 0; +} + +int mk_pci_parse_bdf(const char *pci_id, int len, u16 *domain, u8 *bus, + u8 *slot, u8 *func) +{ + u32 parsed_domain, parsed_bus, parsed_slot, parsed_func; + + if (len != (int)sizeof("0000:00:00.0") || pci_id[12] != '\0' || + pci_id[4] != ':' || pci_id[7] != ':' || pci_id[10] != '.') + return -EINVAL; + + if (mk_pci_parse_hex(pci_id, 4, &parsed_domain) || + mk_pci_parse_hex(pci_id + 5, 2, &parsed_bus) || + mk_pci_parse_hex(pci_id + 8, 2, &parsed_slot) || + mk_pci_parse_hex(pci_id + 11, 1, &parsed_func)) + return -EINVAL; + if (parsed_domain > U16_MAX || parsed_bus > U8_MAX || + parsed_slot > 31 || parsed_func > 7) + return -ERANGE; + + *domain = (u16)parsed_domain; + *bus = (u8)parsed_bus; + *slot = (u8)parsed_slot; + *func = (u8)parsed_func; + return 0; +} + static const void *mk_dt_get_base_fdt(void) { if (!root_instance || !root_instance->dtb_data) { @@ -366,9 +409,11 @@ static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, const fdt32_t *vendor_prop, *device_prop; const fdt64_t *resources_prop; struct mk_pci_device *pci_dev; - unsigned int domain, bus, slot, func; + u32 vendor, device; + u16 domain; + u8 bus, slot, func; const char *node_name; - int len, i; + int len, i, ret; node_name = fdt_get_name(source_fdt, dev_node, NULL); @@ -379,10 +424,11 @@ static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, return -EINVAL; } - if (sscanf(pci_id_str, "%x:%x:%x.%x", &domain, &bus, &slot, &func) != 4) { - pr_err("Invalid pci-id format: '%s' (expected domain:bus:slot.func)\n", - pci_id_str); - return -EINVAL; + ret = mk_pci_parse_bdf(pci_id_str, len, &domain, &bus, &slot, &func); + if (ret) { + pr_err("Invalid or out-of-range pci-id: '%.*s' (expected domain:bus:slot.func)\n", + len, pci_id_str); + return ret; } vendor_prop = fdt_getprop(source_fdt, dev_node, "vendor-id", &len); @@ -398,6 +444,13 @@ static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, device_name, node_name ? node_name : ""); return -EINVAL; } + vendor = fdt32_to_cpu(*vendor_prop); + device = fdt32_to_cpu(*device_prop); + if (vendor > U16_MAX || device > U16_MAX) { + pr_err("Out-of-range vendor-id or device-id in device '%s'\n", + device_name); + return -ERANGE; + } pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); if (!pci_dev) { @@ -406,12 +459,12 @@ static int mk_dt_parse_single_pci_device(const void *source_fdt, int dev_node, } strscpy(pci_dev->name, device_name, sizeof(pci_dev->name)); - pci_dev->vendor = (u16)fdt32_to_cpu(*vendor_prop); - pci_dev->device = (u16)fdt32_to_cpu(*device_prop); - pci_dev->domain = (u16)domain; - pci_dev->bus = (u8)bus; - pci_dev->slot = (u8)slot; - pci_dev->func = (u8)func; + pci_dev->vendor = (u16)vendor; + pci_dev->device = (u16)device; + pci_dev->domain = domain; + pci_dev->bus = bus; + pci_dev->slot = slot; + pci_dev->func = func; resources_prop = fdt_getprop(source_fdt, dev_node, "bar-resources", &len); if (resources_prop) { if (len != MK_PCI_RESOURCE_COUNT * 3 * sizeof(*resources_prop)) { diff --git a/kernel/multikernel/hotplug.c b/kernel/multikernel/hotplug.c index 24fca1d855e17c..18cd7754cb0caa 100644 --- a/kernel/multikernel/hotplug.c +++ b/kernel/multikernel/hotplug.c @@ -663,6 +663,7 @@ static int mk_handle_mem_remove(struct mk_mem_resource_payload *payload, u32 pay * PCI Device Hotplug Operations */ +#ifdef CONFIG_PCI static int mk_do_device_add(u16 domain, u8 bus, u8 devfn, const char *driver_override, u32 flags) { @@ -795,6 +796,18 @@ static int mk_do_device_remove(u16 domain, u8 bus, u8 devfn) return 0; } +#else +static int mk_do_device_add(u16 domain, u8 bus, u8 devfn, + const char *driver_override, u32 flags) +{ + return -EOPNOTSUPP; +} + +static int mk_do_device_remove(u16 domain, u8 bus, u8 devfn) +{ + return -EOPNOTSUPP; +} +#endif struct mk_device_hotplug_work { struct work_struct work; diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index 9830a68ca1f860..74338c7684b4f0 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -26,6 +26,8 @@ int mk_dt_parse_resources(const void *fdt, int resources_node, const char *instance_name, struct mk_dt_config *config); int mk_dt_generate_instance_dtb(struct mk_instance *instance, void **out_dtb, size_t *out_size); +int mk_pci_parse_bdf(const char *pci_id, int len, u16 *domain, u8 *bus, + u8 *slot, u8 *func); int mk_dt_parse_pci_host_bridges(const void *fdt, int resources_node, struct list_head *bridges, int *count, bool *valid); @@ -43,6 +45,7 @@ void mk_cpu_ownership_unlock(void); void mk_cpu_ownership_assert_held(void); /* pci.c */ +#ifdef CONFIG_PCI int mk_pci_lease_system_init(void); void mk_pci_lease_system_cleanup(void); void mk_pci_lease_instance_init(struct mk_instance *instance); @@ -55,9 +58,58 @@ int mk_pci_assign_device(struct mk_instance *instance, u16 domain, u8 bus, int mk_pci_unassign_device(struct mk_instance *instance, u16 domain, u8 bus, u8 devfn); int mk_pci_release_assignments(struct mk_instance *instance); - /* Caller must hold instance->resource_mutex. */ void mk_pci_quiesce_instance_irqs(struct mk_instance *instance); +#else +static inline int mk_pci_lease_system_init(void) +{ + return 0; +} + +static inline void mk_pci_lease_system_cleanup(void) +{ +} + +static inline void mk_pci_lease_instance_init(struct mk_instance *instance) +{ + mutex_init(&instance->resource_mutex); + INIT_LIST_HEAD(&instance->pci_assignments); +} + +static inline bool +mk_pci_iommu_lease_active_locked(struct mk_instance *instance) +{ + return false; +} + +static inline int +mk_pci_assign_devices(struct mk_instance *instance, + const struct list_head *requested_devices, + int requested_count) +{ + if (requested_count < 0) + return -EINVAL; + + return requested_count ? -EOPNOTSUPP : 0; +} + +static inline int mk_pci_assign_device(struct mk_instance *instance, + u16 domain, u8 bus, u8 devfn) +{ + return -EOPNOTSUPP; +} + +static inline int mk_pci_unassign_device(struct mk_instance *instance, + u16 domain, u8 bus, u8 devfn) +{ + return -EOPNOTSUPP; +} + +static inline int mk_pci_release_assignments(struct mk_instance *instance) +{ + return 0; +} +#endif int mk_instance_force_halt(struct mk_instance *instance); /* overlay.c */ diff --git a/kernel/multikernel/overlay.c b/kernel/multikernel/overlay.c index 5098918fee048a..1fafe915b0ecaf 100644 --- a/kernel/multikernel/overlay.c +++ b/kernel/multikernel/overlay.c @@ -581,7 +581,8 @@ static int mk_overlay_parse_and_apply(struct mk_overlay_tx *tx, fdt_for_each_subnode(item_node, fdt, op_node) { const char *name = fdt_get_name(fdt, item_node, NULL); const char *pci_id_str; - unsigned int domain, bus, slot, func; + u16 domain; + u8 bus, slot, func; if (strncmp(name, "pci@", 4) != 0) continue; @@ -593,9 +594,12 @@ static int mk_overlay_parse_and_apply(struct mk_overlay_tx *tx, return -EINVAL; } - if (sscanf(pci_id_str, "%x:%x:%x.%x", &domain, &bus, &slot, &func) != 4) { - pr_err("Invalid pci-id format '%s'\n", pci_id_str); - return -EINVAL; + ret = mk_pci_parse_bdf(pci_id_str, len, &domain, &bus, + &slot, &func); + if (ret) { + pr_err("Invalid or out-of-range pci-id '%.*s'\n", + len, pci_id_str); + return ret; } pr_info("Overlay tx%d: -device %04x:%02x:%02x.%x from %s\n", @@ -624,7 +628,8 @@ static int mk_overlay_parse_and_apply(struct mk_overlay_tx *tx, const char *name = fdt_get_name(fdt, item_node, NULL); const char *pci_id_str; const char *driver_name = NULL; - unsigned int domain, bus, slot, func; + u16 domain; + u8 bus, slot, func; u32 flags = 0; if (strncmp(name, "pci@", 4) != 0) @@ -637,9 +642,12 @@ static int mk_overlay_parse_and_apply(struct mk_overlay_tx *tx, return -EINVAL; } - if (sscanf(pci_id_str, "%x:%x:%x.%x", &domain, &bus, &slot, &func) != 4) { - pr_err("Invalid pci-id format '%s'\n", pci_id_str); - return -EINVAL; + ret = mk_pci_parse_bdf(pci_id_str, len, &domain, &bus, + &slot, &func); + if (ret) { + pr_err("Invalid or out-of-range pci-id '%.*s'\n", + len, pci_id_str); + return ret; } /* Get optional driver override */ @@ -799,7 +807,8 @@ static int mk_overlay_parse_and_rollback(struct mk_overlay_tx *tx, fdt_for_each_subnode(item_node, fdt, op_node) { const char *name = fdt_get_name(fdt, item_node, NULL); const char *pci_id_str; - unsigned int domain, bus, slot, func; + u16 domain; + u8 bus, slot, func; if (strncmp(name, "pci@", 4) != 0) continue; @@ -810,9 +819,12 @@ static int mk_overlay_parse_and_rollback(struct mk_overlay_tx *tx, return -EINVAL; } - if (sscanf(pci_id_str, "%x:%x:%x.%x", &domain, &bus, &slot, &func) != 4) { - pr_err("Invalid pci-id format '%s'\n", pci_id_str); - return -EINVAL; + ret = mk_pci_parse_bdf(pci_id_str, len, &domain, &bus, + &slot, &func); + if (ret) { + pr_err("Invalid or out-of-range pci-id '%.*s'\n", + len, pci_id_str); + return ret; } pr_info("Rollback tx%d: -device %04x:%02x:%02x.%x from %s\n", @@ -845,7 +857,8 @@ static int mk_overlay_parse_and_rollback(struct mk_overlay_tx *tx, const char *name = fdt_get_name(fdt, item_node, NULL); const char *pci_id_str; const char *driver_name = NULL; - unsigned int domain, bus, slot, func; + u16 domain; + u8 bus, slot, func; u32 flags = 0; if (strncmp(name, "pci@", 4) != 0) @@ -857,9 +870,12 @@ static int mk_overlay_parse_and_rollback(struct mk_overlay_tx *tx, return -EINVAL; } - if (sscanf(pci_id_str, "%x:%x:%x.%x", &domain, &bus, &slot, &func) != 4) { - pr_err("Invalid pci-id format '%s'\n", pci_id_str); - return -EINVAL; + ret = mk_pci_parse_bdf(pci_id_str, len, &domain, &bus, + &slot, &func); + if (ret) { + pr_err("Invalid or out-of-range pci-id '%.*s'\n", + len, pci_id_str); + return ret; } /* Get optional driver override */ From a3db37b49f372c1564894652fee70ab042f0f605 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Wed, 5 Aug 2026 10:38:20 +0300 Subject: [PATCH 13/13] pci/multikernel: preserve MSI safety across halt and reset Quiesce host-owned interrupt delivery whenever an instance halts, retain stable forwarding identity across restart, restore host MSI programming after mediated FLR, and keep unsupported affinity changes from being advertised to spawn drivers. Signed-off-by: Nikolay Nikolaev --- kernel/multikernel/core.c | 19 +++++++++++-------- kernel/multikernel/internal.h | 9 +++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/kernel/multikernel/core.c b/kernel/multikernel/core.c index befcdfb56e31f3..fde4e503614a95 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -215,6 +215,14 @@ void mk_instance_set_state(struct mk_instance *instance, */ } +static void mk_instance_finish_halt(struct mk_instance *instance) +{ + mutex_lock(&instance->resource_mutex); + mk_pci_quiesce_instance_irqs(instance); + mk_instance_set_state(instance, MK_STATE_LOADED); + mutex_unlock(&instance->resource_mutex); +} + struct mk_instance *mk_instance_find_by_name(const char *name) { struct mk_instance *instance; @@ -1258,7 +1266,7 @@ static void mk_instance_settle_halted(struct mk_instance *instance) { pr_info("Instance %d (%s) halted, CPUs parking in pool\n", instance->id, instance->name); - mk_instance_set_state(instance, MK_STATE_LOADED); + mk_instance_finish_halt(instance); } struct mk_halted_work { @@ -1403,7 +1411,7 @@ int multikernel_halt_by_id(int mk_id) pr_warn("Multikernel instance %d halted with CPUs unaccounted for\n", mk_id); - mk_instance_set_state(instance, MK_STATE_LOADED); + mk_instance_finish_halt(instance); pr_info("Multikernel instance %d halted (graceful)\n", mk_id); } @@ -1486,12 +1494,7 @@ int mk_instance_force_halt(struct mk_instance *instance) mk_cpu_set_free(snapshot); pr_info("Sent NMI to %d CPUs in instance %d\n", cpu_count, instance->id); - - /* - * The NMI handler parks each CPU on the instance's context. Wait - * for them to arrive before reporting the instance re-spawnable, - * exactly as the graceful path does after its shutdown ACK. - */ + /* Quiesce host-owned resources before making the instance reusable. */ mk_instance_settle_halted(instance); mk_cpu_transaction_unlock(); return 0; diff --git a/kernel/multikernel/internal.h b/kernel/multikernel/internal.h index 74338c7684b4f0..c67fff4eb34246 100644 --- a/kernel/multikernel/internal.h +++ b/kernel/multikernel/internal.h @@ -58,6 +58,7 @@ int mk_pci_assign_device(struct mk_instance *instance, u16 domain, u8 bus, int mk_pci_unassign_device(struct mk_instance *instance, u16 domain, u8 bus, u8 devfn); int mk_pci_release_assignments(struct mk_instance *instance); + /* Caller must hold instance->resource_mutex. */ void mk_pci_quiesce_instance_irqs(struct mk_instance *instance); #else @@ -109,7 +110,15 @@ static inline int mk_pci_release_assignments(struct mk_instance *instance) { return 0; } + +/* Caller must hold instance->resource_mutex. */ +static inline void +mk_pci_quiesce_instance_irqs(struct mk_instance *instance) +{ +} + #endif + int mk_instance_force_halt(struct mk_instance *instance); /* overlay.c */