| // Copyright 2017 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::cmp::min; |
| use std::sync::Arc; |
| use std::time::Duration; |
| use std::time::Instant; |
| |
| use anyhow::anyhow; |
| use anyhow::Context; |
| use base::custom_serde::deserialize_seq_to_arr; |
| use base::custom_serde::serialize_arr; |
| use base::error; |
| use base::info; |
| use base::Event; |
| use base::EventToken; |
| use base::Timer; |
| use base::TimerTrait; |
| use base::Tube; |
| use base::WaitContext; |
| use base::WorkerThread; |
| use chrono::DateTime; |
| use chrono::Datelike; |
| use chrono::TimeZone; |
| use chrono::Timelike; |
| use chrono::Utc; |
| use metrics::log_metric; |
| use metrics::MetricEventType; |
| use serde::Deserialize; |
| use serde::Serialize; |
| use sync::Mutex; |
| use vm_control::VmResponse; |
| |
| use crate::pci::CrosvmDeviceId; |
| use crate::BusAccessInfo; |
| use crate::BusDevice; |
| use crate::DeviceId; |
| use crate::IrqEdgeEvent; |
| use crate::Suspendable; |
| |
| pub const RTC_IRQ: u8 = 8; |
| |
| const INDEX_MASK: u8 = 0x7f; |
| const INDEX_OFFSET: u64 = 0x0; |
| const DATA_OFFSET: u64 = 0x1; |
| const DATA_LEN: usize = 128; |
| |
| const RTC_REG_SEC: u8 = 0x0; |
| const RTC_REG_ALARM_SEC: u8 = 0x1; |
| const RTC_REG_MIN: u8 = 0x2; |
| const RTC_REG_ALARM_MIN: u8 = 0x3; |
| const RTC_REG_HOUR: u8 = 0x4; |
| const RTC_REG_ALARM_HOUR: u8 = 0x5; |
| const RTC_REG_WEEK_DAY: u8 = 0x6; |
| const RTC_REG_DAY: u8 = 0x7; |
| const RTC_REG_MONTH: u8 = 0x8; |
| const RTC_REG_YEAR: u8 = 0x9; |
| pub const RTC_REG_CENTURY: u8 = 0x32; |
| pub const RTC_REG_ALARM_DAY: u8 = 0x33; |
| pub const RTC_REG_ALARM_MONTH: u8 = 0x34; |
| |
| const RTC_REG_B: u8 = 0x0b; |
| const RTC_REG_B_UNSUPPORTED: u8 = 0xdd; |
| const RTC_REG_B_24_HOUR_MODE: u8 = 0x02; |
| const RTC_REG_B_ALARM_ENABLE: u8 = 0x20; |
| |
| const RTC_REG_C: u8 = 0x0c; |
| const RTC_REG_C_IRQF: u8 = 0x80; |
| const RTC_REG_C_AF: u8 = 0x20; |
| |
| const RTC_REG_D: u8 = 0x0d; |
| const RTC_REG_D_VRT: u8 = 0x80; // RAM and time valid |
| |
| pub type CmosNowFn = fn() -> DateTime<Utc>; |
| |
| // Alarm state shared between Cmos and the alarm worker thread. |
| struct AlarmState { |
| alarm: Timer, |
| vm_control: Tube, |
| irq: IrqEdgeEvent, |
| armed_time: Instant, |
| clear_evt: Option<Event>, |
| } |
| |
| impl AlarmState { |
| fn trigger_rtc_interrupt(&self) -> anyhow::Result<Event> { |
| self.irq.trigger().context("failed to trigger irq")?; |
| |
| let elapsed = self.armed_time.elapsed().as_millis(); |
| log_metric( |
| MetricEventType::RtcWakeup, |
| elapsed.try_into().unwrap_or(i64::MAX), |
| ); |
| |
| let msg = vm_control::VmRequest::Rtc { |
| clear_evt: Event::new().context("failed to create clear event")?, |
| }; |
| |
| // The Linux kernel expects wakeups to come via ACPI when ACPI is enabled. There's |
| // no real way to determine that here, so just send this unconditionally. |
| self.vm_control.send(&msg).context("send failed")?; |
| |
| let vm_control::VmRequest::Rtc { clear_evt } = msg else { |
| unreachable!("message type failure"); |
| }; |
| |
| match self.vm_control.recv().context("recv failed")? { |
| VmResponse::Ok => Ok(clear_evt), |
| resp => Err(anyhow!("unexpected rtc response: {:?}", resp)), |
| } |
| } |
| } |
| |
| /// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71. |
| #[derive(Serialize)] |
| pub struct Cmos { |
| index: u8, |
| #[serde(serialize_with = "serialize_arr")] |
| data: [u8; DATA_LEN], |
| #[serde(skip_serializing)] // skip serializing time function. |
| now_fn: CmosNowFn, |
| // alarm_time is re-loaded from data on deserialization, so there's |
| // no need to explicitly serialize it. |
| #[serde(skip_serializing)] |
| alarm_time: Option<DateTime<Utc>>, |
| // alarm_state fields are either constant across snapshotting or |
| // reloaded from |data| on restore, so no need to serialize. |
| #[serde(skip_serializing)] |
| alarm_state: Arc<Mutex<AlarmState>>, |
| #[serde(skip_serializing)] // skip serializing the worker thread |
| worker: Option<WorkerThread<()>>, |
| } |
| |
| impl Cmos { |
| /// Constructs a CMOS/RTC device with initial data. |
| /// `mem_below_4g` is the size of memory in bytes below the 32-bit gap. |
| /// `mem_above_4g` is the size of memory in bytes above the 32-bit gap. |
| /// `now_fn` is a function that returns the current date and time. |
| pub fn new( |
| mem_below_4g: u64, |
| mem_above_4g: u64, |
| now_fn: CmosNowFn, |
| vm_control: Tube, |
| irq: IrqEdgeEvent, |
| ) -> anyhow::Result<Cmos> { |
| let mut data = [0u8; DATA_LEN]; |
| |
| data[0x0B] = RTC_REG_B_24_HOUR_MODE; // Status Register B: 24-hour mode |
| |
| // Extended memory from 16 MB to 4 GB in units of 64 KB |
| let ext_mem = min( |
| 0xFFFF, |
| mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024), |
| ); |
| data[0x34] = ext_mem as u8; |
| data[0x35] = (ext_mem >> 8) as u8; |
| |
| // High memory (> 4GB) in units of 64 KB |
| let high_mem = min(0xFFFFFF, mem_above_4g / (64 * 1024)); |
| data[0x5b] = high_mem as u8; |
| data[0x5c] = (high_mem >> 8) as u8; |
| data[0x5d] = (high_mem >> 16) as u8; |
| |
| Ok(Cmos { |
| index: 0, |
| data, |
| now_fn, |
| alarm_time: None, |
| alarm_state: Arc::new(Mutex::new(AlarmState { |
| alarm: Timer::new().context("cmos timer")?, |
| irq, |
| vm_control, |
| // Not actually armed, but simpler than wrapping with an Option. |
| armed_time: Instant::now(), |
| clear_evt: None, |
| })), |
| worker: None, |
| }) |
| } |
| |
| fn spawn_worker(&mut self, alarm_state: Arc<Mutex<AlarmState>>) { |
| self.worker = Some(WorkerThread::start("CMOS_alarm", move |kill_evt| { |
| if let Err(e) = run_cmos_worker(alarm_state, kill_evt) { |
| error!("Failed to spawn worker {:?}", e); |
| } |
| })); |
| } |
| |
| fn set_alarm(&mut self) { |
| let mut state = self.alarm_state.lock(); |
| if self.data[RTC_REG_B as usize] & RTC_REG_B_ALARM_ENABLE != 0 { |
| let now = (self.now_fn)(); |
| let target = alarm_from_registers(now.year(), &self.data).and_then(|this_year| { |
| // There is no year register for the alarm. If the alarm target has |
| // already passed this year, then the next time it will occur is next |
| // year. |
| // |
| // Note that there is something of a race condition here. If |now| |
| // advances while the driver is configuring the alarm, then an alarm that |
| // should only be one second in the future could become one year in the |
| // future. Unfortunately there isn't anything in the rtc-cmos hardware |
| // specification that lets us handle this race condition in the device, so |
| // we just have to rely on the driver to deal with it. |
| if this_year < now { |
| alarm_from_registers(now.year() + 1, &self.data) |
| } else { |
| Some(this_year) |
| } |
| }); |
| if let Some(target) = target { |
| if Some(target) != self.alarm_time { |
| self.alarm_time = Some(target); |
| state.armed_time = Instant::now(); |
| |
| let duration = target |
| .signed_duration_since(now) |
| .to_std() |
| .unwrap_or(Duration::new(0, 0)); |
| if let Err(e) = state.alarm.reset_oneshot(duration) { |
| error!("Failed to set alarm {:?}", e); |
| } |
| } |
| } |
| } else if self.alarm_time.take().is_some() { |
| if let Err(e) = state.alarm.clear() { |
| error!("Failed to clear alarm {:?}", e); |
| } |
| if let Some(clear_evt) = state.clear_evt.take() { |
| if let Err(e) = clear_evt.signal() { |
| error!("failed to clear rtc pm signal {:?}", e); |
| } |
| } |
| } |
| |
| let needs_worker = self.alarm_time.is_some(); |
| drop(state); |
| |
| if needs_worker && self.worker.is_none() { |
| self.spawn_worker(self.alarm_state.clone()); |
| } |
| } |
| } |
| |
| fn run_cmos_worker(alarm_state: Arc<Mutex<AlarmState>>, kill_evt: Event) -> anyhow::Result<()> { |
| #[derive(EventToken)] |
| enum Token { |
| Alarm, |
| Kill, |
| } |
| |
| let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[ |
| (&alarm_state.lock().alarm, Token::Alarm), |
| (&kill_evt, Token::Kill), |
| ]) |
| .context("worker context failed")?; |
| |
| loop { |
| let events = wait_ctx.wait().context("wait failed")?; |
| let mut state = alarm_state.lock(); |
| for event in events.iter().filter(|e| e.is_readable) { |
| match event.token { |
| Token::Alarm => { |
| if state.alarm.mark_waited().context("timer ack failed")? { |
| continue; |
| } |
| |
| match state.trigger_rtc_interrupt() { |
| Ok(clear_evt) => state.clear_evt = Some(clear_evt), |
| Err(e) => error!("Failed to send rtc {:?}", e), |
| } |
| } |
| Token::Kill => return Ok(()), |
| } |
| } |
| } |
| } |
| |
| fn from_bcd(v: u8) -> Option<u32> { |
| let ones = (v & 0xf) as u32; |
| let tens = (v >> 4) as u32; |
| if ones < 10 && tens < 10 { |
| Some(10 * tens + ones) |
| } else { |
| None |
| } |
| } |
| |
| fn alarm_from_registers(year: i32, data: &[u8; DATA_LEN]) -> Option<DateTime<Utc>> { |
| Utc.with_ymd_and_hms( |
| year, |
| from_bcd(data[RTC_REG_ALARM_MONTH as usize])?, |
| from_bcd(data[RTC_REG_ALARM_DAY as usize])?, |
| from_bcd(data[RTC_REG_ALARM_HOUR as usize])?, |
| from_bcd(data[RTC_REG_ALARM_MIN as usize])?, |
| from_bcd(data[RTC_REG_ALARM_SEC as usize])?, |
| ) |
| .single() |
| } |
| |
| impl BusDevice for Cmos { |
| fn device_id(&self) -> DeviceId { |
| CrosvmDeviceId::Cmos.into() |
| } |
| |
| fn debug_label(&self) -> String { |
| "cmos".to_owned() |
| } |
| |
| fn write(&mut self, info: BusAccessInfo, data: &[u8]) { |
| if data.len() != 1 { |
| return; |
| } |
| |
| match info.offset { |
| INDEX_OFFSET => self.index = data[0] & INDEX_MASK, |
| DATA_OFFSET => { |
| let mut data = data[0]; |
| if self.index == RTC_REG_B { |
| // The features which we don't support are: |
| // 0x80 (SET) - disable clock updates (i.e. let guest configure the clock) |
| // 0x40 (PIE) - enable periodic interrupts |
| // 0x10 (IUE) - enable interrupts after clock updates |
| // 0x08 (SQWE) - enable square wave generation |
| // 0x04 (DM) - use binary data format (instead of BCD) |
| // 0x01 (DSE) - control daylight savings (we just do what the host does) |
| if data & RTC_REG_B_UNSUPPORTED != 0 { |
| info!( |
| "Ignoring unsupported bits: {:x}", |
| data & RTC_REG_B_UNSUPPORTED |
| ); |
| data &= !RTC_REG_B_UNSUPPORTED; |
| } |
| if data & RTC_REG_B_24_HOUR_MODE == 0 { |
| info!("12-hour mode unsupported"); |
| data |= RTC_REG_B_24_HOUR_MODE; |
| } |
| } |
| |
| self.data[self.index as usize] = data; |
| |
| if self.index == RTC_REG_B { |
| self.set_alarm(); |
| } |
| } |
| o => panic!("bad write offset on CMOS device: {}", o), |
| } |
| } |
| |
| fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) { |
| fn to_bcd(v: u8) -> u8 { |
| assert!(v < 100); |
| ((v / 10) << 4) | (v % 10) |
| } |
| |
| if data.len() != 1 { |
| return; |
| } |
| |
| data[0] = match info.offset { |
| INDEX_OFFSET => self.index, |
| DATA_OFFSET => { |
| let now = (self.now_fn)(); |
| let seconds = now.second(); // 0..=59 |
| let minutes = now.minute(); // 0..=59 |
| let hours = now.hour(); // 0..=23 (24-hour mode only) |
| let week_day = now.weekday().number_from_sunday(); // 1 (Sun) ..= 7 (Sat) |
| let day = now.day(); // 1..=31 |
| let month = now.month(); // 1..=12 |
| let year = now.year(); |
| match self.index { |
| RTC_REG_SEC => to_bcd(seconds as u8), |
| RTC_REG_MIN => to_bcd(minutes as u8), |
| RTC_REG_HOUR => to_bcd(hours as u8), |
| RTC_REG_WEEK_DAY => to_bcd(week_day as u8), |
| RTC_REG_DAY => to_bcd(day as u8), |
| RTC_REG_MONTH => to_bcd(month as u8), |
| RTC_REG_YEAR => to_bcd((year % 100) as u8), |
| RTC_REG_CENTURY => to_bcd((year / 100) as u8), |
| RTC_REG_C => { |
| if self |
| .alarm_time |
| .map_or(false, |alarm_time| alarm_time <= now) |
| { |
| // Reading from RTC_REG_C resets interrupts, so clear the |
| // status bits. The IrqEdgeEvent is reset automatically. |
| self.alarm_time.take(); |
| RTC_REG_C_IRQF | RTC_REG_C_AF |
| } else { |
| 0 |
| } |
| } |
| RTC_REG_D => RTC_REG_D_VRT, |
| _ => { |
| // self.index is always guaranteed to be in range via INDEX_MASK. |
| self.data[(self.index & INDEX_MASK) as usize] |
| } |
| } |
| } |
| o => panic!("bad read offset on CMOS device: {}", o), |
| } |
| } |
| } |
| |
| impl Suspendable for Cmos { |
| fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> { |
| serde_json::to_value(self).context("failed to serialize Cmos") |
| } |
| |
| fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { |
| #[derive(Deserialize)] |
| struct CmosIndex { |
| index: u8, |
| #[serde(deserialize_with = "deserialize_seq_to_arr")] |
| data: [u8; DATA_LEN], |
| } |
| |
| let deser: CmosIndex = |
| serde_json::from_value(data).context("failed to deserialize Cmos")?; |
| self.index = deser.index; |
| self.data = deser.data; |
| self.set_alarm(); |
| |
| Ok(()) |
| } |
| |
| fn sleep(&mut self) -> anyhow::Result<()> { |
| if let Some(worker) = self.worker.take() { |
| worker.stop(); |
| } |
| Ok(()) |
| } |
| |
| fn wake(&mut self) -> anyhow::Result<()> { |
| self.spawn_worker(self.alarm_state.clone()); |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::suspendable_tests; |
| |
| fn read_reg(cmos: &mut Cmos, reg: u8) -> u8 { |
| // Write register number to INDEX_OFFSET (0). |
| cmos.write( |
| BusAccessInfo { |
| offset: 0, |
| address: 0x70, |
| id: 0, |
| }, |
| &[reg], |
| ); |
| |
| // Read register value back from DATA_OFFSET (1). |
| |
| let mut data = [0u8]; |
| cmos.read( |
| BusAccessInfo { |
| offset: 1, |
| address: 0x71, |
| id: 0, |
| }, |
| &mut data, |
| ); |
| data[0] |
| } |
| |
| fn write_reg(cmos: &mut Cmos, reg: u8, val: u8) { |
| // Write register number to INDEX_OFFSET (0). |
| cmos.write( |
| BusAccessInfo { |
| offset: 0, |
| address: 0x70, |
| id: 0, |
| }, |
| &[reg], |
| ); |
| |
| // Write register value to DATA_OFFSET (1). |
| |
| let data = [val]; |
| cmos.write( |
| BusAccessInfo { |
| offset: 1, |
| address: 0x71, |
| id: 0, |
| }, |
| &data, |
| ); |
| } |
| |
| fn timestamp_to_datetime(timestamp: i64) -> DateTime<Utc> { |
| DateTime::from_timestamp(timestamp, 0).unwrap() |
| } |
| |
| fn test_now_party_like_its_1999() -> DateTime<Utc> { |
| // 1999-12-31T23:59:59+00:00 |
| timestamp_to_datetime(946684799) |
| } |
| |
| fn test_now_y2k_compliant() -> DateTime<Utc> { |
| // 2000-01-01T00:00:00+00:00 |
| timestamp_to_datetime(946684800) |
| } |
| |
| fn test_now_2016_before_leap_second() -> DateTime<Utc> { |
| // 2016-12-31T23:59:59+00:00 |
| timestamp_to_datetime(1483228799) |
| } |
| |
| fn test_now_2017_after_leap_second() -> DateTime<Utc> { |
| // 2017-01-01T00:00:00+00:00 |
| timestamp_to_datetime(1483228800) |
| } |
| |
| fn new_cmos_for_test(now_fn: CmosNowFn) -> Cmos { |
| let irq = IrqEdgeEvent::new().unwrap(); |
| Cmos::new(1024, 0, now_fn, Tube::pair().unwrap().0, irq).unwrap() |
| } |
| |
| #[test] |
| fn cmos_write_index() { |
| let mut cmos = new_cmos_for_test(test_now_party_like_its_1999); |
| // Write index. |
| cmos.write( |
| BusAccessInfo { |
| offset: 0, |
| address: 0x71, |
| id: 0, |
| }, |
| &[0x41], |
| ); |
| assert_eq!(cmos.index, 0x41); |
| } |
| |
| #[test] |
| fn cmos_write_data() { |
| let mut cmos = new_cmos_for_test(test_now_party_like_its_1999); |
| // Write data 0x01 at index 0x41. |
| cmos.write( |
| BusAccessInfo { |
| offset: 0, |
| address: 0x71, |
| id: 0, |
| }, |
| &[0x41], |
| ); |
| cmos.write( |
| BusAccessInfo { |
| offset: 1, |
| address: 0x71, |
| id: 0, |
| }, |
| &[0x01], |
| ); |
| assert_eq!(cmos.data[0x41], 0x01); |
| } |
| |
| fn modify_device(cmos: &mut Cmos) { |
| let info_index = BusAccessInfo { |
| offset: 0, |
| address: 0x71, |
| id: 0, |
| }; |
| |
| let info_data = BusAccessInfo { |
| offset: 1, |
| address: 0x71, |
| id: 0, |
| }; |
| // change index to 0x42. |
| cmos.write(info_index, &[0x42]); |
| cmos.write(info_data, &[0x01]); |
| } |
| |
| #[test] |
| fn cmos_date_time_1999() { |
| let mut cmos = new_cmos_for_test(test_now_party_like_its_1999); |
| assert_eq!(read_reg(&mut cmos, 0x00), 0x59); // seconds |
| assert_eq!(read_reg(&mut cmos, 0x02), 0x59); // minutes |
| assert_eq!(read_reg(&mut cmos, 0x04), 0x23); // hours |
| assert_eq!(read_reg(&mut cmos, 0x06), 0x06); // day of week |
| assert_eq!(read_reg(&mut cmos, 0x07), 0x31); // day of month |
| assert_eq!(read_reg(&mut cmos, 0x08), 0x12); // month |
| assert_eq!(read_reg(&mut cmos, 0x09), 0x99); // year |
| assert_eq!(read_reg(&mut cmos, 0x32), 0x19); // century |
| } |
| |
| #[test] |
| fn cmos_date_time_2000() { |
| let mut cmos = new_cmos_for_test(test_now_y2k_compliant); |
| assert_eq!(read_reg(&mut cmos, 0x00), 0x00); // seconds |
| assert_eq!(read_reg(&mut cmos, 0x02), 0x00); // minutes |
| assert_eq!(read_reg(&mut cmos, 0x04), 0x00); // hours |
| assert_eq!(read_reg(&mut cmos, 0x06), 0x07); // day of week |
| assert_eq!(read_reg(&mut cmos, 0x07), 0x01); // day of month |
| assert_eq!(read_reg(&mut cmos, 0x08), 0x01); // month |
| assert_eq!(read_reg(&mut cmos, 0x09), 0x00); // year |
| assert_eq!(read_reg(&mut cmos, 0x32), 0x20); // century |
| } |
| |
| #[test] |
| fn cmos_date_time_before_leap_second() { |
| let mut cmos = new_cmos_for_test(test_now_2016_before_leap_second); |
| assert_eq!(read_reg(&mut cmos, 0x00), 0x59); // seconds |
| assert_eq!(read_reg(&mut cmos, 0x02), 0x59); // minutes |
| assert_eq!(read_reg(&mut cmos, 0x04), 0x23); // hours |
| assert_eq!(read_reg(&mut cmos, 0x06), 0x07); // day of week |
| assert_eq!(read_reg(&mut cmos, 0x07), 0x31); // day of month |
| assert_eq!(read_reg(&mut cmos, 0x08), 0x12); // month |
| assert_eq!(read_reg(&mut cmos, 0x09), 0x16); // year |
| assert_eq!(read_reg(&mut cmos, 0x32), 0x20); // century |
| } |
| |
| #[test] |
| fn cmos_date_time_after_leap_second() { |
| let mut cmos = new_cmos_for_test(test_now_2017_after_leap_second); |
| assert_eq!(read_reg(&mut cmos, 0x00), 0x00); // seconds |
| assert_eq!(read_reg(&mut cmos, 0x02), 0x00); // minutes |
| assert_eq!(read_reg(&mut cmos, 0x04), 0x00); // hours |
| assert_eq!(read_reg(&mut cmos, 0x06), 0x01); // day of week |
| assert_eq!(read_reg(&mut cmos, 0x07), 0x01); // day of month |
| assert_eq!(read_reg(&mut cmos, 0x08), 0x01); // month |
| assert_eq!(read_reg(&mut cmos, 0x09), 0x17); // year |
| assert_eq!(read_reg(&mut cmos, 0x32), 0x20); // century |
| } |
| |
| #[test] |
| fn cmos_alarm() { |
| // 2000-01-02T03:04:05+00:00 |
| let now_fn = || timestamp_to_datetime(946782245); |
| let mut cmos = new_cmos_for_test(now_fn); |
| |
| // A date later this year |
| write_reg(&mut cmos, 0x01, 0x06); // seconds |
| write_reg(&mut cmos, 0x03, 0x05); // minutes |
| write_reg(&mut cmos, 0x05, 0x04); // hours |
| write_reg(&mut cmos, 0x33, 0x03); // day of month |
| write_reg(&mut cmos, 0x34, 0x02); // month |
| write_reg(&mut cmos, 0x0b, 0x20); // RTC_REG_B_ALARM_ENABLE |
| // 2000-02-03T04:05:06+00:00 |
| assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(949550706))); |
| |
| // A date (one year - one second) in the future |
| write_reg(&mut cmos, 0x01, 0x04); // seconds |
| write_reg(&mut cmos, 0x03, 0x04); // minutes |
| write_reg(&mut cmos, 0x05, 0x03); // hours |
| write_reg(&mut cmos, 0x33, 0x02); // day of month |
| write_reg(&mut cmos, 0x34, 0x01); // month |
| write_reg(&mut cmos, 0x0b, 0x20); // RTC_REG_B_ALARM_ENABLE |
| // 2001-01-02T03:04:04+00:00 |
| assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(978404644))); |
| |
| // The current time |
| write_reg(&mut cmos, 0x01, 0x05); // seconds |
| write_reg(&mut cmos, 0x03, 0x04); // minutes |
| write_reg(&mut cmos, 0x05, 0x03); // hours |
| write_reg(&mut cmos, 0x33, 0x02); // day of month |
| write_reg(&mut cmos, 0x34, 0x01); // month |
| write_reg(&mut cmos, 0x0b, 0x20); // RTC_REG_B_ALARM_ENABLE |
| assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(946782245))); |
| assert_eq!(read_reg(&mut cmos, 0x0c), 0xa0); // RTC_REG_C_IRQF | RTC_REG_C_AF |
| assert_eq!(cmos.alarm_time, None); |
| assert_eq!(read_reg(&mut cmos, 0x0c), 0); |
| |
| // Invalid BCD |
| write_reg(&mut cmos, 0x01, 0xa0); // seconds |
| write_reg(&mut cmos, 0x0b, 0x20); // RTC_REG_B_ALARM_ENABLE |
| assert_eq!(cmos.alarm_time, None); |
| } |
| |
| #[test] |
| fn cmos_reg_d() { |
| let mut cmos = new_cmos_for_test(test_now_party_like_its_1999); |
| assert_eq!(read_reg(&mut cmos, 0x0d), 0x80) // RAM and time are valid |
| } |
| |
| #[test] |
| fn cmos_snapshot_restore() -> anyhow::Result<()> { |
| // time function doesn't matter in this case. |
| let mut cmos = new_cmos_for_test(test_now_party_like_its_1999); |
| |
| let info_index = BusAccessInfo { |
| offset: 0, |
| address: 0x71, |
| id: 0, |
| }; |
| |
| let info_data = BusAccessInfo { |
| offset: 1, |
| address: 0x71, |
| id: 0, |
| }; |
| |
| // change index to 0x41. |
| cmos.write(info_index, &[0x41]); |
| cmos.write(info_data, &[0x01]); |
| |
| let snap = cmos.snapshot().context("failed to snapshot Cmos")?; |
| |
| // change index to 0x42. |
| cmos.write(info_index, &[0x42]); |
| cmos.write(info_data, &[0x01]); |
| |
| // Restore Cmos. |
| cmos.restore(snap).context("failed to restore Cmos")?; |
| |
| // after restore, the index should be 0x41, which was the index before snapshot was taken. |
| assert_eq!(cmos.index, 0x41); |
| assert_eq!(cmos.data[0x41], 0x01); |
| assert_ne!(cmos.data[0x42], 0x01); |
| Ok(()) |
| } |
| |
| #[test] |
| fn cmos_sleep_wake() { |
| // 2000-01-02T03:04:05+00:00 |
| let irq = IrqEdgeEvent::new().unwrap(); |
| let now_fn = || timestamp_to_datetime(946782245); |
| let mut cmos = Cmos::new(1024, 0, now_fn, Tube::pair().unwrap().0, irq).unwrap(); |
| |
| // A date later this year |
| write_reg(&mut cmos, 0x01, 0x06); // seconds |
| write_reg(&mut cmos, 0x03, 0x05); // minutes |
| write_reg(&mut cmos, 0x05, 0x04); // hours |
| write_reg(&mut cmos, 0x33, 0x03); // day of month |
| write_reg(&mut cmos, 0x34, 0x02); // month |
| write_reg(&mut cmos, 0x0b, 0x20); // RTC_REG_B_ALARM_ENABLE |
| // 2000-02-03T04:05:06+00:00 |
| assert_eq!(cmos.alarm_time, Some(timestamp_to_datetime(949550706))); |
| assert!(cmos.worker.is_some()); |
| |
| cmos.sleep().unwrap(); |
| assert!(cmos.worker.is_none()); |
| |
| cmos.wake().unwrap(); |
| assert!(cmos.worker.is_some()); |
| } |
| |
| suspendable_tests!( |
| cmos1999, |
| new_cmos_for_test(test_now_party_like_its_1999), |
| modify_device |
| ); |
| suspendable_tests!( |
| cmos2k, |
| new_cmos_for_test(test_now_y2k_compliant), |
| modify_device |
| ); |
| suspendable_tests!( |
| cmos2016, |
| new_cmos_for_test(test_now_2016_before_leap_second), |
| modify_device |
| ); |
| suspendable_tests!( |
| cmos2017, |
| new_cmos_for_test(test_now_2017_after_leap_second), |
| modify_device |
| ); |
| } |