From 6f5aba866f01f5690438309629e96812fc099414 Mon Sep 17 00:00:00 2001
From: Ian Moffett <ian@osmora.org>
Date: Mon, 25 Dec 2023 14:57:12 -0500
Subject: kernel/amd64: lapic: Add initial LAPIC Timer logic

The commit adds the initial Local APIC timer support in Hyra... However,
the interrupt service routine is a stub and needs to be completed.

Signed-off-by: Ian Moffett <ian@osmora.org>
---
 sys/arch/amd64/amd64/lapic.c       | 130 ++++++++++++++++++++++++++++++++-----
 sys/arch/amd64/amd64/lapic_timer.S |  40 ++++++++++++
 sys/include/arch/amd64/lapic.h     |   2 +
 sys/include/arch/amd64/lapicvar.h  |  17 ++---
 4 files changed, 160 insertions(+), 29 deletions(-)
 create mode 100644 sys/arch/amd64/amd64/lapic_timer.S

(limited to 'sys')

diff --git a/sys/arch/amd64/amd64/lapic.c b/sys/arch/amd64/amd64/lapic.c
index 0ac2670..52e5501 100644
--- a/sys/arch/amd64/amd64/lapic.c
+++ b/sys/arch/amd64/amd64/lapic.c
@@ -32,6 +32,12 @@
 #include <machine/cpuid.h>
 #include <machine/msr.h>
 #include <machine/cpu.h>
+#include <machine/idt.h>
+#include <machine/intr.h>
+#include <vm/vm.h>
+#include <machine/sysvec.h>
+#include <machine/tss.h>
+#include <machine/isa/i8254.h>
 #include <sys/cdefs.h>
 #include <sys/timer.h>
 #include <sys/syslog.h>
