summaryrefslogtreecommitdiff
path: root/sys/arch/amd64/cpu
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arch/amd64/cpu')
-rw-r--r--sys/arch/amd64/cpu/lapic.c100
1 files changed, 98 insertions, 2 deletions
diff --git a/sys/arch/amd64/cpu/lapic.c b/sys/arch/amd64/cpu/lapic.c
index 22d07a9..def7dea 100644
--- a/sys/arch/amd64/cpu/lapic.c
+++ b/sys/arch/amd64/cpu/lapic.c
@@ -39,6 +39,7 @@
#include <lib/stdbool.h>
#include <mu/cpu.h>
#include <md/lapic.h>
+#include <md/i8254.h>
#include <md/cpuid.h>
#include <md/msr.h>
@@ -53,14 +54,28 @@
*
* See section 2.3.2 of the x2APIC specification
*/
-#define LAPIC_REG_ID 0x0020 /* ID register */
-#define LAPIC_REG_SVR 0x00F0 /* Spurious vector register */
+#define LAPIC_REG_ID 0x0020 /* ID register */
+#define LAPIC_REG_SVR 0x00F0 /* Spurious vector register */
+#define LAPIC_REG_TICR 0x0380 /* Timer initial counter register */
+#define LAPIC_REG_TCCR 0x0390 /* Timer current counter register */
+#define LAPIC_REG_TDCR 0x03E0 /* Timer divide configuration register */
+#define LAPIC_REG_LVTTMR 0x0320 /* LVT timer entry */
/* LAPIC SVR bits */
#define LAPIC_SVR_EBS BIT(12) /* EOI-broadcast supression */
#define LAPIC_SVR_FPC BIT(9) /* Focus processor checking */
#define LAPIC_SVR_APIC_EN BIT(8) /* Software-enable Local APIC */
+/* Local vector table */
+#define LVT_MASK BIT(16)
+
+/* Samples to take */
+#define LAPIC_TMR_SAMPLES 0xFFFF
+
+/* Timer modes */
+#define LAPIC_TMR_ONESHOT 0x00
+#define LAPIC_TMR_PERIODIC 0x01
+
/* Accessed via RDMSR/WRMSR */
#define X2APIC_MSR_BASE 0x00000800
@@ -126,6 +141,85 @@ lapic_has_x2apic(void)
}
/*
+ * Configure the Local APIC timer with a predefined
+ * vector
+ *
+ * XXX: See LAPIC_TMR_* for mode definitions
+ */
+static void
+lapic_tmr_enable(struct mcb *mcb, uint8_t mode)
+{
+ uint32_t lvt_tmr;
+
+ lvt_tmr = lapic_read(mcb, LAPIC_REG_LVTTMR);
+
+ /* Clear out stale values */
+ lvt_tmr &= ~0xFF; /* Clear vector */
+ lvt_tmr &= ~(0x3 << 17); /* Clear mode */
+ lvt_tmr &= ~LVT_MASK; /* Clear mask */
+
+ /* Set them to our own values */
+ lvt_tmr |= (mode & 0x3) << 17; /* Set mode */
+ lvt_tmr |= LAPIC_TMR_VEC; /* Set vector */
+ lapic_write(mcb, LAPIC_REG_LVTTMR, lvt_tmr);
+}
+
+/*
+ * Disable the Local APIC timer
+ */
+static void
+lapic_tmr_disable(struct mcb *mcb)
+{
+ uint32_t lvt_tmr;
+
+ lvt_tmr = lapic_read(mcb, LAPIC_REG_LVTTMR);
+ lvt_tmr |= LVT_MASK; /* Set mask */
+ lvt_tmr &= ~0xFF; /* Clear vector; just in case */
+ lapic_write(mcb, LAPIC_REG_LVTTMR, lvt_tmr);
+}
+
+/*
+ * Calibrate the Local APIC timer
+ */
+static size_t
+lapic_tmr_clbr(struct mcb *mcb)
+{
+ size_t freq;
+ uint16_t ticks_total;
+ uint16_t ticks_begin, ticks_end;
+ uint32_t tmp;
+
+ /*
+ * The divide configuration register basically slices
+ * up the base clock (typically TSC core crystal clock or
+ * bus clock) which makes the counter decrement slower with
+ * respect to higher values
+ */
+ tmp = lapic_read(mcb, LAPIC_REG_TDCR);
+ tmp &= ~BIT(3); /* Clear upper */
+ tmp &= ~0x3; /* Clear lower */
+ tmp |= 0x01; /* DCR=0b001[divide by 4] */
+ lapic_write(mcb, LAPIC_REG_TDCR, tmp);
+
+ lapic_tmr_disable(mcb);
+ i8254_set_count(LAPIC_TMR_SAMPLES);
+
+ /* Take some samples of the counter */
+ ticks_begin = i8254_get_count();
+ lapic_write(mcb, LAPIC_REG_TICR, LAPIC_TMR_SAMPLES);
+ while (lapic_read(mcb, LAPIC_REG_TCCR) != 0);
+
+ /* Compute the deviation [total ticks] */
+ lapic_tmr_disable(mcb);
+ ticks_end = i8254_get_count();
+ ticks_total = ticks_end - ticks_begin;
+
+ /* Compute the frequency */
+ freq = (LAPIC_TMR_SAMPLES / ticks_total) * I8254_DIVIDEND;
+ return freq;
+}
+
+/*
* Enable the Local APIC unit
*/
static void
@@ -183,5 +277,7 @@ lapic_init(void)
mcb = &ci->mcb;
mcb->xapic_io = PHYS_TO_VIRT((uintptr_t)madt->lapic_addr);
+
lapic_enable(mcb);
+ mcb->lapic_tmr_freq = lapic_tmr_clbr(mcb);
}