From 5b47048397c7b87fc475e1334861fdd14491596c Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Wed, 22 Mar 2017 16:05:26 +0000 Subject: [PATCH] Implement rt0 assembly boot code for x86 arch --- arch/x86/asm/gdt.inc | 31 +++++++++ arch/x86/asm/multiboot_header.s | 36 +++++++++++ arch/x86/asm/rt0.s | 109 ++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 arch/x86/asm/gdt.inc create mode 100644 arch/x86/asm/multiboot_header.s create mode 100644 arch/x86/asm/rt0.s diff --git a/arch/x86/asm/gdt.inc b/arch/x86/asm/gdt.inc new file mode 100644 index 0000000..ac510bc --- /dev/null +++ b/arch/x86/asm/gdt.inc @@ -0,0 +1,31 @@ +; vim: set ft=nasm : + +%define SEG_NOEXEC (0 << 3) +%define SEG_EXEC (1 << 3) + +%define SEG_NORW (0 << 1) +%define SEG_R (1 << 1) +%define SEG_W (1 << 1) + +%define SEG_GRAN_BYTE (0 << 7) +%define SEG_GRAN_4K_PAGE (1 << 7) + +;------------------------------------------------------------------------------ +; GDT_ENTRY_32 creates a GDT entry for a 32-bit descriptor. It automatically sets +; the following bits: +; - Privl (ring) bits to 00 (ring 0) +; - Pr (present) bit to 1 +; - Sz (size) bit to 1 (32-bit selector) +; - L (long-mode) bit to 0 +; +; Args: base, limit, access, flags +;------------------------------------------------------------------------------ +%macro GDT_ENTRY_32 4 + dw (%2 & 0xFFFF) ; limit 0:15 + dw (%1 & 0xFFFF) ; base 0:15 + db ((%1 >> 16) & 0xFF) ; base 16:23 + db (0x90 | %3) ; set Pr = 1, bit 5 = 1 (required) + ; and apply access byte flags + db 0x40 | (%4 & 0xC0) | ((%2 >> 16) & 0xF) ; set Sz and flags and limit bits 16:19 + db ((%1 >> 24) & 0xFF) ; base 24:31 +%endmacro diff --git a/arch/x86/asm/multiboot_header.s b/arch/x86/asm/multiboot_header.s new file mode 100644 index 0000000..6401802 --- /dev/null +++ b/arch/x86/asm/multiboot_header.s @@ -0,0 +1,36 @@ +; vim: set ft=nasm : + +section .multiboot_header + +MAGIC equ 0xe85250d6 +ARCH equ 0x0 + +; Define the multiboot header (multiboot 1.6) +; http://nongnu.askapache.com/grub/phcoder/multiboot.pdf +header_start: + dd MAGIC ; magic number + dd ARCH ; i386 protected mode + dd header_end - header_start ; header length + + ; The field ‘checksum’ is a 32-bit unsigned value which, when added to the other + ; magic fields (i.e. ‘magic’, ‘architecture’ and ‘header_length’), must have a + ; 32-bit unsigned sum of zero. + dd (1 << 32) - (MAGIC + ARCH + (header_end - header_start)) + + align 8 ; tags should be 64-bit aligned + + ; Define graphics mode tag + ;dw 5 ; type + ;dw 0 ; flags + ;dd 20 ; size + ;dd 80 ; width (pixels or chars) + ;dd 25 ; height (pixels or chars) + ;dd 0 ; bpp (0 for text mode + + align 8 ; tags should be 64-bit aligned + + ; According to page 6 of the spec, the tag list is terminated by a tag with + ; type 0 and size 8 + dd 0 ; type & flag = 0 + dd 8 ; size +header_end: diff --git a/arch/x86/asm/rt0.s b/arch/x86/asm/rt0.s new file mode 100644 index 0000000..95ea421 --- /dev/null +++ b/arch/x86/asm/rt0.s @@ -0,0 +1,109 @@ +; vim: set ft=nasm : + +section .bss +align 4 + +; Reserve 16K for our stack. Stacks should be aligned to 16 byte boundaries. +stack_bottom: + resb 16384 ; 16 KiB +stack_top: + +; Reserve some extra space for our tls_0 block; GO functions expect the +; GS segment register to point to the current TLS so we need to initialize this +; first before invoking any go functions +tls0: +g0_ptr: resd 1 ; gs:0x00 is a pointer to the current g struct + ; in our case it should point to g0 +g0: +g0_stack_lo: resd 1 +g0_stack_hi: resd 1 +g0_stackguard0: resd 1 ; sp compared to this value in go stack growth prologue +g0_stackguard1: resd 1 ; sp compared to this value in C stack growth prologue + +section .text +bits 32 +align 4 + +;------------------------------------------------------------------------------ +; Kernel arch-specific entry point +; +; The boot loader will jump to this symbol after setting up the CPU according +; to the multiboot standard. At this point: +; - The CPU is using 32-bit protected mode +; - Interrupts are disabled +; - Paging is disabled +;------------------------------------------------------------------------------ +global _rt0_entry +_rt0_entry: + ; Initalize our stack by pointing ESP to the BSS-allocated stack. In x86, + ; stack grows downwards so we need to point ESP to stack_top + mov esp, stack_top + + ; Load initial GDT + call _rt0_load_gdt + + ; init g0 so we can invoke Go functions + mov dword [gs:0x00], g0 + mov dword [g0_stack_hi], stack_top + mov dword [g0_stack_lo], stack_bottom + mov dword [g0_stackguard0], stack_bottom + + extern main.main + call main.main + + ; Main should never return; halt the CPU + cli + hlt +.end: + +;------------------------------------------------------------------------------ +; Load GDT and flush CPU caches +;------------------------------------------------------------------------------ + +_rt0_load_gdt: + ; Go code uses the GS register to access the TLS. Set the base address + ; for the GS descriptor to point to our tls0 table + mov eax, tls0 + mov ebx, gdt0_gs_seg + mov [ebx+2], al + mov [ebx+3], ah + shr eax, 16 + mov [ebx+4], al + + lgdt [gdt0_desc] + + ; GDT has been loaded but the CPU still has the previous GDT data in cache. + ; We need to manually update the descriptors and use a JMP command to set + ; the CS segment descriptor + jmp CS_SEG:update_descriptors +update_descriptors: + mov ax, DS_SEG + mov ds, ax + mov es, ax + mov fs, ax + mov ax, GS_SEG + mov gs, ax + + ret + +;------------------------------------------------------------------------------ +; GDT definition +;------------------------------------------------------------------------------ +%include "gdt.inc" + +align 2 +gdt0: + +gdt0_nil_seg: GDT_ENTRY_32 0x00, 0x0, 0x0, 0x0 ; nil descriptor (not used by CPU but required by some emulators) +gdt0_cs_seg: GDT_ENTRY_32 0x00, 0xFFFFF, SEG_EXEC | SEG_R, SEG_GRAN_4K_PAGE ; code descriptor +gdt0_ds_seg: GDT_ENTRY_32 0x00, 0xFFFFF, SEG_NOEXEC | SEG_W, SEG_GRAN_4K_PAGE ; data descriptor +gdt0_gs_seg: GDT_ENTRY_32 0x00, 0x40, SEG_NOEXEC | SEG_W, SEG_GRAN_BYTE ; TLS descriptor (required in order to use go segmented stacks) + +gdt0_desc: + dw gdt0_desc - gdt0 - 1 ; gdt size should be 1 byte less than actual length + dd gdt0 + +NULL_SEG equ gdt0_nil_seg - gdt0 +CS_SEG equ gdt0_cs_seg - gdt0 +DS_SEG equ gdt0_ds_seg - gdt0 +GS_SEG equ gdt0_gs_seg - gdt0