@@ -57,6 +63,8 @@ __KERNEL_META("$Hyra$: lapic.c, Ian Marco Moffett, "
 
 static struct timer lapic_timer = { 0 };
 
+__naked void lapic_tmr_isr(void);
+
 /*
  * Returns true if LAPIC is supported.
  *
@@ -208,32 +216,109 @@ lapic_set_ldr(struct cpu_info *ci)
         lapic_writel(LAPIC_LDR, LAPIC_STARTUP_LID);
 }
 
+/*
+ * Starts the Local APIC countdown timer...
+ *
+ * @mask: True to mask timer.
+ * @mode: Timer mode.
+ * @count: Count to start at.
+ */
+static inline void
+lapic_timer_start(bool mask, uint8_t mode, uint32_t count)
+{
+    uint32_t tmp;
+
+    tmp = (mode << 17) | (mask << 16) | SYSVEC_LAPIC_TIMER;
+    lapic_writel(LAPIC_LVT_TMR, tmp);
+    lapic_writel(LAPIC_DCR, 0);
+    lapic_writel(LAPIC_INIT_CNT, count);
+}
+
+/*
+ * Start Local APIC timer oneshot with number
+ * of ticks to count down from.
+ *
+ * @mask: If `true', timer will be masked, `count' should be 0.
+ * @count: Number of ticks.
+ */
+void
+lapic_timer_oneshot(bool mask, uint32_t count)
+{
+    lapic_timer_start(mask, LVT_TMR_ONESHOT, count);
+}
+
+/*
+ * Start Local APIC timer oneshot in microseconds.
+ *
+ * @us: Microseconds.
+ */
+
+void
+lapic_timer_oneshot_us(uint32_t us)
+{
+    uint64_t ticks;
+    struct cpu_info *ci = this_cpu();
+
+    ticks = us * (ci->lapic_tmr_freq / 1000000);
+    lapic_timer_oneshot(false, ticks);
+}
+
+/*
+ * Calibrates the Local APIC timer
+ *
+ * TODO: Disable interrupts and put them back in
+ *       old state.
+ *
+ * XXX: Will unmask IRQs if masked; will restore
+ *      IRQ mask state after.
+ */
 void
 lapic_timer_init(size_t *freq_out)
 {
-    uint32_t ticks_per_10ms;
-    const uint32_t MAX_SAMPLES = 0xFFFFFFFF;
+    struct cpu_info *ci;
+    bool irq_mask = is_intr_mask();
+    uint16_t init_tick, final_tick;
+    size_t total_ticks;
+    const uint16_t SAMPLES = 0xFFFF;
 
-    lapic_writel(LAPIC_DCR, 3);                     /* Use divider 16 */
-    lapic_writel(LAPIC_INIT_CNT, MAX_SAMPLES);      /* Set the initial count */
+    ci = this_cpu();
 
-    hpet_msleep(10);                                /* Wait 10ms (10000 usec) */
-    lapic_writel(LAPIC_LVT_TMR, LAPIC_LVT_MASK);    /* Stop the timer w/ LVT mask bit */
+    if (irq_mask) {
+        __ASMV("sti");
+    }
 
-    /* Sanity check */
-    if (freq_out == NULL)
-        panic("lapic_timer_init() freq_out NULL\n");
+    /* Stop the timer */
+    lapic_timer_oneshot(true, 0);
+
+    i8254_set_reload(SAMPLES);
+    init_tick = i8254_get_count();
+
+    lapic_writel(LAPIC_INIT_CNT, SAMPLES);
+    while (lapic_readl(LAPIC_CUR_CNT) != 0);
 
-    ticks_per_10ms = MAX_SAMPLES - lapic_readl(LAPIC_CUR_CNT);
-    *freq_out = ticks_per_10ms;
+    final_tick = i8254_get_count();
+    total_ticks = init_tick - final_tick;
+
+    /* Calculate the frequency */
+    CPU_INFO_LOCK(ci);
+    ci->lapic_tmr_freq = (SAMPLES / total_ticks) * i8254_DIVIDEND;
+    if (freq_out != NULL) *freq_out = ci->lapic_tmr_freq;
+    CPU_INFO_UNLOCK(ci);
+
+    /* Stop timer again */
+    lapic_timer_oneshot(true, 0);
+
+    if (irq_mask) {
+        __ASMV("cli");
+    }
 }
 
 void
 lapic_init(void)
 {
     struct cpu_info *ci;
+    union tss_stack tmr_stack;
     uint64_t tmp;
-    size_t tmr_freq;
 
     if (!lapic_check_support()) {
         /*
@@ -273,13 +358,24 @@ lapic_init(void)
     /* Register the timer for scheduler usage */
     register_timer(TIMER_SCHED, &lapic_timer);
 
-    /* Calibrate timer */
-    lapic_timer_init(&tmr_freq);
-    ci->lapic_tmr_freq = tmr_freq;
-
     /* Set the Local APIC ID */
     ci->id = lapic_get_id(ci);
-
     BSP_KINFO("BSP Local APIC ID: %d\n", ci->id);
+
+    /* Setup LAPIC Timer TSS stack */
+    if (tss_alloc_stack(&tmr_stack, vm_get_page_size()) != 0) {
+        panic("Failed to allocate LAPIC TMR stack! (1 page of mem)\n");
+    }
+    tss_update_ist(ci, tmr_stack, IST_SCHED);
     CPU_INFO_UNLOCK(ci);
+
+    /* Calibrate timer */
+    lapic_timer_init(NULL);
+
+    /* Setup LAPIC Timer ISR */
+    idt_set_desc(SYSVEC_LAPIC_TIMER, IDT_INT_GATE_USER,
+                 (uintptr_t)lapic_tmr_isr, IST_SCHED);
+
+    BSP_KINFO("LAPIC Timer on Interrupt Stack %d (IST_SCHED) with vector 0x%x\n",
+              IST_SCHED, SYSVEC_LAPIC_TIMER);
 }
diff --git a/sys/arch/amd64/amd64/lapic_timer.S b/sys/arch/amd64/amd64/lapic_timer.S
new file mode 100644
index 0000000..bd609f8
--- /dev/null
+++ b/sys/arch/amd64/amd64/lapic_timer.S
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * 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.
+ * 3. Neither the name of Hyra nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * 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.
+ */
+
+.text
+ .globl lapic_tmr_isr
+
+lapic_tmr_isr:
+    lea stub_msg(%rip), %rdi
+    call kprintf
+    cli
+    hlt
+
+.section .rodata
+stub_msg: .ascii "**LAPIC TIMER ISR IS A STUB; HALTING**\n\0"
diff --git a/sys/include/arch/amd64/lapic.h b/sys/include/arch/amd64/lapic.h
index 1e7caf6..1ef41fb 100644
--- a/sys/include/arch/amd64/lapic.h
+++ b/sys/include/arch/amd64/lapic.h
@@ -36,6 +36,8 @@
 #define LAPIC_TMR_PERIODIC  0x01
 
 void lapic_timer_init(size_t *freq_out);
+void lapic_timer_oneshot(bool mask, uint32_t count);
+void lapic_timer_oneshot_us(uint32_t us);
 void lapic_init(void);
 
 #endif  /* !_AMD64_LAPIC_H_ */
diff --git a/sys/include/arch/amd64/lapicvar.h b/sys/include/arch/amd64/lapicvar.h
index aad9fa8..43c89a6 100644
--- a/sys/include/arch/amd64/lapicvar.h
+++ b/sys/include/arch/amd64/lapicvar.h
@@ -90,18 +90,11 @@
 
 /* LVT bits */
 #define LAPIC_LVT_MASK            __BIT(16)
+#define LVT_TMR_ONESHOT           0x00
+#define LVT_TMR_PERIODIC          0x01
+#define LVT_TMR_TSC_DEADLINE      0x02
 
-/*
- * Local APIC Interrupt stack [IST VALUE].
- *
- * This value should be non-zero and reserved
- * for only 1 interrupt vector to prevent clobbering
- * of the interrupt stacks.
- *
- * XXX TODO: The value is currently 0, however, this needs
- *           to be updated to a non-zero value as soon as
- *           possible.
- */
-#define LAPIC_TMR_INTSTACK  0
+/* LAPIC timer interrupt stack size in bytes */
+#define LAPIC_TMR_STACKSZ 4096
 
 #endif  /* !_AMD64_LAPICVAR_H_ */
-- 
cgit v1.2.3