Initial SMP implementation

This commit is contained in:
mintsuki 2020-09-18 14:39:29 +02:00
parent fc51b7e062
commit 41c68e5e43
16 changed files with 632 additions and 46 deletions

View File

@ -42,7 +42,7 @@ echfs-test: limine-install test.img
echfs-utils -m -p0 test.img import test/test.elf boot/test.elf
echfs-utils -m -p0 test.img import test/limine.cfg limine.cfg
./limine-install limine.bin test.img
qemu-system-x86_64 -hda test.img -debugcon stdio -enable-kvm
qemu-system-x86_64 -net none -smp 4 -hda test.img -debugcon stdio -enable-kvm
ext2-test: limine-install test.img
$(MAKE) -C test

View File

@ -193,6 +193,20 @@ Identifier: `0x932f477032007e8f`
This tag does not have extra members.
#### SMP header tag
The presence of this tag enables support for booting up application processors.
```c
struct stivale2_header_tag_smp {
uint64_t identifier; // Identifier: 0x1ab015085f3273df
uint64_t next;
uint64_t flags; // Flags:
// bit 0: 0 = use xAPIC, 1 = use x2APIC
// All other flags are undefined.
} __attribute__((packed));
```
## stivale2 structure
The stivale2 structure returned by the bootloader looks like this:
@ -355,3 +369,38 @@ struct stivale2_struct_tag_firmware {
uint64_t flags; // Bit 0: 0 = UEFI, 1 = BIOS
} __attribute__((packed));
```
#### SMP structure tag
This tag reports to the kernel info about the firmware.
```c
struct stivale2_struct_tag_smp {
uint64_t identifier; // Identifier: 0x34d1d96339647025
uint64_t next;
uint64_t cpu_count; // Total number of logical CPUs (including BSP)
struct stivale2_smp_info smp_info[];
} __attribute__((packed));
```
```c
struct stivale2_smp_info {
uint32_t processor_id; // Processor ID as specified by MADT
uint32_t lapic_id; // LAPIC ID as specified by MADT
uint64_t target_stack; // The stack that will be loaded in ESP/RSP
// once the goto_address field is loaded.
// This MUST point to a valid stack of at least
// 256 bytes in size, and 16-byte aligned.
uint64_t goto_address; // This address is polled by the started APs
// until the kernel on the BSP performs an
// atomic write to this field.
// When that happens, bootloader code will
// load up ESP/RSP with the stack value as
// specified in target_stack.
// It will then proceed to load a pointer to
// this very structure into either register
// RDI for 64-bit or on the stack for 32-bit,
// then, goto_address is called and execution is
// handed off.
} __attribute__((packed));
```

Binary file not shown.

23
stage2/drivers/lapic.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef __DRIVERS__APIC_H__
#define __DRIVERS__APIC_H__
#include <stdint.h>
#include <stddef.h>
#include <lib/cio.h>
#define LAPIC_REG_ICR0 0x300
#define LAPIC_REG_ICR1 0x310
#define LAPIC_REG_SPURIOUS 0x0f0
#define LAPIC_REG_EOI 0x0b0
static inline uint32_t lapic_read(uint32_t reg) {
size_t lapic_mmio_base = (size_t)(rdmsr(0x1b) & 0xfffff000);
return mmind((void *)(lapic_mmio_base + reg));
}
static inline void lapic_write(uint32_t reg, uint32_t data) {
size_t lapic_mmio_base = (size_t)(rdmsr(0x1b) & 0xfffff000);
mmoutd((void *)(lapic_mmio_base + reg), data);
}
#endif

View File

@ -59,7 +59,7 @@ void *acpi_get_table(const char *signature, int index) {
if (!memcmp(ptr->signature, signature, 4)
&& !acpi_checksum(ptr, ptr->length)
&& cnt++ == index) {
print("acpi: Found \"%s\" at %X\n", signature, ptr);
print("acpi: Found \"%s\" at %x\n", signature, ptr);
return ptr;
}
}

View File

@ -11,7 +11,7 @@ int cpuid(uint32_t leaf, uint32_t subleaf,
__attribute__((noreturn)) void panic(const char *fmt, ...);
void pit_sleep(uint64_t pit_ticks);
void pit_sleep(uint32_t pit_ticks);
int pit_sleep_and_quit_on_keypress(uint32_t pit_ticks);
void brewind(size_t count);

View File

@ -3,6 +3,13 @@
#include <stdint.h>
#define FLAT_PTR(PTR) (*((int(*)[])(PTR)))
#define BYTE_PTR(PTR) (*((uint8_t *)(PTR)))
#define WORD_PTR(PTR) (*((uint16_t *)(PTR)))
#define DWORD_PTR(PTR) (*((uint32_t *)(PTR)))
#define QWORD_PTR(PTR) (*((uint64_t *)(PTR)))
static inline void port_out_b(uint16_t port, uint8_t value) {
asm volatile ("out dx, al" : : "a" (value), "d" (port) : "memory");
}
@ -33,4 +40,134 @@ static inline uint32_t port_in_d(uint16_t port) {
return value;
}
static inline void mmoutb(void *addr, uint8_t value) {
asm volatile (
"mov %0, %1\n\t"
: "=m"(BYTE_PTR(addr))
: "r"(value)
: "memory"
);
}
static inline void mmoutw(void *addr, uint16_t value) {
asm volatile (
"mov %0, %1\n\t"
: "=m"(WORD_PTR(addr))
: "r"(value)
: "memory"
);
}
static inline void mmoutd(void *addr, uint32_t value) {
asm volatile (
"mov %0, %1\n\t"
: "=m"(DWORD_PTR(addr))
: "r"(value)
: "memory"
);
}
static inline void mmoutq(void *addr, uint64_t value) {
asm volatile (
"mov %0, %1\n\t"
: "=m"(QWORD_PTR(addr))
: "r"(value)
: "memory"
);
}
static inline uint8_t mminb(void *addr) {
uint8_t ret;
asm volatile (
"mov %0, %1\n\t"
: "=r"(ret)
: "m"(BYTE_PTR(addr))
: "memory"
);
return ret;
}
static inline uint16_t mminw(void *addr) {
uint16_t ret;
asm volatile (
"mov %0, %1\n\t"
: "=r"(ret)
: "m"(WORD_PTR(addr))
: "memory"
);
return ret;
}
static inline uint32_t mmind(void *addr) {
uint32_t ret;
asm volatile (
"mov %0, %1\n\t"
: "=r"(ret)
: "m"(DWORD_PTR(addr))
: "memory"
);
return ret;
}
static inline uint64_t mminq(void *addr) {
uint64_t ret;
asm volatile (
"mov %0, %1\n\t"
: "=r"(ret)
: "m"(QWORD_PTR(addr))
: "memory"
);
return ret;
}
static inline uint64_t rdmsr(uint32_t msr) {
uint32_t edx, eax;
asm volatile ("rdmsr"
: "=a" (eax), "=d" (edx)
: "c" (msr)
: "memory");
return ((uint64_t)edx << 32) | eax;
}
static inline void wrmsr(uint32_t msr, uint64_t value) {
uint32_t edx = value >> 32;
uint32_t eax = (uint32_t)value;
asm volatile ("wrmsr"
:
: "a" (eax), "d" (edx), "c" (msr)
: "memory");
}
#define write_cr(reg, val) ({ \
asm volatile ("mov cr" reg ", %0" : : "r" (val)); \
})
#define read_cr(reg) ({ \
size_t cr; \
asm volatile ("mov %0, cr" reg : "=r" (cr)); \
cr; \
})
#define locked_read(var) ({ \
typeof(*var) ret = 0; \
asm volatile ( \
"lock xadd %1, %0;" \
: "+r" (ret) \
: "m" (*(var)) \
: "memory", "cc" \
); \
ret; \
})
#define locked_write(var, val) ({ \
typeof(*var) ret = val; \
asm volatile ( \
"lock xchg %1, %0;" \
: "+r" ((ret)) \
: "m" (*(var)) \
: "memory" \
); \
ret; \
})
#endif

View File

@ -9,6 +9,83 @@ int_08_isr:
iret
bits 32
global pit_sleep
pit_sleep:
; Hook int 0x08
mov edx, dword [0x08*4]
mov dword [0x80*4], edx
mov edx, int_08_isr
mov dword [0x08*4], int_08_isr
; pit_ticks in edx
mov edx, dword [esp+4]
mov dword [int_08_ticks_counter], 0
; Save non-scratch GPRs
push ebx
push esi
push edi
push ebp
; Jump to real mode
jmp 0x08:.bits16
.bits16:
bits 16
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 0xfe
mov cr0, eax
jmp 0x00:.cszero
.cszero:
xor ax, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
sti
.loop:
cmp dword [int_08_ticks_counter], edx
je .done
jmp .loop
.done:
cli
; Jump back to pmode
mov eax, cr0
or al, 1
mov cr0, eax
jmp 0x18:.bits32
.bits32:
bits 32
mov ax, 0x20
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Restore non-scratch GPRs
pop ebp
pop edi
pop esi
pop ebx
; Dehook int 0x08
mov edx, dword [0x80*4]
mov dword [0x08*4], edx
ret
global pit_sleep_and_quit_on_keypress
pit_sleep_and_quit_on_keypress:
; Hook int 0x08

127
stage2/lib/smp.c Normal file
View File

@ -0,0 +1,127 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <lib/acpi.h>
#include <lib/cio.h>
#include <lib/blib.h>
#include <lib/smp.h>
#include <drivers/lapic.h>
#include <mm/vmm64.h>
struct madt {
struct sdt;
uint32_t local_controller_addr;
uint32_t flags;
char madt_entries_begin[];
} __attribute__((packed));
struct madt_header {
uint8_t type;
uint8_t length;
} __attribute__((packed));
struct madt_lapic {
struct madt_header;
uint8_t processor_id;
uint8_t lapic_id;
uint32_t flags;
} __attribute__((packed));
struct gdtr {
uint16_t limit;
uint32_t ptr;
} __attribute__((packed));
void smp_trampoline(void);
extern struct gdtr smp_tpl_gdt;
struct smp_information *smp_tpl_info_struct;
uint8_t smp_tpl_booted_flag;
uint32_t smp_tpl_pagemap;
uint8_t smp_tpl_target_mode;
static bool smp_start_ap(uint8_t lapic_id, struct gdtr *gdtr,
struct smp_information *info_struct,
uint8_t target_mode, uint32_t pagemap) {
// Prepare the trampoline
smp_tpl_info_struct = info_struct;
smp_tpl_booted_flag = 0;
smp_tpl_pagemap = pagemap;
smp_tpl_target_mode = target_mode;
smp_tpl_gdt = *gdtr;
// Send the INIT IPI
lapic_write(LAPIC_REG_ICR1, lapic_id << 24);
lapic_write(LAPIC_REG_ICR0, 0x500);
pit_sleep(1);
// Send the Startup IPI
lapic_write(LAPIC_REG_ICR1, lapic_id << 24);
lapic_write(LAPIC_REG_ICR0, ((size_t)smp_trampoline / 4096) | 0x600);
for (int i = 0; i < 20; i++) {
pit_sleep(1);
if (locked_read(&smp_tpl_booted_flag) == 1) {
return true;
}
}
return false;
}
struct smp_information *init_smp(size_t *cpu_count,
bool longmode,
pagemap_t pagemap,
bool x2apic) {
// Search for MADT table
struct madt *madt = acpi_get_table("APIC", 0);
if (madt == NULL)
return NULL;
struct gdtr gdtr;
asm volatile ("sgdt %0" :: "m"(gdtr));
struct smp_information *ret = balloc(0);
*cpu_count = 1;
// Parse the MADT entries
for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin;
(uintptr_t)madt_ptr < (uintptr_t)madt + madt->length;
madt_ptr += *(madt_ptr + 1)) {
switch (*madt_ptr) {
case 0: {
// Processor local xAPIC
if (x2apic)
continue;
struct madt_lapic *lapic = (void *)madt_ptr;
// Do not try to restart the BSP
if (lapic->lapic_id == 0)
continue;
// Check if we can actually try to start the AP
if (!((lapic->flags & 1) ^ ((lapic->flags >> 1) & 1)))
continue;
print("smp: Found candidate AP for bring-up. LAPIC ID: %u\n", lapic->lapic_id);
struct smp_information *info_struct =
balloc_aligned(sizeof(struct smp_information), 1);
// Try to start the AP
if (!smp_start_ap(lapic->lapic_id, &gdtr, info_struct,
longmode ? 1 : 0, (uint32_t)pagemap.top_level)) {
print("smp: FAILED to bring-up AP\n");
brewind(sizeof(struct smp_information));
continue;
}
(*cpu_count)++;
break;
}
}
}
return ret;
}

20
stage2/lib/smp.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __LIB__SMP_H__
#define __LIB__SMP_H__
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <mm/vmm64.h>
struct smp_information {
uint32_t processor_id;
uint32_t lapic_id;
uint64_t goto_address;
} __attribute__((packed));
struct smp_information *init_smp(size_t *cpu_count,
bool longmode,
pagemap_t pagemap,
bool x2apic);
#endif

View File

@ -0,0 +1,92 @@
extern smp_tpl_info_struct
extern smp_tpl_booted_flag
extern smp_tpl_pagemap
extern smp_tpl_target_mode
section .realmode
global smp_trampoline
align 0x1000
bits 16
smp_trampoline:
cli
cld
xor ax, ax
mov ds, ax
lgdt [smp_tpl_gdt]
mov eax, cr0
bts eax, 0
mov cr0, eax
jmp 0x18:.mode32
bits 32
.mode32:
mov ax, 0x20
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
btr eax, 29
btr eax, 30
mov cr0, eax
cmp dword [smp_tpl_target_mode], 0
je parking32
mov eax, dword [smp_tpl_pagemap]
mov cr3, eax
mov eax, cr4
bts eax, 5
mov cr4, eax
mov ecx, 0xc0000080
rdmsr
bts eax, 8
wrmsr
mov eax, cr0
bts eax, 31
mov cr0, eax
jmp 0x28:.mode64
bits 64
.mode64:
mov ax, 0x30
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp parking64
global smp_tpl_gdt
align 16
smp_tpl_gdt:
dw 0
dd 0
section .text
bits 32
parking32:
mov ecx, dword [smp_tpl_info_struct]
mov eax, 1
lock xchg dword [smp_tpl_booted_flag], eax
mov eax, 0xcafebabe
jmp $
bits 64
parking64:
mov ecx, dword [smp_tpl_info_struct]
mov eax, 1
lock xchg dword [smp_tpl_booted_flag], eax
mov eax, 0xdeadbeef
jmp $

View File

@ -210,14 +210,52 @@ void stivale_load(char *cmdline, int boot_drive) {
stivale_struct.memory_map_entries = (uint64_t)memmap_entries;
stivale_struct.memory_map_addr = (uint64_t)(size_t)memmap;
stivale_spinup(bits, level5pg && (stivale_hdr.flags & (1 << 1)),
entry_point, &stivale_struct, stivale_hdr.stack,
memmap, memmap_entries);
bool want_5lv = level5pg && (stivale_hdr.flags & (1 << 1));
pagemap_t pagemap = stivale_build_pagemap(want_5lv, memmap, memmap_entries);
stivale_spinup(bits, want_5lv, pagemap,
entry_point, &stivale_struct, stivale_hdr.stack);
}
__attribute__((noreturn)) void stivale_spinup(int bits, bool level5pg,
uint64_t entry_point, void *stivale_struct, uint64_t stack,
struct e820_entry_t *memmap, size_t memmap_entries) {
pagemap_t stivale_build_pagemap(bool level5pg, struct e820_entry_t *memmap,
size_t memmap_entries) {
pagemap_t pagemap = new_pagemap(level5pg ? 5 : 4);
uint64_t higher_half_base = level5pg ? 0xff00000000000000 : 0xffff800000000000;
// Map 0 to 2GiB at 0xffffffff80000000
for (uint64_t i = 0; i < 0x80000000; i += PAGE_SIZE) {
map_page(pagemap, 0xffffffff80000000 + i, i, 0x03);
}
// Map 0 to 4GiB at higher half base and 0
for (uint64_t i = 0; i < 0x100000000; i += PAGE_SIZE) {
map_page(pagemap, i, i, 0x03);
map_page(pagemap, higher_half_base + i, i, 0x03);
}
// Map any other region of memory from the memmap
for (size_t i = 0; i < memmap_entries; i++) {
uint64_t base = memmap[i].base;
uint64_t length = memmap[i].length;
uint64_t top = base + length;
uint64_t aligned_base = ALIGN_DOWN(base, PAGE_SIZE);
uint64_t aligned_top = ALIGN_UP(top, PAGE_SIZE);
uint64_t aligned_length = aligned_top - aligned_base;
for (uint64_t i = 0; i < aligned_length; i += PAGE_SIZE) {
uint64_t page = aligned_base + i;
map_page(pagemap, page, page, 0x03);
map_page(pagemap, higher_half_base + page, page, 0x03);
}
}
return pagemap;
}
__attribute__((noreturn)) void stivale_spinup(
int bits, bool level5pg, pagemap_t pagemap,
uint64_t entry_point, void *stivale_struct, uint64_t stack) {
if (bits == 64) {
// If we're going 64, we might as well call this BIOS interrupt
// to tell the BIOS that we are entering Long Mode, since it is in
@ -241,37 +279,6 @@ __attribute__((noreturn)) void stivale_spinup(int bits, bool level5pg,
);
}
pagemap_t pagemap = new_pagemap(level5pg ? 5 : 4);
uint64_t higher_half_base = level5pg ? 0xff00000000000000 : 0xffff800000000000;
// Map 0 to 2GiB at 0xffffffff80000000
for (uint64_t i = 0; i < 0x80000000; i += PAGE_SIZE) {
map_page(pagemap, 0xffffffff80000000 + i, i, 0x03);
}
// Map 0 to 4GiB at higher half base and 0
for (uint64_t i = 0; i < 0x100000000; i += PAGE_SIZE) {
map_page(pagemap, i, i, 0x03);
map_page(pagemap, higher_half_base + i, i, 0x03);
}
// Map any other region of memory from the memmap
for (size_t i = 0; i < memmap_entries; i++) {
uint64_t base = memmap[i].base;
uint64_t length = memmap[i].length;
uint64_t top = base + length;
uint64_t aligned_base = ALIGN_DOWN(base, PAGE_SIZE);
uint64_t aligned_top = ALIGN_UP(top, PAGE_SIZE);
uint64_t aligned_length = aligned_top - aligned_base;
for (uint64_t i = 0; i < aligned_length; i += PAGE_SIZE) {
uint64_t page = aligned_base + i;
map_page(pagemap, page, page, 0x03);
map_page(pagemap, higher_half_base + page, page, 0x03);
}
}
asm volatile (
"cli\n\t"
"cld\n\t"

View File

@ -4,10 +4,15 @@
#include <stdbool.h>
#include <stdint.h>
#include <lib/memmap.h>
#include <lib/e820.h>
#include <mm/vmm64.h>
void stivale_load(char *cmdline, int boot_drive);
__attribute__((noreturn)) void stivale_spinup(int bits, bool level5pg,
uint64_t entry_point, void *stivale_struct, uint64_t stack,
struct e820_entry_t *memmap, size_t memmap_entries);
pagemap_t stivale_build_pagemap(bool level5pg, struct e820_entry_t *memmap,
size_t memmap_entries);
__attribute__((noreturn)) void stivale_spinup(
int bits, bool level5pg, pagemap_t pagemap,
uint64_t entry_point, void *stivale_struct, uint64_t stack);
#endif

View File

@ -14,6 +14,7 @@
#include <lib/rand.h>
#include <lib/real.h>
#include <lib/libc.h>
#include <lib/smp.h>
#include <drivers/vbe.h>
#include <lib/term.h>
#include <drivers/pic.h>
@ -313,7 +314,26 @@ void stivale2_load(char *cmdline, int boot_drive) {
// Check if 5-level paging tag is requesting support
bool level5pg_requested = get_tag(&stivale2_hdr, STIVALE2_HEADER_TAG_5LV_PAGING_ID) ? true : false;
stivale_spinup(bits, level5pg && level5pg_requested,
entry_point, &stivale2_struct, stivale2_hdr.stack,
memmap, memmap_entries);
pagemap_t pagemap = {0};
if (bits == 64)
pagemap = stivale_build_pagemap(level5pg && level5pg_requested,
memmap, memmap_entries);
//////////////////////////////////////////////
// Create SMP struct tag
//////////////////////////////////////////////
{
struct stivale2_header_tag_smp *smp_hdr_tag = get_tag(&stivale2_hdr, STIVALE2_HEADER_TAG_SMP_ID);
if (smp_hdr_tag != NULL) {
struct stivale2_struct_tag_smp *tag = balloc(sizeof(struct stivale2_struct_tag_smp));
init_smp((size_t*)&tag->cpu_count, bits == 64, pagemap,
smp_hdr_tag->flags & 1);
append_tag(&stivale2_struct, (struct stivale2_tag *)tag);
}
}
stivale_spinup(bits, level5pg && level5pg_requested, pagemap,
entry_point, &stivale2_struct, stivale2_hdr.stack);
}

View File

@ -27,6 +27,13 @@ struct stivale2_header_tag_framebuffer {
uint16_t framebuffer_bpp;
} __attribute__((packed));
#define STIVALE2_HEADER_TAG_SMP_ID 0x1ab015085f3273df
struct stivale2_header_tag_smp {
struct stivale2_tag tag;
uint64_t flags;
} __attribute__((packed));
#define STIVALE2_HEADER_TAG_5LV_PAGING_ID 0x932f477032007e8f
/* --- Struct --------------------------------------------------------------- */
@ -112,4 +119,21 @@ struct stivale2_struct_tag_firmware {
uint64_t flags;
} __attribute__((packed));
#define STIVALE2_STRUCT_TAG_SMP_ID 0x34d1d96339647025
struct stivale2_smp_info {
uint32_t processor_id;
uint32_t lapic_id;
uint64_t target_stack;
uint64_t goto_address;
} __attribute__((packed));
struct stivale2_struct_tag_smp {
struct stivale2_tag tag;
uint64_t identifier;
uint64_t next;
uint64_t cpu_count;
struct stivale2_smp_info smp_info[];
} __attribute__((packed));
#endif

View File

@ -13,6 +13,11 @@ section .rodata
lv5:
dq 0x932f477032007e8f
dq smp
smp:
dq 0x1ab015085f3273df
dq 0
dq 0
section .bss