From 89f7834b1752e42aefbd718b85a08da43fb88b2e Mon Sep 17 00:00:00 2001 From: Benjamin Lamowski Date: Wed, 8 Jan 2025 10:45:33 +0100 Subject: [PATCH] hw: calibrate TSC via ACPI timer To get the Time Stamp Counter's frequency, hw relied on a complex and incomplete algorithm. Since this is a one-time initialization issue, move TSC calibration to bootstrap and implement it using the ACPI timer. Issue #5215 --- repos/base-hw/src/bootstrap/platform.h | 1 + .../src/bootstrap/spec/x86_64/platform.cc | 27 +++ .../src/core/spec/x86_64/platform_support.cc | 4 +- .../spec/x86_64/virtualization/kernel/svm.cc | 2 +- .../spec/x86_64/virtualization/kernel/vm.cc | 2 +- .../spec/x86_64/virtualization/kernel/vmx.cc | 2 +- .../base-hw/src/include/hw/spec/x86_64/acpi.h | 48 +++++ .../src/include/hw/spec/x86_64/pc_board.h | 9 +- .../src/include/hw/spec/x86_64/x86_64.h | 176 ++---------------- repos/base-hw/src/include/hw/util.h | 1 + 10 files changed, 100 insertions(+), 172 deletions(-) diff --git a/repos/base-hw/src/bootstrap/platform.h b/repos/base-hw/src/bootstrap/platform.h index f9aa11657b..8fb1b5743f 100644 --- a/repos/base-hw/src/bootstrap/platform.h +++ b/repos/base-hw/src/bootstrap/platform.h @@ -27,6 +27,7 @@ namespace Bootstrap { using Genode::addr_t; using Genode::size_t; + using Genode::uint32_t; using Boot_info = Hw::Boot_info<::Board::Boot_info>; using Hw::Mmio_space; using Hw::Mapping; diff --git a/repos/base-hw/src/bootstrap/spec/x86_64/platform.cc b/repos/base-hw/src/bootstrap/spec/x86_64/platform.cc index 624579fce8..3c8b657ee1 100644 --- a/repos/base-hw/src/bootstrap/spec/x86_64/platform.cc +++ b/repos/base-hw/src/bootstrap/spec/x86_64/platform.cc @@ -22,6 +22,7 @@ #include #include #include +#include using namespace Genode; @@ -66,6 +67,30 @@ static Hw::Acpi_rsdp search_rsdp(addr_t area, addr_t area_size) } +static uint32_t calibrate_tsc_frequency(addr_t fadt_addr) +{ + uint32_t const default_freq = 2'400'000; + + if (!fadt_addr) { + warning("FADT not found, returning fixed TSC frequency of ", default_freq, "kHz"); + return default_freq; + } + + uint32_t const sleep_ms = 10; + + Hw::Acpi_fadt fadt(reinterpret_cast(fadt_addr)); + + uint32_t const freq = fadt.calibrate_freq_khz(sleep_ms, []() { return Hw::Tsc::rdtsc(); }); + + if (!freq) { + warning("Unable to calibrate TSC, returning fixed TSC frequency of ", default_freq, "kHz"); + return default_freq; + } + + return freq; +} + + Bootstrap::Platform::Board::Board() : core_mmio(Memory_region { 0, 0x1000 }, @@ -250,6 +275,8 @@ Bootstrap::Platform::Board::Board() cpus = !cpus ? 1 : max_cpus; } + info.tsc_freq_khz = calibrate_tsc_frequency(info.acpi_fadt); + /* copy 16 bit boot code for AP CPUs and for ACPI resume */ addr_t ap_code_size = (addr_t)&_start - (addr_t)&_ap; memcpy((void *)AP_BOOT_CODE_PAGE, &_ap, ap_code_size); diff --git a/repos/base-hw/src/core/spec/x86_64/platform_support.cc b/repos/base-hw/src/core/spec/x86_64/platform_support.cc index d7dcb4528c..7778f15010 100644 --- a/repos/base-hw/src/core/spec/x86_64/platform_support.cc +++ b/repos/base-hw/src/core/spec/x86_64/platform_support.cc @@ -59,8 +59,8 @@ void Platform::_init_additional_platform_info(Xml_generator &xml) xml.attribute("vmx", Hw::Virtualization_support::has_vmx()); }); xml.node("tsc", [&] { - xml.attribute("invariant", Hw::Lapic::invariant_tsc()); - xml.attribute("freq_khz", Hw::Lapic::tsc_freq()); + xml.attribute("invariant", Hw::Tsc::invariant_tsc()); + xml.attribute("freq_khz", _boot_info().plat_info.tsc_freq_khz); }); }); } diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc index 2b5099b415..3e75e67933 100644 --- a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/svm.cc @@ -267,7 +267,7 @@ void Vmcb::write_vcpu_state(Vcpu_state &state) /* Guest activity state (actv) not used by SVM */ state.actv_state.set_charged(); - state.tsc.charge(Hw::Lapic::rdtsc()); + state.tsc.charge(Hw::Tsc::rdtsc()); state.tsc_offset.charge(v.read()); state.efer.charge(v.read()); diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc index 95a24d0642..3b26635afd 100644 --- a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vm.cc @@ -253,7 +253,7 @@ void Board::Vcpu_context::write_vcpu_state(Vcpu_state &state) state.r14.charge(regs->r14); state.r15.charge(regs->r15); - state.tsc.charge(Hw::Lapic::rdtsc()); + state.tsc.charge(Hw::Tsc::rdtsc()); tsc_aux_guest = Cpu::Ia32_tsc_aux::read(); state.tsc_aux.charge(tsc_aux_guest); diff --git a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vmx.cc b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vmx.cc index 83900e6df4..1fca997910 100644 --- a/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vmx.cc +++ b/repos/base-hw/src/core/spec/x86_64/virtualization/kernel/vmx.cc @@ -599,7 +599,7 @@ void Vmcs::write_vcpu_state(Genode::Vcpu_state &state) state.actv_state.charge( static_cast(read(E_GUEST_ACTIVITY_STATE))); - state.tsc.charge(Hw::Lapic::rdtsc()); + state.tsc.charge(Hw::Tsc::rdtsc()); state.tsc_offset.charge(read(E_TSC_OFFSET)); state.efer.charge(read(E_GUEST_IA32_EFER)); diff --git a/repos/base-hw/src/include/hw/spec/x86_64/acpi.h b/repos/base-hw/src/include/hw/spec/x86_64/acpi.h index fc261db406..163f5da473 100644 --- a/repos/base-hw/src/include/hw/spec/x86_64/acpi.h +++ b/repos/base-hw/src/include/hw/spec/x86_64/acpi.h @@ -94,6 +94,8 @@ struct Hw::Acpi_fadt : Genode::Mmio<276> struct Smi_cmd : Register<0x30, 32> { }; struct Acpi_enable : Register<0x34, 8> { }; + struct Pm_tmr_len : Register< 91, 8> { }; + struct Pm1a_cnt_blk : Register < 64, 32> { struct Slp_typ : Bitfield < 10, 3> { }; struct Slp_ena : Bitfield < 13, 1> { }; @@ -123,6 +125,13 @@ struct Hw::Acpi_fadt : Genode::Mmio<276> }; struct Pm1b_cnt_blk_ext_addr : Register < 184 + 4, 64> { }; + struct X_pm_tmr_blk : Register < 208, 32> { + struct Addressspace : Bitfield < 0, 8> { }; + struct Width : Bitfield < 8, 8> { }; + }; + + struct X_pm_tmr_blk_addr : Register < 208 + 4, 64> { }; + struct Gpe0_blk_ext : Register < 220, 32> { struct Addressspace : Bitfield < 0, 8> { }; struct Width : Bitfield < 8, 8> { }; @@ -232,6 +241,45 @@ struct Hw::Acpi_fadt : Genode::Mmio<276> return pm1_a | pm1_b; } + /* see ACPI spec version 6.5 4.8.3.3. Power Management Timer (PM_TMR) */ + uint32_t read_pm_tmr() + { + if (read() != 4) + return 0; + + addr_t const tmr_addr = read(); + + if (!tmr_addr) + return 0; + + uint8_t const tmr_addr_type = + read(); + + /* I/O port address, most likely */ + if (tmr_addr_type == 1) return inl((uint16_t)tmr_addr); + + /* System Memory space address */ + if (tmr_addr_type == 0) return *(uint32_t *)tmr_addr; + + return 0; + } + + uint32_t calibrate_freq_khz(uint32_t sleep_ms, auto get_value_fn) + { + unsigned const acpi_timer_freq = 3'579'545; + + uint32_t const initial = read_pm_tmr(); + + if (!initial) return 0; + + uint64_t const t1 = get_value_fn(); + while ((read_pm_tmr() - initial) < (acpi_timer_freq * sleep_ms / 1000)) + asm volatile ("pause":::"memory"); + uint64_t const t2 = get_value_fn(); + + return (uint32_t)((t2 - t1) / sleep_ms); + } + void write_cnt_blk(unsigned value_a, unsigned value_b) { _write= Cpu_id_clock) { - Cpu::Cpuid_15_eax::access_t eax_15 = Cpu::Cpuid_15_eax::read(); - Cpu::Cpuid_15_ebx::access_t ebx_15 = Cpu::Cpuid_15_ebx::read(); - Cpu::Cpuid_15_ecx::access_t ecx_15 = Cpu::Cpuid_15_ecx::read(); - - if (eax_15 && ebx_15) { - if (ecx_15) - return static_cast( - ((Genode::uint64_t)(ecx_15) * ebx_15) / eax_15 / 1000 - ); - - if (family == 6) { - if (model == 0x5c) /* Goldmont */ - return static_cast((19200ull * ebx_15) / eax_15); - if (model == 0x55) /* Xeon */ - return static_cast((25000ull * ebx_15) / eax_15); - } - - if (family >= 6) - return static_cast((24000ull * ebx_15) / eax_15); - } - } - - - /* - * Specific methods for family 6 models - */ - if (family == 6) { - unsigned freq_tsc = 0U; - - if (model == 0x2a || - model == 0x2d || /* Sandy Bridge */ - model >= 0x3a) /* Ivy Bridge and later */ - { - Cpu::Platform_info::access_t platform_info = Cpu::Platform_info::read(); - Genode::uint64_t ratio = Cpu::Platform_info::Ratio::get(platform_info); - freq_tsc = static_cast(ratio * 100000); - } else if (model == 0x1a || - model == 0x1e || - model == 0x1f || - model == 0x2e || /* Nehalem */ - model == 0x25 || - model == 0x2c || - model == 0x2f) /* Xeon Westmere */ - { - Cpu::Platform_info::access_t platform_info = Cpu::Platform_info::read(); - Genode::uint64_t ratio = Cpu::Platform_info::Ratio::get(platform_info); - freq_tsc = static_cast(ratio * 133330); - } else if (model == 0x17 || model == 0xf) { /* Core 2 */ - Cpu::Fsb_freq::access_t fsb_freq = Cpu::Fsb_freq::read(); - Genode::uint64_t freq_bus = Cpu::Fsb_freq::Speed::get(fsb_freq); - - switch (freq_bus) { - case 0b101: freq_bus = 100000; break; - case 0b001: freq_bus = 133330; break; - case 0b011: freq_bus = 166670; break; - case 0b010: freq_bus = 200000; break; - case 0b000: freq_bus = 266670; break; - case 0b100: freq_bus = 333330; break; - case 0b110: freq_bus = 400000; break; - default: freq_bus = 0; break; - } - - Cpu::Platform_id::access_t platform_id = Cpu::Platform_id::read(); - Genode::uint64_t ratio = Cpu::Platform_id::Bus_ratio::get(platform_id); - - freq_tsc = static_cast(freq_bus * ratio); - } - - if (!freq_tsc) - Genode::warning("TSC: family 6 Intel platform info reports bus frequency of 0"); - else - return freq_tsc; - } - - - /* - * Finally, using Processor Frequency Information for a rough estimate - */ - if (eax_0 >= Cpu_id_base_freq) { - Cpu::Cpuid_16_eax::access_t base_mhz = Cpu::Cpuid_16_eax::read(); - - if (base_mhz) { - Genode::warning("TSC: using processor base frequency: ", base_mhz, " MHz"); - return base_mhz * 1000; - } else { - Genode::warning("TSC: CPUID reported processor base frequency of 0"); - } - } - - return 0; - } - - static unsigned _measure_tsc_freq() - { - const unsigned Tsc_fixed_value = 2400; - - Genode::warning("TSC: calibration not yet implemented, using fixed value of ", Tsc_fixed_value, " MHz"); - /* TODO: implement TSC calibration on AMD */ - return Tsc_fixed_value * 1000; - } - - public: - static Genode::uint64_t rdtsc() - { - Genode::uint32_t low, high; - asm volatile("rdtsc" : "=a"(low), "=d"(high)); - return (Genode::uint64_t)(high) << 32 | low; - } - - static bool invariant_tsc() - { - using Cpu = Hw::X86_64_cpu; - - Cpu::Cpuid_80000007_eax::access_t eax = - Cpu::Cpuid_80000007_eax::read(); - return Cpu::Cpuid_80000007_eax::Invariant_tsc::get(eax); - } - - static unsigned tsc_freq() - { - unsigned freq = _read_tsc_freq(); - if (freq) - return freq; - else - return _measure_tsc_freq(); - } }; struct Hw::Virtualization_support diff --git a/repos/base-hw/src/include/hw/util.h b/repos/base-hw/src/include/hw/util.h index 8774123321..e00cf2f56f 100644 --- a/repos/base-hw/src/include/hw/util.h +++ b/repos/base-hw/src/include/hw/util.h @@ -21,6 +21,7 @@ namespace Hw { using Genode::addr_t; using Genode::size_t; + using Genode::uint32_t; using Genode::get_page_size; using Genode::get_page_size_log2;