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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 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,23 @@ 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);
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__ */

#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
4 changes: 3 additions & 1 deletion arch/x86/kernel/apic/apic.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
#include <asm/intel-family.h>
#include <asm/irq_regs.h>
#include <asm/cpu.h>
#include <asm/multikernel.h>

#include "local.h"

Expand Down Expand Up @@ -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");

/*
Expand Down
1 change: 1 addition & 0 deletions 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
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 timer.o direct_boot.o head_64.o
169 changes: 169 additions & 0 deletions arch/x86/multikernel/pci.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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/pci.h>

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

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_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)
{
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;
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;
}
#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;
}
22 changes: 22 additions & 0 deletions arch/x86/multikernel/timer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0-only
/* x86 timer policy for multikernel spawn kernels. */

#include <linux/printk.h>

#include <asm/bootparam.h>
#include <asm/multikernel.h>
#include <asm/setup.h>

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;
}
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
11 changes: 0 additions & 11 deletions drivers/pci/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
#include <linux/irqdomain.h>
#include <linux/pm_runtime.h>
#include <linux/bitfield.h>
#ifdef CONFIG_MULTIKERNEL
#include <linux/multikernel.h>
#endif
#include "pci.h"

#define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */
Expand Down Expand Up @@ -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
Expand Down
Loading