chipset/
cmos_rtc.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! CMOS RTC device (MC146818 compatible), as found on PC (and PC compatible)
5//! platforms.
6
7#![warn(missing_docs)]
8
9use self::spec::CmosReg;
10use self::spec::ENABLE_OSCILLATOR_CONTROL;
11use self::spec::StatusRegA;
12use self::spec::StatusRegB;
13use self::spec::StatusRegC;
14use self::spec::StatusRegD;
15use chipset_device::ChipsetDevice;
16use chipset_device::io::IoError;
17use chipset_device::io::IoResult;
18use chipset_device::pio::PortIoIntercept;
19use chipset_device::poll_device::PollDevice;
20use inspect::Inspect;
21use inspect::InspectMut;
22use local_clock::InspectableLocalClock;
23use local_clock::LocalClockTime;
24use std::ops::RangeInclusive;
25use std::task::Poll;
26use std::time::Duration;
27use vmcore::device_state::ChangeDeviceState;
28use vmcore::line_interrupt::LineInterrupt;
29use vmcore::vmtime::VmTime;
30use vmcore::vmtime::VmTimeAccess;
31use vmcore::vmtime::VmTimeSource;
32use vmcore::vmtime::VmTimerPeriodic;
33
34mod spec {
35    //! Definitions pulled directly from the CMOS RTC spec sheet.
36
37    use bitfield_struct::bitfield;
38    use inspect::Inspect;
39
40    open_enum::open_enum! {
41        /// Standardized set of CMOS registers
42        #[derive(Inspect)]
43        #[inspect(debug)]
44        pub enum CmosReg: u8 {
45            SECOND       = 0x00,
46            SECOND_ALARM = 0x01,
47            MINUTE       = 0x02,
48            MINUTE_ALARM = 0x03,
49            HOUR         = 0x04,
50            HOUR_ALARM   = 0x05,
51            DAY_OF_WEEK  = 0x06,
52            DAY_OF_MONTH = 0x07,
53            MONTH        = 0x08,
54            YEAR         = 0x09,
55            STATUS_A     = 0x0A,
56            STATUS_B     = 0x0B,
57            STATUS_C     = 0x0C,
58            STATUS_D     = 0x0D,
59        }
60    }
61
62    impl CmosReg {
63        /// Returns true if the register's value is tied to the real time.
64        pub fn depends_on_rtc(&self, century: CmosReg) -> bool {
65            matches!(
66                *self,
67                CmosReg::SECOND
68                    | CmosReg::MINUTE
69                    | CmosReg::HOUR
70                    | CmosReg::DAY_OF_WEEK
71                    | CmosReg::DAY_OF_MONTH
72                    | CmosReg::MONTH
73                    | CmosReg::YEAR
74            ) || *self == century
75        }
76
77        /// Returns true if the register's value is tied to the Alarm
78        pub fn depends_on_alarm(&self) -> bool {
79            matches!(
80                *self,
81                CmosReg::SECOND_ALARM | CmosReg::MINUTE_ALARM | CmosReg::HOUR_ALARM
82            )
83        }
84    }
85
86    pub const ENABLE_OSCILLATOR_CONTROL: u8 = 0b010;
87
88    /// Corresponding values for periodic_timer_rate values in range
89    /// `0b0001` to `0b1111`
90    pub const PERIODIC_TIMER_RATE_HZ: [usize; 15] = [
91        256, 128, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2,
92    ];
93
94    #[rustfmt::skip]
95    #[bitfield(u8)]
96    pub struct StatusRegA {
97        #[bits(4)] pub periodic_timer_rate: u8,
98        #[bits(3)] pub oscillator_control: u8,
99        #[bits(1)] pub update: bool,
100    }
101
102    #[rustfmt::skip]
103    #[bitfield(u8)]
104    pub struct StatusRegB {
105        pub dst: bool, // not used in PCs
106        pub h24_mode: bool,
107        pub disable_bcd: bool,
108        pub square_wave_enable: bool, // not used in PCs
109        pub irq_enable_update: bool,
110        pub irq_enable_alarm: bool,
111        pub irq_enable_periodic: bool,
112        pub set: bool,
113    }
114
115    #[rustfmt::skip]
116    #[bitfield(u8)]
117    pub struct StatusRegC {
118        #[bits(4)] _unused: u8,
119        pub irq_update: bool,
120        pub irq_alarm: bool,
121        pub irq_periodic: bool,
122        pub irq_combined: bool,
123    }
124
125    #[rustfmt::skip]
126    #[bitfield(u8)]
127    pub struct StatusRegD {
128        #[bits(7)] _unused: u8,
129        /// Valid Ram And Time. Always set to 1 in emulated systems (as it's not
130        /// like there's a real battery backing our rtc lol)
131        pub vrt: bool,
132    }
133
134    pub const ALARM_WILDCARD: u8 = 0xFF;
135}
136
137open_enum::open_enum! {
138    /// x86 standard RTC IO ports
139    enum RtcIoPort: u16 {
140        ADDR = 0x70,
141        DATA = 0x71,
142    }
143}
144
145/// Newtype around `[u8; 256]` that only supports indexing via [`CmosReg`]
146#[derive(Debug, Clone, Inspect)]
147#[inspect(transparent)]
148struct CmosData(#[inspect(hex, iter_by_index)] [u8; 256]);
149
150impl CmosData {
151    fn empty() -> CmosData {
152        CmosData([0; 256])
153    }
154}
155
156impl std::ops::Index<CmosReg> for CmosData {
157    type Output = u8;
158
159    fn index(&self, index: CmosReg) -> &Self::Output {
160        &self.0[index.0 as usize]
161    }
162}
163
164impl std::ops::IndexMut<CmosReg> for CmosData {
165    fn index_mut(&mut self, index: CmosReg) -> &mut Self::Output {
166        &mut self.0[index.0 as usize]
167    }
168}
169
170/// CMOS RTC device
171#[derive(InspectMut)]
172pub struct Rtc {
173    // Static configuration
174    century_reg: CmosReg,
175    initial_cmos: Option<[u8; 256]>,
176    enlightened_interrupts: bool,
177
178    // Runtime deps
179    real_time_source: Box<dyn InspectableLocalClock>,
180    interrupt: LineInterrupt,
181    vmtime_alarm: VmTimeAccess,
182    vmtimer_periodic: VmTimerPeriodic,
183    vmtimer_update: VmTimerPeriodic,
184
185    // Runtime book-keeping
186    #[inspect(debug)]
187    last_update_bit_blip: LocalClockTime,
188
189    // Volatile state
190    state: RtcState,
191}
192
193#[derive(Debug, Inspect)]
194struct RtcState {
195    addr: u8,
196    cmos: CmosData,
197}
198
199impl RtcState {
200    fn new(initial_cmos: Option<[u8; 256]>) -> Self {
201        let mut cmos = initial_cmos.map(CmosData).unwrap_or_else(CmosData::empty);
202
203        cmos[CmosReg::STATUS_A] = {
204            StatusRegA::new()
205                .with_periodic_timer_rate(0b0110)
206                .with_oscillator_control(ENABLE_OSCILLATOR_CONTROL)
207                .into()
208        };
209        cmos[CmosReg::STATUS_B] = {
210            StatusRegB::new()
211                .with_disable_bcd(false)
212                .with_h24_mode(true)
213                .into()
214        };
215        cmos[CmosReg::STATUS_C] = StatusRegC::new().into();
216        cmos[CmosReg::STATUS_D] = StatusRegD::new().with_vrt(true).into();
217
218        Self {
219            // technically, the default addr is undefined, but this is the
220            // default Hyper-V used, so we'll stick with it
221            addr: 0x80,
222            cmos,
223        }
224    }
225}
226
227impl ChangeDeviceState for Rtc {
228    fn start(&mut self) {}
229
230    async fn stop(&mut self) {}
231
232    async fn reset(&mut self) {
233        self.state = RtcState::new(self.initial_cmos);
234
235        self.update_timers();
236        self.update_interrupt_line_level();
237    }
238}
239
240impl ChipsetDevice for Rtc {
241    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
242        Some(self)
243    }
244
245    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
246        Some(self)
247    }
248}
249
250fn to_bcd(n: u8) -> u8 {
251    ((n / 10) << 4) | (n % 10)
252}
253
254fn from_bcd(n: u8) -> u8 {
255    (n >> 4) * 10 + (n & 0xf)
256}
257
258/// Applies BCD + 24H time calculations on-top of HMS values fetched from CMOS.
259fn canonical_hms(status_b: StatusRegB, mut hour: u8, mut min: u8, mut sec: u8) -> (u8, u8, u8) {
260    if !status_b.disable_bcd() {
261        sec = from_bcd(sec);
262        min = from_bcd(min);
263
264        if status_b.h24_mode() {
265            hour = from_bcd(hour);
266        } else {
267            let hour_ampm = from_bcd(hour & 0x7F);
268            if hour & 0x80 != 0 {
269                hour = hour_ampm + 12;
270            } else {
271                hour = hour_ampm
272            }
273        }
274    } else {
275        if !status_b.h24_mode() {
276            if hour & 0x80 != 0 {
277                hour = (hour & 0x7F) + 12;
278            }
279        }
280    }
281
282    (hour, min, sec)
283}
284
285impl Rtc {
286    /// Create a new CMOS RTC device
287    ///
288    /// `century_reg_idx` sets which byte of CMOS RAM to use as the century
289    /// byte. This index is not standard between different platforms. e.g: on
290    /// modern x86 platforms, the presence / index of the century register is
291    /// determined by an entry in the FADT ACPI table.
292    ///
293    /// If `enlightened_interrupts`, then whenever a timer expires and the
294    /// interrupt line would be set, pulse the interrupt line low then high to
295    /// ensure an interrupt is delivered. This is used by older Windows guests
296    /// to allow them to skip an extra PIO exit to clear the status C register.
297    /// This behavior is indicated to the guest via an appropriate flag is set
298    /// in the WAET ACPI table. See [Windows ACPI Emulated Devices Table][].
299    ///
300    /// [Windows ACPI Emulated Devices Table]:
301    ///     <https://download.microsoft.com/download/7/E/7/7E7662CF-CBEA-470B-A97E-CE7CE0D98DC2/WAET.docx>
302    pub fn new(
303        real_time_source: Box<dyn InspectableLocalClock>,
304        interrupt: LineInterrupt,
305        vmtime_source: &VmTimeSource,
306        century_reg_idx: u8,
307        initial_cmos: Option<[u8; 256]>,
308        enlightened_interrupts: bool,
309    ) -> Self {
310        Rtc {
311            century_reg: CmosReg(century_reg_idx),
312            initial_cmos,
313            enlightened_interrupts,
314
315            real_time_source,
316            interrupt,
317            vmtime_alarm: vmtime_source.access("rtc-alarm"),
318            vmtimer_periodic: VmTimerPeriodic::new(vmtime_source.access("rtc-periodic")),
319            vmtimer_update: VmTimerPeriodic::new(vmtime_source.access("rtc-update")),
320
321            last_update_bit_blip: LocalClockTime::from_millis_since_unix_epoch(0),
322
323            state: RtcState::new(initial_cmos),
324        }
325    }
326
327    /// (for use by wrapper devices) reference to the raw underlying CMOS data
328    pub fn raw_cmos(&mut self) -> &mut [u8; 256] {
329        &mut self.state.cmos.0
330    }
331
332    /// Calculate the duration between the current time and the alarm time.
333    ///
334    /// (this logic was ported over from Hyper-V, instructive comment and all)
335    ///
336    /// * * *
337    ///
338    /// What an ugly mess.
339    ///
340    /// The alarm works like an alarm clock. So if you set it for 5:02:03, it
341    /// will go off every day at 5:02:03. But if any of the time elements are
342    /// 0xFF, that acts as a wildcard. So 5:FF:03 would go off at 5:00:03 and
343    /// 5:01:03 and 5:02:03, etc. An alarm set for FF:FF:FF goes off every
344    /// second.
345    ///
346    /// The problem is, what happens if, when we compute when the timer should
347    /// go off, that it should go off NOW?
348    ///
349    /// Suppose, for instance, that they ask for 5:02:03, and at 5:02:03 we
350    /// dutifully fire off the interrupt. The next thing we have to do is figure
351    /// out when the alarm goes off next. It's supposed to go off next at
352    /// 5:02:03, but if we've serviced the thing in reasonable time, it's
353    /// *still* 5:02:03. We don't want to keep interrupting continuously for the
354    /// next second, until it's 5:02:04. We want to wait a full day, and go off
355    /// *tomorrow* at 5:02:03. Ahhh, but what if we're supposed to go off at
356    /// 5:FF:03? In this case, we need to go off again in a minute. For 5:02:FF,
357    /// it has to be in a second.
358    ///
359    /// Handling all of this is what the `go_around_again` variable is for.
360    ///
361    /// It will be set to the appropriate amount of time we need to wait if, by
362    /// calculation, the alarm time would be immediate, as just described (which
363    /// is the amount for the smallest unit of time which has a wildcard (24
364    /// hours if none)).
365    fn calculate_alarm_duration(&self) -> Duration {
366        use self::spec::ALARM_WILDCARD;
367
368        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
369        let (now_hour, now_min, now_sec) = canonical_hms(
370            status_b,
371            self.state.cmos[CmosReg::HOUR],
372            self.state.cmos[CmosReg::MINUTE],
373            self.state.cmos[CmosReg::SECOND],
374        );
375        let (alarm_hour, alarm_min, alarm_sec) = canonical_hms(
376            status_b,
377            self.state.cmos[CmosReg::HOUR_ALARM],
378            self.state.cmos[CmosReg::MINUTE_ALARM],
379            self.state.cmos[CmosReg::SECOND_ALARM],
380        );
381
382        let mut delta_hour: u8 = 0;
383        let mut delta_min: u8 = 0;
384        let mut delta_sec: u8 = 0;
385
386        if alarm_sec == ALARM_WILDCARD {
387            delta_sec = 0;
388        } else {
389            delta_sec = delta_sec.wrapping_add(alarm_sec.wrapping_sub(now_sec));
390            if alarm_sec < now_sec {
391                delta_sec = delta_sec.wrapping_add(60);
392                delta_min = delta_min.wrapping_sub(1);
393            }
394        }
395
396        if alarm_min == ALARM_WILDCARD {
397            delta_min = 0;
398        } else {
399            delta_min = delta_min.wrapping_add(alarm_min.wrapping_sub(now_min));
400            if alarm_min < now_min {
401                delta_min = delta_min.wrapping_add(60);
402                delta_hour = delta_hour.wrapping_sub(1);
403            }
404        }
405
406        if alarm_hour == ALARM_WILDCARD {
407            delta_hour = 0;
408        } else {
409            delta_hour = delta_hour.wrapping_add(alarm_hour.wrapping_sub(now_hour));
410            if alarm_hour < now_hour {
411                delta_hour = delta_hour.wrapping_add(24);
412            }
413        }
414
415        const DURATION_SEC: Duration = Duration::from_secs(1);
416        const DURATION_MIN: Duration = Duration::from_secs(60);
417        const DURATION_HOUR: Duration = Duration::from_secs(60 * 60);
418        const DURATION_DAY: Duration = Duration::from_secs(60 * 60 * 24);
419
420        let go_around_again = match (alarm_sec, alarm_min, alarm_hour) {
421            (ALARM_WILDCARD, _, _) => DURATION_SEC,
422            (_, ALARM_WILDCARD, _) => DURATION_MIN,
423            (_, _, ALARM_WILDCARD) => DURATION_HOUR,
424            // if no wildcards were specified, then the alarm is set to fire in a day
425            _ => DURATION_DAY,
426        };
427
428        let alarm_duration = {
429            DURATION_HOUR * delta_hour as u32
430                + DURATION_MIN * delta_min as u32
431                + DURATION_SEC * delta_sec as u32
432        };
433
434        tracing::debug!(
435            now = ?(now_hour, now_min, now_sec),
436            alarm = ?(alarm_hour, alarm_min, alarm_sec),
437            delta = ?(delta_hour, delta_min, delta_sec),
438            ?go_around_again,
439            ?alarm_duration,
440            "setting alarm"
441        );
442
443        if alarm_duration.is_zero() {
444            go_around_again
445        } else {
446            alarm_duration
447        }
448    }
449
450    fn set_alarm_timer(&mut self, now: VmTime) {
451        self.sync_clock_to_cmos();
452        let alarm_duration = self.calculate_alarm_duration();
453
454        self.vmtime_alarm
455            .set_timeout(now.wrapping_add(alarm_duration));
456    }
457
458    fn on_alarm_timer(&mut self, now: VmTime) {
459        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
460        self.state.cmos[CmosReg::STATUS_C] =
461            status_c.with_irq_alarm(true).with_irq_combined(true).into();
462
463        self.update_interrupt_line_level();
464
465        // re-arm the alarm timer
466        self.set_alarm_timer(now)
467    }
468
469    fn set_periodic_timer(&mut self) {
470        use self::spec::PERIODIC_TIMER_RATE_HZ;
471
472        let status_a = StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]);
473
474        // 0b0000 means the periodic timer is off
475        if status_a.periodic_timer_rate() == 0 {
476            return;
477        }
478
479        let tick_hz = PERIODIC_TIMER_RATE_HZ[status_a.periodic_timer_rate() as usize - 1];
480        let tick_period = Duration::from_secs_f32(1. / tick_hz as f32);
481
482        tracing::debug!(
483            periodic_timer_rate = ?status_a.periodic_timer_rate(),
484            ?tick_hz,
485            ?tick_period,
486            "setting periodic timer"
487        );
488
489        self.vmtimer_periodic.start(tick_period);
490    }
491
492    fn on_periodic_timer(&mut self) {
493        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
494        self.state.cmos[CmosReg::STATUS_C] = status_c
495            .with_irq_periodic(true)
496            .with_irq_combined(true)
497            .into();
498
499        self.update_interrupt_line_level();
500    }
501
502    fn set_update_timer(&mut self) {
503        self.vmtimer_update.start(Duration::from_secs(1));
504    }
505
506    fn on_update_timer(&mut self) {
507        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
508        self.state.cmos[CmosReg::STATUS_C] = status_c
509            .with_irq_update(true)
510            .with_irq_combined(true)
511            .into();
512
513        self.update_interrupt_line_level();
514    }
515
516    /// Synchronizes the line level of the interrupt line with the state of
517    /// status register C.
518    fn update_interrupt_line_level(&self) {
519        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
520
521        if status_c.irq_update() || status_c.irq_periodic() || status_c.irq_alarm() {
522            assert!(status_c.irq_combined());
523            if self.enlightened_interrupts {
524                self.interrupt.set_level(false);
525            }
526            self.interrupt.set_level(true);
527        } else {
528            assert!(!status_c.irq_combined());
529            self.interrupt.set_level(false);
530        }
531    }
532
533    /// Synchronize vmtime-backed timers with current timer / alarm register
534    /// configuration.
535    fn update_timers(&mut self) {
536        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
537
538        if status_b.irq_enable_alarm() {
539            if self.vmtime_alarm.get_timeout().is_none() {
540                self.set_alarm_timer(self.vmtime_alarm.now())
541            }
542        } else {
543            self.vmtime_alarm.cancel_timeout();
544        }
545
546        if status_b.irq_enable_periodic() {
547            if !self.vmtimer_periodic.is_running() {
548                self.set_periodic_timer()
549            }
550        } else {
551            self.vmtimer_periodic.cancel();
552        }
553
554        if status_b.irq_enable_update() {
555            if !self.vmtimer_update.is_running() {
556                self.set_update_timer()
557            }
558        } else {
559            self.vmtimer_update.cancel();
560        }
561    }
562
563    /// Directly write a byte in the CMOS.
564    ///
565    /// This method is marked `pub` to in order to implement wrapper devices
566    /// that inject platform-specific CMOS memory contents. e.g: the AMI RTC
567    /// device.
568    pub fn set_cmos_byte(&mut self, addr: u8, data: u8) {
569        let addr = CmosReg(addr);
570
571        tracing::trace!(?addr, ?data, "set_cmos_byte");
572
573        if (CmosReg::STATUS_A..=CmosReg::STATUS_D).contains(&addr) {
574            self.set_status_byte(addr, data);
575        } else {
576            let old_data = self.state.cmos[addr];
577
578            if data != old_data {
579                // make sure the cmos time is up-to-date
580                if addr.depends_on_rtc(self.century_reg) {
581                    self.sync_clock_to_cmos();
582                }
583
584                self.state.cmos[addr] = data;
585
586                // update the skew after setting the time
587                if addr.depends_on_rtc(self.century_reg) {
588                    self.sync_cmos_to_clock();
589                }
590
591                if addr.depends_on_alarm() || addr.depends_on_rtc(self.century_reg) {
592                    if self.vmtime_alarm.get_timeout().is_some() {
593                        self.set_alarm_timer(self.vmtime_alarm.now());
594                    }
595                }
596            }
597        }
598    }
599
600    /// Directly read a byte in the CMOS.
601    ///
602    /// This method is marked `pub` to in order to implement wrapper devices
603    /// that inject platform-specific CMOS memory contents. e.g: the AMI RTC
604    /// device.
605    pub fn get_cmos_byte(&mut self, addr: u8) -> u8 {
606        let addr = CmosReg(addr);
607
608        let data = if (CmosReg::STATUS_A..=CmosReg::STATUS_D).contains(&addr) {
609            self.get_status_byte(addr)
610        } else {
611            if addr.depends_on_rtc(self.century_reg) {
612                self.sync_clock_to_cmos();
613            }
614
615            self.state.cmos[addr]
616        };
617
618        tracing::trace!(?addr, ?data, "get_cmos_byte");
619
620        data
621    }
622
623    fn set_status_byte(&mut self, addr: CmosReg, data: u8) {
624        match addr {
625            CmosReg::STATUS_A => {
626                let new_reg = StatusRegA::from(data);
627                let old_reg = StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]);
628
629                // Determine if the oscillator is being programmed
630                if new_reg.oscillator_control() != old_reg.oscillator_control() {
631                    // need to re-prime alarm timer
632                    self.vmtime_alarm.cancel_timeout();
633                }
634
635                if new_reg.periodic_timer_rate() != old_reg.periodic_timer_rate() {
636                    // need to re-prime the periodic timer
637                    self.vmtimer_periodic.cancel();
638                }
639
640                // update bit is read-only
641                self.state.cmos[CmosReg::STATUS_A] = data & 0x7F;
642
643                self.update_timers();
644            }
645            CmosReg::STATUS_B => {
646                let mut new_reg = StatusRegB::from(data);
647
648                // When updates are disabled, update interrupts are also disabled
649                if new_reg.set() {
650                    tracing::debug!("disable timer update and interrupt");
651                    new_reg.set_irq_enable_update(false)
652                }
653
654                self.state.cmos[CmosReg::STATUS_B] = new_reg.into();
655
656                self.update_timers();
657            }
658            // The AMI BIOS, in all its great wisdom, writes to these read-only registers.
659            // We'll just silently allow that to happen...
660            CmosReg::STATUS_C | CmosReg::STATUS_D => {}
661            _ => unreachable!("passed invalid status reg"),
662        }
663    }
664
665    fn get_status_byte(&mut self, addr: CmosReg) -> u8 {
666        match addr {
667            CmosReg::STATUS_A => {
668                let mut data = StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]);
669
670                // The high bit of status A indicates a time update is in progress.
671                // On real HW, this bit blips to 1 for a brief interval each second.
672                // Guest OSes tend to use this brief blip once per second to calibrate
673                // the rate of other timers such as the TSC.  Guest OSes tend to spin
674                // wait for a rising or falling transition of the bit (typically rising edge),
675                // and then the guest OS will wait for another of the same transition.
676                if !StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]).set() {
677                    let now = self.real_time_source.get_time();
678                    let elapsed = now - self.last_update_bit_blip;
679
680                    // check if the programmed time jumped backwards
681                    if elapsed.as_millis().is_negative() {
682                        tracing::debug!("clock jumped backwards between update bit blips");
683                        self.last_update_bit_blip = LocalClockTime::from_millis_since_unix_epoch(0);
684                    }
685
686                    tracing::trace!(
687                        ?self.last_update_bit_blip,
688                        ?now,
689                        ?elapsed,
690                        "get_status_byte"
691                    );
692
693                    let elapsed_millis = elapsed.as_millis();
694                    if elapsed_millis > 1000 {
695                        // Update the date/time and note that we set the update bit now.
696                        data.set_update(true);
697                        self.sync_clock_to_cmos();
698                        self.last_update_bit_blip = now;
699                        tracing::trace!(
700                            ?data,
701                            ?elapsed,
702                            cmos_date_time = ?self.read_cmos_date_time(),
703                            "blip'd status a update bit"
704                        );
705                    }
706                }
707
708                data.into()
709            }
710            CmosReg::STATUS_B => self.state.cmos[CmosReg::STATUS_B],
711            CmosReg::STATUS_C => {
712                let data = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
713
714                // clear pending interrupt flags.
715                tracing::debug!("clearing rtc interrupts");
716                self.state.cmos[CmosReg::STATUS_C] = StatusRegC::new().into();
717                self.update_interrupt_line_level();
718
719                data.into()
720            }
721            CmosReg::STATUS_D => {
722                // always report valid ram time
723                StatusRegD::new().with_vrt(true).into()
724            }
725            _ => unreachable!("passed invalid status reg"),
726        }
727    }
728
729    fn read_cmos_date_time(&self) -> Result<jiff::civil::DateTime, jiff::Error> {
730        let mut sec = self.state.cmos[CmosReg::SECOND];
731        let mut min = self.state.cmos[CmosReg::MINUTE];
732        let mut hour = self.state.cmos[CmosReg::HOUR];
733        let mut day = self.state.cmos[CmosReg::DAY_OF_MONTH];
734        let mut month = self.state.cmos[CmosReg::MONTH];
735        let mut year = self.state.cmos[CmosReg::YEAR];
736        let mut century = self.state.cmos[self.century_reg];
737
738        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
739
740        // factor in BCD, 24h time
741        (hour, min, sec) = canonical_hms(status_b, hour, min, sec);
742        if !status_b.disable_bcd() {
743            (day, month, year, century) = (
744                from_bcd(day),
745                from_bcd(month),
746                from_bcd(year),
747                from_bcd(century),
748            );
749        }
750
751        jiff::civil::DateTime::new(
752            year as i16 + century as i16 * 100,
753            month as i8,
754            day as i8,
755            hour as i8,
756            min as i8,
757            sec as i8,
758            0,
759        )
760    }
761
762    /// Update the CMOS RTC registers with the current time from the backing
763    /// `real_time_source`.
764    fn sync_clock_to_cmos(&mut self) {
765        if StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]).oscillator_control()
766            != ENABLE_OSCILLATOR_CONTROL
767        {
768            // Oscillator is disabled
769            tracing::trace!(
770                cmos_reg_status_a = self.state.cmos[CmosReg::STATUS_A],
771                "sync_clock_to_cmos: Oscillator is disabled."
772            );
773
774            return;
775        }
776
777        let real_time = self.real_time_source.get_time();
778        let Ok(clock_time): Result<jiff::Timestamp, _> = real_time.try_into() else {
779            tracelimit::warn_ratelimited!(
780                ?real_time,
781                "invalid date/time in real_time_source, skipping sync"
782            );
783            return;
784        };
785
786        let clock_time = clock_time.to_zoned(jiff::tz::TimeZone::UTC).datetime();
787
788        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
789
790        self.state.cmos[CmosReg::SECOND] = clock_time.second() as u8;
791        self.state.cmos[CmosReg::MINUTE] = clock_time.minute() as u8;
792        self.state.cmos[CmosReg::HOUR] = {
793            let hour = clock_time.hour() as u8;
794            if status_b.h24_mode() {
795                hour
796            } else {
797                if clock_time.hour() > 12 {
798                    hour - 12
799                } else {
800                    hour
801                }
802            }
803        };
804        self.state.cmos[CmosReg::DAY_OF_WEEK] =
805            clock_time.date().weekday().to_sunday_one_offset() as u8;
806        self.state.cmos[CmosReg::DAY_OF_MONTH] = clock_time.day() as u8;
807        self.state.cmos[CmosReg::MONTH] = clock_time.month() as u8;
808        self.state.cmos[CmosReg::YEAR] = (clock_time.year() % 100) as u8;
809        self.state.cmos[self.century_reg] = (clock_time.year() / 100) as u8;
810
811        if !status_b.disable_bcd() {
812            let regs = [
813                CmosReg::SECOND,
814                CmosReg::MINUTE,
815                CmosReg::HOUR,
816                CmosReg::DAY_OF_WEEK,
817                CmosReg::DAY_OF_MONTH,
818                CmosReg::MONTH,
819                CmosReg::YEAR,
820                self.century_reg,
821            ];
822
823            for reg in regs {
824                self.state.cmos[reg] = to_bcd(self.state.cmos[reg])
825            }
826        }
827
828        if !status_b.h24_mode() {
829            if clock_time.hour() > 12 {
830                self.state.cmos[CmosReg::HOUR] |= 0x80;
831            }
832        }
833
834        tracing::trace!(
835            cmos_reg_status_b = self.state.cmos[CmosReg::STATUS_B],
836            use_bcd_encoding = ?!status_b.disable_bcd(),
837            use_24h_time = ?status_b.h24_mode(),
838            cmos_date_time = ?self.read_cmos_date_time(),
839            "sync_clock_to_cmos"
840        );
841    }
842
843    /// Write-back the current contents of the CMOS RTC registers into the
844    /// backing `real_time_source`
845    fn sync_cmos_to_clock(&mut self) {
846        let cmos_time: jiff::Timestamp = match self
847            .read_cmos_date_time()
848            .and_then(|cmos_time| cmos_time.to_zoned(jiff::tz::TimeZone::UTC))
849        {
850            Ok(zoned) => zoned.timestamp(),
851            Err(e) => {
852                tracelimit::warn_ratelimited!(?e, "invalid date/time in RTC registers!");
853                return;
854            }
855        };
856        self.real_time_source.set_time(cmos_time.into());
857    }
858
859    #[cfg(test)]
860    fn get_cmos_date_time(&mut self) -> Result<jiff::civil::DateTime, jiff::Error> {
861        self.sync_clock_to_cmos();
862        self.read_cmos_date_time()
863    }
864}
865
866impl PortIoIntercept for Rtc {
867    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
868        // We assume all accesses are one byte in size. Attempts to
869        // access larger sizes will return a single byte of information
870        // (zero-extended to the size of the access).
871        data[0] = match RtcIoPort(io_port) {
872            RtcIoPort::ADDR => self.state.addr,
873            RtcIoPort::DATA => self.get_cmos_byte(self.state.addr),
874            _ => return IoResult::Err(IoError::InvalidRegister),
875        };
876
877        IoResult::Ok
878    }
879
880    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
881        // We assume all accesses are one byte in size. Attempts to
882        // access larger sizes will return a single byte of information
883        // (zero-extended to the size of the access).
884        match RtcIoPort(io_port) {
885            RtcIoPort::ADDR => {
886                if data[0] & 0x7F != data[0] {
887                    tracing::debug!("guest tried to set high-bit in CMOS addr register")
888                }
889
890                self.state.addr = data[0] & 0x7F
891            }
892            RtcIoPort::DATA => self.set_cmos_byte(self.state.addr, data[0]),
893            _ => return IoResult::Err(IoError::InvalidRegister),
894        }
895
896        IoResult::Ok
897    }
898
899    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
900        &[("io", (RtcIoPort::ADDR.0)..=(RtcIoPort::DATA.0))]
901    }
902}
903
904impl PollDevice for Rtc {
905    fn poll_device(&mut self, cx: &mut std::task::Context<'_>) {
906        while let Poll::Ready(now) = self.vmtime_alarm.poll_timeout(cx) {
907            self.on_alarm_timer(now)
908        }
909
910        if let Poll::Ready(_now) = self.vmtimer_periodic.poll_timeout(cx) {
911            self.on_periodic_timer()
912        }
913
914        if let Poll::Ready(_now) = self.vmtimer_update.poll_timeout(cx) {
915            self.on_update_timer()
916        }
917    }
918}
919
920mod save_restore {
921    use super::*;
922    use vmcore::save_restore::RestoreError;
923    use vmcore::save_restore::SaveError;
924    use vmcore::save_restore::SaveRestore;
925
926    mod state {
927        use mesh::payload::Protobuf;
928        use vmcore::save_restore::SavedStateRoot;
929
930        /// RTC saved state.
931        #[derive(Protobuf, SavedStateRoot)]
932        #[mesh(package = "chipset.cmos_rtc")]
933        pub struct SavedState {
934            #[mesh(1)]
935            pub addr: u8,
936            #[mesh(2)]
937            pub cmos: [u8; 256],
938        }
939    }
940
941    impl SaveRestore for Rtc {
942        type SavedState = state::SavedState;
943
944        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
945            let RtcState { addr, ref cmos } = self.state;
946
947            let saved_state = state::SavedState { addr, cmos: cmos.0 };
948
949            Ok(saved_state)
950        }
951
952        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
953            let state::SavedState { addr, cmos } = state;
954
955            self.state = RtcState {
956                addr,
957                cmos: CmosData(cmos),
958            };
959
960            self.update_timers();
961            self.update_interrupt_line_level();
962
963            Ok(())
964        }
965    }
966}
967
968#[cfg(test)]
969mod tests {
970    use super::*;
971    use local_clock::MockLocalClock;
972    use local_clock::MockLocalClockAccessor;
973    use test_with_tracing::test;
974
975    fn new_test_rtc() -> (
976        pal_async::DefaultPool,
977        vmcore::vmtime::VmTimeKeeper,
978        MockLocalClockAccessor,
979        Rtc,
980    ) {
981        let mut pool = pal_async::DefaultPool::new();
982        let driver = pool.driver();
983        let vm_time_keeper =
984            vmcore::vmtime::VmTimeKeeper::new(&pool.driver(), VmTime::from_100ns(0));
985        let vm_time_source = pool
986            .run_until(vm_time_keeper.builder().build(&driver))
987            .unwrap();
988
989        let time = MockLocalClock::new();
990        let time_access = time.accessor();
991
992        let rtc = Rtc::new(
993            Box::new(time),
994            LineInterrupt::detached(),
995            &vm_time_source,
996            0x32,
997            None,
998            false,
999        );
1000
1001        (pool, vm_time_keeper, time_access, rtc)
1002    }
1003
1004    fn get_cmos_data(rtc: &mut Rtc, addr: CmosReg) -> u8 {
1005        let mut temp = [addr.0];
1006        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1007        rtc.io_read(RtcIoPort::DATA.0, &mut temp).unwrap();
1008        temp[0]
1009    }
1010
1011    fn set_cmos_data(rtc: &mut Rtc, addr: CmosReg, data: u8) {
1012        let mut temp = [addr.0];
1013        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1014        temp[0] = data;
1015        rtc.io_write(RtcIoPort::DATA.0, &temp).unwrap();
1016    }
1017
1018    fn get_rtc_data(rtc: &mut Rtc, addr: CmosReg, bcd: bool, hour24: bool) -> u8 {
1019        let mut temp = [addr.0];
1020        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1021        rtc.io_read(RtcIoPort::DATA.0, &mut temp).unwrap();
1022        let mut data = temp[0];
1023        if addr == CmosReg::HOUR {
1024            let pm = if hour24 { false } else { (data & 0x80) != 0 };
1025            if pm {
1026                data &= 0x7f
1027            };
1028            if bcd {
1029                data = from_bcd(data)
1030            };
1031            if pm {
1032                data += 12;
1033            }
1034        } else {
1035            if bcd {
1036                data = from_bcd(data)
1037            };
1038        }
1039
1040        println!("get {0}({0:#x}) convert to {1}", temp[0], data);
1041        data
1042    }
1043
1044    fn set_rtc_data(rtc: &mut Rtc, addr: CmosReg, data: u8, bcd: bool, hour24: bool) {
1045        let mut temp = [addr.0];
1046        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1047        let mut new_data = data;
1048        if addr == CmosReg::HOUR {
1049            let pm = if hour24 { false } else { new_data > 12 };
1050            if bcd {
1051                new_data =
1052                    to_bcd(if pm { new_data - 12 } else { new_data }) | if pm { 0x80 } else { 0 };
1053            } else {
1054                if pm {
1055                    new_data = (new_data - 12) | 0x80;
1056                }
1057            }
1058        } else {
1059            if bcd {
1060                new_data = to_bcd(new_data)
1061            };
1062        }
1063
1064        println!("set {0} convert to {1}({1:#x})", data, new_data);
1065        temp[0] = new_data;
1066        rtc.io_write(RtcIoPort::DATA.0, &temp).unwrap();
1067    }
1068
1069    // Local support routine.
1070    // Wait for CMOS update strobed rising edge.
1071    fn wait_for_edge(rtc: &mut Rtc, high: bool, time: MockLocalClockAccessor) -> bool {
1072        let limit = 5; //seconds
1073        let stall_ms = 10; //10ms to pause
1074
1075        for _i in 0..(limit * 1000 / stall_ms) {
1076            let value = get_cmos_data(rtc, CmosReg::STATUS_A);
1077            if high {
1078                if value & 0x80 != 0 {
1079                    return true;
1080                }
1081            } else {
1082                if value & 0x80 == 0 {
1083                    return true;
1084                }
1085            }
1086
1087            time.tick(Duration::from_millis(stall_ms));
1088        }
1089
1090        false
1091    }
1092
1093    fn set_bcd(rtc: &mut Rtc) {
1094        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1095        value &= 0xFB;
1096        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1097    }
1098
1099    fn set_binary(rtc: &mut Rtc) {
1100        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1101        value |= 0x4;
1102        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1103    }
1104
1105    fn set_24hour(rtc: &mut Rtc) {
1106        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1107        value |= 0x2;
1108        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1109    }
1110
1111    fn set_12hour(rtc: &mut Rtc) {
1112        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1113        value &= 0xFD;
1114        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1115    }
1116
1117    #[test]
1118    fn test_setup() {
1119        let (_, _, _, _rtc) = new_test_rtc();
1120    }
1121
1122    #[test]
1123    fn test_default() {
1124        let default_state = RtcState::new(None);
1125
1126        let (_, _, _, mut rtc) = new_test_rtc();
1127
1128        let mut data = [0];
1129        rtc.io_read(RtcIoPort::ADDR.0, &mut data).unwrap();
1130        assert_eq!(data[0], 0x80);
1131        assert_eq!(
1132            get_cmos_data(&mut rtc, CmosReg::STATUS_A),
1133            default_state.cmos[CmosReg::STATUS_A] | 0x80
1134        );
1135        assert_eq!(
1136            get_cmos_data(&mut rtc, CmosReg::STATUS_B),
1137            default_state.cmos[CmosReg::STATUS_B]
1138        );
1139        assert_eq!(
1140            get_cmos_data(&mut rtc, CmosReg::STATUS_C),
1141            default_state.cmos[CmosReg::STATUS_C]
1142        );
1143        assert_eq!(
1144            get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1145            default_state.cmos[CmosReg::STATUS_D]
1146        );
1147    }
1148
1149    fn test_time_move(rtc: &mut Rtc, is_move: bool, time: MockLocalClockAccessor) {
1150        if let Ok(before) = rtc.get_cmos_date_time() {
1151            time.tick(Duration::from_secs(2));
1152            if let Ok(after) = rtc.get_cmos_date_time() {
1153                if is_move {
1154                    assert_ne!(before, after);
1155                } else {
1156                    assert_eq!(before, after);
1157                }
1158            } else {
1159                panic!("get_cmos_date_time failed");
1160            }
1161        } else {
1162            panic!("get_cmos_date_time failed");
1163        }
1164    }
1165
1166    #[test]
1167    fn test_oscillator() {
1168        let default_state = RtcState::new(None);
1169
1170        let (_, _, time, mut rtc) = new_test_rtc();
1171
1172        assert_eq!(
1173            get_cmos_data(&mut rtc, CmosReg::STATUS_A),
1174            default_state.cmos[CmosReg::STATUS_A] | 0x80
1175        );
1176
1177        println!("RTC should move forward when oscillator is enabled (default control mask 010)");
1178        test_time_move(&mut rtc, true, time.clone());
1179
1180        // Disable the oscillator
1181        set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x66);
1182        println!("RTC should not move forward when oscillator is disabled (control mask 110)");
1183        test_time_move(&mut rtc, false, time.clone());
1184
1185        // Re-enable the oscillator
1186        set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x26);
1187        println!("RTC should move forward when oscillator is re-enabled (control mask 010)");
1188        test_time_move(&mut rtc, true, time);
1189    }
1190
1191    #[test]
1192    fn test_uip() {
1193        let (_, _, time, mut rtc) = new_test_rtc();
1194
1195        assert!(
1196            wait_for_edge(&mut rtc, false, time.clone())
1197                && wait_for_edge(&mut rtc, true, time.clone())
1198        );
1199        if let Ok(start) = rtc.get_cmos_date_time() {
1200            let seconds_to_wait: i64 = 65;
1201            for _i in 0..seconds_to_wait {
1202                assert!(
1203                    wait_for_edge(&mut rtc, false, time.clone())
1204                        && wait_for_edge(&mut rtc, true, time.clone())
1205                );
1206            }
1207
1208            if let Ok(end) = rtc.get_cmos_date_time() {
1209                let elapsed_secs =
1210                    end.since(start).unwrap().total(jiff::Unit::Second).unwrap() as i64;
1211                let expected_secs = seconds_to_wait;
1212                let allowance_secs: i64 = 1;
1213                println!(
1214                    "Expected: {}s Start: {:?} End: {:?} Elapsed: {}s Allowance: {}s, RTC generates update strobe at expected rate.",
1215                    expected_secs, start, end, elapsed_secs, allowance_secs
1216                );
1217                assert!(
1218                    elapsed_secs <= (expected_secs + allowance_secs)
1219                        && elapsed_secs >= (expected_secs - allowance_secs)
1220                );
1221            } else {
1222                panic!("get_cmos_date_time failed");
1223            }
1224        } else {
1225            panic!("get_cmos_date_time failed");
1226        }
1227    }
1228
1229    #[test]
1230    fn test_readonly() {
1231        let default_state = RtcState::new(None);
1232
1233        let (_, _, _, mut rtc) = new_test_rtc();
1234
1235        assert_eq!(
1236            get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1237            default_state.cmos[CmosReg::STATUS_D]
1238        );
1239
1240        //Status D bits are read-only
1241        for i in 0..=0xFF {
1242            set_cmos_data(&mut rtc, CmosReg::STATUS_D, i);
1243            assert_eq!(
1244                get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1245                default_state.cmos[CmosReg::STATUS_D]
1246            );
1247        }
1248    }
1249
1250    #[test]
1251    fn test_writeable() {
1252        let (_, _, _, mut rtc) = new_test_rtc();
1253
1254        //Registers 0x0f..0x7f should be writable, skip 0x32 which is century field of RTC
1255        for i in (0x0F..=0x7F).map(CmosReg) {
1256            if i == rtc.century_reg {
1257                continue;
1258            }
1259
1260            set_cmos_data(&mut rtc, i, 0xFF);
1261            assert_eq!(get_cmos_data(&mut rtc, i), 0xFF);
1262            set_cmos_data(&mut rtc, i, 0);
1263            assert_eq!(get_cmos_data(&mut rtc, i), 0);
1264        }
1265    }
1266
1267    fn test_count(bcd: bool, hour24: bool) {
1268        let (_, _, time, mut rtc) = new_test_rtc();
1269
1270        if bcd {
1271            set_bcd(&mut rtc);
1272        } else {
1273            set_binary(&mut rtc);
1274        }
1275
1276        if hour24 {
1277            set_24hour(&mut rtc);
1278        } else {
1279            set_12hour(&mut rtc);
1280        }
1281
1282        let init_ts: jiff::Timestamp = time.get_time().try_into().unwrap();
1283        let init = init_ts.to_zoned(jiff::tz::TimeZone::UTC).datetime();
1284        println!("init: {:?}", init);
1285        set_rtc_data(&mut rtc, CmosReg::HOUR, 11, bcd, hour24);
1286        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1287        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1288        time.tick(Duration::from_secs(2));
1289        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1290        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1291        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 12);
1292        assert_eq!(
1293            get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1294            init.day() as u8
1295        );
1296        assert_eq!(
1297            get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24),
1298            init.date().weekday().to_sunday_one_offset() as u8
1299        );
1300
1301        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1302        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1303        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1304        time.tick(Duration::from_secs(2));
1305        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1306        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1307        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1308        let temp = init_ts
1309            .checked_add(jiff::SignedDuration::from_hours(24))
1310            .unwrap()
1311            .to_zoned(jiff::tz::TimeZone::UTC)
1312            .datetime();
1313        assert_eq!(
1314            get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1315            temp.day() as u8
1316        );
1317        assert_eq!(
1318            get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24),
1319            temp.date().weekday().to_sunday_one_offset() as u8
1320        );
1321
1322        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1323        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1324        time.tick(Duration::from_secs(2));
1325        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1326        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1327        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 1);
1328    }
1329
1330    #[test]
1331    fn test_bcd_binary() {
1332        println!("----Testing BCD mode...");
1333        test_count(true, true);
1334        println!("----Testing Binary mode...");
1335        test_count(false, true);
1336    }
1337
1338    #[test]
1339    fn test_hour_mode() {
1340        println!("----Testing Binary + 12 hour mode...");
1341        test_count(false, false);
1342        println!("----Testing Binary + 24 hour mode...");
1343        test_count(false, true);
1344        println!("----Testing BCD + 12 hour mode...");
1345        test_count(true, false);
1346        println!("----Testing BCD + 24 hour mode...");
1347        test_count(true, true);
1348    }
1349
1350    fn test_day_of_month(bcd: bool, hour24: bool) {
1351        let century_reg_idx = 0x32;
1352        let century_reg = CmosReg(century_reg_idx);
1353
1354        let (_, _, time, mut rtc) = new_test_rtc();
1355
1356        if bcd {
1357            set_bcd(&mut rtc);
1358        } else {
1359            set_binary(&mut rtc);
1360        }
1361
1362        if hour24 {
1363            set_24hour(&mut rtc);
1364        } else {
1365            set_12hour(&mut rtc);
1366        }
1367
1368        for month in 1..=12 {
1369            let mut day;
1370            let mut year_check_count = 1;
1371            if month == 4 || month == 6 || month == 9 || month == 11 {
1372                day = 30;
1373            } else if month != 2 {
1374                day = 31;
1375            } else {
1376                day = 0;
1377                year_check_count = 4;
1378            }
1379
1380            while year_check_count > 0 {
1381                let century = 30;
1382                let year = 4 + year_check_count;
1383                if month == 2 {
1384                    day = if (year & 3) > 0 { 28 } else { 29 };
1385                }
1386
1387                println!(
1388                    "----Testing {:02}{:02}-{:02}-{:02}",
1389                    century, year, month, day
1390                );
1391                set_rtc_data(&mut rtc, century_reg, century, bcd, hour24);
1392                set_rtc_data(&mut rtc, CmosReg::YEAR, year, bcd, hour24);
1393                set_rtc_data(&mut rtc, CmosReg::MONTH, month, bcd, hour24);
1394                set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day, bcd, hour24);
1395                set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1396                set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1397                set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1398                time.tick(Duration::from_secs(2));
1399                assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1400                assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1401                assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1402                assert_eq!(
1403                    get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1404                    1
1405                );
1406                assert_eq!(
1407                    get_rtc_data(&mut rtc, CmosReg::MONTH, bcd, hour24),
1408                    if month == 12 { 1 } else { month + 1 }
1409                );
1410                assert_eq!(
1411                    get_rtc_data(&mut rtc, CmosReg::YEAR, bcd, hour24),
1412                    if month < 12 { year } else { year + 1 }
1413                );
1414                assert_eq!(get_rtc_data(&mut rtc, century_reg, bcd, hour24), century);
1415                year_check_count -= 1;
1416            }
1417        }
1418    }
1419
1420    #[test]
1421    fn test_month() {
1422        println!("----Testing BCD mode...");
1423        test_day_of_month(true, true);
1424        println!("----Testing Binary mode...");
1425        test_day_of_month(false, true);
1426    }
1427
1428    fn test_day_of_week(bcd: bool, hour24: bool) {
1429        let century_reg_idx = 0x32;
1430        let century_reg = CmosReg(century_reg_idx);
1431
1432        let (_, _, time, mut rtc) = new_test_rtc();
1433
1434        if bcd {
1435            set_bcd(&mut rtc);
1436        } else {
1437            set_binary(&mut rtc);
1438        }
1439
1440        if hour24 {
1441            set_24hour(&mut rtc);
1442        } else {
1443            set_12hour(&mut rtc);
1444        }
1445
1446        let century = 30;
1447        let year = 5;
1448        let month = 1;
1449        let day = 5;
1450
1451        println!(
1452            "----Testing {:02}{:02}-{:02}-{:02}",
1453            century, year, month, day
1454        );
1455        set_rtc_data(&mut rtc, century_reg, century, bcd, hour24);
1456        set_rtc_data(&mut rtc, CmosReg::YEAR, year, bcd, hour24);
1457        set_rtc_data(&mut rtc, CmosReg::MONTH, month, bcd, hour24);
1458        set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day, bcd, hour24);
1459        set_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, 7, bcd, hour24);
1460        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1461        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1462        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1463        time.tick(Duration::from_secs(2));
1464        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1465        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1466        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1467        assert_eq!(get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24), 1);
1468
1469        set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day - 1, bcd, hour24);
1470        set_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, 6, bcd, hour24);
1471        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1472        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1473        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1474        time.tick(Duration::from_secs(2));
1475        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1476        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1477        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1478        assert_eq!(get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24), 7);
1479    }
1480
1481    #[test]
1482    fn test_week() {
1483        println!("----Testing Binary mode...");
1484        test_day_of_week(false, true);
1485        println!("----Testing BCD mode...");
1486        test_day_of_week(true, true);
1487    }
1488
1489    #[test]
1490    fn test_alarm() {
1491        fn hms_to_duration(h: u8, m: u8, s: u8) -> Duration {
1492            Duration::from_secs(s as u64 + 60 * m as u64 + h as u64 * 60 * 60)
1493        }
1494
1495        let (_, _, _time, mut rtc) = new_test_rtc();
1496
1497        set_binary(&mut rtc);
1498
1499        set_cmos_data(&mut rtc, CmosReg::HOUR, 2);
1500        set_cmos_data(&mut rtc, CmosReg::MINUTE, 2);
1501        set_cmos_data(&mut rtc, CmosReg::SECOND, 2);
1502
1503        set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, 3);
1504        set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 3);
1505        set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 3);
1506
1507        assert_eq!(rtc.calculate_alarm_duration(), hms_to_duration(1, 1, 1));
1508        set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, 0xff);
1509        set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 0xff);
1510        set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 0xff);
1511        assert_eq!(rtc.calculate_alarm_duration(), hms_to_duration(0, 0, 1));
1512
1513        // TODO: test some more alarm scenarios
1514    }
1515}