hw: calibrate Local APIC via ACPI timer

Upto now, bootstrap used the Programmable Interval Timer to set a
suitable divider and determine the frequency of the Local APIC.
The PIT is not available on recent x86_64 hardware anymore.

Move Local APIC calibration to bootstrap and use the ACPI timer as a
reference. Clean up hw's timer implementation a little and disable the
PIT in bootstrap.

Fixes #5215
This commit is contained in:
Benjamin Lamowski
2025-01-09 11:31:42 +01:00
committed by Christian Helmuth
parent 103d03b590
commit 8bdddbd46a
22 changed files with 176 additions and 155 deletions

View File

@@ -11,13 +11,14 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _CORE__SPEC__X86_64__PORT_IO_H_
#define _CORE__SPEC__X86_64__PORT_IO_H_
#ifndef _INCLUDE__SPEC__X86_64__PORT_IO_H_
#define _INCLUDE__SPEC__X86_64__PORT_IO_H_
/* core includes */
#include <types.h>
#include <base/fixed_stdint.h>
namespace Core {
namespace Hw {
using Genode::uint8_t;
using Genode::uint16_t;
/**
* Read byte from I/O port
@@ -38,4 +39,4 @@ namespace Core {
}
}
#endif /* _CORE__SPEC__X86_64__PORT_IO_H_ */
#endif /* _INCLUDE__SPEC__X86_64__PORT_IO_H_ */

View File

@@ -24,7 +24,7 @@ SRC_CC += spec/x86_64/virtualization/kernel/svm.cc
SRC_CC += spec/x86_64/virtualization/kernel/vmx.cc
SRC_CC += kernel/lock.cc
SRC_CC += spec/x86_64/pic.cc
SRC_CC += spec/x86_64/pit.cc
SRC_CC += spec/x86_64/timer.cc
SRC_CC += spec/x86_64/kernel/thread_exception.cc
SRC_CC += spec/x86_64/platform_support.cc
SRC_CC += spec/x86_64/virtualization/platform_services.cc

View File

@@ -18,6 +18,7 @@
#include <platform.h>
#include <multiboot.h>
#include <multiboot2.h>
#include <port_io.h>
#include <hw/memory_consts.h>
#include <hw/spec/x86_64/acpi.h>
@@ -91,6 +92,58 @@ static uint32_t calibrate_tsc_frequency(addr_t fadt_addr)
}
static void calibrate_lapic_frequency(addr_t fadt_addr, uint32_t &ticks_per_ms, uint32_t &div)
{
uint32_t const default_ticks_per_ms = TIMER_MIN_TICKS_PER_MS;
uint32_t const sleep_ms = 10;
if (!fadt_addr) {
warning("FADT not found, setting minimum Local APIC frequency of ", default_ticks_per_ms, "kHz");
ticks_per_ms = default_ticks_per_ms;
}
Hw::Acpi_fadt fadt(reinterpret_cast<Hw::Acpi_generic *>(fadt_addr));
Hw::Local_apic lapic(Hw::Cpu_memory_map::lapic_phys_base());
ticks_per_ms = 0;
lapic.calibrate_divider(ticks_per_ms, div, [&]() {
return fadt.calibrate_freq_khz(sleep_ms, [&]() {
return lapic.read<Hw::Local_apic::Tmr_current>();;
}, true);
});
if (!ticks_per_ms) {
warning("FADT not found, setting minimum Local APIC frequency of ", default_ticks_per_ms, "kHz");
ticks_per_ms = default_ticks_per_ms;
}
}
static void disable_pit()
{
using Hw::outb;
enum {
/* PIT constants */
PIT_CH0_DATA = 0x40,
PIT_MODE = 0x43,
};
struct Calibration_failed : Genode::Exception { };
/**
* Disable PIT timer channel. This is necessary since BIOS sets up
* channel 0 to fire periodically.
*/
outb(PIT_MODE, 0x30);
outb(PIT_CH0_DATA, 0);
outb(PIT_CH0_DATA, 0);
}
Bootstrap::Platform::Board::Board()
:
core_mmio(Memory_region { 0, 0x1000 },
@@ -275,8 +328,11 @@ Bootstrap::Platform::Board::Board()
cpus = !cpus ? 1 : max_cpus;
}
calibrate_lapic_frequency(info.acpi_fadt, info.lapic_ticks_per_ms, info.lapic_div);
info.tsc_freq_khz = calibrate_tsc_frequency(info.acpi_fadt);
disable_pit();
/* 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

@@ -21,7 +21,7 @@
/* base-hw core includes */
#include <spec/x86_64/pic.h>
#include <spec/x86_64/pit.h>
#include <spec/x86_64/timer.h>
#include <spec/x86_64/cpu.h>
namespace Board {

View File

@@ -47,6 +47,8 @@ Local_interrupt_controller(Global_interrupt_controller &global_irq_ctrl)
void Local_interrupt_controller::init()
{
using Hw::outb;
/* Start initialization sequence in cascade mode */
outb(PIC_CMD_MASTER, 0x11);
outb(PIC_CMD_SLAVE, 0x11);

View File

@@ -1,80 +0,0 @@
/*
* \brief Timer driver for core
* \author Adrian-Ken Rueegsegger
* \author Reto Buerki
* \date 2015-02-06
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SRC__CORE__SPEC__ARM__PIT_H_
#define _SRC__CORE__SPEC__ARM__PIT_H_
/* Genode includes */
#include <util/mmio.h>
#include <base/stdint.h>
/* core includes */
#include <port_io.h>
namespace Board { class Timer; }
/**
* LAPIC-based timer driver for core
*/
struct Board::Timer: Genode::Mmio<Hw::Cpu_memory_map::LAPIC_SIZE>
{
enum {
/* PIT constants */
PIT_TICK_RATE = 1193182ul,
PIT_SLEEP_MS = 50,
PIT_SLEEP_TICS = (PIT_TICK_RATE / 1000) * PIT_SLEEP_MS,
PIT_CH0_DATA = 0x40,
PIT_CH2_DATA = 0x42,
PIT_CH2_GATE = 0x61,
PIT_MODE = 0x43,
};
/* Timer registers */
struct Tmr_lvt : Register<0x320, 32>
{
struct Vector : Bitfield<0, 8> { };
struct Delivery : Bitfield<8, 3> { };
struct Mask : Bitfield<16, 1> { };
struct Timer_mode : Bitfield<17, 2> { };
};
struct Tmr_initial : Register <0x380, 32> { };
struct Tmr_current : Register <0x390, 32> { };
struct Divide_configuration : Register <0x03e0, 32>
{
struct Divide_value_0_2 : Bitfield<0, 2> { };
struct Divide_value_2_1 : Bitfield<3, 1> { };
struct Divide_value :
Genode::Bitset_2<Divide_value_0_2, Divide_value_2_1>
{
enum { MAX = 6 };
};
};
struct Calibration_failed : Genode::Exception { };
Divide_configuration::access_t divider = 0;
Genode::uint32_t ticks_per_ms = 0;
/* Measure LAPIC timer frequency using PIT channel 2 */
Genode::uint32_t pit_calc_timer_freq(void);
Timer(unsigned);
void init();
};
#endif /* _SRC__CORE__SPEC__ARM__PIT_H_ */

View File

@@ -15,9 +15,6 @@
#include <hw/spec/x86_64/x86_64.h>
/* Genode includes */
#include <drivers/timer/util.h>
/* core includes */
#include <kernel/timer.h>
#include <platform.h>
@@ -25,37 +22,9 @@
using namespace Core;
using namespace Kernel;
uint32_t Board::Timer::pit_calc_timer_freq(void)
{
uint32_t t_start, t_end;
/* set channel gate high and disable speaker */
outb(PIT_CH2_GATE, (uint8_t)((inb(0x61) & ~0x02) | 0x01));
/* set timer counter (mode 0, binary count) */
outb(PIT_MODE, 0xb0);
outb(PIT_CH2_DATA, PIT_SLEEP_TICS & 0xff);
outb(PIT_CH2_DATA, PIT_SLEEP_TICS >> 8);
write<Tmr_initial>(~0U);
t_start = read<Tmr_current>();
while ((inb(PIT_CH2_GATE) & 0x20) == 0)
{
asm volatile("pause" : : : "memory");
}
t_end = read<Tmr_current>();
write<Tmr_initial>(0);
return (t_start - t_end) / PIT_SLEEP_MS;
}
Board::Timer::Timer(unsigned)
:
Mmio({(char *)Platform::mmio_to_virt(Hw::Cpu_memory_map::lapic_phys_base()), Mmio::SIZE})
Local_apic(Platform::mmio_to_virt(Hw::Cpu_memory_map::lapic_phys_base()))
{
init();
}
@@ -75,28 +44,10 @@ void Board::Timer::init()
return;
}
/* calibrate LAPIC frequency to fullfill our requirements */
for (Divide_configuration::access_t div = Divide_configuration::Divide_value::MAX;
div && ticks_per_ms < TIMER_MIN_TICKS_PER_MS; div--)
{
if (!div){
raw("Failed to calibrate timer frequency");
throw Calibration_failed();
}
write<Divide_configuration::Divide_value>((uint8_t)div);
/* Calculate timer frequency */
ticks_per_ms = pit_calc_timer_freq();
divider = div;
}
/**
* Disable PIT timer channel. This is necessary since BIOS sets up
* channel 0 to fire periodically.
*/
outb(Board::Timer::PIT_MODE, 0x30);
outb(Board::Timer::PIT_CH0_DATA, 0);
outb(Board::Timer::PIT_CH0_DATA, 0);
Platform::apply_with_boot_info([&](auto const &boot_info) {
ticks_per_ms = boot_info.plat_info.lapic_ticks_per_ms;
divider = boot_info.plat_info.lapic_div;
});
}

View File

@@ -0,0 +1,40 @@
/*
* \brief Timer driver for core
* \author Adrian-Ken Rueegsegger
* \author Reto Buerki
* \date 2015-02-06
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SRC__CORE__SPEC__ARM__PIT_H_
#define _SRC__CORE__SPEC__ARM__PIT_H_
/* Genode includes */
#include <base/stdint.h>
/* hw includes */
#include <hw/spec/x86_64/apic.h>
namespace Board { class Timer; }
/**
* LAPIC-based timer driver for core
*/
struct Board::Timer: public Hw::Local_apic
{
Divide_configuration::access_t divider = 0;
Genode::uint32_t ticks_per_ms = 0;
Timer(unsigned);
void init();
};
#endif /* _SRC__CORE__SPEC__ARM__PIT_H_ */

View File

@@ -264,7 +264,7 @@ struct Hw::Acpi_fadt : Genode::Mmio<276>
return 0;
}
uint32_t calibrate_freq_khz(uint32_t sleep_ms, auto get_value_fn)
uint32_t calibrate_freq_khz(uint32_t sleep_ms, auto get_value_fn, bool reverse = false)
{
unsigned const acpi_timer_freq = 3'579'545;
@@ -277,7 +277,7 @@ struct Hw::Acpi_fadt : Genode::Mmio<276>
asm volatile ("pause":::"memory");
uint64_t const t2 = get_value_fn();
return (uint32_t)((t2 - t1) / sleep_ms);
return (uint32_t)((reverse ? (t1 - t2) : (t2 - t1)) / sleep_ms);
}
void write_cnt_blk(unsigned value_a, unsigned value_b)

