Skip to content

Commit 7d5c649

Browse files
committed
feat(virtio-mem): add virtio request parsing and dummy response
Parse virtio requests over the queue and always ack them. Following commits will add the state management inside the device. Signed-off-by: Riccardo Mancini <[email protected]>
1 parent 31470aa commit 7d5c649

File tree

4 files changed

+326
-9
lines changed

4 files changed

+326
-9
lines changed

src/vmm/src/devices/virtio/mem/device.rs

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::sync::atomic::AtomicU32;
99
use log::info;
1010
use serde::{Deserialize, Serialize};
1111
use vm_memory::{
12-
Address, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryRegion, GuestUsize,
12+
Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryRegion, GuestUsize,
1313
};
1414
use vmm_sys_util::eventfd::EventFd;
1515

@@ -20,12 +20,15 @@ use crate::devices::virtio::device::{ActiveState, DeviceState, VirtioDevice};
2020
use crate::devices::virtio::generated::virtio_config::VIRTIO_F_VERSION_1;
2121
use crate::devices::virtio::generated::virtio_ids::VIRTIO_ID_MEM;
2222
use crate::devices::virtio::generated::virtio_mem::{
23-
VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE, virtio_mem_config,
23+
self, VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE, virtio_mem_config,
2424
};
2525
use crate::devices::virtio::iov_deque::IovDequeError;
2626
use crate::devices::virtio::mem::metrics::METRICS;
27+
use crate::devices::virtio::mem::request::{BlockRangeState, Request, RequestedRange, Response};
2728
use crate::devices::virtio::mem::{VIRTIO_MEM_DEV_ID, VIRTIO_MEM_GUEST_ADDRESS};
28-
use crate::devices::virtio::queue::{FIRECRACKER_MAX_QUEUE_SIZE, InvalidAvailIdx, Queue};
29+
use crate::devices::virtio::queue::{
30+
DescriptorChain, FIRECRACKER_MAX_QUEUE_SIZE, InvalidAvailIdx, Queue, QueueError,
31+
};
2932
use crate::devices::virtio::transport::{VirtioInterrupt, VirtioInterruptType};
3033
use crate::logger::{IncMetric, debug, error};
3134
use crate::utils::{bytes_to_mib, mib_to_bytes, u64_to_usize, usize_to_u64};
@@ -47,6 +50,24 @@ pub enum VirtioMemError {
4750
InvalidSize(u64),
4851
/// Device is not active
4952
DeviceNotActive,
53+
/// Descriptor is write-only
54+
UnexpectedWriteOnlyDescriptor,
55+
/// Error reading virtio descriptor
56+
DescriptorWriteFailed,
57+
/// Error writing virtio descriptor
58+
DescriptorReadFailed,
59+
/// Unknown request type: {0:?}
60+
UnknownRequestType(u32),
61+
/// Descriptor chain is too short
62+
DescriptorChainTooShort,
63+
/// Descriptor is too small
64+
DescriptorLengthTooSmall,
65+
/// Descriptor is read-only
66+
UnexpectedReadOnlyDescriptor,
67+
/// Error popping from virtio queue: {0}
68+
InvalidAvailIdx(#[from] InvalidAvailIdx),
69+
/// Error adding used queue: {0}
70+
QueueError(#[from] QueueError),
5071
}
5172

5273
#[derive(Debug)]
@@ -170,8 +191,139 @@ impl VirtioMem {
170191
.map_err(VirtioMemError::InterruptError)
171192
}
172193

194+
fn guest_memory(&self) -> &GuestMemoryMmap {
195+
&self.device_state.active_state().unwrap().mem
196+
}
197+
198+
fn parse_request(
199+
&self,
200+
avail_desc: &DescriptorChain,
201+
) -> Result<(Request, GuestAddress, u16), VirtioMemError> {
202+
// The head contains the request type which MUST be readable.
203+
if avail_desc.is_write_only() {
204+
return Err(VirtioMemError::UnexpectedWriteOnlyDescriptor);
205+
}
206+
207+
if (avail_desc.len as usize) < size_of::<virtio_mem::virtio_mem_req>() {
208+
return Err(VirtioMemError::DescriptorLengthTooSmall);
209+
}
210+
211+
let request: virtio_mem::virtio_mem_req = self
212+
.guest_memory()
213+
.read_obj(avail_desc.addr)
214+
.map_err(|_| VirtioMemError::DescriptorReadFailed)?;
215+
216+
let resp_desc = avail_desc
217+
.next_descriptor()
218+
.ok_or(VirtioMemError::DescriptorChainTooShort)?;
219+
220+
// The response MUST always be writable.
221+
if !resp_desc.is_write_only() {
222+
return Err(VirtioMemError::UnexpectedReadOnlyDescriptor);
223+
}
224+
225+
if (resp_desc.len as usize) < std::mem::size_of::<virtio_mem::virtio_mem_resp>() {
226+
return Err(VirtioMemError::DescriptorLengthTooSmall);
227+
}
228+
229+
Ok((request.into(), resp_desc.addr, avail_desc.index))
230+
}
231+
232+
fn write_response(
233+
&mut self,
234+
resp: Response,
235+
resp_addr: GuestAddress,
236+
used_idx: u16,
237+
) -> Result<(), VirtioMemError> {
238+
debug!("virtio-mem: Response: {:?}", resp);
239+
self.guest_memory()
240+
.write_obj(virtio_mem::virtio_mem_resp::from(resp), resp_addr)
241+
.map_err(|_| VirtioMemError::DescriptorWriteFailed)
242+
.map(|_| size_of::<virtio_mem::virtio_mem_resp>())?;
243+
self.queues[MEM_QUEUE]
244+
.add_used(
245+
used_idx,
246+
u32::try_from(std::mem::size_of::<virtio_mem::virtio_mem_resp>()).unwrap(),
247+
)
248+
.map_err(VirtioMemError::QueueError)
249+
}
250+
251+
fn handle_plug_request(
252+
&mut self,
253+
range: &RequestedRange,
254+
resp_addr: GuestAddress,
255+
used_idx: u16,
256+
) -> Result<(), VirtioMemError> {
257+
METRICS.plug_count.inc();
258+
let _metric = METRICS.plug_agg.record_latency_metrics();
259+
260+
// TODO: implement PLUG request
261+
let response = Response::ack();
262+
self.write_response(response, resp_addr, used_idx)
263+
}
264+
265+
fn handle_unplug_request(
266+
&mut self,
267+
range: &RequestedRange,
268+
resp_addr: GuestAddress,
269+
used_idx: u16,
270+
) -> Result<(), VirtioMemError> {
271+
METRICS.unplug_count.inc();
272+
let _metric = METRICS.unplug_agg.record_latency_metrics();
273+
274+
// TODO: implement UNPLUG request
275+
let response = Response::ack();
276+
self.write_response(response, resp_addr, used_idx)
277+
}
278+
279+
fn handle_unplug_all_request(
280+
&mut self,
281+
resp_addr: GuestAddress,
282+
used_idx: u16,
283+
) -> Result<(), VirtioMemError> {
284+
METRICS.unplug_all_count.inc();
285+
let _metric = METRICS.unplug_all_agg.record_latency_metrics();
286+
287+
// TODO: implement UNPLUG ALL request
288+
let response = Response::ack();
289+
self.write_response(response, resp_addr, used_idx)
290+
}
291+
292+
fn handle_state_request(
293+
&mut self,
294+
range: &RequestedRange,
295+
resp_addr: GuestAddress,
296+
used_idx: u16,
297+
) -> Result<(), VirtioMemError> {
298+
METRICS.state_count.inc();
299+
let _metric = METRICS.state_agg.record_latency_metrics();
300+
301+
// TODO: implement STATE request
302+
let response = Response::ack_with_state(BlockRangeState::Mixed);
303+
self.write_response(response, resp_addr, used_idx)
304+
}
305+
173306
fn process_mem_queue(&mut self) -> Result<(), VirtioMemError> {
174-
info!("TODO: Received mem queue event, but it's not implemented.");
307+
while let Some(desc) = self.queues[MEM_QUEUE].pop()? {
308+
let index = desc.index;
309+
310+
let (req, resp_addr, used_idx) = self.parse_request(&desc)?;
311+
debug!("virtio-mem: Request: {:?}", req);
312+
// Handle request and write response
313+
match req {
314+
Request::State(ref range) => self.handle_state_request(range, resp_addr, used_idx),
315+
Request::Plug(ref range) => self.handle_plug_request(range, resp_addr, used_idx),
316+
Request::Unplug(ref range) => {
317+
self.handle_unplug_request(range, resp_addr, used_idx)
318+
}
319+
Request::UnplugAll => self.handle_unplug_all_request(resp_addr, used_idx),
320+
Request::Unsupported(t) => Err(VirtioMemError::UnknownRequestType(t)),
321+
}?;
322+
}
323+
324+
self.queues[MEM_QUEUE].advance_used_ring_idx();
325+
self.signal_used_queue()?;
326+
175327
Ok(())
176328
}
177329

@@ -237,11 +389,9 @@ impl VirtioMem {
237389
"virtio-mem: Updated requested size to {} bytes",
238390
requested_size
239391
);
240-
// TODO(virtio-mem): trigger interrupt once we add handling for the requests
241-
// self.interrupt_trigger()
242-
// .trigger(VirtioInterruptType::Config)
243-
// .map_err(VirtioMemError::InterruptError)
244-
Ok(())
392+
self.interrupt_trigger()
393+
.trigger(VirtioInterruptType::Config)
394+
.map_err(VirtioMemError::InterruptError)
245395
}
246396
}
247397

