diff options
author | Tom <tomut@yahoo.com> | 2019-10-16 10:27:00 -0600 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-10-16 19:14:02 +0200 |
commit | 00a7c48d6e60b7a89e3ac621997795d4314ca293 (patch) | |
tree | 4ffa184a408f911fec1293c31bc5888322de1422 /Kernel/Arch | |
parent | 4c8341d0806bd86553c1939c9910c1f3f2aff6cf (diff) | |
download | serenity-00a7c48d6e60b7a89e3ac621997795d4314ca293.zip |
APIC: Enable APIC and start APs
Diffstat (limited to 'Kernel/Arch')
-rw-r--r-- | Kernel/Arch/i386/APIC.cpp | 193 | ||||
-rw-r--r-- | Kernel/Arch/i386/APIC.h | 10 | ||||
-rw-r--r-- | Kernel/Arch/i386/CPU.cpp | 1 | ||||
-rw-r--r-- | Kernel/Arch/i386/CPU.h | 50 |
4 files changed, 254 insertions, 0 deletions
diff --git a/Kernel/Arch/i386/APIC.cpp b/Kernel/Arch/i386/APIC.cpp new file mode 100644 index 0000000000..fc60da2dbd --- /dev/null +++ b/Kernel/Arch/i386/APIC.cpp @@ -0,0 +1,193 @@ +#include <AK/Assertions.h> +#include <AK/Types.h> +#include <Kernel/Arch/i386/CPU.h> +#include <Kernel/Arch/i386/APIC.h> +#include <Kernel/IO.h> +#include <Kernel/VM/MemoryManager.h> + +#define IRQ_APIC_SPURIOUS 0x1f + +#define APIC_BASE_MSR 0x1b + +#define APIC_REG_LD 0xd0 +#define APIC_REG_DF 0xe0 +#define APIC_REG_SIV 0xf0 +#define APIC_REG_ICR_LOW 0x300 +#define APIC_REG_ICR_HIGH 0x310 +#define APIC_REG_LVT_TIMER 0x320 +#define APIC_REG_LVT_THERMAL 0x330 +#define APIC_REG_LVT_PERFORMANCE_COUNTER 0x340 +#define APIC_REG_LVT_LINT0 0x350 +#define APIC_REG_LVT_LINT1 0x360 +#define APIC_REG_LVT_ERR 0x370 + +extern "C" void apic_spurious_interrupt_entry(); + +asm( + ".globl apic_spurious_interrupt_entry \n" + "apic_spurious_interrupt_entry: \n" + " iret\n"); + +namespace APIC { + +class ICRReg +{ + u32 m_reg{0}; +public: + enum DeliveryMode + { + Fixed = 0x0, + LowPriority = 0x1, + SMI = 0x2, + NMI = 0x4, + INIT = 0x5, + StartUp = 0x6, + }; + enum DestinationMode + { + Physical = 0x0, + Logical = 0x0, + }; + enum Level + { + DeAssert = 0x0, + Assert = 0x1 + }; + enum class TriggerMode + { + Edge = 0x0, + Level = 0x1, + }; + enum DestinationShorthand + { + NoShorthand = 0x0, + Self = 0x1, + AllIncludingSelf = 0x2, + AllExcludingSelf = 0x3, + }; + + ICRReg(u8 vector, DeliveryMode delivery_mode, DestinationMode destination_mode, Level level, TriggerMode trigger_mode, DestinationShorthand destination): + m_reg(vector | (delivery_mode << 8) | (destination_mode << 11) | (level << 14) | (static_cast<u32>(trigger_mode) << 15) | (destination << 18)) + { + } + + u32 low() const { return m_reg; } + u32 high() const { return 0; } +}; + +static volatile u8* g_apic_base = nullptr; + +static PhysicalAddress get_base() +{ + u32 lo, hi; + MSR msr(APIC_BASE_MSR); + msr.get(lo, hi); + return PhysicalAddress(lo & 0xfffff000); +} + +static void set_base(const PhysicalAddress& base) +{ + u32 hi = 0; + u32 lo = base.get() | 0x800; + MSR msr(APIC_BASE_MSR); + msr.set(lo, hi); +} + +static u32 apic_read(u32 off) +{ + return *(volatile u32*)(&g_apic_base[off]); +} + +static void apic_write(u32 off, u32 val) +{ + *reinterpret_cast<volatile u32*>(&g_apic_base[off]) = val; +} + +static void apic_write_icr(const ICRReg& icr) +{ + apic_write(APIC_REG_ICR_HIGH, icr.high()); + apic_write(APIC_REG_ICR_LOW, icr.low()); +} + +#define APIC_LVT_MASKED (1 << 15) +#define APIC_LVT_TRIGGER_LEVEL (1 << 14) +#define APIC_LVT(iv, dm) ((iv & 0xff) | ((dm & 0x7) << 8)) + +asm( + ".globl apic_ap_start \n" + ".type apic_ap_start, @function \n" + "apic_ap_start: \n" + ".set begin_apic_ap_start, . \n" + " jmp apic_ap_start\n" // TODO: implement + ".set end_apic_ap_start, . \n" + "\n" + ".globl apic_ap_start_size \n" + "apic_ap_start_size: \n" + ".word end_apic_ap_start - begin_apic_ap_start \n"); + +extern "C" void apic_ap_start(void); +extern "C" u16 apic_ap_start_size; + +bool init() +{ + if (!MSR::have()) + return false; + + // check if we support local apic + CPUID id(1); + if ((id.edx() & (1 << 9)) == 0) + return false; + + PhysicalAddress apic_base = get_base(); + kprintf("Initializing APIC, base: P%x\n", apic_base); + set_base(apic_base); + + MM.map_for_kernel(VirtualAddress(apic_base.get()), apic_base, true); // Map memory, disable cache! + g_apic_base = apic_base.as_ptr(); + + // copy ap init code to P8000 + MM.map_for_kernel(VirtualAddress(0x8000), PhysicalAddress(0x8000)); + memcpy(reinterpret_cast<u8*>(0x8000), reinterpret_cast<const u8*>(apic_ap_start), apic_ap_start_size); + return true; +} + +void enable(u32 cpu) +{ + kprintf("Enabling local APIC for cpu #%u\n", cpu); + + // set spurious interrupt vector + apic_write(APIC_REG_SIV, apic_read(APIC_REG_SIV) | 0x100); + + // local destination mode (flat mode) + apic_write(APIC_REG_DF, 0xf000000); + + // set destination id (note that this limits it to 8 cpus) + apic_write(APIC_REG_LD, (1 << cpu) << 24); + + register_interrupt_handler(IRQ_APIC_SPURIOUS, apic_spurious_interrupt_entry); + + apic_write(APIC_REG_LVT_TIMER, APIC_LVT(0xff, 0) | APIC_LVT_MASKED); + apic_write(APIC_REG_LVT_THERMAL, APIC_LVT(0xff, 0) | APIC_LVT_MASKED); + apic_write(APIC_REG_LVT_PERFORMANCE_COUNTER, APIC_LVT(0xff, 0) | APIC_LVT_MASKED); + apic_write(APIC_REG_LVT_LINT0, APIC_LVT(0x1f, 7) | APIC_LVT_MASKED); + apic_write(APIC_REG_LVT_LINT1, APIC_LVT(0xff, 4) | APIC_LVT_TRIGGER_LEVEL); // nmi + apic_write(APIC_REG_LVT_ERR, APIC_LVT(0xe3, 0) | APIC_LVT_MASKED); + + if (cpu == 0) { + static volatile u32 foo = 0; + + // INIT + apic_write_icr(ICRReg(0, ICRReg::INIT, ICRReg::Physical, ICRReg::Assert, ICRReg::TriggerMode::Edge, ICRReg::AllExcludingSelf)); + + for (foo = 0; foo < 0x800000; foo++); // TODO: 10 millisecond delay + + for (int i = 0; i < 2; i++) { + // SIPI + apic_write_icr(ICRReg(0x08, ICRReg::StartUp, ICRReg::Physical, ICRReg::Assert, ICRReg::TriggerMode::Edge, ICRReg::AllExcludingSelf)); // start execution at P8000 + + for (foo = 0; foo < 0x80000; foo++); // TODO: 200 microsecond delay + } + } +} + +} diff --git a/Kernel/Arch/i386/APIC.h b/Kernel/Arch/i386/APIC.h new file mode 100644 index 0000000000..3dd98a097e --- /dev/null +++ b/Kernel/Arch/i386/APIC.h @@ -0,0 +1,10 @@ +#pragma once + +#include <AK/Types.h> + +namespace APIC { + +bool init(); +void enable(u32 cpu); + +} diff --git a/Kernel/Arch/i386/CPU.cpp b/Kernel/Arch/i386/CPU.cpp index a7e4d112f5..834a612a3e 100644 --- a/Kernel/Arch/i386/CPU.cpp +++ b/Kernel/Arch/i386/CPU.cpp @@ -1,6 +1,7 @@ #include "Assertions.h" #include "IRQHandler.h" #include "PIC.h" +#include "APIC.h" #include "Process.h" #include "Scheduler.h" #include <AK/Types.h> diff --git a/Kernel/Arch/i386/CPU.h b/Kernel/Arch/i386/CPU.h index d0fa59a824..5cdcfa4467 100644 --- a/Kernel/Arch/i386/CPU.h +++ b/Kernel/Arch/i386/CPU.h @@ -238,6 +238,24 @@ inline u32 cpu_flags() return flags; } +inline u32 read_fs_u32(u32 offset) +{ + u32 val; + asm volatile( + "movl %%fs:%a[off], %k[val]" + : [val] "=r" (val) + : [off] "ir" (offset)); + return val; +} + +inline void write_fs_u32(u32 offset, u32 val) +{ + asm volatile( + "movl %k[val], %%fs:%a[off]" + :: [off] "ir" (offset), [val] "ir" (val) + : "memory"); +} + inline bool are_interrupts_enabled() { return cpu_flags() & 0x200; @@ -419,3 +437,35 @@ private: const char* m_name { nullptr }; SplitQword m_start; }; + +class MSR { + uint32_t m_msr; + +public: + static bool have() + { + CPUID id(1); + return (id.edx() & (1 << 5)) != 0; + } + + MSR(const MSR&) = delete; + MSR& operator=(const MSR&) = delete; + + MSR(uint32_t msr): + m_msr(msr) + { + } + + void get(u32& low, u32& high) + { + asm volatile("rdmsr" + : "=a"(low), "=d"(high) + : "c"(m_msr)); + } + + void set(u32 low, u32 high) + { + asm volatile("wrmsr" + :: "a"(low), "d"(high), "c"(m_msr)); + } +}; |