forked from Imagelibrary/rtems
amd64: Move APIC implementation out of clock.c
- Move the APIC implementation to its on file instead of having it in clock.c; - Use the MADT for retrieving the Local APIC base address; - Initialize the APIC during interrupt initialization.
This commit is contained in:
committed by
Chris Johns
parent
0665da8f1d
commit
7440264601
@@ -36,7 +36,6 @@
|
||||
#include <assert.h>
|
||||
#include <bsp.h>
|
||||
#include <rtems.h>
|
||||
#include <pic.h>
|
||||
#include <apic.h>
|
||||
#include <clock.h>
|
||||
#include <rtems/score/idt.h>
|
||||
@@ -46,12 +45,9 @@
|
||||
#include <rtems/score/x86_64.h>
|
||||
#include <bsp/irq-generic.h>
|
||||
|
||||
/* Use the amd64_apic_base as an array of 32-bit APIC registers */
|
||||
volatile uint32_t *amd64_apic_base;
|
||||
static struct timecounter amd64_clock_tc;
|
||||
|
||||
extern volatile uint32_t Clock_driver_ticks;
|
||||
extern void apic_spurious_handler(void);
|
||||
extern void Clock_isr(void *param);
|
||||
|
||||
static uint32_t amd64_clock_get_timecount(struct timecounter *tc)
|
||||
@@ -59,224 +55,24 @@ static uint32_t amd64_clock_get_timecount(struct timecounter *tc)
|
||||
return Clock_driver_ticks;
|
||||
}
|
||||
|
||||
/*
|
||||
* When the CPUID instruction is executed with a source operand of 1 in the EAX
|
||||
* register, bit 9 of the CPUID feature flags returned in the EDX register
|
||||
* indicates the presence (set) or absence (clear) of a local APIC.
|
||||
*/
|
||||
bool has_apic_support()
|
||||
{
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
return (edx >> 9) & 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes the APIC by hardware and software enabling it, and sets up the
|
||||
* amd64_apic_base pointer that can be used as a 32-bit addressable array to
|
||||
* access APIC registers.
|
||||
*/
|
||||
void apic_initialize(void)
|
||||
{
|
||||
if ( !has_apic_support() ) {
|
||||
printf("warning: cpuid claims no APIC support - trying anyway.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* The APIC base address is a 36-bit physical address.
|
||||
* We have identity-paging setup at the moment, which makes this simpler, but
|
||||
* that's something to note since the variables below use virtual addresses.
|
||||
*
|
||||
* Bits 0-11 (inclusive) are 0, making the address page (4KiB) aligned.
|
||||
* Bits 12-35 (inclusive) of the MSR point to the rest of the address.
|
||||
*/
|
||||
uint64_t apic_base_msr = rdmsr(APIC_BASE_MSR);
|
||||
amd64_apic_base = (uint32_t*) apic_base_msr;
|
||||
amd64_apic_base = (uint32_t*) ((uintptr_t) amd64_apic_base & 0x0ffffff000);
|
||||
|
||||
/* Hardware enable the APIC just to be sure */
|
||||
wrmsr(
|
||||
APIC_BASE_MSR,
|
||||
apic_base_msr | APIC_BASE_MSR_ENABLE,
|
||||
apic_base_msr >> 32
|
||||
);
|
||||
|
||||
DBG_PRINTF("APIC is at 0x%" PRIxPTR "\n", (uintptr_t) amd64_apic_base);
|
||||
DBG_PRINTF(
|
||||
"APIC ID at *0x%" PRIxPTR "=0x%" PRIx32 "\n",
|
||||
(uintptr_t) &amd64_apic_base[APIC_REGISTER_APICID],
|
||||
amd64_apic_base[APIC_REGISTER_APICID]
|
||||
);
|
||||
|
||||
DBG_PRINTF(
|
||||
"APIC spurious vector register *0x%" PRIxPTR "=0x%" PRIx32 "\n",
|
||||
(uintptr_t) &amd64_apic_base[APIC_REGISTER_SPURIOUS],
|
||||
amd64_apic_base[APIC_REGISTER_SPURIOUS]
|
||||
);
|
||||
|
||||
/*
|
||||
* Software enable the APIC by mapping spurious vector and setting enable bit.
|
||||
*/
|
||||
uintptr_t old;
|
||||
amd64_install_raw_interrupt(
|
||||
BSP_VECTOR_SPURIOUS,
|
||||
(uintptr_t) apic_spurious_handler,
|
||||
&old
|
||||
);
|
||||
amd64_apic_base[APIC_REGISTER_SPURIOUS] =
|
||||
APIC_SPURIOUS_ENABLE | BSP_VECTOR_SPURIOUS;
|
||||
|
||||
DBG_PRINTF(
|
||||
"APIC spurious vector register *0x%" PRIxPTR "=0x%" PRIx32 "\n",
|
||||
(uintptr_t) &amd64_apic_base[APIC_REGISTER_SPURIOUS],
|
||||
amd64_apic_base[APIC_REGISTER_SPURIOUS]
|
||||
);
|
||||
|
||||
/*
|
||||
* The PIC may send spurious IRQs even when disabled, and without remapping
|
||||
* IRQ7 would look like an exception.
|
||||
*/
|
||||
pic_remap(PIC1_REMAP_DEST, PIC2_REMAP_DEST);
|
||||
pic_disable();
|
||||
}
|
||||
|
||||
static void apic_isr(void *param)
|
||||
static void lapic_timer_isr(void *param)
|
||||
{
|
||||
Clock_isr(param);
|
||||
amd64_apic_base[APIC_REGISTER_EOI] = APIC_EOI_ACK;
|
||||
lapic_eoi();
|
||||
}
|
||||
|
||||
void apic_timer_install_handler(void)
|
||||
void lapic_timer_install_handler(void)
|
||||
{
|
||||
rtems_status_code sc = rtems_interrupt_handler_install(
|
||||
BSP_VECTOR_APIC_TIMER,
|
||||
"APIC timer",
|
||||
"LAPIC timer",
|
||||
RTEMS_INTERRUPT_UNIQUE,
|
||||
apic_isr,
|
||||
lapic_timer_isr,
|
||||
NULL
|
||||
);
|
||||
assert(sc == RTEMS_SUCCESSFUL);
|
||||
}
|
||||
|
||||
uint32_t apic_timer_calibrate(void)
|
||||
{
|
||||
/* Configure APIC timer in one-shot mode to prepare for calibration */
|
||||
amd64_apic_base[APIC_REGISTER_LVT_TIMER] = BSP_VECTOR_APIC_TIMER;
|
||||
amd64_apic_base[APIC_REGISTER_TIMER_DIV] = APIC_TIMER_SELECT_DIVIDER;
|
||||
|
||||
/* Enable the channel 2 timer gate and disable the speaker output */
|
||||
uint8_t chan2_value = (inport_byte(PIT_PORT_CHAN2_GATE) | PIT_CHAN2_TIMER_BIT)
|
||||
& ~PIT_CHAN2_SPEAKER_BIT;
|
||||
outport_byte(PIT_PORT_CHAN2_GATE, chan2_value);
|
||||
|
||||
/* Initialize PIT in one-shot mode on Channel 2 */
|
||||
outport_byte(
|
||||
PIT_PORT_MCR,
|
||||
PIT_SELECT_CHAN2 | PIT_SELECT_ACCESS_LOHI |
|
||||
PIT_SELECT_ONE_SHOT_MODE | PIT_SELECT_BINARY_MODE
|
||||
);
|
||||
|
||||
/*
|
||||
* Make sure interrupts are disabled while we calibrate for 2 reasons:
|
||||
* - Writing values to the PIT should be atomic (for now, this is okay
|
||||
* because we're the only ones ever touching the PIT ports, but an
|
||||
* interrupt resetting the PIT could mess calibration up).
|
||||
* - We need to make sure that no interrupts throw our synchronization of
|
||||
* the APIC timer off.
|
||||
*/
|
||||
rtems_interrupt_level level;
|
||||
rtems_interrupt_local_disable(level);
|
||||
|
||||
/* Set PIT reload value */
|
||||
uint32_t pit_ticks = PIT_CALIBRATE_TICKS;
|
||||
uint8_t low_tick = pit_ticks & 0xff;
|
||||
uint8_t high_tick = (pit_ticks >> 8) & 0xff;
|
||||
|
||||
outport_byte(PIT_PORT_CHAN2, low_tick);
|
||||
stub_io_wait();
|
||||
outport_byte(PIT_PORT_CHAN2, high_tick);
|
||||
|
||||
/* Start APIC timer's countdown */
|
||||
const uint32_t apic_calibrate_init_count = 0xffffffff;
|
||||
|
||||
/* Restart PIT by disabling the gated input and then re-enabling it */
|
||||
chan2_value &= ~PIT_CHAN2_TIMER_BIT;
|
||||
outport_byte(PIT_PORT_CHAN2_GATE, chan2_value);
|
||||
chan2_value |= PIT_CHAN2_TIMER_BIT;
|
||||
outport_byte(PIT_PORT_CHAN2_GATE, chan2_value);
|
||||
amd64_apic_base[APIC_REGISTER_TIMER_INITCNT] = apic_calibrate_init_count;
|
||||
|
||||
while ( pit_ticks <= PIT_CALIBRATE_TICKS ) {
|
||||
/* Send latch command to read multi-byte value atomically */
|
||||
outport_byte(PIT_PORT_MCR, PIT_SELECT_CHAN2);
|
||||
pit_ticks = inport_byte(PIT_PORT_CHAN2);
|
||||
pit_ticks |= inport_byte(PIT_PORT_CHAN2) << 8;
|
||||
}
|
||||
uint32_t apic_currcnt = amd64_apic_base[APIC_REGISTER_TIMER_CURRCNT];
|
||||
|
||||
DBG_PRINTF("PIT stopped at 0x%" PRIx32 "\n", pit_ticks);
|
||||
|
||||
/* Stop APIC timer to calculate ticks to time ratio */
|
||||
amd64_apic_base[APIC_REGISTER_LVT_TIMER] = APIC_DISABLE;
|
||||
|
||||
/* Get counts passed since we started counting */
|
||||
uint32_t apic_ticks_per_sec = apic_calibrate_init_count - apic_currcnt;
|
||||
|
||||
DBG_PRINTF(
|
||||
"APIC ticks passed in 1/%d of a second: 0x%" PRIx32 "\n",
|
||||
PIT_CALIBRATE_DIVIDER,
|
||||
apic_ticks_per_sec
|
||||
);
|
||||
|
||||
/* We ran the PIT for a fraction of a second */
|
||||
apic_ticks_per_sec = apic_ticks_per_sec * PIT_CALIBRATE_DIVIDER;
|
||||
|
||||
/* Restore interrupts now that calibration is complete */
|
||||
rtems_interrupt_local_enable(level);
|
||||
|
||||
/* Confirm that the APIC timer never hit 0 and IRQ'd during calibration */
|
||||
assert(Clock_driver_ticks == 0);
|
||||
assert(apic_ticks_per_sec != 0 &&
|
||||
apic_ticks_per_sec != apic_calibrate_init_count);
|
||||
|
||||
DBG_PRINTF(
|
||||
"CPU frequency: 0x%" PRIu64 "\nAPIC ticks/sec: 0x%" PRIu32 "\n",
|
||||
/* Multiply to undo effects of divider */
|
||||
(uint64_t) apic_ticks_per_sec * APIC_TIMER_DIVIDE_VALUE,
|
||||
apic_ticks_per_sec
|
||||
);
|
||||
|
||||
return apic_ticks_per_sec;
|
||||
}
|
||||
|
||||
void apic_timer_initialize(uint64_t desired_freq_hz)
|
||||
{
|
||||
uint32_t apic_ticks_per_sec = 0;
|
||||
uint32_t apic_tick_collections[APIC_TIMER_NUM_CALIBRATIONS] = {0};
|
||||
uint64_t apic_tick_total = 0;
|
||||
for (uint32_t i = 0; i < APIC_TIMER_NUM_CALIBRATIONS; i++) {
|
||||
apic_tick_collections[i] = apic_timer_calibrate();
|
||||
apic_tick_total += apic_tick_collections[i];
|
||||
}
|
||||
apic_ticks_per_sec = apic_tick_total / APIC_TIMER_NUM_CALIBRATIONS;
|
||||
|
||||
/*
|
||||
* The APIC timer counter is decremented at the speed of the CPU bus
|
||||
* frequency (and we use a frequency divider).
|
||||
*
|
||||
* This means:
|
||||
* apic_ticks_per_sec = (cpu_bus_frequency / timer_divide_value)
|
||||
*
|
||||
* Therefore:
|
||||
* reload_value = apic_ticks_per_sec / desired_freq_hz
|
||||
*/
|
||||
uint32_t apic_timer_reload_value = apic_ticks_per_sec / desired_freq_hz;
|
||||
|
||||
amd64_apic_base[APIC_REGISTER_LVT_TIMER] = BSP_VECTOR_APIC_TIMER | APIC_SELECT_TMR_PERIODIC;
|
||||
amd64_apic_base[APIC_REGISTER_TIMER_DIV] = APIC_TIMER_SELECT_DIVIDER;
|
||||
amd64_apic_base[APIC_REGISTER_TIMER_INITCNT] = apic_timer_reload_value;
|
||||
}
|
||||
|
||||
void amd64_clock_driver_initialize(void)
|
||||
{
|
||||
uint64_t us_per_tick = rtems_configuration_get_microseconds_per_tick();
|
||||
@@ -287,10 +83,8 @@ void amd64_clock_driver_initialize(void)
|
||||
irq_ticks_per_sec
|
||||
);
|
||||
|
||||
/* Setup and enable the APIC itself */
|
||||
apic_initialize();
|
||||
/* Setup and initialize the APIC timer */
|
||||
apic_timer_initialize(irq_ticks_per_sec);
|
||||
/* Setup and initialize the Local APIC timer */
|
||||
lapic_timer_initialize(irq_ticks_per_sec);
|
||||
|
||||
amd64_clock_tc.tc_get_timecount = amd64_clock_get_timecount;
|
||||
amd64_clock_tc.tc_counter_mask = 0xffffffff;
|
||||
@@ -300,7 +94,7 @@ void amd64_clock_driver_initialize(void)
|
||||
}
|
||||
|
||||
#define Clock_driver_support_install_isr(_new) \
|
||||
apic_timer_install_handler()
|
||||
lapic_timer_install_handler()
|
||||
|
||||
#define Clock_driver_support_initialize_hardware() \
|
||||
amd64_clock_driver_initialize()
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
/*
|
||||
* Copyright (c) 2018.
|
||||
* Amaan Cheval <amaan.cheval@gmail.com>
|
||||
* Copyright (C) 2024 Matheus Pecoraro
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -35,6 +36,8 @@
|
||||
#ifndef _AMD64_APIC_H
|
||||
#define _AMD64_APIC_H
|
||||
|
||||
#include <rtems/score/basedefs.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -45,23 +48,107 @@ extern "C" {
|
||||
#define APIC_BASE_MSR_ENABLE 0x800
|
||||
|
||||
/*
|
||||
* Since amd64_apic_base is an array of 32-bit elements, these byte-offsets
|
||||
* need to be divided by 4 to index the array.
|
||||
* Since the LAPIC registers are contained in an array of 32-bit elements
|
||||
* these byte-offsets need to be divided by 4 to index the array.
|
||||
*/
|
||||
#define APIC_OFFSET(val) (val >> 2)
|
||||
#define LAPIC_OFFSET(val) (val >> 2)
|
||||
|
||||
#define APIC_REGISTER_APICID APIC_OFFSET(0x20)
|
||||
#define APIC_REGISTER_EOI APIC_OFFSET(0x0B0)
|
||||
#define APIC_REGISTER_SPURIOUS APIC_OFFSET(0x0F0)
|
||||
#define APIC_REGISTER_LVT_TIMER APIC_OFFSET(0x320)
|
||||
#define APIC_REGISTER_TIMER_INITCNT APIC_OFFSET(0x380)
|
||||
#define APIC_REGISTER_TIMER_CURRCNT APIC_OFFSET(0x390)
|
||||
#define APIC_REGISTER_TIMER_DIV APIC_OFFSET(0x3E0)
|
||||
#define LAPIC_REGISTER_APICID LAPIC_OFFSET(0x20)
|
||||
#define LAPIC_REGISTER_EOI LAPIC_OFFSET(0x0B0)
|
||||
#define LAPIC_REGISTER_SPURIOUS LAPIC_OFFSET(0x0F0)
|
||||
#define LAPIC_REGISTER_LVT_TIMER LAPIC_OFFSET(0x320)
|
||||
#define LAPIC_REGISTER_TIMER_INITCNT LAPIC_OFFSET(0x380)
|
||||
#define LAPIC_REGISTER_TIMER_CURRCNT LAPIC_OFFSET(0x390)
|
||||
#define LAPIC_REGISTER_TIMER_DIV LAPIC_OFFSET(0x3E0)
|
||||
|
||||
#define APIC_DISABLE 0x10000
|
||||
#define APIC_EOI_ACK 0
|
||||
#define APIC_SELECT_TMR_PERIODIC 0x20000
|
||||
#define APIC_SPURIOUS_ENABLE 0x100
|
||||
#define LAPIC_LVT_MASK 0x10000
|
||||
|
||||
#define LAPIC_EOI_ACK 0
|
||||
#define LAPIC_SELECT_TMR_PERIODIC 0x20000
|
||||
#define LAPIC_SPURIOUS_ENABLE 0x100
|
||||
|
||||
/* Number of times to calibrate the LAPIC timer to average it out */
|
||||
#define LAPIC_TIMER_NUM_CALIBRATIONS 5
|
||||
/* Default divide value used by LAPIC timer */
|
||||
#define LAPIC_TIMER_DIVIDE_VALUE 16
|
||||
/* Value to set in register to pick the divide value above */
|
||||
#define LAPIC_TIMER_SELECT_DIVIDER 3
|
||||
|
||||
/* PIT defines used during LAPIC timer calibration */
|
||||
#define PIT_FREQUENCY 1193180
|
||||
/*
|
||||
* The PIT_FREQUENCY determines how many times the PIT counter is decremented
|
||||
* per second - therefore, we can calculate how many ticks we set based on what
|
||||
* fraction of a second we're okay with spending on calibration
|
||||
*/
|
||||
#define PIT_CALIBRATE_DIVIDER 20
|
||||
#define PIT_CALIBRATE_TICKS (PIT_FREQUENCY/PIT_CALIBRATE_DIVIDER)
|
||||
/*
|
||||
* Since the PIT only has 2 one-byte registers, the maximum tick value is
|
||||
* limited to 16-bits. We can set the PIT to use a frequency divider if
|
||||
* needed.
|
||||
*/
|
||||
RTEMS_STATIC_ASSERT(
|
||||
PIT_CALIBRATE_TICKS <= 0xffff,
|
||||
PIT_CALIBRATE_DIVIDER
|
||||
);
|
||||
|
||||
/* I/O ports for the PIT */
|
||||
#define PIT_PORT_CHAN0 0x40
|
||||
#define PIT_PORT_CHAN1 0x41
|
||||
#define PIT_PORT_CHAN2 0x42
|
||||
/*
|
||||
* The input to channel 2 can be gated through software, using bit 0 of port
|
||||
* 0x61.
|
||||
*/
|
||||
#define PIT_PORT_CHAN2_GATE 0x61
|
||||
#define PIT_CHAN2_TIMER_BIT 1
|
||||
#define PIT_CHAN2_SPEAKER_BIT 2
|
||||
/* The PIT mode/command register */
|
||||
#define PIT_PORT_MCR 0x43
|
||||
|
||||
/* PIT values to select channels, access, and operating modes */
|
||||
#define PIT_SELECT_CHAN0 0b00000000
|
||||
#define PIT_SELECT_CHAN1 0b01000000
|
||||
#define PIT_SELECT_CHAN2 0b10000000
|
||||
/*
|
||||
* In the lo/hi mode, the low-byte is sent to the data port, followed by the
|
||||
* high-byte; this makes it important that this be an atomic operation.
|
||||
*/
|
||||
#define PIT_SELECT_ACCESS_LOHI 0b00110000
|
||||
#define PIT_SELECT_ONE_SHOT_MODE 0b00000010
|
||||
#define PIT_SELECT_BINARY_MODE 0
|
||||
|
||||
extern volatile uint32_t* amd64_lapic_base;
|
||||
|
||||
/**
|
||||
* @brief Initializes the Local APIC by hardware and software enabling it.
|
||||
*
|
||||
* Initializes the Local APIC by hardware and software enabling it, and sets
|
||||
* up the amd64_lapic_base pointer that can be used as a 32-bit addressable array to
|
||||
* access Local APIC registers.
|
||||
*
|
||||
* @return true if successful.
|
||||
*/
|
||||
bool lapic_initialize(void);
|
||||
|
||||
/**
|
||||
* @brief Initializes the Local APIC timer
|
||||
*
|
||||
* Calibrates and initializes the Local APIC timer configuring it to
|
||||
* periodically generate interrupts on vector BSP_VECTOR_APIC_TIMER
|
||||
*
|
||||
* @param desired_freq_hz The desired frequency of the Local APIC timer
|
||||
*/
|
||||
void lapic_timer_initialize(uint64_t desired_freq_hz);
|
||||
|
||||
/**
|
||||
* @brief Signals an end of interrupt to the Local APIC
|
||||
*/
|
||||
void inline lapic_eoi(void)
|
||||
{
|
||||
amd64_lapic_base[LAPIC_REGISTER_EOI] = LAPIC_EOI_ACK;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -35,71 +35,15 @@
|
||||
#ifndef _AMD64_CLOCK_H
|
||||
#define _AMD64_CLOCK_H
|
||||
|
||||
#include <rtems/score/basedefs.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef ASM
|
||||
extern volatile uint32_t *amd64_apic_base;
|
||||
bool has_apic_support(void);
|
||||
void apic_initialize(void);
|
||||
void apic_timer_install_handler(void);
|
||||
uint32_t apic_timer_calibrate(void);
|
||||
void apic_timer_initialize(uint64_t desired_freq_hz);
|
||||
void lapic_timer_install_handler(void);
|
||||
void amd64_clock_driver_initialize(void);
|
||||
#endif
|
||||
|
||||
/* Number of times to calibrate the APIC timer to average it out */
|
||||
#define APIC_TIMER_NUM_CALIBRATIONS 5
|
||||
/* Default divide value used by APIC timer */
|
||||
#define APIC_TIMER_DIVIDE_VALUE 16
|
||||
/* Value to set in register to pick the divide value above */
|
||||
#define APIC_TIMER_SELECT_DIVIDER 3
|
||||
|
||||
#define PIT_FREQUENCY 1193180
|
||||
/*
|
||||
* The PIT_FREQUENCY determines how many times the PIT counter is decremented
|
||||
* per second - therefore, we can calculate how many ticks we set based on what
|
||||
* fraction of a second we're okay with spending on calibration
|
||||
*/
|
||||
#define PIT_CALIBRATE_DIVIDER 20
|
||||
#define PIT_CALIBRATE_TICKS (PIT_FREQUENCY/PIT_CALIBRATE_DIVIDER)
|
||||
/* Since the PIT only has 2 one-byte registers, the maximum tick value is
|
||||
* limited to 16-bits. We can set the PIT to use a frequency divider if
|
||||
* needed. */
|
||||
RTEMS_STATIC_ASSERT(
|
||||
PIT_CALIBRATE_TICKS <= 0xffff,
|
||||
PIT_CALIBRATE_DIVIDER
|
||||
);
|
||||
|
||||
/* I/O ports for the PIT */
|
||||
#define PIT_PORT_CHAN0 0x40
|
||||
#define PIT_PORT_CHAN1 0x41
|
||||
#define PIT_PORT_CHAN2 0x42
|
||||
/*
|
||||
* The input to channel 2 can be gated through software, using bit 0 of port
|
||||
* 0x61.
|
||||
*/
|
||||
#define PIT_PORT_CHAN2_GATE 0x61
|
||||
#define PIT_CHAN2_TIMER_BIT 1
|
||||
#define PIT_CHAN2_SPEAKER_BIT 2
|
||||
/* The PIT mode/command register */
|
||||
#define PIT_PORT_MCR 0x43
|
||||
|
||||
/* PIT values to select channels, access, and operating modes */
|
||||
#define PIT_SELECT_CHAN0 0b00000000
|
||||
#define PIT_SELECT_CHAN1 0b01000000
|
||||
#define PIT_SELECT_CHAN2 0b10000000
|
||||
/*
|
||||
* In the lo/hi mode, the low-byte is sent to the data port, followed by the
|
||||
* high-byte; this makes it important that this be an atomic operation.
|
||||
*/
|
||||
#define PIT_SELECT_ACCESS_LOHI 0b00110000
|
||||
#define PIT_SELECT_ONE_SHOT_MODE 0b00000010
|
||||
#define PIT_SELECT_BINARY_MODE 0
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
277
bsps/x86_64/amd64/interrupts/apic.c
Normal file
277
bsps/x86_64/amd64/interrupts/apic.c
Normal file
@@ -0,0 +1,277 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* @ingroup RTEMSBSPsX8664AMD64
|
||||
*
|
||||
* @brief APIC implementation
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024 Matheus Pecoraro
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <acpi/acpi.h>
|
||||
#include <apic.h>
|
||||
#include <assert.h>
|
||||
#include <bsp.h>
|
||||
#include <pic.h>
|
||||
#include <rtems/score/idt.h>
|
||||
|
||||
extern void apic_spurious_handler(void);
|
||||
|
||||
volatile uint32_t* amd64_lapic_base;
|
||||
|
||||
/**
|
||||
* @brief Returns wheather the system contains a local APIC or not.
|
||||
*
|
||||
* When the CPUID instruction is executed with a source operand of 1 in the EAX
|
||||
* register, bit 9 of the CPUID feature flags returned in the EDX register
|
||||
* indicates the presence (set) or absence (clear) of a local APIC.
|
||||
*
|
||||
* @return true if CPUID reports the presence of a local APIC.
|
||||
*/
|
||||
static bool has_lapic_support(void)
|
||||
{
|
||||
uint32_t eax, ebx, ecx, edx;
|
||||
cpuid(1, &eax, &ebx, &ecx, &edx);
|
||||
return (edx >> 9) & 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Passed to acpi_walk_subtables to parse information from MADT entries
|
||||
*
|
||||
* @param entry An entry of the table passed to acpi_walk_subtables
|
||||
*/
|
||||
static void madt_subtables_handler(ACPI_SUBTABLE_HEADER* entry)
|
||||
{
|
||||
switch (entry->Type) {
|
||||
case ACPI_MADT_TYPE_LOCAL_APIC_OVERRIDE:
|
||||
ACPI_MADT_LOCAL_APIC_OVERRIDE* lapic_override =
|
||||
(ACPI_MADT_LOCAL_APIC_OVERRIDE*) entry;
|
||||
amd64_lapic_base = (uint32_t*) lapic_override->Address;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve the MADT and parse the values we need.
|
||||
*
|
||||
* @return true if successful.
|
||||
*/
|
||||
static bool parse_madt(void)
|
||||
{
|
||||
ACPI_STATUS status;
|
||||
ACPI_TABLE_MADT* madt;
|
||||
|
||||
status = AcpiGetTable(ACPI_SIG_MADT, 1, (ACPI_TABLE_HEADER**) &madt);
|
||||
if (status != (AE_OK)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
amd64_lapic_base = (uint32_t*) ((uintptr_t) madt->Address);
|
||||
acpi_walk_subtables(
|
||||
(ACPI_TABLE_HEADER*) madt,
|
||||
sizeof(ACPI_TABLE_MADT),
|
||||
madt_subtables_handler
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t lapic_timer_calibrate(void)
|
||||
{
|
||||
/* Configure LAPIC timer in one-shot mode to prepare for calibration */
|
||||
amd64_lapic_base[LAPIC_REGISTER_LVT_TIMER] = BSP_VECTOR_APIC_TIMER;
|
||||
amd64_lapic_base[LAPIC_REGISTER_TIMER_DIV] = LAPIC_TIMER_SELECT_DIVIDER;
|
||||
|
||||
/* Enable the channel 2 timer gate and disable the speaker output */
|
||||
uint8_t chan2_value = (inport_byte(PIT_PORT_CHAN2_GATE) | PIT_CHAN2_TIMER_BIT)
|
||||
& ~PIT_CHAN2_SPEAKER_BIT;
|
||||
outport_byte(PIT_PORT_CHAN2_GATE, chan2_value);
|
||||
|
||||
/* Initialize PIT in one-shot mode on Channel 2 */
|
||||
outport_byte(
|
||||
PIT_PORT_MCR,
|
||||
PIT_SELECT_CHAN2 | PIT_SELECT_ACCESS_LOHI |
|
||||
PIT_SELECT_ONE_SHOT_MODE | PIT_SELECT_BINARY_MODE
|
||||
);
|
||||
|
||||
/*
|
||||
* Make sure interrupts are disabled while we calibrate for 2 reasons:
|
||||
* - Writing values to the PIT should be atomic (for now, this is okay
|
||||
* because we're the only ones ever touching the PIT ports, but an
|
||||
* interrupt resetting the PIT could mess calibration up).
|
||||
* - We need to make sure that no interrupts throw our synchronization of
|
||||
* the LAPIC timer off.
|
||||
*/
|
||||
rtems_interrupt_level level;
|
||||
rtems_interrupt_local_disable(level);
|
||||
|
||||
/* Set PIT reload value */
|
||||
uint32_t pit_ticks = PIT_CALIBRATE_TICKS;
|
||||
uint8_t low_tick = pit_ticks & 0xff;
|
||||
uint8_t high_tick = (pit_ticks >> 8) & 0xff;
|
||||
|
||||
outport_byte(PIT_PORT_CHAN2, low_tick);
|
||||
stub_io_wait();
|
||||
outport_byte(PIT_PORT_CHAN2, high_tick);
|
||||
|
||||
/* Start LAPIC timer's countdown */
|
||||
const uint32_t lapic_calibrate_init_count = 0xffffffff;
|
||||
|
||||
/* Restart PIT by disabling the gated input and then re-enabling it */
|
||||
chan2_value &= ~PIT_CHAN2_TIMER_BIT;
|
||||
outport_byte(PIT_PORT_CHAN2_GATE, chan2_value);
|
||||
chan2_value |= PIT_CHAN2_TIMER_BIT;
|
||||
outport_byte(PIT_PORT_CHAN2_GATE, chan2_value);
|
||||
amd64_lapic_base[LAPIC_REGISTER_TIMER_INITCNT] = lapic_calibrate_init_count;
|
||||
|
||||
while ( pit_ticks <= PIT_CALIBRATE_TICKS ) {
|
||||
/* Send latch command to read multi-byte value atomically */
|
||||
outport_byte(PIT_PORT_MCR, PIT_SELECT_CHAN2);
|
||||
pit_ticks = inport_byte(PIT_PORT_CHAN2);
|
||||
pit_ticks |= inport_byte(PIT_PORT_CHAN2) << 8;
|
||||
}
|
||||
uint32_t lapic_currcnt = amd64_lapic_base[LAPIC_REGISTER_TIMER_CURRCNT];
|
||||
|
||||
DBG_PRINTF("PIT stopped at 0x%" PRIx32 "\n", pit_ticks);
|
||||
|
||||
/* Stop APIC timer to calculate ticks to time ratio */
|
||||
amd64_lapic_base[LAPIC_REGISTER_LVT_TIMER] = LAPIC_LVT_MASK;
|
||||
|
||||
/* Get counts passed since we started counting */
|
||||
uint32_t lapic_ticks_per_sec = lapic_calibrate_init_count - lapic_currcnt;
|
||||
|
||||
DBG_PRINTF(
|
||||
"APIC ticks passed in 1/%d of a second: 0x%" PRIx32 "\n",
|
||||
PIT_CALIBRATE_DIVIDER,
|
||||
lapic_ticks_per_sec
|
||||
);
|
||||
|
||||
/* We ran the PIT for a fraction of a second */
|
||||
lapic_ticks_per_sec = lapic_ticks_per_sec * PIT_CALIBRATE_DIVIDER;
|
||||
|
||||
/* Restore interrupts now that calibration is complete */
|
||||
rtems_interrupt_local_enable(level);
|
||||
|
||||
/* Confirm that the APIC timer never hit 0 and IRQ'd during calibration */
|
||||
assert(lapic_ticks_per_sec != 0 &&
|
||||
lapic_ticks_per_sec != lapic_calibrate_init_count);
|
||||
|
||||
DBG_PRINTF(
|
||||
"CPU frequency: 0x%" PRIu64 "\nAPIC ticks/sec: 0x%" PRIu32 "\n",
|
||||
/* Multiply to undo effects of divider */
|
||||
(uint64_t) lapic_ticks_per_sec * LAPIC_TIMER_DIVIDE_VALUE,
|
||||
lapic_ticks_per_sec
|
||||
);
|
||||
|
||||
return lapic_ticks_per_sec;
|
||||
}
|
||||
|
||||
bool lapic_initialize(void)
|
||||
{
|
||||
if (has_lapic_support() == false || parse_madt() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Hardware enable the APIC just to be sure */
|
||||
uint64_t apic_base_msr = rdmsr(APIC_BASE_MSR);
|
||||
wrmsr(
|
||||
APIC_BASE_MSR,
|
||||
apic_base_msr | APIC_BASE_MSR_ENABLE,
|
||||
apic_base_msr >> 32
|
||||
);
|
||||
|
||||
DBG_PRINTF("APIC is at 0x%" PRIxPTR "\n", (uintptr_t) amd64_lapic_base);
|
||||
DBG_PRINTF(
|
||||
"APIC ID at *0x%" PRIxPTR "=0x%" PRIx32 "\n",
|
||||
(uintptr_t) &amd64_lapic_base[LAPIC_REGISTER_APICID],
|
||||
amd64_lapic_base[LAPIC_REGISTER_APICID]
|
||||
);
|
||||
|
||||
DBG_PRINTF(
|
||||
"APIC spurious vector register *0x%" PRIxPTR "=0x%" PRIx32 "\n",
|
||||
(uintptr_t) &amd64_lapic_base[LAPIC_REGISTER_SPURIOUS],
|
||||
amd64_lapic_base[LAPIC_REGISTER_SPURIOUS]
|
||||
);
|
||||
|
||||
/*
|
||||
* Software enable the APIC by mapping spurious vector and setting enable bit.
|
||||
*/
|
||||
uintptr_t old;
|
||||
amd64_install_raw_interrupt(
|
||||
BSP_VECTOR_SPURIOUS,
|
||||
(uintptr_t) apic_spurious_handler,
|
||||
&old
|
||||
);
|
||||
amd64_lapic_base[LAPIC_REGISTER_SPURIOUS] =
|
||||
LAPIC_SPURIOUS_ENABLE | BSP_VECTOR_SPURIOUS;
|
||||
|
||||
DBG_PRINTF(
|
||||
"APIC spurious vector register *0x%" PRIxPTR "=0x%" PRIx32 "\n",
|
||||
(uintptr_t) &amd64_lapic_base[LAPIC_REGISTER_SPURIOUS],
|
||||
amd64_lapic_base[LAPIC_REGISTER_SPURIOUS]
|
||||
);
|
||||
|
||||
/*
|
||||
* The PIC may send spurious IRQs even when disabled, and without remapping
|
||||
* IRQ7 would look like an exception.
|
||||
*/
|
||||
pic_remap(PIC1_REMAP_DEST, PIC2_REMAP_DEST);
|
||||
pic_disable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void lapic_timer_initialize(uint64_t desired_freq_hz)
|
||||
{
|
||||
uint32_t lapic_ticks_per_sec = 0;
|
||||
uint32_t lapic_tick_collections[LAPIC_TIMER_NUM_CALIBRATIONS] = {0};
|
||||
uint64_t lapic_tick_total = 0;
|
||||
for (uint32_t i = 0; i < LAPIC_TIMER_NUM_CALIBRATIONS; i++) {
|
||||
lapic_tick_collections[i] = lapic_timer_calibrate();
|
||||
lapic_tick_total += lapic_tick_collections[i];
|
||||
}
|
||||
lapic_ticks_per_sec = lapic_tick_total / LAPIC_TIMER_NUM_CALIBRATIONS;
|
||||
|
||||
/*
|
||||
* The APIC timer counter is decremented at the speed of the CPU bus
|
||||
* frequency (and we use a frequency divider).
|
||||
*
|
||||
* This means:
|
||||
* apic_ticks_per_sec = (cpu_bus_frequency / timer_divide_value)
|
||||
*
|
||||
* Therefore:
|
||||
* reload_value = apic_ticks_per_sec / desired_freq_hz
|
||||
*/
|
||||
uint32_t lapic_timer_reload_value = lapic_ticks_per_sec / desired_freq_hz;
|
||||
|
||||
amd64_lapic_base[LAPIC_REGISTER_LVT_TIMER] = BSP_VECTOR_APIC_TIMER | LAPIC_SELECT_TMR_PERIODIC;
|
||||
amd64_lapic_base[LAPIC_REGISTER_TIMER_DIV] = LAPIC_TIMER_SELECT_DIVIDER;
|
||||
amd64_lapic_base[LAPIC_REGISTER_TIMER_INITCNT] = lapic_timer_reload_value;
|
||||
}
|
||||
@@ -32,13 +32,16 @@
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <apic.h>
|
||||
#include <bsp/fatal.h>
|
||||
#include <bsp/irq-generic.h>
|
||||
#include <rtems.h>
|
||||
#include <rtems/score/idt.h>
|
||||
#include <rtems/score/basedefs.h>
|
||||
#include <rtems/score/x86_64.h>
|
||||
#include <rtems/score/cpuimpl.h>
|
||||
#include <bsp/irq-generic.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* The IDT maps every interrupt vector to an interrupt_descriptor based on the
|
||||
@@ -145,6 +148,10 @@ void bsp_interrupt_facility_initialize(void)
|
||||
}
|
||||
|
||||
lidt(&idtr);
|
||||
|
||||
if (lapic_initialize() == false) {
|
||||
bsp_fatal(BSP_FATAL_INTERRUPT_INITIALIZATION);
|
||||
}
|
||||
}
|
||||
|
||||
rtems_status_code bsp_interrupt_vector_disable(rtems_vector_number vector)
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <acpi/acpi.h>
|
||||
#include <bsp.h>
|
||||
#include <bsp/bootcard.h>
|
||||
#include <libcpu/page.h>
|
||||
@@ -51,6 +53,7 @@ void bsp_start(void)
|
||||
if (!uefi_bootservices_running()) {
|
||||
#endif
|
||||
paging_init();
|
||||
assert(acpi_tables_initialize());
|
||||
bsp_interrupt_initialize();
|
||||
#ifdef BSP_MULTIBOOT_SUPPORT
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ includes: []
|
||||
install:
|
||||
- destination: ${BSP_INCLUDEDIR}
|
||||
source:
|
||||
- bsps/x86_64/amd64/include/apic.h
|
||||
- bsps/x86_64/amd64/include/bsp.h
|
||||
- bsps/x86_64/amd64/include/clock.h
|
||||
- bsps/x86_64/amd64/include/freebsd_loader.h
|
||||
@@ -41,6 +42,7 @@ source:
|
||||
- bsps/x86_64/amd64/acpi/osl/osl_interrupts.c
|
||||
- bsps/x86_64/amd64/acpi/osl/osl_memory.c
|
||||
- bsps/x86_64/amd64/acpi/osl/osl_tables.c
|
||||
- bsps/x86_64/amd64/interrupts/apic.c
|
||||
- bsps/x86_64/amd64/interrupts/idt.c
|
||||
- bsps/x86_64/amd64/interrupts/isr_handler.S
|
||||
- bsps/x86_64/amd64/interrupts/pic.c
|
||||
|
||||
Reference in New Issue
Block a user