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:
committed by
waddlesplash
parent
b801f14f55
commit
3d840c4289
@@ -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
|
|
||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user