diff --git a/Cargo.lock b/Cargo.lock index f9a10054843..0b23658e0e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,7 @@ dependencies = [ "rate_limiter 0.1.0", "sys_util 0.1.0", "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "vhost_backend 0.1.0", "vhost_gen 0.1.0", "virtio_gen 0.1.0", diff --git a/arch/src/aarch64/fdt.rs b/arch/src/aarch64/fdt.rs index 981d7740fe6..0166f81829c 100644 --- a/arch/src/aarch64/fdt.rs +++ b/arch/src/aarch64/fdt.rs @@ -22,6 +22,8 @@ use memory_model::{GuestAddress, GuestMemory, GuestMemoryError}; // This is a value for uniquely identifying the FDT node declaring the interrupt controller. const GIC_PHANDLE: u32 = 1; +// This is a value for uniquely identifying the FDT node containing the clock definition. +const CLOCK_PHANDLE: u32 = 2; // Read the documentation specified when appending the root node to the FDT. const ADDRESS_CELLS: u32 = 0x2; const SIZE_CELLS: u32 = 0x2; @@ -73,14 +75,6 @@ pub enum Error { pub type Result = result::Result; -/// Types of devices that get added to the FDT. -#[derive(Clone, Debug)] -pub enum FDTDeviceType { - Virtio, - #[cfg(target_arch = "aarch64")] - Serial, -} - // Creates the flattened device tree for this VM. pub fn create_fdt( guest_mem: &GuestMemory, @@ -112,6 +106,7 @@ pub fn create_fdt( create_chosen_node(&mut fdt, cmdline)?; create_gic_node(&mut fdt, u64::from(num_cpus))?; create_timer_node(&mut fdt)?; + create_clock_node(&mut fdt)?; create_psci_node(&mut fdt)?; device_info.map_or(Ok(()), |v| create_devices_node(&mut fdt, v))?; @@ -220,7 +215,6 @@ fn append_property_string(fdt: &mut Vec, name: &str, value: &str) -> Result< fn append_property_cstring(fdt: &mut Vec, name: &str, cstr_value: &CStr) -> Result<()> { let value_bytes = cstr_value.to_bytes_with_nul(); let cstr_name = CString::new(name).map_err(CstringFDTTransform)?; - // Safe because we allocated fdt, converted name and value to CStrings let fdt_ret = unsafe { fdt_property( @@ -382,6 +376,22 @@ fn create_gic_node(fdt: &mut Vec, vcpu_count: u64) -> Result<()> { Ok(()) } +fn create_clock_node(fdt: &mut Vec) -> Result<()> { + // The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture + // (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power + // consumption and reduced interface complexity. + // PCLK is the clock source and this node defines exactly the clock for the APB. + append_begin_node(fdt, "apb-pclk")?; + append_property_string(fdt, "compatible", "fixed-clock")?; + append_property_u32(fdt, "#clock-cells", 0x0)?; + append_property_u32(fdt, "clock-frequency", 24000000)?; + append_property_string(fdt, "clock-output-names", "clk24mhz")?; + append_property_u32(fdt, "phandle", CLOCK_PHANDLE)?; + append_end_node(fdt)?; + + Ok(()) +} + fn create_timer_node(fdt: &mut Vec) -> Result<()> { // See // https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt @@ -453,14 +463,33 @@ fn create_serial_node( Ok(()) } +fn create_rtc_node( + fdt: &mut Vec, + dev_info: T, +) -> Result<()> { + let compatible = b"arm,pl031\0arm,primecell\0"; + let rtc_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]); + let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_LEVEL_HI]); + append_begin_node(fdt, &format!("rtc@{:x}", dev_info.addr()))?; + append_property(fdt, "compatible", compatible)?; + append_property(fdt, "reg", &rtc_reg_prop)?; + append_property(fdt, "interrupts", &irq)?; + append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?; + append_property_string(fdt, "clock-names", "apb_pclk")?; + append_end_node(fdt)?; + + Ok(()) +} + fn create_devices_node( fdt: &mut Vec, dev_info: &HashMap, ) -> Result<()> { for (_, info) in &*dev_info { match info.type_() { - DeviceType::Virtio => create_virtio_node(fdt, info.clone())?, + DeviceType::RTC => create_rtc_node(fdt, info.clone())?, DeviceType::Serial => create_serial_node(fdt, info.clone())?, + DeviceType::Virtio => create_virtio_node(fdt, info.clone())?, }; } @@ -478,7 +507,7 @@ mod tests { pub struct MMIODeviceInfo { addr: u64, irq: u32, - type_: u32, + type_: DeviceType, } impl DeviceInfoForFDT for MMIODeviceInfo { @@ -491,8 +520,8 @@ mod tests { fn length(&self) -> u64 { LEN } - fn type_(&self) -> u32 { - self.type_ + fn type_(&self) -> &DeviceType { + &self.type_ } } // The `load` function from the `device_tree` will mistakenly check the actual size @@ -514,7 +543,7 @@ mod tests { MMIODeviceInfo { addr: 0x00, irq: 1, - type_: 0, + type_: DeviceType::Serial, }, ), ( @@ -522,15 +551,28 @@ mod tests { MMIODeviceInfo { addr: 0x00 + LEN, irq: 2, - type_: 1, + type_: DeviceType::Virtio, + }, + ), + ( + "rtc".to_string(), + MMIODeviceInfo { + addr: 0x00 + 2 * LEN, + irq: 3, + type_: DeviceType::RTC, }, ), ] .iter() .cloned() .collect(); - let mut dtb = - create_fdt(&mem, 1, &CString::new("console=tty0").unwrap(), &dev_info).unwrap(); + let mut dtb = create_fdt( + &mem, + 1, + &CString::new("console=tty0").unwrap(), + Some(&dev_info), + ) + .unwrap(); /* Use this code when wanting to generate a new DTB sample. { @@ -546,6 +588,7 @@ mod tests { output.write_all(&dtb).unwrap(); } */ + let bytes = include_bytes!("output.dtb"); let pos = 4; let val = layout::FDT_MAX_SIZE; diff --git a/arch/src/aarch64/mod.rs b/arch/src/aarch64/mod.rs index f6dae10f9f7..44499efd846 100644 --- a/arch/src/aarch64/mod.rs +++ b/arch/src/aarch64/mod.rs @@ -24,7 +24,7 @@ impl From for super::Error { } } -pub use self::fdt::{DeviceInfoForFDT, FDTDeviceType}; +pub use self::fdt::DeviceInfoForFDT; /// Returns a Vec of the valid memory addresses for aarch64. /// See [`layout`](layout) module for a drawing of the specific memory model for this platform. diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 72031006f51..d8abd4eddcb 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -46,4 +46,6 @@ pub enum DeviceType { Virtio, #[cfg(target_arch = "aarch64")] Serial, + #[cfg(target_arch = "aarch64")] + RTC, } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 8612a34da24..ce4a86d8cc7 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -7,6 +7,7 @@ authors = ["The Chromium OS Authors"] byteorder = ">=1.2.1" epoll = "=4.0.1" libc = ">=0.2.39" +time = ">=0.1.39" dumbo = { path = "../dumbo" } logger = { path = "../logger" } diff --git a/devices/src/legacy/mod.rs b/devices/src/legacy/mod.rs index ae5156d5a12..620367f77ed 100644 --- a/devices/src/legacy/mod.rs +++ b/devices/src/legacy/mod.rs @@ -6,8 +6,12 @@ // found in the THIRD-PARTY file. mod i8042; +#[cfg(target_arch = "aarch64")] +mod rtc_pl031; mod serial; pub use self::i8042::Error as I8042DeviceError; pub use self::i8042::I8042Device; +#[cfg(target_arch = "aarch64")] +pub use self::rtc_pl031::RTC; pub use self::serial::Serial; diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs new file mode 100644 index 00000000000..83d2b3af1ad --- /dev/null +++ b/devices/src/legacy/rtc_pl031.rs @@ -0,0 +1,273 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! ARM PL031 Real Time Clock +//! +//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter. +//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of +//! a real-time clock input. +//! + +use std::fmt; +use std::ops::Add; +use std::{io, result}; + +use byteorder::{ByteOrder, LittleEndian}; + +use crate::BusDevice; +use logger::{Metric, METRICS}; +use sys_util::EventFd; +use time::{SteadyTime, Timespec}; +//use bus::Error; + +// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf +// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000. +// From 0x0 to 0x1C we have following registers: +const RTCDR: u64 = 0x0; // Data Register. +const RTCMR: u64 = 0x4; // Match Register. +const RTCLR: u64 = 0x8; // Load Regiser. +const RTCCR: u64 = 0xc; // Control Register. +const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register. +const RTCRIS: u64 = 0x14; // Raw Interrupt Status. +const RTCMIS: u64 = 0x18; // Masked Interrupt Status. +const RTCICR: u64 = 0x1c; // Interrupt Clear Register. + // From 0x020 to 0xFDC => reserved space. + // From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers. + // AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity + // of these devices (i.e look at the `amba_device_try_add` function). + // We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array. +const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]; +// We are only interested in the margins. +const AMBA_ID_LOW: u64 = 0xFE0; +const AMBA_ID_HIGH: u64 = 0x1000; + +#[derive(Debug)] +pub enum Error { + BadWriteOffset(u64), + InterruptFailure(io::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset), + Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e), + } + } +} +type Result = result::Result; + +/// A RTC device following the PL031 specification.. +pub struct RTC { + previous_now: SteadyTime, + tick_offset: Timespec, + // This is used for implementing the RTC alarm. However, in Firecracker we do not need it. + match_value: u32, + // Writes to this register load an update value into the RTC. + load: u32, + imsc: u32, + ris: u32, + interrupt_evt: EventFd, +} + +impl RTC { + /// Constructs an AMBA PL031 RTC device. + pub fn new(interrupt_evt: EventFd) -> RTC { + RTC { + // This is used only for duration measuring purposes. + previous_now: SteadyTime::now(), + tick_offset: time::get_time(), + match_value: 0, + load: 0, + imsc: 0, + ris: 0, + interrupt_evt, + } + } + + fn trigger_interrupt(&mut self) -> Result<()> { + self.interrupt_evt.write(1).map_err(Error::InterruptFailure) + } + + fn get_time(&self) -> u32 { + let ts = self.tick_offset.add(SteadyTime::now() - self.previous_now); + ts.sec as u32 + } + + fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> { + match offset { + RTCMR => { + // The MR register is used for implementing the RTC alarm. A real time clock alarm is + // a feature that can be used to allow a computer to 'wake up' after shut down to execute + // tasks every day or on a certain day. It can sometimes be found in the 'Power Management' + // section of a motherboard's BIOS setup. This is functionality that extends beyond + // Firecracker intended use. However, we increment a metric just in case. + self.match_value = val; + METRICS.rtc.missed_write_count.inc(); + } + RTCLR => { + self.load = val; + self.previous_now = SteadyTime::now(); + self.tick_offset = Timespec::new(i64::from(val), 0); + } + RTCIMSC => { + self.imsc = val & 1; + self.trigger_interrupt()?; + } + RTCICR => { + // As per above mentioned doc, the interrupt is cleared by writing any data value to + // the Interrupt Clear Register. + self.ris = 0; + self.trigger_interrupt()?; + } + RTCCR => (), // ignore attempts to turn off the timer. + o => { + return Err(Error::BadWriteOffset(o)); + } + } + Ok(()) + } +} + +impl BusDevice for RTC { + fn read(&mut self, offset: u64, data: &mut [u8]) { + let v; + let mut read_ok = true; + + if offset < AMBA_ID_HIGH && offset >= AMBA_ID_LOW { + let index = ((offset - AMBA_ID_LOW) >> 2) as usize; + v = u32::from(PL031_ID[index]); + } else { + v = match offset { + RTCDR => self.get_time(), + RTCMR => { + METRICS.rtc.missed_read_count.inc(); + // Even though we are not implementing RTC alarm we return the last value + self.match_value + } + RTCLR => self.load, + RTCCR => 1, // RTC is always enabled. + RTCIMSC => self.imsc, + RTCRIS => self.ris, + RTCMIS => self.ris & self.imsc, + _ => { + read_ok = false; + 0 + } + }; + } + if read_ok && data.len() <= 4 { + LittleEndian::write_u32(data, v); + } else { + warn!( + "Invalid RTC PL031 read: offset {}, data length {}", + offset, + data.len() + ); + METRICS.rtc.error_count.inc(); + } + } + + fn write(&mut self, offset: u64, data: &[u8]) { + if data.len() <= 4 { + let v = LittleEndian::read_u32(data); + if let Err(e) = self.handle_write(offset, v) { + warn!("Failed to write to RTC PL031 device: {}", e); + METRICS.rtc.error_count.inc(); + } + } else { + warn!( + "Invalid RTC PL031 write: offset {}, data length {}", + offset, + data.len() + ); + METRICS.rtc.error_count.inc(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rtc_read_write_and_event() { + let mut rtc = RTC::new(EventFd::new().unwrap()); + let mut data = [0; 4]; + + // Read and write to the MR register. + LittleEndian::write_u32(&mut data, 123); + rtc.write(RTCMR, &mut data); + rtc.read(RTCMR, &mut data); + let v = LittleEndian::read_u32(&data); + assert_eq!(v, 123); + + // Read and write to the LR register. + let v = time::get_time(); + LittleEndian::write_u32(&mut data, v.sec as u32); + let tick_offset_before = rtc.tick_offset; + let previous_now_before = rtc.previous_now; + rtc.write(RTCLR, &mut data); + + assert!(rtc.tick_offset.sec == tick_offset_before.sec); + assert!(rtc.previous_now > previous_now_before); + + rtc.read(RTCLR, &mut data); + let v_read = LittleEndian::read_u32(&data); + assert_eq!(v.sec as u32, v_read); + + // Read and write to IMSC register. + // Test with non zero value. + let non_zero = 1; + LittleEndian::write_u32(&mut data, non_zero); + rtc.write(RTCIMSC, &mut data); + // The interrupt line should be on. + assert!(rtc.interrupt_evt.read().unwrap() == 1); + rtc.read(RTCIMSC, &mut data); + let v = LittleEndian::read_u32(&data); + assert_eq!(non_zero & 1, v); + + // Now test with 0. + LittleEndian::write_u32(&mut data, 0); + rtc.write(RTCIMSC, &mut data); + rtc.read(RTCIMSC, &mut data); + let v = LittleEndian::read_u32(&data); + assert_eq!(0, v); + + // Read and write to the ICR register. + LittleEndian::write_u32(&mut data, 1); + rtc.write(RTCICR, &mut data); + // The interrupt line should be on. + assert!(rtc.interrupt_evt.read().unwrap() > 1); + let v_before = LittleEndian::read_u32(&data); + let no_errors_before = METRICS.rtc.error_count.count(); + + rtc.read(RTCICR, &mut data); + let no_errors_after = METRICS.rtc.error_count.count(); + let v = LittleEndian::read_u32(&data); + // ICR is a write only register. Data received should stay equal to data sent. + assert_eq!(v, v_before); + assert_eq!(no_errors_after - no_errors_before, 1); + + // Attempts to turn off the RTC should not go through. + LittleEndian::write_u32(&mut data, 0); + rtc.write(RTCCR, &mut data); + rtc.read(RTCCR, &mut data); + let v = LittleEndian::read_u32(&data); + assert_eq!(v, 1); + + // Attempts to write beyond the writable space. Using here the space used to read + // the CID and PID from. + LittleEndian::write_u32(&mut data, 0); + let no_errors_before = METRICS.rtc.error_count.count(); + rtc.write(AMBA_ID_LOW, &mut data); + let no_errors_after = METRICS.rtc.error_count.count(); + assert_eq!(no_errors_after - no_errors_before, 1); + // However, reading from the AMBA_ID_LOW should succeed upon read. + + let mut data = [0; 8]; + rtc.read(AMBA_ID_LOW, &mut data); + let index = AMBA_ID_LOW + 3; + assert_eq!(data[0], PL031_ID[((index - AMBA_ID_LOW) >> 2) as usize]); + } +} diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 21ace157c09..944dd2df40f 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -9,6 +9,7 @@ extern crate byteorder; extern crate epoll; extern crate libc; +extern crate time; extern crate dumbo; #[macro_use] diff --git a/logger/src/metrics.rs b/logger/src/metrics.rs index 622b95f12ef..c5c0a3baf71 100644 --- a/logger/src/metrics.rs +++ b/logger/src/metrics.rs @@ -321,6 +321,17 @@ pub struct NetDeviceMetrics { pub tx_spoofed_mac_count: SharedMetric, } +/// Metrics specific to the i8042 device. +#[derive(Default, Serialize)] +pub struct RTCDeviceMetrics { + /// Errors triggered while using the i8042 device. + pub error_count: SharedMetric, + /// Number of superfluous read intents on this i8042 device. + pub missed_read_count: SharedMetric, + /// Number of superfluous read intents on this i8042 device. + pub missed_write_count: SharedMetric, +} + /// Metrics for the seccomp filtering. #[derive(Default, Serialize)] pub struct SeccompMetrics { @@ -410,6 +421,8 @@ pub struct FirecrackerMetrics { pub patch_api_requests: PatchRequestsMetrics, /// Metrics related to API PUT requests. pub put_api_requests: PutRequestsMetrics, + /// Metrics related to the RTC device. + pub rtc: RTCDeviceMetrics, /// Metrics related to seccomp filtering. pub seccomp: SeccompMetrics, /// Metrics related to a vcpu's functioning. diff --git a/vmm/src/device_manager/mmio.rs b/vmm/src/device_manager/mmio.rs index d25199a7689..d165b94e5ec 100644 --- a/vmm/src/device_manager/mmio.rs +++ b/vmm/src/device_manager/mmio.rs @@ -61,7 +61,7 @@ type Result = ::std::result::Result; /// This represents the size of the mmio device specified to the kernel as a cmdline option /// It has to be larger than 0x100 (the offset where the configuration space starts from /// the beginning of the memory mapped device registers) + the size of the configuration space -/// Currently hardcoded to 4K +/// Currently hardcoded to 4K. const MMIO_LEN: u64 = 0x1000; /// This represents the offset at which the device should call BusDevice::write in order to write @@ -159,7 +159,7 @@ impl MMIODeviceManager { #[cfg(target_arch = "aarch64")] /// Register an early console at some MMIO address. - pub fn enable_earlycon( + pub fn register_mmio_serial( &mut self, vm: &VmFd, cmdline: &mut kernel_cmdline::Cmdline, @@ -203,6 +203,40 @@ impl MMIODeviceManager { Ok(()) } + #[cfg(target_arch = "aarch64")] + /// Register a MMIO RTC device. + pub fn register_mmio_rtc(&mut self, vm: &VmFd) -> Result<()> { + if self.irq > self.last_irq { + return Err(Error::IrqsExhausted); + } + + // Attaching the RTC device. + let rtc_evt = sys_util::EventFd::new().map_err(Error::EventFd)?; + let device = devices::legacy::RTC::new(rtc_evt.try_clone().map_err(Error::EventFd)?); + vm.register_irqfd(rtc_evt.as_raw_fd(), self.irq) + .map_err(Error::RegisterIrqFd)?; + + self.bus + .insert(Arc::new(Mutex::new(device)), self.mmio_base, MMIO_LEN) + .map_err(|err| Error::BusError(err))?; + + let ret = self.mmio_base; + self.id_to_dev_info.insert( + "rtc".to_string(), + MMIODeviceInfo { + addr: ret, + len: MMIO_LEN, + irq: self.irq, + type_: DeviceType::RTC, + }, + ); + + self.mmio_base += MMIO_LEN; + self.irq += 1; + + Ok(()) + } + #[cfg(target_arch = "aarch64")] /// Gets the information of the devices registered up to some point in time. pub fn get_device_info(&self) -> &HashMap { diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 36c4dcafd2a..89ccef9863c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1186,8 +1186,13 @@ impl Vmm { .as_mut() .ok_or(StartMicrovmError::MissingKernelConfig)?; + if kernel_config.cmdline.as_str().contains("console=") { + device_manager + .register_mmio_serial(self.vm.get_fd(), &mut kernel_config.cmdline) + .map_err(StartMicrovmError::RegisterMMIODevice)?; + } device_manager - .enable_earlycon(self.vm.get_fd(), &mut kernel_config.cmdline) + .register_mmio_rtc(self.vm.get_fd()) .map_err(StartMicrovmError::RegisterMMIODevice)?; Ok(()) }