Skip to content

Fix packed-ring completion ordering and buffer ID#50

Merged
jserv merged 1 commit intomasterfrom
packed-ring-completion
Apr 29, 2026
Merged

Fix packed-ring completion ordering and buffer ID#50
jserv merged 1 commit intomasterfrom
packed-ring-completion

Conversation

@jserv
Copy link
Copy Markdown
Contributor

@jserv jserv commented Apr 29, 2026

Per Virtio 1.x packed virtqueue, the device must publish the buffer ID and used.len before flipping the USED flag, with a release barrier separating the body writes from the flag publish. Today virtio-blk and virtio-net XOR the USED bit in desc->flags with a plain store, never write desc->id, and on virtio-blk the desc->len write is also unfenced. A guest CPU can therefore observe USED set with stale len/id, and drivers that demux completions by buffer ID misbehave.

Each completion site now writes id (taken from the chain's last descriptor — Virtio 1.x packed places the buffer ID on the descriptor without F_NEXT, matching QEMU's virtqueue_packed_pop) and len, then publishes the new flags via __atomic_store_n with __ATOMIC_RELEASE on a value precomputed from a plain load. The slot is single-writer between AVAIL and USED, so the load is safe non-atomic, and the release store compiles to a plain mov on x86: no lock prefix. virtio-net tx explicitly sets desc->len = 0 since transmit chains have no device-writable bytes.

virtq_get_avail now reads desc->flags with __ATOMIC_ACQUIRE so addr/len/id reads after the AVAIL check observe the driver's release write. virtq_handle_avail does the same for guest_event->flags.

isr_status writes were a non-atomic read-modify-write shared between virtio-net rx and tx workers; concurrent updates could lose the QUEUE bit. Switch the writers to __atomic_fetch_or with __ATOMIC_RELEASE and the guest-side read-clear in virtio_pci_space_read to __atomic_exchange_n to zero with __ATOMIC_ACQUIRE so a worker's fetch_or that races the guest read is captured rather than dropped.


Summary by cubic

Fixes packed virtqueue completion ordering to publish buffer ID and used.len before flipping USED, per Virtio 1.x. Prevents guests from seeing stale len/id and avoids dropped QUEUE interrupts under concurrency.

  • Bug Fixes
    • virtio-blk: write used_desc->id from the chain tail, set used_desc->len, then release-store flags (USED bit).
    • virtio-net RX: update desc->len, release-store flags, and use atomic fetch_or to set QUEUE in isr_status.
    • virtio-net TX: set desc->len = 0, release-store flags, and use atomic fetch_or for isr_status.
    • virtq: acquire-load desc->flags in virtq_get_avail and guest_event->flags in virtq_handle_avail.
    • virtio-pci: read-and-clear isr_status with an atomic exchange to avoid losing concurrent updates.

Written for commit 4b26e80. Summary will update on new commits. Review in cubic

Per Virtio 1.x packed virtqueue, the device must publish the buffer ID
and used.len before flipping the USED flag, with a release barrier
separating the body writes from the flag publish. Today virtio-blk and
virtio-net XOR the USED bit in desc->flags with a plain store, never
write desc->id, and on virtio-blk the desc->len write is also unfenced.
A guest CPU can therefore observe USED set with stale len/id, and
drivers that demux completions by buffer ID misbehave.

Each completion site now writes id (taken from the chain's last
descriptor — Virtio 1.x packed places the buffer ID on the descriptor
without F_NEXT, matching QEMU's virtqueue_packed_pop) and len, then
publishes the new flags via __atomic_store_n with __ATOMIC_RELEASE on a
value precomputed from a plain load. The slot is single-writer between
AVAIL and USED, so the load is safe non-atomic, and the release store
compiles to a plain mov on x86: no lock prefix. virtio-net tx explicitly
sets desc->len = 0 since transmit chains have no device-writable bytes.

virtq_get_avail now reads desc->flags with __ATOMIC_ACQUIRE so
addr/len/id reads after the AVAIL check observe the driver's release
write. virtq_handle_avail does the same for guest_event->flags.

isr_status writes were a non-atomic read-modify-write shared between
virtio-net rx and tx workers; concurrent updates could lose the QUEUE
bit. Switch the writers to __atomic_fetch_or with __ATOMIC_RELEASE and
the guest-side read-clear in virtio_pci_space_read to __atomic_exchange_n
to zero with __ATOMIC_ACQUIRE so a worker's fetch_or that races the
guest read is captured rather than dropped.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

@jserv jserv merged commit 2231254 into master Apr 29, 2026
11 checks passed
@jserv jserv deleted the packed-ring-completion branch April 29, 2026 04:41
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.

1 participant