From 312b63822042db9b241ef67634c6d0817af074e1 Mon Sep 17 00:00:00 2001 From: Benjamin Lamowski Date: Fri, 13 Jan 2023 14:31:12 +0100 Subject: [PATCH] base-hw: export Intel TSC platform information Export TSC invariant and the frequency reported on Intel processors. Currently lacks TSC calibration and reports a fixed frequency on AMD. Ref #4826 --- .../src/core/spec/x86_64/platform_support.cc | 6 +- .../base-hw/src/include/hw/spec/x86_64/cpu.h | 26 +++ .../src/include/hw/spec/x86_64/x86_64.h | 162 +++++++++++++++++- 3 files changed, 192 insertions(+), 2 deletions(-) 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 7b3d97cece..68dd4d3820 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 @@ -52,11 +52,15 @@ void Platform::_init_additional_platform_info(Xml_generator &xml) xml.attribute("pitch", boot_fb.pitch); }); }); - xml.node("hardware", [&] () { + xml.node("hardware", [&]() { xml.node("features", [&] () { xml.attribute("svm", Hw::Virtualization_support::has_svm()); xml.attribute("vmx", false); }); + xml.node("tsc", [&]() { + xml.attribute("invariant", Hw::Lapic::invariant_tsc()); + xml.attribute("freq_khz", Hw::Lapic::tsc_freq()); + }); }); } diff --git a/repos/base-hw/src/include/hw/spec/x86_64/cpu.h b/repos/base-hw/src/include/hw/spec/x86_64/cpu.h index a0a558d757..d050821c9e 100644 --- a/repos/base-hw/src/include/hw/spec/x86_64/cpu.h +++ b/repos/base-hw/src/include/hw/spec/x86_64/cpu.h @@ -117,6 +117,17 @@ struct Hw::X86_64_cpu /* AMD host save physical address */ X86_64_MSR_REGISTER(Amd_vm_hsavepa, 0xC0010117); + X86_64_MSR_REGISTER(Platform_id, 0x17, + struct Bus_ratio : Bitfield<8, 5> { }; /* Bus ratio on Core 2, see SDM 19.7.3 */ + ); + + X86_64_MSR_REGISTER(Platform_info, 0xCE, + struct Ratio : Bitfield< 8, 8> { }; /* Maximum Non-Turbo Ratio (R/O) */ + ); + + X86_64_MSR_REGISTER(Fsb_freq, 0xCD, + struct Speed : Bitfield< 0, 3> { }; /* Scaleable Bus Speed (R/O) */ + ); X86_64_MSR_REGISTER(Ia32_efer, 0xC0000080, struct Svme : Bitfield< 12, 1> { }; /* Secure Virtual Machine Enable */ @@ -129,10 +140,17 @@ struct Hw::X86_64_cpu */ X86_64_MSR_REGISTER(Ia32_tsc_aux, 0xc0000103); + X86_64_CPUID_REGISTER(Cpuid_0_eax, 0, eax); X86_64_CPUID_REGISTER(Cpuid_0_ebx, 0, ebx); X86_64_CPUID_REGISTER(Cpuid_0_ecx, 0, ecx); X86_64_CPUID_REGISTER(Cpuid_0_edx, 0, edx); + X86_64_CPUID_REGISTER(Cpuid_1_eax, 1, eax); + + X86_64_CPUID_REGISTER(Cpuid_1_ecx, 1, ecx, + struct Tsc_deadline : Bitfield<24, 1> { }; + ); + X86_64_CPUID_REGISTER(Cpuid_1_edx, 1, edx, struct Pat : Bitfield<16, 1> { }; ); @@ -140,10 +158,18 @@ struct Hw::X86_64_cpu /* Number of address space identifiers (ASID) */ X86_64_CPUID_REGISTER(Amd_nasid, 0x8000000A, ebx); + X86_64_CPUID_REGISTER(Cpuid_15_eax, 15, eax); + X86_64_CPUID_REGISTER(Cpuid_15_ebx, 15, ebx); + X86_64_CPUID_REGISTER(Cpuid_15_ecx, 15, ecx); + X86_64_CPUID_REGISTER(Cpuid_8000000A_edx, 0x8000000A, edx, struct Np : Bitfield<0, 1> { }; /* Nested paging */ ); + X86_64_CPUID_REGISTER(Cpuid_80000007_eax, 0x80000007, eax, + struct Invariant_tsc : Bitfield<2, 1> { }; /* Invariant TSC */ + ); + X86_64_CPUID_REGISTER(Cpuid_80000001_ecx, 0x80000001, ecx, struct Svm : Bitfield<2, 1> { }; ); diff --git a/repos/base-hw/src/include/hw/spec/x86_64/x86_64.h b/repos/base-hw/src/include/hw/spec/x86_64/x86_64.h index 37468a7266..4d130a1641 100644 --- a/repos/base-hw/src/include/hw/spec/x86_64/x86_64.h +++ b/repos/base-hw/src/include/hw/spec/x86_64/x86_64.h @@ -22,6 +22,7 @@ namespace Hw { struct Cpu_memory_map; struct Virtualization_support; class Vendor; + class Lapic; } @@ -40,7 +41,7 @@ struct Hw::Cpu_memory_map } }; -struct Hw::Vendor +class Hw::Vendor { private: static constexpr char const *const vendor_string[] = { @@ -85,6 +86,165 @@ public: return Vendor_id(v); } + + static unsigned get_family() + { + using Cpu = Hw::X86_64_cpu; + + Cpu::Cpuid_1_eax::access_t eax = Cpu::Cpuid_1_eax::read(); + return ((eax >> 8 & 0xf) + (eax >> 20 & 0xff)) & 0xff; + } + + static unsigned get_model() + { + using Cpu = Hw::X86_64_cpu; + + Cpu::Cpuid_1_eax::access_t eax = Cpu::Cpuid_1_eax::read(); + return ((eax >> 4 & 0xf) + (eax >> 12 & 0xf0)) & 0xff; + } +}; + + +class Hw::Lapic +{ + private: + static bool _has_tsc_dl() + { + 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); + } + + /* + * Adapted from Christian Prochaska's and Alexander Boettcher's + * implementation for Nova. + * + * For details, see Vol. 3A of the Intel SDM: + * 19.7.3 Determining the Processor Base Frequency + */ + static unsigned _read_tsc_freq() + { + using Cpu = Hw::X86_64_cpu; + + unsigned freq_tsc = 0U; + + 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::Cpuid_0_eax::access_t eax = Cpu::Cpuid_0_eax::read(); + + if (eax >= Cpu_id_clock) { + Cpu::Cpuid_15_eax::access_t eax = Cpu::Cpuid_15_eax::read(); + Cpu::Cpuid_15_ebx::access_t ebx = Cpu::Cpuid_15_ebx::read(); + Cpu::Cpuid_15_ecx::access_t ecx = Cpu::Cpuid_15_ecx::read(); + + if (eax && ebx) { + if (ecx) + return static_cast( + ((Genode::uint64_t)(ecx) * ebx) / eax / 1000 + ); + + if (family == 6) { + if (model == 0x5c) /* Goldmont */ + freq_tsc = static_cast((19200ull * ebx) / eax); + if (model == 0x55) /* Xeon */ + freq_tsc = static_cast((25000ull * ebx) / eax); + } + + if (!freq_tsc && family >= 6) + freq_tsc = static_cast((24000ull * ebx) / eax); + + if (freq_tsc) + return freq_tsc; + } + + if (family != 6) + return 0; + + 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); + } + } + + return freq_tsc; + } + + static unsigned _measure_tsc_freq() + { + Genode::warning("TSC calibration not yet implemented, using fixed value"); + /* TODO: implement TSC calibration on AMD */ + return 1000000U; + } + + 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