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