kernel/x86: Determine the APIC frequency in the kernel instead of the bootloader.

We use the TSC to measure the APIC frequency, but the TSC frequency
is only fully initialized in the kernel (where it's read from CPUID
on recent hardware).

Furthermore, on some more recent systems, it seems there may only be
X2APIC and no MMIO APIC timer at all, and the bootloader does not
handle that case.

The method in the kernel uses spin() instead of a fixed instruction
count as well as system_time_nsecs() and a double for (hopefully)
more accurate calibration.

At least on VMware, this method seems more accurate: the APIC frequency
read from the hypervisor CPUID leaf is 66,000,000; the old method in
in the bootloader yielded values like "65,801,075" and "65,106,382",
while the new one yields values like "65,963,920" and "65,962,580"
(those pairs of values are from two consecutive boots.)

The difference was sometimes similar (but smaller as a percentage) in
QEMU with software emulation: e.g. "993,218,085" vs. "992,965,761". But
sometimes it wasn't: e.g "991,619,585" vs. "993,689,669". (QEMU in
software mode doesn't report a frequency via hypervisor CPUID leaf.)

Change-Id: I4fb8535d1d984f13867e2f84e7dfad1ceed42c13
Reviewed-on: https://review.haiku-os.org/c/haiku/+/8999
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Augustin Cavalier
2025-02-18 21:04:59 +00:00
committed by waddlesplash
parent b801f14f55
commit 3d840c4289
11 changed files with 59 additions and 176 deletions
+1
View File
@@ -131,6 +131,7 @@ uint32 apic_lvt_error();
void apic_set_lvt_error(uint32 config); void apic_set_lvt_error(uint32 config);
uint32 apic_lvt_initial_timer_count(); uint32 apic_lvt_initial_timer_count();
void apic_set_lvt_initial_timer_count(uint32 config); void apic_set_lvt_initial_timer_count(uint32 config);
uint32 apic_lvt_current_timer_count();
uint32 apic_lvt_timer_divide_config(); uint32 apic_lvt_timer_divide_config();
void apic_set_lvt_timer_divide_config(uint32 config); void apic_set_lvt_timer_divide_config(uint32 config);
@@ -29,7 +29,7 @@ typedef struct {
uint64 virtual_end; uint64 virtual_end;
uint64 page_hole; uint64 page_hole;
// smp stuff // smp stuff
uint32 apic_time_cv_factor; // apic ticks per second uint32 _unused; // previously: apic ticks per second
uint32 apic_phys; uint32 apic_phys;
FixedWidthPointer<void> apic; FixedWidthPointer<void> apic;
uint32 ioapic_phys; uint32 ioapic_phys;
@@ -44,8 +44,6 @@ static struct scan_spots_struct smp_scan_spots[] = {
{ 0, 0, 0 } { 0, 0, 0 }
}; };
extern "C" void execute_n_instructions(int count);
extern "C" void smp_trampoline(void); extern "C" void smp_trampoline(void);
extern "C" void smp_trampoline_end(void); extern "C" void smp_trampoline_end(void);
@@ -319,41 +317,6 @@ smp_do_acpi_config(void)
} }
static void
calculate_apic_timer_conversion_factor(void)
{
int64 t1, t2;
uint32 config;
uint32 count;
// setup the timer
config = apic_read(APIC_LVT_TIMER);
config = (config & APIC_LVT_TIMER_MASK) + APIC_LVT_MASKED;
// timer masked, vector 0
apic_write(APIC_LVT_TIMER, config);
config = (apic_read(APIC_TIMER_DIVIDE_CONFIG) & ~0x0000000f);
apic_write(APIC_TIMER_DIVIDE_CONFIG, config | APIC_TIMER_DIVIDE_CONFIG_1);
// divide clock by one
t1 = system_time();
apic_write(APIC_INITIAL_TIMER_COUNT, 0xffffffff); // start the counter
execute_n_instructions(128 * 20000);
count = apic_read(APIC_CURRENT_TIMER_COUNT);
t2 = system_time();
count = 0xffffffff - count;
gKernelArgs.arch_args.apic_time_cv_factor
= (uint32)((1000000.0/(t2 - t1)) * count);
dprintf("APIC ticks/sec = %d\n",
gKernelArgs.arch_args.apic_time_cv_factor);
}
// #pragma mark - // #pragma mark -
@@ -403,9 +366,6 @@ smp_init_other_cpus(void)
dprintf("smp: apic (mapped) = %p\n", (void *)gKernelArgs.arch_args.apic); dprintf("smp: apic (mapped) = %p\n", (void *)gKernelArgs.arch_args.apic);
// calculate how fast the apic timer is
calculate_apic_timer_conversion_factor();
if (gKernelArgs.num_cpus < 2) if (gKernelArgs.num_cpus < 2)
return; return;
@@ -7,29 +7,6 @@
#define FUNCTION(x) .global x; .type x,@function; x #define FUNCTION(x) .global x; .type x,@function; x
FUNCTION(execute_n_instructions):
movl 4(%esp), %ecx
shrl $4, %ecx
.again:
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
loop .again
ret
null_idt_descr: null_idt_descr:
.word 0 .word 0
.word 0,0 .word 0,0
@@ -20,7 +20,6 @@ for platform in [ MultiBootSubDirSetup efi ] {
crt0-efi-$(TARGET_ARCH).S crt0-efi-$(TARGET_ARCH).S
entry.S entry.S
smp_trampoline.S smp_trampoline.S
support.S
relocation_func.cpp relocation_func.cpp
arch_mmu.cpp arch_mmu.cpp
arch_smp.cpp arch_smp.cpp
@@ -36,8 +36,6 @@
#endif #endif
extern "C" void execute_n_instructions(int count);
void copy_trampoline_code(uint64 trampolineCode, uint64 trampolineStack); void copy_trampoline_code(uint64 trampolineCode, uint64 trampolineStack);
void prepare_trampoline_args(uint64 trampolineCode, uint64 trampolineStack, void prepare_trampoline_args(uint64 trampolineCode, uint64 trampolineStack,
uint32 pagedir, uint64 kernelEntry, addr_t virtKernelArgs, uint32 pagedir, uint64 kernelEntry, addr_t virtKernelArgs,
@@ -125,43 +123,6 @@ acpi_do_smp_config(void)
} }
static void
calculate_apic_timer_conversion_factor(void)
{
int64 t1, t2;
uint32 config;
uint32 count;
TRACE("calculating apic timer conversion factor\n");
// setup the timer
config = apic_read(APIC_LVT_TIMER);
config = (config & APIC_LVT_TIMER_MASK) + APIC_LVT_MASKED;
// timer masked, vector 0
apic_write(APIC_LVT_TIMER, config);
config = (apic_read(APIC_TIMER_DIVIDE_CONFIG) & ~0x0000000f);
apic_write(APIC_TIMER_DIVIDE_CONFIG, config | APIC_TIMER_DIVIDE_CONFIG_1);
// divide clock by one
t1 = system_time();
apic_write(APIC_INITIAL_TIMER_COUNT, 0xffffffff); // start the counter
execute_n_instructions(128 * 20000);
count = apic_read(APIC_CURRENT_TIMER_COUNT);
t2 = system_time();
count = 0xffffffff - count;
gKernelArgs.arch_args.apic_time_cv_factor
= (uint32)((1000000.0/(t2 - t1)) * count);
TRACE("APIC ticks/sec = %" B_PRId32 "\n",
gKernelArgs.arch_args.apic_time_cv_factor);
}
// #pragma mark - // #pragma mark -
@@ -211,9 +172,6 @@ arch_smp_init_other_cpus(void)
TRACE("smp: apic (mapped) = %lx\n", (addr_t)gKernelArgs.arch_args.apic.Pointer()); TRACE("smp: apic (mapped) = %lx\n", (addr_t)gKernelArgs.arch_args.apic.Pointer());
// calculate how fast the apic timer is
calculate_apic_timer_conversion_factor();
if (gKernelArgs.num_cpus < 2) if (gKernelArgs.num_cpus < 2)
return; return;
@@ -1,35 +0,0 @@
/*
* Copyright 2021-2022 Haiku, Inc. All rights reserved.
* Released under the terms of the MIT License.
*
* Copyright 2001, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#define FUNCTION(x) .global x; .type x,@function; x
FUNCTION(execute_n_instructions):
movl 4(%esp), %ecx
shrl $4, %ecx
.again:
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
loop .again
ret
@@ -19,7 +19,6 @@ for platform in [ MultiBootSubDirSetup efi ] {
crt0-efi-$(TARGET_ARCH).S crt0-efi-$(TARGET_ARCH).S
entry.S entry.S
long_smp_trampoline.S long_smp_trampoline.S
support.S
relocation_func.cpp relocation_func.cpp
arch_start.cpp arch_start.cpp
arch_smp.cpp arch_smp.cpp
@@ -1,31 +0,0 @@
/*
** Copyright 2001, Travis Geiselbrecht. All rights reserved.
** Distributed under the terms of the NewOS License.
*/
#define FUNCTION(x) .global x; .type x,@function; x
FUNCTION(execute_n_instructions):
movl %edi, %ecx
shrl $4, %ecx
.again:
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
xorl %eax, %eax
loop .again
ret
+10
View File
@@ -236,6 +236,16 @@ apic_set_lvt_initial_timer_count(uint32 config)
} }
uint32
apic_lvt_current_timer_count()
{
if (sX2APIC)
return x86_read_msr(IA32_MSR_APIC_CURRENT_TIMER_COUNT);
else
return apic_read(APIC_CURRENT_TIMER_COUNT);
}
uint32 uint32
apic_lvt_timer_divide_config() apic_lvt_timer_divide_config()
{ {
+47 -2
View File
@@ -1,6 +1,7 @@
/* /*
* Copyright 2025, Haiku, Inc. All rights reserved.
* Copyright 2008, Dustin Howett, [email protected]. All rights reserved. * Copyright 2008, Dustin Howett, [email protected]. All rights reserved.
* Copyright 2002-2005, Axel Dörfler, [email protected]. All rights reserved. * Copyright 2002-2010, Axel Dörfler, [email protected]. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
* *
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
@@ -106,19 +107,62 @@ apic_timer_clear_hardware_timer()
} }
// #pragma mark - initialization
static uint32
calculate_apic_timer_conversion_factor()
{
// setup the timer
uint32 config = apic_lvt_timer() & APIC_LVT_TIMER_MASK;
config |= APIC_LVT_MASKED;
// timer masked, vector 0
apic_set_lvt_timer(config);
config = (apic_lvt_timer_divide_config() & ~0xf);
apic_set_lvt_timer_divide_config(config | APIC_TIMER_DIVIDE_CONFIG_1);
// divide clock by one
apic_set_lvt_initial_timer_count(UINT32_MAX); // start the counter
// Use CPUID as a fence (same as in TSC calibration).
asm volatile ("cpuid" : : : "eax", "ebx", "ecx", "edx");
int64 t1 = system_time_nsecs();
uint32 startCount = apic_lvt_current_timer_count();
spin(5000);
asm volatile ("cpuid" : : : "eax", "ebx", "ecx", "edx");
int64 t2 = system_time_nsecs();
uint32 endCount = apic_lvt_current_timer_count();
uint32 count = startCount - endCount;
uint32 factor
= (uint32)(((double(1) * 1000 * 1000 * 1000) / (t2 - t1)) * count);
dprintf("APIC timer frequency: %d\n", factor);
return factor;
}
static status_t static status_t
apic_timer_init(struct kernel_args *args) apic_timer_init(struct kernel_args *args)
{ {
if (!apic_available()) if (!apic_available())
return B_ERROR; return B_ERROR;
sApicTicsPerSec = args->arch_args.apic_time_cv_factor; sApicTicsPerSec = calculate_apic_timer_conversion_factor();
reserve_io_interrupt_vectors(1, 0xfb - ARCH_INTERRUPT_BASE, reserve_io_interrupt_vectors(1, 0xfb - ARCH_INTERRUPT_BASE,
INTERRUPT_TYPE_LOCAL_IRQ); INTERRUPT_TYPE_LOCAL_IRQ);
install_io_interrupt_handler(0xfb - ARCH_INTERRUPT_BASE, install_io_interrupt_handler(0xfb - ARCH_INTERRUPT_BASE,
&apic_timer_interrupt, NULL, B_NO_LOCK_VECTOR); &apic_timer_interrupt, NULL, B_NO_LOCK_VECTOR);
apic_timer_per_cpu_init(args, 0);
// will be called on non-boot CPUs by apic_per_cpu_init()
return B_OK; return B_OK;
} }
@@ -136,5 +180,6 @@ apic_timer_per_cpu_init(struct kernel_args *args, int32 cpu)
config = apic_lvt_timer_divide_config() & 0xfffffff0; config = apic_lvt_timer_divide_config() & 0xfffffff0;
config |= APIC_TIMER_DIVIDE_CONFIG_1; // clock division by 1 config |= APIC_TIMER_DIVIDE_CONFIG_1; // clock division by 1
apic_set_lvt_timer_divide_config(config); apic_set_lvt_timer_divide_config(config);
return B_OK; return B_OK;
} }