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