#include "thread/Clock.h" #include "Log.h" #include "arch/Serial.h" #include "boot/bootboot.h" #include "thread/Thread.h" Clock g_realtime_clock; Clock g_monotonic_clock; static inline constexpr bool isleap(u32 year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } static constexpr u32 make_yday(u32 year, u32 month) { constexpr u16 upto[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; u32 yd = upto[month - 1]; if (month > 2 && isleap(year)) yd++; return yd; } // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_16 static constexpr u64 broken_down_to_unix(u64 year, u64 yday, u64 hour, u64 min, u64 sec) { return sec + min * 60 + hour * 3600 + yday * 86400 + (year - 70) * 31536000 + ((year - 69) / 4) * 86400 - ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400; } // The bootloader encodes the date and time in Binary-Coded Decimal (BCD), which represents decimal digits using // hexadecimal digits. For example, BCD 0x22 is 22 in decimal. // https://gitlab.com/bztsrc/bootboot/-/blob/master/bootboot_spec_1st_ed.pdf, page 15. static inline constexpr u32 bcd_number_to_decimal(u32 num) { return ((num >> 4) * 10) + (num & 0xf); } static u64 bootloader_time_to_unix(const u8 boottime[8]) { const u32 year = bcd_number_to_decimal(boottime[0]) * 100 + bcd_number_to_decimal(boottime[1]); const u32 month = bcd_number_to_decimal(boottime[2]); const u32 day = bcd_number_to_decimal(boottime[3]); const u32 hour = bcd_number_to_decimal(boottime[4]); const u32 minute = bcd_number_to_decimal(boottime[5]); const u32 second = bcd_number_to_decimal(boottime[6]); // "The last byte can store 1/100th second precision, but in lack of support on most platforms, it is 0x00". // Therefore, let's not rely on it. kinfoln("Current time: %.2d/%.2d/%d %.2d:%.2d:%.2d UTC", day, month, year, hour, minute, second); return broken_down_to_unix(year - 1900, make_yday(year, month) + (day - 1), hour, minute, second); } extern const BOOTBOOT bootboot; void Clock::init() { struct timespec time = { .tv_sec = (time_t)bootloader_time_to_unix(bootboot.datetime), .tv_nsec = 0 }; g_realtime_clock.update(time); arch_init(); } void Clock::set_resolution(long resolution) { m_resolution = resolution; } void Clock::update(const struct timespec& time) { m_time = time; } void Clock::add_to_timer_queue(Timer* timer) { check(timer->active_clock == nullptr); for (auto* t : m_timer_queue) { if (timer->delta_ticks <= t->delta_ticks) { t->delta_ticks -= timer->delta_ticks; m_timer_queue.add_before(t, timer); timer->active_clock = this; return; } timer->delta_ticks -= t->delta_ticks; } m_timer_queue.append(timer); timer->active_clock = this; } void Clock::remove_from_timer_queue(Timer* timer) { check(timer->active_clock == this); auto maybe_next = m_timer_queue.next(timer); if (maybe_next.has_value()) { auto next = maybe_next.value(); next->delta_ticks += timer->delta_ticks; } m_timer_queue.remove(timer); timer->active_clock = nullptr; } void Clock::tick() { struct timespec interval = { .tv_sec = 0, .tv_nsec = m_resolution }; timespecadd(&m_time, &interval, &m_time); auto maybe_first = m_timer_queue.first(); if (!maybe_first.has_value()) return; auto first = *maybe_first; first->delta_ticks--; LinkedList timers_to_be_restarted; m_timer_queue.delayed_for_each([&](Timer* t) { if (t->delta_ticks == 0) { this->m_timer_queue.remove(t); t->active_clock = nullptr; t->thread->send_signal(t->signo); if (t->restart) timers_to_be_restarted.append(t); return true; } return false; }); timers_to_be_restarted.consume([this](Timer* t) { t->delta_ticks = t->interval_ticks; add_to_timer_queue(t); }); } void Clock::get_time(struct timespec& out) { out = m_time; } long Clock::resolution() { return m_resolution; } u64 Clock::ticks_left(Timer* timer) { check(timer->active_clock == this); u64 total = 0; if (timer->delta_ticks == 0) return 0; for (auto* t : m_timer_queue) { total += t->delta_ticks; if (t == timer) break; } return total; } struct timespec Clock::from_ticks(u64 ticks) { u64 nanoseconds = ticks * m_resolution; return timespec { .tv_sec = static_cast(nanoseconds / 1'000'000'000), .tv_nsec = static_cast(nanoseconds % 1'000'000'000) }; }