summary refs log tree commit diff
diff options
context:
space:
mode:
authorSonny Rao <sonnyrao@chromium.org>2018-05-01 18:55:05 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-05-04 03:03:00 -0700
commite0823392f4dde29f3bde7f98d0bc481654843d27 (patch)
tree68410729f513742d8ef7876829bd3f348b27a1df
parentcaaeb044603f2f4859b29a93cb16730d85afcb3d (diff)
downloadcrosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.tar
crosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.tar.gz
crosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.tar.bz2
crosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.tar.lz
crosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.tar.xz
crosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.tar.zst
crosvm-e0823392f4dde29f3bde7f98d0bc481654843d27.zip
devices: add an emulated ARM pl030 RTC clock
This adds a very simple RTC device and implements reading the time of
day based on the host's time of day.  It currently doesn't support
setting the time or wake up alarms but could do so in the future.
Also instantiate it and add the appropriate nodes to the device-tree
for ARM guests.

BUG=chromium:833825
TEST=manual test on kevin, date is properly set when VM is started

Change-Id: I032ec7df2cba9e9016966eb4160b413fec9a40ba
Reviewed-on: https://chromium-review.googlesource.com/1038801
Commit-Ready: Sonny Rao <sonnyrao@chromium.org>
Tested-by: Sonny Rao <sonnyrao@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
-rw-r--r--aarch64/src/fdt.rs37
-rw-r--r--aarch64/src/lib.rs22
-rw-r--r--devices/src/lib.rs2
-rw-r--r--devices/src/pl030.rs152
4 files changed, 209 insertions, 4 deletions
diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs
index 6e1776d..ef125cb 100644
--- a/aarch64/src/fdt.rs
+++ b/aarch64/src/fdt.rs
@@ -20,6 +20,12 @@ use AARCH64_GIC_CPUI_SIZE;
 use AARCH64_GIC_DIST_BASE;
 use AARCH64_GIC_CPUI_BASE;
 
+// These are RTC related constants
+use AARCH64_RTC_ADDR;
+use AARCH64_RTC_SIZE;
+use AARCH64_RTC_IRQ;
+use devices::pl030::PL030_AMBA_ID;
+
 // These are serial device related constants.
 use AARCH64_SERIAL_ADDR;
 use AARCH64_SERIAL_SIZE;
@@ -42,6 +48,7 @@ const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
 const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8;
 const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = (0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT);
 const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001;
+const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004;
 const IRQ_TYPE_LEVEL_LOW: u32 = 0x00000008;
 
 // This links to libfdt which handles the creation of the binary blob
@@ -366,6 +373,35 @@ fn create_io_nodes(fdt: &mut Vec<u8>) -> Result<(), Box<Error>> {
     Ok(())
 }
 
+fn create_rtc_node(fdt: &mut Vec<u8>) -> Result<(), Box<Error>> {
+    // the kernel driver for pl030 really really wants a clock node
+    // associated with an AMBA device or it will fail to probe, so we
+    // need to make up a clock node to associate with the pl030 rtc
+    // node and an associated handle with a unique phandle value.
+    const CLK_PHANDLE: u32 = 24;
+    begin_node(fdt, "pclk@3M")?;
+    property_u32(fdt, "#clock-cells", 0)?;
+    property_string(fdt, "compatible", "fixed-clock")?;
+    property_u32(fdt, "clock-frequency", 3141592)?;
+    property_u32(fdt, "phandle", CLK_PHANDLE)?;
+    end_node(fdt)?;
+
+    let rtc_name = format!("rtc@{:x}", AARCH64_RTC_ADDR);
+    let reg = generate_prop64(&[AARCH64_RTC_ADDR, AARCH64_RTC_SIZE]);
+    let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, AARCH64_RTC_IRQ,
+                                IRQ_TYPE_LEVEL_HIGH]);
+
+    begin_node(fdt, &rtc_name)?;
+    property_string(fdt, "compatible", "arm,primecell")?;
+    property_u32(fdt, "arm,primecell-periphid", PL030_AMBA_ID)?;
+    property(fdt, "reg", &reg)?;
+    property(fdt, "interrupts", &irq)?;
+    property_u32(fdt, "clocks", CLK_PHANDLE)?;
+    property_string(fdt, "clock-names", "apb_pclk")?;
+    end_node(fdt)?;
+    Ok(())
+}
+
 /// Creates a flattened device tree containing all of the parameters for the
 /// kernel and loads it into the guest memory at the specified offset.
 ///
