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_COM1, PORT_FW_CFG_SELECTOR};
30use crate::board::{Board, BoardConfig};
31#[cfg(target_arch = "x86_64")]
32use crate::device::fw_cfg::{FwCfg, FwCfgItemParam};
33#[cfg(target_arch = "aarch64")]
34use crate::device::pl011::Pl011;
35#[cfg(target_arch = "aarch64")]
36use crate::device::pl031::Pl031;
37#[cfg(target_arch = "x86_64")]
38use crate::device::serial::Serial;
39use crate::errors::{DebugTrace, trace_error};
40use crate::hv::{Hypervisor, IoeventFdRegistry, Vm};
41use crate::loader::Payload;
42use crate::pci::pvpanic::PvPanic;
43use crate::pci::{Bdf, Pci};
44#[cfg(target_os = "linux")]
45use crate::sys::vfio::VfioIommu;
46#[cfg(target_os = "linux")]
47use crate::vfio::cdev::Cdev;
48#[cfg(target_os = "linux")]
49use crate::vfio::container::{Container, UpdateContainerMapping};
50#[cfg(target_os = "linux")]
51use crate::vfio::group::{DevFd, Group};
52#[cfg(target_os = "linux")]
53use crate::vfio::iommu::{Ioas, Iommu, UpdateIommuIoas};
54#[cfg(target_os = "linux")]
55use crate::vfio::pci::VfioPciDev;
56#[cfg(target_os = "linux")]
57use crate::vfio::{CdevParam, ContainerParam, GroupParam, IoasParam};
58use crate::virtio::dev::{DevParam, Virtio, VirtioDevice};
59use crate::virtio::pci::VirtioPciDevice;
60
61#[trace_error]
62#[derive(Snafu, DebugTrace)]
63#[snafu(module, context(suffix(false)))]
64pub 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
97type Result<T, E = Error> = std::result::Result<T, E>;
98
99pub struct Machine<H>
100where
101 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
110pub 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
115impl<H> Machine<H>
116where
117 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 bdf
180 } 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 = params
201 .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 where
219 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 ret
281 }
282}
283
284#[cfg(target_os = "linux")]
285impl<H> Machine<H>
286where
287 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(&param.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) = &param.dev_iommu {
301 dev_iommu
302 } else {
303 Path::new("/dev/iommu")
304 };
305 let iommu = Arc::new(Iommu::new(iommu_path)?);
306 maybe_iommu.replace(iommu.clone());
307 iommu
308 };
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(&param.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(&param.name) {
350 return error::AlreadyExists { name: param.name }.fail();
351 }
352 let vfio_path = if let Some(dev_vfio) = &param.dev_vfio {
353 dev_vfio
354 } 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(&param.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