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