Alioth Code Coverage

vm.rs0.00%

1// Copyright 2024 Google LLC
2//
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 at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// 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 and
13// limitations under the License.
14
15#[cfg(target_os = "linux")]
16use std::path::Path;
17use std::sync::Arc;
18use std::sync::mpsc::{self, Receiver, Sender};
19use std::thread;
20use std::time::Duration;
21
22#[cfg(target_os = "linux")]
23use parking_lot::Mutex;
24use snafu::{ResultExt, Snafu};
25
26#[cfg(target_arch = "aarch64")]
27use crate::arch::layout::{PL011_START, PL031_START};
28#[cfg(target_arch = "x86_64")]
29use crate::arch::layout::{PORT_CMOS_REG, PORT_COM1, PORT_FW_CFG_SELECTOR, PORT_FWDBG};
30use crate::board::{Board, BoardConfig};
31use crate::device::clock::SystemClock;
32#[cfg(target_arch = "x86_64")]
33use crate::device::cmos::Cmos;
34use crate::device::console::StdioConsole;
35#[cfg(target_arch = "x86_64")]
36use crate::device::fw_cfg::{FwCfg, FwCfgItemParam};
37#[cfg(target_arch = "x86_64")]
38use crate::device::fw_dbg::FwDbg;
39#[cfg(target_arch = "aarch64")]
40use crate::device::pl011::Pl011;
41#[cfg(target_arch = "aarch64")]
42use crate::device::pl031::Pl031;
43#[cfg(target_arch = "x86_64")]
44use crate::device::serial::Serial;
45use crate::errors::{DebugTrace, trace_error};
46use crate::hv::{Hypervisor, IoeventFdRegistry, Vm};
47use crate::loader::Payload;
48use crate::pci::pvpanic::PvPanic;
49use crate::pci::{Bdf, Pci};
50#[cfg(target_os = "linux")]
51use crate::sys::vfio::VfioIommu;
52#[cfg(target_os = "linux")]
53use crate::vfio::cdev::Cdev;
54#[cfg(target_os = "linux")]
55use crate::vfio::container::{Container, UpdateContainerMapping};
56#[cfg(target_os = "linux")]
57use crate::vfio::group::{DevFd, Group};
58#[cfg(target_os = "linux")]
59use crate::vfio::iommu::{Ioas, Iommu, UpdateIommuIoas};
60#[cfg(target_os = "linux")]
61use crate::vfio::pci::VfioPciDev;
62#[cfg(target_os = "linux")]
63use crate::vfio::{CdevParam, ContainerParam, GroupParam, IoasParam};
64use crate::virtio::dev::{DevParam, Virtio, VirtioDevice};
65use crate::virtio::pci::VirtioPciDevice;
66
67#[trace_error]
68#[derive(Snafu, DebugTrace)]
69#[snafu(module, context(suffix(false)))]
70pub 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
103type Result<T, E = Error> = std::result::Result<T, E>;
104
105pub struct Machine<H>
106where
107 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
116pub 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
121impl<H> Machine<H>
122where
123 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 bdf
202 } 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 = params
223 .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 where
241 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 ret
303 }
304}
305
306#[cfg(target_os = "linux")]
307impl<H> Machine<H>
308where
309 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(&param.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) = &param.dev_iommu {
323 dev_iommu
324 } else {
325 Path::new("/dev/iommu")
326 };
327 let iommu = Arc::new(Iommu::new(iommu_path)?);
328 maybe_iommu.replace(iommu.clone());
329 iommu
330 };
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(&param.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(&param.name) {
372 return error::AlreadyExists { name: param.name }.fail();
373 }
374 let vfio_path = if let Some(dev_vfio) = &param.dev_vfio {
375 dev_vfio
376 } 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(&param.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