multikernel: support isolated SR-IOV VF assignment - #4
Conversation
b715339 to
0ff04f5
Compare
|
Hi @nickolaev Nice work, thanks for the PR! I finally have time to look into it. A few high-level comments:
|
|
@congwang-mk thanks for the high-level review. Indeed, this is a naive implementation of "let's make ICMP pass"; It is very fragile as it is. The questions you asked are setting a proper direction. I have some ideas; let me try to clean things up and will ping you once I have a better patch series. |
8d8960b to
4e05ab7
Compare
|
@congwang-mk I have updated the commit history with the revised work and the PR message reflects the new state. |
b2d1b55 to
64608bc
Compare
Thank you for doing it. I will take a look during the weekend, since I am still working on the refactoring to prepare for ARM/RISCV (which is also why you got merge conflicts). |
64608bc to
5efa61c
Compare
|
Thanks for the update! The layering is a clear improvement: synthetic roots instead of physical bridges, manifest-owned BAR/identity metadata,
|
|
Separate from the item list above, a thought about the shape rather than the bugs. SR-IOV VFs are built assuming something mediates. VF config space is impoverished by design because a hypervisor is expected to synthesize the missing parts from the PF's SR-IOV capability. Multikernel's premise is that there is no mediator, so the series meets a mediator-shaped hole and fills it two ways: the manifest for BARs and identity, which works because those are static, and a config-op filter for everything else, which does not, because the filter lives inside the kernel it is meant to constrain. The interrupt gap is the same mismatch surfacing where it cannot be papered over. But multikernel already has the right mechanism: a host that owns global state and a message ring to reach it. That suggests splitting along the axis that actually exists:
Keep the synthetic roots and the IOMMU domain either way. The identity-mapped domain in particular fits well: with no second-level translation it range-restricts a device rather than virtualizing an address space, which is simpler than what VFIO needs. The obvious cost is latency on mediated config access, and I have not measured what that does to something like igbvf at probe time. Worth a quick experiment before committing. |
5efa61c to
75e1053
Compare
Thanks for the review @congwang-mk |
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
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 <nicknickolaev@gmail.com>
1e25d2f to
a3db37b
Compare
|
OK, so the host-mediated PCI config is now in place and IRQ ownership is resolved. |
congwang-mk
left a comment
There was a problem hiding this comment.
Code review of the SR-IOV VF assignment series. 8 findings inline: 3 that look like real breakage (link failure, atomic-context deadlock, use-after-free), 4 medium, 1 cleanup.
| return ret; | ||
| } | ||
|
|
||
| bool mk_pci_msi_controlled(struct pci_dev *dev) |
There was a problem hiding this comment.
Link failure when CONFIG_PCI_MMCONFIG=n. The five mk_pci_msi_* functions (230, 238, 271, 285, 306) are inside the #ifdef CONFIG_PCI_MMCONFIG block; the #else arm only re-provides mk_arch_snapshot_pci_host_bridges and the x86_init.pci hooks. multikernel.h declares them unconditionally under MULTIKERNEL && X86 && PCI, and drivers/pci/msi/msi.c:244 + irqdomain.c:15,19,31,34,42 call them unconditionally.
PCI_MMCONFIG depends on ACPI || JAILHOUSE_GUEST, so a spawn kernel with CONFIG_ACPI=n (what this series targets) fails to link vmlinux with five undefined references.
| goto out; | ||
|
|
||
| /* Pairs with the response handler's publication of status and value. */ | ||
| while (!smp_load_acquire(&pending.done)) { |
There was a problem hiding this comment.
Synchronous RPC re-dispatches the whole IPI ring from atomic context. mk_pci_remote_config() and mk_pci_send_irq_request() (:214) busy-wait up to 1s in mk_poll_ipi_messages(), which drains the ring and runs every handler, including mk_pci_irq_forward_handler() -> generic_handle_irq_safe() -> the assigned device's ISR.
Both callers run IRQs-off under a raw spinlock: config accesses come via pci_bus_read/write_config_* holding pci_lock; mk_pci_msi_bind() is reached from __pci_write_msi_msg() under desc->lock with IRQs off during irq_startup().
Deadlock: driver probe does pci_read_config_word(), the poll dispatches a forwarded MSI, that ISR issues a config read -> self-deadlock on the non-recursive pci_lock, same CPU. Even without re-entry, spinning 1s IRQs-off trips the hard-lockup watchdog.
| 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); |
There was a problem hiding this comment.
Instance freed after failed resource release -> use-after-free. This only WARN_ON_ONCE()s the error, then kfree(instance) two lines down.
On failure mk_pci_release_assignments() (kernel/multikernel/pci.c:1697-1717) breaks out of its loop, leaving the mk_pci_assignment linked on both instance->pci_assignments (now freed) and the global active list, with assignment->instance dangling. A VF whose FLR times out during teardown gets there; the next BUS_NOTIFY_UNBOUND_DRIVER runs mk_pci_assignment_failure_work(), which dereferences assignment->instance->state and ->name.
mk_instance_destroy() and mk_create_instance_from_dtb() do honor this error; the kref path is the outlier.
| tail = atomic_read(&ring->tail); | ||
|
|
||
| slot = &root_instance->ipi_data->ring.entries[tail]; | ||
| for (scanned = 0; scanned < MK_IPI_RING_SIZE; scanned++) { |
There was a problem hiding this comment.
Ring consumption is no longer FIFO. The new drain scans all MK_IPI_RING_SIZE slots from tail and consumes anything READY, skipping WRITING. Slot order no longer equals delivery order: A claims slot 5, B claims 6, B publishes first -> 6 is delivered before 5.
mk_vsock_ipi_handler() turns each payload into an skb on a byte-stream socket, so two concurrent senders now corrupt the stream rather than just delaying it. The goal here (an interrupted producer must not block others) is reachable without breaking ordering in the common case.
| { | ||
| struct pci_dev *dev = msi_desc_to_pci_dev(entry); | ||
|
|
||
| if (mk_pci_msi_write_msg(dev, entry->msi_index, entry->irq, |
There was a problem hiding this comment.
MSI bind failure is swallowed, leaving a silently dead vector. mk_pci_msi_write_msg() (arch/x86/multikernel/pci.c:271) logs the mk_pci_msi_bind() error with pr_err_ratelimited() and then returns true unconditionally, so __pci_write_msi_msg() stores entry->msg and returns as if programmed.
On RPC timeout or -ESTALE/-ENODEV, pci_alloc_irq_vectors() and request_irq() both succeed while the host never enabled the corresponding vector. The VF driver then waits forever for an interrupt that never arrives, with no error visible to it.
| instance = mk_instance_find(irq_work->request.sender_instance_id); | ||
| if (!instance) | ||
| goto out; | ||
| mk_cpu_ownership_lock(); |
There was a problem hiding this comment.
CPU-ownership mutex held across sleeping MSI work. This holds mk_cpu_ownership_lock() across mk_pci_irq_access(), which can msleep(MK_PCI_FLR_SETTLE_MS + 1), pci_alloc_irq_vectors(), request_irq() and free_irq() (synchronize_irq). mk_pci_cfg_work_fn() (:505) does the same.
Every mk_instance_transfer_cpus() / mk_send_cpu_add() / mk_send_cpu_remove() on any instance stalls behind an unrelated instance's MSI setup, and the requester's own 1s deadline can expire while the host is still in the FLR sleep -> spurious -ETIMEDOUT. Only the mk_pci_request_route_stale check actually needs the ownership lock.
| 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, |
There was a problem hiding this comment.
Forwarded MSI silently dropped on allocation/backpressure failure. This hardirq handler forwards via mk_send_message_to_instance() -> __mk_send_message(), which does a kzalloc(GFP_ATOMIC) per interrupt and returns -ENOSPC from mk_ipi_ring_claim_slot() when the 64-slot ring is full. The handler only pr_warn_ratelimited()s and returns IRQ_HANDLED.
Edge-triggered MSI never re-asserts, so under memory pressure or ring backpressure the spawn kernel permanently loses that completion (e.g. an igbvf TX/RX cleanup interrupt) and the queue hangs. No retry, coalescing, or pending-interrupt fallback.
| return 0; | ||
| } | ||
|
|
||
| raw_pci_ops = &pci_mmcfg; |
There was a problem hiding this comment.
Dead stores; the "saved backend" does not exist. raw_pci_ops/raw_pci_ext_ops are set to &pci_mmcfg and immediately overwritten by the filtered ops, and nothing captures pci_mmcfg anywhere.
The commit message says accepted accesses are "forwarded to the saved backend", but mk_pci_raw_read/write always go out as a host RPC. The ECAM windows registered by pci_mmconfig_add() and mapped by pci_mmcfg_arch_init() just above are therefore mapped and never used. Either wire the local backend up or drop the ECAM mapping and these two stores, since as written it reads like there is a local fast path.
| 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 */ |
There was a problem hiding this comment.
I am wondering why you have to handle clock in this PR? If anything is wrong with clock, please separate it out
Multikernel instances currently lack a complete PCI ownership model. A spawned
x86 kernel needs enough platform and device state to enumerate and operate its
assigned device without exposing or disturbing devices retained by the primary
kernel.
This series adds end-to-end assignment of an SR-IOV VF to a spawned kernel while
the primary retains the PF and enforces DMA isolation.
The 13-patch series:
The resulting ownership model is:
Assignment requires an active hardware IOMMU, a singleton IOMMU group,
host-side isolated-MSI capability, Function Level Reset support, and a DMA
aperture capable of covering every instance memory region. Instance memory
cannot be changed while an IOMMU-backed VF lease is active.
The spawned kernel remains a privileged, cooperative kernel. The host-mediated
PCI configuration path prevents accidental access through normal in-kernel
paths; it is not a security boundary against a spawned kernel deliberately
accessing physical configuration mechanisms. The host-owned IOMMU domain is the
hardware boundary for device DMA.
Intel VT-d blocks and logs out-of-domain DMA faults through its normal fault
path, but this series does not claim portable automatic instance failure on
IOMMU faults.
This work can be demonstrated using the
sriov-vf-assignmentbranch here:https://github.com/nickolaev/multikernel/tree/sriov-vf-assignment
The QEMU harness verifies host-mediated PCI configuration and MSI-X delivery,
VF enumeration and traffic in the spawned kernel, continued PF operation in the
primary, IOMMU-domain setup, exclusive ownership, adverse lifecycle operations,
same-instance restart with VF reset, repeated create/delete cycles, driver
restoration, and unexpected VF loss handling.
It has been tested with
qemu-system-x86_64using Q35, IGB SR-IOV, Intel IOMMU,interrupt remapping, and strict IOMMU mode. It has not yet been tested on real
SR-IOV hardware.