src/vmm/src/devices/virtio/mem/metrics.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ pub(super) struct VirtioMemDeviceMetrics {
4545
pub queue_event_fails: SharedIncMetric,
4646
/// Number of queue events handled
4747
pub queue_event_count: SharedIncMetric,
48+
/// Latency of Plug operations
49+
pub plug_agg: LatencyAggregateMetrics,
50+
/// Number of Plug operations
51+
pub plug_count: SharedIncMetric,
52+
/// Latency of Unplug operations
53+
pub unplug_agg: LatencyAggregateMetrics,
54+
/// Number of Unplug operations
55+
pub unplug_count: SharedIncMetric,
56+
/// Latency of UnplugAll operations
57+
pub unplug_all_agg: LatencyAggregateMetrics,
58+
/// Number of UnplugAll operations
59+
pub unplug_all_count: SharedIncMetric,
60+
/// Latency of State operations
61+
pub state_agg: LatencyAggregateMetrics,
62+
/// Number of State operations
63+
pub state_count: SharedIncMetric,
4864
}
4965

5066
impl VirtioMemDeviceMetrics {
@@ -54,6 +70,14 @@ impl VirtioMemDeviceMetrics {
5470
activate_fails: SharedIncMetric::new(),
5571
queue_event_fails: SharedIncMetric::new(),
5672
queue_event_count: SharedIncMetric::new(),
73+
plug_agg: LatencyAggregateMetrics::new(),
74+
plug_count: SharedIncMetric::new(),
75+
unplug_agg: LatencyAggregateMetrics::new(),
76+
unplug_count: SharedIncMetric::new(),
77+
unplug_all_agg: LatencyAggregateMetrics::new(),
78+
unplug_all_count: SharedIncMetric::new(),
79+
state_agg: LatencyAggregateMetrics::new(),
80+
state_count: SharedIncMetric::new(),
5781
}
5882
}
5983
}

