From b0201d83d5f392a0655e3fafd428d5d46faa4f31 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Sun, 19 Jul 2026 03:11:26 +0300 Subject: [PATCH 1/4] multikernel: centralize assigned PCI resources Keep assigned PCI devices and BAR metadata in each instance. Carry PCI host-bridge descriptors through the device-tree transport. This gives architecture code one owned source of assignment state. Signed-off-by: Nikolay Nikolaev --- include/linux/multikernel.h | 43 ++++- kernel/multikernel/Makefile | 2 +- kernel/multikernel/baseline.c | 12 ++ kernel/multikernel/core.c | 26 +++ kernel/multikernel/dts.c | 311 +++++++++++++++++++++++++++++++++- kernel/multikernel/internal.h | 8 + kernel/multikernel/kernfs.c | 1 + kernel/multikernel/kho.c | 123 +++----------- kernel/multikernel/pci.c | 136 +++++++++++++++ 9 files changed, 557 insertions(+), 105 deletions(-) create mode 100644 kernel/multikernel/pci.c diff --git a/include/linux/multikernel.h b/include/linux/multikernel.h index fb5474f7a0a11a..69c04ce04c83f8 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -14,6 +14,8 @@ #include #include #include +struct pci_bus; +struct pci_dev; /** * Multikernel IPI interface @@ -350,6 +352,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 */ @@ -358,9 +366,29 @@ 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 * @@ -401,13 +429,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]; /* Reduced due to added fields */ /* Raw device tree data */ void *dtb_data; @@ -440,6 +473,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 */ @@ -831,6 +869,9 @@ void __init mk_register_cpus_from_kho(void); * Returns: true if probing should proceed, false to skip entirely */ 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); /** * mk_platform_device_allowed() - Check if a platform device is allowed by DTB allowlist diff --git a/kernel/multikernel/Makefile b/kernel/multikernel/Makefile index c62c2cc40c09b0..8c9142945b65b3 100644 --- a/kernel/multikernel/Makefile +++ b/kernel/multikernel/Makefile @@ -3,7 +3,7 @@ # Makefile for multikernel support # -obj-y += core.o mem.o kernfs.o dts.o kho.o ipi.o messaging.o overlay.o hotplug.o baseline.o +obj-y += core.o mem.o kernfs.o dts.o kho.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 5ef0375196802d..2ab1c466a373aa 100644 --- a/kernel/multikernel/baseline.c +++ b/kernel/multikernel/baseline.c @@ -176,6 +176,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); @@ -591,6 +594,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 3c1efd6e34d5e3..ee7e90dfe30a8d 100644 --- a/kernel/multikernel/core.c +++ b/kernel/multikernel/core.c @@ -149,6 +149,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); kfree(instance->cpus); @@ -936,6 +939,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 e45c079b323259..ffc7a2dfb993ba 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,13 @@ #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) { @@ -55,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; @@ -64,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 +93,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 +119,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 */ @@ -211,10 +346,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); @@ -251,12 +387,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++; @@ -560,6 +722,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); @@ -567,9 +739,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, %d 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, config->cpus ? bitmap_weight(config->cpus, NR_CPUS) : 0, - config->pci_device_count, config->platform_device_count); + config->pci_host_bridge_count, config->pci_device_count, + config->platform_device_count); return 0; } @@ -610,6 +783,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); @@ -617,10 +801,11 @@ int mk_dt_parse_resources(const void *fdt, int resources_node, return ret; } - pr_info("Successfully parsed instance '%s': %zu bytes memory, %d 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, config->cpus ? bitmap_weight(config->cpus, NR_CPUS) : 0, - 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 +1001,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) { @@ -843,6 +1029,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"); @@ -877,6 +1074,67 @@ 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) @@ -971,6 +1229,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"); @@ -982,6 +1244,40 @@ 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; + int i; + + if (!pci_dev->resources_valid) { + live_dev = pci_get_domain_bus_and_slot( + pci_dev->domain, pci_dev->bus, + PCI_DEVFN(pci_dev->slot, pci_dev->func)); + 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"); @@ -1003,6 +1299,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/internal.h b/kernel/multikernel/internal.h index a3aa40eb1e886b..3ea686b71f5fcc 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 5615e5b9211458..a1cd5f24b2c871 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/kho.c b/kernel/multikernel/kho.c index 010d44012b5118..05f46bf3965197 100644 --- a/kernel/multikernel/kho.c +++ b/kernel/multikernel/kho.c @@ -14,7 +14,6 @@ #include #include #include -#include #ifdef CONFIG_KEXEC_HANDOVER #include #include @@ -321,6 +320,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; @@ -368,19 +370,13 @@ static int __init mk_kho_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++; } @@ -389,6 +385,17 @@ static int __init mk_kho_copy_pci_devices(const struct mk_dt_config *config, return 0; } +static int __init mk_kho_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_kho_copy_platform_devices(const struct mk_dt_config *config, struct mk_instance *instance) { @@ -675,6 +682,12 @@ int __init mk_kho_restore_dtbs(void) goto cleanup_devices; } + ret = mk_kho_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_kho_copy_platform_devices(&config, instance); if (ret) { pr_err("Failed to copy platform devices: %d\n", ret); @@ -701,6 +714,9 @@ int __init mk_kho_restore_dtbs(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) { @@ -732,89 +748,6 @@ int __init mk_kho_restore_dtbs(void) /* Run at early_initcall to enforce CPU restrictions before per-CPU allocations */ early_initcall(mk_kho_restore_dtbs); -/** - * 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; @@ -856,12 +789,6 @@ int __init mk_kho_restore_dtbs(void) return 0; } -bool mk_pci_should_probe(struct pci_bus *bus, int devfn) -{ - return true; -} -EXPORT_SYMBOL_GPL(mk_pci_should_probe); - bool mk_platform_device_allowed(const char *name, const char *hid) { return true; diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c new file mode 100644 index 00000000000000..2ddb47f2a1772d --- /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 KHO transport 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 adaf3bd0da91070e6e139458299ae4803e187c33 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Sun, 19 Jul 2026 03:11:40 +0300 Subject: [PATCH 2/4] x86/multikernel: own PCI ECAM state transfer Move ECAM restore and snapshot policy into arch/x86/multikernel. Leave generic MMCONFIG with only a locked region visitor. Use x86 platform callbacks so arch/x86/pci/init.c stays generic. Signed-off-by: Nikolay Nikolaev --- arch/x86/include/asm/multikernel.h | 13 +++ 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, 164 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 3ec464f6d9b89c..6b36dc557d0503 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 @@ -65,6 +66,18 @@ void mk_park_cpu(void); /* Park an offlined pool CPU (host park area, or instance context) */ void mk_pool_park_cpu(void); +struct mk_instance; +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__ */ #endif /* _ASM_X86_MULTIKERNEL_H */ 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 bd4aa1c37e1c3d..0a24b8bbd3306f 100644 --- a/arch/x86/kernel/platform-quirks.c +++ b/arch/x86/kernel/platform-quirks.c @@ -133,6 +133,7 @@ void __init x86_early_init_platform_quirks(void) x86_platform.legacy.i8042 = X86_LEGACY_I8042_PLATFORM_ABSENT; break; case X86_SUBARCH_MULTIKERNEL: + 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 69c04ce04c83f8..62bcd7dfd34162 100644 --- a/include/linux/multikernel.h +++ b/include/linux/multikernel.h @@ -385,9 +385,6 @@ struct mk_pci_host_bridge { 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 diff --git a/kernel/multikernel/dts.c b/kernel/multikernel/dts.c index ffc7a2dfb993ba..ebe202ee33aff6 100644 --- a/kernel/multikernel/dts.c +++ b/kernel/multikernel/dts.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "internal.h" From bf319c7c8e6aa86f50e81a418b2c62090c7c9bfb Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Sun, 19 Jul 2026 03:01:31 +0300 Subject: [PATCH 3/4] pci/multikernel: virtualize assigned config access Filter root PCI config access in arch/x86/multikernel. Synthesize assigned VF identity and restore BARs with a PCI header fixup. Remove multikernel branches from drivers/pci/probe.c. Signed-off-by: Nikolay Nikolaev --- arch/x86/multikernel/pci.c | 44 +++++++++++++++++++++++++++++++++++++ drivers/pci/probe.c | 11 ---------- include/linux/multikernel.h | 6 ++--- kernel/multikernel/pci.c | 40 +++++++++++++++++++-------------- 4 files changed, 71 insertions(+), 30 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 eed1f98d556f9a..41183aed8f5d94 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -22,9 +22,6 @@ #include #include #include -#ifdef CONFIG_MULTIKERNEL -#include -#endif #include "pci.h" #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ @@ -2625,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 62bcd7dfd34162..b81ea1c157ab9b 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; /** * Multikernel IPI interface @@ -865,10 +865,10 @@ void __init mk_register_cpus_from_kho(void); * * 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); 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); /** * mk_platform_device_allowed() - Check if a platform device is allowed by DTB allowlist diff --git a/kernel/multikernel/pci.c b/kernel/multikernel/pci.c index 2ddb47f2a1772d..acf949c7eb2cd0 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 0ff04f59d87a9572e3e5a8ee6f78a20295a483e4 Mon Sep 17 00:00:00 2001 From: Nikolay Nikolaev Date: Sun, 19 Jul 2026 03:01:43 +0300 Subject: [PATCH 4/4] x86/multikernel: isolate LAPIC timer policy Keep spawned kernels from rejecting their calibrated LAPIC timer. The host-owned platform timer cannot advance local jiffies. Keep the policy in arch/x86/multikernel and one predicate in APIC code. Signed-off-by: Nikolay Nikolaev --- arch/x86/include/asm/multikernel.h | 5 +++++ arch/x86/kernel/apic/apic.c | 4 +++- arch/x86/multikernel/Makefile | 2 +- arch/x86/multikernel/timer.c | 22 ++++++++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 arch/x86/multikernel/timer.c diff --git a/arch/x86/include/asm/multikernel.h b/arch/x86/include/asm/multikernel.h index 6b36dc557d0503..456f36ccb35106 100644 --- a/arch/x86/include/asm/multikernel.h +++ b/arch/x86/include/asm/multikernel.h @@ -71,11 +71,16 @@ struct mk_pci_host_bridge; #ifdef CONFIG_MULTIKERNEL void __init x86_multikernel_pci_platform_init(void); +bool x86_multikernel_skip_apic_timer_verify(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) { } +static inline bool x86_multikernel_skip_apic_timer_verify(void) +{ + return false; +} #endif #endif /* __ASSEMBLY__ */ diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c index d93f87f29d03b4..eb33a314b380c6 100644 --- a/arch/x86/kernel/apic/apic.c +++ b/arch/x86/kernel/apic/apic.c @@ -67,6 +67,7 @@ #include #include #include +#include #include "local.h" @@ -923,7 +924,8 @@ static int __init calibrate_APIC_clock(void) * timer based calibration, if a global clockevent device is * available. */ - if (!pm_referenced && global_clock_event) { + if (!pm_referenced && global_clock_event && + !x86_multikernel_skip_apic_timer_verify()) { apic_pr_verbose("... verify APIC timer\n"); /* diff --git a/arch/x86/multikernel/Makefile b/arch/x86/multikernel/Makefile index f4d399154f4ce6..dd6e311ef8a45f 100644 --- a/arch/x86/multikernel/Makefile +++ b/arch/x86/multikernel/Makefile @@ -3,4 +3,4 @@ # Makefile for multikernel spawn support # -obj-y += spawn.o pci.o direct_boot.o head_64.o +obj-y += spawn.o pci.o timer.o direct_boot.o head_64.o diff --git a/arch/x86/multikernel/timer.c b/arch/x86/multikernel/timer.c new file mode 100644 index 00000000000000..aaedeb5c490de7 --- /dev/null +++ b/arch/x86/multikernel/timer.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* x86 timer policy for multikernel spawn kernels. */ + +#include + +#include +#include +#include + +bool x86_multikernel_skip_apic_timer_verify(void) +{ + if (boot_params.hdr.hardware_subarch != X86_SUBARCH_MULTIKERNEL) + return false; + + /* + * A spawn kernel does not own the platform timer interrupt used by the + * legacy verification. Its LAPIC was calibrated against the TSC, while + * jiffies driven by the primary kernel's timer cannot advance here. + */ + pr_info("APIC timer: skipping legacy timer verification for multikernel\n"); + return true; +}