Skip to content

feat(virtq): add packed virtio ring primitives#1382

Open
andreiltd wants to merge 7 commits intohyperlight-dev:mainfrom
andreiltd:tandr/virtq-1-ring
Open

feat(virtq): add packed virtio ring primitives#1382
andreiltd wants to merge 7 commits intohyperlight-dev:mainfrom
andreiltd:tandr/virtq-1-ring

Conversation

@andreiltd
Copy link
Copy Markdown
Member

@andreiltd andreiltd commented Apr 16, 2026

Add low-level packed virtqueue ring implementation in hyperlight_common::virtq, based on the virtio packed ring format.

This is split from: #1368 and does not include any actual plumbing for guest/host communication.

Useful materials:

@andreiltd andreiltd force-pushed the tandr/virtq-1-ring branch from 3db3820 to bd2fd96 Compare April 16, 2026 10:20
@andreiltd andreiltd added the kind/enhancement For PRs adding features, improving functionality, docs, tests, etc. label Apr 16, 2026
@andreiltd andreiltd force-pushed the tandr/virtq-1-ring branch 2 times, most recently from b4c8142 to 16de50c Compare April 16, 2026 10:30
Copy link
Copy Markdown
Contributor

@dblnz dblnz left a comment

Choose a reason for hiding this comment

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

Great stuff, @andreiltd !
I left a few comments, most are small remarks, others are things I might have missed.
This is part 1 of my review, I still have some things left to look at that I plan on doing tomorrow.

Comment thread src/hyperlight_common/src/virtq/access.rs Outdated
Comment thread src/hyperlight_common/src/virtq/access.rs Outdated
Comment thread src/hyperlight_common/src/virtq/desc.rs Outdated
Comment thread src/hyperlight_common/src/virtq/desc.rs Outdated
Comment thread src/hyperlight_common/src/virtq/desc.rs Outdated
Comment thread src/hyperlight_common/src/virtq/event.rs Outdated
Comment thread src/hyperlight_common/src/virtq/ring.rs
/// and readable buffers must be added before writable buffers.
#[derive(Debug, Default)]
pub struct BufferChainBuilder<T> {
elems: SmallVec<[BufferElement; 16]>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe documenting this 16 inline capacity as an optimization for the user to be aware

Comment thread src/hyperlight_common/src/virtq/ring.rs Outdated
Comment thread src/hyperlight_common/src/virtq/ring.rs Outdated
@andreiltd andreiltd force-pushed the tandr/virtq-1-ring branch 4 times, most recently from 857f4bf to 2c6adcb Compare April 22, 2026 13:09
Comment thread src/hyperlight_common/src/virtq/access.rs
Comment thread src/hyperlight_common/src/virtq/access.rs
Comment thread src/hyperlight_common/src/virtq/ring.rs
Comment thread src/hyperlight_common/src/virtq/ring.rs
@andreiltd andreiltd force-pushed the tandr/virtq-1-ring branch 3 times, most recently from 79ee809 to 141fc6b Compare April 27, 2026 12:32
///
/// - [`RingError::WouldBlock`] - No free descriptor slots
/// - [`RingError::OutOfMemory`] - No free descriptor IDs (internal error)
/// - [`RingError::InvalidState`] - ID tracking corrupted (internal error)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This API can also return RingError::MemError

Add low-level packed virtqueue ring implementation in
hyperlight_common::virtq, based on the virtio packed ring format.

Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
@andreiltd andreiltd force-pushed the tandr/virtq-1-ring branch from 141fc6b to e27dacb Compare April 27, 2026 14:06
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Copy link
Copy Markdown
Contributor

@ludfjig ludfjig left a comment

Choose a reason for hiding this comment

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

This PR is very clean, big fan!

I know we don't really have policy on asserts vs errors etc in hyperlight (I personally like asserts...), but ring.rs has 3 debug_assert! that could be useful in release mode as well, and they're already in methods that return Results. Do you think it makes sense to convert them to errros instead?

Comment on lines +187 to +198
/// Add multiple readable buffers from an iterator.
pub fn readables(
mut self,
elements: impl IntoIterator<Item = impl Into<BufferElement>>,
) -> Self {
for elem in elements {
self.elems.push(elem.into());
self.split += 1;
}

self
}
Copy link
Copy Markdown
Contributor

@ludfjig ludfjig May 5, 2026

Choose a reason for hiding this comment

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

should we set elem.writeable = false here in case a caller passes writeable? Or error out?

elements: impl IntoIterator<Item = impl Into<BufferElement>>,
) -> BufferChainBuilder<Writable> {
for elem in elements {
self.elems.push(elem.into());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same here but vice versa, shouldwe set writable = true?

///
/// `ids` contains the descriptor IDs that are in-flight.
/// Sets cursors, counters, and `id_num` accordingly. The chain lengths are all set to 1.
pub fn reset_prefilled(&mut self, ids: &[u16]) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if a ring has size 8 and we call reset_prefilled(&[4,5,6,7]), and call producer.submit_one(addr, len, false), it will fail because self.id_free is empty, I'm not sure this is correct?

}

// Record chain length for later used submission
self.id_num[id as usize] = chain_len;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is overwritten unconditionally, should we check that this id is not 0 like the producer already does:

// We should never reuse an ID that is still outstanding
if self.id_num[id as usize] != 0 {
    return Err(RingError::InvalidState);
}

Comment on lines +86 to +89
#[inline]
const fn align_up(val: usize, align: usize) -> usize {
(val + align - 1) & !(align - 1)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: maybe next_multiple_of() can be used instead?

Comment on lines +502 to +503
desc.write_release(&self.mem, addr)
.map_err(|_| RingError::MemError)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: lot's of map_err lose their context when going to RingError::MemError, maybe can parametrize RingError<E> on generic E which is some M::Error? Not sure, feel free to ignore

const NEXT = 1 << 0;
/// This marks a buffer as device write-only (otherwise device read-only).
const WRITE = 1 << 1;
/// This means the buffer contains a list of buffer descriptors (unsupported here).
Copy link
Copy Markdown
Contributor

@ludfjig ludfjig May 5, 2026

Choose a reason for hiding this comment

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

nit: should we add runtimes check for this unsupported?

let head_idx = self.avail_cursor.head();
let head_wrap = self.avail_cursor.wrap();

let id = self.id_free.pop().ok_or(RingError::InvalidState)?;
Copy link
Copy Markdown
Contributor

@ludfjig ludfjig May 5, 2026

Choose a reason for hiding this comment

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

nit: this uses RingError::InvalidState but submit_one uses another error for the same error condition

self.elems.as_slice()
}

/// Get writable buffers in chain
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo writable -> readable

Comment on lines +1067 to +1069
// addr is unused for used descriptor according to packed-virtqueue spec
let mut used_desc = Descriptor::new(0, 0, id, DescFlags::empty());
used_desc.len = written_len;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why not pass written_len to new(..) directly?

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

Labels

kind/enhancement For PRs adding features, improving functionality, docs, tests, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants