vm.rs0.00%
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
#[cfg(target_os = "linux")]16
use std::path::Path;17
use std::sync::Arc;18
use std::sync::mpsc::{self, Receiver, Sender};19
use std::thread;20
use std::time::Duration;21
22
#[cfg(target_os = "linux")]23
use parking_lot::Mutex;24
use snafu::{ResultExt, Snafu};25
26
#[cfg(target_arch = "aarch64")]27
use crate::arch::layout::{PL011_START, PL031_START};28
#[cfg(target_arch = "x86_64")]29
use crate::arch::layout::{PORT_COM1, PORT_FW_CFG_SELECTOR};30
use crate::board::{Board, BoardConfig};31
#[cfg(target_arch = "x86_64")]32
use crate::device::fw_cfg::{FwCfg, FwCfgItemParam};33
#[cfg(target_arch = "aarch64")]34
use crate::device::pl011::Pl011;35
#[cfg(target_arch = "aarch64")]36
use crate::device::pl031::Pl031;37
#[cfg(target_arch = "x86_64")]38
use crate::device::serial::Serial;39
use crate::errors::{DebugTrace, trace_error};40
use crate::hv::{Hypervisor, IoeventFdRegistry, Vm};41
use crate::loader::Payload;42
use crate::pci::pvpanic::PvPanic;43
use crate::pci::{Bdf, Pci};44
#[cfg(target_os = "linux")]45
use crate::sys::vfio::VfioIommu;46
#[cfg(target_os = "linux")]47
use crate::vfio::cdev::Cdev;48
#[cfg(target_os = "linux")]49
use crate::vfio::container::{Container, UpdateContainerMapping};50
#[cfg(target_os = "linux")]51
use crate::vfio::group::{DevFd, Group};52
#[cfg(target_os = "linux")]53
use crate::vfio::iommu::{Ioas, Iommu, UpdateIommuIoas};54
#[cfg(target_os = "linux")]55
use crate::vfio::pci::VfioPciDev;56
#[cfg(target_os = "linux")]57
use crate::vfio::{CdevParam, ContainerParam, GroupParam, IoasParam};58
use crate::virtio::dev::{DevParam, Virtio, VirtioDevice};59
use crate::virtio::pci::VirtioPciDevice;60
61
#[trace_error]62
#[derive(Snafu, DebugTrace)]63
#[snafu(module, context(suffix(false)))]64
pub enum Error {65
#[snafu(display("Hypervisor internal error"), context(false))]66
HvError { source: Box<crate::hv::Error> },67
#[snafu(display("Failed to create board"), context(false))]68
CreateBoard { source: Box<crate::board::Error> },69
#[snafu(display("Failed to create VCPU-{index} thread"))]70
VcpuThread { index: u16, error: std::io::Error },71
#[snafu(display("Failed to create a console"))]72
CreateConsole { error: std::io::Error },73
#[snafu(display("Failed to create fw-cfg device"))]74
FwCfg { error: std::io::Error },75
#[snafu(display("Failed to create a VirtIO device"), context(false))]76
CreateVirtio { source: Box<crate::virtio::Error> },77
#[snafu(display("Guest memory is not backed by sharable file descriptors"))]78
MemNotSharedFd,79
#[cfg(target_os = "linux")]80
#[snafu(display("Failed to create a VFIO device"), context(false))]81
CreateVfio { source: Box<crate::vfio::Error> },82
#[snafu(display("VCPU-{index} error"))]83
VcpuError {84
index: u16,85
source: Box<crate::board::Error>,86
},87
#[snafu(display("Failed to configure guest memory"), context(false))]88
Memory { source: Box<crate::mem::Error> },89
#[cfg(target_os = "linux")]90
#[snafu(display("{name:?} already exists"))]91
AlreadyExists { name: Box<str> },92
#[cfg(target_os = "linux")]93
#[snafu(display("{name:?} does not exist"))]94
NotExist { name: Box<str> },95
}96
97
type Result<T, E = Error> = std::result::Result<T, E>;98
99
pub struct Machine<H>100
where101
H: Hypervisor,102
{103
board: Arc<Board<H::Vm>>,104
#[cfg(target_os = "linux")]105
iommu: Mutex<Option<Arc<Iommu>>>,106
event_rx: Receiver<u16>,107
_event_tx: Sender<u16>,108
}109
110
pub type VirtioPciDev<H> = VirtioPciDevice<111
<<H as Hypervisor>::Vm as Vm>::MsiSender,112
<<<H as Hypervisor>::Vm as Vm>::IoeventFdRegistry as IoeventFdRegistry>::IoeventFd,113
>;114
115
impl<H> Machine<H>116
where117
H: Hypervisor,118
{119
pub fn new(hv: &H, config: BoardConfig) -> Result<Self> {120
let board = Arc::new(Board::new(hv, config)?);121
122
let (event_tx, event_rx) = mpsc::channel();123
124
let mut vcpus = board.vcpus.write();125
for index in 0..board.config.cpu.count {126
let event_tx = event_tx.clone();127
let board = board.clone();128
let handle = thread::Builder::new()129
.name(format!("vcpu_{index}"))130
.spawn(move || board.run_vcpu(index, event_tx))131
.context(error::VcpuThread { index })?;132
if event_rx.recv_timeout(Duration::from_secs(2)).is_err() {133
let err = std::io::ErrorKind::TimedOut.into();134
Err(err).context(error::VcpuThread { index })?;135
}136
vcpus.push(handle);137
}138
drop(vcpus);139
140
board.arch_init()?;141
142
let vm = Machine {143
board,144
event_rx,145
_event_tx: event_tx,146
#[cfg(target_os = "linux")]147
iommu: Mutex::new(None),148
};149
150
Ok(vm)151
}152
153
#[cfg(target_arch = "x86_64")]154
pub fn add_com1(&self) -> Result<(), Error> {155
let io_apic = self.board.arch.io_apic.clone();156
let com1 = Serial::new(PORT_COM1, io_apic, 4).context(error::CreateConsole)?;157
self.board.io_devs.write().push((PORT_COM1, Arc::new(com1)));158
Ok(())159
}160
161
#[cfg(target_arch = "aarch64")]162
pub fn add_pl011(&self) -> Result<(), Error> {163
let irq_line = self.board.vm.create_irq_sender(1)?;164
let pl011_dev = Pl011::new(PL011_START, irq_line).context(error::CreateConsole)?;165
let mut mmio_devs = self.board.mmio_devs.write();166
mmio_devs.push((PL011_START, Arc::new(pl011_dev)));167
Ok(())168
}169
170
#[cfg(target_arch = "aarch64")]171
pub fn add_pl031(&self) {172
let pl031_dev = Pl031::new(PL031_START);173
let mut mmio_devs = self.board.mmio_devs.write();174
mmio_devs.push((PL031_START, Arc::new(pl031_dev)));175
}176
177
pub fn add_pci_dev(&self, bdf: Option<Bdf>, dev: Arc<dyn Pci>) -> Result<(), Error> {178
let bdf = if let Some(bdf) = bdf {179
bdf180
} else {181
self.board.pci_bus.reserve(None).unwrap()182
};183
dev.config().get_header().set_bdf(bdf);184
log::info!("{bdf}: device: {}", dev.name());185
self.board.pci_bus.add(bdf, dev);186
Ok(())187
}188
189
pub fn add_pvpanic(&self) -> Result<(), Error> {190
let dev = PvPanic::new();191
let pci_dev = Arc::new(dev);192
self.add_pci_dev(None, pci_dev)193
}194
195
#[cfg(target_arch = "x86_64")]196
pub fn add_fw_cfg(197
&self,198
params: impl Iterator<Item = FwCfgItemParam>,199
) -> Result<Arc<Mutex<FwCfg>>, Error> {200
let items = params201
.map(|p| p.build())202
.collect::<Result<Vec<_>, _>>()203
.context(error::FwCfg)?;204
let fw_cfg = Arc::new(Mutex::new(205
FwCfg::new(self.board.memory.ram_bus(), items).context(error::FwCfg)?,206
));207
let mut io_devs = self.board.io_devs.write();208
io_devs.push((PORT_FW_CFG_SELECTOR, fw_cfg.clone()));209
*self.board.fw_cfg.lock() = Some(fw_cfg.clone());210
Ok(fw_cfg)211
}212
213
pub fn add_virtio_dev<D, P>(214
&self,215
name: impl Into<Arc<str>>,216
param: P,217
) -> Result<Arc<VirtioPciDev<H>>, Error>218
where219
P: DevParam<Device = D>,220
D: Virtio,221
{222
if param.needs_mem_shared_fd() && !self.board.config.mem.has_shared_fd() {223
return error::MemNotSharedFd.fail();224
}225
let name = name.into();226
let bdf = self.board.pci_bus.reserve(None).unwrap();227
let dev = param.build(name.clone())?;228
if let Some(callback) = dev.mem_update_callback() {229
self.board.memory.register_update_callback(callback)?;230
}231
if let Some(callback) = dev.mem_change_callback() {232
self.board.memory.register_change_callback(callback)?;233
}234
let registry = self.board.vm.create_ioeventfd_registry()?;235
let virtio_dev = VirtioDevice::new(236
name.clone(),237
dev,238
self.board.memory.ram_bus(),239
self.board.config.coco.is_some(),240
)?;241
let msi_sender = self.board.vm.create_msi_sender(242
#[cfg(target_arch = "aarch64")]243
u32::from(bdf.0),244
)?;245
let dev = VirtioPciDevice::new(virtio_dev, msi_sender, registry)?;246
let dev = Arc::new(dev);247
self.add_pci_dev(Some(bdf), dev.clone())?;248
Ok(dev)249
}250
251
pub fn add_payload(&self, payload: Payload) {252
*self.board.payload.write() = Some(payload)253
}254
255
pub fn boot(&self) -> Result<(), Error> {256
self.board.boot()?;257
Ok(())258
}259
260
pub fn wait(&self) -> Result<()> {261
self.event_rx.recv().unwrap();262
let vcpus = self.board.vcpus.read();263
for _ in 1..vcpus.len() {264
self.event_rx.recv().unwrap();265
}266
drop(vcpus);267
let mut vcpus = self.board.vcpus.write();268
let mut ret = Ok(());269
for (index, handle) in vcpus.drain(..).enumerate() {270
let Ok(r) = handle.join() else {271
log::error!("Cannot join VCPU-{index}");272
continue;273
};274
if r.is_err() && ret.is_ok() {275
ret = r.context(error::Vcpu {276
index: index as u16,277
});278
}279
}280
ret281
}282
}283
284
#[cfg(target_os = "linux")]285
impl<H> Machine<H>286
where287
H: Hypervisor,288
{289
const DEFAULT_NAME: &str = "default";290
291
pub fn add_vfio_ioas(&self, param: IoasParam) -> Result<Arc<Ioas>, Error> {292
let mut ioases = self.board.vfio_ioases.lock();293
if ioases.contains_key(¶m.name) {294
return error::AlreadyExists { name: param.name }.fail();295
}296
let maybe_iommu = &mut *self.iommu.lock();297
let iommu = if let Some(iommu) = maybe_iommu {298
iommu.clone()299
} else {300
let iommu_path = if let Some(dev_iommu) = ¶m.dev_iommu {301
dev_iommu302
} else {303
Path::new("/dev/iommu")304
};305
let iommu = Arc::new(Iommu::new(iommu_path)?);306
maybe_iommu.replace(iommu.clone());307
iommu308
};309
let ioas = Arc::new(Ioas::alloc_on(iommu)?);310
let update = Box::new(UpdateIommuIoas { ioas: ioas.clone() });311
self.board.memory.register_change_callback(update)?;312
ioases.insert(param.name, ioas.clone());313
Ok(ioas)314
}315
316
fn get_ioas(&self, name: Option<&str>) -> Result<Arc<Ioas>> {317
let ioas_name = name.unwrap_or(Self::DEFAULT_NAME);318
if let Some(ioas) = self.board.vfio_ioases.lock().get(ioas_name) {319
return Ok(ioas.clone());320
};321
if name.is_none() {322
self.add_vfio_ioas(IoasParam {323
name: Self::DEFAULT_NAME.into(),324
dev_iommu: None,325
})326
} else {327
error::NotExist { name: ioas_name }.fail()328
}329
}330
331
pub fn add_vfio_cdev(&self, name: Arc<str>, param: CdevParam) -> Result<(), Error> {332
let ioas = self.get_ioas(param.ioas.as_deref())?;333
334
let mut cdev = Cdev::new(¶m.path)?;335
cdev.attach_iommu_ioas(ioas.clone())?;336
337
let bdf = self.board.pci_bus.reserve(None).unwrap();338
let msi_sender = self.board.vm.create_msi_sender(339
#[cfg(target_arch = "aarch64")]340
u32::from(bdf.0),341
)?;342
let dev = VfioPciDev::new(name.clone(), cdev, msi_sender)?;343
self.add_pci_dev(Some(bdf), Arc::new(dev))?;344
Ok(())345
}346
347
pub fn add_vfio_container(&self, param: ContainerParam) -> Result<Arc<Container>, Error> {348
let mut containers = self.board.vfio_containers.lock();349
if containers.contains_key(¶m.name) {350
return error::AlreadyExists { name: param.name }.fail();351
}352
let vfio_path = if let Some(dev_vfio) = ¶m.dev_vfio {353
dev_vfio354
} else {355
Path::new("/dev/vfio/vfio")356
};357
let container = Arc::new(Container::new(vfio_path)?);358
let update = Box::new(UpdateContainerMapping {359
container: container.clone(),360
});361
self.board.memory.register_change_callback(update)?;362
containers.insert(param.name, container.clone());363
Ok(container)364
}365
366
fn get_container(&self, name: Option<&str>) -> Result<Arc<Container>> {367
let container_name = name.unwrap_or(Self::DEFAULT_NAME);368
if let Some(container) = self.board.vfio_containers.lock().get(container_name) {369
return Ok(container.clone());370
}371
if name.is_none() {372
self.add_vfio_container(ContainerParam {373
name: Self::DEFAULT_NAME.into(),374
dev_vfio: None,375
})376
} else {377
error::NotExist {378
name: container_name,379
}380
.fail()381
}382
}383
384
pub fn add_vfio_devs_in_group(&self, name: &str, param: GroupParam) -> Result<()> {385
let container = self.get_container(param.container.as_deref())?;386
let mut group = Group::new(¶m.path)?;387
group.attach(container, VfioIommu::TYPE1_V2)?;388
389
let group = Arc::new(group);390
for device in param.devices {391
let devfd = DevFd::new(group.clone(), &device)?;392
let name = format!("{name}-{device}");393
self.add_vfio_devfd(name.into(), devfd)?;394
}395
396
Ok(())397
}398
399
fn add_vfio_devfd(&self, name: Arc<str>, devfd: DevFd) -> Result<()> {400
let bdf = self.board.pci_bus.reserve(None).unwrap();401
let msi_sender = self.board.vm.create_msi_sender(402
#[cfg(target_arch = "aarch64")]403
u32::from(bdf.0),404
)?;405
let dev = VfioPciDev::new(name.clone(), devfd, msi_sender)?;406
self.add_pci_dev(Some(bdf), Arc::new(dev))407
}408
}409