diff --git a/devices/src/legacy/mod.rs b/devices/src/legacy/mod.rs index 8bc7c542a..8eceb4cfd 100644 --- a/devices/src/legacy/mod.rs +++ b/devices/src/legacy/mod.rs @@ -10,6 +10,8 @@ mod cmos; #[cfg(feature = "fwdebug")] mod fwdebug; mod i8042; +#[cfg(target_arch = "aarch64")] +mod rtc_pl031; mod serial; #[cfg(feature = "cmos")] @@ -18,3 +20,6 @@ pub use self::cmos::Cmos; pub use self::fwdebug::FwDebugDevice; pub use self::i8042::I8042Device; pub use self::serial::Serial; + +#[cfg(target_arch = "aarch64")] +pub use self::rtc_pl031::RTC; diff --git a/devices/src/legacy/rtc_pl031.rs b/devices/src/legacy/rtc_pl031.rs new file mode 100644 index 000000000..9dc98d627 --- /dev/null +++ b/devices/src/legacy/rtc_pl031.rs @@ -0,0 +1,626 @@ +// Copyright 2020 Arm Limited (or its affiliates). All rights reserved. +// 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::sync::Arc; +use std::time::Instant; +use std::{io, result}; + +use crate::BusDevice; +use vm_device::interrupt::InterruptSourceGroup; + +// 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; +/// Constant to convert seconds to nanoseconds. +pub const NANOS_PER_SECOND: u64 = 1_000_000_000; + +#[allow(unused_macros)] +macro_rules! generate_read_fn { + ($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => { + #[allow(dead_code)] + pub fn $fn_name(input: &[$byte_type]) -> $data_type { + assert!($type_size == std::mem::size_of::<$data_type>()); + let mut array = [0u8; $type_size]; + for (byte, read) in array.iter_mut().zip(input.iter().cloned()) { + *byte = read as u8; + } + <$data_type>::$endian_type(array) + } + }; +} + +#[allow(unused_macros)] +macro_rules! generate_write_fn { + ($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => { + #[allow(dead_code)] + pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) { + for (byte, read) in buf + .iter_mut() + .zip(<$data_type>::$endian_type(n).iter().cloned()) + { + *byte = read as $byte_type; + } + } + }; +} + +generate_read_fn!(read_le_u16, u16, u8, 2, from_le_bytes); +generate_read_fn!(read_le_u32, u32, u8, 4, from_le_bytes); +generate_read_fn!(read_le_u64, u64, u8, 8, from_le_bytes); +generate_read_fn!(read_le_i32, i32, i8, 4, from_le_bytes); + +generate_read_fn!(read_be_u16, u16, u8, 2, from_be_bytes); +generate_read_fn!(read_be_u32, u32, u8, 4, from_be_bytes); + +generate_write_fn!(write_le_u16, u16, u8, to_le_bytes); +generate_write_fn!(write_le_u32, u32, u8, to_le_bytes); +generate_write_fn!(write_le_u64, u64, u8, to_le_bytes); +generate_write_fn!(write_le_i32, i32, i8, to_le_bytes); + +generate_write_fn!(write_be_u16, u16, u8, to_be_bytes); +generate_write_fn!(write_be_u32, u32, u8, to_be_bytes); + +#[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; + +/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source. +pub enum ClockType { + /// Equivalent to `libc::CLOCK_MONOTONIC`. + Monotonic, + /// Equivalent to `libc::CLOCK_REALTIME`. + #[allow(dead_code)] + Real, + /// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`. + ProcessCpu, + /// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`. + #[allow(dead_code)] + ThreadCpu, +} + +impl Into for ClockType { + fn into(self) -> libc::clockid_t { + match self { + ClockType::Monotonic => libc::CLOCK_MONOTONIC, + ClockType::Real => libc::CLOCK_REALTIME, + ClockType::ProcessCpu => libc::CLOCK_PROCESS_CPUTIME_ID, + ClockType::ThreadCpu => libc::CLOCK_THREAD_CPUTIME_ID, + } + } +} + +/// Structure representing the date in local time with nanosecond precision. +pub struct LocalTime { + /// Seconds in current minute. + sec: i32, + /// Minutes in current hour. + min: i32, + /// Hours in current day, 24H format. + hour: i32, + /// Days in current month. + mday: i32, + /// Months in current year. + mon: i32, + /// Years passed since 1900 BC. + year: i32, + /// Nanoseconds in current second. + nsec: i64, +} + +impl LocalTime { + /// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment. + #[allow(dead_code)] + pub fn now() -> LocalTime { + let mut timespec = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + let mut tm: libc::tm = libc::tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: std::ptr::null(), + }; + + // Safe because the parameters are valid. + unsafe { + libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec); + libc::localtime_r(×pec.tv_sec, &mut tm); + } + + LocalTime { + sec: tm.tm_sec, + min: tm.tm_min, + hour: tm.tm_hour, + mday: tm.tm_mday, + mon: tm.tm_mon, + year: tm.tm_year, + nsec: timespec.tv_nsec, + } + } +} + +impl fmt::Display for LocalTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}", + self.year + 1900, + self.mon + 1, + self.mday, + self.hour, + self.min, + self.sec, + self.nsec + ) + } +} + +/// Holds a micro-second resolution timestamp with both the real time and cpu time. +#[derive(Clone)] +pub struct TimestampUs { + /// Real time in microseconds. + pub time_us: u64, + /// Cpu time in microseconds. + pub cputime_us: u64, +} + +impl Default for TimestampUs { + fn default() -> TimestampUs { + TimestampUs { + time_us: get_time(ClockType::Monotonic) / 1000, + cputime_us: get_time(ClockType::ProcessCpu) / 1000, + } + } +} + +/// Returns a timestamp in nanoseconds from a monotonic clock. +/// +/// Uses `_rdstc` on `x86_64` and [`get_time`](fn.get_time.html) on other architectures. +#[allow(dead_code)] +pub fn timestamp_cycles() -> u64 { + #[cfg(target_arch = "x86_64")] + // Safe because there's nothing that can go wrong with this call. + unsafe { + std::arch::x86_64::_rdtsc() as u64 + } + #[cfg(not(target_arch = "x86_64"))] + { + get_time(ClockType::Monotonic) + } +} + +/// Returns a timestamp in nanoseconds based on the provided clock type. +/// +/// # Arguments +/// +/// * `clock_type` - Identifier of the Linux Kernel clock on which to act. +pub fn get_time(clock_type: ClockType) -> u64 { + let mut time_struct = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + // Safe because the parameters are valid. + unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) }; + seconds_to_nanoseconds(time_struct.tv_sec).unwrap() as u64 + (time_struct.tv_nsec as u64) +} + +/// Converts a timestamp in seconds to an equivalent one in nanoseconds. +/// Returns `None` if the conversion overflows. +/// +/// # Arguments +/// +/// * `value` - Timestamp in seconds. +pub fn seconds_to_nanoseconds(value: i64) -> Option { + value.checked_mul(NANOS_PER_SECOND as i64) +} + +/// A RTC device following the PL031 specification.. +pub struct RTC { + previous_now: Instant, + tick_offset: i64, + // 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: Arc>, +} + +impl RTC { + /// Constructs an AMBA PL031 RTC device. + pub fn new(interrupt: Arc>) -> RTC { + RTC { + // This is used only for duration measuring purposes. + previous_now: Instant::now(), + tick_offset: get_time(ClockType::Real) as i64, + match_value: 0, + load: 0, + imsc: 0, + ris: 0, + interrupt, + } + } + + fn trigger_interrupt(&mut self) -> Result<()> { + self.interrupt.trigger(0).map_err(Error::InterruptFailure)?; + Ok(()) + } + + fn get_time(&self) -> u32 { + let ts = (self.tick_offset as i128) + + (Instant::now().duration_since(self.previous_now).as_nanos() as i128); + (ts / NANOS_PER_SECOND as i128) 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; + } + RTCLR => { + self.load = val; + self.previous_now = Instant::now(); + // If the unwrap fails, then the internal value of the clock has been corrupted and + // we want to terminate the execution of the process. + self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap(); + } + 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, _base: u64, 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 => { + // 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 { + write_le_u32(data, v); + } else { + warn!( + "Invalid RTC PL031 read: offset {}, data length {}", + offset, + data.len() + ); + } + } + + fn write(&mut self, _base: u64, offset: u64, data: &[u8]) { + if data.len() <= 4 { + let v = read_le_u32(&data[..]); + if let Err(e) = self.handle_write(offset, v) { + warn!("Failed to write to RTC PL031 device: {}", e); + } + } else { + warn!( + "Invalid RTC PL031 write: offset {}, data length {}", + offset, + data.len() + ); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; + use vmm_sys_util::eventfd::EventFd; + + const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000; + + #[test] + fn test_get_time() { + for _ in 0..1000 { + assert!(get_time(ClockType::Monotonic) <= get_time(ClockType::Monotonic)); + } + + for _ in 0..1000 { + assert!(get_time(ClockType::ProcessCpu) <= get_time(ClockType::ProcessCpu)); + } + + for _ in 0..1000 { + assert!(get_time(ClockType::ThreadCpu) <= get_time(ClockType::ThreadCpu)); + } + + assert_ne!(get_time(ClockType::Real), 0); + } + + #[test] + fn test_local_time_display() { + let local_time = LocalTime { + sec: 30, + min: 15, + hour: 10, + mday: 4, + mon: 6, + year: 119, + nsec: 123_456_789, + }; + assert_eq!( + String::from("2019-07-04T10:15:30.123456789"), + local_time.to_string() + ); + + let local_time = LocalTime { + sec: 5, + min: 5, + hour: 5, + mday: 23, + mon: 7, + year: 44, + nsec: 123, + }; + assert_eq!( + String::from("1944-08-23T05:05:05.000000123"), + local_time.to_string() + ); + + let local_time = LocalTime::now(); + assert!(local_time.mon >= 0 && local_time.mon <= 11); + } + + #[test] + fn test_seconds_to_nanoseconds() { + assert_eq!( + seconds_to_nanoseconds(100).unwrap() as u64, + 100 * NANOS_PER_SECOND + ); + + assert!(seconds_to_nanoseconds(9_223_372_037).is_none()); + } + + struct TestInterrupt { + event_fd: EventFd, + } + + impl InterruptSourceGroup for TestInterrupt { + fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> { + self.event_fd.write(1) + } + + fn update( + &self, + _index: InterruptIndex, + _config: InterruptSourceConfig, + ) -> result::Result<(), std::io::Error> { + Ok(()) + } + + fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> { + Some(&self.event_fd) + } + } + + impl TestInterrupt { + fn new(event_fd: EventFd) -> Self { + TestInterrupt { event_fd } + } + } + + #[test] + fn test_rtc_read_write_and_event() { + let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + + let mut rtc = RTC::new(Arc::new(Box::new(TestInterrupt::new( + intr_evt.try_clone().unwrap(), + )))); + let mut data = [0; 4]; + + // Read and write to the MR register. + write_le_u32(&mut data, 123); + rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data); + rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 123); + + // Read and write to the LR register. + let v = get_time(ClockType::Real); + write_le_u32(&mut data, (v / NANOS_PER_SECOND) as u32); + let previous_now_before = rtc.previous_now; + rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data); + + assert!(rtc.previous_now > previous_now_before); + + rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data); + let v_read = read_le_u32(&data[..]); + assert_eq!((v / NANOS_PER_SECOND) as u32, v_read); + + // Read and write to IMSC register. + // Test with non zero value. + let non_zero = 1; + write_le_u32(&mut data, non_zero); + rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data); + // The interrupt line should be on. + assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1); + rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(non_zero & 1, v); + + // Now test with 0. + write_le_u32(&mut data, 0); + rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data); + rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(0, v); + + // Read and write to the ICR register. + write_le_u32(&mut data, 1); + rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data); + // The interrupt line should be on. + assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1); + let v_before = read_le_u32(&data[..]); + + rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data); + let v = read_le_u32(&data[..]); + // ICR is a write only register. Data received should stay equal to data sent. + assert_eq!(v, v_before); + + // Attempts to turn off the RTC should not go through. + write_le_u32(&mut data, 0); + rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data); + rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data); + let v = read_le_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. + write_le_u32(&mut data, 0); + rtc.write(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data); + // However, reading from the AMBA_ID_LOW should succeed upon read. + + let mut data = [0; 4]; + rtc.read(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data); + let index = AMBA_ID_LOW + 3; + assert_eq!(data[0], PL031_ID[((index - AMBA_ID_LOW) >> 2) as usize]); + } + + macro_rules! byte_order_test_read_write { + ($test_name: ident, $write_fn_name: ident, $read_fn_name: ident, $is_be: expr, $data_type: ty) => { + #[test] + fn $test_name() { + #[allow(overflowing_literals)] + let test_cases = [ + ( + 0x0123_4567_89AB_CDEF as u64, + [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef], + ), + ( + 0x0000_0000_0000_0000 as u64, + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + ), + ( + 0x1923_2345_ABF3_CCD4 as u64, + [0x19, 0x23, 0x23, 0x45, 0xAB, 0xF3, 0xCC, 0xD4], + ), + ( + 0x0FF0_0FF0_0FF0_0FF0 as u64, + [0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0], + ), + ( + 0xFFFF_FFFF_FFFF_FFFF as u64, + [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + ), + ( + 0x89AB_12D4_C2D2_09BB as u64, + [0x89, 0xAB, 0x12, 0xD4, 0xC2, 0xD2, 0x09, 0xBB], + ), + ]; + + let type_size = std::mem::size_of::<$data_type>(); + for (test_val, v_arr) in &test_cases { + let v = *test_val as $data_type; + let cmp_iter: Box> = if $is_be { + Box::new(v_arr[(8 - type_size)..].iter()) + } else { + Box::new(v_arr.iter().rev()) + }; + // test write + let mut write_arr = vec![Default::default(); type_size]; + $write_fn_name(&mut write_arr, v); + for (cmp, cur) in cmp_iter.zip(write_arr.iter()) { + assert_eq!(*cmp, *cur as u8) + } + // test read + let read_val = $read_fn_name(&write_arr); + assert_eq!(v, read_val); + } + } + }; + } + + byte_order_test_read_write!(test_le_u16, write_le_u16, read_le_u16, false, u16); + byte_order_test_read_write!(test_le_u32, write_le_u32, read_le_u32, false, u32); + byte_order_test_read_write!(test_le_u64, write_le_u64, read_le_u64, false, u64); + byte_order_test_read_write!(test_le_i32, write_le_i32, read_le_i32, false, i32); + byte_order_test_read_write!(test_be_u16, write_be_u16, read_be_u16, true, u16); + byte_order_test_read_write!(test_be_u32, write_be_u32, read_be_u32, true, u32); +}