diff --git a/bsps/x86_64/amd64/clock/clock.c b/bsps/x86_64/amd64/clock/clock.c index b1a6d8148b..b7d34f05fe 100644 --- a/bsps/x86_64/amd64/clock/clock.c +++ b/bsps/x86_64/amd64/clock/clock.c @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -46,12 +45,9 @@ #include #include -/* 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() diff --git a/bsps/x86_64/amd64/include/apic.h b/bsps/x86_64/amd64/include/apic.h index 4b77485eec..07de962222 100644 --- a/bsps/x86_64/amd64/include/apic.h +++ b/bsps/x86_64/amd64/include/apic.h @@ -9,6 +9,7 @@ /* * Copyright (c) 2018. * Amaan Cheval + * 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 + #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 } diff --git a/bsps/x86_64/amd64/include/clock.h b/bsps/x86_64/amd64/include/clock.h index 593a4e36b7..963a6d13c6 100644 --- a/bsps/x86_64/amd64/include/clock.h +++ b/bsps/x86_64/amd64/include/clock.h @@ -35,71 +35,15 @@ #ifndef _AMD64_CLOCK_H #define _AMD64_CLOCK_H -#include - #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 diff --git a/bsps/x86_64/amd64/interrupts/apic.c b/bsps/x86_64/amd64/interrupts/apic.c new file mode 100644 index 0000000000..5e34170d3a --- /dev/null +++ b/bsps/x86_64/amd64/interrupts/apic.c @@ -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 +#include +#include +#include +#include +#include + +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; +} diff --git a/bsps/x86_64/amd64/interrupts/idt.c b/bsps/x86_64/amd64/interrupts/idt.c index d8bd363f8c..b9c45a1b22 100644 --- a/bsps/x86_64/amd64/interrupts/idt.c +++ b/bsps/x86_64/amd64/interrupts/idt.c @@ -32,13 +32,16 @@ * SUCH DAMAGE. */ -#include +#include +#include +#include #include #include #include #include #include -#include + +#include /* * 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) diff --git a/bsps/x86_64/amd64/start/bspstart.c b/bsps/x86_64/amd64/start/bspstart.c index 40c0c96e22..ef3e21da83 100644 --- a/bsps/x86_64/amd64/start/bspstart.c +++ b/bsps/x86_64/amd64/start/bspstart.c @@ -34,6 +34,8 @@ * SUCH DAMAGE. */ +#include +#include #include #include #include @@ -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 } diff --git a/spec/build/bsps/x86_64/amd64/obj.yml b/spec/build/bsps/x86_64/amd64/obj.yml index dd8c8094ed..a3aadd748b 100644 --- a/spec/build/bsps/x86_64/amd64/obj.yml +++ b/spec/build/bsps/x86_64/amd64/obj.yml @@ -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