Skip to content

Commit 82b5bb5

Browse files
committed
vhost-user-frontend: Provide generic implementation of VirtioDevice
Provide a generic implementation of VirtioDevice, which can be reused by all sort of devices. Signed-off-by: Viresh Kumar <[email protected]>
1 parent f55eef4 commit 82b5bb5

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed
+293
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
// Copyright 2022 Linaro Ltd. All Rights Reserved.
2+
// Viresh Kumar <[email protected]>
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
use seccompiler::SeccompAction;
7+
use std::sync::{Arc, Barrier, Mutex};
8+
use std::thread;
9+
use std::vec::Vec;
10+
11+
use vhost::vhost_user::message::{
12+
VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
13+
};
14+
use vhost::vhost_user::{MasterReqHandler, VhostUserMaster, VhostUserMasterReqHandler};
15+
use virtio_queue::Queue;
16+
use vm_memory::GuestMemoryAtomic;
17+
use vmm_sys_util::eventfd::EventFd;
18+
19+
use crate::{
20+
spawn_virtio_thread, ActivateResult, Error, GuestMemoryMmap, GuestRegionMmap, Result, Thread,
21+
VhostUserCommon, VhostUserConfig, VhostUserHandle, VirtioCommon, VirtioDevice,
22+
VirtioDeviceType, VirtioInterrupt,
23+
};
24+
25+
const MIN_NUM_QUEUES: usize = 1;
26+
27+
pub struct State {
28+
pub avail_features: u64,
29+
pub acked_features: u64,
30+
pub acked_protocol_features: u64,
31+
pub vu_num_queues: usize,
32+
}
33+
34+
struct SlaveReqHandler {}
35+
impl VhostUserMasterReqHandler for SlaveReqHandler {}
36+
37+
pub struct Generic {
38+
common: VirtioCommon,
39+
vu_common: VhostUserCommon,
40+
id: String,
41+
guest_memory: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
42+
epoll_thread: Option<thread::JoinHandle<()>>,
43+
seccomp_action: SeccompAction,
44+
exit_evt: EventFd,
45+
device_features: u64,
46+
num_queues: u32,
47+
name: String,
48+
}
49+
50+
impl Generic {
51+
/// Create a new vhost-user-blk device
52+
pub fn new(
53+
vu_cfg: VhostUserConfig,
54+
seccomp_action: SeccompAction,
55+
exit_evt: EventFd,
56+
device_type: VirtioDeviceType,
57+
) -> Result<Generic> {
58+
let num_queues = vu_cfg.num_queues;
59+
60+
let vu =
61+
VhostUserHandle::connect_vhost_user(false, &vu_cfg.socket, num_queues as u64, false)?;
62+
let device_features = vu.device_features()?;
63+
64+
Ok(Generic {
65+
common: VirtioCommon {
66+
device_type: device_type as u32,
67+
queue_sizes: vec![vu_cfg.queue_size; num_queues],
68+
avail_features: 0,
69+
acked_features: 0,
70+
paused_sync: Some(Arc::new(Barrier::new(2))),
71+
min_queues: MIN_NUM_QUEUES as u16,
72+
..Default::default()
73+
},
74+
vu_common: VhostUserCommon {
75+
vu: Some(Arc::new(Mutex::new(vu))),
76+
acked_protocol_features: 0,
77+
socket_path: vu_cfg.socket,
78+
vu_num_queues: num_queues,
79+
..Default::default()
80+
},
81+
id: "generic_device".to_string(),
82+
guest_memory: None,
83+
epoll_thread: None,
84+
seccomp_action,
85+
exit_evt,
86+
device_features,
87+
num_queues: 0,
88+
name: String::from(device_type),
89+
})
90+
}
91+
92+
pub fn device_features(&self) -> u64 {
93+
self.device_features
94+
}
95+
96+
pub fn name(&self) -> String {
97+
self.name.clone()
98+
}
99+
100+
pub fn negotiate_features(&mut self, avail_features: u64) -> Result<(u64, u64)> {
101+
let mut vu = self.vu_common.vu.as_ref().unwrap().lock().unwrap();
102+
let avail_protocol_features = VhostUserProtocolFeatures::MQ
103+
| VhostUserProtocolFeatures::CONFIG
104+
| VhostUserProtocolFeatures::REPLY_ACK;
105+
106+
// Virtio spec says following for VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits():
107+
//
108+
// Bit 30 is used by qemu’s implementation to check for experimental early versions of
109+
// virtio which did not perform correct feature negotiation, and SHOULD NOT be negotiated.
110+
//
111+
// And so Linux clears it in available features. Lets set it forcefully here to make things
112+
// work.
113+
114+
let avail_features = avail_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
115+
116+
let (acked_features, acked_protocol_features) =
117+
vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
118+
119+
let backend_num_queues =
120+
if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 {
121+
vu.socket_handle()
122+
.get_queue_num()
123+
.map_err(Error::VhostUserGetQueueMaxNum)? as usize
124+
} else {
125+
MIN_NUM_QUEUES
126+
};
127+
128+
if self.vu_common.vu_num_queues > backend_num_queues {
129+
error!("vhost-user-device requested too many queues ({}) since the backend only supports {}\n",
130+
self.vu_common.vu_num_queues, backend_num_queues);
131+
return Err(Error::BadQueueNum);
132+
}
133+
134+
self.common.acked_features = acked_features;
135+
self.vu_common.acked_protocol_features = acked_protocol_features;
136+
self.num_queues = backend_num_queues as u32;
137+
138+
Ok((acked_features, acked_protocol_features))
139+
}
140+
141+
pub fn state(&self) -> State {
142+
State {
143+
avail_features: self.common.avail_features,
144+
acked_features: self.common.acked_features,
145+
acked_protocol_features: self.vu_common.acked_protocol_features,
146+
vu_num_queues: self.vu_common.vu_num_queues,
147+
}
148+
}
149+
150+
pub fn set_state(&mut self, state: &State) {
151+
self.common.avail_features = state.avail_features;
152+
self.common.acked_features = state.acked_features;
153+
self.vu_common.acked_protocol_features = state.acked_protocol_features;
154+
self.vu_common.vu_num_queues = state.vu_num_queues;
155+
156+
if let Err(e) = self
157+
.vu_common
158+
.restore_backend_connection(self.common.acked_features)
159+
{
160+
error!(
161+
"Failed restoring connection with vhost-user backend: {:?}",
162+
e
163+
);
164+
}
165+
}
166+
}
167+
168+
impl Drop for Generic {
169+
fn drop(&mut self) {
170+
if let Some(kill_evt) = self.common.kill_evt.take() {
171+
if let Err(e) = kill_evt.write(1) {
172+
error!("failed to kill vhost-user-blk: {:?}", e);
173+
}
174+
}
175+
}
176+
}
177+
178+
impl VirtioDevice for Generic {
179+
fn device_type(&self) -> u32 {
180+
self.common.device_type as u32
181+
}
182+
183+
fn queue_max_sizes(&self) -> &[u16] {
184+
&self.common.queue_sizes
185+
}
186+
187+
fn features(&self) -> u64 {
188+
self.common.avail_features
189+
}
190+
191+
fn ack_features(&mut self, value: u64) {
192+
self.common.ack_features(value)
193+
}
194+
195+
fn read_config(&self, offset: u64, data: &mut [u8]) {
196+
let mut vu = self.vu_common.vu.as_ref().unwrap().lock().unwrap();
197+
let len = data.len();
198+
let config_space: Vec<u8> = vec![0u8; len];
199+
let (_, config_space) = vu
200+
.socket_handle()
201+
.get_config(
202+
offset as u32,
203+
len as u32,
204+
VhostUserConfigFlags::WRITABLE,
205+
config_space.as_slice(),
206+
)
207+
.unwrap();
208+
209+
data.copy_from_slice(config_space.as_slice());
210+
}
211+
212+
fn write_config(&mut self, offset: u64, data: &[u8]) {
213+
let mut vu = self.vu_common.vu.as_ref().unwrap().lock().unwrap();
214+
vu.socket_handle()
215+
.set_config(offset as u32, VhostUserConfigFlags::WRITABLE, data)
216+
.unwrap();
217+
}
218+
219+
fn activate(
220+
&mut self,
221+
mem: GuestMemoryAtomic<GuestMemoryMmap>,
222+
interrupt: Arc<dyn VirtioInterrupt>,
223+
queues: Vec<(usize, Queue, EventFd)>,
224+
) -> ActivateResult {
225+
self.common.activate(&queues, &interrupt)?;
226+
self.guest_memory = Some(mem.clone());
227+
228+
let slave_req_handler: Option<MasterReqHandler<SlaveReqHandler>> = None;
229+
230+
// Run a dedicated thread for handling potential reconnections with
231+
// the backend.
232+
let (kill_evt, pause_evt) = self.common.dup_eventfds();
233+
234+
let mut handler = self.vu_common.activate(
235+
mem,
236+
queues,
237+
interrupt,
238+
self.common.acked_features,
239+
slave_req_handler,
240+
kill_evt,
241+
pause_evt,
242+
)?;
243+
244+
let paused = self.common.paused.clone();
245+
let paused_sync = self.common.paused_sync.clone();
246+
247+
let mut epoll_threads = Vec::new();
248+
249+
spawn_virtio_thread(
250+
&self.id,
251+
&self.seccomp_action,
252+
Thread::VirtioVhostBlock,
253+
&mut epoll_threads,
254+
&self.exit_evt,
255+
move || {
256+
if let Err(e) = handler.run(paused, paused_sync.unwrap()) {
257+
error!("Error running worker: {:?}", e);
258+
}
259+
},
260+
)?;
261+
self.epoll_thread = Some(epoll_threads.remove(0));
262+
263+
Ok(())
264+
}
265+
266+
fn reset(&mut self) -> Option<Arc<dyn VirtioInterrupt>> {
267+
if let Some(vu) = &self.vu_common.vu {
268+
if let Err(e) = vu.lock().unwrap().reset_vhost_user() {
269+
error!("Failed to reset vhost-user daemon: {:?}", e);
270+
return None;
271+
}
272+
}
273+
274+
if let Some(kill_evt) = self.common.kill_evt.take() {
275+
// Ignore the result because there is nothing we can do about it.
276+
let _ = kill_evt.write(1);
277+
}
278+
279+
// Return the interrupt
280+
Some(self.common.interrupt_cb.take().unwrap())
281+
}
282+
283+
fn shutdown(&mut self) {
284+
self.vu_common.shutdown()
285+
}
286+
287+
fn add_memory_region(
288+
&mut self,
289+
region: &Arc<GuestRegionMmap>,
290+
) -> std::result::Result<(), Error> {
291+
self.vu_common.add_memory_region(&self.guest_memory, region)
292+
}
293+
}

crates/vhost-user-frontend/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ extern crate log;
1111

1212
mod device;
1313
mod epoll_helper;
14+
mod generic;
1415
mod seccomp_filters;
1516
mod thread_helper;
1617
mod vhost_user;
1718

1819
pub use crate::device::*;
1920
pub use crate::epoll_helper::*;
21+
pub use crate::generic::*;
2022
pub use crate::seccomp_filters::*;
2123
pub(crate) use crate::thread_helper::*;
2224
pub use crate::vhost_user::*;

crates/vhost-user-frontend/src/vhost_user/vu_common_ctrl.rs

+4
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ impl VhostUserHandle {
147147
Ok((acked_features, acked_protocol_features.bits()))
148148
}
149149

150+
pub fn device_features(&self) -> Result<u64> {
151+
self.vu.get_features().map_err(Error::VhostUserGetFeatures)
152+
}
153+
150154
#[allow(clippy::too_many_arguments)]
151155
pub fn setup_vhost_user<S: VhostUserMasterReqHandler>(
152156
&mut self,

0 commit comments

Comments
 (0)