Skip to content

Commit aa4c417

Browse files
committed
feat(virtq): add virtqueue ring plumbing in scratch region
Place G2H and H2G packed virtqueue descriptor rings at deterministic offsets in the scratch region. Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
1 parent 55fbff4 commit aa4c417

21 files changed

Lines changed: 494 additions & 57 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_common/src/arch/aarch64/layout.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ pub const SNAPSHOT_PT_GVA_MIN: usize = 0xffff_8000_0000_0000;
2020
pub const SNAPSHOT_PT_GVA_MAX: usize = 0xffff_80ff_ffff_ffff;
2121
pub const MAX_GPA: usize = 0x0000_000f_ffff_ffff;
2222

23-
pub fn min_scratch_size(_input_data_size: usize, _output_data_size: usize) -> usize {
23+
pub fn min_scratch_size(
24+
_input_data_size: usize,
25+
_output_data_size: usize,
26+
_g2h_num_descs: usize,
27+
_h2g_num_descs: usize,
28+
) -> usize {
2429
unimplemented!("min_scratch_size")
2530
}

src/hyperlight_common/src/arch/amd64/layout.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,17 @@ pub const MAX_GPA: usize = 0x0000_000f_ffff_ffff;
3737
/// - A page for the smallest possible non-exception stack
3838
/// - (up to) 3 pages for mapping that
3939
/// - Two pages for the exception stack and metadata
40-
/// - A page-aligned amount of memory for I/O buffers (for now)
41-
pub fn min_scratch_size(input_data_size: usize, output_data_size: usize) -> usize {
42-
(input_data_size + output_data_size).next_multiple_of(crate::vmem::PAGE_SIZE)
40+
/// - A page-aligned amount of memory for I/O buffers and virtqueue rings
41+
pub fn min_scratch_size(
42+
input_data_size: usize,
43+
output_data_size: usize,
44+
g2h_num_descs: usize,
45+
h2g_num_descs: usize,
46+
) -> usize {
47+
let g2h_ring_size = crate::virtq::Layout::query_size(g2h_num_descs);
48+
let h2g_ring_size = crate::virtq::Layout::query_size(h2g_num_descs);
49+
50+
(input_data_size + output_data_size + g2h_ring_size + h2g_ring_size)
51+
.next_multiple_of(crate::vmem::PAGE_SIZE)
4352
+ 12 * crate::vmem::PAGE_SIZE
4453
}

src/hyperlight_common/src/arch/i686/layout.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ limitations under the License.
2020
pub const MAX_GVA: usize = 0xffff_ffff;
2121
pub const MAX_GPA: usize = 0xffff_ffff;
2222

23-
pub fn min_scratch_size(_input_data_size: usize, _output_data_size: usize) -> usize {
23+
pub fn min_scratch_size(
24+
_input_data_size: usize,
25+
_output_data_size: usize,
26+
_g2h_num_descs: usize,
27+
_h2g_num_descs: usize,
28+
) -> usize {
2429
crate::vmem::PAGE_SIZE
2530
}

src/hyperlight_common/src/layout.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,26 @@ pub use arch::{MAX_GPA, MAX_GVA};
3333
))]
3434
pub use arch::{SNAPSHOT_PT_GVA_MAX, SNAPSHOT_PT_GVA_MIN};
3535

