dev.rs48.37%
1
// Copyright 2024 Google LLC2
//3
// Licensed under the Apache License, Version 2.0 (the "License");4
// you may not use this file except in compliance with the License.5
// You may obtain a copy of the License at6
//7
// https://www.apache.org/licenses/LICENSE-2.08
//9
// Unless required by applicable law or agreed to in writing, software10
// distributed under the License is distributed on an "AS IS" BASIS,11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12
// See the License for the specific language governing permissions and13
// limitations under the License.14
15
pub mod balloon;16
pub mod blk;17
pub mod entropy;18
#[path = "fs/fs.rs"]19
pub mod fs;20
#[path = "net/net.rs"]21
pub mod net;22
#[path = "vsock/vsock.rs"]23
pub mod vsock;24
25
use std::fmt::Debug;26
use std::sync::Arc;27
use std::sync::atomic::{AtomicU8, AtomicU16, AtomicU32};28
use std::sync::mpsc::{self, Receiver, Sender};29
use std::thread::JoinHandle;30
31
use bitflags::Flags;32
use snafu::ResultExt;33
34
use crate::hv::IoeventFd;35
use crate::mem::emulated::Mmio;36
use crate::mem::mapped::{Ram, RamBus};37
use crate::mem::{LayoutChanged, LayoutUpdated, MemRegion};38
use crate::sync::notifier::Notifier;39
use crate::virtio::queue::packed::PackedQueue;40
use crate::virtio::queue::split::SplitQueue;41
use crate::virtio::queue::{QUEUE_SIZE_MAX, Queue, QueueReg, VirtQueue};42
#[cfg(target_os = "linux")]43
use crate::virtio::vu::conn::VuChannel;44
use crate::virtio::{DeviceId, IrqSender, Result, VirtioFeature, error};45
46
pub trait Virtio: Debug + Send + Sync + 'static {47
type Config: Mmio;48
type Feature: Flags<Bits = u128> + Debug;49
50
fn name(&self) -> &str;51
fn id(&self) -> DeviceId;52
fn num_queues(&self) -> u16;53
fn config(&self) -> Arc<Self::Config>;54
fn feature(&self) -> u128;55
fn spawn_worker<S: IrqSender, E: IoeventFd>(56
self,57
event_rx: Receiver<WakeEvent<S, E>>,58
memory: Arc<RamBus>,59
queue_regs: Arc<[QueueReg]>,60
) -> Result<(JoinHandle<()>, Arc<Notifier>)>;61
fn shared_mem_regions(&self) -> Option<Arc<MemRegion>> {62
None63
}64
fn ioeventfd_offloaded(&self, _q_index: u16) -> Result<bool> {65
Ok(false)66
}67
fn mem_update_callback(&self) -> Option<Box<dyn LayoutUpdated>> {68
None69
}70
fn mem_change_callback(&self) -> Option<Box<dyn LayoutChanged>> {71
None72
}73
#[cfg(target_os = "linux")]74
fn set_vu_channel(&mut self, _channel: Arc<VuChannel>) {}75
}76
77
#[derive(Debug, Default)]78
pub struct Register {79
pub device_feature: [u32; 4],80
pub driver_feature: [AtomicU32; 4],81
pub device_feature_sel: AtomicU8,82
pub driver_feature_sel: AtomicU8,83
pub queue_sel: AtomicU16,84
pub status: AtomicU8,85
}86
87
const TOKEN_WARKER: u64 = 1 << 63;88
89
#[derive(Debug, Clone)]90
pub struct StartParam<S, E>91
where92
S: IrqSender,93
E: IoeventFd,94
{95
pub(crate) feature: u128,96
pub(crate) irq_sender: Arc<S>,97
pub(crate) ioeventfds: Option<Arc<[E]>>,98
}99
100
#[derive(Debug, Clone)]101
pub enum WakeEvent<S, E>102
where103
S: IrqSender,104
E: IoeventFd,105
{106
Notify {107
q_index: u16,108
},109
Shutdown,110
#[cfg(target_os = "linux")]111
VuChannel {112
channel: Arc<VuChannel>,113
},114
Start {115
param: StartParam<S, E>,116
},117
Reset,118
}119
120
#[derive(Debug, PartialEq, Eq)]121
pub enum WorkerState {122
Pending,123
Running,124
Shutdown,125
}126
127
#[derive(Debug)]128
pub struct Worker<D, S, E, B>129
where130
S: IrqSender,131
E: IoeventFd,132
{133
context: Context<D, S, E>,134
backend: B,135
}136
137
#[derive(Debug)]138
pub struct VirtioDevice<S, E>139
where140
S: IrqSender,141
E: IoeventFd,142
{143
pub name: Arc<str>,144
pub id: DeviceId,145
pub device_config: Arc<dyn Mmio>,146
pub device_feature: u128,147
pub queue_regs: Arc<[QueueReg]>,148
pub shared_mem_regions: Option<Arc<MemRegion>>,149
pub notifier: Arc<Notifier>,150
pub event_tx: Sender<WakeEvent<S, E>>,151
worker_handle: Option<JoinHandle<()>>,152
}153
154
impl<S, E> VirtioDevice<S, E>155
where156
S: IrqSender,157
E: IoeventFd,158
{159
fn shutdown(&mut self) -> Result<(), Box<dyn std::error::Error>> {160
let Some(handle) = self.worker_handle.take() else {161
return Ok(());162
};163
self.event_tx.send(WakeEvent::Shutdown)?;164
self.notifier.notify()?;165
if let Err(e) = handle.join() {166
log::error!("{}: failed to join worker thread: {e:?}", self.name)167
}168
Ok(())169
}170
171
pub fn new<D>(172
name: impl Into<Arc<str>>,173
dev: D,174
memory: Arc<RamBus>,175
restricted_memory: bool,176
) -> Result<Self>177
where178
D: Virtio,179
{180
let name = name.into();181
let id = dev.id();182
let device_config = dev.config();183
let mut device_feature = dev.feature();184
if restricted_memory {185
device_feature |= VirtioFeature::ACCESS_PLATFORM.bits()186
} else {187
device_feature &= !VirtioFeature::ACCESS_PLATFORM.bits()188
}189
let num_queues = dev.num_queues();190
let queue_regs = (0..num_queues).map(|_| QueueReg {191
size: AtomicU16::new(QUEUE_SIZE_MAX),192
..Default::default()193
});194
let queue_regs = queue_regs.collect::<Arc<_>>();195
196
let shared_mem_regions = dev.shared_mem_regions();197
let (event_tx, event_rx) = mpsc::channel();198
let (handle, notifier) = dev.spawn_worker(event_rx, memory, queue_regs.clone())?;199
log::debug!(200
"{name}: created with {:x?}, {:x?}",201
VirtioFeature::from_bits_retain(device_feature & !D::Feature::all().bits()),202
D::Feature::from_bits_truncate(device_feature)203
);204
let virtio_dev = VirtioDevice {205
name,206
id,207
device_feature,208
queue_regs,209
worker_handle: Some(handle),210
event_tx,211
notifier,212
device_config,213
shared_mem_regions,214
};215
Ok(virtio_dev)216
}217
}218
219
impl<S, E> Drop for VirtioDevice<S, E>220
where221
S: IrqSender,222
E: IoeventFd,223
{224
fn drop(&mut self) {225
if let Err(e) = self.shutdown() {226
log::error!("{}: failed to shutdown: {e}", self.name);227
}228
}229
}230
231
pub trait Backend<D: Virtio>: Send + 'static {232
fn register_notifier(&mut self, token: u64) -> Result<Arc<Notifier>>;233
fn reset(&self, dev: &mut D) -> Result<()>;234
fn event_loop<'m, S, Q, E>(235
&mut self,236
memory: &'m Ram,237
context: &mut Context<D, S, E>,238
queues: &mut [Option<Queue<'_, 'm, Q>>],239
param: &StartParam<S, E>,240
) -> Result<()>241
where242
S: IrqSender,243
Q: VirtQueue<'m>,244
E: IoeventFd;245
}246
247
pub trait BackendEvent {248
fn token(&self) -> u64;249
}250
251
pub trait ActiveBackend<D: Virtio> {252
type Event: BackendEvent;253
fn handle_event(&mut self, dev: &mut D, event: &Self::Event) -> Result<()>;254
fn handle_queue(&mut self, dev: &mut D, index: u16) -> Result<()>;255
}256
257
#[derive(Debug)]258
pub struct Context<D, S, E>259
where260
S: IrqSender,261
E: IoeventFd,262
{263
pub dev: D,264
memory: Arc<RamBus>,265
event_rx: Receiver<WakeEvent<S, E>>,266
queue_regs: Arc<[QueueReg]>,267
pub state: WorkerState,268
}269
270
impl<D, S, E> Context<D, S, E>271
where272
D: Virtio,273
S: IrqSender,274
E: IoeventFd,275
{276
fn handle_wake_events<B>(&mut self, backend: &mut B) -> Result<()>29x277
where29x278
B: ActiveBackend<D>,29x279
{280
while let Ok(event) = self.event_rx.try_recv() {59x281
match event {36x282
WakeEvent::Notify { q_index } => backend.handle_queue(&mut self.dev, q_index)?,30x283
WakeEvent::Shutdown => {284
self.state = WorkerState::Shutdown;6x285
break;6x286
}287
WakeEvent::Start { .. } => {288
log::error!("{}: device has already started", self.dev.name())289
}290
#[cfg(target_os = "linux")]291
WakeEvent::VuChannel { channel } => self.dev.set_vu_channel(channel),292
WakeEvent::Reset => {293
log::info!("{}: guest requested reset", self.dev.name());294
self.state = WorkerState::Pending;295
break;296
}297
}298
}299
Ok(())29x300
}29x301
302
fn wait_start(&mut self) -> Option<StartParam<S, E>> {6x303
for wake_event in self.event_rx.iter() {6x304
match wake_event {6x305
WakeEvent::Reset => {}306
WakeEvent::Start { param } => {6x307
self.state = WorkerState::Running;6x308
return Some(param);6x309
}310
#[cfg(target_os = "linux")]311
WakeEvent::VuChannel { channel } => self.dev.set_vu_channel(channel),312
WakeEvent::Shutdown => break,313
WakeEvent::Notify { q_index } => {314
log::error!(315
"{}: driver notified queue {q_index} before device is ready",316
self.dev.name()317
)318
}319
}320
}321
self.state = WorkerState::Shutdown;322
None323
}6x324
325
pub fn handle_event<B>(&mut self, event: &B::Event, backend: &mut B) -> Result<()>38x326
where38x327
B: ActiveBackend<D>,38x328
{329
if event.token() == TOKEN_WARKER {38x330
self.handle_wake_events(backend)29x331
} else {332
backend.handle_event(&mut self.dev, event)9x333
}334
}38x335
}336
337
impl<D, S, E, B> Worker<D, S, E, B>338
where339
D: Virtio,340
S: IrqSender,341
B: Backend<D>,342
E: IoeventFd,343
{344
pub fn spawn(6x345
dev: D,6x346
mut backend: B,6x347
event_rx: Receiver<WakeEvent<S, E>>,6x348
memory: Arc<RamBus>,6x349
queue_regs: Arc<[QueueReg]>,6x350
) -> Result<(JoinHandle<()>, Arc<Notifier>)> {6x351
let notifier = backend.register_notifier(TOKEN_WARKER)?;6x352
let worker = Worker {6x353
context: Context {6x354
dev,6x355
event_rx,6x356
memory,6x357
queue_regs,6x358
state: WorkerState::Pending,6x359
},6x360
backend,6x361
};6x362
let name = worker.context.dev.name();6x363
let handle = std::thread::Builder::new()6x364
.name(name.to_owned())6x365
.spawn(move || worker.do_work())6x366
.context(error::WorkerThread)?;6x367
Ok((handle, notifier))6x368
}6x369
370
fn event_loop<'m, Q>(6x371
&mut self,6x372
queues: &mut [Option<Queue<'_, 'm, Q>>],6x373
ram: &'m Ram,6x374
param: &StartParam<S, E>,6x375
) -> Result<()>6x376
where6x377
Q: VirtQueue<'m>,6x378
E: IoeventFd,6x379
{380
log::debug!(6x381
"{}: activated with {:x?}, {:x?}",382
self.context.dev.name(),6x383
VirtioFeature::from_bits_retain(param.feature & !D::Feature::all().bits()),6x384
D::Feature::from_bits_truncate(param.feature)6x385
);386
self.backend6x387
.event_loop(ram, &mut self.context, queues, param)6x388
}6x389
390
fn loop_until_reset(&mut self) -> Result<()> {6x391
let Some(param) = self.context.wait_start() else {6x392
return Ok(());393
};394
let memory = self.context.memory.clone();6x395
let ram = memory.lock_layout();6x396
let feature = param.feature & !VirtioFeature::ACCESS_PLATFORM.bits();6x397
let queue_regs = self.context.queue_regs.clone();6x398
let feature = VirtioFeature::from_bits_retain(feature);6x399
let event_idx = feature.contains(VirtioFeature::EVENT_IDX);6x400
if feature.contains(VirtioFeature::RING_PACKED) {6x401
let new_queue = |reg| {402
let Some(split_queue) = PackedQueue::new(reg, &ram, event_idx)? else {403
return Ok(None);404
};405
Ok(Some(Queue::new(split_queue, reg, &ram)))406
};407
let queues: Result<Box<_>> = queue_regs.iter().map(new_queue).collect();408
self.event_loop(&mut (queues?), &ram, ¶m)?;409
} else {410
let new_queue = |reg| {12x411
let Some(split_queue) = SplitQueue::new(reg, &ram, event_idx)? else {12x412
return Ok(None);413
};414
Ok(Some(Queue::new(split_queue, reg, &ram)))12x415
};12x416
let queues: Result<Box<_>> = queue_regs.iter().map(new_queue).collect();6x417
self.event_loop(&mut (queues?), &ram, ¶m)?;6x418
};419
self.backend.reset(&mut self.context.dev)?;6x420
Ok(())6x421
}6x422
423
fn do_work(mut self) {6x424
while self.context.state != WorkerState::Shutdown {12x425
if let Err(e) = self.loop_until_reset() {6x426
log::error!("worker {}: {e:?}", self.context.dev.name(),);427
return;428
}6x429
}430
log::debug!("worker {}: done", self.context.dev.name())6x431
}6x432
}433
434
pub trait DevParam {435
type Device;436
fn build(self, name: impl Into<Arc<str>>) -> Result<Self::Device>;437
fn needs_mem_shared_fd(&self) -> bool {438
false439
}440
}441