blob: f295b96d34809b985e0e3bd11288268c051ffb64 [file] [log] [blame]
// 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 base::{warn, Event};
use std::convert::TryFrom;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{BusAccessInfo, 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 {
// Event to be used to interrupt the guest for an alarm event
alarm_evt: Event,
// 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: Event) -> Pl030 {
Pl030 {
alarm_evt: evt,
counter_delta_time: get_epoch_time(),
match_value: 0,
interrupt_active: false,
}
}
}
impl BusDevice for Pl030 {
fn debug_label(&self) -> String {
"Pl030".to_owned()
}
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
let data_array = match <&[u8; 4]>::try_from(data) {
Ok(array) => array,
_ => {
warn!("bad write size: {} for pl030", data.len());
return;
}
};
let reg_val = u32::from_ne_bytes(*data_array);
match info.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 {}", o),
}
}
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
let data_array = match <&mut [u8; 4]>::try_from(data) {
Ok(array) => array,
_ => {
warn!("bad write size for pl030");
return;
}
};
let reg_content: u32 = match info.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 {}", o),
};
*data_array = reg_content.to_ne_bytes();
}
}
#[cfg(test)]
mod tests {
use super::*;
// The RTC device is placed at page 2 in the mmio bus
const AARCH64_RTC_ADDR: u64 = 0x2000;
fn pl030_bus_address(offset: u64) -> BusAccessInfo {
BusAccessInfo {
address: AARCH64_RTC_ADDR + offset,
offset,
id: 0,
}
}
#[test]
fn test_interrupt_status_register() {
let event = Event::new().unwrap();
let mut device = Pl030::new(event.try_clone().unwrap());
let mut register = [0, 0, 0, 0];
// set interrupt
device.write(pl030_bus_address(RTCEOI), &[1, 0, 0, 0]);
device.read(pl030_bus_address(RTCSTAT), &mut register);
assert_eq!(register, [1, 0, 0, 0]);
assert_eq!(event.read().unwrap(), 1);
// clear interrupt
device.write(pl030_bus_address(RTCEOI), &[0, 0, 0, 0]);
device.read(pl030_bus_address(RTCSTAT), &mut register);
assert_eq!(register, [0, 0, 0, 0]);
}
#[test]
fn test_match_register() {
let mut device = Pl030::new(Event::new().unwrap());
let mut register = [0, 0, 0, 0];
device.write(pl030_bus_address(RTCMR), &[1, 2, 3, 4]);
device.read(pl030_bus_address(RTCMR), &mut register);
assert_eq!(register, [1, 2, 3, 4]);
}
}