feat: add fadt based lapic timer calibration
This commit is contained in:
parent
54d48e7d91
commit
61dfda6a70
7 changed files with 257 additions and 28 deletions
|
|
@ -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<signature>(header->signature)) {
|
||||
ACPI_DISPATCH(madt, madt_t)
|
||||
ACPI_DISPATCH(fadt, fadt_t)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
17
kernel/include/acpi/discovery.h
Normal file
17
kernel/include/acpi/discovery.h
Normal file
|
|
@ -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> rsdp;
|
||||
optional<Madt> madt;
|
||||
// TODO(wiring): capture the FADT in setupAcpi and store it here
|
||||
optional<Fadt> fadt;
|
||||
};
|
||||
|
||||
} // namespace acpi
|
||||
98
kernel/include/acpi/fadt.h
Normal file
98
kernel/include/acpi/fadt.h
Normal file
|
|
@ -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<fadt_t>()) {}
|
||||
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<uint16_t>(table->pm_timer_block);
|
||||
}
|
||||
|
||||
bool pm_timer_is_32bit() const {
|
||||
return table->flags.timer_value_extended != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const fadt_t* table;
|
||||
};
|
||||
|
||||
} // namespace acpi
|
||||
|
|
@ -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
|
||||
|
|
|
|||
33
kernel/include/util/port_io.h
Normal file
33
kernel/include/util/port_io.h
Normal file
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "apic/timer.h"
|
||||
|
||||
#include "util/cpuid.h"
|
||||
#include "util/port_io.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -46,24 +47,59 @@ optional<uint64_t> tscFrequencyFromProcessorBase() {
|
|||
});
|
||||
}
|
||||
|
||||
optional<uint64_t> 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<uint64_t>(high) << 32) | low;
|
||||
}
|
||||
|
||||
[[maybe_unused]] optional<uint64_t>
|
||||
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<uint64_t> 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;
|
||||
|
|
|
|||
|
|
@ -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<acpi::Rsdp> rsdp;
|
||||
optional<acpi::Madt> 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<acpi::Madt> 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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue