mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-15 06:57:12 +00:00
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:
parent
ddeebbe513
commit
89f7834b17
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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>());
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user