Xyris  0.5
rtc.cpp
Go to the documentation of this file.
1 /**
2  * @file rtc.cpp
3  * @author Keeton Feavel ([email protected])
4  * @author Michel (JMallone) Gomes ([email protected])
5  * @brief
6  * @version 0.4
7  * @date 2021-07-25
8  *
9  * @copyright Copyright the Xyris Contributors (c) 2021
10  *
11  * References:
12  * https://github.com/limine-bootloader/limine/blob/trunk/stage23/lib/time.c
13  * https://en.wikipedia.org/wiki/Julian_day
14  */
15 #include <Arch/Arch.hpp>
16 #include <Devices/Clock/rtc.hpp>
17 
18 namespace RTC {
19 
20 #define RTC_CMOS_PORT 0x70
21 #define RTC_DATA_PORT 0x71
22 #define RTC_CURRENT_YEAR 2021 // Needs to be updated every year!
23 #define RTC_CURRENT_CENTURY 0 // Needs to be updated every century!
24 
25 static void callback(struct registers* regs);
26 static bool getUpdateInProgress();
27 static uint8_t getRegister(uint8_t reg);
28 static void read();
29 
30 // Current values from RTC
31 // These variables are way larger than they ever
32 // should be, but the compiler doesn't like our
33 // math with anything smaller, so we're going
34 // to live with it since memory is "cheap"
35 uint32_t second; // Current UTC second
36 uint32_t minute; // Current UTC minute
37 uint32_t hour; // Current UTC hour
38 uint32_t day; // Current UTC day (not reliable)
39 uint32_t month; // Current UTC month
40 uint32_t year; // Current UTC year
41 uint32_t century; // Current UTC century
42 
43 void init()
44 {
45  // Initializer
46  writeByte(RTC_CMOS_PORT, 0x8A);
47  writeByte(RTC_DATA_PORT, 0x20);
48  // Enable hardware interrupt 8 - Make sure interrupts are disabled beforehand
49  writeByte(RTC_CMOS_PORT, 0x8B);
50  char prev = readByte(RTC_DATA_PORT);
51  writeByte(RTC_CMOS_PORT, 0x8B);
52  writeByte(RTC_DATA_PORT, (prev | 0x40));
53  // Register our callback function with hardware interrupt 8
55 }
56 
57 static void callback(struct registers* regs)
58 {
59  (void)regs;
60 }
61 
62 static bool getUpdateInProgress()
63 {
64  writeByte(RTC_CMOS_PORT, 0x0A);
65  return (readByte(RTC_DATA_PORT) & 0x80);
66 }
67 
68 static uint8_t getRegister(uint8_t reg)
69 {
71  return readByte(RTC_DATA_PORT);
72 }
73 
74 /*
75  Big thanks to the OSDev Wiki for providing this function. It saved me a lot of time an energy trying to
76  debug and convert and do whatever to get RTC to work. I don't know who it was who wrote this code originally,
77  but thanks a lot. Time in programming is the worst.
78  Note: This uses the "read registers until you get the same values twice in a row" technique
79  to avoid getting dodgy/inconsistent values due to RTC updates
80 */
81 static void read()
82 {
83  // Previous values from RTC
84  // Used as a cache to check if we should update
85  uint32_t last_second = 0, last_minute = 0,
86  last_hour = 0, last_day = 0,
87  last_month = 0, last_year = 0,
88  last_century = 0, registerB = 0;
89  // Make sure an update isn't in progress
90  while (getUpdateInProgress());
91 
92  second = getRegister(0x00);
93  minute = getRegister(0x02);
94  hour = getRegister(0x04);
95  day = getRegister(0x07);
96  month = getRegister(0x08);
97  year = getRegister(0x09);
98  century = getRegister(RTC_CURRENT_CENTURY);
99 
100  while ((last_second != second) || (last_minute != minute) ||
101  (last_hour != hour) || (last_day != day) ||
102  (last_month != month) || (last_year != year) ||
103  (last_century != century))
104  {
105  last_second = second;
106  last_minute = minute;
107  last_hour = hour;
108  last_day = day;
109  last_month = month;
110  last_year = year;
111  last_century = century;
112 
113  // Make sure an update isn't in progress
114  while (getUpdateInProgress());
115 
116  second = getRegister(0x00);
117  minute = getRegister(0x02);
118  hour = getRegister(0x04);
119  day = getRegister(0x07);
120  month = getRegister(0x08);
121  year = getRegister(0x09);
122  if (RTC_CURRENT_CENTURY != 0) {
123  century = getRegister(RTC_CURRENT_CENTURY);
124  }
125  }
126 
127  registerB = getRegister(0x0B);
128 
129  // Convert BCD to binary values if necessary
130  if (!(registerB & 0x04)) {
131  second = (second & 0x0F) + ((second / 16) * 10);
132  minute = (minute & 0x0F) + ((minute / 16) * 10);
133  hour = ((hour & 0x0F) + (((hour & 0x70) / 16) * 10)) | (hour & 0x80);
134  day = (day & 0x0F) + ((day / 16) * 10);
135  month = (month & 0x0F) + ((month / 16) * 10);
136  year = (year & 0x0F) + ((year / 16) * 10);
137 
138  if (RTC_CURRENT_CENTURY != 0) {
139  century = (century & 0x0F) + ((century / 16) * 10);
140  }
141  }
142 
143  // Convert 12 hour clock to 24 hour clock if necessary
144  if (!(registerB & 0x02) && (hour & 0x80)) {
145  hour = ((hour & 0x7F) + 12) % 24;
146  }
147 
148  // Calculate the full (4-digit) year
149  if (RTC_CURRENT_CENTURY != 0) {
150  year += century * 100;
151  } else {
152  year += (RTC_CURRENT_YEAR / 100) * 100;
153  if (year < RTC_CURRENT_YEAR)
154  year += 100;
155  }
156 }
157 
158 /**
159  * JDN is time since noon Universal Time on January 1, 4713 BCE (on the Julian calendar)
160  * Developed by Fliegel and van Flandern (1968), published in "Compact Computer Algorithms"
161  *
162  * (153 * m) + 2 y y y
163  * JDN = d + ------------- + (365 * y) + --- - --- + --- - 32045
164  * 5 4 100 400
165  * < Part #1 > < Part #2 > < Part #3 > < Part #4 >
166  *
167  * Part #1: Calculates the number of days in the previous months (where March -> m = 0)
168  * Part #2: Adds the number of days in all non-leap years
169  * Part #3: Calculates and adds the number of leap years since the year -4800
170  * (which is 0 for y). Leap years occur on every 4th, 100th, and 400th
171  * year, which corresponds to each sub-step in this part)
172  * Part #4: Ensure that the result will be 0 for January 1, 4713 BCE
173  * (This is the start of the Julian Calendar)
174  */
175 static uint64_t getJulianDay(uint8_t days, uint8_t months, uint16_t years)
176 {
177  int a = (14 - months) / 12;
178  int y = years + 4800 - a;
179  int m = months + 12 * a - 3;
180 
181  int jdn = days + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045;
182  return jdn;
183 }
184 
185 static uint64_t getUnixEpoch(uint8_t seconds, uint8_t minutes, uint8_t hours, uint8_t days, uint8_t months, uint16_t years)
186 {
187  uint64_t jdnNow = getJulianDay(days, months, years);
188  uint64_t jdn1970 = getJulianDay(1, 1, 1970);
189  uint64_t jdnDiff = jdnNow - jdn1970;
190  return (jdnDiff * (60 * 60 * 24)) + hours * 3600 + minutes * 60 + seconds;
191 }
192 
193 uint64_t getEpoch()
194 {
195  read();
196  return getUnixEpoch(second, minute, hour, day, month, year);
197 }
198 
199 } // !namespace RTC
RTC::second
uint32_t second
Definition: rtc.cpp:35
RTC
Definition: rtc.cpp:18
RTC::hour
uint32_t hour
Definition: rtc.cpp:37
RTC_DATA_PORT
#define RTC_DATA_PORT
Definition: rtc.cpp:21
rtc.hpp
RTC::minute
uint32_t minute
Definition: rtc.cpp:36
RTC::month
uint32_t month
Definition: rtc.cpp:39
registers
A structure definining values for all x86 registers. Cannot be namespaced due to C linkage and ASM in...
Definition: regs.hpp:19
Interrupts::registerHandler
void registerHandler(uint8_t interrupt, InterruptHandler_t handler)
Definition: isr.cpp:98
RTC::init
void init()
Initializes the Real Time Clock driver for the x86_64 architecture.
Definition: rtc.cpp:43
RTC::getEpoch
uint64_t getEpoch()
Get a epoch number from rtc.
Definition: rtc.cpp:193
Arch.hpp
Architecture control and initialization.
Interrupts::INTERRUPT_8
@ INTERRUPT_8
Definition: isr.hpp:68
RTC::day
uint32_t day
Definition: rtc.cpp:38
RTC_CURRENT_YEAR
#define RTC_CURRENT_YEAR
Definition: rtc.cpp:22
RTC_CURRENT_CENTURY
#define RTC_CURRENT_CENTURY
Definition: rtc.cpp:23
writeByte
void writeByte(uint16_t port, uint8_t data)
Writes a byte (8 bits) to the CPU bus at a given port address.
Definition: ports.cpp:19
RTC_CMOS_PORT
#define RTC_CMOS_PORT
Definition: rtc.cpp:20
RTC::century
uint32_t century
Definition: rtc.cpp:41
readByte
uint8_t readByte(uint16_t port)
Reads a byte (8 bits) off the CPU bus at a given port address.
Definition: ports.cpp:13
RTC::year
uint32_t year
Definition: rtc.cpp:40