The Pedigree Project 0.1
RtcCalendar.h
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#ifndef PEDIGREE_KERNEL_MACHINE_MACH_PC_RTCCALENDAR_H
9#define PEDIGREE_KERNEL_MACHINE_MACH_PC_RTCCALENDAR_H
10
11#include "RtcTimeAccounting.h"
12
13namespace RtcCalendar {
14inline bool validCenturyIndex(uint8_t index) {
15 return index == 0 || (index >= 0x0e && index <= 0x7f);
16}
17
18inline bool decode(uint8_t raw, bool binary, uint8_t& value) {
19 if (!binary && ((raw & 15) > 9 || (raw >> 4) > 9)) {
20 return false;
21 }
22 value = binary ? raw : (raw >> 4) * 10 + (raw & 15);
23 return true;
24}
25
26inline uint8_t encode(uint8_t value, bool binary) {
27 return binary ? value : ((value / 10) << 4) | (value % 10);
28}
29
30inline bool valid(const RtcTimeAccounting::CivilTime& time) {
31 return time.year >= 1970 && time.year <= 9999 && time.month >= 1 && time.month <= 12 &&
32 time.day >= 1 && time.day <= RtcTimeAccounting::daysInMonth(time.year, time.month) &&
33 time.hour < 24 && time.minute < 60 && time.second < 60;
34}
35
36// Callers hold the CMOS transaction lock and inhibit updates for the snapshot.
37template <typename Read>
38bool read(Read readRegister, uint8_t centuryIndex, RtcTimeAccounting::CivilTime& result) {
39 if (!validCenturyIndex(centuryIndex) || !(readRegister(0x0d) & 0x80)) {
40 return false;
41 }
42 const uint8_t status = readRegister(0x0b);
43 const bool binary = status & 4;
44 const bool hours24 = status & 2;
45 const uint8_t rawHour = readRegister(4);
46 uint8_t year = 0, century = 0;
48 if (!decode(readRegister(0), binary, time.second) ||
49 !decode(readRegister(2), binary, time.minute) ||
50 !decode(hours24 ? rawHour : (rawHour & 0x7f), binary, time.hour) ||
51 !decode(readRegister(7), binary, time.day) || !decode(readRegister(8), binary, time.month) ||
52 !decode(readRegister(9), binary, year) || year > 99) {
53 return false;
54 }
55 if (!hours24) {
56 if (time.hour < 1 || time.hour > 12) {
57 return false;
58 }
59 time.hour = (time.hour % 12) + ((rawHour & 0x80) ? 12 : 0);
60 }
61 if (centuryIndex) {
62 if (!decode(readRegister(centuryIndex), binary, century) || century > 99) {
63 return false;
64 }
65 time.year = century * 100 + year;
66 } else {
67 // A missing FADT century field provides only a two-digit year. Keep the
68 // interpretation fixed and reversible rather than guessing a CMOS byte.
69 time.year = (year >= 70 ? 1900 : 2000) + year;
70 }
71 if (!valid(time)) {
72 return false;
73 }
74 result = time;
75 return true;
76}
77
78template <typename Read, typename Write>
79bool write(Read readRegister, Write writeRegister, uint8_t centuryIndex,
80 const RtcTimeAccounting::CivilTime& time) {
81 if (!validCenturyIndex(centuryIndex) || !valid(time) || (!centuryIndex && time.year > 2069)) {
82 return false;
83 }
84 const uint8_t status = readRegister(0x0b);
85 const bool binary = status & 4;
86 const bool hours24 = status & 2;
87 uint8_t hour = time.hour;
88 if (!hours24) {
89 hour = time.hour % 12;
90 if (!hour) {
91 hour = 12;
92 }
93 }
94 const uint8_t rawHour = encode(hour, binary) | ((!hours24 && time.hour >= 12) ? 0x80 : 0);
95 writeRegister(0, encode(time.second, binary));
96 writeRegister(2, encode(time.minute, binary));
97 writeRegister(4, rawHour);
98 writeRegister(7, encode(time.day, binary));
99 writeRegister(8, encode(time.month, binary));
100 writeRegister(9, encode(time.year % 100, binary));
101 if (centuryIndex) {
102 writeRegister(centuryIndex, encode(time.year / 100, binary));
103 }
104 return true;
105}
106} // namespace RtcCalendar
107
108#endif