Luna/kernel/src/thread/Clock.cpp
apio fd402083d7
kernel: Fix kernel panic when adding a timer before another one
This was caused because add_to_timer_queue() did not set active_clock when inserting a timer before the end, making disarm() a no-op.
Therefore, the clock would continue to use the timer after it had been freed.
2024-01-06 18:12:25 +01:00

171 lines
4.6 KiB
C++

#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<Timer> 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<time_t>(nanoseconds / 1'000'000'000),
.tv_nsec = static_cast<long>(nanoseconds % 1'000'000'000) };
}