vmm/device_manager/
mod.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7
8use std::convert::Infallible;
9use std::fmt::Debug;
10use std::os::unix::io::FromRawFd;
11use std::path::PathBuf;
12use std::sync::{Arc, Mutex};
13
14use acpi::ACPIDeviceManager;
15use event_manager::{MutEventSubscriber, SubscriberOps};
16#[cfg(target_arch = "x86_64")]
17use legacy::{LegacyDeviceError, PortIODeviceManager};
18use linux_loader::loader::Cmdline;
19use log::{info, warn};
20use mmio::{MMIODeviceManager, MmioError};
21use pci_mngr::{PciDevices, PciDevicesConstructorArgs, PciManagerError};
22use persist::MMIODevManagerConstructorArgs;
23use serde::{Deserialize, Serialize};
24use utils::time::TimestampUs;
25use vmm_sys_util::eventfd::EventFd;
26
27use crate::device_manager::acpi::ACPIDeviceError;
28#[cfg(target_arch = "x86_64")]
29use crate::devices::legacy::I8042Device;
30#[cfg(target_arch = "aarch64")]
31use crate::devices::legacy::RTCDevice;
32use crate::devices::legacy::serial::SerialOut;
33use crate::devices::legacy::{IER_RDA_BIT, IER_RDA_OFFSET, SerialDevice};
34use crate::devices::pseudo::BootTimer;
35use crate::devices::virtio::device::VirtioDevice;
36use crate::devices::virtio::transport::mmio::{IrqTrigger, MmioTransport};
37use crate::resources::VmResources;
38use crate::snapshot::Persist;
39use crate::utils::open_file_write_nonblock;
40use crate::vstate::bus::BusError;
41use crate::vstate::memory::GuestMemoryMmap;
42use crate::{EmulateSerialInitError, EventManager, Vm};
43
44/// ACPI device manager.
45pub mod acpi;
46/// Legacy Device Manager.
47pub mod legacy;
48/// Memory Mapped I/O Manager.
49pub mod mmio;
50/// PCIe device manager
51pub mod pci_mngr;
52/// Device managers (de)serialization support.
53pub mod persist;
54
55#[derive(Debug, thiserror::Error, displaydoc::Display)]
56/// Error while creating a new [`DeviceManager`]
57pub enum DeviceManagerCreateError {
58    /// Error with EventFd: {0}
59    EventFd(#[from] std::io::Error),
60    #[cfg(target_arch = "x86_64")]
61    /// Legacy device manager error: {0}
62    PortIOError(#[from] LegacyDeviceError),
63    /// Resource allocator error: {0}
64    ResourceAllocator(#[from] vm_allocator::Error),
65}
66
67#[derive(Debug, thiserror::Error, displaydoc::Display)]
68/// Error while attaching a VirtIO device
69pub enum AttachDeviceError {
70    /// MMIO transport error: {0}
71    MmioTransport(#[from] MmioError),
72    /// Error inserting device in bus: {0}
73    Bus(#[from] BusError),
74    /// Error while registering ACPI with KVM: {0}
75    AttachAcpiDevice(#[from] ACPIDeviceError),
76    #[cfg(target_arch = "aarch64")]
77    /// Cmdline error
78    Cmdline,
79    #[cfg(target_arch = "aarch64")]
80    /// Error creating serial device: {0}
81    CreateSerial(#[from] std::io::Error),
82    /// Error attach PCI device: {0}
83    PciTransport(#[from] PciManagerError),
84}
85
86#[derive(Debug, thiserror::Error, displaydoc::Display)]
87/// Error while searching for a VirtIO device
88pub enum FindDeviceError {
89    /// Device not found
90    DeviceNotFound,
91}
92
93#[derive(Debug)]
94/// A manager of all peripheral devices of Firecracker
95pub struct DeviceManager {
96    /// MMIO devices
97    pub mmio_devices: MMIODeviceManager,
98    #[cfg(target_arch = "x86_64")]
99    /// Legacy devices
100    pub legacy_devices: PortIODeviceManager,
101    /// ACPI devices
102    pub acpi_devices: ACPIDeviceManager,
103    /// PCIe devices
104    pub pci_devices: PciDevices,
105}
106
107impl DeviceManager {
108    // Create a non-blocking duplicate of stdout without changing the global stdout flags.
109    fn open_stdout_nonblocking() -> Result<std::fs::File, std::io::Error> {
110        // SAFETY: libc::dup returns a new fd or -1 on error.
111        let fd = unsafe { libc::dup(libc::STDOUT_FILENO) };
112        if fd < 0 {
113            return Err(std::io::Error::last_os_error());
114        }
115        // SAFETY: Call is safe since parameters are valid.
116        let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
117        if flags < 0 {
118            // SAFETY: closing the fd is safe.
119            unsafe { libc::close(fd) };
120            return Err(std::io::Error::last_os_error());
121        }
122        // SAFETY: Call is safe since parameters are valid.
123        let rc = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
124        if rc < 0 {
125            // SAFETY: closing the fd is safe.
126            unsafe { libc::close(fd) };
127            return Err(std::io::Error::last_os_error());
128        }
129        // SAFETY: fd is a valid file descriptor we own.
130        Ok(unsafe { std::fs::File::from_raw_fd(fd) })
131    }
132
133    /// Sets up the serial device.
134    fn setup_serial_device(
135        event_manager: &mut EventManager,
136        output: Option<&PathBuf>,
137    ) -> Result<Arc<Mutex<SerialDevice>>, std::io::Error> {
138        let (serial_in, serial_out) = match output {
139            Some(path) => (None, open_file_write_nonblock(path).map(SerialOut::File)?),
140            None => {
141                let stdout_file = match Self::open_stdout_nonblocking() {
142                    Ok(file) => SerialOut::File(file),
143                    Err(err) => {
144                        warn!("Falling back to blocking stdout for serial output: {}", err);
145                        SerialOut::Stdout(std::io::stdout())
146                    }
147                };
148                (Some(std::io::stdin()), stdout_file)
149            }
150        };
151
152        let serial = Arc::new(Mutex::new(SerialDevice::new(serial_in, serial_out)?));
153        event_manager.add_subscriber(serial.clone());
154        Ok(serial)
155    }
156
157    #[cfg(target_arch = "x86_64")]
158    fn create_legacy_devices(
159        event_manager: &mut EventManager,
160        vcpus_exit_evt: &EventFd,
161        vm: &Vm,
162        serial_output: Option<&PathBuf>,
163    ) -> Result<PortIODeviceManager, DeviceManagerCreateError> {
164        // Create serial device
165        let serial = Self::setup_serial_device(event_manager, serial_output)?;
166        let reset_evt = vcpus_exit_evt
167            .try_clone()
168            .map_err(DeviceManagerCreateError::EventFd)?;
169        // Create keyboard emulator for reset event
170        let i8042 = Arc::new(Mutex::new(I8042Device::new(reset_evt)?));
171
172        // create pio dev manager with legacy devices
173        let mut legacy_devices = PortIODeviceManager::new(serial, i8042)?;
174        legacy_devices.register_devices(vm)?;
175        Ok(legacy_devices)
176    }
177
178    #[cfg_attr(target_arch = "aarch64", allow(unused))]
179    pub fn new(
180        event_manager: &mut EventManager,
181        vcpus_exit_evt: &EventFd,
182        vm: &Vm,
183        serial_output: Option<&PathBuf>,
184    ) -> Result<Self, DeviceManagerCreateError> {
185        #[cfg(target_arch = "x86_64")]
186        let legacy_devices =
187            Self::create_legacy_devices(event_manager, vcpus_exit_evt, vm, serial_output)?;
188
189        Ok(DeviceManager {
190            mmio_devices: MMIODeviceManager::new(),
191            #[cfg(target_arch = "x86_64")]
192            legacy_devices,
193            acpi_devices: ACPIDeviceManager::new(&mut vm.resource_allocator()),
194            pci_devices: PciDevices::new(),
195        })
196    }
197
198    /// Attaches an MMIO VirtioDevice device to the device manager and event manager.
199    pub(crate) fn attach_mmio_virtio_device<
200        T: 'static + VirtioDevice + MutEventSubscriber + Debug,
201    >(
202        &mut self,
203        vm: &Vm,
204        id: String,
205        device: Arc<Mutex<T>>,
206        cmdline: &mut Cmdline,
207        is_vhost_user: bool,
208    ) -> Result<(), AttachDeviceError> {
209        let interrupt = Arc::new(IrqTrigger::new());
210        // The device mutex mustn't be locked here otherwise it will deadlock.
211        let device =
212            MmioTransport::new(vm.guest_memory().clone(), interrupt, device, is_vhost_user);
213        self.mmio_devices
214            .register_mmio_virtio_for_boot(vm, id, device, cmdline)?;
215
216        Ok(())
217    }
218
219    /// Attaches a VirtioDevice device to the device manager and event manager.
220    pub(crate) fn attach_virtio_device<T: 'static + VirtioDevice + MutEventSubscriber + Debug>(
221        &mut self,
222        vm: &Arc<Vm>,
223        id: String,
224        device: Arc<Mutex<T>>,
225        cmdline: &mut Cmdline,
226        is_vhost_user: bool,
227    ) -> Result<(), AttachDeviceError> {
228        if self.pci_devices.pci_segment.is_some() {
229            self.pci_devices.attach_pci_virtio_device(vm, id, device)?;
230        } else {
231            self.attach_mmio_virtio_device(vm, id, device, cmdline, is_vhost_user)?;
232        }
233
234        Ok(())
235    }
236
237    /// Attaches a [`BootTimer`] to the VM
238    pub(crate) fn attach_boot_timer_device(
239        &mut self,
240        vm: &Vm,
241        request_ts: TimestampUs,
242    ) -> Result<(), AttachDeviceError> {
243        let boot_timer = Arc::new(Mutex::new(BootTimer::new(request_ts)));
244
245        self.mmio_devices
246            .register_mmio_boot_timer(&vm.common.mmio_bus, boot_timer)?;
247
248        Ok(())
249    }
250
251    pub(crate) fn attach_vmgenid_device(&mut self, vm: &Vm) -> Result<(), AttachDeviceError> {
252        self.acpi_devices.attach_vmgenid(vm)?;
253        Ok(())
254    }
255
256    #[cfg(target_arch = "x86_64")]
257    pub(crate) fn attach_vmclock_device(&mut self, vm: &Vm) -> Result<(), AttachDeviceError> {
258        self.acpi_devices.attach_vmclock(vm)?;
259        Ok(())
260    }
261
262    #[cfg(target_arch = "aarch64")]
263    pub(crate) fn attach_legacy_devices_aarch64(
264        &mut self,
265        vm: &Vm,
266        event_manager: &mut EventManager,
267        cmdline: &mut Cmdline,
268        serial_out_path: Option<&PathBuf>,
269    ) -> Result<(), AttachDeviceError> {
270        // Serial device setup.
271        let cmdline_contains_console = cmdline
272            .as_cstring()
273            .map_err(|_| AttachDeviceError::Cmdline)?
274            .into_string()
275            .map_err(|_| AttachDeviceError::Cmdline)?
276            .contains("console=");
277
278        if cmdline_contains_console {
279            let serial = Self::setup_serial_device(event_manager, serial_out_path)?;
280            self.mmio_devices.register_mmio_serial(vm, serial, None)?;
281            self.mmio_devices.add_mmio_serial_to_cmdline(cmdline)?;
282        }
283
284        let rtc = Arc::new(Mutex::new(RTCDevice::new()));
285        self.mmio_devices.register_mmio_rtc(vm, rtc, None)?;
286        Ok(())
287    }
288
289    /// Enables PCIe support for Firecracker devices
290    pub fn enable_pci(&mut self, vm: &Arc<Vm>) -> Result<(), PciManagerError> {
291        self.pci_devices.attach_pci_segment(vm)
292    }
293
294    /// Artificially kick VirtIO devices as if they had external events.
295    pub fn kick_virtio_devices(&self) {
296        info!("Artificially kick devices");
297        // Go through MMIO VirtIO devices
298        let _: Result<(), MmioError> = self.mmio_devices.for_each_virtio_device(|_, _, device| {
299            let mmio_transport_locked = device.inner.lock().expect("Poisoned lock");
300            mmio_transport_locked
301                .device()
302                .lock()
303                .expect("Poisoned lock")
304                .kick();
305            Ok(())
306        });
307        // Go through PCI VirtIO devices
308        for virtio_pci_device in self.pci_devices.virtio_devices.values() {
309            virtio_pci_device
310                .lock()
311                .expect("Poisoned lock")
312                .virtio_device()
313                .lock()
314                .expect("Poisoned lock")
315                .kick();
316        }
317    }
318
319    fn do_mark_virtio_queue_memory_dirty(
320        device: Arc<Mutex<dyn VirtioDevice>>,
321        mem: &GuestMemoryMmap,
322    ) {
323        // SAFETY:
324        // This should never fail as we mark pages only if device has already been activated,
325        // and the address validation was already performed on device activation.
326        let mut locked_device = device.lock().expect("Poisoned lock");
327        if locked_device.is_activated() {
328            locked_device.mark_queue_memory_dirty(mem).unwrap()
329        }
330    }
331
332    /// Mark queue memory dirty for activated VirtIO devices
333    pub fn mark_virtio_queue_memory_dirty(&self, mem: &GuestMemoryMmap) {
334        // Go through MMIO VirtIO devices
335        let _: Result<(), Infallible> = self.mmio_devices.for_each_virtio_device(|_, _, device| {
336            let mmio_transport_locked = device.inner.lock().expect("Poisoned locked");
337            Self::do_mark_virtio_queue_memory_dirty(mmio_transport_locked.device(), mem);
338            Ok(())
339        });
340
341        // Go through PCI VirtIO devices
342        for device in self.pci_devices.virtio_devices.values() {
343            let virtio_device = device.lock().expect("Poisoned lock").virtio_device();
344            Self::do_mark_virtio_queue_memory_dirty(virtio_device, mem);
345        }
346    }
347
348    /// Get a VirtIO device of type `virtio_type` with ID `device_id`
349    pub fn get_virtio_device(
350        &self,
351        virtio_type: u32,
352        device_id: &str,
353    ) -> Option<Arc<Mutex<dyn VirtioDevice>>> {
354        if self.pci_devices.pci_segment.is_some() {
355            let pci_device = self.pci_devices.get_virtio_device(virtio_type, device_id)?;
356            Some(
357                pci_device
358                    .lock()
359                    .expect("Poisoned lock")
360                    .virtio_device()
361                    .clone(),
362            )
363        } else {
364            let mmio_device = self
365                .mmio_devices
366                .get_virtio_device(virtio_type, device_id)?;
367            Some(
368                mmio_device
369                    .inner
370                    .lock()
371                    .expect("Poisoned lock")
372                    .device()
373                    .clone(),
374            )
375        }
376    }
377
378    /// Run fn `f()` for the virtio device matching `virtio_type` and `id`.
379    pub fn with_virtio_device<T, F, R>(&self, id: &str, f: F) -> Result<R, FindDeviceError>
380    where
381        T: VirtioDevice + 'static + Debug,
382        F: FnOnce(&mut T) -> R,
383    {
384        if let Some(device) = self.get_virtio_device(T::const_device_type(), id) {
385            let mut dev = device.lock().expect("Poisoned lock");
386            Ok(f(dev
387                .as_mut_any()
388                .downcast_mut::<T>()
389                .expect("Invalid device for a given device type")))
390        } else {
391            Err(FindDeviceError::DeviceNotFound)
392        }
393    }
394}
395
396#[derive(Debug, Default, Clone, Serialize, Deserialize)]
397/// State of devices in the system
398pub struct DevicesState {
399    /// MMIO devices state
400    pub mmio_state: persist::DeviceStates,
401    /// ACPI devices state
402    pub acpi_state: persist::ACPIDeviceManagerState,
403    /// PCI devices state
404    pub pci_state: pci_mngr::PciDevicesState,
405}
406
407#[derive(Debug, thiserror::Error, displaydoc::Display)]
408pub enum DevicePersistError {
409    /// Error restoring MMIO devices: {0}
410    MmioRestore(#[from] persist::DevicePersistError),
411    /// Error restoring ACPI devices: {0}
412    AcpiRestore(#[from] ACPIDeviceError),
413    /// Error restoring PCI devices: {0}
414    PciRestore(#[from] PciManagerError),
415    /// Error notifying VMGenID device: {0}
416    VmGenidUpdate(#[from] std::io::Error),
417    /// Error resetting serial console: {0}
418    SerialRestore(#[from] EmulateSerialInitError),
419    /// Error inserting device in bus: {0}
420    Bus(#[from] BusError),
421    /// Error creating DeviceManager: {0}
422    DeviceManager(#[from] DeviceManagerCreateError),
423}
424
425pub struct DeviceRestoreArgs<'a> {
426    pub mem: &'a GuestMemoryMmap,
427    pub vm: &'a Arc<Vm>,
428    pub event_manager: &'a mut EventManager,
429    pub vcpus_exit_evt: &'a EventFd,
430    pub vm_resources: &'a mut VmResources,
431    pub instance_id: &'a str,
432}
433
434impl std::fmt::Debug for DeviceRestoreArgs<'_> {
435    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436        f.debug_struct("DeviceRestoreArgs")
437            .field("mem", &self.mem)
438            .field("vm", &self.vm)
439            .field("vm_resources", &self.vm_resources)
440            .field("instance_id", &self.instance_id)
441            .finish()
442    }
443}
444
445impl<'a> Persist<'a> for DeviceManager {
446    type State = DevicesState;
447    type ConstructorArgs = DeviceRestoreArgs<'a>;
448    type Error = DevicePersistError;
449
450    fn save(&self) -> Self::State {
451        DevicesState {
452            mmio_state: self.mmio_devices.save(),
453            acpi_state: self.acpi_devices.save(),
454            pci_state: self.pci_devices.save(),
455        }
456    }
457
458    fn restore(
459        constructor_args: Self::ConstructorArgs,
460        state: &Self::State,
461    ) -> Result<Self, Self::Error> {
462        // Setup legacy devices in case of x86
463        #[cfg(target_arch = "x86_64")]
464        let legacy_devices = Self::create_legacy_devices(
465            constructor_args.event_manager,
466            constructor_args.vcpus_exit_evt,
467            constructor_args.vm,
468            constructor_args.vm_resources.serial_out_path.as_ref(),
469        )?;
470
471        // Restore MMIO devices
472        let mmio_ctor_args = MMIODevManagerConstructorArgs {
473            mem: constructor_args.mem,
474            vm: constructor_args.vm,
475            event_manager: constructor_args.event_manager,
476            vm_resources: constructor_args.vm_resources,
477            instance_id: constructor_args.instance_id,
478        };
479        let mmio_devices = MMIODeviceManager::restore(mmio_ctor_args, &state.mmio_state)?;
480
481        // Restore ACPI devices
482        let mut acpi_devices = ACPIDeviceManager::restore(constructor_args.vm, &state.acpi_state)?;
483        acpi_devices.vmgenid.notify_guest()?;
484
485        // Restore PCI devices
486        let pci_ctor_args = PciDevicesConstructorArgs {
487            vm: constructor_args.vm,
488            mem: constructor_args.mem,
489            vm_resources: constructor_args.vm_resources,
490            instance_id: constructor_args.instance_id,
491            event_manager: constructor_args.event_manager,
492        };
493        let pci_devices = PciDevices::restore(pci_ctor_args, &state.pci_state)?;
494
495        let device_manager = DeviceManager {
496            mmio_devices,
497            #[cfg(target_arch = "x86_64")]
498            legacy_devices,
499            acpi_devices,
500            pci_devices,
501        };
502
503        // Restore serial.
504        // We need to do that after we restore mmio devices, otherwise it won't succeed in Aarch64
505        device_manager.emulate_serial_init()?;
506
507        Ok(device_manager)
508    }
509}
510
511impl DeviceManager {
512    /// Sets RDA bit in serial console
513    pub fn emulate_serial_init(&self) -> Result<(), EmulateSerialInitError> {
514        // When restoring from a previously saved state, there is no serial
515        // driver initialization, therefore the RDA (Received Data Available)
516        // interrupt is not enabled. Because of that, the driver won't get
517        // notified of any bytes that we send to the guest. The clean solution
518        // would be to save the whole serial device state when we do the vm
519        // serialization. For now we set that bit manually
520
521        #[cfg(target_arch = "aarch64")]
522        {
523            if let Some(device) = &self.mmio_devices.serial {
524                let mut device_locked = device.inner.lock().expect("Poisoned lock");
525
526                device_locked
527                    .serial
528                    .write(IER_RDA_OFFSET, IER_RDA_BIT)
529                    .map_err(|_| EmulateSerialInitError(std::io::Error::last_os_error()))?;
530            }
531            Ok(())
532        }
533
534        #[cfg(target_arch = "x86_64")]
535        {
536            let mut serial = self
537                .legacy_devices
538                .stdio_serial
539                .lock()
540                .expect("Poisoned lock");
541
542            serial
543                .serial
544                .write(IER_RDA_OFFSET, IER_RDA_BIT)
545                .map_err(|_| EmulateSerialInitError(std::io::Error::last_os_error()))?;
546            Ok(())
547        }
548    }
549}
550
551#[cfg(test)]
552pub(crate) mod tests {
553    use super::*;
554    #[cfg(target_arch = "aarch64")]
555    use crate::builder::tests::default_vmm;
556    use crate::vstate::resources::ResourceAllocator;
557
558    pub(crate) fn default_device_manager() -> DeviceManager {
559        let mut resource_allocator = ResourceAllocator::new();
560        let mmio_devices = MMIODeviceManager::new();
561        let acpi_devices = ACPIDeviceManager::new(&mut resource_allocator);
562        let pci_devices = PciDevices::new();
563
564        #[cfg(target_arch = "x86_64")]
565        let legacy_devices = PortIODeviceManager::new(
566            Arc::new(Mutex::new(
567                SerialDevice::new(None, SerialOut::Sink).unwrap(),
568            )),
569            Arc::new(Mutex::new(
570                I8042Device::new(EventFd::new(libc::EFD_NONBLOCK).unwrap()).unwrap(),
571            )),
572        )
573        .unwrap();
574
575        DeviceManager {
576            mmio_devices,
577            #[cfg(target_arch = "x86_64")]
578            legacy_devices,
579            acpi_devices,
580            pci_devices,
581        }
582    }
583
584    #[cfg(target_arch = "aarch64")]
585    #[test]
586    fn test_attach_legacy_serial() {
587        let mut vmm = default_vmm();
588        assert!(vmm.device_manager.mmio_devices.rtc.is_none());
589        assert!(vmm.device_manager.mmio_devices.serial.is_none());
590
591        let mut cmdline = Cmdline::new(4096).unwrap();
592        let mut event_manager = EventManager::new().unwrap();
593        vmm.device_manager
594            .attach_legacy_devices_aarch64(&vmm.vm, &mut event_manager, &mut cmdline, None)
595            .unwrap();
596        assert!(vmm.device_manager.mmio_devices.rtc.is_some());
597        assert!(vmm.device_manager.mmio_devices.serial.is_none());
598
599        let mut vmm = default_vmm();
600        cmdline.insert("console", "/dev/blah").unwrap();
601        vmm.device_manager
602            .attach_legacy_devices_aarch64(&vmm.vm, &mut event_manager, &mut cmdline, None)
603            .unwrap();
604        assert!(vmm.device_manager.mmio_devices.rtc.is_some());
605        assert!(vmm.device_manager.mmio_devices.serial.is_some());
606
607        assert!(
608            cmdline
609                .as_cstring()
610                .unwrap()
611                .into_string()
612                .unwrap()
613                .contains(&format!(
614                    "earlycon=uart,mmio,0x{:08x}",
615                    vmm.device_manager
616                        .mmio_devices
617                        .serial
618                        .as_ref()
619                        .unwrap()
620                        .resources
621                        .addr
622                ))
623        );
624    }
625}