36-
// offsets down from the top of scratch memory for various things
3736
pub const SCRATCH_TOP_SIZE_OFFSET: u64 = 0x08;
3837
pub const SCRATCH_TOP_ALLOCATOR_OFFSET: u64 = 0x10;
3938
pub const SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET: u64 = 0x18;
40-
pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x20;
39+
pub const SCRATCH_TOP_G2H_RING_GVA_OFFSET: u64 = 0x20;
40+
pub const SCRATCH_TOP_H2G_RING_GVA_OFFSET: u64 = 0x28;
41+
pub const SCRATCH_TOP_G2H_QUEUE_DEPTH_OFFSET: u64 = 0x30;
42+
pub const SCRATCH_TOP_H2G_QUEUE_DEPTH_OFFSET: u64 = 0x32;
43+
pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x40;
44+
45+
// fields must not overlap, and exception stack address must be 16-byte aligned.
46+
const _: () = {
47+
assert!(SCRATCH_TOP_SIZE_OFFSET + 8 <= SCRATCH_TOP_ALLOCATOR_OFFSET);
48+
assert!(SCRATCH_TOP_ALLOCATOR_OFFSET + 8 <= SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET);
49+
assert!(SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET + 8 <= SCRATCH_TOP_G2H_RING_GVA_OFFSET);
50+
assert!(SCRATCH_TOP_G2H_RING_GVA_OFFSET + 8 <= SCRATCH_TOP_H2G_RING_GVA_OFFSET);
51+
assert!(SCRATCH_TOP_H2G_RING_GVA_OFFSET + 8 <= SCRATCH_TOP_G2H_QUEUE_DEPTH_OFFSET);
52+
assert!(SCRATCH_TOP_G2H_QUEUE_DEPTH_OFFSET + 2 <= SCRATCH_TOP_H2G_QUEUE_DEPTH_OFFSET);
53+
assert!(SCRATCH_TOP_H2G_QUEUE_DEPTH_OFFSET + 2 <= SCRATCH_TOP_EXN_STACK_OFFSET);
54+
assert!(SCRATCH_TOP_EXN_STACK_OFFSET % 0x10 == 0);
55+
};
4156

4257
/// Offset from the top of scratch memory for a shared host-guest u64 counter.
4358
///
@@ -55,5 +70,30 @@ pub fn scratch_base_gva(size: usize) -> u64 {
5570
(MAX_GVA - size + 1) as u64
5671
}
5772

73+
/// Compute the byte offset from the scratch base to the G2H ring.
74+
///
75+
/// TODO(ring): Remove input/output
76+
pub const fn g2h_ring_scratch_offset(input_data_size: usize, output_data_size: usize) -> usize {
77+
let io_off = input_data_size + output_data_size;
78+
let align = crate::virtq::Descriptor::ALIGN;
79+
80+
(io_off + align - 1) & !(align - 1)
81+
}
82+
83+
/// Compute the byte offset from the scratch base to the H2G ring.
84+
///
85+
/// TODO(ring): Remove input/output
86+
pub const fn h2g_ring_scratch_offset(
87+
input_data_size: usize,
88+
output_data_size: usize,
89+
g2h_num_descs: usize,
90+
) -> usize {
91+
let g2h_offset = g2h_ring_scratch_offset(input_data_size, output_data_size);
92+
let g2h_size = crate::virtq::Layout::query_size(g2h_num_descs);
93+
let align = crate::virtq::Descriptor::ALIGN;
94+
95+
(g2h_offset + g2h_size + align - 1) & !(align - 1)
96+
}
97+
5898
/// Compute the minimum scratch region size needed for a sandbox.
5999
pub use arch::min_scratch_size;

src/hyperlight_common/src/outb.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub enum OutBAction {
105105
TraceMemoryAlloc = 105,
106106
#[cfg(feature = "mem_profile")]
107107
TraceMemoryFree = 106,
108+
/// Notification that entries are available on a virtqueue.
109+
VirtqNotify = 109,
108110
}
109111

110112
/// IO-port actions intercepted at the hypervisor level (in `run_vcpu`)
@@ -137,6 +139,7 @@ impl TryFrom<u16> for OutBAction {
137139
105 => Ok(OutBAction::TraceMemoryAlloc),
138140
#[cfg(feature = "mem_profile")]
139141
106 => Ok(OutBAction::TraceMemoryFree),
142+
109 => Ok(OutBAction::VirtqNotify),
140143
_ => Err(anyhow::anyhow!("Invalid OutBAction value: {}", val)),
141144
}
142145
}

src/hyperlight_common/src/virtq/desc.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,16 @@ pub struct Descriptor {
5959
}
6060

