summaryrefslogtreecommitdiffstats
path: root/bsps/x86_64/amd64/clock/clock.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/x86_64/amd64/clock/clock.c')
-rw-r--r--bsps/x86_64/amd64/clock/clock.c299
1 files changed, 299 insertions, 0 deletions
diff --git a/bsps/x86_64/amd64/clock/clock.c b/bsps/x86_64/amd64/clock/clock.c
new file mode 100644
index 0000000000..76e537755a
--- /dev/null
+++ b/bsps/x86_64/amd64/clock/clock.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2018.
+ * Amaan Cheval <amaan.cheval@gmail.com>
+ *
+ * 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 AUTHOR 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 AUTHOR 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 <stdio.h>
+#include <assert.h>
+#include <bsp.h>
+#include <rtems.h>
+#include <pic.h>
+#include <apic.h>
+#include <clock.h>
+#include <rtems/score/idt.h>
+#include <rtems/timecounter.h>
+#include <rtems/score/cpu.h>
+#include <rtems/score/cpuimpl.h>
+#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)
+{
+ 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)
+{
+ Clock_isr(param);
+ amd64_apic_base[APIC_REGISTER_EOI] = APIC_EOI_ACK;
+}
+
+void apic_timer_install_handler(void)
+{
+ rtems_status_code sc = rtems_interrupt_handler_install(
+ BSP_VECTOR_APIC_TIMER,
+ "APIC timer",
+ RTEMS_INTERRUPT_UNIQUE,
+ apic_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
+ );
+
+ /*
+ * Disable interrupts 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.
+ */
+ amd64_disable_interrupts();
+
+ /* 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;
+
+ /* Re-enable interrupts now that calibration is complete */
+ amd64_enable_interrupts();
+
+ /* 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();
+ uint64_t irq_ticks_per_sec = 1000000 / us_per_tick;
+ DBG_PRINTF(
+ "us_per_tick = %d\nDesired frequency = %d irqs/sec\n",
+ us_per_tick,
+ 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);
+
+ amd64_clock_tc.tc_get_timecount = amd64_clock_get_timecount;
+ amd64_clock_tc.tc_counter_mask = 0xffffffff;
+ amd64_clock_tc.tc_frequency = irq_ticks_per_sec;
+ amd64_clock_tc.tc_quality = RTEMS_TIMECOUNTER_QUALITY_CLOCK_DRIVER;
+ rtems_timecounter_install(&amd64_clock_tc);
+}
+
+#define Clock_driver_support_install_isr(_new) \
+ apic_timer_install_handler()
+
+#define Clock_driver_support_initialize_hardware() \
+ amd64_clock_driver_initialize()
+
+#include "../../../shared/dev/clock/clockimpl.h"