Skip to content

Commit 9d00018

Browse files
committed
virtio-device: extend VirtioDeviceActions trait
This commit enhances the VirtioDeviceActions Trait to allow the VMM leverage the VirtIO MMIO implementation for not only VirtIO devices but also for vhost and vhost-user devices. Problems: 1) Since the device configuration space can be managed by various handlers outside of the VMM, such as vhost-user devices when the VHOST_USER_PROTOCOL_F_CONFIG feature is negotiated between the backend and the VMM, we need to invoke dedicated logic instead of performing the MMIO operations through the vm-virtio workspace. 2) When the driver completes the negotiation of the driver features with the VMM, , selecting page zero within the DriverFeatures MMIO field, we need to negotiate both the acknowledged features and the respective protocol features with the vhost/vhost-user backend devices. Otherwise, the device will not be prepared to support, for example, multiple queues and configuration space reads and writes. 3) When the backend device sends an interrupt to the frontend driver, it needs to write the interrupt status so that later the driver determines the type of notification — whether it is a used buffer or a configuration change notification. Since the responsibility for managing the interrupt status lies with the VMM through a normal MMIO write/read, if the device is not implemented within the VMM, it cannot manage the interrupt status field. Solution: 1) Added `read_config` and `write_config` methods to allow the VMM to instead of executing MMIO writes and reads within the vm-virtio workspace, using the set_config and get_config methods provided by the Rust-VMM vhost workspace. 2) Added the `negotiate_driver_features` method to allow the VMM to exchange the driver and protocol features with the backend device. 3) Added the `interrupt_status` method to allow the VMM to, for example, read the configuration space from the backend device and check for any changes. If the device configuration space has changed, the VMM may update the interrupt status register by setting bit 1. Conversely, if no changes are detected, the VMM may set the bit 0, signaling a used buffer notifcation. This applies to the backends that do not use the VHOST_USER_BACKEND_CONFIG_CHANGE_MSG message to notify about configuration changes (which I think is the case for all Rust-VMM vhost-user backends). Otherwise, the VMM can simply handle the message and update the interrupt status register accordingly. Signed-off-by: João Peixoto <[email protected]>
1 parent d6c8938 commit 9d00018

File tree

1 file changed

+68
-30
lines changed

1 file changed

+68
-30
lines changed

virtio-device/src/virtio_config.rs

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub trait VirtioDeviceType {
7676
/// Helper trait that can be implemented for objects which represent virtio devices. Together
7777
/// with `VirtioDeviceType`, it enables an automatic `VirtioDevice` implementation for objects
7878
/// that also implement `BorrowMut<VirtioConfig>`.
79-
pub trait VirtioDeviceActions {
79+
pub trait VirtioDeviceActions: BorrowMut<VirtioConfig<Queue>> {
8080
/// Type of the error that can be returned by `activate` and `reset`.
8181
type E;
8282

@@ -85,6 +85,58 @@ pub trait VirtioDeviceActions {
8585

8686
/// Invoke the logic associated with resetting this device.
8787
fn reset(&mut self) -> result::Result<(), Self::E>;
88+
89+
/// Invoke the logic associated with reading from the device configuration space.
90+
///
91+
/// A default implementation is provided as we cannot expect all backends to implement
92+
/// this function.
93+
fn read_config(&self, offset: usize, data: &mut [u8]) {
94+
let config_space = &self.borrow().config_space;
95+
let config_len = config_space.len();
96+
if offset >= config_len {
97+
error!("Failed to read from config space");
98+
return;
99+
}
100+
101+
// TODO: Are partial reads ok?
102+
let end = cmp::min(offset.saturating_add(data.len()), config_len);
103+
let read_len = end - offset;
104+
// Cannot fail because the lengths are identical and we do bounds checking beforehand.
105+
data[..read_len].copy_from_slice(&config_space[offset..end])
106+
}
107+
108+
/// Invoke the logic associated with writing to the device configuration space.
109+
///
110+
/// A default implementation is provided as we cannot expect all backends to implement
111+
/// this function.
112+
fn write_config(&mut self, offset: usize, data: &[u8]) {
113+
let config_space = &mut self.borrow_mut().config_space;
114+
let config_len = config_space.len();
115+
if offset >= config_len {
116+
error!("Failed to write to config space");
117+
return;
118+
}
119+
120+
// TODO: Are partial writes ok?
121+
let end = cmp::min(offset.saturating_add(data.len()), config_len);
122+
let write_len = end - offset;
123+
// Cannot fail because the lengths are identical and we do bounds checking beforehand.
124+
config_space[offset..end].copy_from_slice(&data[..write_len]);
125+
}
126+
127+
/// Invoke the logic associated with negotiating the driver features.
128+
///
129+
/// A default implementation is provided as we cannot expect all backends to implement
130+
/// this function.
131+
fn negotiate_driver_features(&mut self) {}
132+
133+
/// Invoke the logic associated with the device interrupt status.
134+
///
135+
/// A default implementation is provided as we cannot expect all backends to implement
136+
/// this function.
137+
fn interrupt_status(&self) -> &Arc<AtomicU8> {
138+
&self.borrow().interrupt_status
139+
}
88140
}
89141

90142
// We can automatically implement the `VirtioDevice` trait for objects that only explicitly
@@ -130,6 +182,10 @@ where
130182
1 => ((features << 32) >> 32) + (v << 32),
131183
// Accessing an unknown page has no effect.
132184
_ => features,
185+
};
186+
187+
if page == 0 {
188+
<Self as VirtioDeviceActions>::negotiate_driver_features(self);
133189
}
134190
}
135191

@@ -150,41 +206,19 @@ where
150206
}
151207

