Skip to content

multikernel: support isolated SR-IOV VF assignment - #4

Draft
nickolaev wants to merge 13 commits into
multikernel:masterfrom
nickolaev:sriov-vf-assignment
Draft

multikernel: support isolated SR-IOV VF assignment#4
nickolaev wants to merge 13 commits into
multikernel:masterfrom
nickolaev:sriov-vf-assignment

Conversation

@nickolaev

@nickolaev nickolaev commented Jul 22, 2026

Copy link
Copy Markdown

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:

  1. Adds PCI identity, BAR resources, and host-bridge information to each Multikernel instance.
  2. Routes spawned-kernel PCI configuration accesses through the primary, which validates the sender and active VF lease before accessing hardware.
  3. Exposes assigned devices through synthetic PCI roots instead of enumerating shared physical bridges.
  4. Filters raw x86 PCI configuration operations so only assigned functions can be requested through the host control plane.
  5. Adds exclusive SR-IOV VF leases while keeping the PF bound to its host driver.
  6. Makes memory, CPU, PCI, and host-bridge reservation atomic, with reverse-order unwind on failure.
  7. Creates a host-owned IOMMU paging domain for each assigned VF and maps only memory owned by the instance.
  8. Quiesces and resets the VF before release and before instance restart, then restores its original host driver on release.
  9. Forces an instance to halt on unexpected PF or VF loss.
  10. Passes the primary kernel's timer calibration state to spawned x86 kernels.
  11. Keeps MSI/MSI-X allocation and interrupt-remapping ownership in the primary and forwards assigned-vector interrupts to the spawned kernel.

The resulting ownership model is:

  • the primary kernel retains the PF and controls the VF assignment lifecycle;
  • only SR-IOV VFs can be assigned;
  • each VF is exclusively owned by one Multikernel instance;
  • the primary owns the VF's IOMMU domain and DMA mappings;
  • only instance-owned memory is reachable through VF DMA;
  • the secondary sees its assigned VF through a synthetic PCI root;
  • the PF, physical bridges, and unrelated PCI functions are not enumerated through normal PCI paths;
  • normal PCI configuration accesses are serviced by the primary after sender and active-lease validation;
  • the primary allocates assigned-device MSI/MSI-X vectors and forwards their interrupts to the secondary;
  • VF identity and BAR resources survive the handoff through owned instance metadata; and
  • teardown stops DMA before returning the VF to the host.

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-assignment branch 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_64 using Q35, IGB SR-IOV, Intel IOMMU,
interrupt remapping, and strict IOMMU mode. It has not yet been tested on real
SR-IOV hardware.

@congwang-mk

Copy link
Copy Markdown

Hi @nickolaev

Nice work, thanks for the PR! I finally have time to look into it.

A few high-level comments:

  1. If I understand it correctly, it only works with IOMMU off? This looks a big limitation.
  2. VF lifetime is coupled to the PF driver with no coordination
  3. Exposed bridges are shared and mutable?
  4. For the timer fix, it would be better if we could use existing x86 quirks.

@nickolaev

Copy link
Copy Markdown
Author

@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.

@nickolaev
nickolaev force-pushed the sriov-vf-assignment branch 2 times, most recently from 8d8960b to 4e05ab7 Compare July 30, 2026 16:41
@nickolaev

Copy link
Copy Markdown
Author

@congwang-mk I have updated the commit history with the revised work and the PR message reflects the new state.
Please take a look when you can.

@nickolaev
nickolaev force-pushed the sriov-vf-assignment branch 3 times, most recently from b2d1b55 to 64608bc Compare July 31, 2026 20:52
@congwang-mk

Copy link
Copy Markdown

@congwang-mk I have updated the commit history with the revised work and the PR message reflects the new state. Please take a look when you can.

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).

@congwang-mk

congwang-mk commented Aug 3, 2026

Copy link
Copy Markdown

