Skip to content

multikernel: use per-instance duplex IPI links - #7

Open
nickolaev wants to merge 1 commit into
multikernel:masterfrom
nickolaev:ipi-transport
Open

nickolaev wants to merge 1 commit into
multikernel:masterfrom
nickolaev:ipi-transport

Conversation

@nickolaev

@nickolaev nickolaev commented Aug 18, 2026 •

Copy link
Copy Markdown

Give each parent/child pair separate IPI rings and serialize local producers in each direction. Preserve queue cursors across detach, reset the selected link after confirmed park, and route messages to the parent recorded in the manifest.

Protect endpoint and handler lifetime with RCU. Keep receive callbacks in hardirq, with at most eight links and eight slots per link in each dispatch pass. Enable dispatch after built-in consumers register so boot-time messages can queue. Validate image/endpoint publication against the referenced instance under the instance mutex.

Parent and child kernels must use the same transport layout; ABI negotiation and SR-IOV changes are outside this PR.

Comment thread kernel/multikernel/ipi.c Outdated
*/
mk_arch_send_ipi(target);
if (ret == -ENOSPC)
pr_warn_ratelimited("multikernel: IPI ring full for instance %d\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code this replaces used printk_deferred for the ring-full case, with a comment explaining that console output reaches this path. That is still true: mktty_spawn_console_write() runs as a console ->write callback during printk flushing, holding mktty_console_lock with IRQs off, and calls multikernel_send_ipi_data(). If publish fails here, these plain printks recursively enter console flushing, which re-enters mktty_spawn_console_write() and self-deadlocks on mktty_console_lock. Ring full is most likely precisely during heavy console output, so this fires under exactly the wrong conditions. These messages should stay printk_deferred (ratelimiting can be kept via __ratelimit).

Comment thread kernel/kexec_core.c Outdated
}
rc = mk_arch_spawn_instance(mk_image, instance, cpu);
if (rc == 0) {
rc = mk_ipi_shared_wait_ready(instance->ipi_data, mk_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This waits up to 120 seconds while still holding the kexec lock taken by kexec_trylock() at the top of multikernel_kexec_by_id(). While a wedged spawn is timing out, every other kexec operation returns -EBUSY, and if the host panics in that window __crash_kexec() fails its trylock, so the crash kernel never boots and the vmcore is lost. Consider dropping the lock before the wait (the new MK_STATE_LOADED check already excludes concurrent exec of the same instance), or at least a much smaller default timeout.

Comment thread kernel/multikernel/ipi.c Outdated
ret = mk_ipi_shared_validate(instance->ipi_data);
if (ret)
return ret;
if (!atomic_read_acquire(&instance->ipi_data->ready))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior change worth confirming: previously the host could publish into the spawn ring while the spawn booted, and the spawn drained everything after init. Now sends fail with -EAGAIN until the spawn multikernel_init (subsys_initcall) marks the downlink ready, and the callers in messaging.c and mktty do not retry, so anything sent in that window is silently dropped. If this fail-closed behavior is intended it deserves a mention in the commit message; otherwise callers need a retry, or the ready gate needs to allow queuing during boot.

Comment thread kernel/multikernel/instance_dt.c Outdated
if (ret)
mk_manifest_reject_and_park(ret);
if (!atomic_read_acquire(&host_instance->ipi_data->ready) ||
READ_ONCE(host_instance->ipi_data->ready_instance_id) != 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires the host ring ready_instance_id to be exactly 0, but a mid-level kernel acting as a host marks its receive ring with its own root_instance->id, which is nonzero for a spawn. A nested spawn therefore always parks here with -EHOSTDOWN. Nesting already has known gaps, but this turns them into a hard block at the handshake.

Comment thread kernel/multikernel/core.c Outdated
return ret;
}

ret = mk_ipi_shared_mark_ready(root_instance->ipi_data,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If mk_instance_restore_from_manifest() fails to allocate the root instance (its -ENOMEM path returns with root_instance still NULL and does not park), this dereferences NULL at subsys_initcall time. A NULL check turning it into a clean init failure would be safer.

@nickolaev
nickolaev force-pushed the ipi-transport branch 3 times, most recently from 3330928 to 1a984b7 Compare August 23, 2026 18:40
@congwang-mk

Copy link
Copy Markdown

Why struct mk_elf_note_desc?

This grows the vmlinux MK ELF note from a bare u64 entry to {u64 entry; u32 ipi_abi_version; u32 reserved} only so the loader can refuse a spawn built against a different IPI ABI. That is now the fourth ABI stamp in this PR (XLF_MULTIKERNEL_IPI, this note, MK_BOOT_CONTEXT_MAGIC, MK_IPI_ABI_MAGIC/version/size, plus the FDT compatible bump). The spawn kernel already validates the context magic at boot; one version check there covers all of these. Please drop the struct and keep the note as the 8-byte entry, or fold the version into the note type if a load-time check is really wanted.

Note also that an older vmlinux with the 8-byte note now silently falls through to "entry not found" instead of a clear mismatch error like the bzImage path gives.

More generally the design looks over-complicated: the 64-bit owner+index producer gate with five slot states and cancel/recover logic, and the host-side 120s readiness poll with abort-on-timeout, could both be replaced by a plain per-slot ready flag plus "reset the ring after confirm_parked", which is what the old mk_ipi_ring_drop_pending did.

@congwang-mk

Copy link
Copy Markdown

@nickolaev Both the PR description and the commit message still contain "timer calibration". Also, there is a merge conflict.

@congwang-mk

Copy link
Copy Markdown

@nickolaev Please keep this PR to the duplex parent/child link fix (per-link rings, single producer, reset after confirm_parked, routing to the real parent) and drop the unrelated changes:

  • ABI stamps: XLF_MULTIKERNEL_IPI, MK_BOOT_CONTEXT_MAGIC, the new vmlinux note type
  • mk_cpu_set locking
  • kexec_file error path cleanup and the unload-while-not-parked check
  • bzImage hardware_subarch / +0x200 entry
  • manifest reject-and-park, mk_arch_prepare_park state tracking
  • force halt refactor
  • CONFIG_PCI stubs, depends on OF
  • moving IPI drain from hardirq to schedule_work() (this also needs a justification if resent)

Send any of these separately if still needed. Please also update the PR title and description.

Children currently publish into a shared parent inbox, while hardirq
sends depend on mutex-backed instance lookup. Give each parent/child
pair separate rings and serialize local producers in each direction.

Use RCU for endpoint and handler lifetime. Keep callbacks in hardirq,
bound each dispatch pass to eight links and eight slots per link, and
scan readiness for fair continuation. Enable dispatch after built-in
consumers register so messages can queue during boot.

Preserve cursors across detach and reset only the selected link after
confirmed park. Route to the manifest's parent ID, map its halt area
separately, and order publication before the APIC doorbell. Validate
image and endpoint publication under mk_instance_mutex against the
referenced instance so removal and ID reuse cannot attach a stale link.

Signed-off-by: Nikolay Nikolaev <nicknickolaev@gmail.com>
@nickolaev nickolaev changed the title multikernel: make IPI publication ordered and recoverable multikernel: use per-instance duplex IPI links Oct 7, 2026
@congwang-mk

Copy link
Copy Markdown

I am very concerned about mk_instance_publish_kimage().

Publishing a kimage is a kexec job: set instance->kimage, set LOADED, add it to the image list. Master already does that inline in kexec_file_load(). The IPI link is a different thing: it only makes sense while a kernel is running on the other end, so its lifetime is exec to halt, not load to unload. A loaded image is just bytes in the pool, nobody is there to receive or send.

This function ties the two together so the endpoint can be registered at load time, and that coupling causes real problems:

  1. The endpoint goes live on a dirty ring. The ring is allocated once and kept across unload/reload, and kimage_free_multikernel() does not reset it. On reload, mk_ipi_endpoint_register() kicks the local CPU and mk_ipi_drain_endpoint() dispatches whatever to_parent slots the previous kernel left ready (stale MK_SYS_HALTED, MK_SYS_SHUTDOWN_ACK, mktty data) under the new image's instance id, before any new kernel exists. Stale to_child slots make sends fail with -ENOSPC until exec. Calling mk_ipi_link_reset() in publish is not a fix either, because it clears parked[] and force_halt, which exec's mk_instance_confirm_parked() needs.
  2. Load now requires CPUs. Publish returns -EINVAL when instance->cpus is empty. Load used to succeed and exec enforced the CPU requirement.
  3. It is wasted work. multikernel_kexec_by_id() already unregisters, confirms parked, resets the link and registers again with a fresh doorbell, so anything queued while LOADED is discarded and the RX side can only ever see stale data.
  4. IPI teardown leaks into the kexec free path (mk_ipi_endpoint_unregister() in kimage_free_multikernel()).

None of the problems this PR fixes (single producer per ring, halt notices to the real parent, RCU handler lifetime) need anything at load time.

Suggested change:

  • Drop mk_instance_publish_kimage() and leave kexec_file_load() as on master.
  • Remove mk_ipi_endpoint_unregister() from kimage_free_multikernel().
  • Attach the endpoint only in multikernel_kexec_by_id() (confirm parked, reset, register), which the PR already does, and detach on halt, exec or destroy. A send to a LOADED but not running instance then fails with -ENODEV, which is the correct answer.

The destroy vs. load ID reuse check (idr_find() == instance under mk_instance_mutex) is a real race, but it is unrelated to the IPI transport. Please send it as a separate small patch.

Minor: a publish failure returns without any pr_err, and image->mk_instance != instance can never be true for the only caller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants