1 // Copyright 2018 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use base::{warn, Event};
6 use std::convert::TryFrom;
7 use std::time::{SystemTime, UNIX_EPOCH};
8
9 use crate::{BusAccessInfo, BusDevice};
10
11 // Register offsets
12 // Data register
13 const RTCDR: u64 = 0x0;
14 // Match register
15 const RTCMR: u64 = 0x4;
16 // Interrupt status register
17 const RTCSTAT: u64 = 0x8;
18 // Interrupt clear register
19 const RTCEOI: u64 = 0x8;
20 // Counter load register
21 const RTCLR: u64 = 0xC;
22 // Counter register
23 const RTCCR: u64 = 0x10;
24
25 // A single 4K page is mapped for this device
26 pub const PL030_AMBA_IOMEM_SIZE: u64 = 0x1000;
27
28 // AMBA id registers are at the end of the allocated memory space
29 const AMBA_ID_OFFSET: u64 = PL030_AMBA_IOMEM_SIZE - 0x20;
30 const AMBA_MASK_OFFSET: u64 = PL030_AMBA_IOMEM_SIZE - 0x28;
31
32 // This is the AMBA id for this device
33 pub const PL030_AMBA_ID: u32 = 0x00041030;
34 pub const PL030_AMBA_MASK: u32 = 0x000FFFFF;
35
36 /// An emulated ARM pl030 RTC
37 pub struct Pl030 {
38 // Event to be used to interrupt the guest for an alarm event
39 alarm_evt: Event,
40
41 // This is the delta we subtract from current time to get the
42 // counter value
43 counter_delta_time: u32,
44
45 // This is the value that triggers an alarm interrupt when it
46 // matches with the rtc time
47 match_value: u32,
48
49 // status flag to keep track of whether the interrupt is cleared
50 // or not
51 interrupt_active: bool,
52 }
53
get_epoch_time() -> u3254 fn get_epoch_time() -> u32 {
55 let epoch_time = SystemTime::now()
56 .duration_since(UNIX_EPOCH)
57 .expect("SystemTime::duration_since failed");
58 epoch_time.as_secs() as u32
59 }
60
61 impl Pl030 {
62 /// Constructs a Pl030 device
new(evt: Event) -> Pl03063 pub fn new(evt: Event) -> Pl030 {
64 Pl030 {
65 alarm_evt: evt,
66 counter_delta_time: get_epoch_time(),
67 match_value: 0,
68 interrupt_active: false,
69 }
70 }
71 }
72
73 impl BusDevice for Pl030 {
debug_label(&self) -> String74 fn debug_label(&self) -> String {
75 "Pl030".to_owned()
76 }
77
write(&mut self, info: BusAccessInfo, data: &[u8])78 fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
79 let data_array = match <&[u8; 4]>::try_from(data) {
80 Ok(array) => array,
81 _ => {
82 warn!("bad write size: {} for pl030", data.len());
83 return;
84 }
85 };
86
87 let reg_val = u32::from_ne_bytes(*data_array);
88 match info.offset {
89 RTCDR => {
90 warn!("invalid write to read-only RTCDR register");
91 }
92 RTCMR => {
93 self.match_value = reg_val;
94 // TODO(sonnyrao): here we need to set up a timer for
95 // when host time equals the value written here and
96 // fire the interrupt
97 warn!("Not implemented: VM tried to set an RTC alarm");
98 }
99 RTCEOI => {
100 if reg_val == 0 {
101 self.interrupt_active = false;
102 } else {
103 self.alarm_evt.write(1).unwrap();
104 self.interrupt_active = true;
105 }
106 }
107 RTCLR => {
108 // TODO(sonnyrao): if we ever need to let the VM set it's own time
109 // then we'll need to keep track of the delta between
110 // the rtc time it sets and the host's rtc time and
111 // record that here
112 warn!("Not implemented: VM tried to set the RTC");
113 }
114 RTCCR => {
115 self.counter_delta_time = get_epoch_time();
116 }
117 o => panic!("pl030: bad write {}", o),
118 }
119 }
120
read(&mut self, info: BusAccessInfo, data: &mut [u8])121 fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
122 let data_array = match <&mut [u8; 4]>::try_from(data) {
123 Ok(array) => array,
124 _ => {
125 warn!("bad write size for pl030");
126 return;
127 }
128 };
129
130 let reg_content: u32 = match info.offset {
131 RTCDR => get_epoch_time(),
132 RTCMR => self.match_value,
133 RTCSTAT => self.interrupt_active as u32,
134 RTCLR => {
135 warn!("invalid read of RTCLR register");
136 0
137 }
138 RTCCR => get_epoch_time() - self.counter_delta_time,
139 AMBA_ID_OFFSET => PL030_AMBA_ID,
140 AMBA_MASK_OFFSET => PL030_AMBA_MASK,
141
142 o => panic!("pl030: bad read {}", o),
143 };
144 *data_array = reg_content.to_ne_bytes();
145 }
146 }
147 #[cfg(test)]
148 mod tests {
149 use super::*;
150
151 // The RTC device is placed at page 2 in the mmio bus
152 const AARCH64_RTC_ADDR: u64 = 0x2000;
153
pl030_bus_address(offset: u64) -> BusAccessInfo154 fn pl030_bus_address(offset: u64) -> BusAccessInfo {
155 BusAccessInfo {
156 address: AARCH64_RTC_ADDR + offset,
157 offset,
158 id: 0,
159 }
160 }
161
162 #[test]
test_interrupt_status_register()163 fn test_interrupt_status_register() {
164 let event = Event::new().unwrap();
165 let mut device = Pl030::new(event.try_clone().unwrap());
166 let mut register = [0, 0, 0, 0];
167
168 // set interrupt
169 device.write(pl030_bus_address(RTCEOI), &[1, 0, 0, 0]);
170 device.read(pl030_bus_address(RTCSTAT), &mut register);
171 assert_eq!(register, [1, 0, 0, 0]);
172 assert_eq!(event.read().unwrap(), 1);
173
174 // clear interrupt
175 device.write(pl030_bus_address(RTCEOI), &[0, 0, 0, 0]);
176 device.read(pl030_bus_address(RTCSTAT), &mut register);
177 assert_eq!(register, [0, 0, 0, 0]);
178 }
179
180 #[test]
test_match_register()181 fn test_match_register() {
182 let mut device = Pl030::new(Event::new().unwrap());
183 let mut register = [0, 0, 0, 0];
184
185 device.write(pl030_bus_address(RTCMR), &[1, 2, 3, 4]);
186 device.read(pl030_bus_address(RTCMR), &mut register);
187 assert_eq!(register, [1, 2, 3, 4]);
188 }
189 }
190