Skip to main content

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.sync_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 mut hour_ampm = from_bcd(hour & 0x7F);
268            if hour_ampm == 12 {
269                hour_ampm = 0;
270            }
271            if hour & 0x80 != 0 {
272                hour = hour_ampm + 12;
273            } else {
274                hour = hour_ampm
275            }
276        }
277    } else {
278        if !status_b.h24_mode() {
279            let mut hour_ampm = hour & 0x7F;
280            if hour_ampm == 12 {
281                hour_ampm = 0;
282            }
283            hour = hour_ampm + if hour & 0x80 != 0 { 12 } else { 0 };
284        }
285    }
286
287    (hour, min, sec)
288}
289
290impl Rtc {
291    /// Create a new CMOS RTC device
292    ///
293    /// `century_reg_idx` sets which byte of CMOS RAM to use as the century
294    /// byte. This index is not standard between different platforms. e.g: on
295    /// modern x86 platforms, the presence / index of the century register is
296    /// determined by an entry in the FADT ACPI table.
297    ///
298    /// If `enlightened_interrupts`, then whenever a timer expires and the
299    /// interrupt line would be set, pulse the interrupt line low then high to
300    /// ensure an interrupt is delivered. This is used by older Windows guests
301    /// to allow them to skip an extra PIO exit to clear the status C register.
302    /// This behavior is indicated to the guest via an appropriate flag is set
303    /// in the WAET ACPI table. See [Windows ACPI Emulated Devices Table][].
304    ///
305    /// [Windows ACPI Emulated Devices Table]:
306    ///     <https://download.microsoft.com/download/7/E/7/7E7662CF-CBEA-470B-A97E-CE7CE0D98DC2/WAET.docx>
307    pub fn new(
308        real_time_source: Box<dyn InspectableLocalClock>,
309        interrupt: LineInterrupt,
310        vmtime_source: &VmTimeSource,
311        century_reg_idx: u8,
312        initial_cmos: Option<[u8; 256]>,
313        enlightened_interrupts: bool,
314    ) -> Self {
315        Rtc {
316            century_reg: CmosReg(century_reg_idx),
317            initial_cmos,
318            enlightened_interrupts,
319
320            real_time_source,
321            interrupt,
322            vmtime_alarm: vmtime_source.access("rtc-alarm"),
323            vmtimer_periodic: VmTimerPeriodic::new(vmtime_source.access("rtc-periodic")),
324            vmtimer_update: VmTimerPeriodic::new(vmtime_source.access("rtc-update")),
325
326            last_update_bit_blip: LocalClockTime::from_millis_since_unix_epoch(0),
327
328            state: RtcState::new(initial_cmos),
329        }
330    }
331
332    /// (for use by wrapper devices) reference to the raw underlying CMOS data
333    pub fn raw_cmos(&mut self) -> &mut [u8; 256] {
334        &mut self.state.cmos.0
335    }
336
337    /// Calculate the duration to the next strictly future matching alarm time.
338    /// Raw alarm fields in 0xC0..=0xFF are wildcards; all other fields must
339    /// match. Invalid decoded alarm fields are retried after one day.
340    fn calculate_alarm_duration(&self) -> Duration {
341        use self::spec::ALARM_WILDCARD;
342
343        const SECONDS_PER_MINUTE: u32 = 60;
344        const MINUTES_PER_HOUR: u32 = 60;
345        const HOURS_PER_DAY: u32 = 24;
346        const SECONDS_PER_HOUR: u32 = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
347        const SECONDS_PER_DAY: u32 = SECONDS_PER_HOUR * HOURS_PER_DAY;
348
349        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
350        let (now_hour, now_min, now_sec) = canonical_hms(
351            status_b,
352            self.state.cmos[CmosReg::HOUR],
353            self.state.cmos[CmosReg::MINUTE],
354            self.state.cmos[CmosReg::SECOND],
355        );
356        let [
357            (hour_wildcard, hour),
358            (min_wildcard, min),
359            (sec_wildcard, sec),
360        ] = [
361            self.state.cmos[CmosReg::HOUR_ALARM],
362            self.state.cmos[CmosReg::MINUTE_ALARM],
363            self.state.cmos[CmosReg::SECOND_ALARM],
364        ]
365        .map(|value| {
366            if value & 0xC0 == 0xC0 {
367                (true, 0)
368            } else {
369                (false, value)
370            }
371        });
372        let (hour, min, sec) = canonical_hms(status_b, hour, min, sec);
373        let (alarm_hour, alarm_min, alarm_sec) = (
374            if hour_wildcard { ALARM_WILDCARD } else { hour },
375            if min_wildcard { ALARM_WILDCARD } else { min },
376            if sec_wildcard { ALARM_WILDCARD } else { sec },
377        );
378
379        let fields = [
380            (alarm_sec, 1, SECONDS_PER_MINUTE),
381            (alarm_min, SECONDS_PER_MINUTE, MINUTES_PER_HOUR),
382            (alarm_hour, SECONDS_PER_HOUR, HOURS_PER_DAY),
383        ];
384        if fields
385            .iter()
386            .any(|&(value, _, radix)| value != ALARM_WILDCARD && u32::from(value) >= radix)
387        {
388            return Duration::from_secs(u64::from(SECONDS_PER_DAY));
389        }
390
391        let now_seconds = (u32::from(now_hour) * SECONDS_PER_HOUR
392            + u32::from(now_min) * SECONDS_PER_MINUTE
393            + u32::from(now_sec))
394            % SECONDS_PER_DAY;
395        let mut next_seconds = now_seconds + 1;
396        let mut lower_fields = 0;
397        for (value, unit, radix) in fields {
398            if value == ALARM_WILDCARD {
399                continue;
400            }
401            let value = u32::from(value);
402            let current = (next_seconds / unit) % radix;
403            let advance = (value + radix - current) % radix;
404            if advance != 0 {
405                next_seconds = (next_seconds / unit + advance) * unit + lower_fields;
406            }
407            lower_fields += value * unit;
408        }
409        let alarm_duration = Duration::from_secs(u64::from(next_seconds - now_seconds));
410
411        tracing::debug!(
412            now = ?(now_hour, now_min, now_sec),
413            alarm = ?(alarm_hour, alarm_min, alarm_sec),
414            ?alarm_duration,
415            "setting alarm"
416        );
417
418        alarm_duration
419    }
420
421    fn set_alarm_timer(&mut self, now: VmTime) {
422        self.sync_clock_to_cmos();
423        let alarm_duration = self.calculate_alarm_duration();
424
425        self.vmtime_alarm
426            .set_timeout(now.wrapping_add(alarm_duration));
427    }
428
429    fn on_alarm_timer(&mut self, now: VmTime) {
430        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
431        self.state.cmos[CmosReg::STATUS_C] = status_c.with_irq_alarm(true).into();
432
433        self.signal_interrupt();
434
435        // re-arm the alarm timer
436        self.set_alarm_timer(now)
437    }
438
439    fn set_periodic_timer(&mut self) {
440        use self::spec::PERIODIC_TIMER_RATE_HZ;
441
442        let status_a = StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]);
443
444        // 0b0000 means the periodic timer is off
445        if status_a.periodic_timer_rate() == 0 {
446            return;
447        }
448
449        let tick_hz = PERIODIC_TIMER_RATE_HZ[status_a.periodic_timer_rate() as usize - 1];
450        let tick_period = Duration::from_secs_f32(1. / tick_hz as f32);
451
452        tracing::debug!(
453            periodic_timer_rate = ?status_a.periodic_timer_rate(),
454            ?tick_hz,
455            ?tick_period,
456            "setting periodic timer"
457        );
458
459        self.vmtimer_periodic.start(tick_period);
460    }
461
462    fn on_periodic_timer(&mut self) {
463        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
464        self.state.cmos[CmosReg::STATUS_C] = status_c.with_irq_periodic(true).into();
465
466        self.signal_interrupt();
467    }
468
469    fn set_update_timer(&mut self) {
470        self.vmtimer_update.start(Duration::from_secs(1));
471    }
472
473    fn on_update_timer(&mut self) {
474        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
475        self.state.cmos[CmosReg::STATUS_C] = status_c.with_irq_update(true).into();
476
477        self.signal_interrupt();
478    }
479
480    /// Signals an interrupt event, forcing an edge for enlightened interrupts.
481    fn signal_interrupt(&mut self) {
482        let irq_combined = self.update_irqf();
483        if self.enlightened_interrupts && irq_combined {
484            self.interrupt.set_level(false);
485        }
486        self.interrupt.set_level(irq_combined);
487    }
488
489    fn update_irqf(&mut self) -> bool {
490        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
491        let status_c = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
492        let irq_combined = (status_c.irq_update() && status_b.irq_enable_update())
493            || (status_c.irq_periodic() && status_b.irq_enable_periodic())
494            || (status_c.irq_alarm() && status_b.irq_enable_alarm());
495        self.state.cmos[CmosReg::STATUS_C] = status_c.with_irq_combined(irq_combined).into();
496        irq_combined
497    }
498
499    /// Recomputes interrupt status and publishes its level without forcing an edge.
500    fn sync_interrupt_line_level(&mut self) {
501        let irq_combined = self.update_irqf();
502        self.interrupt.set_level(irq_combined);
503    }
504
505    /// Synchronize vmtime-backed timers with current timer / alarm register
506    /// configuration.
507    fn update_timers(&mut self) {
508        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
509
510        if status_b.irq_enable_alarm() {
511            if self.vmtime_alarm.get_timeout().is_none() {
512                self.set_alarm_timer(self.vmtime_alarm.now())
513            }
514        } else {
515            self.vmtime_alarm.cancel_timeout();
516        }
517
518        if status_b.irq_enable_periodic() {
519            if !self.vmtimer_periodic.is_running() {
520                self.set_periodic_timer()
521            }
522        } else {
523            self.vmtimer_periodic.cancel();
524        }
525
526        if status_b.irq_enable_update() {
527            if !self.vmtimer_update.is_running() {
528                self.set_update_timer()
529            }
530        } else {
531            self.vmtimer_update.cancel();
532        }
533    }
534
535    /// Directly write a byte in the CMOS.
536    ///
537    /// This method is marked `pub` to in order to implement wrapper devices
538    /// that inject platform-specific CMOS memory contents. e.g: the AMI RTC
539    /// device.
540    pub fn set_cmos_byte(&mut self, addr: u8, data: u8) {
541        let addr = CmosReg(addr);
542
543        tracing::trace!(?addr, ?data, "set_cmos_byte");
544
545        if (CmosReg::STATUS_A..=CmosReg::STATUS_D).contains(&addr) {
546            self.set_status_byte(addr, data);
547        } else {
548            let old_data = self.state.cmos[addr];
549
550            if data != old_data {
551                // make sure the cmos time is up-to-date
552                if addr.depends_on_rtc(self.century_reg) {
553                    self.sync_clock_to_cmos();
554                }
555
556                self.state.cmos[addr] = data;
557
558                // update the skew after setting the time
559                if addr.depends_on_rtc(self.century_reg) {
560                    self.sync_cmos_to_clock();
561                }
562
563                if addr.depends_on_alarm() || addr.depends_on_rtc(self.century_reg) {
564                    if self.vmtime_alarm.get_timeout().is_some() {
565                        self.set_alarm_timer(self.vmtime_alarm.now());
566                    }
567                }
568            }
569        }
570    }
571
572    /// Directly read a byte in the CMOS.
573    ///
574    /// This method is marked `pub` to in order to implement wrapper devices
575    /// that inject platform-specific CMOS memory contents. e.g: the AMI RTC
576    /// device.
577    pub fn get_cmos_byte(&mut self, addr: u8) -> u8 {
578        let addr = CmosReg(addr);
579
580        let data = if (CmosReg::STATUS_A..=CmosReg::STATUS_D).contains(&addr) {
581            self.get_status_byte(addr)
582        } else {
583            if addr.depends_on_rtc(self.century_reg) {
584                self.sync_clock_to_cmos();
585            }
586
587            self.state.cmos[addr]
588        };
589
590        tracing::trace!(?addr, ?data, "get_cmos_byte");
591
592        data
593    }
594
595    fn set_status_byte(&mut self, addr: CmosReg, data: u8) {
596        match addr {
597            CmosReg::STATUS_A => {
598                let new_reg = StatusRegA::from(data);
599                let old_reg = StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]);
600
601                // Determine if the oscillator is being programmed
602                if new_reg.oscillator_control() != old_reg.oscillator_control() {
603                    // need to re-prime alarm timer
604                    self.vmtime_alarm.cancel_timeout();
605                }
606
607                if new_reg.periodic_timer_rate() != old_reg.periodic_timer_rate() {
608                    // need to re-prime the periodic timer
609                    self.vmtimer_periodic.cancel();
610                }
611
612                // update bit is read-only
613                self.state.cmos[CmosReg::STATUS_A] = data & 0x7F;
614
615                self.update_timers();
616            }
617            CmosReg::STATUS_B => {
618                let mut new_reg = StatusRegB::from(data);
619
620                // When updates are disabled, update interrupts are also disabled
621                if new_reg.set() {
622                    tracing::debug!("disable timer update and interrupt");
623                    new_reg.set_irq_enable_update(false)
624                }
625
626                self.state.cmos[CmosReg::STATUS_B] = new_reg.into();
627
628                self.update_timers();
629                self.sync_interrupt_line_level();
630            }
631            // The AMI BIOS, in all its great wisdom, writes to these read-only registers.
632            // We'll just silently allow that to happen...
633            CmosReg::STATUS_C | CmosReg::STATUS_D => {}
634            _ => unreachable!("passed invalid status reg"),
635        }
636    }
637
638    fn get_status_byte(&mut self, addr: CmosReg) -> u8 {
639        match addr {
640            CmosReg::STATUS_A => {
641                let mut data = StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]);
642
643                // The high bit of status A indicates a time update is in progress.
644                // On real HW, this bit blips to 1 for a brief interval each second.
645                // Guest OSes tend to use this brief blip once per second to calibrate
646                // the rate of other timers such as the TSC.  Guest OSes tend to spin
647                // wait for a rising or falling transition of the bit (typically rising edge),
648                // and then the guest OS will wait for another of the same transition.
649                if !StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]).set() {
650                    let now = self.real_time_source.get_time();
651                    let elapsed = now - self.last_update_bit_blip;
652
653                    // check if the programmed time jumped backwards
654                    if elapsed.as_millis().is_negative() {
655                        tracing::debug!("clock jumped backwards between update bit blips");
656                        self.last_update_bit_blip = LocalClockTime::from_millis_since_unix_epoch(0);
657                    }
658
659                    tracing::trace!(
660                        ?self.last_update_bit_blip,
661                        ?now,
662                        ?elapsed,
663                        "get_status_byte"
664                    );
665
666                    let elapsed_millis = elapsed.as_millis();
667                    if elapsed_millis > 1000 {
668                        // Update the date/time and note that we set the update bit now.
669                        data.set_update(true);
670                        self.sync_clock_to_cmos();
671                        self.last_update_bit_blip = now;
672                        tracing::trace!(
673                            ?data,
674                            ?elapsed,
675                            cmos_date_time = ?self.read_cmos_date_time(),
676                            "blip'd status a update bit"
677                        );
678                    }
679                }
680
681                data.into()
682            }
683            CmosReg::STATUS_B => self.state.cmos[CmosReg::STATUS_B],
684            CmosReg::STATUS_C => {
685                let data = StatusRegC::from(self.state.cmos[CmosReg::STATUS_C]);
686
687                // clear pending interrupt flags.
688                tracing::debug!("clearing rtc interrupts");
689                self.state.cmos[CmosReg::STATUS_C] = StatusRegC::new().into();
690                self.sync_interrupt_line_level();
691
692                data.into()
693            }
694            CmosReg::STATUS_D => {
695                // always report valid ram time
696                StatusRegD::new().with_vrt(true).into()
697            }
698            _ => unreachable!("passed invalid status reg"),
699        }
700    }
701
702    fn read_cmos_date_time(&self) -> Result<jiff::civil::DateTime, jiff::Error> {
703        let mut sec = self.state.cmos[CmosReg::SECOND];
704        let mut min = self.state.cmos[CmosReg::MINUTE];
705        let mut hour = self.state.cmos[CmosReg::HOUR];
706        let mut day = self.state.cmos[CmosReg::DAY_OF_MONTH];
707        let mut month = self.state.cmos[CmosReg::MONTH];
708        let mut year = self.state.cmos[CmosReg::YEAR];
709        let mut century = self.state.cmos[self.century_reg];
710
711        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
712
713        // factor in BCD, 24h time
714        (hour, min, sec) = canonical_hms(status_b, hour, min, sec);
715        if !status_b.disable_bcd() {
716            (day, month, year, century) = (
717                from_bcd(day),
718                from_bcd(month),
719                from_bcd(year),
720                from_bcd(century),
721            );
722        }
723
724        jiff::civil::DateTime::new(
725            year as i16 + century as i16 * 100,
726            month as i8,
727            day as i8,
728            hour as i8,
729            min as i8,
730            sec as i8,
731            0,
732        )
733    }
734
735    /// Update the CMOS RTC registers with the current time from the backing
736    /// `real_time_source`.
737    fn sync_clock_to_cmos(&mut self) {
738        if StatusRegA::from(self.state.cmos[CmosReg::STATUS_A]).oscillator_control()
739            != ENABLE_OSCILLATOR_CONTROL
740        {
741            // Oscillator is disabled
742            tracing::trace!(
743                cmos_reg_status_a = self.state.cmos[CmosReg::STATUS_A],
744                "sync_clock_to_cmos: Oscillator is disabled."
745            );
746
747            return;
748        }
749
750        let real_time = self.real_time_source.get_time();
751        let Ok(clock_time): Result<jiff::Timestamp, _> = real_time.try_into() else {
752            tracelimit::warn_ratelimited!(
753                ?real_time,
754                "invalid date/time in real_time_source, skipping sync"
755            );
756            return;
757        };
758
759        let clock_time = clock_time.to_zoned(jiff::tz::TimeZone::UTC).datetime();
760
761        let status_b = StatusRegB::from(self.state.cmos[CmosReg::STATUS_B]);
762
763        self.state.cmos[CmosReg::SECOND] = clock_time.second() as u8;
764        self.state.cmos[CmosReg::MINUTE] = clock_time.minute() as u8;
765        self.state.cmos[CmosReg::HOUR] = {
766            let hour = clock_time.hour() as u8;
767            if status_b.h24_mode() {
768                hour
769            } else {
770                let hour_ampm = hour % 12;
771                if hour_ampm == 0 { 12 } else { hour_ampm }
772            }
773        };
774        self.state.cmos[CmosReg::DAY_OF_WEEK] =
775            clock_time.date().weekday().to_sunday_one_offset() as u8;
776        self.state.cmos[CmosReg::DAY_OF_MONTH] = clock_time.day() as u8;
777        self.state.cmos[CmosReg::MONTH] = clock_time.month() as u8;
778        self.state.cmos[CmosReg::YEAR] = (clock_time.year() % 100) as u8;
779        self.state.cmos[self.century_reg] = (clock_time.year() / 100) as u8;
780
781        if !status_b.disable_bcd() {
782            let regs = [
783                CmosReg::SECOND,
784                CmosReg::MINUTE,
785                CmosReg::HOUR,
786                CmosReg::DAY_OF_WEEK,
787                CmosReg::DAY_OF_MONTH,
788                CmosReg::MONTH,
789                CmosReg::YEAR,
790                self.century_reg,
791            ];
792
793            for reg in regs {
794                self.state.cmos[reg] = to_bcd(self.state.cmos[reg])
795            }
796        }
797
798        if !status_b.h24_mode() {
799            if clock_time.hour() >= 12 {
800                self.state.cmos[CmosReg::HOUR] |= 0x80;
801            }
802        }
803
804        tracing::trace!(
805            cmos_reg_status_b = self.state.cmos[CmosReg::STATUS_B],
806            use_bcd_encoding = ?!status_b.disable_bcd(),
807            use_24h_time = ?status_b.h24_mode(),
808            cmos_date_time = ?self.read_cmos_date_time(),
809            "sync_clock_to_cmos"
810        );
811    }
812
813    /// Write-back the current contents of the CMOS RTC registers into the
814    /// backing `real_time_source`
815    fn sync_cmos_to_clock(&mut self) {
816        let cmos_time: jiff::Timestamp = match self
817            .read_cmos_date_time()
818            .and_then(|cmos_time| cmos_time.to_zoned(jiff::tz::TimeZone::UTC))
819        {
820            Ok(zoned) => zoned.timestamp(),
821            Err(e) => {
822                tracelimit::warn_ratelimited!(?e, "invalid date/time in RTC registers!");
823                return;
824            }
825        };
826        self.real_time_source.set_time(cmos_time.into());
827    }
828
829    #[cfg(test)]
830    fn get_cmos_date_time(&mut self) -> Result<jiff::civil::DateTime, jiff::Error> {
831        self.sync_clock_to_cmos();
832        self.read_cmos_date_time()
833    }
834}
835
836impl PortIoIntercept for Rtc {
837    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
838        // We assume all accesses are one byte in size. Attempts to
839        // access larger sizes will return a single byte of information
840        // (zero-extended to the size of the access).
841        data[0] = match RtcIoPort(io_port) {
842            RtcIoPort::ADDR => self.state.addr,
843            RtcIoPort::DATA => self.get_cmos_byte(self.state.addr),
844            _ => return IoResult::Err(IoError::InvalidRegister),
845        };
846
847        IoResult::Ok
848    }
849
850    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
851        // We assume all accesses are one byte in size. Attempts to
852        // access larger sizes will return a single byte of information
853        // (zero-extended to the size of the access).
854        match RtcIoPort(io_port) {
855            RtcIoPort::ADDR => {
856                if data[0] & 0x7F != data[0] {
857                    tracing::debug!("guest tried to set high-bit in CMOS addr register")
858                }
859
860                self.state.addr = data[0] & 0x7F
861            }
862            RtcIoPort::DATA => self.set_cmos_byte(self.state.addr, data[0]),
863            _ => return IoResult::Err(IoError::InvalidRegister),
864        }
865
866        IoResult::Ok
867    }
868
869    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
870        &[("io", (RtcIoPort::ADDR.0)..=(RtcIoPort::DATA.0))]
871    }
872}
873
874impl PollDevice for Rtc {
875    fn poll_device(&mut self, cx: &mut std::task::Context<'_>) {
876        while let Poll::Ready(now) = self.vmtime_alarm.poll_timeout(cx) {
877            self.on_alarm_timer(now)
878        }
879
880        if let Poll::Ready(_now) = self.vmtimer_periodic.poll_timeout(cx) {
881            self.on_periodic_timer()
882        }
883
884        if let Poll::Ready(_now) = self.vmtimer_update.poll_timeout(cx) {
885            self.on_update_timer()
886        }
887    }
888}
889
890mod save_restore {
891    use super::*;
892    use vmcore::save_restore::RestoreError;
893    use vmcore::save_restore::SaveError;
894    use vmcore::save_restore::SaveRestore;
895
896    mod state {
897        use mesh::payload::Protobuf;
898        use vmcore::save_restore::SavedStateRoot;
899
900        /// RTC saved state.
901        #[derive(Protobuf, SavedStateRoot)]
902        #[mesh(package = "chipset.cmos_rtc")]
903        pub struct SavedState {
904            #[mesh(1)]
905            pub addr: u8,
906            #[mesh(2)]
907            pub cmos: [u8; 256],
908        }
909    }
910
911    impl SaveRestore for Rtc {
912        type SavedState = state::SavedState;
913
914        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
915            let RtcState { addr, ref cmos } = self.state;
916
917            let saved_state = state::SavedState { addr, cmos: cmos.0 };
918
919            Ok(saved_state)
920        }
921
922        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
923            let state::SavedState { addr, cmos } = state;
924
925            self.state = RtcState {
926                addr,
927                cmos: CmosData(cmos),
928            };
929
930            self.update_timers();
931            self.sync_interrupt_line_level();
932
933            Ok(())
934        }
935    }
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use local_clock::MockLocalClock;
942    use local_clock::MockLocalClockAccessor;
943    use parking_lot::Mutex;
944    use std::sync::Arc;
945    use test_with_tracing::test;
946    use vmcore::line_interrupt::LineSetTarget;
947
948    #[derive(Default)]
949    struct RecordingTarget(Mutex<Vec<bool>>);
950
951    impl LineSetTarget for RecordingTarget {
952        fn set_irq(&self, _: u32, high: bool) {
953            self.0.lock().push(high);
954        }
955    }
956
957    #[test]
958    fn test_interrupt_masking() {
959        for enlightened in [false, true] {
960            for (source, other_source) in [(0x10, 0x20), (0x20, 0x40), (0x40, 0x10)] {
961                let (_, _, _, mut rtc) = new_test_rtc();
962                rtc.enlightened_interrupts = enlightened;
963                set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06 | source);
964                let target = Arc::new(RecordingTarget::default());
965                rtc.interrupt = LineInterrupt::new_with_target("rtc", target.clone(), 0);
966                target.0.lock().clear();
967
968                let fire = |rtc: &mut Rtc| match source {
969                    0x10 => rtc.on_update_timer(),
970                    0x20 => rtc.on_alarm_timer(VmTime::from_100ns(0)),
971                    0x40 => rtc.on_periodic_timer(),
972                    _ => unreachable!(),
973                };
974                fire(&mut rtc);
975                assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], source | 0x80);
976                assert_eq!(target.0.lock().last(), Some(&true));
977
978                target.0.lock().clear();
979                fire(&mut rtc);
980                if enlightened {
981                    assert_eq!(*target.0.lock(), [false, true]);
982                } else {
983                    assert!(!target.0.lock().contains(&false));
984                }
985
986                target.0.lock().clear();
987                set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06);
988                assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], source);
989                assert_eq!(*target.0.lock(), [false]);
990
991                target.0.lock().clear();
992                set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06 | other_source);
993                assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], source);
994                assert!(!target.0.lock().contains(&true));
995
996                set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06 | source);
997                assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], source | 0x80);
998                assert_eq!(target.0.lock().last(), Some(&true));
999
1000                target.0.lock().clear();
1001                set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06 | source);
1002                assert!(!target.0.lock().contains(&false));
1003                assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), source | 0x80);
1004                assert_eq!(target.0.lock().last(), Some(&false));
1005                assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), 0);
1006
1007                target.0.lock().clear();
1008                set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06 | other_source);
1009                fire(&mut rtc);
1010                assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], source);
1011                assert!(!target.0.lock().contains(&true));
1012                assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), source);
1013                assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), 0);
1014            }
1015        }
1016    }
1017
1018    #[test]
1019    fn test_interrupt_mixed_sources() {
1020        for enlightened in [false, true] {
1021            let (_, _, _, mut rtc) = new_test_rtc();
1022            rtc.enlightened_interrupts = enlightened;
1023            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x76);
1024            let target = Arc::new(RecordingTarget::default());
1025            rtc.interrupt = LineInterrupt::new_with_target("rtc", target.clone(), 0);
1026            rtc.on_periodic_timer();
1027            rtc.on_update_timer();
1028            assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], 0xD0);
1029
1030            target.0.lock().clear();
1031            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x26);
1032            assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], 0x50);
1033            assert_eq!(*target.0.lock(), [false]);
1034
1035            target.0.lock().clear();
1036            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x16);
1037            assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], 0xD0);
1038            assert_eq!(*target.0.lock(), [true]);
1039
1040            target.0.lock().clear();
1041            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x46);
1042            assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], 0xD0);
1043            assert!(!target.0.lock().contains(&false));
1044            assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), 0xD0);
1045            assert_eq!(target.0.lock().last(), Some(&false));
1046            assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), 0);
1047        }
1048    }
1049
1050    #[test]
1051    fn restore_masked_pending_flag_clears_stale_irqf() {
1052        use vmcore::save_restore::SaveRestore;
1053
1054        for enlightened in [false, true] {
1055            let (mut pool, _, _, mut rtc) = new_test_rtc();
1056            rtc.enlightened_interrupts = enlightened;
1057            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x46);
1058            rtc.on_periodic_timer();
1059            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x06);
1060            let mut state = SaveRestore::save(&mut rtc).unwrap();
1061            state.cmos[usize::from(CmosReg::STATUS_C.0)] = 0xC0;
1062            pool.run_until(rtc.reset());
1063
1064            let target = Arc::new(RecordingTarget::default());
1065            rtc.interrupt = LineInterrupt::new_with_target("rtc", target.clone(), 0);
1066            rtc.interrupt.set_level(true);
1067            target.0.lock().clear();
1068            SaveRestore::restore(&mut rtc, state).unwrap();
1069            assert_eq!(rtc.state.cmos[CmosReg::STATUS_C], 0x40);
1070            assert_eq!(*target.0.lock(), [false]);
1071
1072            target.0.lock().clear();
1073            set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x46);
1074            assert_eq!(*target.0.lock(), [true]);
1075            assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), 0xC0);
1076            assert_eq!(get_cmos_data(&mut rtc, CmosReg::STATUS_C), 0);
1077        }
1078    }
1079
1080    #[test]
1081    fn restore_enlightened_rtc_publishes_level_without_a_pulse() {
1082        use vmcore::save_restore::SaveRestore;
1083
1084        let (mut pool, _, _, mut rtc) = new_test_rtc();
1085        rtc.enlightened_interrupts = true;
1086        set_cmos_data(&mut rtc, CmosReg::STATUS_B, 0x46);
1087        rtc.on_periodic_timer();
1088        let state = SaveRestore::save(&mut rtc).unwrap();
1089        pool.run_until(rtc.reset());
1090        let target = Arc::new(RecordingTarget::default());
1091        rtc.interrupt = LineInterrupt::new_with_target("rtc", target.clone(), 0);
1092        target.0.lock().clear();
1093        SaveRestore::restore(&mut rtc, state).unwrap();
1094        assert_eq!(*target.0.lock(), [true]);
1095
1096        target.0.lock().clear();
1097        rtc.on_periodic_timer();
1098        assert_eq!(*target.0.lock(), [false, true]);
1099    }
1100
1101    fn new_test_rtc() -> (
1102        pal_async::DefaultPool,
1103        vmcore::vmtime::VmTimeKeeper,
1104        MockLocalClockAccessor,
1105        Rtc,
1106    ) {
1107        let mut pool = pal_async::DefaultPool::new();
1108        let driver = pool.driver();
1109        let vm_time_keeper =
1110            vmcore::vmtime::VmTimeKeeper::new(&pool.driver(), VmTime::from_100ns(0));
1111        let vm_time_source = pool
1112            .run_until(vm_time_keeper.builder().build(&driver))
1113            .unwrap();
1114
1115        let time = MockLocalClock::new();
1116        let time_access = time.accessor();
1117
1118        let rtc = Rtc::new(
1119            Box::new(time),
1120            LineInterrupt::detached(),
1121            &vm_time_source,
1122            0x32,
1123            None,
1124            false,
1125        );
1126
1127        (pool, vm_time_keeper, time_access, rtc)
1128    }
1129
1130    fn get_cmos_data(rtc: &mut Rtc, addr: CmosReg) -> u8 {
1131        let mut temp = [addr.0];
1132        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1133        rtc.io_read(RtcIoPort::DATA.0, &mut temp).unwrap();
1134        temp[0]
1135    }
1136
1137    fn set_cmos_data(rtc: &mut Rtc, addr: CmosReg, data: u8) {
1138        let mut temp = [addr.0];
1139        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1140        temp[0] = data;
1141        rtc.io_write(RtcIoPort::DATA.0, &temp).unwrap();
1142    }
1143
1144    fn get_rtc_data(rtc: &mut Rtc, addr: CmosReg, bcd: bool, hour24: bool) -> u8 {
1145        let mut temp = [addr.0];
1146        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1147        rtc.io_read(RtcIoPort::DATA.0, &mut temp).unwrap();
1148        let mut data = temp[0];
1149        if addr == CmosReg::HOUR {
1150            let pm = if hour24 { false } else { (data & 0x80) != 0 };
1151            if pm {
1152                data &= 0x7f
1153            };
1154            if bcd {
1155                data = from_bcd(data)
1156            };
1157            if !hour24 && data == 12 {
1158                data = 0;
1159            }
1160            if pm {
1161                data += 12;
1162            }
1163        } else {
1164            if bcd {
1165                data = from_bcd(data)
1166            };
1167        }
1168
1169        println!("get {0}({0:#x}) convert to {1}", temp[0], data);
1170        data
1171    }
1172
1173    fn set_rtc_data(rtc: &mut Rtc, addr: CmosReg, data: u8, bcd: bool, hour24: bool) {
1174        let mut temp = [addr.0];
1175        rtc.io_write(RtcIoPort::ADDR.0, &temp).unwrap();
1176        let mut new_data = data;
1177        if addr == CmosReg::HOUR {
1178            let pm = !hour24 && new_data >= 12;
1179            if !hour24 {
1180                new_data %= 12;
1181                if new_data == 0 {
1182                    new_data = 12;
1183                }
1184            }
1185            if bcd {
1186                new_data = to_bcd(new_data);
1187            }
1188            if pm {
1189                new_data |= 0x80;
1190            }
1191        } else {
1192            if bcd {
1193                new_data = to_bcd(new_data)
1194            };
1195        }
1196
1197        println!("set {0} convert to {1}({1:#x})", data, new_data);
1198        temp[0] = new_data;
1199        rtc.io_write(RtcIoPort::DATA.0, &temp).unwrap();
1200    }
1201
1202    // Local support routine.
1203    // Wait for CMOS update strobed rising edge.
1204    fn wait_for_edge(rtc: &mut Rtc, high: bool, time: MockLocalClockAccessor) -> bool {
1205        let limit = 5; //seconds
1206        let stall_ms = 10; //10ms to pause
1207
1208        for _i in 0..(limit * 1000 / stall_ms) {
1209            let value = get_cmos_data(rtc, CmosReg::STATUS_A);
1210            if high {
1211                if value & 0x80 != 0 {
1212                    return true;
1213                }
1214            } else {
1215                if value & 0x80 == 0 {
1216                    return true;
1217                }
1218            }
1219
1220            time.tick(Duration::from_millis(stall_ms));
1221        }
1222
1223        false
1224    }
1225
1226    fn set_bcd(rtc: &mut Rtc) {
1227        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1228        value &= 0xFB;
1229        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1230    }
1231
1232    fn set_binary(rtc: &mut Rtc) {
1233        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1234        value |= 0x4;
1235        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1236    }
1237
1238    fn set_24hour(rtc: &mut Rtc) {
1239        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1240        value |= 0x2;
1241        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1242    }
1243
1244    fn set_12hour(rtc: &mut Rtc) {
1245        let mut value = get_cmos_data(rtc, CmosReg::STATUS_B);
1246        value &= 0xFD;
1247        set_cmos_data(rtc, CmosReg::STATUS_B, value);
1248    }
1249
1250    #[test]
1251    fn test_setup() {
1252        let (_, _, _, _rtc) = new_test_rtc();
1253    }
1254
1255    #[test]
1256    fn test_default() {
1257        let default_state = RtcState::new(None);
1258
1259        let (_, _, _, mut rtc) = new_test_rtc();
1260
1261        let mut data = [0];
1262        rtc.io_read(RtcIoPort::ADDR.0, &mut data).unwrap();
1263        assert_eq!(data[0], 0x80);
1264        assert_eq!(
1265            get_cmos_data(&mut rtc, CmosReg::STATUS_A),
1266            default_state.cmos[CmosReg::STATUS_A] | 0x80
1267        );
1268        assert_eq!(
1269            get_cmos_data(&mut rtc, CmosReg::STATUS_B),
1270            default_state.cmos[CmosReg::STATUS_B]
1271        );
1272        assert_eq!(
1273            get_cmos_data(&mut rtc, CmosReg::STATUS_C),
1274            default_state.cmos[CmosReg::STATUS_C]
1275        );
1276        assert_eq!(
1277            get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1278            default_state.cmos[CmosReg::STATUS_D]
1279        );
1280    }
1281
1282    fn test_time_move(rtc: &mut Rtc, is_move: bool, time: MockLocalClockAccessor) {
1283        if let Ok(before) = rtc.get_cmos_date_time() {
1284            time.tick(Duration::from_secs(2));
1285            if let Ok(after) = rtc.get_cmos_date_time() {
1286                if is_move {
1287                    assert_ne!(before, after);
1288                } else {
1289                    assert_eq!(before, after);
1290                }
1291            } else {
1292                panic!("get_cmos_date_time failed");
1293            }
1294        } else {
1295            panic!("get_cmos_date_time failed");
1296        }
1297    }
1298
1299    #[test]
1300    fn test_oscillator() {
1301        let default_state = RtcState::new(None);
1302
1303        let (_, _, time, mut rtc) = new_test_rtc();
1304
1305        assert_eq!(
1306            get_cmos_data(&mut rtc, CmosReg::STATUS_A),
1307            default_state.cmos[CmosReg::STATUS_A] | 0x80
1308        );
1309
1310        println!("RTC should move forward when oscillator is enabled (default control mask 010)");
1311        test_time_move(&mut rtc, true, time.clone());
1312
1313        // Disable the oscillator
1314        set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x66);
1315        println!("RTC should not move forward when oscillator is disabled (control mask 110)");
1316        test_time_move(&mut rtc, false, time.clone());
1317
1318        // Re-enable the oscillator
1319        set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x26);
1320        println!("RTC should move forward when oscillator is re-enabled (control mask 010)");
1321        test_time_move(&mut rtc, true, time);
1322    }
1323
1324    #[test]
1325    fn test_uip() {
1326        let (_, _, time, mut rtc) = new_test_rtc();
1327
1328        assert!(
1329            wait_for_edge(&mut rtc, false, time.clone())
1330                && wait_for_edge(&mut rtc, true, time.clone())
1331        );
1332        if let Ok(start) = rtc.get_cmos_date_time() {
1333            let seconds_to_wait: i64 = 65;
1334            for _i in 0..seconds_to_wait {
1335                assert!(
1336                    wait_for_edge(&mut rtc, false, time.clone())
1337                        && wait_for_edge(&mut rtc, true, time.clone())
1338                );
1339            }
1340
1341            if let Ok(end) = rtc.get_cmos_date_time() {
1342                let elapsed_secs =
1343                    end.since(start).unwrap().total(jiff::Unit::Second).unwrap() as i64;
1344                let expected_secs = seconds_to_wait;
1345                let allowance_secs: i64 = 1;
1346                println!(
1347                    "Expected: {}s Start: {:?} End: {:?} Elapsed: {}s Allowance: {}s, RTC generates update strobe at expected rate.",
1348                    expected_secs, start, end, elapsed_secs, allowance_secs
1349                );
1350                assert!(
1351                    elapsed_secs <= (expected_secs + allowance_secs)
1352                        && elapsed_secs >= (expected_secs - allowance_secs)
1353                );
1354            } else {
1355                panic!("get_cmos_date_time failed");
1356            }
1357        } else {
1358            panic!("get_cmos_date_time failed");
1359        }
1360    }
1361
1362    #[test]
1363    fn test_readonly() {
1364        let default_state = RtcState::new(None);
1365
1366        let (_, _, _, mut rtc) = new_test_rtc();
1367
1368        assert_eq!(
1369            get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1370            default_state.cmos[CmosReg::STATUS_D]
1371        );
1372
1373        //Status D bits are read-only
1374        for i in 0..=0xFF {
1375            set_cmos_data(&mut rtc, CmosReg::STATUS_D, i);
1376            assert_eq!(
1377                get_cmos_data(&mut rtc, CmosReg::STATUS_D),
1378                default_state.cmos[CmosReg::STATUS_D]
1379            );
1380        }
1381    }
1382
1383    #[test]
1384    fn test_writeable() {
1385        let (_, _, _, mut rtc) = new_test_rtc();
1386
1387        //Registers 0x0f..0x7f should be writable, skip 0x32 which is century field of RTC
1388        for i in (0x0F..=0x7F).map(CmosReg) {
1389            if i == rtc.century_reg {
1390                continue;
1391            }
1392
1393            set_cmos_data(&mut rtc, i, 0xFF);
1394            assert_eq!(get_cmos_data(&mut rtc, i), 0xFF);
1395            set_cmos_data(&mut rtc, i, 0);
1396            assert_eq!(get_cmos_data(&mut rtc, i), 0);
1397        }
1398    }
1399
1400    fn test_count(bcd: bool, hour24: bool) {
1401        let (_, _, time, mut rtc) = new_test_rtc();
1402
1403        if bcd {
1404            set_bcd(&mut rtc);
1405        } else {
1406            set_binary(&mut rtc);
1407        }
1408
1409        if hour24 {
1410            set_24hour(&mut rtc);
1411        } else {
1412            set_12hour(&mut rtc);
1413        }
1414
1415        let init_ts: jiff::Timestamp = time.get_time().try_into().unwrap();
1416        let init = init_ts.to_zoned(jiff::tz::TimeZone::UTC).datetime();
1417        println!("init: {:?}", init);
1418        set_rtc_data(&mut rtc, CmosReg::HOUR, 11, bcd, hour24);
1419        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1420        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1421        time.tick(Duration::from_secs(2));
1422        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1423        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1424        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 12);
1425        assert_eq!(
1426            get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1427            init.day() as u8
1428        );
1429        assert_eq!(
1430            get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24),
1431            init.date().weekday().to_sunday_one_offset() as u8
1432        );
1433
1434        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1435        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1436        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1437        time.tick(Duration::from_secs(2));
1438        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1439        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1440        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1441        let temp = init_ts
1442            .checked_add(jiff::SignedDuration::from_hours(24))
1443            .unwrap()
1444            .to_zoned(jiff::tz::TimeZone::UTC)
1445            .datetime();
1446        assert_eq!(
1447            get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1448            temp.day() as u8
1449        );
1450        assert_eq!(
1451            get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24),
1452            temp.date().weekday().to_sunday_one_offset() as u8
1453        );
1454
1455        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1456        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1457        time.tick(Duration::from_secs(2));
1458        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1459        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1460        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 1);
1461    }
1462
1463    #[test]
1464    fn test_bcd_binary() {
1465        println!("----Testing BCD mode...");
1466        test_count(true, true);
1467        println!("----Testing Binary mode...");
1468        test_count(false, true);
1469    }
1470
1471    #[test]
1472    fn test_hour_encodings() {
1473        let (_, _, _, mut rtc) = new_test_rtc();
1474        let hours = [
1475            (0, 0x12, 0x0C),
1476            (1, 0x01, 0x01),
1477            (2, 0x02, 0x02),
1478            (3, 0x03, 0x03),
1479            (4, 0x04, 0x04),
1480            (5, 0x05, 0x05),
1481            (6, 0x06, 0x06),
1482            (7, 0x07, 0x07),
1483            (8, 0x08, 0x08),
1484            (9, 0x09, 0x09),
1485            (10, 0x10, 0x0A),
1486            (11, 0x11, 0x0B),
1487            (12, 0x92, 0x8C),
1488            (13, 0x81, 0x81),
1489            (14, 0x82, 0x82),
1490            (15, 0x83, 0x83),
1491            (16, 0x84, 0x84),
1492            (17, 0x85, 0x85),
1493            (18, 0x86, 0x86),
1494            (19, 0x87, 0x87),
1495            (20, 0x88, 0x88),
1496            (21, 0x89, 0x89),
1497            (22, 0x90, 0x8A),
1498            (23, 0x91, 0x8B),
1499        ];
1500
1501        for status in [0x00, 0x02, 0x04, 0x06] {
1502            set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1503            for (hour, bcd_hour, binary_hour) in hours {
1504                let timestamp: jiff::Timestamp =
1505                    format!("2026-01-01T{hour:02}:00:00Z").parse().unwrap();
1506                rtc.real_time_source.set_time(timestamp.into());
1507                let expected = match status {
1508                    0x00 => bcd_hour,
1509                    0x02 => to_bcd(hour),
1510                    0x04 => binary_hour,
1511                    0x06 => hour,
1512                    _ => unreachable!(),
1513                };
1514                assert_eq!(
1515                    get_cmos_data(&mut rtc, CmosReg::HOUR),
1516                    expected,
1517                    "hour {hour}, status B {status:#04x}"
1518                );
1519            }
1520        }
1521    }
1522
1523    #[test]
1524    fn test_hour_encoding_transitions() {
1525        let (_, _, time, mut rtc) = new_test_rtc();
1526
1527        for (mode, status) in [0x00, 0x02, 0x04, 0x06].into_iter().enumerate() {
1528            set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1529            for (start_hour, expected_hours, expected_day) in [
1530                (11, [0x92, 0x12, 0x8C, 0x0C], 1),
1531                (23, [0x12, 0x00, 0x0C, 0x00], 2),
1532            ] {
1533                let timestamp: jiff::Timestamp = format!("2026-01-01T{start_hour:02}:59:59Z")
1534                    .parse()
1535                    .unwrap();
1536                rtc.real_time_source.set_time(timestamp.into());
1537                time.tick(Duration::from_secs(2));
1538
1539                assert_eq!(
1540                    get_cmos_data(&mut rtc, CmosReg::HOUR),
1541                    expected_hours[mode],
1542                    "start hour {start_hour}, status B {status:#04x}"
1543                );
1544                assert_eq!(get_cmos_data(&mut rtc, CmosReg::DAY_OF_MONTH), expected_day);
1545                assert_eq!(get_cmos_data(&mut rtc, CmosReg::MINUTE), 0);
1546                assert_eq!(get_cmos_data(&mut rtc, CmosReg::SECOND), 1);
1547            }
1548        }
1549    }
1550
1551    #[test]
1552    fn test_hour_inputs() {
1553        let (_, _, time, mut rtc) = new_test_rtc();
1554
1555        for (status, midnight_hour) in [(0x00, 0x12), (0x04, 0x0C)] {
1556            for (pm_bit, expected_hour) in [(0x00, 0), (0x80, 12)] {
1557                set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1558                let timestamp: jiff::Timestamp = "2026-01-01T06:00:00Z".parse().unwrap();
1559                rtc.real_time_source.set_time(timestamp.into());
1560                rtc.sync_clock_to_cmos();
1561                set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x06);
1562                let encoded_hour = midnight_hour | pm_bit;
1563                set_cmos_data(&mut rtc, CmosReg::HOUR, encoded_hour);
1564
1565                let timestamp: jiff::Timestamp = time.get_time().try_into().unwrap();
1566                assert_eq!(
1567                    timestamp.to_zoned(jiff::tz::TimeZone::UTC).hour(),
1568                    expected_hour,
1569                    "hour {encoded_hour:#04x}, status B {status:#04x}"
1570                );
1571
1572                set_cmos_data(&mut rtc, CmosReg::STATUS_A, 0x26);
1573                time.tick(Duration::from_secs(2));
1574                assert_eq!(get_cmos_data(&mut rtc, CmosReg::HOUR), encoded_hour);
1575                assert_eq!(get_cmos_data(&mut rtc, CmosReg::SECOND), 2);
1576                time.tick(Duration::from_secs(3600));
1577                assert_eq!(get_cmos_data(&mut rtc, CmosReg::HOUR), 0x01 | pm_bit);
1578                assert_eq!(get_cmos_data(&mut rtc, CmosReg::DAY_OF_MONTH), 1);
1579            }
1580        }
1581    }
1582
1583    #[test]
1584    fn test_hour_alarms() {
1585        let (_, _, _, mut rtc) = new_test_rtc();
1586
1587        for (mode, status) in [0x00, 0x02, 0x04, 0x06].into_iter().enumerate() {
1588            set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1589            for (start_hour, alarm_hours) in [
1590                (11, [0x92, 0x12, 0x8C, 0x0C]),
1591                (23, [0x12, 0x00, 0x0C, 0x00]),
1592                (12, [0x81, 0x13, 0x81, 0x0D]),
1593                (0, [0x01, 0x01, 0x01, 0x01]),
1594            ] {
1595                let timestamp: jiff::Timestamp = format!("2026-01-01T{start_hour:02}:59:59Z")
1596                    .parse()
1597                    .unwrap();
1598                rtc.real_time_source.set_time(timestamp.into());
1599                rtc.sync_clock_to_cmos();
1600                set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, alarm_hours[mode]);
1601                set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 0);
1602                set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 0);
1603
1604                assert_eq!(
1605                    rtc.calculate_alarm_duration(),
1606                    Duration::from_secs(1),
1607                    "start hour {start_hour}, status B {status:#04x}"
1608                );
1609            }
1610        }
1611    }
1612
1613    #[test]
1614    fn test_hour_mode() {
1615        println!("----Testing Binary + 12 hour mode...");
1616        test_count(false, false);
1617        println!("----Testing Binary + 24 hour mode...");
1618        test_count(false, true);
1619        println!("----Testing BCD + 12 hour mode...");
1620        test_count(true, false);
1621        println!("----Testing BCD + 24 hour mode...");
1622        test_count(true, true);
1623    }
1624
1625    fn test_day_of_month(bcd: bool, hour24: bool) {
1626        let century_reg_idx = 0x32;
1627        let century_reg = CmosReg(century_reg_idx);
1628
1629        let (_, _, time, mut rtc) = new_test_rtc();
1630
1631        if bcd {
1632            set_bcd(&mut rtc);
1633        } else {
1634            set_binary(&mut rtc);
1635        }
1636
1637        if hour24 {
1638            set_24hour(&mut rtc);
1639        } else {
1640            set_12hour(&mut rtc);
1641        }
1642
1643        for month in 1..=12 {
1644            let mut day;
1645            let mut year_check_count = 1;
1646            if month == 4 || month == 6 || month == 9 || month == 11 {
1647                day = 30;
1648            } else if month != 2 {
1649                day = 31;
1650            } else {
1651                day = 0;
1652                year_check_count = 4;
1653            }
1654
1655            while year_check_count > 0 {
1656                let century = 30;
1657                let year = 4 + year_check_count;
1658                if month == 2 {
1659                    day = if (year & 3) > 0 { 28 } else { 29 };
1660                }
1661
1662                println!(
1663                    "----Testing {:02}{:02}-{:02}-{:02}",
1664                    century, year, month, day
1665                );
1666                set_rtc_data(&mut rtc, century_reg, century, bcd, hour24);
1667                set_rtc_data(&mut rtc, CmosReg::YEAR, year, bcd, hour24);
1668                set_rtc_data(&mut rtc, CmosReg::MONTH, month, bcd, hour24);
1669                set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day, bcd, hour24);
1670                set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1671                set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1672                set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1673                time.tick(Duration::from_secs(2));
1674                assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1675                assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1676                assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1677                assert_eq!(
1678                    get_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, bcd, hour24),
1679                    1
1680                );
1681                assert_eq!(
1682                    get_rtc_data(&mut rtc, CmosReg::MONTH, bcd, hour24),
1683                    if month == 12 { 1 } else { month + 1 }
1684                );
1685                assert_eq!(
1686                    get_rtc_data(&mut rtc, CmosReg::YEAR, bcd, hour24),
1687                    if month < 12 { year } else { year + 1 }
1688                );
1689                assert_eq!(get_rtc_data(&mut rtc, century_reg, bcd, hour24), century);
1690                year_check_count -= 1;
1691            }
1692        }
1693    }
1694
1695    #[test]
1696    fn test_month() {
1697        println!("----Testing BCD mode...");
1698        test_day_of_month(true, true);
1699        println!("----Testing Binary mode...");
1700        test_day_of_month(false, true);
1701    }
1702
1703    fn test_day_of_week(bcd: bool, hour24: bool) {
1704        let century_reg_idx = 0x32;
1705        let century_reg = CmosReg(century_reg_idx);
1706
1707        let (_, _, time, mut rtc) = new_test_rtc();
1708
1709        if bcd {
1710            set_bcd(&mut rtc);
1711        } else {
1712            set_binary(&mut rtc);
1713        }
1714
1715        if hour24 {
1716            set_24hour(&mut rtc);
1717        } else {
1718            set_12hour(&mut rtc);
1719        }
1720
1721        let century = 30;
1722        let year = 5;
1723        let month = 1;
1724        let day = 5;
1725
1726        println!(
1727            "----Testing {:02}{:02}-{:02}-{:02}",
1728            century, year, month, day
1729        );
1730        set_rtc_data(&mut rtc, century_reg, century, bcd, hour24);
1731        set_rtc_data(&mut rtc, CmosReg::YEAR, year, bcd, hour24);
1732        set_rtc_data(&mut rtc, CmosReg::MONTH, month, bcd, hour24);
1733        set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day, bcd, hour24);
1734        set_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, 7, bcd, hour24);
1735        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1736        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1737        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1738        time.tick(Duration::from_secs(2));
1739        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1740        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1741        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1742        assert_eq!(get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24), 1);
1743
1744        set_rtc_data(&mut rtc, CmosReg::DAY_OF_MONTH, day - 1, bcd, hour24);
1745        set_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, 6, bcd, hour24);
1746        set_rtc_data(&mut rtc, CmosReg::HOUR, 23, bcd, hour24);
1747        set_rtc_data(&mut rtc, CmosReg::MINUTE, 59, bcd, hour24);
1748        set_rtc_data(&mut rtc, CmosReg::SECOND, 59, bcd, hour24);
1749        time.tick(Duration::from_secs(2));
1750        assert_eq!(get_rtc_data(&mut rtc, CmosReg::SECOND, bcd, hour24), 1);
1751        assert_eq!(get_rtc_data(&mut rtc, CmosReg::MINUTE, bcd, hour24), 0);
1752        assert_eq!(get_rtc_data(&mut rtc, CmosReg::HOUR, bcd, hour24), 0);
1753        assert_eq!(get_rtc_data(&mut rtc, CmosReg::DAY_OF_WEEK, bcd, hour24), 7);
1754    }
1755
1756    #[test]
1757    fn test_week() {
1758        println!("----Testing Binary mode...");
1759        test_day_of_week(false, true);
1760        println!("----Testing BCD mode...");
1761        test_day_of_week(true, true);
1762    }
1763
1764    #[test]
1765    fn test_alarm_next_match() {
1766        let (_, _, _, mut rtc) = new_test_rtc();
1767
1768        for status in [0x00, 0x02, 0x04, 0x06] {
1769            set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1770            for (time, alarm, delays) in [
1771                ("12:30:59", [12, 30, 0], [86341, 86400]),
1772                ("12:30:58", [12, 30, 0xFF], [1, 86341]),
1773                ("12:30:59", [12, 30, 0xFF], [86341, 1]),
1774                ("12:31:00", [12, 30, 0xFF], [86340, 1]),
1775                ("12:59:00", [12, 0xFF, 0], [82860, 60]),
1776                ("12:30:59", [0xFF, 30, 0], [3541, 3600]),
1777                ("12:30:59", [0xFF, 30, 0xFF], [3541, 1]),
1778                ("12:30:59", [0xFF, 0xFF, 0], [1, 60]),
1779                ("12:30:59", [0xFF, 0xFF, 0xFF], [1, 1]),
1780                ("12:59:59", [12, 0xFF, 0xFF], [82801, 1]),
1781                ("12:30:00", [12, 30, 0], [86400, 86400]),
1782                ("23:59:59", [0, 0, 0], [1, 86400]),
1783                ("11:59:59", [12, 0, 0], [1, 86400]),
1784                ("12:59:59", [13, 0, 0], [1, 86400]),
1785                ("12:30:59", [0xFF, 31, 59], [60, 3600]),
1786                ("12:22:33", [14, 0xFF, 0xFF], [5847, 1]),
1787                ("12:22:33", [0xFF, 23, 0xFF], [27, 1]),
1788                ("23:59:59", [23, 59, 59], [86400, 86400]),
1789            ] {
1790                let encode = |value: u8| {
1791                    if value == 0xFF || status & 4 != 0 {
1792                        value
1793                    } else {
1794                        (value / 10) * 16 + value % 10
1795                    }
1796                };
1797                let hour = if alarm[0] == 0xFF || status & 2 != 0 {
1798                    encode(alarm[0])
1799                } else {
1800                    let magnitude = match alarm[0] % 12 {
1801                        0 => 12,
1802                        magnitude => magnitude,
1803                    };
1804                    encode(magnitude) | if alarm[0] >= 12 { 0x80 } else { 0 }
1805                };
1806                set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, hour);
1807                set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, encode(alarm[1]));
1808                set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, encode(alarm[2]));
1809
1810                let mut timestamp: jiff::Timestamp = format!("2026-01-01T{time}Z").parse().unwrap();
1811                for expected_seconds in delays {
1812                    rtc.real_time_source.set_time(timestamp.into());
1813                    rtc.sync_clock_to_cmos();
1814                    assert_eq!(
1815                        rtc.calculate_alarm_duration(),
1816                        Duration::from_secs(expected_seconds),
1817                        "status B {status:#04x}, now {timestamp}, alarm {alarm:?}"
1818                    );
1819                    timestamp = timestamp
1820                        .checked_add(Duration::from_secs(expected_seconds))
1821                        .unwrap();
1822                }
1823            }
1824        }
1825    }
1826
1827    #[test]
1828    fn test_alarm_invalid_fields() {
1829        let (_, _, _, mut rtc) = new_test_rtc();
1830        for status in [0x00, 0x02, 0x04, 0x06] {
1831            set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1832            let timestamp: jiff::Timestamp = "2026-01-01T12:00:00Z".parse().unwrap();
1833            rtc.real_time_source.set_time(timestamp.into());
1834            rtc.sync_clock_to_cmos();
1835            for invalid_field in 0..3 {
1836                for (field, register) in [
1837                    CmosReg::HOUR_ALARM,
1838                    CmosReg::MINUTE_ALARM,
1839                    CmosReg::SECOND_ALARM,
1840                ]
1841                .into_iter()
1842                .enumerate()
1843                {
1844                    set_cmos_data(
1845                        &mut rtc,
1846                        register,
1847                        if field == invalid_field { 0x7F } else { 0xFF },
1848                    );
1849                }
1850                assert_eq!(rtc.calculate_alarm_duration(), Duration::from_hours(24));
1851            }
1852        }
1853    }
1854
1855    #[test]
1856    fn test_alarm_wildcards() {
1857        let (_, _, _, mut rtc) = new_test_rtc();
1858
1859        for (status, hour, minute, second) in [
1860            (0x00, 0x82, 0x01, 0x01),
1861            (0x02, 0x14, 0x01, 0x01),
1862            (0x04, 0x82, 1, 1),
1863            (0x06, 14, 1, 1),
1864        ] {
1865            set_cmos_data(&mut rtc, CmosReg::STATUS_B, status);
1866            let timestamp: jiff::Timestamp = "2026-01-01T13:00:00Z".parse().unwrap();
1867            rtc.real_time_source.set_time(timestamp.into());
1868            rtc.sync_clock_to_cmos();
1869
1870            for wildcard in 0xC0..=0xFF {
1871                for (mask, expected_seconds) in [
1872                    (0, 3661),
1873                    (1, 3660),
1874                    (2, 3601),
1875                    (3, 3600),
1876                    (4, 61),
1877                    (5, 60),
1878                    (6, 1),
1879                    (7, 1),
1880                ] {
1881                    let alarm_hour = if mask & 4 != 0 { wildcard } else { hour };
1882                    let alarm_minute = if mask & 2 != 0 { wildcard } else { minute };
1883                    let alarm_second = if mask & 1 != 0 { wildcard } else { second };
1884                    set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, alarm_hour);
1885                    set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, alarm_minute);
1886                    set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, alarm_second);
1887
1888                    assert_eq!(
1889                        rtc.calculate_alarm_duration(),
1890                        Duration::from_secs(expected_seconds),
1891                        "status B {status:#04x}, wildcard {wildcard:#04x}, mask {mask:#05b}"
1892                    );
1893                    assert_eq!(get_cmos_data(&mut rtc, CmosReg::HOUR_ALARM), alarm_hour);
1894                    assert_eq!(get_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM), alarm_minute);
1895                    assert_eq!(get_cmos_data(&mut rtc, CmosReg::SECOND_ALARM), alarm_second);
1896                }
1897            }
1898        }
1899    }
1900
1901    #[test]
1902    fn test_alarm() {
1903        fn hms_to_duration(h: u8, m: u8, s: u8) -> Duration {
1904            Duration::from_secs(s as u64 + 60 * m as u64 + h as u64 * 60 * 60)
1905        }
1906
1907        let (_, _, _time, mut rtc) = new_test_rtc();
1908
1909        set_binary(&mut rtc);
1910
1911        set_cmos_data(&mut rtc, CmosReg::HOUR, 2);
1912        set_cmos_data(&mut rtc, CmosReg::MINUTE, 2);
1913        set_cmos_data(&mut rtc, CmosReg::SECOND, 2);
1914
1915        set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, 3);
1916        set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 3);
1917        set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 3);
1918
1919        assert_eq!(rtc.calculate_alarm_duration(), hms_to_duration(1, 1, 1));
1920        set_cmos_data(&mut rtc, CmosReg::HOUR_ALARM, 0xff);
1921        set_cmos_data(&mut rtc, CmosReg::MINUTE_ALARM, 0xff);
1922        set_cmos_data(&mut rtc, CmosReg::SECOND_ALARM, 0xff);
1923        assert_eq!(rtc.calculate_alarm_duration(), hms_to_duration(0, 0, 1));
1924
1925        // TODO: test some more alarm scenarios
1926    }
1927}