#include #include #include #include /* * 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 CMOS_REG_B 0x0B #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 i64 cmos_get_time(void); static void cmos_set_time(i64 time); int cmos_has_command = 0; int cmos_command_is_read = 0; int *cmos_done_ptr = NULL; i64 *cmos_time_ptr = NULL; void cmos_handler(reg_t *reg) { (void)reg; if (!cmos_has_command) { goto cmos_handler_exit; } if (cmos_command_is_read) { *cmos_time_ptr = cmos_get_time(); *cmos_done_ptr = 1; goto cmos_handler_exit; } if (!cmos_command_is_read) { cmos_set_time(*cmos_time_ptr); *cmos_done_ptr = 1; goto cmos_handler_exit; } cmos_handler_exit: cmos_has_command = 0; u8 reg_b = cmos_get_register(CMOS_REG_B); reg_b &= ~0x40; // Disable interrupts cmos_set_register(CMOS_REG_B, reg_b); EOI(0x8); } void cmos_init(void) { install_handler((interrupt_handler)cmos_handler, INT_32_INTERRUPT_GATE(0x0), 0x28); } int cmos_start_call(int is_read, int *done, i64 *time) { if (cmos_has_command) { return 0; } *done = 0; cmos_done_ptr = done; cmos_time_ptr = time; cmos_command_is_read = is_read; cmos_has_command = 1; u8 reg_b = cmos_get_register(CMOS_REG_B); reg_b |= 0x40; // Enable interrupts cmos_set_register(CMOS_REG_B, reg_b); return 1; } // 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. static i64 cmos_get_time(void) { 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; } static void cmos_set_time(i64 time) { assert(time >= 0); // TODO: Add support for time travelers 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); }