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>
This commit is contained in:
Raul E Rangel 2018-08-20 12:31:11 -06:00 committed by Martin Roth
parent ac8ebd0e73
commit 24ae85c3ff
2 changed files with 79 additions and 0 deletions

View File

@ -61,8 +61,13 @@
#define APIC_LVT_SIZE 0x010 #define APIC_LVT_SIZE 0x010
#define APIC_TIMER_VECTOR 0x20
static uint32_t apic_bar; static uint32_t apic_bar;
static int _apic_initialized; 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 { enum APIC_CAPABILITY {
DISABLED = 0, DISABLED = 0,
@ -95,6 +100,47 @@ uint8_t apic_id(void)
return id; 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) void apic_eoi(void)
{ {
die_if(!apic_bar, "APIC is not initialized"); 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); 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) static void apic_sw_enable(void)
{ {
uint32_t reg = apic_read32(APIC_SPURIOUS); uint32_t reg = apic_read32(APIC_SPURIOUS);
@ -183,6 +256,10 @@ void apic_init(void)
apic_sw_enable(); apic_sw_enable();
apic_init_timer();
set_interrupt_handler(APIC_TIMER_VECTOR, &timer_interrupt_handler);
_apic_initialized = 1; _apic_initialized = 1;
printf("APIC Configured\n"); printf("APIC Configured\n");

View File

@ -40,4 +40,6 @@ uint8_t apic_id(void);
/** Signal the end of the interrupt handler. */ /** Signal the end of the interrupt handler. */
void apic_eoi(void); void apic_eoi(void);
void apic_delay(unsigned int usec);
#endif /* __ARCH_X86_INCLUDES_ARCH_APIC_H__ */ #endif /* __ARCH_X86_INCLUDES_ARCH_APIC_H__ */