[kernel] Fix SMP boot on KVM

KVM didn't like setting all the CR4 bits we wanted at once. I suspect
that means real hardware won't either. Delay the setting of the rest of
CR4 until after the CPU is in long mode - only set PAE and PGE from real
mode.
This commit is contained in:
Justin C. Miller
2021-02-13 01:45:17 -08:00
parent 36da65e15b
commit 2a347942bc
4 changed files with 55 additions and 17 deletions

View File

@@ -20,6 +20,7 @@ CR4_OSFXSR equ (1 << 9)
CR4_OSCMMEXCPT equ (1 << 10)
CR4_FSGSBASE equ (1 << 16)
CR4_PCIDE equ (1 << 17)
CR4_INIT equ CR4_PAE|CR4_PGE
CR4_VAL equ CR4_DE|CR4_PAE|CR4_MCE|CR4_PGE|CR4_OSFXSR|CR4_OSCMMEXCPT|CR4_FSGSBASE|CR4_PCIDE
EFER_MSR equ 0xC0000080
@@ -69,7 +70,8 @@ align 4
lidt [BASE + (.idtd - ap_startup)]
; Enter long mode
mov eax, CR4_VAL
mov eax, cr4
or eax, CR4_INIT
mov cr4, eax
mov eax, [BASE + (.pml4 - ap_startup)]
@@ -100,6 +102,8 @@ align 8
mov gs, ax
mov ss, ax
mov eax, CR4_VAL
mov rdi, [BASE + (.cpu - ap_startup)]
mov rax, [rdi + CPU_DATA.rsp0]
mov rsp, rax

View File

@@ -70,16 +70,32 @@ lapic::get_id()
}
void
lapic::send_ipi(ipi_mode mode, uint8_t vector, uint8_t dest)
lapic::send_ipi(ipi mode, uint8_t vector, uint8_t dest)
{
// Wait until the APIC is ready to send
ipi_wait();
apic_write(m_base, lapic_icr_high, static_cast<uint32_t>(dest) << 24);
uint32_t command =
static_cast<uint32_t>(vector) |
static_cast<uint32_t>(mode) << 8;
static_cast<uint32_t>(mode);
apic_write(m_base, lapic_icr_high, static_cast<uint32_t>(dest) << 24);
apic_write(m_base, lapic_icr_low, command);
}
void
lapic::send_ipi_broadcast(ipi mode, bool self, uint8_t vector)
{
// Wait until the APIC is ready to send
ipi_wait();
uint32_t command =
static_cast<uint32_t>(vector) |
static_cast<uint32_t>(mode) |
(self ? 0 : (1 << 18)) |
(1 << 19);
apic_write(m_base, lapic_icr_high, 0);
apic_write(m_base, lapic_icr_low, command);
}

View File

@@ -3,6 +3,7 @@
/// Classes to control both local and I/O APICs.
#include <stdint.h>
#include "kutil/enum_bitfields.h"
enum class isr : uint8_t;
@@ -18,6 +19,22 @@ protected:
uint32_t *m_base;
};
enum class ipi : uint32_t
{
// Delivery modes
fixed = 0x0000,
smi = 0x0200,
nmi = 0x0400,
init = 0x0500,
startup = 0x0600,
// Flags
deassert = 0x0000,
assert = 0x4000,
edge = 0x0000, ///< edge-triggered
level = 0x8000, ///< level-triggered
};
IS_BITFIELD(ipi);
/// Controller for processor-local APICs
class lapic :
@@ -32,19 +49,17 @@ public:
/// Get the local APIC's ID
uint8_t get_id();
enum class ipi_mode : uint8_t {
fixed = 0,
smi = 2,
nmi = 4,
init = 5,
startup = 6,
};
/// Send an inter-processor interrupt.
/// \arg mode The sending mode
/// \arg vector The interrupt vector
/// \arg dest The APIC ID of the destination
void send_ipi(ipi_mode mode, uint8_t vector, uint8_t dest);
void send_ipi(ipi mode, uint8_t vector, uint8_t dest);
/// Send an inter-processor broadcast interrupt to all other CPUs
/// \arg mode The sending mode
/// \arg self If true, include this CPU in the broadcast
/// \arg vector The interrupt vector
void send_ipi_broadcast(ipi mode, bool self, uint8_t vector);
/// Wait for an IPI to finish sending. This is done automatically
/// before sending another IPI with send_ipi().

View File

@@ -254,6 +254,9 @@ start_aps(void *kpml4)
size_t free_stack_count = 0;
uintptr_t stack_area_start = 0;
ipi mode = ipi::init | ipi::level | ipi::assert;
apic.send_ipi_broadcast(mode, false, 0);
for (uint8_t id : ids) {
if (id == apic.get_id()) continue;
@@ -287,10 +290,10 @@ start_aps(void *kpml4)
// Kick it off!
size_t current_count = ap_startup_count;
log::debug(logs::boot, "Starting AP %d: stack %llx", cpu->index, stack_end);
apic.send_ipi(lapic::ipi_mode::init, 0, id);
clk.spinwait(1000);
apic.send_ipi(lapic::ipi_mode::startup, vector, id);
ipi startup = ipi::startup | ipi::assert;
apic.send_ipi(startup, vector, id);
for (unsigned i = 0; i < 20; ++i) {
if (ap_startup_count > current_count) break;
clk.spinwait(20);
@@ -301,7 +304,7 @@ start_aps(void *kpml4)
continue;
// Send the second SIPI (intel recommends this)
apic.send_ipi(lapic::ipi_mode::startup, vector, id);
apic.send_ipi(startup, vector, id);
for (unsigned i = 0; i < 100; ++i) {
if (ap_startup_count > current_count) break;
clk.spinwait(100);