Thanks for the update! The layering is a clear improvement: synthetic roots instead of physical bridges, manifest-owned BAR/identity metadata, pci_set_dev_assigned() for the SR-IOV interlock, and prepare/commit/rollback. Main things I think need an answer before this lands:

  1. Interrupts are not in the ownership model. The spawn kernel calls disable_acpi(), so no DMAR, no IR, no IO-APIC: it composes compatibility-format MSIs. But mk_pci_iommu_validate_group() requires iommu_group_has_isolated_msi(), i.e. host IR is on, and Linux blocks compat-format interrupts in that case. Nothing allocates an IRTE for the VF. Who owns interrupt remapping for an assigned VF?

  2. Fail-closed on IOMMU faults does not work on Intel. report_iommu_fault() is only called by amd/arm-smmu and the embedded drivers; VT-d faults go through dmar_fault(), which just logs. mk_pci_iommu_fault() is dead code on the platform you tested.

  3. Teardown deadlock. mk_pci_release_assignment() calls cancel_work_sync(&failure_work) on a path holding mk_instance_mutex (via mk_instance_destroy()), while the work reaches mk_instance_find() through mk_send_message() and takes that same mutex.

  4. No VF reset across instance restart. Quiesce runs only at lease release, so a force-halted and restarted instance gets a VF with bus mastering possibly still on and DMA in flight into memory the new boot reuses.

  5. The early fixup fires on the host. mk_pci_restore_resources() is PCI_ANY_ID and gated only on root_instance->dtb_data, which baseline sets on the host too. resources_valid is set at commit and never cleared on release, so after one assign/release cycle a host rescan of that VF gets non_compliant_bars = true and stale BARs.

  6. Filter is one layer too high. raw_pci_ops/raw_pci_ext_ops are left unfiltered and only pci_root_ops is wrapped. On x86 everything funnels through raw_pci_read(), so filtering there would cover both and make "before hardware access" accurate.

  7. Framing. An instance is a full kernel with its own CR3: it can map ECAM directly (bus-granular, so it covers sibling VFs including other instances'), use CF8/CFC, or map any physical page. The config filter prevents accidents, not attacks. The IOMMU domain is the one real boundary, and it constrains the device rather than the instance. Worth saying so explicitly.

@congwang-mk

Copy link
Copy Markdown

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:

  • Static facts (BAR resources, identity, ECAM geometry): manifest, as you already do. This is the strongest part of the series.
  • Shared control planes (config space, MSI vector allocation and IRTE programming): host-mediated over the message ring. Config access is not a fast path, and mediating it makes the filter enforceable rather than advisory. For MSI, the host allocates vectors targeting instance CPUs, programs the IRTEs, and returns address/data; the instance's MSI domain becomes a thin client. That is what a VMM does for a guest, expressed as a host-kernel service instead of emulation.

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.

@nickolaev
nickolaev force-pushed the sriov-vf-assignment branch from 5efa61c to 75e1053 Compare August 4, 2026 07:41
@nickolaev

Copy link
Copy Markdown
Author

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:

  • Static facts (BAR resources, identity, ECAM geometry): manifest, as you already do. This is the strongest part of the series.
  • Shared control planes (config space, MSI vector allocation and IRTE programming): host-mediated over the message ring. Config access is not a fast path, and mediating it makes the filter enforceable rather than advisory. For MSI, the host allocates vectors targeting instance CPUs, programs the IRTEs, and returns address/data; the instance's MSI domain becomes a thin client. That is what a VMM does for a guest, expressed as a host-kernel service instead of emulation.

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.

Thanks for the review @congwang-mk
I have updated the branch to handle all the point except the interrupt ownership and the proposed host-mediated PCI configuration. Let me run sme more tests and see if these can fit on top of this branch or we want a follow-up.

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>
@nickolaev
nickolaev force-pushed the sriov-vf-assignment branch from 1e25d2f to a3db37b Compare August 5, 2026 09:13
@nickolaev

Copy link
Copy Markdown
Author

OK, so the host-mediated PCI config is now in place and IRQ ownership is resolved.

@congwang-mk congwang-mk left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread kernel/multikernel/core.c
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread kernel/multikernel/ipi.c
tail = atomic_read(&ring->tail);

slot = &root_instance->ipi_data->ring.entries[tail];
for (scanned = 0; scanned < MK_IPI_RING_SIZE; scanned++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread drivers/pci/msi/msi.c
{
struct pci_dev *dev = msi_desc_to_pci_dev(entry);

if (mk_pci_msi_write_msg(dev, entry->msi_index, entry->irq,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread kernel/multikernel/pci.c
instance = mk_instance_find(irq_work->request.sender_instance_id);
if (!instance)
goto out;
mk_cpu_ownership_lock();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread kernel/multikernel/pci.c
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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 */

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I am wondering why you have to handle clock in this PR? If anything is wrong with clock, please separate it out

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