hw: always serialize rdtsc reads

While implementing TSC calibration in #5215, the issue of properly serializing
TSC reads came up. Some learnings of the discussion were noted in #5430.

Using `cpuid` for serialization as in Trace::timestamp() is portable,
but will cause VM exits on VMX and SVM and is therefore unsuitable to
retain a roughly working calibration loop while running virtualized.
On the other hand on most AMD systems, dispatch serializing `lfence`
needs to be explicitly enabled via a non-architectural MSR.

Enable setting up dispatch serializing lfence on AMD systems and always
serialize rdtsc accesses in Hw::Tsc::rdtsc() for maximum reliability.

Issues #5215, #5430
This commit is contained in:
Benjamin Lamowski 2025-01-21 16:26:52 +01:00 committed by Christian Helmuth
parent ec5e1a6b4b
commit 02b7878229
3 changed files with 61 additions and 3 deletions

View File

@ -131,7 +131,7 @@ static void disable_pit()
PIT_MODE = 0x43,
};
/**
/*
* Disable PIT timer channel. This is necessary since BIOS sets up
* channel 0 to fire periodically.
*/
@ -141,6 +141,35 @@ static void disable_pit()
}
/*
* Enable dispatch serializing lfence instruction on AMD processors
*
* See Software techniques for managing speculation on AMD processors
* Revision 5.09.23
* Mitigation G-2
*/
static void amd_enable_serializing_lfence()
{
using Cpu = Hw::X86_64_cpu;
if (Hw::Vendor::get_vendor_id() != Hw::Vendor::Vendor_id::AMD)
return;
unsigned const family = Hw::Vendor::get_family();
/*
* In family 0Fh and 11h, lfence is always dispatch serializing and
* "AMD plans support for this MSR and access to this bit for all future
* processors." from family 14h on.
*/
if ((family == 0x10) || (family == 0x12) || (family >= 0x14)) {
Cpu::Amd_lfence::access_t amd_lfence = Cpu::Amd_lfence::read();
Cpu::Amd_lfence::Enable_dispatch_serializing::set(amd_lfence);
Cpu::Amd_lfence::write(amd_lfence);
}
}
Bootstrap::Platform::Board::Board()
:
core_mmio(Memory_region { 0, 0x1000 },
@ -325,6 +354,14 @@ Bootstrap::Platform::Board::Board()
cpus = !cpus ? 1 : max_cpus;
}
/*
* Enable serializing lfence on supported AMD processors
*
* For APs this will be set up later, but we need it already to obtain
* the most acurate results when calibrating the TSC frequency.
*/
amd_enable_serializing_lfence();
auto r = calibrate_lapic_frequency(info.acpi_fadt);
info.lapic_freq_khz = r.freq_khz;
info.lapic_div = r.div;
@ -401,9 +438,12 @@ unsigned Bootstrap::Platform::enable_mmu()
if (board.cpus <= 1)
return (unsigned)cpu_id;
if (!Cpu::IA32_apic_base::Bsp::get(lapic_msr))
if (!Cpu::IA32_apic_base::Bsp::get(lapic_msr)) {
/* AP - done */
/* enable serializing lfence on supported AMD processors. */
amd_enable_serializing_lfence();
return (unsigned)cpu_id;
}
/* BSP - we're primary CPU - wake now all other CPUs */

View File

@ -118,6 +118,12 @@ struct Hw::X86_64_cpu
/* AMD host save physical address */
X86_64_MSR_REGISTER(Amd_vm_hsavepa, 0xC0010117);
/* Non-architectural MSR used to make lfence serializing */
X86_64_MSR_REGISTER(Amd_lfence, 0xC0011029,
struct Enable_dispatch_serializing : Bitfield<1, 1> { }; /* Enable lfence dispatch serializing */
)
X86_64_MSR_REGISTER(Platform_id, 0x17,
struct Bus_ratio : Bitfield<8, 5> { }; /* Bus ratio on Core 2, see SDM 19.7.3 */
);

View File

@ -109,10 +109,22 @@ public:
struct Hw::Tsc
{
/*
* Provide serialized access to the Timestamp Counter
*
* See #5430 for more information.
*/
static Genode::uint64_t rdtsc()
{
Genode::uint32_t low, high;
asm volatile("rdtsc" : "=a"(low), "=d"(high));
asm volatile(
"lfence;"
"rdtsc;"
"lfence;"
: "=a"(low), "=d"(high)
:
: "memory"
);
return (Genode::uint64_t)(high) << 32 | low;
}