WIP x86-smp

add uspace mmu support for x86-64
trampoline x86-64 cpus to long mode and into the kernel aspace
This commit is contained in:
Travis Geiselbrecht
2024-12-11 00:17:09 -08:00
parent 1ca821ec54
commit 6538baea70
13 changed files with 446 additions and 59 deletions

View File

@@ -22,10 +22,59 @@
#include <platform/pc.h>
#include <kernel/vm.h>
#define LOCAL_TRACE 0
#define LOCAL_TRACE 1
static bool lapic_present = false;
static uint8_t *lapic_mmio;
static volatile uint32_t *lapic_mmio;
// local apic registers
enum lapic_regs {
LAPIC_ID = 0x20,
LAPIC_VERSION = 0x30,
LAPIC_TPR = 0x80,
LAPIC_APR = 0x90,
LAPIC_PPR = 0xa0,
LAPIC_EOI = 0xb0,
LAPIC_RRD = 0xc0,
LAPIC_LDR = 0xd0,
LAPIC_DFR = 0xe0,
LAPIC_SVR = 0xf0,
LAPIC_ISR0 = 0x100,
LAPIC_TMR0 = 0x180,
LAPIC_IRR0 = 0x200,
LAPIC_ESR = 0x280,
LAPIC_CMCI = 0x2f0,
LAPIC_ICRLO = 0x300,
LAPIC_ICRHI = 0x310,
LAPIC_TIMER = 0x320,
LAPIC_THERMAL = 0x330,
LAPIC_PERF = 0x340,
LAPIC_LINT0 = 0x350,
LAPIC_LINT1 = 0x360,
LAPIC_ERROR = 0x370,
LAPIC_TICR = 0x380,
LAPIC_TCCR = 0x390,
LAPIC_DIV = 0x3e0,
// Extended features
LAPIC_EXT_FEATURES = 0x400,
LAPIC_EXT_CONTROL = 0x410,
LAPIC_EXT_SEOI = 0x420,
LAPIC_EXT_IER0 = 0x480,
LAPIC_EXT_LVT0 = 0x500,
};
static uint32_t lapic_read(enum lapic_regs reg) {
return mmio_read32(lapic_mmio + reg / 4);
}
static void lapic_write(enum lapic_regs reg, uint32_t val) {
mmio_write32(lapic_mmio + reg / 4, val);
}
void lapic_init(void) {
// discover the presence of the local apic and map it
@@ -43,9 +92,14 @@ void lapic_init_postvm(uint level) {
// IA32_APIC_BASE_MSR
uint64_t apic_base = read_msr(0x1b);
LTRACEF("apic base %#llx\n", apic_base);
LTRACEF("raw apic base msr %#llx\n", apic_base);
// TODO: assert that it's enabled
// make sure it's enabled
if ((apic_base & 0x800) == 0) {
dprintf(INFO, "X86: enabling lapic\n");
apic_base |= 0x800;
write_msr(0x1b, apic_base);
}
apic_base &= ~0xfff;
dprintf(INFO, "X86: lapic physical address %#llx\n", apic_base);
@@ -54,6 +108,20 @@ void lapic_init_postvm(uint level) {
status_t err = vmm_alloc_physical(vmm_get_kernel_aspace(), "lapic", PAGE_SIZE, (void **)&lapic_mmio, 0,
apic_base & ~0xfff, /* vmm_flags */ 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
ASSERT(err == NO_ERROR);
// Read the local apic id and version and features
uint32_t id = lapic_read(LAPIC_ID);
uint32_t version = lapic_read(LAPIC_VERSION);
bool eas = version & (1u<<31);
uint32_t max_lvt = (version >> 16) & 0xff;
version &= 0xff;
dprintf(INFO, "X86: local apic id %#x version %#x\n", id, version);
dprintf(INFO, "X86: local apic max lvt entries %u\n", max_lvt);
if (eas) {
dprintf(INFO, "X86: local apic EAS features %#x\n", lapic_read(LAPIC_EXT_FEATURES));
}
}
LK_INIT_HOOK(lapic, lapic_init_postvm, LK_INIT_LEVEL_VM);
@@ -61,7 +129,28 @@ LK_INIT_HOOK(lapic, lapic_init_postvm, LK_INIT_LEVEL_VM);
void lapic_eoi(unsigned int vector) {
LTRACEF("vector %#x\n", vector);
if (lapic_present) {
*REG32(lapic_mmio + 0xb0) = 1;
lapic_write(LAPIC_EOI, 0);
}
}
void lapic_send_init_ipi(uint32_t apic_id, bool level) {
if (lapic_present) {
lapic_write(LAPIC_ICRHI, apic_id << 24);
lapic_write(LAPIC_ICRLO, (5u << 8) | (level ? (1u << 14) : 0));
}
}
void lapic_send_startup_ipi(uint32_t apic_id, uint32_t startup_vector) {
if (lapic_present) {
lapic_write(LAPIC_ICRHI, apic_id << 24);
lapic_write(LAPIC_ICRLO, (6u << 8) | (startup_vector >> 12));
}
}
void lapic_send_ipi(uint32_t apic_id, uint32_t vector) {
if (lapic_present) {
lapic_write(LAPIC_ICRHI, apic_id << 24);
// XXX add correct flag bits
lapic_write(LAPIC_ICRLO, vector);
}
}

167
platform/pc/mp-boot.S Normal file
View File

@@ -0,0 +1,167 @@
#include <lk/asm.h>
#include <arch/x86/descriptor.h>
#define LOAD_ADDRESS 0x4000
#define MSR_EFER 0xc0000080
#define EFER_LME 0x00000100
#define ARGS_ADDRESS (LOAD_ADDRESS + 0x1000)
#define ARGS_CR3 (ARGS_ADDRESS + 0x00)
#define ARGS_STACK (ARGS_ADDRESS + 0x08)
.text
.code16
// secondary cpu boot entry point and switch to protected mode
// enters with the following state:
// real mode, CS 0x0400, PC 0 (physical address 0x4000)
FUNCTION(mp_boot_start)
// jump over the temp GDT below and switch to a flat memory segment (0)
ljmp $0, $(LOAD_ADDRESS + 0x28)
.org 0x8
.Lgdt:
// temporary GDT to get us into protected mode
// stuff the GDTR in the first entry
.short (8*4)
.int (LOAD_ADDRESS + 0x8) // address of .Lgdt
.short 0
// 0x8 code flat 32bit
.short 0xffff /* limit 15:00 */
.short 0x0000 /* base 15:00 */
.byte 0x00 /* base 23:16 */
.byte 0b10011010 /* P(1) DPL(00) S(1) 1 C(0) R(1) A(0) */
.byte 0b11001111 /* G(1) D(1) 0 0 limit 19:16 */
.byte 0x0 /* base 31:24 */
// 0x10 data flat 32bit
.short 0xffff /* limit 15:00 */
.short 0x0000 /* base 15:00 */
.byte 0x00 /* base 23:16 */
.byte 0b10010010 /* P(1) DPL(00) S(1) 0 E(0) W(1) A(0) */
.byte 0b11001111 /* G(1) B(1) 0 0 limit 19:16 */
.byte 0x0 /* base 31:24 */
// 0x18 code 64bit
.short 0xffff /* limit 15:00 */
.short 0x0000 /* base 15:00 */
.byte 0x00 /* base 23:16 */
.byte 0b10011010 /* P(1) DPL(00) S(1) 1 C(0) R(1) A(0) */
.byte 0b10101111 /* G(1) D(0) L(1) AVL(0) limit 19:16 */
.byte 0x0 /* base 31:24 */
.org 0x28 // 0x08 + 0x20
// load the above GDT
lgdt (LOAD_ADDRESS + 0x08)
// switch to protected mode
movl %cr0, %eax
orl $1, %eax
movl %eax, %cr0
// jump to 32bit mode
ljmpl $0x8, $(LOAD_ADDRESS + 0x40)
.org 0x40
.code32
.Lprot:
// we're now in 32bit mode, set up the 32bit data segment registers
mov $0x10, %ax
mov %ax, %ss
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
#if ARCH_X86_64
// set up 64bit paging
// set PAE bit in CR4
mov %cr4, %eax
or $(1<<5), %eax
mov %eax, %cr4
// Enable Long mode
movl $MSR_EFER ,%ecx
rdmsr
orl $EFER_LME,%eax
wrmsr
// load trampoline page table
movl (ARGS_CR3), %eax
mov %eax, %cr3
// enable paging, now we're in 32bit compatibility mode
mov %cr0, %eax
btsl $(31), %eax
mov %eax, %cr0
movl $(LOAD_ADDRESS + 0x800), %esp
// Use a far jump to get into 64bit mode
pushl $0x18
pushl $(LOAD_ADDRESS + 0x90)
lret
.org 0x90
.code64
farjump64:
/* branch to our high address */
movq (.Lhigh_addr), %rax
jmp *%rax
.Lhigh_addr:
.quad mp_boot_start_high
#else // ARCH_X86_32
// set up 32bit paging
// set PSE bit in CR4
mov %cr4, %eax
or $(1<<4), %eax
mov %eax, %cr4
// XXX load trampoline page table
// get into high address
// set up stack pointer
// call into C
cld
jmp .
#endif
DATA(mp_boot_end)
END_FUNCTION(mp_boot_start)
FUNCTION(mp_boot_start_high)
#if ARCH_X86_64
// set up stack pointer
mov $(ARGS_STACK), %rsp
// load the real GDT
lgdt _gdtr
push $CODE_64_SELECTOR
lea .Lnext(%rip), %rax
push %rax
lretq
.Lnext:
// zero out the segment registers
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
#else // ARCH_X86_32
#endif
// set up stack pointer
// call into C
cld
jmp .
END_FUNCTION(mp_boot_start_high)

View File

@@ -10,16 +10,52 @@
#include <lk/main.h>
#include <lib/acpi_lite.h>
#include <string.h>
#include <lk/trace.h>
#include <kernel/vm.h>
#if WITH_SMP
#define TRAMPOLINE_ADDRESS 0x4000
#define LOCAL_TRACE 1
static void start_cpu(uint cpu_num, uint32_t apic_id) {
extern void mp_boot_start(void);
extern void mp_boot_end(void);
struct bootstrap_args {
uintptr_t trampoline_cr3;
};
static void start_cpu(uint cpu_num, uint32_t apic_id, struct bootstrap_args *args) {
LTRACEF("cpu_num %u, apic_id %u\n", cpu_num, apic_id);
// XXX do work here
arch_disable_ints();
// start x86 secondary cpu
// send INIT IPI
lapic_send_init_ipi(apic_id, true);
thread_sleep(10);
// deassert INIT
lapic_send_init_ipi(apic_id, false);
thread_sleep(10);
lapic_send_startup_ipi(apic_id, TRAMPOLINE_ADDRESS);
// wait 200us
thread_sleep(1);
// send SIPI again
lapic_send_startup_ipi(apic_id, TRAMPOLINE_ADDRESS);
// wait 10ms
thread_sleep(10);
for (;;);
}
struct detected_cpus {
@@ -51,16 +87,48 @@ void platform_start_secondary_cpus(void) {
// TODO: deal with cpu topology
// start up the secondary cpus
if (cpus.num_detected > 1) {
dprintf(INFO, "PC: detected %u cpus\n", cpus.num_detected);
lk_init_secondary_cpus(cpus.num_detected - 1);
for (uint i = 1; i < cpus.num_detected; i++) {
dprintf(INFO, "PC: starting cpu %u\n", cpus.apic_ids[i]);
start_cpu(i, cpus.apic_ids[i]);
}
if (cpus.num_detected < 2) {
dprintf(INFO, "PC: no secondary cpus detected\n");
return;
}
// create a new aspace to build an identity map in
vmm_aspace_t *aspace;
status_t err = vmm_create_aspace(&aspace, "identity map", 0);
if (err < 0) {
panic("failed to create identity map aspace\n");
}
// set up an identity map for the trampoline code
void *ptr = (void *)TRAMPOLINE_ADDRESS;
err = vmm_alloc_physical(aspace, "trampoline", 0x10000, &ptr, 0,
TRAMPOLINE_ADDRESS, VMM_FLAG_VALLOC_SPECIFIC, ARCH_MMU_FLAG_CACHED);
if (err < 0) {
panic("failed to allocate trampoline memory\n");
}
vmm_aspace_t *old_aspace = vmm_set_active_aspace(aspace);
// set up bootstrap code page at TRAMPOLINE_ADDRESS for secondary cpu
memcpy(ptr, mp_boot_start, mp_boot_end - mp_boot_start);
// next page has args in it
struct bootstrap_args *args = (struct bootstrap_args *)((uintptr_t)ptr + 0x1000);
args->trampoline_cr3 = aspace->arch_aspace.cr3_phys;
dprintf(INFO, "PC: detected %u cpus\n", cpus.num_detected);
lk_init_secondary_cpus(cpus.num_detected - 1);
for (uint i = 1; i < cpus.num_detected; i++) {
dprintf(INFO, "PC: starting cpu %u\n", cpus.apic_ids[i]);
start_cpu(i, cpus.apic_ids[i], args);
}
// XXX restore old aspace
vmm_set_active_aspace(old_aspace);
// XXX free aspace when done
}
#endif // WITH_SMP

View File

@@ -25,6 +25,9 @@ void pic_mask_interrupts(void);
// local apic
void lapic_init(void);
void lapic_eoi(unsigned int vector);
void lapic_send_init_ipi(uint32_t apic_id, bool level);
void lapic_send_startup_ipi(uint32_t apic_id, uint32_t startup_vector);
void lapic_send_ipi(uint32_t apic_id, uint32_t vector);
// secondary cpus
void platform_start_secondary_cpus(void);

View File

@@ -24,6 +24,7 @@ MODULE_SRCS += \
$(LOCAL_DIR)/keyboard.c \
$(LOCAL_DIR)/lapic.c \
$(LOCAL_DIR)/mp.c \
$(LOCAL_DIR)/mp-boot.S \
$(LOCAL_DIR)/pic.c \
$(LOCAL_DIR)/platform.c \
$(LOCAL_DIR)/timer.c \