From 61dfda6a70744ca3dc5861e3c0ac144a9dc4a662 Mon Sep 17 00:00:00 2001 From: Katharina Heidenreich Date: Fri, 3 Jul 2026 22:35:16 +0200 Subject: [PATCH] feat: add fadt based lapic timer calibration --- kernel/include/acpi/acpi.h | 2 + kernel/include/acpi/discovery.h | 17 ++++++ kernel/include/acpi/fadt.h | 98 +++++++++++++++++++++++++++++++++ kernel/include/apic/timer.h | 13 ++++- kernel/include/util/port_io.h | 33 +++++++++++ kernel/src/apic/timer.cpp | 65 ++++++++++++++++++++-- kernel/src/init/init.cpp | 57 ++++++++++++------- 7 files changed, 257 insertions(+), 28 deletions(-) create mode 100644 kernel/include/acpi/discovery.h create mode 100644 kernel/include/acpi/fadt.h create mode 100644 kernel/include/util/port_io.h diff --git a/kernel/include/acpi/acpi.h b/kernel/include/acpi/acpi.h index 34c0288..e7dde82 100644 --- a/kernel/include/acpi/acpi.h +++ b/kernel/include/acpi/acpi.h @@ -60,6 +60,7 @@ struct rsdp_t { static_assert(sizeof(rsdp_t) == 36); struct madt_t; +struct fadt_t; struct Rsdp { explicit Rsdp(paddr_t rsdp_physical) : physical(rsdp_physical) {} @@ -90,6 +91,7 @@ private: switch (static_cast(header->signature)) { ACPI_DISPATCH(madt, madt_t) + ACPI_DISPATCH(fadt, fadt_t) default: break; } diff --git a/kernel/include/acpi/discovery.h b/kernel/include/acpi/discovery.h new file mode 100644 index 0000000..0c133da --- /dev/null +++ b/kernel/include/acpi/discovery.h @@ -0,0 +1,17 @@ +#pragma once + +#include "acpi/acpi.h" +#include "acpi/fadt.h" +#include "acpi/madt.h" +#include "util/optional.h" + +namespace acpi { + +struct discovery_result_t { + optional rsdp; + optional madt; + // TODO(wiring): capture the FADT in setupAcpi and store it here + optional fadt; +}; + +} // namespace acpi diff --git a/kernel/include/acpi/fadt.h b/kernel/include/acpi/fadt.h new file mode 100644 index 0000000..ab09b09 --- /dev/null +++ b/kernel/include/acpi/fadt.h @@ -0,0 +1,98 @@ +#pragma once + +#include "acpi/acpi.h" +#include "memory/pointer.h" +#include "util/number.h" + +namespace acpi { + +struct fadt_flags_t { + uint32_t wbinvd : 1; + uint32_t wbinvd_flush : 1; + uint32_t processor_c1 : 1; + uint32_t p_level2_up : 1; + uint32_t power_button : 1; + uint32_t sleep_button : 1; + uint32_t fixed_rtc : 1; + uint32_t rtc_s4 : 1; + uint32_t timer_value_extended : 1; + uint32_t reserved : 23; +}; +static_assert(sizeof(fadt_flags_t) == 4); + +// Prefix of the FADT through the flags field; real tables continue past this, +// so no field after flags may be added without also handling table revisions. +struct fadt_t { + sdt_header_t header; + uint32_t firmware_control; + uint32_t dsdt_address; + uint8_t reserved; + uint8_t preferred_pm_profile; + uint16_t sci_interrupt; + uint32_t smi_command_port; + uint8_t acpi_enable; + uint8_t acpi_disable; + uint8_t s4bios_request; + uint8_t pstate_control; + uint32_t pm1a_event_block; + uint32_t pm1b_event_block; + uint32_t pm1a_control_block; + uint32_t pm1b_control_block; + uint32_t pm2_control_block; + uint32_t pm_timer_block; + uint32_t gpe0_block; + uint32_t gpe1_block; + uint8_t pm1_event_length; + uint8_t pm1_control_length; + uint8_t pm2_control_length; + uint8_t pm_timer_length; + uint8_t gpe0_block_length; + uint8_t gpe1_block_length; + uint8_t gpe1_base; + uint8_t cstate_control; + uint16_t p_level2_latency; + uint16_t p_level3_latency; + uint16_t flush_size; + uint16_t flush_stride; + uint8_t duty_offset; + uint8_t duty_width; + uint8_t day_alarm; + uint8_t month_alarm; + uint8_t century; + uint16_t iapc_boot_architecture; + uint8_t reserved2; + fadt_flags_t flags; +} __attribute__((packed)); +static_assert(sizeof(fadt_t) == 116); + +constexpr uint64_t pm_timer_frequency_hz = 3'579'545; + +struct pm_timer_duration_t { + uint64_t pm_ticks; +}; + +constexpr pm_timer_duration_t pm_timer_milliseconds(uint64_t count) { + return pm_timer_duration_t{count * pm_timer_frequency_hz / 1'000}; +} + +struct Fadt { + explicit Fadt(paddr_t fadt_physical) : table(fadt_physical.access()) {} + explicit Fadt(const fadt_t* fadt_table) : table(fadt_table) {} + + bool has_pm_timer() const { + return table->pm_timer_block != 0 && table->pm_timer_length == 4; + } + + uint16_t pm_timer_port() const { + return static_cast(table->pm_timer_block); + } + + bool pm_timer_is_32bit() const { + return table->flags.timer_value_extended != 0; + } + +private: + const fadt_t* table; +}; + +} // namespace acpi diff --git a/kernel/include/apic/timer.h b/kernel/include/apic/timer.h index 86602be..22016b3 100644 --- a/kernel/include/apic/timer.h +++ b/kernel/include/apic/timer.h @@ -1,6 +1,6 @@ #pragma once -#include "acpi/madt.h" +#include "acpi/discovery.h" namespace apic { @@ -12,6 +12,11 @@ struct timestamp_t { uint64_t timer_count; }; +struct seconds_nanoseconds_t { + uint64_t seconds; + uint64_t nanoseconds; +}; + constexpr duration_t operator+(duration_t left, duration_t right) { return duration_t{left.timer_count + right.timer_count}; } @@ -44,13 +49,17 @@ constexpr duration_t operator-(timestamp_t left, timestamp_t right) { return duration_t{left.timer_count - right.timer_count}; } -bool calibrateTimer(const acpi::Madt& madt); +bool calibrateTimer(const acpi::discovery_result_t& discovery); duration_t seconds(uint64_t count); duration_t milliseconds(uint64_t count); duration_t microseconds(uint64_t count); duration_t nanoseconds(uint64_t count); +seconds_nanoseconds_t toSecondsAndNanoseconds(duration_t duration); + +void busyWait(duration_t duration); + timestamp_t now(); // TODO: when dynamic allocation and lists are available diff --git a/kernel/include/util/port_io.h b/kernel/include/util/port_io.h new file mode 100644 index 0000000..bd3dac6 --- /dev/null +++ b/kernel/include/util/port_io.h @@ -0,0 +1,33 @@ +#pragma once + +#include "util/number.h" + +inline uint8_t port_read_8(uint16_t port) { + uint8_t value = 0; + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +inline uint16_t port_read_16(uint16_t port) { + uint16_t value = 0; + __asm__ volatile("inw %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +inline uint32_t port_read_32(uint16_t port) { + uint32_t value = 0; + __asm__ volatile("inl %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +inline void port_write_8(uint16_t port, uint8_t value) { + __asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +inline void port_write_16(uint16_t port, uint16_t value) { + __asm__ volatile("outw %0, %1" : : "a"(value), "Nd"(port)); +} + +inline void port_write_32(uint16_t port, uint32_t value) { + __asm__ volatile("outl %0, %1" : : "a"(value), "Nd"(port)); +} diff --git a/kernel/src/apic/timer.cpp b/kernel/src/apic/timer.cpp index bf1df94..45aadcc 100644 --- a/kernel/src/apic/timer.cpp +++ b/kernel/src/apic/timer.cpp @@ -1,6 +1,7 @@ #include "apic/timer.h" #include "util/cpuid.h" +#include "util/port_io.h" namespace { @@ -46,24 +47,59 @@ optional tscFrequencyFromProcessorBase() { }); } -optional tscFrequency() { - return tscFrequencyFromCrystalRatio().or_else(tscFrequencyFromProcessorBase); +// The counter is 24 or 32 bit wide (fadt_flags_t::timer_value_extended); masking +// deltas to 24 bits is correct for both as long as polling outruns a wrap (~4.7 s). +constexpr uint32_t pm_timer_counter_mask = 0xFFFFFF; + +uint64_t readTscSerialized() { + uint32_t low = 0; + uint32_t high = 0; + __asm__ volatile("lfence; rdtsc" : "=a"(low), "=d"(high)); + return (static_cast(high) << 32) | low; +} + +[[maybe_unused]] optional +tscFrequencyFromPmTimer(const acpi::Fadt& fadt, acpi::pm_timer_duration_t sample_window) { + if (!fadt.has_pm_timer() || sample_window.pm_ticks == 0) { + return nullopt; + } + uint16_t timer_port = fadt.pm_timer_port(); + uint32_t previous_count = port_read_32(timer_port) & pm_timer_counter_mask; + uint64_t tsc_start = readTscSerialized(); + uint64_t elapsed_pm_ticks = 0; + while (elapsed_pm_ticks < sample_window.pm_ticks) { + uint32_t current_count = port_read_32(timer_port) & pm_timer_counter_mask; + elapsed_pm_ticks += (current_count - previous_count) & pm_timer_counter_mask; + previous_count = current_count; + } + uint64_t tsc_end = readTscSerialized(); + return (tsc_end - tsc_start) * acpi::pm_timer_frequency_hz / elapsed_pm_ticks; } apic::duration_t durationFromUnit(uint64_t count, uint64_t units_per_second) { uint64_t whole_seconds = count / units_per_second; uint64_t remainder_units = count % units_per_second; - return apic::duration_t{whole_seconds * timer_calibration.tsc_frequency_hz + - remainder_units * timer_calibration.tsc_frequency_hz / units_per_second}; + return apic::duration_t{ + whole_seconds * timer_calibration.tsc_frequency_hz + + remainder_units * timer_calibration.tsc_frequency_hz / units_per_second + }; +} + +optional tscFrequency(const acpi::discovery_result_t& discovery) { + return tscFrequencyFromCrystalRatio().or_else(tscFrequencyFromProcessorBase).or_else([&discovery] { + return discovery.fadt.bind([](const acpi::Fadt& fadt) { + return tscFrequencyFromPmTimer(fadt, acpi::pm_timer_milliseconds(50)); + }); + }); } } // namespace -bool apic::calibrateTimer(const acpi::Madt&) { +bool apic::calibrateTimer(const acpi::discovery_result_t& discovery) { if (!isTscSupported()) { return false; } - return tscFrequency() + return tscFrequency(discovery) .map([](uint64_t tsc_frequency_hz) { timer_calibration.tsc_frequency_hz = tsc_frequency_hz; return true; @@ -87,6 +123,23 @@ apic::duration_t apic::nanoseconds(uint64_t count) { return durationFromUnit(count, 1'000'000'000); } +apic::seconds_nanoseconds_t apic::toSecondsAndNanoseconds(duration_t duration) { + uint64_t frequency_hz = timer_calibration.tsc_frequency_hz; + if (frequency_hz == 0) { + return seconds_nanoseconds_t{}; + } + uint64_t whole_seconds = duration.timer_count / frequency_hz; + uint64_t remainder_ticks = duration.timer_count % frequency_hz; + return seconds_nanoseconds_t{whole_seconds, remainder_ticks * 1'000'000'000 / frequency_hz}; +} + +void apic::busyWait(duration_t duration) { + timestamp_t start = now(); + while ((now() - start).timer_count < duration.timer_count) { + __asm__ volatile("pause"); + } +} + apic::timestamp_t apic::now() { uint32_t low = 0; uint32_t high = 0; diff --git a/kernel/src/init/init.cpp b/kernel/src/init/init.cpp index f7b891a..6feb16d 100644 --- a/kernel/src/init/init.cpp +++ b/kernel/src/init/init.cpp @@ -1,5 +1,7 @@ #include "init/init.h" #include "acpi/acpi.h" +#include "acpi/discovery.h" +#include "acpi/fadt.h" #include "acpi/madt.h" #include "apic/timer.h" #include "init/multiboot2.h" @@ -187,38 +189,53 @@ static acpi::Rsdp findRsdp() { return acpi::Rsdp(*rsdp); } -struct AcpiDiscoveryResult { - optional rsdp; - optional madt; -}; - -static AcpiDiscoveryResult setupAcpi() { - AcpiDiscoveryResult res{}; +static acpi::discovery_result_t setupAcpi() { + acpi::discovery_result_t res{}; res.rsdp = findRsdp(); - res.rsdp->for_each_table(overloaded{[&](const acpi::madt_t* header) { - res.madt = acpi::Madt{header}; - }}); + res.rsdp->for_each_table( + overloaded{ + [&](const acpi::madt_t* header) { + res.madt = acpi::Madt{header}; + }, + [&](const acpi::fadt_t* header) { + res.fadt = acpi::Fadt{header}; + } + } + ); return res; } -static void calibrateTimer(optional madt) { - auto success = madt.map(apic::calibrateTimer).value_or(false); - if (!success) { +static void calibrateTimer(acpi::discovery_result_t acpi) { + if (!apic::calibrateTimer(acpi)) { panic("Could not calibrate timer"); } } +static void demonstateTimer() { + auto time1 = apic::now(); + print("Time 1: "); + print_hex(time1.timer_count); + print("\nTime 2: "); + auto time2 = apic::now(); + print_hex(time2.timer_count); + print("\n"); + auto duration = time2 - time1; + auto readableDuration = apic::toSecondsAndNanoseconds(duration); + print_dec(readableDuration.seconds); + print("s "); + print_dec(readableDuration.nanoseconds); + print("ns\n"); + print("Wait for 5s!\n"); + apic::busyWait(apic::seconds(5)); + print("Waited for 5s!\n"); +} + [[maybe_unused]] [[noreturn]] __attribute__((used)) void init() { initFromLow(); print("Reached init\n"); step("memory", setupMemory); auto acpi = step("acpi", setupAcpi); - step("timer", partial(calibrateTimer, acpi.madt)); - auto now = apic::now(); - print_hex(now.timer_count); - now = apic::now(); - print_hex(now.timer_count); - now = apic::now(); - print_hex(now.timer_count); + step("timer", partial(calibrateTimer, acpi)); + demonstateTimer(); halt(); }