summaryrefslogtreecommitdiff
path: root/kernel/drivers/cmos.c
diff options
context:
space:
mode:
authorAnton Kling <anton@kling.gg>2024-07-03 15:45:30 +0200
committerAnton Kling <anton@kling.gg>2024-07-03 15:45:30 +0200
commit658c4e9645bf46268ed13bf5ef76d0ba60a347b9 (patch)
tree3d15e84a9e9c6750cbc9eb6aab543ddfa4b5280a /kernel/drivers/cmos.c
parentdd9aece19f93c1cafe07dfc0e63e1599a06db04b (diff)
CMOS: Add basic CMOS RTC support
Diffstat (limited to 'kernel/drivers/cmos.c')
-rw-r--r--kernel/drivers/cmos.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/kernel/drivers/cmos.c b/kernel/drivers/cmos.c
new file mode 100644
index 0000000..3df1d97
--- /dev/null
+++ b/kernel/drivers/cmos.c
@@ -0,0 +1,231 @@
+#include <assert.h>
+#include <cpu/io.h>
+#include <drivers/cmos.h>
+
+/*
+ * Why the fuck would somebody go through the effort of converting their
+ * seconds counter to minutes, hours IN PM AND AM AND THE DAYS OF THE MONTH???
+ * It is obvious that the first thing any person reading these values
+ * will do is convert them into seconds SINCE THAT IS THE ONLY SANE WAY
+ * OF STORING TIME FOR COMPUTERS.
+ *
+ * Register Contents Range
+ * 0x00 Seconds 0–59
+ * 0x02 Minutes 0–59
+ * 0x04 Hours 0–23 in 24-hour mode,
+ * 1–12 in 12-hour mode, highest bit set if pm
+ * 0x06 Weekday 1–7, Sunday = 1
+ * 0x07 Day of Month 1–31
+ * 0x08 Month 1–12
+ * 0x09 Year 0–99
+ * 0x32 Century (maybe) 19–20?
+ * 0x0A Status Register A
+ * 0x0B Status Register B
+ */
+#define CMOS_SECONDS 0x00
+#define CMOS_MINUTES 0x02
+#define CMOS_HOURS 0x04
+#define CMOS_WEEKDAY 0x06
+#define CMOS_DAY_OF_MONTH 0x07
+#define CMOS_MONTH 0x08
+#define CMOS_YEAR 0x09
+#define CMOS_CENTURY 0x32
+
+#define NMI_disable_bit 1
+
+static u8 cmos_get_register(u8 reg) {
+ outb(0x70, (NMI_disable_bit << 7) | reg);
+ return inb(0x71);
+}
+
+static void cmos_set_register(u8 reg, u8 value) {
+ outb(0x70, (NMI_disable_bit << 7) | reg);
+ outb(0x71, value);
+}
+
+static u8 binary_to_bcd(int binary) {
+ int result = 0;
+ int shift = 0;
+ while (binary > 0) {
+ result |= (binary % 10) << (shift++ << 2);
+ binary /= 10;
+ }
+ return result;
+}
+
+static u8 bcd_to_binary(u8 bcd) {
+ return ((bcd / 16) * 10) + (bcd & 0xf);
+}
+
+u8 days_in_month[] = {
+ 31, // January: 31 days
+ 28, // February: 28 days and 29 in every leap year
+ 31, // March: 31 days
+ 30, // April: 30 days
+ 31, // May: 31 days
+ 30, // June: 30 days
+ 31, // July: 31 days
+ 31, // August: 31 days
+ 30, // September: 30 days
+ 31, // October: 31 days
+ 30, // November: 30 days
+ 31, // December: 31 days
+};
+
+static int cmos_update_in_progress(void) {
+ return (cmos_get_register(0x0A) & (1 << 7));
+}
+
+// TODO: Optimize by using interrupts as osdev wiki recommends
+static void cmos_wait(void) {
+ // It firsts wait until there is a update
+ // Then we wait for it to be done.
+ // This is to avoid race conditions.
+ for (; !cmos_update_in_progress();)
+ ;
+
+ for (; cmos_update_in_progress();)
+ ;
+}
+
+// This function returns the current unix timestamp from the CMOS RTC
+// TODO: It currently makes the assumption that time travel is not possible and
+// as a result will not give negative values. This support should maybe be added
+// to prepare ourselves for the past.
+i64 cmos_get_time(void) {
+ cmos_wait();
+
+ u8 seconds = cmos_get_register(CMOS_SECONDS);
+ u8 minutes = cmos_get_register(CMOS_MINUTES);
+
+ u8 reg_b = cmos_get_register(0x0B);
+
+ u8 hours = cmos_get_register(CMOS_HOURS);
+ u8 day_of_month = cmos_get_register(CMOS_DAY_OF_MONTH);
+ u8 month = cmos_get_register(CMOS_MONTH);
+ u8 incomplete_year = cmos_get_register(CMOS_YEAR);
+ u8 century = cmos_get_register(CMOS_CENTURY);
+ if (!(reg_b & (1 << 2))) {
+ hours = bcd_to_binary(hours);
+ day_of_month = bcd_to_binary(day_of_month);
+ month = bcd_to_binary(month);
+ incomplete_year = bcd_to_binary(incomplete_year);
+ century = bcd_to_binary(century);
+ }
+ if (!(reg_b & (1 << 1))) {
+ // TODO: Add support for AM/PM so the americans can use this
+ return 0;
+ }
+
+ u16 year = century * 100 + incomplete_year;
+
+ u64 second_sum = 0;
+
+ // TODO: There is probably a more clever algorithm for this
+ const u32 seconds_in_a_day = 24 * 60 * 60;
+ for (int i = 1970; i < year; i++) {
+ second_sum += 365 * seconds_in_a_day;
+ if (0 == i % 4) {
+ second_sum += seconds_in_a_day;
+ }
+ }
+
+ for (int i = 0; i < (int)month - 1
+ /*-1 to account for it being 1 indexed*/;
+ i++) {
+ second_sum += days_in_month[i] * seconds_in_a_day;
+ int is_leap_year = 0 == (year % 4);
+ if (is_leap_year && 1 == i /*February*/) {
+ second_sum += seconds_in_a_day;
+ }
+ }
+ second_sum += (day_of_month - 1) * seconds_in_a_day;
+ second_sum += hours * 60 * 60;
+ second_sum += minutes * 60;
+ second_sum += seconds;
+ return second_sum;
+}
+
+void cmos_set_time(i64 time) {
+ assert(time > 0); // TODO: Add support for time travelers
+ cmos_wait();
+ u8 reg_b = cmos_get_register(0x0B);
+
+ u8 seconds = 0;
+ u8 minutes = 0;
+ u8 hours = 0;
+
+ const u32 seconds_in_a_day = 24 * 60 * 60;
+
+ // 1 January 1970 was a thursday, that is why there is a +4
+ u8 weekday = ((((time / seconds_in_a_day) + 4)) % 7) + 1;
+ u8 day_of_month = 0;
+ u8 month = 0;
+ u16 year = 1970;
+
+ if (!(reg_b & (1 << 1))) {
+ // TODO: Add support for AM/PM so the americans can use this
+ return;
+ }
+
+ // Keep adding years for as long as possible
+ for (;;) {
+ u32 s = 365 * seconds_in_a_day;
+ if (0 == year % 4) {
+ s += seconds_in_a_day;
+ if (0 == year % 100 && 0 != year % 400) {
+ s -= seconds_in_a_day;
+ }
+ }
+ if (s > time) {
+ break;
+ }
+ year++;
+ time -= s;
+ }
+ // Keep adding months
+ for (;;) {
+ u32 s = days_in_month[month] * seconds_in_a_day;
+ if (0 == (year % 4) && 1 == month && (0 != year % 100 || 0 == year % 400)) {
+ s += seconds_in_a_day;
+ }
+ if (s > time) {
+ break;
+ }
+ month++;
+ time -= s;
+ }
+ month++;
+ day_of_month = time / seconds_in_a_day;
+ day_of_month++;
+ time %= seconds_in_a_day;
+
+ hours = time / (60 * 60);
+ time %= 60 * 60;
+
+ minutes = time / (60);
+ time %= 60;
+
+ seconds = time;
+
+ u8 s_year = year % 100;
+ u8 century = (year - (year % 100)) / 100;
+ if (!(reg_b & (1 << 2))) {
+ seconds = binary_to_bcd(seconds);
+ minutes = binary_to_bcd(minutes);
+ hours = binary_to_bcd(hours);
+ day_of_month = binary_to_bcd(day_of_month);
+ month = binary_to_bcd(month);
+ weekday = binary_to_bcd(weekday);
+ s_year = binary_to_bcd(s_year);
+ century = binary_to_bcd(century);
+ }
+ cmos_set_register(CMOS_SECONDS, seconds);
+ cmos_set_register(CMOS_MINUTES, minutes);
+ cmos_set_register(CMOS_HOURS, hours);
+ cmos_set_register(CMOS_DAY_OF_MONTH, day_of_month);
+ cmos_set_register(CMOS_MONTH, month);
+ cmos_set_register(CMOS_YEAR, s_year);
+ cmos_set_register(CMOS_CENTURY, century);
+ cmos_set_register(CMOS_WEEKDAY, weekday);
+}