Skip to content

Commit 8512e44

Browse files
ShadowCursekalyazindianpopa
committed
feat(vhost-user-block): added vhost-user-block
Added implementation for vhost-user-block device. Signed-off-by: Egor Lazarchuk <[email protected]> Co-authored-by: Nikita Kalyazin <[email protected]> Co-authored-by: Diana Popa <[email protected]>
1 parent 407e54f commit 8512e44

File tree

5 files changed

+439
-1
lines changed

5 files changed

+439
-1
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod queue;
2222
pub mod rng;
2323
pub mod test_utils;
2424
pub mod vhost_user;
25+
pub mod vhost_user_block;
2526
pub mod vsock;
2627

2728
pub use self::balloon::*;
@@ -32,6 +33,7 @@ pub use self::net::*;
3233
pub use self::persist::*;
3334
pub use self::queue::*;
3435
pub use self::rng::*;
36+
pub use self::vhost_user_block::*;
3537
pub use self::vsock::*;
3638

3739
/// When the driver initializes the device, it lets the device know about the
@@ -67,12 +69,14 @@ pub const TYPE_BALLOON: u32 = 5;
6769
pub const NOTIFY_REG_OFFSET: u32 = 0x50;
6870

6971
/// Errors triggered when activating a VirtioDevice.
70-
#[derive(Debug)]
72+
#[derive(Debug, displaydoc::Display)]
7173
pub enum ActivateError {
7274
/// Epoll error.
7375
EpollCtl(IOError),
7476
/// General error at activation.
7577
BadActivate,
78+
/// Vhost user: {0}
79+
VhostUser(vhost_user::VhostUserError),
7680
}
7781

