diff options
author | Raul E Rangel <rrangel@chromium.org> | 2018-08-20 12:31:11 -0600 |
---|---|---|
committer | Martin Roth <martinroth@google.com> | 2018-09-12 14:15:11 +0000 |
commit | 24ae85c3ffb48c72f67a514dcb843c1de54e5416 (patch) | |
tree | 7990b569495136ce505de044f6c27e265ff80233 /payloads/libpayload/arch/x86 | |
parent | ac8ebd0e73774c83bfa5cb3dd460c662000e0d19 (diff) |
libpayload/x86/apic: Add an apic_delay method and calibrate the timer
The apic_delay method will halt the CPU and wait for a timer interrupt
to fire. I went with usec because nsec is too granular to guarantee.
This method will be called from an arch_ndelay() method when the delay
is large enough to justify a sleep.
BUG=b:109749762
TEST=Tested it on grunt by changing the _delay method to call
apic_delay().
Change-Id: I80363f06bdb22d0907f895885e607fde1c4c468d
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
Reviewed-on: https://review.coreboot.org/28242
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Martin Roth <martinroth@google.com>
Diffstat (limited to 'payloads/libpayload/arch/x86')
-rw-r--r-- | payloads/libpayload/arch/x86/apic.c | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/payloads/libpayload/arch/x86/apic.c b/payloads/libpayload/arch/x86/apic.c index d5cf6ae14f..079f456eec 100644 --- a/payloads/libpayload/arch/x86/apic.c +++ b/payloads/libpayload/arch/x86/apic.c @@ -61,8 +61,13 @@ #define APIC_LVT_SIZE 0x010 +#define APIC_TIMER_VECTOR 0x20 + static uint32_t apic_bar; static int _apic_initialized; +// TODO: Build a lookup table to avoid calculating it. +static uint32_t ticks_per_ms; +static volatile uint8_t timer_waiting; enum APIC_CAPABILITY { DISABLED = 0, @@ -95,6 +100,47 @@ uint8_t apic_id(void) return id; } +void apic_delay(unsigned int usec) +{ + die_if(!ticks_per_ms, "apic_init_timer was not run."); + die_if(timer_waiting, "timer already started."); + die_if(!interrupts_enabled(), "Interrupts disabled."); + + /* The order is important so we don't underflow */ + uint64_t ticks = usec * ticks_per_ms / USECS_PER_MSEC; + + /* Not enough resolution */ + if (!ticks) + return; + + /* Disable interrupts so we don't get a race condition between + * starting the timer and the hlt instruction. */ + disable_interrupts(); + + timer_waiting = 1; + + apic_write32(APIC_TIMER_INIT_COUNT, ticks); + + /* Loop in case another interrupt has fired and resumed execution. */ + do { + asm volatile( + "sti\n\t" + "hlt\n\t" + /* Disable interrupts to prevent a race condition + * between checking timer_waiting and executing the hlt + * instruction again. */ + "cli\n\t"); + } while (timer_waiting); + + /* Leave hardware interrupts enabled. */ + enable_interrupts(); +} + +static void timer_interrupt_handler(u8 vector) +{ + timer_waiting = 0; +} + void apic_eoi(void) { die_if(!apic_bar, "APIC is not initialized"); @@ -150,6 +196,33 @@ static void apic_set_task_priority(uint8_t priority) apic_write32(APIC_TASK_PRIORITY, priority); } +static void apic_init_timer(void) +{ + die_if(!apic_bar, "APIC is not initialized"); + + apic_write32(APIC_LVT_TIMER, APIC_MASKED_BIT); + + /* Divide the clock by 1. */ + apic_write32(APIC_TIMER_DIV_CFG, 0xB); + + /* Calibrate the APIC timer */ + if (!ticks_per_ms) { + /* Set APIC init counter to MAX and count for 1 ms */ + apic_write32(APIC_TIMER_INIT_COUNT, UINT32_MAX); + + mdelay(1); + + ticks_per_ms = + UINT32_MAX - apic_read32(APIC_TIMER_CUR_COUNT); + } + + /* Clear the count so we don't get any stale interrupts */ + apic_write32(APIC_TIMER_INIT_COUNT, 0); + + /* Unmask the timer and set the vector. */ + apic_write32(APIC_LVT_TIMER, APIC_TIMER_VECTOR); +} + static void apic_sw_enable(void) { uint32_t reg = apic_read32(APIC_SPURIOUS); @@ -183,6 +256,10 @@ void apic_init(void) apic_sw_enable(); + apic_init_timer(); + + set_interrupt_handler(APIC_TIMER_VECTOR, &timer_interrupt_handler); + _apic_initialized = 1; printf("APIC Configured\n"); |