• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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