View File

@@ -18,6 +18,9 @@ namespace Hw { class Local_apic; }
#include <hw/spec/x86_64/x86_64.h>
/* Genode includes */
#include <drivers/timer/util.h>
struct Hw::Local_apic : Genode::Mmio<Hw::Cpu_memory_map::LAPIC_SIZE>
{
struct Id : Register<0x020, 32> { };
@@ -58,6 +61,52 @@ struct Hw::Local_apic : Genode::Mmio<Hw::Cpu_memory_map::LAPIC_SIZE>
struct Destination : Bitfield<24, 8> { };
};
/* Timer registers */
struct Tmr_lvt : Register<0x320, 32>
{
struct Vector : Bitfield<0, 8> { };
struct Delivery : Bitfield<8, 3> { };
struct Mask : Bitfield<16, 1> { };
struct Timer_mode : Bitfield<17, 2> { };
};
struct Tmr_initial : Register <0x380, 32> { };
struct Tmr_current : Register <0x390, 32> { };
struct Divide_configuration : Register <0x03e0, 32>
{
struct Divide_value_0_2 : Bitfield<0, 2> { };
struct Divide_value_2_1 : Bitfield<3, 1> { };
struct Divide_value :
Genode::Bitset_2<Divide_value_0_2, Divide_value_2_1>
{
enum { MAX = 6 };
};
};
void calibrate_divider(uint32_t &ticks_per_ms, uint32_t &divider, auto calibration_fn)
{
/* calibrate LAPIC frequency to fullfill our requirements */
for (Divide_configuration::access_t div = Divide_configuration::Divide_value::MAX;
div && ticks_per_ms < TIMER_MIN_TICKS_PER_MS; div--)
{
if (!div) {
raw("Failed to calibrate Local APIC frequency");
ticks_per_ms = 0;
break;
}
write<Divide_configuration::Divide_value>((uint8_t)div);
write<Tmr_initial>(~0U);
/* Calculate timer frequency */
ticks_per_ms = calibration_fn();
divider = div;
write<Tmr_initial>(0);
}
}
Local_apic(addr_t const addr) : Mmio({(char*)addr, Mmio::SIZE}) {}
};

