Skip to content
13 changes: 13 additions & 0 deletions arch/x86/include/asm/multikernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#ifndef __ASSEMBLY__

#include <linux/init.h>
#include <linux/types.h>
#include <asm/bootparam.h>
#include <asm/page_types.h>
Expand Down Expand Up @@ -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 */
4 changes: 4 additions & 0 deletions arch/x86/include/asm/pci_x86.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

/*
Expand Down
5 changes: 4 additions & 1 deletion arch/x86/kernel/platform-quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -171,7 +172,9 @@ void __init x86_early_init_platform_quirks(void)
* the PIT - which belongs to the host - and then request
* legacy IRQ0, which can never reach an instance CPU that
* has neither a PIC nor an IO-APIC. Ticks come from the
* local APIC timer via setup_percpu_clockev() instead.
* local APIC timer initialized by setup_percpu_clockev().
* Keeping global_clock_event unset bypasses LAPIC timer
* verification, whose fallback path requires legacy IRQ0.
*/
x86_init.timers.timer_init = x86_init_noop;
x86_init.timers.wallclock_init = x86_init_noop;
Expand Down
2 changes: 1 addition & 1 deletion arch/x86/multikernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
286 changes: 286 additions & 0 deletions arch/x86/multikernel/pci.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
// 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 <linux/init.h>
#include <linux/multikernel.h>
#include <linux/panic.h>
#include <linux/pci.h>

#include <asm/multikernel.h>
#include <asm/pci.h>
#include <asm/pci_x86.h>
#include <asm/topology.h>
#include <asm/x86_init.h>

#ifdef CONFIG_PCI_MMCONFIG
struct mk_mmcfg_snapshot {
const struct mk_instance *instance;
struct mk_pci_host_bridge *bridges;
size_t capacity;
size_t count;
};

static struct pci_ops mk_pci_native_ops;
static bool mk_pci_roots_ready;

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(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)) {
*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))
return PCIBIOS_DEVICE_NOT_FOUND;

return mk_pci_native_ops.write(bus, devfn, where, size, value);
}

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 (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 = device->bus;
snapshot->bridges[snapshot->count].bus_end = device->bus;
snapshot->bridges[snapshot->count].ecam_base = region->address;
pr_info("Multikernel publishing synthetic PCI root %04x:%02x ECAM base %#llx\n",
region->segment, device->bus,
(unsigned long long)region->address);
snapshot->count++;
}

return 0;
}

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,
.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);
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 0;
}
if (!list_empty(&pci_mmcfg_list)) {
pr_err("Multikernel PCI host bridge list was not empty before restore\n");
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)) {
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 0;
}

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_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,
size_t capacity)
{
return 0;
}

static int __init x86_multikernel_pci_arch_init(void)
{
pr_err("Multikernel PCI requires CONFIG_PCI_MMCONFIG\n");
return 0;
}

static int __init x86_multikernel_pci_init(void)
{
return 0;
}
#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 = x86_multikernel_pci_init;
}
19 changes: 19 additions & 0 deletions arch/x86/pci/mmconfig-shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading