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: time::Duration = elapsed.into();
694                    if elapsed > time::Duration::seconds(1) {
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<time::PrimitiveDateTime, time::error::ComponentRange> {
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        Ok(time::PrimitiveDateTime::new(
752            time::Date::from_calendar_date(
753                year as i32 + century as i32 * 100,
754                month.try_into()?,
755                day,
756            )?,
757            time::Time::from_hms(hour, min, sec)?,
758        ))
759    }
760
761    /// Update the CMOS RTC registers with the current time from the backing
762    /// `real_time_source`.
763    fn sync_clock_to_cmos(&mut self) {
764        if StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]).oscillator_control()
765            != ENABLE_OSCILLATOR_CONTROL
766        {
767            // Oscillator is disabled
768            tracing::trace!(
769                cmos_reg_status_a = self.state.cmos[CmosReg::STATUS_A],
770                "sync_clock_to_cmos: Oscillator is disabled."
771            );
772
773            return;
774        }
775
776        let real_time = self.real_time_source.get_time();
777        let Ok(clock_time): Result<time::OffsetDateTime, _> = real_time.try_into() else {
778            tracelimit::warn_ratelimited!(
779                ?real_time,
780                "invalid date/time in real_time_source, skipping sync"
781            );
782            return;
783        };
784
785        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
786
787        self.state.cmos[CmosReg::SECOND] = clock_time.second();
788        self.state.cmos[CmosReg::MINUTE] = clock_time.minute();
789        self.state.cmos[CmosReg::HOUR] = {
790            let hour = clock_time.hour();
791            if status_b.h24_mode() {
792                hour
793            } else {
794                if clock_time.hour() > 12 {
795                    hour - 12
796                } else {
797                    hour
798                }
799            }
800        };
801        self.state.cmos[CmosReg::DAY_OF_WEEK] = clock_time.weekday().number_from_sunday();
802        self.state.cmos[CmosReg::DAY_OF_MONTH] = clock_time.day();
803        self.state.cmos[CmosReg::MONTH] = clock_time.month() as u8;
804        self.state.cmos[CmosReg::YEAR] = (clock_time.year() % 100) as u8;
805        self.state.cmos[self.century_reg] = (clock_time.year() / 100) as u8;
806
807        if !status_b.disable_bcd() {
808            let regs = [
809                CmosReg::SECOND,
810                CmosReg::MINUTE,
811                CmosReg::HOUR,
812                CmosReg::DAY_OF_WEEK,
813                CmosReg::DAY_OF_MONTH,
814                CmosReg::MONTH,
815                CmosReg::YEAR,
816                self.century_reg,
817            ];
818
819            for reg in regs {
820                self.state.cmos[reg] = to_bcd(self.state.cmos[reg])
821            }
822        }
823
824        if !status_b.h24_mode() {
825            if clock_time.hour() > 12 {
826                self.state.cmos[CmosReg::HOUR] |= 0x80;
827            }
828        }
829
830        tracing::trace!(
831            cmos_reg_status_b = self.state.cmos[CmosReg::STATUS_B],
832            use_bcd_encoding = ?!status_b.disable_bcd(),
833            use_24h_time = ?status_b.h24_mode(),
834            cmos_date_time = ?self.read_cmos_date_time(),
835            "sync_clock_to_cmos"
836        );
837    }
838
839    /// Write-back the current contents of the CMOS RTC registers into the
840    /// backing `real_time_source`
841    fn sync_cmos_to_clock(&mut self) {
842        let cmos_time: time::OffsetDateTime = match self.read_cmos_date_time() {
843            Ok(cmos_time) => cmos_time.assume_utc(),
844            Err(e) => {
845                tracelimit::warn_ratelimited!(?e, "invalid date/time in RTC registers!");
846                return;
847            }
848        };
849        self.real_time_source.set_time(cmos_time.into());
850    }
851
852    #[cfg(test)]
853    fn get_cmos_date_time(
854        &mut self,
855    ) -> Result<time::PrimitiveDateTime, time::error::ComponentRange> {
856        self.sync_clock_to_cmos();
857        self.read_cmos_date_time()
858    }
859}
860
861impl PortIoIntercept for Rtc {
862    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
863        // We assume all accesses are one byte in size. Attempts to
864        // access larger sizes will return a single byte of information
865        // (zero-extended to the size of the access).
866        data[0] = match RtcIoPort(io_port) {
867            RtcIoPort::ADDR => self.state.addr,
868            RtcIoPort::DATA => self.get_cmos_byte(self.state.addr),
869            _ => return IoResult::Err(IoError::InvalidRegister),
870        };
871
872        IoResult::Ok
873    }
874
875    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
876        // We assume all accesses are one byte in size. Attempts to
877        // access larger sizes will return a single byte of information
878        // (zero-extended to the size of the access).
879        match RtcIoPort(io_port) {
880            RtcIoPort::ADDR => {
881                if data[0] & 0x7F != data[0] {
882                    tracing::debug!("guest tried to set high-bit in CMOS addr register")
883                }
884
885                self.state.addr = data[0] & 0x7F
886            }
887            RtcIoPort::DATA => self.set_cmos_byte(self.state.addr, data[0]),
888            _ => return IoResult::Err(IoError::InvalidRegister),
889        }
890
891        IoResult::Ok
892    }
893
894    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
895        &[("io", (RtcIoPort::ADDR.0)..=(RtcIoPort::DATA.0))]
896    }
897}
898
899impl PollDevice for Rtc {
900    fn poll_device(&mut self, cx: &mut std::task::Context<'_>) {
901        while let Poll::Ready(now) = self.vmtime_alarm.poll_timeout(cx) {
902            self.on_alarm_timer(now)
903        }
904
905        if let Poll::Ready(_now) = self.vmtimer_periodic.poll_timeout(cx) {
906            self.on_periodic_timer()
907        }
908
909        if let Poll::Ready(_now) = self.vmtimer_update.poll_timeout(cx) {
910            self.on_update_timer()
911        }
912    }
913}
914
915mod save_restore {
916    use super::*;
917    use vmcore::save_restore::RestoreError;
918    use vmcore::save_restore::SaveError;
919    use vmcore::save_restore::SaveRestore;
920
921    mod state {
922        use mesh::payload::Protobuf;
923        use vmcore::save_restore::SavedStateRoot;
924
925        /// RTC saved state.
926        #[derive(Protobuf, SavedStateRoot)]
927        #[mesh(package = "chipset.cmos_rtc")]
928        pub struct SavedState {
929            #[mesh(1)]
930            pub addr: u8,
931            #[mesh(2)]
932            pub cmos: [u8; 256],
933        }
934    }
935
936    impl SaveRestore for Rtc {
937        type SavedState = state::SavedState;
938
939        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
940            let RtcState { addr, ref cmos } = self.state;
941
942            let saved_state = state::SavedState { addr, cmos: cmos.0 };
943
944            Ok(saved_state)
945        }
946
947        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
948            let state::SavedState { addr, cmos } = state;
949
950            self.state = RtcState {
951                addr,
952                cmos: CmosData(cmos),
953            };
954
955            self.update_timers();
956            self.update_interrupt_line_level();
957
958            Ok(())
959        }
960    }
961}
962
963#[cfg(test)]
964mod tests {
965    use super::*;
966    use local_clock::MockLocalClock;
967    use local_clock::MockLocalClockAccessor;
968    use test_with_tracing::test;
969
970    fn new_test_rtc() -> (
971        pal_async::DefaultPool,
972        vmcore::vmtime::VmTimeKeeper,
973        MockLocalClockAccessor,
974        Rtc,
975    ) {
976        let mut pool = pal_async::DefaultPool::new();
977        let driver = pool.driver();
978        let vm_time_keeper =
979            vmcore::vmtime::VmTimeKeeper::new(&pool.driver(), VmTime::from_100ns(0));
980        let vm_time_source = pool
981            .run_until(vm_time_keeper.builder().build(&driver))
982            .unwrap();
983
984        let time = MockLocalClock::new();
985        let time_access = time.accessor();
986
987        let rtc = Rtc::new(
988            Box::new(time),
989            LineInterrupt::detached(),
990            &vm_time_source,
991            0x32,
992            None,
993            false,
994        );
995
996        (pool, vm_time_keeper, time_access, rtc)
997    }
998
999    fn get_cmos_data(rtc: &mut Rtc, addr: CmosReg) -> u8 {
1000        let mut temp = [addr.0];
1001        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1002        rtc.io_read(RtcIoPort::DATA.0, &mut temp).unwrap();
1003        temp[0]
1004    }
1005
1006    fn set_cmos_data(rtc: &mut Rtc, addr: CmosReg, data: u8) {
1007        let mut temp = [addr.0];
1008        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1009        temp[0] = data;
1010        rtc.io_write(RtcIoPort::DATA.0, &temp).unwrap();
1011    }
1012
1013    fn get_rtc_data(rtc: &mut Rtc, addr: CmosReg, bcd: bool, hour24: bool) -> u8 {
1014        let mut temp = [addr.0];
1015        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1016        rtc.io_read(RtcIoPort::DATA.0, &mut temp).unwrap();
1017        let mut data = temp[0];
1018        if addr == CmosReg::HOUR {
1019            let pm = if hour24 { false } else { (data & 0x80) != 0 };
1020            if pm {
1021                data &= 0x7f
1022            };
1023            if bcd {
1024                data = from_bcd(data)
1025            };
1026            if pm {
1027                data += 12;
1028            }
1029        } else {
1030            if bcd {
1031                data = from_bcd(data)
1032            };
1033        }
1034
1035        println!("get {0}({0:#x}) convert to {1}", temp[0], data);
1036        data
1037    }
1038
1039    fn set_rtc_data(rtc: &mut Rtc, addr: CmosReg, data: u8, bcd: bool, hour24: bool) {
1040        let mut temp = [addr.0];
1041        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1042        let mut new_data = data;
1043        if addr == CmosReg::HOUR {
1044            let pm = if hour24 { false } else { new_data > 12 };
1045            if bcd {
1046                new_data =
1047                    to_bcd(if pm { new_data - 12 } else { new_data }) | if pm { 0x80 } else { 0 };
1048            } else {
1049                if pm {
1050                    new_data = (new_data - 12) | 0x80;
1051                }
1052            }
1053        } else {
1054            if bcd {
1055                new_data = to_bcd(new_data)
1056            };
1057        }
1058
1059        println!("set {0} convert to {1}({1:#x})", data, new_data);
1060        temp[0] = new_data;
1061        rtc.io_write(RtcIoPort::DATA.0, &temp).unwrap();
1062    }
1063
1064    // Local support routine.
1065    // Wait for CMOS update strobed rising edge.
1066    fn wait_for_edge(rtc: &mut Rtc, high: bool, time: MockLocalClockAccessor) -> bool {
1067        let limit = 5; //seconds
1068        let stall_ms = 10; //10ms to pause
1069
1070        for _i in 0..(limit * 1000 / stall_ms) {
1071            let value = get_cmos_data(rtc, CmosReg::STATUS_A);
1072            if high {
1073                if value & 0x80 != 0 {
1074                    return true;
1075                }
1076            } else {
1077                if value & 0x80 == 0 {
1078                    return true;
1079                }
1080            }
1081
1082            time.tick(Duration::from_millis(stall_ms));
1083        }
1084
1085        false
1086    }
1087
1088    fn set_bcd(rtc: &mut Rtc) {
1089        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1090        value &= 0xFB;
1091        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1092    }
1093
1094    fn set_binary(rtc: &mut Rtc) {
1095        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1096        value |= 0x4;
1097        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1098    }
1099
1100    fn set_24hour(rtc: &mut Rtc) {
1101        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1102        value |= 0x2;
1103        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1104    }
1105
1106    fn set_12hour(rtc: &mut Rtc) {
1107        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1108        value &= 0xFD;
1109        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1110    }
1111
1112    #[test]
1113    fn test_setup() {
1114        let (_, _, _, _rtc) = new_test_rtc();
1115    }
1116
1117    #[test]
1118    fn test_default() {
1119        let default_state = RtcState::new(None);
1120
1121        let (_, _, _, mut rtc) = new_test_rtc();
1122
1123        let mut data = [0];
1124        rtc.io_read(RtcIoPort::ADDR.0, &mut data).unwrap();
1125        assert_eq!(data[0], 0x80);
1126        assert_eq!(
1127            get_cmos_data(&mut rtc, CmosReg::STATUS_A),
1128            default_state.cmos[CmosReg::STATUS_A] | 0x80
1129        );
1130        assert_eq!(
1131            get_cmos_data(&mut rtc, CmosReg::STATUS_B),
1132            default_state.cmos[CmosReg::STATUS_B]
1133        );
1134        assert_eq!(
1135            get_cmos_data(&mut rtc, CmosReg::STATUS_C),
1136            default_state.cmos[CmosReg::STATUS_C]
1137        );
1138        assert_eq!(
1139            get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1140            default_state.cmos[CmosReg::STATUS_D]
1141        );
1142    }
1143
1144    fn test_time_move(rtc: &mut Rtc, is_move: bool, time: MockLocalClockAccessor) {
1145        if let Ok(before) = rtc.get_cmos_date_time() {
1146            time.tick(Duration::from_secs(2));
1147            if let Ok(after) = rtc.get_cmos_date_time() {
1148                if is_move {
1149                    assert_ne!(before, after);
1150                } else {
1151                    assert_eq!(before, after);
1152                }
1153            } else {
1154                panic!("get_cmos_date_time failed");
1155            }
1156        } else {
1157            panic!("get_cmos_date_time failed");
1158        }
1159    }
1160
1161    #[test]
1162    fn test_oscillator() {
1163        let default_state = RtcState::new(None);
1164
1165        let (_, _, time, mut rtc) = new_test_rtc();
1166
1167        assert_eq!(
1168            get_cmos_data(&mut rtc, CmosReg::STATUS_A),
1169            default_state.cmos[CmosReg::STATUS_A] | 0x80
1170        );
1171
1172        println!("RTC should move forward when oscillator is enabled (default control mask 010)");
1173        test_time_move(&mut rtc, true, time.clone());
1174
1175        // Disable the oscillator
1176        set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x66);
1177        println!("RTC should not move forward when oscillator is disabled (control mask 110)");
1178        test_time_move(&mut rtc, false, time.clone());
1179
1180        // Re-enable the oscillator
1181        set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x26);
1182        println!("RTC should move forward when oscillator is re-enabled (control mask 010)");
1183        test_time_move(&mut rtc, true, time);
1184    }
1185
1186    #[test]
1187    fn test_uip() {
1188        let (_, _, time, mut rtc) = new_test_rtc();
1189
1190        assert!(
1191            wait_for_edge(&mut rtc, false, time.clone())
1192                && wait_for_edge(&mut rtc, true, time.clone())
1193        );
1194        if let Ok(start) = rtc.get_cmos_date_time() {
1195            let seconds_to_wait: i64 = 10;
1196            for _i in 0..seconds_to_wait {
1197                assert!(
1198                    wait_for_edge(&mut rtc, false, time.clone())
1199                        && wait_for_edge(&mut rtc, true, time.clone())
1200                );
1201            }
1202
1203            if let Ok(end) = rtc.get_cmos_date_time() {
1204                let elapsed = end - start;
1205                let expected = Duration::from_secs(seconds_to_wait as u64);
1206                let allowance = Duration::from_secs(1);
1207                println!(
1208                    "Expected: {:?} Start: {:?} End: {:?} Elapsed: {:?} Allowance: {:?}, RTC generates update strobe at expected rate.",
1209                    expected, start, end, elapsed, allowance
1210                );
1211                assert!(elapsed <= (expected + allowance) && elapsed >= (expected - allowance));
1212            } else {
1213                panic!("get_cmos_date_time failed");
1214            }
1215        } else {
1216            panic!("get_cmos_date_time failed");
1217        }
1218    }
1219
1220    #[test]
1221    fn test_readonly() {
1222        let default_state = RtcState::new(None);
1223
1224        let (_, _, _, mut rtc) = new_test_rtc();
1225
1226        assert_eq!(
1227            get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1228            default_state.cmos[CmosReg::STATUS_D]
1229        );
1230
1231        //Status D bits are read-only
1232        for i in 0..=0xFF {
1233            set_cmos_data(&mut rtc, CmosReg::STATUS_D, i);
1234            assert_eq!(
1235                get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1236                default_state.cmos[CmosReg::STATUS_D]
1237            );
1238        }
1239    }
1240
1241    #[test]
1242    fn test_writeable() {
1243        let (_, _, _, mut rtc) = new_test_rtc();
1244
1245        //Registers 0x0f..0x7f should be writable, skip 0x32 which is century field of RTC
1246        for i in (0x0F..=0x7F).map(CmosReg) {
1247            if i == rtc.century_reg {
1248                continue;
1249            }
1250
1251            set_cmos_data(&mut rtc, i, 0xFF);
1252            assert_eq!(get_cmos_data(&mut rtc, i), 0xFF);
1253            set_cmos_data(&mut rtc, i, 0);
1254            assert_eq!(get_cmos_data(&mut rtc, i), 0);
1255        }
1256    }
1257
1258    fn test_count(bcd: bool, hour24: bool) {
1259        let (_, _, time, mut rtc) = new_test_rtc();
1260
1261        if bcd {
1262            set_bcd(&mut rtc);
1263        } else {
1264            set_binary(&mut rtc);
1265        }
1266
1267        if hour24 {
1268            set_24hour(&mut rtc);
1269        } else {
1270            set_12hour(&mut rtc);
1271        }
1272
1273        let init: time::OffsetDateTime = time.get_time().try_into().unwrap();
1274        println!("init: {:?}", init);
1275        set_rtc_data(&mut rtc, CmosReg::HOUR, 11, bcd, hour24);
1276        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1277        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1278        time.tick(Duration::from_secs(2));
1279        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1280        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1281        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 12);
1282        assert_eq!(
1283            get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1284            init.day()
1285        );
1286        assert_eq!(
1287            get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24),
1288            init.weekday().number_from_sunday()
1289        );
1290
1291        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1292        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1293        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1294        time.tick(Duration::from_secs(2));
1295        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1296        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1297        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1298        let temp = init + time::Duration::days(1);
1299        assert_eq!(
1300            get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1301            temp.day()
1302        );
1303        assert_eq!(
1304            get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24),
1305            temp.weekday().number_from_sunday()
1306        );
1307
1308        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1309        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1310        time.tick(Duration::from_secs(2));
1311        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1312        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1313        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 1);
1314    }
1315
1316    #[test]
1317    fn test_bcd_binary() {
1318        println!("----Testing BCD mode...");
1319        test_count(true, true);
1320        println!("----Testing Binary mode...");
1321        test_count(false, true);
1322    }
1323
1324    #[test]
1325    fn test_hour_mode() {
1326        println!("----Testing Binary + 12 hour mode...");
1327        test_count(false, false);
1328        println!("----Testing Binary + 24 hour mode...");
1329        test_count(false, true);
1330        println!("----Testing BCD + 12 hour mode...");
1331        test_count(true, false);
1332        println!("----Testing BCD + 24 hour mode...");
1333        test_count(true, true);
1334    }
1335
1336    fn test_day_of_month(bcd: bool, hour24: bool) {
1337        let century_reg_idx = 0x32;
1338        let century_reg = CmosReg(century_reg_idx);
1339
1340        let (_, _, time, mut rtc) = new_test_rtc();
1341
1342        if bcd {
1343            set_bcd(&mut rtc);
1344        } else {
1345            set_binary(&mut rtc);
1346        }
1347
1348        if hour24 {
1349            set_24hour(&mut rtc);
1350        } else {
1351            set_12hour(&mut rtc);
1352        }
1353
1354        for month in 1..=12 {
1355            let mut day;
1356            let mut year_check_count = 1;
1357            if month == 4 || month == 6 || month == 9 || month == 11 {
1358                day = 30;
1359            } else if month != 2 {
1360                day = 31;
1361            } else {
1362                day = 0;
1363                year_check_count = 4;
1364            }
1365
1366            while year_check_count > 0 {
1367                let century = 30;
1368                let year = 4 + year_check_count;
1369                if month == 2 {
1370                    day = if (year & 3) > 0 { 28 } else { 29 };
1371                }
1372
1373                println!(
1374                    "----Testing {:02}{:02}-{:02}-{:02}",
1375                    century, year, month, day
1376                );
1377                set_rtc_data(&mut rtc, century_reg, century, bcd, hour24);
1378                set_rtc_data(&mut rtc, CmosReg::YEAR, year, bcd, hour24);
1379                set_rtc_data(&mut rtc, CmosReg::MONTH, month, bcd, hour24);
1380                set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day, bcd, hour24);
1381                set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1382                set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1383                set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1384                time.tick(Duration::from_secs(2));
1385                assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1386                assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1387                assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1388                assert_eq!(
1389                    get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1390                    1
1391                );
1392                assert_eq!(
1393                    get_rtc_data(&mut rtc, CmosReg::MONTH, bcd, hour24),
1394                    if month == 12 { 1 } else { month + 1 }
1395                );
1396                assert_eq!(
1397                    get_rtc_data(&mut rtc, CmosReg::YEAR, bcd, hour24),
1398                    if month < 12 { year } else { year + 1 }
1399                );
1400                assert_eq!(get_rtc_data(&mut rtc, century_reg, bcd, hour24), century);
1401                year_check_count -= 1;
1402            }
1403        }
1404    }
1405
1406    #[test]
1407    fn test_month() {
1408        println!("----Testing BCD mode...");
1409        test_day_of_month(true, true);
1410        println!("----Testing Binary mode...");
1411        test_day_of_month(false, true);
1412    }
1413
1414    fn test_day_of_week(bcd: bool, hour24: bool) {
1415        let century_reg_idx = 0x32;
1416        let century_reg = CmosReg(century_reg_idx);
1417
1418        let (_, _, time, mut rtc) = new_test_rtc();
1419
1420        if bcd {
1421            set_bcd(&mut rtc);
1422        } else {
1423            set_binary(&mut rtc);
1424        }
1425
1426        if hour24 {
1427            set_24hour(&mut rtc);
1428        } else {
1429            set_12hour(&mut rtc);
1430        }
1431
1432        let century = 30;
1433        let year = 5;
1434        let month = 1;
1435        let day = 5;
1436
1437        println!(
1438            "----Testing {:02}{:02}-{:02}-{:02}",
1439            century, year, month, day
1440        );
1441        set_rtc_data(&mut rtc, century_reg, century, bcd, hour24);
1442        set_rtc_data(&mut rtc, CmosReg::YEAR, year, bcd, hour24);
1443        set_rtc_data(&mut rtc, CmosReg::MONTH, month, bcd, hour24);
1444        set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day, bcd, hour24);
1445        set_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, 7, bcd, hour24);
1446        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1447        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1448        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1449        time.tick(Duration::from_secs(2));
1450        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1451        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1452        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1453        assert_eq!(get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24), 1);
1454
1455        set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day - 1, bcd, hour24);
1456        set_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, 6, bcd, hour24);
1457        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1458        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1459        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1460        time.tick(Duration::from_secs(2));
1461        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1462        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1463        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1464        assert_eq!(get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24), 7);
1465    }
1466
1467    #[test]
1468    fn test_week() {
1469        println!("----Testing Binary mode...");
1470        test_day_of_week(false, true);
1471        println!("----Testing BCD mode...");
1472        test_day_of_week(true, true);
1473    }
1474
1475    #[test]
1476    fn test_alarm() {
1477        fn hms_to_duration(h: u8, m: u8, s: u8) -> Duration {
1478            Duration::from_secs(s as u64 + 60 * m as u64 + h as u64 * 60 * 60)
1479        }
1480
1481        let (_, _, _time, mut rtc) = new_test_rtc();
1482
1483        set_binary(&mut rtc);
1484
1485        set_cmos_data(&mut rtc, CmosReg::HOUR, 2);
1486        set_cmos_data(&mut rtc, CmosReg::MINUTE, 2);
1487        set_cmos_data(&mut rtc, CmosReg::SECOND, 2);
1488
1489        set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, 3);
1490        set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 3);
1491        set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 3);
1492
1493        assert_eq!(rtc.calculate_alarm_duration(), hms_to_duration(1, 1, 1));
1494        set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, 0xff);
1495        set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 0xff);
1496        set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 0xff);
1497        assert_eq!(rtc.calculate_alarm_duration(), hms_to_duration(0, 0, 1));
1498
1499        // TODO: test some more alarm scenarios
1500    }
1501}