Parcourir la source

APIC: Enable APIC and start APs

Tom il y a 5 ans
Parent
commit
00a7c48d6e

+ 193 - 0
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
+        }
+    }
+}
+
+}

+ 10 - 0
Kernel/Arch/i386/APIC.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <AK/Types.h>
+
+namespace APIC {
+
+bool init();
+void enable(u32 cpu);
+
+}

+ 1 - 0
Kernel/Arch/i386/CPU.cpp

@@ -1,6 +1,7 @@
 #include "Assertions.h"
 #include "Assertions.h"
 #include "IRQHandler.h"
 #include "IRQHandler.h"
 #include "PIC.h"
 #include "PIC.h"
+#include "APIC.h"
 #include "Process.h"
 #include "Process.h"
 #include "Scheduler.h"
 #include "Scheduler.h"
 #include <AK/Types.h>
 #include <AK/Types.h>

+ 50 - 0
Kernel/Arch/i386/CPU.h

@@ -238,6 +238,24 @@ inline u32 cpu_flags()
     return 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()
 inline bool are_interrupts_enabled()
 {
 {
     return cpu_flags() & 0x200;
     return cpu_flags() & 0x200;
@@ -419,3 +437,35 @@ private:
     const char* m_name { nullptr };
     const char* m_name { nullptr };
     SplitQword m_start;
     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));
+    }
+};

+ 1 - 0
Kernel/Makefile

@@ -13,6 +13,7 @@ KERNEL_OBJS = \
        Arch/i386/PIT.o \
        Arch/i386/PIT.o \
        Devices/KeyboardDevice.o \
        Devices/KeyboardDevice.o \
        CMOS.o \
        CMOS.o \
+       Arch/i386/APIC.o \
        Arch/i386/PIC.o \
        Arch/i386/PIC.o \
        Syscall.o \
        Syscall.o \
        Devices/PATAChannel.o \
        Devices/PATAChannel.o \

+ 2 - 1
Kernel/VM/MemoryManager.cpp

@@ -622,13 +622,14 @@ void MemoryManager::flush_tlb(VirtualAddress vaddr)
                  : "memory");
                  : "memory");
 }
 }
 
 
-void MemoryManager::map_for_kernel(VirtualAddress vaddr, PhysicalAddress paddr)
+void MemoryManager::map_for_kernel(VirtualAddress vaddr, PhysicalAddress paddr, bool cache_disabled)
 {
 {
     auto& pte = ensure_pte(kernel_page_directory(), vaddr);
     auto& pte = ensure_pte(kernel_page_directory(), vaddr);
     pte.set_physical_page_base(paddr.get());
     pte.set_physical_page_base(paddr.get());
     pte.set_present(true);
     pte.set_present(true);
     pte.set_writable(true);
     pte.set_writable(true);
     pte.set_user_allowed(false);
     pte.set_user_allowed(false);
+    pte.set_cache_disabled(cache_disabled);
     flush_tlb(vaddr);
     flush_tlb(vaddr);
 }
 }
 
 

+ 1 - 1
Kernel/VM/MemoryManager.h

@@ -69,7 +69,7 @@ public:
 
 
     void remap_region(PageDirectory&, Region&);
     void remap_region(PageDirectory&, Region&);
 
 
-    void map_for_kernel(VirtualAddress, PhysicalAddress);
+    void map_for_kernel(VirtualAddress, PhysicalAddress, bool cache_disabled = false);
 
 
     OwnPtr<Region> allocate_kernel_region(size_t, const StringView& name, bool user_accessible = false, bool should_commit = true);
     OwnPtr<Region> allocate_kernel_region(size_t, const StringView& name, bool user_accessible = false, bool should_commit = true);
     OwnPtr<Region> allocate_user_accessible_kernel_region(size_t, const StringView& name);
     OwnPtr<Region> allocate_user_accessible_kernel_region(size_t, const StringView& name);

+ 5 - 0
Kernel/init.cpp

@@ -6,6 +6,7 @@
 #include "kstdio.h"
 #include "kstdio.h"
 #include <AK/Types.h>
 #include <AK/Types.h>
 #include <Kernel/Arch/i386/CPU.h>
 #include <Kernel/Arch/i386/CPU.h>
+#include <Kernel/Arch/i386/APIC.h>
 #include <Kernel/Arch/i386/PIC.h>
 #include <Kernel/Arch/i386/PIC.h>
 #include <Kernel/Arch/i386/PIT.h>
 #include <Kernel/Arch/i386/PIT.h>
 #include <Kernel/CMOS.h>
 #include <Kernel/CMOS.h>
@@ -241,6 +242,10 @@ extern "C" [[noreturn]] void init()
     kprintf("Starting Serenity Operating System...\n");
     kprintf("Starting Serenity Operating System...\n");
 
 
     MemoryManager::initialize();
     MemoryManager::initialize();
+
+    if (APIC::init())
+        APIC::enable(0);
+
     PIT::initialize();
     PIT::initialize();
 
 
     PCI::enumerate_all([](const PCI::Address& address, PCI::ID id) {
     PCI::enumerate_all([](const PCI::Address& address, PCI::ID id) {