devices/
pl030.rs

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