@@ -411,6 +447,7 @@ pub fn create_fdt(fdt_max_size: usize,
     create_serial_node(&mut fdt)?;
     create_psci_node(&mut fdt)?;
     create_io_nodes(&mut fdt)?;
+    create_rtc_node(&mut fdt)?;
     // End giant node
     end_node(&mut fdt)?;
 
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index c53d2c2..b218a4d 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -86,12 +86,20 @@ const AARCH64_SERIAL_SIZE: u64 = 0x8;
 // This was the speed kvmtool used, not sure if it matters.
 const AARCH64_SERIAL_SPEED: u32 = 1843200;
 
+// Place the RTC device at page 2
+const AARCH64_RTC_ADDR: u64 = 0x2000;
+// The RTC device gets one 4k page
+const AARCH64_RTC_SIZE: u64 = 0x1000;
+// The RTC device gets the first interrupt line
+// Which gets mapped to the first SPI interrupt (physical 32).
+const AARCH64_RTC_IRQ: u32  = 0;
+
 // This is the base address of MMIO devices.
 const AARCH64_MMIO_BASE: u64 = 0x10000;
 // Each MMIO device gets a 4k page.
 const AARCH64_MMIO_LEN: u64 = 0x1000;
-// We start at 0 which gets mapped to the first SPI interrupt (physical 32).
-const AARCH64_IRQ_BASE: u32 = 0;
+// Virtio devices start at SPI interrupt number 1
+const AARCH64_IRQ_BASE: u32 = 1;
 
 #[derive(Debug)]
 pub enum Error {
@@ -202,6 +210,9 @@ impl arch::LinuxArch for AArch64 {
     /// * `mem` - A copy of the GuestMemory object for this VM.
     fn get_device_manager(vm: &mut Vm, mem: GuestMemory) ->
         Result<device_manager::DeviceManager> {
+        let rtc_evt = EventFd::new()?;
+        vm.register_irqfd(&rtc_evt, AARCH64_RTC_IRQ)?;
+
         let mut dm = device_manager::DeviceManager::new(vm,
                                                         mem,
                                                         AARCH64_MMIO_LEN,
@@ -211,9 +222,12 @@ impl arch::LinuxArch for AArch64 {
         let serial = Arc::new(Mutex::new(devices::Serial::new_out(
             com_evt_1_3.try_clone()?,
             Box::new(stdout()))));
+        dm.bus.insert(serial.clone(), AARCH64_SERIAL_ADDR,
+                      AARCH64_SERIAL_SIZE).expect("failed to add serial device");
 
-            dm.bus.insert(serial.clone(), AARCH64_SERIAL_ADDR,
-                          AARCH64_SERIAL_SIZE).expect("failed to add serial device");
+        let rtc = Arc::new(Mutex::new(devices::pl030::Pl030::new(rtc_evt)));
+        dm.bus.insert(rtc, AARCH64_RTC_ADDR,
+                      AARCH64_RTC_SIZE).expect("failed to add rtc device");
         Ok(dm)
     }
 
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index e174a0b..7ef8af2 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -21,10 +21,12 @@ mod cmos;
 mod i8042;
 mod proxy;
 mod serial;
+pub mod pl030;
 pub mod virtio;
 
 pub use self::bus::{Bus, BusDevice};
 pub use self::cmos::Cmos;
+pub use self::pl030::Pl030;
 pub use self::i8042::I8042Device;
 pub use self::proxy::ProxyDevice;
 pub use self::proxy::Error as ProxyError;
diff --git a/devices/src/pl030.rs b/devices/src/pl030.rs
new file mode 100644
index 0000000..b555114
--- /dev/null
+++ b/devices/src/pl030.rs
@@ -0,0 +1,152 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::time::{SystemTime, UNIX_EPOCH};
+use sys_util::EventFd;
+
+use BusDevice;
+
+// Register offsets
+// Data register
+const RTCDR:   u64 = 0x0;
+// Match register
+const RTCMR:   u64 = 0x4;
+// Interrupt status register
+const RTCSTAT: u64 = 0x8;
+// Interrupt clear register
+const RTCEOI:  u64 = 0x8;
+// Counter load register
+const RTCLR:   u64 = 0xC;
+// Counter register
+const RTCCR:   u64 = 0x10;
+
+// A single 4K page is mapped for this device
+pub const PL030_AMBA_IOMEM_SIZE: u64 = 0x1000;
+
+// AMBA id registers are at the end of the allocated memory space
+const AMBA_ID_OFFSET: u64 = PL030_AMBA_IOMEM_SIZE - 0x20;
+const AMBA_MASK_OFFSET: u64 = PL030_AMBA_IOMEM_SIZE - 0x28;
+
+// This is the AMBA id for this device
+pub const PL030_AMBA_ID:   u32 = 0x00041030;
+pub const PL030_AMBA_MASK: u32 = 0x000FFFFF;
+
+/// An emulated ARM pl030 RTC
+pub struct Pl030 {
+    // EventFD to be used to interrupt the guest for an alarm event
+    alarm_evt: EventFd,
+
+    // This is the delta we subtract from current time to get the
+    // counter value
+    counter_delta_time: u32,
+
+    // This is the value that triggers an alarm interrupt when it
+    // matches with the rtc time
+    match_value: u32,
+
+    // status flag to keep track of whether the interrupt is cleared
+    // or not
+    interrupt_active: bool,
+}
+
+fn get_epoch_time() -> u32 {
+    let epoch_time =
+        SystemTime::now().duration_since(UNIX_EPOCH).
+        expect("SystemTime::duration_since failed");
+    epoch_time.as_secs() as u32
+}
+
+
+impl Pl030 {
+    /// Constructs a Pl030 device
+    pub fn new(evt: EventFd) -> Pl030 {
+        Pl030 {
+            alarm_evt: evt,
+            counter_delta_time: get_epoch_time(),
+            match_value: 0,
+            interrupt_active: false,
+        }
+    }
+}
+
+impl BusDevice for Pl030 {
+    fn write(&mut self, offset: u64, data: &[u8]) {
+        if data.len() != 4 {
+            warn!("bad write size: {} for pl030", data.len());
+            return;
+        }
+
+        let reg_val: u32 = (data[0] as u32) << 24 | (data[1] as u32) << 16 |
+                           (data[2] as u32) << 8 | (data[3] as u32);
+        match offset {
+            RTCDR => {
+                warn!("invalid write to read-only RTCDR register");
+            },
+            RTCMR => {
+                self.match_value = reg_val;
+                // TODO(sonnyrao): here we need to set up a timer for
+                // when host time equals the value written here and
+                // fire the interrupt
+                warn!("Not implemented: VM tried to set an RTC alarm");
+            },
+            RTCEOI => {
+                if reg_val == 0 {
+                    self.interrupt_active = false;
+                } else {
+                    self.alarm_evt.write(1).unwrap();
+                    self.interrupt_active = true;
+                }
+            },
+            RTCLR => {
+                // TODO(sonnyrao): if we ever need to let the VM set it's own time
+                // then we'll need to keep track of the delta between
+                // the rtc time it sets and the host's rtc time and
+                // record that here
+                warn!("Not implemented: VM tried to set the RTC");
+            },
+            RTCCR => {
+                self.counter_delta_time = get_epoch_time();
+            },
+            o => panic!("pl030: bad write offset {}", o)
+        }
+    }
+
+    fn read(&mut self, offset: u64, data: &mut [u8]) {
+        if data.len() != 4 {
+            warn!("bad read size: {} for pl030", data.len());
+            return;
+        }
+
+        let reg_content: u32 = match offset {
+            RTCDR => {
+                get_epoch_time()
+            },
+            RTCMR => {
+                self.match_value
+            },
+            RTCSTAT => {
+                self.interrupt_active as u32
+            },
+            RTCLR => {
+                warn!("invalid read of RTCLR register");
+                0
+            },
+            RTCCR => {
+                get_epoch_time() - self.counter_delta_time
+            },
+            AMBA_ID_OFFSET => {
+                PL030_AMBA_ID
+            },
+            AMBA_MASK_OFFSET => {
+                PL030_AMBA_MASK
+            },
+
+            o => panic!("pl030: bad read offset {}", o)
+        };
+        data[0] = reg_content as u8;
+        data[1] = (reg_content >> 8) as u8;
+        data[2] = (reg_content >> 16) as u8;
+        data[3] = (reg_content >> 24) as u8;
+    }
+}