View File

@@ -45,6 +45,8 @@ struct Hw::Pc_board::Boot_info
Genode::addr_t efi_system_table { 0 };
Genode::addr_t acpi_fadt { 0 };
Genode::uint32_t tsc_freq_khz { 0 };
Genode::uint32_t lapic_ticks_per_ms { 0 };
Genode::uint32_t lapic_div { 0 };
Boot_info() {}
Boot_info(Acpi_rsdp const &acpi_rsdp,

View File

@@ -1 +1 @@
2024-12-10 19af2857787095f29ff1156ca12921a84b8c2c88
2025-01-16 2cdd7e0cf9eab4981f58b89b33b9a106c3c2bd33

View File

@@ -1 +1 @@
2024-12-10 0cb59fb39e1ef2c557804fe74904f262e7cc990d
2025-01-16 9f4e7fa4380d2eb941adb197daf5952916a71463

View File

@@ -1 +1 @@
2024-12-10 6cfb903adb9b3a37335b8a153735ea5f2add9479
2025-01-16 95ced46da790f7a009eb0522f132fe2335bce645

View File

@@ -1 +1 @@
2024-12-10 f6e7dcb77f24f2f69afe225449744ca3f78e94ac
2025-01-16 befac925d2cc4461c3fd3fe63877f05e338e80ce

View File

@@ -1 +1 @@
2024-12-10 11b1ec97e8ebc727801208d4d1c5c1066e7037ed
2025-01-16 a67c57084fc6a10686dc283c7479ef3c05065b5f

View File

@@ -1 +1 @@
2024-12-10 121ec4ccd1bb65240ce5000ca49a48bf58bb74d8
2025-01-16 67e33ff7cf31a432bd8d93a0394ebc55199e888d

View File

@@ -1 +1 @@
2024-12-10 fe0faac47649d3c25dfc42bb764f18802862def9
2025-01-16 049b93c7a4e9bfd6b8637aab8f37f774bcf9a5dc

View File

@@ -1 +1 @@
2024-12-10 7fdaf553f171d5af35643f456d09af93f4167e69
2025-01-16 3e48430b91da4cb566a1b6f7c3fec02a915a782d

View File

@@ -1 +1 @@
2024-12-10 126809511e7c96a49bcc4109ccea98102afc9495
2025-01-16 473ed002fe1d43c491db3c9730774d09e16f8b2a

View File

@@ -1 +1 @@
2024-12-10 855cac282025e574e9a9619bfc063dbd0fa3700d
2025-01-16 f21f3cbf85a383a0d03729c0aa0853815c8d67a1

View File

@@ -1 +1 @@
2024-12-10 f87120b971a2fe5732884f78ecd127c86eb6ce28
2025-01-16 376e356fec3b8b0dd163d2f6408f8c9796618b7d