152208
fn interrupt_status(&self) -> &Arc<AtomicU8> {
153-
&self.borrow().interrupt_status
209+
<Self as VirtioDeviceActions>::interrupt_status(self)
154210
}
155211

156212
fn config_generation(&self) -> u8 {
157213
self.borrow().config_generation
158214
}
159215

160216
fn read_config(&self, offset: usize, data: &mut [u8]) {
161-
let config_space = &self.borrow().config_space;
162-
let config_len = config_space.len();
163-
if offset >= config_len {
164-
error!("Failed to read from config space");
165-
return;
166-
}
167-
168-
// TODO: Are partial reads ok?
169-
let end = cmp::min(offset.saturating_add(data.len()), config_len);
170-
let read_len = end - offset;
171-
// Cannot fail because the lengths are identical and we do bounds checking beforehand.
172-
data[..read_len].copy_from_slice(&config_space[offset..end])
217+
<Self as VirtioDeviceActions>::read_config(self, offset, data)
173218
}
174219

175220
fn write_config(&mut self, offset: usize, data: &[u8]) {
176-
let config_space = &mut self.borrow_mut().config_space;
177-
let config_len = config_space.len();
178-
if offset >= config_len {
179-
error!("Failed to write to config space");
180-
return;
181-
}
182-
183-
// TODO: Are partial writes ok?
184-
let end = cmp::min(offset.saturating_add(data.len()), config_len);
185-
let write_len = end - offset;
186-
// Cannot fail because the lengths are identical and we do bounds checking beforehand.
187-
config_space[offset..end].copy_from_slice(&data[..write_len]);
221+
<Self as VirtioDeviceActions>::write_config(self, offset, data)
188222
}
189223
}
190224

@@ -222,6 +256,7 @@ pub(crate) mod tests {
222256
use super::*;
223257
use crate::mmio::VirtioMmioDevice;
224258
use std::borrow::Borrow;
259+
use std::sync::atomic::Ordering;
225260

226261
pub struct Dummy {
227262
pub cfg: VirtioConfig<Queue>,
@@ -320,10 +355,10 @@ pub(crate) mod tests {
320355
let mut v2 = vec![0u8; len];
321356

322357
// Offset to large to read anything.
323-
d.read_config(len, v2.as_mut_slice());
358+
VirtioDevice::read_config(&d, len, v2.as_mut_slice());
324359
assert_eq!(v1, v2);
325360

326-
d.read_config(len / 2, v2.as_mut_slice());
361+
VirtioDevice::read_config(&d, len / 2, v2.as_mut_slice());
327362
for i in 0..len {
328363
if i < len / 2 {
329364
assert_eq!(v2[i], config_space[len / 2 + i]);
@@ -333,10 +368,10 @@ pub(crate) mod tests {
333368
}
334369

335370
// Offset too large to overwrite anything.
336-
d.write_config(len, v1.as_slice());
371+
VirtioDevice::write_config(&mut d, len, v1.as_slice());
337372
assert_eq!(d.cfg.config_space, config_space);
338373

339-
d.write_config(len / 2, v1.as_slice());
374+
VirtioDevice::write_config(&mut d, len / 2, v1.as_slice());
340375
for (i, &value) in config_space.iter().enumerate().take(len) {
341376
if i < len / 2 {
342377
assert_eq!(d.cfg.config_space[i], value);
@@ -345,6 +380,9 @@ pub(crate) mod tests {
345380
}
346381
}
347382

383+
d.cfg.interrupt_status.fetch_or(1, Ordering::SeqCst);
384+
assert_eq!(VirtioDevice::interrupt_status(&d).load(Ordering::SeqCst), 1);
385+
348386
// Let's test the `WithDriverSelect` auto impl now.
349387
assert_eq!(d.queue_select(), 0);
350388
d.set_queue_select(1);

0 commit comments

Comments
 (0)