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