7882
/// Trait that helps in upcasting an object to Any
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Portions Copyright 2019 Intel Corporation. All Rights Reserved.
5+
// SPDX-License-Identifier: Apache-2.0
6+
7+
use std::cmp;
8+
use std::io::Write;
9+
use std::sync::atomic::AtomicU32;
10+
use std::sync::Arc;
11+
12+
use log::error;
13+
use utils::eventfd::EventFd;
14+
use utils::u64_to_usize;
15+
use vhost::vhost_user::message::*;
16+
use vhost::vhost_user::VhostUserFrontend;
17+
18+
use super::{VhostUserBlockError, NUM_QUEUES, QUEUE_SIZE};
19+
use crate::devices::virtio::block::device::FileEngineType;
20+
use crate::devices::virtio::gen::virtio_blk::{
21+
VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_RO, VIRTIO_F_VERSION_1,
22+
};
23+
use crate::devices::virtio::gen::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
24+
use crate::devices::virtio::queue::Queue;
25+
use crate::devices::virtio::vhost_user::VhostUserHandle;
26+
use crate::devices::virtio::{
27+
ActivateError, CacheType, DeviceState, IrqTrigger, VirtioDevice, TYPE_BLOCK,
28+
};
29+
use crate::vmm_config::drive::BlockDeviceConfig;
30+
use crate::vstate::memory::GuestMemoryMmap;
31+
32+
/// Block device config space size in bytes.
33+
const BLOCK_CONFIG_SPACE_SIZE: u32 = 60;
34+
35+
/// Use this structure to set up the Block Device before booting the kernel.
36+
#[derive(Debug, PartialEq, Eq)]
37+
pub struct VhostUserBlockConfig {
38+
/// Unique identifier of the drive.
39+
pub drive_id: String,
40+
/// Part-UUID. Represents the unique id of the boot partition of this device. It is
41+
/// optional and it will be used only if the `is_root_device` field is true.
42+
pub partuuid: Option<String>,
43+
/// If set to true, it makes the current device the root block device.
44+
/// Setting this flag to true will mount the block device in the
45+
/// guest under /dev/vda unless the partuuid is present.
46+
pub is_root_device: bool,
47+
/// If set to true, the drive will ignore flush requests coming from
48+
/// the guest driver.
49+
pub cache_type: CacheType,
50+
/// Socket path of the vhost-user process
51+
pub socket: String,
52+
}
53+
54+
impl From<BlockDeviceConfig> for VhostUserBlockConfig {
55+
fn from(value: BlockDeviceConfig) -> Self {
56+
Self {
57+
drive_id: value.drive_id,
58+
is_root_device: value.is_root_device,
59+
partuuid: value.partuuid,
60+
cache_type: value.cache_type,
61+
socket: Default::default(),
62+
}
63+
}
64+
}
65+
66+
impl From<VhostUserBlockConfig> for BlockDeviceConfig {
67+
fn from(value: VhostUserBlockConfig) -> Self {
68+
Self {
69+
drive_id: value.drive_id,
70+
path_on_host: Default::default(),
71+
is_root_device: value.is_root_device,
72+
partuuid: value.partuuid,
73+
is_read_only: false,
74+
cache_type: value.cache_type,
75+
rate_limiter: None,
76+
file_engine_type: FileEngineType::default(),
77+
}
78+
}
79+
}
80+
81+
/// vhost-user block device.
82+
#[derive(Debug)]
83+
pub struct VhostUserBlock {
84+
// Virtio fields.
85+
pub avail_features: u64,
86+
pub acked_features: u64,
87+
pub config_space: Vec<u8>,
88+
pub activate_evt: EventFd,
89+
90+
// Transport related fields.
91+
pub queues: Vec<Queue>,
92+
pub queue_evts: [EventFd; u64_to_usize(NUM_QUEUES)],
93+
pub device_state: DeviceState,
94+
pub irq_trigger: IrqTrigger,
95+
96+
// Implementation specific fields.
97+
pub id: String,
98+
pub partuuid: Option<String>,
99+
pub cache_type: CacheType,
100+
pub root_device: bool,
101+
pub read_only: bool,
102+
103+
// Vhost user protocol handle
104+
pub vu_handle: VhostUserHandle,
105+
pub vu_acked_protocol_features: u64,
106+
}
107+
108+
impl VhostUserBlock {
109+
pub fn new(config: VhostUserBlockConfig) -> Result<Self, VhostUserBlockError> {
110+
let mut requested_features = (1 << VIRTIO_F_VERSION_1)
111+
| (1 << VIRTIO_RING_F_EVENT_IDX)
112+
// vhost-user specific bit. Not defined in standart virtio spec.
113+
// Specifies ability of frontend to negotiate protocol features.
114+
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
115+
// We always try to negotiate readonly with the backend.
116+
// If the backend is configured as readonly, we will accept it.
117+
| (1 << VIRTIO_BLK_F_RO);
118+
119+
if config.cache_type == CacheType::Writeback {
120+
requested_features |= 1 << VIRTIO_BLK_F_FLUSH;
121+
}
122+
123+
let requested_protocol_features = VhostUserProtocolFeatures::CONFIG;
124+
125+
let mut vu_handle = VhostUserHandle::new(&config.socket, NUM_QUEUES)
126+
.map_err(VhostUserBlockError::VhostUser)?;
127+
let (acked_features, acked_protocol_features) = vu_handle
128+
.negotiate_features(requested_features, requested_protocol_features)
129+
.map_err(VhostUserBlockError::VhostUser)?;
130+
131+
// Get config from backend if CONFIG is acked or use empty buffer.
132+
let config_space =
133+
if acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() != 0 {
134+
// This buffer is read only. Ask vhost implementation why.
135+
let buffer = [0u8; BLOCK_CONFIG_SPACE_SIZE as usize];
136+
let (_, new_config_space) = vu_handle
137+
.vu
138+
.get_config(
139+
VHOST_USER_CONFIG_OFFSET,
140+
BLOCK_CONFIG_SPACE_SIZE,
141+
VhostUserConfigFlags::WRITABLE,
142+
&buffer,
143+
)
144+
.map_err(VhostUserBlockError::Vhost)?;
145+
new_config_space
146+
} else {
147+
vec![]
148+
};
149+
150+
let activate_evt =
151+
EventFd::new(libc::EFD_NONBLOCK).map_err(VhostUserBlockError::EventFd)?;
152+
153+
let queues = vec![Queue::new(QUEUE_SIZE)];
154+
let queue_evts = [EventFd::new(libc::EFD_NONBLOCK).map_err(VhostUserBlockError::EventFd)?;
155+
u64_to_usize(NUM_QUEUES)];
156+
let device_state = DeviceState::Inactive;
157+
let irq_trigger = IrqTrigger::new().map_err(VhostUserBlockError::IrqTrigger)?;
158+
159+
// We negotiated features with backend. Now these acked_features
160+
// are available for guest driver to choose from.
161+
let avail_features = acked_features;
162+
let acked_features = acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
163+
let read_only = acked_features & (1 << VIRTIO_BLK_F_RO) != 0;
164+
165+
Ok(Self {
166+
avail_features,
167+
acked_features,
168+
config_space,
169+
activate_evt,
170+
171+
queues,
172+
queue_evts,
173+
device_state,
174+
irq_trigger,
175+
176+
id: config.drive_id,
177+
partuuid: config.partuuid,
178+
cache_type: config.cache_type,
179+
read_only,
180+
root_device: config.is_root_device,
181+
182+
vu_handle,
183+
vu_acked_protocol_features: acked_protocol_features,
184+
})
185+
}
186+
187+
/// Prepare device for being snapshotted.
188+
pub fn prepare_save(&mut self) {
189+
unimplemented!("VhostUserBlock does not support snapshotting yet");
190+
}
191+
192+
pub fn config(&self) -> VhostUserBlockConfig {
193+
VhostUserBlockConfig {
194+
drive_id: self.id.clone(),
195+
partuuid: self.partuuid.clone(),
196+
is_root_device: self.root_device,
197+
cache_type: self.cache_type,
198+
socket: self.vu_handle.socket_path.clone(),
199+
}
200+
}
201+
}
202+
203+
impl VirtioDevice for VhostUserBlock {
204+
fn avail_features(&self) -> u64 {
205+
self.avail_features
206+
}
207+
208+
fn acked_features(&self) -> u64 {
209+
self.acked_features
210+
}
211+
212+
fn set_acked_features(&mut self, acked_features: u64) {
213+
self.acked_features = acked_features;
214+
}
215+
216+
fn device_type(&self) -> u32 {
217+
TYPE_BLOCK
218+
}
219+
220+
fn queues(&self) -> &[Queue] {
221+
&self.queues
222+
}
223+
224+
fn queues_mut(&mut self) -> &mut [Queue] {
225+
&mut self.queues
226+
}
227+
228+
fn queue_events(&self) -> &[EventFd] {
229+
&self.queue_evts
230+
}
231+
232+
fn interrupt_evt(&self) -> &EventFd {
233+
&self.irq_trigger.irq_evt
234+
}
235+
236+
/// Returns the current device interrupt status.
237+
fn interrupt_status(&self) -> Arc<AtomicU32> {
238+
self.irq_trigger.irq_status.clone()
239+
}
240+
241+
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
242+
let config_len = self.config_space.len() as u64;
243+
if offset >= config_len {
244+
error!("Failed to read config space");
245+
return;
246+
}
247+
if let Some(end) = offset.checked_add(data.len() as u64) {
248+
// This write can't fail, offset and end are checked against config_len.
249+
data.write_all(
250+
&self.config_space[u64_to_usize(offset)..u64_to_usize(cmp::min(end, config_len))],
251+
)
252+
.unwrap();
253+
}
254+
}
255+
256+
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
257+
// We do not advertise VIRTIO_BLK_F_CONFIG_WCE
258+
// that would allow configuring the "writeback" field.
259+
// Other block config fields are immutable.
260+
}
261+
262+
fn activate(&mut self, mem: GuestMemoryMmap) -> Result<(), ActivateError> {
263+
// Setting features again, because now we negotiated them
264+
// with guest driver as well.
265+
self.vu_handle
266+
.set_features(self.acked_features)
267+
.map_err(ActivateError::VhostUser)?;
268+
self.vu_handle
269+
.setup_backend(
270+
&mem,
271+
&[(0, &self.queues[0], &self.queue_evts[0])],
272+
&self.irq_trigger,
273+
)
274+
.map_err(ActivateError::VhostUser)?;
275+
self.device_state = DeviceState::Activated(mem);
276+
Ok(())
277+
}
278+
279+
fn is_activated(&self) -> bool {
280+
self.device_state.is_activated()
281+
}
282+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::os::fd::AsRawFd;
5+
6+
use event_manager::{EventOps, Events, MutEventSubscriber};
7+
use utils::epoll::EventSet;
8+
9+
use super::VhostUserBlock;
10+
use crate::devices::virtio::device::VirtioDevice;
11+
use crate::logger::{error, warn};
12+
13+
impl VhostUserBlock {
14+
fn register_activate_event(&self, ops: &mut EventOps) {
15+
if let Err(err) = ops.add(Events::new(&self.activate_evt, EventSet::IN)) {
16+
error!("Failed to register activate event: {}", err);
17+
}
18+
}
19+
20+
fn process_activate_event(&self, ops: &mut EventOps) {
21+
if let Err(err) = self.activate_evt.read() {
22+
error!("Failed to consume block activate event: {:?}", err);
23+
}
24+
if let Err(err) = ops.remove(Events::new(&self.activate_evt, EventSet::IN)) {
25+
error!("Failed to un-register activate event: {}", err);
26+
}
27+
}
28+
}
29+
30+
impl MutEventSubscriber for VhostUserBlock {
31+
// Handle an event for queue or rate limiter.
32+
fn process(&mut self, event: Events, ops: &mut EventOps) {
33+
let source = event.fd();
34+
let event_set = event.event_set();
35+
let supported_events = EventSet::IN;
36+
37+
if !supported_events.contains(event_set) {
38+
warn!(
39+
"Received unknown event: {:?} from source: {:?}",
40+
event_set, source
41+
);
42+
return;
43+
}
44+
45+
if self.is_activated() {
46+
let activate_fd = self.activate_evt.as_raw_fd();
47+
if activate_fd == source {
48+
self.process_activate_event(ops)
49+
} else {
50+
warn!("BlockVhost: Spurious event received: {:?}", source)
51+
}
52+
} else {
53+
warn!(
54+
"BlockVhost: The device is not yet activated. Spurious event received: {:?}",
55+
source
56+
);
57+
}
58+
}
59+
60+
fn init(&mut self, ops: &mut EventOps) {
61+
// This function can be called during different points in the device lifetime:
62+
// - shortly after device creation,
63+
// - on device activation (is-activated already true at this point),
64+
// - on device restore from snapshot.
65+
if self.is_activated() {
66+
error!("This a vhost backed block. Not sure why I received this event");
67+
} else {
68+
self.register_activate_event(ops);
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)