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
This commit is contained in:
Benjamin Lamowski 2025-01-08 10:45:33 +01:00 committed by Christian Helmuth
parent ddeebbe513
commit 89f7834b17
10 changed files with 100 additions and 172 deletions

View File

@ -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;

View File

@ -22,6 +22,7 @@
#include <hw/memory_consts.h>
#include <hw/spec/x86_64/acpi.h>
#include <hw/spec/x86_64/apic.h>
#include <hw/spec/x86_64/x86_64.h>
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<Hw::Acpi_generic *>(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);

View File

@ -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);
});
});
}

View File

@ -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<Vmcb_buf::Tsc_offset>());
state.efer.charge(v.read<Vmcb_buf::Efer>());

View File

@ -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);

View File

@ -599,7 +599,7 @@ void Vmcs::write_vcpu_state(Genode::Vcpu_state &state)
state.actv_state.charge(
static_cast<uint32_t>(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));

View File

@ -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<Pm_tmr_len>() != 4)
return 0;
addr_t const tmr_addr = read<X_pm_tmr_blk_addr>();
if (!tmr_addr)
return 0;
uint8_t const tmr_addr_type =
read<Hw::Acpi_fadt::X_pm_tmr_blk::Addressspace>();
/* 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<Pm1_cnt_len, Pm1a_cnt_blk, Pm1a_cnt_blk_ext::Width,

View File

@ -40,10 +40,11 @@ struct Hw::Pc_board::Serial : Genode::X86_uart
struct Hw::Pc_board::Boot_info
{
Acpi_rsdp acpi_rsdp { };
Framebuffer framebuffer { };
Genode::addr_t efi_system_table { 0 };
Genode::addr_t acpi_fadt { 0 };
Acpi_rsdp acpi_rsdp { };
Framebuffer framebuffer { };
Genode::addr_t efi_system_table { 0 };
Genode::addr_t acpi_fadt { 0 };
Genode::uint32_t tsc_freq_khz { 0 };
Boot_info() {}
Boot_info(Acpi_rsdp const &acpi_rsdp,

View File

@ -22,8 +22,8 @@
namespace Hw {
struct Cpu_memory_map;
struct Virtualization_support;
class Vendor;
class Lapic;
class Vendor;
struct Tsc;
}
@ -107,172 +107,22 @@ public:
};
class Hw::Lapic
struct Hw::Tsc
{
private:
static bool _has_tsc_dl()
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_1_ecx::access_t ecx = Cpu::Cpuid_1_ecx::read();
return (bool)Cpu::Cpuid_1_ecx::Tsc_deadline::get(ecx);
Cpu::Cpuid_80000007_eax::access_t eax = Cpu::Cpuid_80000007_eax::read();
return Cpu::Cpuid_80000007_eax::Invariant_tsc::get(eax);
}
/*
* Adapted from Christian Prochaska's and Alexander Boettcher's
* implementation for Nova.
*
* For details, see Vol. 3B of the Intel SDM (September 2023):
* 20.7.3 Determining the Processor Base Frequency
*/
static unsigned _read_tsc_freq()
{
using Cpu = Hw::X86_64_cpu;
if (Vendor::get_vendor_id() != Vendor::INTEL)
return 0;
unsigned const model = Vendor::get_model();
unsigned const family = Vendor::get_family();
enum
{
Cpu_id_clock = 0x15,
Cpu_id_base_freq = 0x16
};
Cpu::Cpuid_0_eax::access_t eax_0 = Cpu::Cpuid_0_eax::read();
/*
* If CPUID leaf 15 is available, return the frequency reported there.
*/
if (eax_0 >= 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<unsigned>(
((Genode::uint64_t)(ecx_15) * ebx_15) / eax_15 / 1000
);
if (family == 6) {
if (model == 0x5c) /* Goldmont */
return static_cast<unsigned>((19200ull * ebx_15) / eax_15);
if (model == 0x55) /* Xeon */
return static_cast<unsigned>((25000ull * ebx_15) / eax_15);
}
if (family >= 6)
return static_cast<unsigned>((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<unsigned>(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<unsigned>(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<unsigned>(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

View File

@ -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;