src/vmm/src/devices/virtio/mem/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod device;
55
mod event_handler;
66
pub mod metrics;
77
pub mod persist;
8+
mod request;
89

910
use vm_memory::GuestAddress;
1011

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use vm_memory::{ByteValued, GuestAddress};
5+
6+
use crate::devices::virtio::generated::virtio_mem;
7+
8+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9+
pub struct RequestedRange {
10+
pub(crate) addr: GuestAddress,
11+
pub(crate) nb_blocks: usize,
12+
}
13+
14+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15+
pub(crate) enum Request {
16+
Plug(RequestedRange),
17+
Unplug(RequestedRange),
18+
UnplugAll,
19+
State(RequestedRange),
20+
Unsupported(u32),
21+
}
22+
23+
// SAFETY: this is safe, trust me bro
24+
unsafe impl ByteValued for virtio_mem::virtio_mem_req {}
25+
26+
impl From<virtio_mem::virtio_mem_req> for Request {
27+
fn from(req: virtio_mem::virtio_mem_req) -> Self {
28+
match req.type_.into() {
29+
// SAFETY: union type is checked in the match
30+
virtio_mem::VIRTIO_MEM_REQ_PLUG => unsafe {
31+
Request::Plug(RequestedRange {
32+
addr: GuestAddress(req.u.plug.addr),
33+
nb_blocks: req.u.plug.nb_blocks.into(),
34+
})
35+
},
36+
// SAFETY: union type is checked in the match
37+
virtio_mem::VIRTIO_MEM_REQ_UNPLUG => unsafe {
38+
Request::Unplug(RequestedRange {
39+
addr: GuestAddress(req.u.unplug.addr),
40+
nb_blocks: req.u.unplug.nb_blocks.into(),
41+
})
42+
},
43+
virtio_mem::VIRTIO_MEM_REQ_UNPLUG_ALL => Request::UnplugAll,
44+
// SAFETY: union type is checked in the match
45+
virtio_mem::VIRTIO_MEM_REQ_STATE => unsafe {
46+
Request::State(RequestedRange {
47+
addr: GuestAddress(req.u.state.addr),
48+
nb_blocks: req.u.state.nb_blocks.into(),
49+
})
50+
},
51+
t => Request::Unsupported(t),
52+
}
53+
}
54+
}
55+
56+
#[derive(Debug, Clone, Copy)]
57+
pub enum ResponseType {
58+
Ack,
59+
Nack,
60+
Busy,
61+
Error,
62+
}
63+
64+
impl From<ResponseType> for u16 {
65+
fn from(code: ResponseType) -> Self {
66+
match code {
67+
ResponseType::Ack => virtio_mem::VIRTIO_MEM_RESP_ACK,
68+
ResponseType::Nack => virtio_mem::VIRTIO_MEM_RESP_NACK,
69+
ResponseType::Busy => virtio_mem::VIRTIO_MEM_RESP_BUSY,
70+
ResponseType::Error => virtio_mem::VIRTIO_MEM_RESP_ERROR,
71+
}
72+
.try_into()
73+
.unwrap()
74+
}
75+
}
76+
77+
#[derive(Debug, Clone, Copy)]
78+
pub enum BlockRangeState {
79+
Plugged,
80+
Unplugged,
81+
Mixed,
82+
}
83+
84+
impl From<BlockRangeState> for virtio_mem::virtio_mem_resp_state {
85+
fn from(code: BlockRangeState) -> Self {
86+
virtio_mem::virtio_mem_resp_state {
87+
state: match code {
88+
BlockRangeState::Plugged => virtio_mem::VIRTIO_MEM_STATE_PLUGGED,
89+
BlockRangeState::Unplugged => virtio_mem::VIRTIO_MEM_STATE_UNPLUGGED,
90+
BlockRangeState::Mixed => virtio_mem::VIRTIO_MEM_STATE_MIXED,
91+
}
92+
.try_into()
93+
.unwrap(),
94+
}
95+
}
96+
}
97+
98+
#[derive(Debug, Clone)]
99+
pub struct Response {
100+
pub resp_type: ResponseType,
101+
// Only for State requests
102+
pub state: Option<BlockRangeState>,
103+
}
104+
105+
impl Response {
106+
pub(crate) fn error() -> Self {
107+
Response {
108+
resp_type: ResponseType::Error,
109+
state: None,
110+
}
111+
}
112+
113+
pub(crate) fn ack() -> Self {
114+
Response {
115+
resp_type: ResponseType::Ack,
116+
state: None,
117+
}
118+
}
119+
120+
pub(crate) fn ack_with_state(state: BlockRangeState) -> Self {
121+
Response {
122+
resp_type: ResponseType::Error,
123+
state: Some(state),
124+
}
125+
}
126+
}
127+
128+
// SAFETY: Plain data structures
129+
unsafe impl ByteValued for virtio_mem::virtio_mem_resp {}
130+
131+
impl From<Response> for virtio_mem::virtio_mem_resp {
132+
fn from(resp: Response) -> Self {
133+
let mut out = virtio_mem::virtio_mem_resp {
134+
type_: resp.resp_type.into(),
135+
..Default::default()
136+
};
137+
if let Some(state) = resp.state {
138+
out.u.state = state.into();
139+
}
140+
out
141+
}
142+
}

0 commit comments

Comments
 (0)