summary refs log blame commit diff
path: root/devices/src/pl030.rs
blob: b5338acc40c17cd298af258af9d97e52bb6b0b6d (plain) (tree)
1
2
3
4
5
6
7
8




                                                                         
                              
 
                     


                   
                       
                 
                       


                            
                        
                        
                       
                   
                        








                                                                 
                                          




















                                                                    


                                                     


                               












                                                 



                                     





                                                              



                                                 


                                                                   
             





                                                                       
             






                                                     
             





                                                                                  
             

                                                           

                                                         









                                                             


                                                    


                                                        





                                                                






                                            
// 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 std::time::{SystemTime, UNIX_EPOCH};
use sys_util::{warn, EventFd};

use crate::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 {
    // EventFD to be used to interrupt the guest for an alarm event
    alarm_evt: EventFd,

    // 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: EventFd) -> 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, offset: u64, data: &[u8]) {
        if data.len() != 4 {
            warn!("bad write size: {} for pl030", data.len());
            return;
        }

        let reg_val: u32 = (data[0] as u32) << 24
            | (data[1] as u32) << 16
            | (data[2] as u32) << 8
            | (data[3] as u32);
        match 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 offset {}", o),
        }
    }

    fn read(&mut self, offset: u64, data: &mut [u8]) {
        if data.len() != 4 {
            warn!("bad read size: {} for pl030", data.len());
            return;
        }

        let reg_content: u32 = match 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 offset {}", o),
        };
        data[0] = reg_content as u8;
        data[1] = (reg_content >> 8) as u8;
        data[2] = (reg_content >> 16) as u8;
        data[3] = (reg_content >> 24) as u8;
    }
}