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