Document amd64 platform-specific code

This commit is contained in:
2025-12-30 16:50:15 +01:00
parent 4f4f5c3d2f
commit 34f1e0ba30
17 changed files with 171 additions and 19 deletions

View File

@@ -45,7 +45,9 @@ static size_t intr_src_override_entries = 0;
/// Local APIC MMIO base address. It comes from MSR_APIC_BASE
static uintptr_t lapic_mmio_base = 0;
/** @cond DOXYGEN_IGNORE */
extern void amd64_spin (void);
/** @endcond */
/// Read IOAPIC
static uint32_t amd64_ioapic_read (uintptr_t vaddr, uint32_t reg) {

View File

@@ -1,3 +1,5 @@
/** @file */
#include <amd64/apic.h>
#include <amd64/debug.h>
#include <amd64/hpet.h>
@@ -19,8 +21,15 @@
#define UACPI_MEMORY_BUFFER_MAX 4096
/** @cond DOXYGEN_IGNORE */
ALIGNED (16) static uint8_t uacpi_memory_buffer[UACPI_MEMORY_BUFFER_MAX];
/** @endcond */
/**
* @brief The kernel starts booting here. This is the entry point after
* Limine hands control. We set up all the necessary platform-dependent
* subsystems/drivers and jump into the init app.
*/
void bootmain (void) {
struct cpu* bsp_cpu = cpu_make ();
cpu_assign (bsp_cpu->id);

View File

@@ -6,21 +6,29 @@
#include <sync/spin_lock.h>
#include <sys/debug.h>
/// Port for printing to serial
#define PORT_COM1 0x03F8
/// \ref debugprintf buffer size
#define BUFFER_SIZE 1024
/// Lock, which ensures that prints to the serial port are atomic
static spin_lock_t serial_lock = SPIN_LOCK_INIT;
spin_lock_t serial_lock = SPIN_LOCK_INIT;
/// Block until TX buffer is empty
static bool amd64_debug_serial_tx_empty (void) {
return (bool)(amd64_io_inb (PORT_COM1 + 5) & 0x20);
}
/// Write a single character to serial
static void amd64_debug_serial_write (char x) {
while (!amd64_debug_serial_tx_empty ())
;
amd64_io_outb (PORT_COM1, (uint8_t)x);
}
/**
* @brief Formatted printing to serial. \ref serial_lock ensures that
* all prints are atomic.
*/
void debugprintf (const char* fmt, ...) {
char buffer[BUFFER_SIZE];
memset (buffer, 0, sizeof (buffer));
@@ -44,6 +52,7 @@ void debugprintf (const char* fmt, ...) {
spin_unlock (&serial_lock);
}
/// Initialize serial
void amd64_debug_init (void) {
amd64_io_outb (PORT_COM1 + 1, 0x00);
amd64_io_outb (PORT_COM1 + 3, 0x80);

View File

@@ -5,8 +5,17 @@
#include <libk/std.h>
#include <proc/proc.h>
/// Size of kernel stack
#define KSTACK_SIZE (32 * 1024)
/**
* @file
*
* @brief 64-bit GDT structure. For more info see:
* - https://wiki.osdev.org/Global_Descriptor_Table
* - https://wiki.osdev.org/GDT_Tutorial
*/
struct gdt_entry {
uint16_t limitlow;
uint16_t baselow;

View File

@@ -9,19 +9,34 @@
#include <uacpi/tables.h>
#include <uacpi/uacpi.h>
#define HPET_MCVR 0xF0 /* Main Counter Value Register */
#define HPET_GCR 0x10 /* General Configuration Register */
#define HPET_GCIDR 0x00 /* General Capabilities and ID Register */
/**
* @file
*
* @brief HPET (High Precision Event Timer) driver code.
* See more at https://wiki.osdev.org/HPET
*/
/// HPET Main Counter Value Register
#define HPET_MCVR 0xF0
/// HPET General Configuration Register
#define HPET_GCR 0x10
/// HPET General Capabilities and ID Register
#define HPET_GCIDR 0x00
/// Set whether we sould use 32-bit or 64-bit reads/writes
static bool hpet_32bits = 1;
/// Physical address for HPET MMIO
static uintptr_t hpet_paddr;
/// HPET nanoseconds for conversion
static uint64_t hpet_clock_nano;
/// Lock, which protects concurrent access. See \ref amd64/smp.c
static spin_lock_t hpet_lock = SPIN_LOCK_INIT;
/** @cond DOXYGEN_IGNORE */
extern void amd64_spin (void);
/** @endcond */
/* These functions assume hpet_lock is held by the caller! */
/// Read a HPET register. Assumes caller holds \ref hpet_lock
static uint64_t amd64_hpet_read (uint32_t reg) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
uintptr_t hpet_vaddr = hpet_paddr + (uintptr_t)hhdm->offset;
@@ -29,6 +44,7 @@ static uint64_t amd64_hpet_read (uint32_t reg) {
: *(volatile uint64_t*)(hpet_vaddr + reg));
}
/// Write a HPET register. Assumes caller holds \ref hpet_lock
static void amd64_hpet_write (uint32_t reg, uint64_t value) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
uintptr_t hpet_vaddr = hpet_paddr + (uintptr_t)hhdm->offset;
@@ -38,8 +54,15 @@ static void amd64_hpet_write (uint32_t reg, uint64_t value) {
*(volatile uint64_t*)(hpet_vaddr + reg) = value;
}
/// Read current value of \ref HPET_MCVR register.
static uint64_t amd64_hpet_timestamp (void) { return amd64_hpet_read (HPET_MCVR); }
/**
* @brief Get current HPET timestamp in nanoseconds
*
* @param lock
* if true, hold \ref hpet_lock
*/
uint64_t amd64_hpet_current_nano (bool lock) {
if (lock)
spin_lock (&hpet_lock);
@@ -52,6 +75,7 @@ uint64_t amd64_hpet_current_nano (bool lock) {
return t;
}
/// Sleep for a given amount of microseconds. This time can last longer due to \ref hpet_lock being held.
void amd64_hpet_sleep_micro (uint64_t us) {
spin_lock (&hpet_lock);
@@ -63,6 +87,7 @@ void amd64_hpet_sleep_micro (uint64_t us) {
spin_unlock (&hpet_lock);
}
/// Initialize HPET
void amd64_hpet_init (void) {
struct uacpi_table hpet_table;
uacpi_status status = uacpi_table_find_by_signature (ACPI_HPET_SIGNATURE, &hpet_table);

View File

@@ -14,6 +14,7 @@
#define TSS 0x80
#define TSS_PRESENT 0x89
/// Set a GDT entry
static void amd64_gdt_set (volatile struct gdt_entry* ent, uint32_t base, uint32_t limit,
uint8_t acc, uint8_t gran) {
ent->baselow = (base & 0xFFFF);
@@ -24,6 +25,7 @@ static void amd64_gdt_set (volatile struct gdt_entry* ent, uint32_t base, uint32
ent->access = acc;
}
/// Initialize GDT and TSS structures for a given CPU
static void amd64_gdt_init (struct cpu* cpu) {
volatile struct tss* tss = &cpu->tss;
volatile struct gdt_extended* gdt = &cpu->gdt;
@@ -75,6 +77,13 @@ static void amd64_gdt_init (struct cpu* cpu) {
__asm__ volatile ("ltr %0" ::"r"((uint16_t)GDT_TSS));
}
/**
* @brief Initialize essentials (GDT, TSS, IDT) for a given CPU
*
* @param load_idt
* Tell whether the IDT needs to be loaded. It only has to be loaded once on
* the BSP
*/
void amd64_init (struct cpu* cpu, bool load_idt) {
amd64_gdt_init (cpu);
if (load_idt)

View File

@@ -37,6 +37,7 @@
#define IDT_ENTRIES_MAX 256
/// 64-bit <IDT entry structure: https://wiki.osdev.org/Interrupt_Descriptor_Table
struct idt_entry {
uint16_t intrlow;
uint16_t kernel_cs;
@@ -52,16 +53,22 @@ struct idt {
uint64_t base;
} PACKED;
/** @cond DOXYGEN_IGNORE */
ALIGNED (16) static volatile struct idt_entry idt_entries[IDT_ENTRIES_MAX];
/** @endcond */
static volatile struct idt idt;
/** @cond DOXYGEN_IGNORE */
extern void amd64_spin (void);
/** @endcond */
/* Remaps and disables old 8259 PIC, since we'll be using APIC. */
/// Remaps and disables old 8259 PIC, since we'll be using APIC.
static void amd64_init_pic (void) {
/** @cond DOXYGEN_IGNORE */
#define IO_OP(fn, ...) \
fn (__VA_ARGS__); \
amd64_io_wait ()
/** @endcond */
IO_OP (amd64_io_outb, PIC1_CMD, (ICW1_INIT | ICW1_ICW4));
IO_OP (amd64_io_outb, PIC2_CMD, (ICW1_INIT | ICW1_ICW4));
@@ -82,6 +89,7 @@ static void amd64_init_pic (void) {
#undef IO_OP
}
/// Set IDT entry
static void amd64_idt_set (volatile struct idt_entry* ent, uint64_t handler, uint8_t flags,
uint8_t ist) {
ent->intrlow = (handler & 0xFFFF);
@@ -93,14 +101,18 @@ static void amd64_idt_set (volatile struct idt_entry* ent, uint64_t handler, uin
ent->resv = 0;
}
/// Load the IDT
void amd64_load_idt (void) { __asm__ volatile ("lidt %0" ::"m"(idt)); }
/// Initialize IDT entries
static void amd64_idt_init (void) {
memset ((void*)idt_entries, 0, sizeof (idt_entries));
/** @cond DOXYGEN_IGNORE */
#define IDT_ENTRY(n, ist) \
extern void amd64_intr##n (void); \
amd64_idt_set (&idt_entries[(n)], (uint64_t)&amd64_intr##n, 0x8E, (ist))
/** @endcond */
/* clang-format off */
IDT_ENTRY (0, 0); IDT_ENTRY (1, 0); IDT_ENTRY (2, 0); IDT_ENTRY (3, 0);
IDT_ENTRY (4, 0); IDT_ENTRY (5, 0); IDT_ENTRY (6, 0); IDT_ENTRY (7, 0);
@@ -126,6 +138,13 @@ static void amd64_idt_init (void) {
amd64_load_idt ();
}
/**
* @brief Handle CPU exception and dump registers. If incoming CS has CPL3, kill the
* process.
*
* @param regs
* saved registers
*/
static void amd64_intr_exception (struct saved_regs* regs) {
DEBUG ("cpu exception %lu (%lu)\n", regs->trap, regs->error);
@@ -154,6 +173,7 @@ static void amd64_intr_exception (struct saved_regs* regs) {
}
}
/// Handle incoming interrupt, dispatch IRQ handlers.
void amd64_intr_handler (void* stack_ptr) {
struct saved_regs* regs = stack_ptr;
@@ -176,6 +196,7 @@ void amd64_intr_handler (void* stack_ptr) {
}
}
/// Initialize interrupts
void amd64_intr_init (void) {
amd64_init_pic ();
amd64_idt_init ();
@@ -183,17 +204,20 @@ void amd64_intr_init (void) {
/* Aux. */
/// Save RFLAGS of the current CPU
static uint64_t amd64_irq_save_flags (void) {
uint64_t rflags;
__asm__ volatile ("pushfq; cli; popq %0" : "=r"(rflags)::"memory", "cc");
return rflags;
}
/// Restore interrupts (IF bit) from RFLAGS
static void amd64_irq_restore_flags (uint64_t rflags) {
if (rflags & (1ULL << 9))
__asm__ volatile ("sti");
}
/// Save current interrupt state
void irq_save (void) {
/* before smp init. */
if (thiscpu == NULL)
@@ -204,6 +228,7 @@ void irq_save (void) {
thiscpu->irq_ctx.rflags = amd64_irq_save_flags ();
}
/// Restore interrupt state
void irq_restore (void) {
/* before smp init. */
if (thiscpu == NULL)
@@ -214,6 +239,7 @@ void irq_restore (void) {
amd64_irq_restore_flags (thiscpu->irq_ctx.rflags);
}
/// Map custom IRQ mappings to legacy IRQs
uint8_t amd64_resolve_irq (uint8_t irq) {
static const uint8_t mappings[] = {
[SCHED_PREEMPT_TIMER] = 0,

View File

@@ -1,6 +1,12 @@
#ifndef _KERNEL_AMD64_INTR_DEFS_H
#define _KERNEL_AMD64_INTR_DEFS_H
/**
* @file
* Definitions for custom, nonstandard IDT entries. They have to be remapped
* by \ref amd64_resolve_irq into legacy IRQs.
*/
#define SCHED_PREEMPT_TIMER 80
#define TLB_SHOOTDOWN 81

View File

@@ -1,42 +1,51 @@
#include <amd64/io.h>
#include <libk/std.h>
/// Perform outb instruction (send 8-bit int)
void amd64_io_outb (uint16_t port, uint8_t v) {
__asm__ volatile ("outb %1, %0" ::"dN"(port), "a"(v));
}
/// Perform outw instruction (send 16-bit int)
void amd64_io_outw (uint16_t port, uint16_t v) {
__asm__ volatile ("outw %%ax, %%dx" ::"a"(v), "d"(port));
}
/// Perform outl instruction (send 32-bit int)
void amd64_io_outl (uint16_t port, uint32_t v) {
__asm__ volatile ("outl %%eax, %%dx" ::"d"(port), "a"(v));
}
/// Perform outsw instruction (send a string)
void amd64_io_outsw (uint16_t port, const void* addr, int cnt) {
__asm__ volatile ("cld; rep outsw" : "+S"(addr), "+c"(cnt) : "d"(port) : "memory", "cc");
}
/// Perform inb instruction (receive 8-bit int)
uint8_t amd64_io_inb (uint16_t port) {
uint8_t r;
__asm__ volatile ("inb %1, %0" : "=a"(r) : "dN"(port));
return r;
}
/// Perform inw instruction (receive 16-bit int)
uint16_t amd64_io_inw (uint16_t port) {
uint16_t r;
__asm__ volatile ("inw %%dx, %%ax" : "=a"(r) : "d"(port));
return r;
}
/// Perform inl instruction (receive 32-bit int)
uint32_t amd64_io_inl (uint16_t port) {
uint32_t r;
__asm__ volatile ("inl %%dx, %%eax" : "=a"(r) : "d"(port));
return r;
}
/// Perform insw instruction (receive a string)
void amd64_io_insw (uint16_t port, void* addr, int cnt) {
__asm__ volatile ("cld; rep insw" : "+D"(addr), "+c"(cnt) : "d"(port) : "memory", "cc");
}
/// output a byte on port 0x80, which does a small IO delay
void amd64_io_wait (void) { amd64_io_outb (0x80, 0); }

View File

@@ -11,28 +11,36 @@
#include <sys/mm.h>
#include <sys/smp.h>
/// Present flag
#define AMD64_PG_PRESENT (1 << 0)
/// Writable flag
#define AMD64_PG_RW (1 << 1)
/// User-accessible flag
#define AMD64_PG_USER (1 << 2)
/// Auxilary struct for page directory walking
struct pg_index {
uint16_t pml4, pml3, pml2, pml1;
} PACKED;
/// Kernel page directory
static struct pd kernel_pd = {.lock = SPIN_LOCK_INIT};
/* This is needed to sync between map/unmap operations and TLB shootdown. */
/// Lock needed to sync between map/unmap operations and TLB shootdown
static spin_lock_t mm_lock = SPIN_LOCK_INIT;
/// Get current value of CR3 register
static uintptr_t amd64_current_cr3 (void) {
uintptr_t cr3;
__asm__ volatile ("movq %%cr3, %0" : "=r"(cr3)::"memory");
return cr3;
}
/// Load kernel CR3 as current CR3
void amd64_load_kernel_cr3 (void) {
__asm__ volatile ("movq %0, %%cr3" ::"r"(kernel_pd.cr3_paddr) : "memory");
}
/// Extract PML info from virtual address
static struct pg_index amd64_mm_page_index (uint64_t vaddr) {
struct pg_index ret;
@@ -44,6 +52,7 @@ static struct pg_index amd64_mm_page_index (uint64_t vaddr) {
return ret;
}
/// Walk paging tables and allocate necessary structures along the way
static uint64_t* amd64_mm_next_table (uint64_t* table, uint64_t entry_idx, bool alloc) {
uint64_t entry = table[entry_idx];
physaddr_t paddr;
@@ -68,6 +77,7 @@ static uint64_t* amd64_mm_next_table (uint64_t* table, uint64_t entry_idx, bool
return (uint64_t*)((uintptr_t)hhdm->offset + (uintptr_t)paddr);
}
/// Convert generic memory management subsystem flags into AMD64-specific flags
static uint64_t amd64_mm_resolve_flags (uint32_t generic) {
uint64_t flags = 0;
@@ -78,11 +88,16 @@ static uint64_t amd64_mm_resolve_flags (uint32_t generic) {
return flags;
}
/// Reload the current CR3 value ON A LOCAL CPU
static void amd64_reload_cr3 (void) {
uint64_t cr3;
__asm__ volatile ("movq %%cr3, %0; movq %0, %%cr3" : "=r"(cr3)::"memory");
}
/**
* @brief Map physical address to virtual address with flags. TLB needs to be flushed
* afterwards.
*/
void mm_map_page (struct pd* pd, uintptr_t paddr, uintptr_t vaddr, uint32_t flags) {
spin_lock (&mm_lock);
@@ -124,10 +139,12 @@ done:
spin_unlock (&mm_lock);
}
/// Map a page into kernel page directory
void mm_map_kernel_page (uintptr_t paddr, uintptr_t vaddr, uint32_t flags) {
mm_map_page (&kernel_pd, paddr, vaddr, flags);
}
/// Unmap a virtual address. TLB needs to be flushed afterwards
void mm_unmap_page (struct pd* pd, uintptr_t vaddr, uint32_t flags) {
spin_lock (&mm_lock);
@@ -167,14 +184,18 @@ done:
spin_unlock (&mm_lock);
}
/// Unmap a page from kernel page directory
void mm_unmap_kernel_page (uintptr_t vaddr, uint32_t flags) {
mm_unmap_page (&kernel_pd, vaddr, flags);
}
/// Lock kernel page directory
void mm_lock_kernel (void) { spin_lock (&kernel_pd.lock); }
/// Unlock kernel page directory
void mm_unlock_kernel (void) { spin_unlock (&kernel_pd.lock); }
/// Allocate a userspace-ready page directory
uintptr_t mm_alloc_user_pd_phys (void) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
@@ -192,6 +213,10 @@ uintptr_t mm_alloc_user_pd_phys (void) {
return cr3;
}
/**
* @brief Reload after map/unmap operation was performed. This function does the TLB
* shootdown.
*/
void mm_reload (void) {
spin_lock (&mm_lock);
@@ -204,6 +229,7 @@ void mm_reload (void) {
spin_unlock (&mm_lock);
}
/// TLB shootdown IRQ handler
static void amd64_tlb_shootdown_irq (void* arg, void* regs) {
(void)arg, (void)regs;
@@ -211,8 +237,13 @@ static void amd64_tlb_shootdown_irq (void* arg, void* regs) {
DEBUG ("cpu %u TLB shootdown\n", thiscpu->id);
}
/**
* @brief Continue initializing memory management subsystem for AMD64 after the
* essential parts were initialized
*/
void mm_init2 (void) {
irq_attach (&amd64_tlb_shootdown_irq, NULL, TLB_SHOOTDOWN, IRQ_INTERRUPT_SAFE);
}
/// Initialize essentials for the AMD64 memory management subsystem
void mm_init (void) { kernel_pd.cr3_paddr = amd64_current_cr3 (); }

View File

@@ -1,12 +1,14 @@
#include <amd64/msr.h>
#include <libk/std.h>
/// Read a model-specific register
uint64_t amd64_rdmsr (uint32_t msr) {
uint32_t low, high;
__asm__ volatile ("rdmsr" : "=a"(low), "=d"(high) : "c"(msr));
return ((uint64_t)high << 32 | (uint64_t)low);
}
/// Write a model-specific register
void amd64_wrmsr (uint32_t msr, uint64_t value) {
uint32_t low = (uint32_t)(value & 0xFFFFFFFF);
uint32_t high = (uint32_t)(value >> 32);

View File

@@ -4,9 +4,12 @@
#include <amd64/intr.h>
#include <libk/std.h>
/// Top of userspace process' stack
#define PROC_USTACK_TOP 0x00007FFFFFFFF000ULL
/// Size of userspace process' stack
#define USTACK_SIZE (256 * PAGE_SIZE)
/// Platform-dependent process data
struct proc_platformdata {
struct saved_regs regs;
uintptr_t syscall_stack;

View File

@@ -1,6 +1,7 @@
#ifndef _KERNEL_AMD64_SCHED_H
#define _KERNEL_AMD64_SCHED_H
/// Perform process context switch
void amd64_do_sched (void* regs, void* cr3);
#endif // _KERNEL_AMD64_SCHED_H

View File

@@ -11,10 +11,14 @@
#include <sys/debug.h>
#include <sys/smp.h>
/// Cpu ID counter
static uint32_t cpu_counter = 0;
/// Lock for \ref cpu_counter
static spin_lock_t cpu_counter_lock = SPIN_LOCK_INIT;
/// The CPUs
static struct cpu cpus[CPUS_MAX];
/// Allocate a CPU structure
struct cpu* cpu_make (void) {
spin_lock (&cpu_counter_lock);
int id = cpu_counter++;
@@ -36,10 +40,13 @@ struct cpu* cpu_get (uint32_t id) {
return &cpus[id];
}
/// Get ID of current running CPU
uint32_t cpu_id (void) { return (uint32_t)amd64_rdmsr (MSR_GS_BASE); }
/// Assign an ID to the current running CPU
void cpu_assign (uint32_t id) { amd64_wrmsr (MSR_GS_BASE, (uint64_t)id); }
/// Bootstrap code for non-BSP CPUs
static void amd64_smp_bootstrap (struct limine_mp_info* mp_info) {
amd64_load_kernel_cr3 ();
@@ -59,6 +66,7 @@ static void amd64_smp_bootstrap (struct limine_mp_info* mp_info) {
;
}
/// Initialize SMP subsystem for AMD64. Start AP CPUs
void smp_init (void) {
thiscpu->lapic_ticks = amd64_lapic_init (2500);

View File

@@ -1,3 +1,4 @@
#include <sys/spin_lock.h>
/// Relax the spinlock using AMD64 pause instruction
void spin_lock_relax (void) { __asm__ volatile ("pause"); }

View File

@@ -2,4 +2,5 @@
#include <libk/std.h>
#include <sys/time.h>
/// Sleep for given amount of microseconds
void sleep_micro (size_t us) { amd64_hpet_sleep_micro (us); }

View File

@@ -4,6 +4,7 @@
#include <aux/compiler.h>
#include <libk/std.h>
/// 64-bit TSS structure: https://wiki.osdev.org/Task_State_Segment
struct tss {
uint32_t resv0;
uint64_t rsp0;