6161
const _: () = assert!(core::mem::size_of::<Descriptor>() == 16);
62+
const _: () = assert!(Descriptor::ALIGN == 16);
6263
const _: () = assert!(Descriptor::ADDR_OFFSET == 0);
6364
const _: () = assert!(Descriptor::LEN_OFFSET == 8);
6465
const _: () = assert!(Descriptor::ID_OFFSET == 12);
6566
const _: () = assert!(Descriptor::FLAGS_OFFSET == 14);
6667

6768
impl Descriptor {
69+
// VIRTIO spec requires 16-byte alignment for descriptors
70+
pub const ALIGN: usize = 16;
6871
pub const SIZE: usize = core::mem::size_of::<Self>();
69-
pub const ALIGN: usize = core::mem::align_of::<Self>();
7072

7173
pub const ADDR_OFFSET: usize = core::mem::offset_of!(Self, addr);
7274
pub const LEN_OFFSET: usize = core::mem::offset_of!(Self, len);

src/hyperlight_common/src/virtq/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ mod access;
154154
mod consumer;
155155
mod desc;
156156
mod event;
157+
pub mod msg;
157158
mod pool;
158159
mod producer;
159160
mod ring;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
Copyright 2026 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
//! Wire format header for all virtqueue messages.
18+
//!
19+
//! Every payload on both the G2H and H2G queues starts with this
20+
//! fixed 8-byte header, enabling message type discrimination and
21+
//! request/response correlation.
22+
23+
/// Message types for the virtqueue wire protocol.
24+
#[repr(u8)]
25+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26+
pub enum MsgKind {
27+
/// A function call request (FunctionCall payload follows).
28+
Request = 0x01,
29+
/// A function call response (FunctionCallResult payload follows).
30+
Response = 0x02,
31+
/// A stream data chunk.
32+
StreamChunk = 0x03,
33+
/// End-of-stream marker.
34+
StreamEnd = 0x04,
35+
/// Cancel a pending request.
36+
Cancel = 0x05,
37+
}
38+
39+
/// Wire header for all virtqueue messages
40+
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
41+
#[repr(C)]
42+
pub struct VirtqMsgHeader {
43+
/// Discriminates the message type.
44+
pub kind: u8,
45+
/// Per-type flags TODO(ring): add flags type.
46+
pub flags: u8,
47+
/// Caller-assigned correlation ID. Responses echo the request's ID.
48+
pub req_id: u16,
49+
/// Byte length of the payload following this header.
50+
pub payload_len: u32,
51+
}
52+
53+
impl VirtqMsgHeader {
54+
pub const SIZE: usize = core::mem::size_of::<Self>();
55+
56+
/// Create a new message header.
57+
pub const fn new(kind: MsgKind, req_id: u16, payload_len: u32) -> Self {
58+
Self {
59+
kind: kind as u8,
60+
flags: 0,
61+
req_id,
62+
payload_len,
63+
}
64+
}
65+
66+
/// Create a new header with flags.
67+
pub const fn with_flags(kind: MsgKind, flags: u8, req_id: u16, payload_len: u32) -> Self {
68+
Self {
69+
kind: kind as u8,
70+
flags,
71+
req_id,
72+
payload_len,
73+
}
74+
}
75+
}

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub mod host_comm;
5252
pub mod memory;
5353
#[cfg(target_arch = "x86_64")]
5454
pub mod paging;
55+
mod virtq_init;
5556

5657
// Globals
5758
#[cfg(all(feature = "mem_profile", target_arch = "x86_64"))]
@@ -235,6 +236,9 @@ pub(crate) extern "C" fn generic_init(
235236
OS_PAGE_SIZE = ops as u32;
236237
}
237238

239+
// Initialize virtqueues
240+
virtq_init::init_virtqueues();
241+
238242
// set up the logger
239243
let guest_log_level_filter =
240244
GuestLogFilter::try_from(max_log_level).expect("Invalid log level");

0 commit comments

Comments
 (0)