/* * hal_boot.S - Kernel boot entry point * * Copyright (c) 2017 Maxime Villard * This code is inspired a lot from the NetBSD boot procedure, written by * Maxime Villard too. XXX copyright * * This file is part of ALMOS-MKH. * * ALMOS-MKH is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2.0 of the License. * * ALMOS-MKH is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ALMOS-MKH; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define x86_ASM #include #include #include #include #include #define MULTIBOOT_HEADER_MAGIC 0x1BADB002 #define MULTIBOOT_HEADER_FLAGS 0x00000000 #define MULTIBOOT_INFO_MAGIC 0x2BADB002 #define CHECKSUM -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) #if L2_SLOT_KERNBASE > 0 #define TABLE_L2_ENTRIES (2 * (NKL2_KIMG_ENTRIES + 1)) #else #define TABLE_L2_ENTRIES (NKL2_KIMG_ENTRIES + 1) #endif #if L3_SLOT_KERNBASE > 0 #define TABLE_L3_ENTRIES (2 * NKL3_KIMG_ENTRIES) #else #define TABLE_L3_ENTRIES NKL3_KIMG_ENTRIES #endif #define PROC0_PML4_OFF 0 #define PROC0_STK_OFF (PROC0_PML4_OFF + 1 * PAGE_SIZE) #define PROC0_PTP3_OFF (PROC0_STK_OFF + STKPAGES * PAGE_SIZE) #define PROC0_PTP2_OFF (PROC0_PTP3_OFF + NKL4_KIMG_ENTRIES * PAGE_SIZE) #define PROC0_PTP1_OFF (PROC0_PTP2_OFF + TABLE_L3_ENTRIES * PAGE_SIZE) #define TABLESIZE \ ((NKL4_KIMG_ENTRIES + TABLE_L3_ENTRIES + TABLE_L2_ENTRIES + 1 + STKPAGES) \ * PAGE_SIZE) /* * fillkpt - Fill in a kernel page table * eax = pte (page frame | control | status) * ebx = page table address * ecx = number of pages to map * * Each entry is 8 (PDE_SIZE) bytes long: we must set the 4 upper bytes to 0. */ #define fillkpt \ cmpl $0,%ecx ; /* zero-sized? */ \ je 2f ; \ 1: movl $0,(PDE_SIZE-4)(%ebx) ; /* upper 32 bits: 0 */ \ movl %eax,(%ebx) ; /* store phys addr */ \ addl $PDE_SIZE,%ebx ; /* next PTE/PDE */ \ addl $PAGE_SIZE,%eax ; /* next phys page */ \ loop 1b ; \ 2: ; /* * fillkpt_nox - Same as fillkpt, but sets the NX/XD bit. */ #define fillkpt_nox \ cmpl $0,%ecx ; /* zero-sized? */ \ je 2f ; \ 1: movl $PG_NX32,(PDE_SIZE-4)(%ebx); /* upper 32 bits: NX */ \ movl %eax,(%ebx) ; /* store phys addr */ \ addl $PDE_SIZE,%ebx ; /* next PTE/PDE */ \ addl $PAGE_SIZE,%eax ; /* next phys page */ \ loop 1b ; \ 2: ; /* * fillkpt_blank - Fill in a kernel page table with blank entries * ebx = page table address * ecx = number of pages to map */ #define fillkpt_blank \ cmpl $0,%ecx ; /* zero-sized? */ \ je 2f ; \ 1: movl $0,(PDE_SIZE-4)(%ebx) ; /* upper 32 bits: 0 */ \ movl $0,(%ebx) ; /* lower 32 bits: 0 */ \ addl $PDE_SIZE,%ebx ; /* next PTE/PDE */ \ loop 1b ; \ 2: ; /* * killkpt - Destroy a kernel page table (long mode) * rbx = page table address * rcx = number of pages to destroy */ #define killkpt \ 1: movq $0,(%rbx) ; \ addq $PDE_SIZE,%rbx ; \ loop 1b ; /* 32bit version of PG_NX */ #define PG_NX32 0x80000000 #define RELOC(x) ((x) - KERNBASE) .globl start_x86_64 .globl L4paddr .globl iom_base /* * The multiboot header */ .section .boot,"ax",@progbits multiboot_header: .align 4 .long MULTIBOOT_HEADER_MAGIC .long MULTIBOOT_HEADER_FLAGS .long CHECKSUM /* * The variables used in the boot procedure. */ .section .data .align 4 .type L4paddr, @object L4paddr: .quad 0 /* paddr of L4 */ .type iom_base, @object iom_base: .quad 0 /* virt. addr. of ISA I/O MEM */ #define GDT64_LIMIT gdt64_end-gdt64_start-1 /* Temporary gdt64, with base address in low memory */ .type gdt64_lo, @object gdt64_lo: .word GDT64_LIMIT .quad RELOC(gdt64_start) .align 64 /* Temporary gdt64, with base address in high memory */ .type gdt64_hi, @object gdt64_hi: .word GDT64_LIMIT .quad gdt64_start .align 64 #undef GDT64_LIMIT .type gdt64_start, @object gdt64_start: .quad 0x0000000000000000 /* always empty */ .quad 0x00af9a000000ffff /* kernel CS */ .quad 0x00cf92000000ffff /* kernel DS */ gdt64_end: .type farjmp64, @object farjmp64: .long RELOC(longmode) .word GDT_FIXED_SEL(GDT_KCODE_SEL, SEL_KPL) .align 64 /* Space for the temporary stack */ .size tmpstk, tmpstk - . .space 512 tmpstk: .text start_x86_64: .code32 /* Warm boot */ movw $0x1234,0x472 cld /* Make sure it is a multiboot-compliant bootloader. */ cmpl $MULTIBOOT_INFO_MAGIC,%eax je 1f ret /* what to do? */ 1: movl $RELOC(tmpstk),%esp /* Reset the PSL. */ pushl $PSL_MBO popfl /* * Copy the various multiboot structures */ movl %ebx,%esi /* src */ movl $RELOC(mb_info),%edi /* dst */ movl $MULTIBOOT_INFO_SIZE,%ecx /* len */ rep movsb /* copy esi -> edi */ testl $MULTIBOOT_INFO_HAS_LOADER_NAME,MB_MI_FLAGS(%ebx) jz 1f movl MB_MI_LOADER_NAME(%ebx),%esi /* src */ movl $RELOC(mb_loader_name),%edi /* dst */ copy_loop: cmpb $0,(%esi) je copy_end movsb /* copy esi -> edi */ jmp copy_loop copy_end: movsb /* copy esi -> edi */ 1: testl $MULTIBOOT_INFO_HAS_MMAP,MB_MI_FLAGS(%ebx) jz 1f movl MB_MI_MMAP_ADDR(%ebx),%esi /* src */ movl $RELOC(mb_mmap),%edi /* dst */ movl MB_MI_MMAP_LENGTH(%ebx),%ecx /* len */ rep movsb /* copy esi -> edi */ 1: testl $MULTIBOOT_INFO_HAS_CMDLINE,MB_MI_FLAGS(%ebx) jz 1f movl MB_MI_CMDLINE(%ebx),%esi /* src */ movl $RELOC(mb_cmdline),%edi /* dst */ movl $PAGE_SIZE,%ecx /* len */ rep movsb /* copy esi -> edi */ 1: /* * There are four levels of pages in amd64: PML4 -> PDP -> PD -> PT. They will * be referred to as: L4 -> L3 -> L2 -> L1. * * Virtual address space of the kernel: * +---------------+------+-----------------------------------+-------------+ * | TEXT + RODATA | DATA | L4 -> PROC0 STK -> L3 -> L2 -> L1 | ISA I/O MEM | * +---------------+------+-----------------------------------+-------------+ * (1) (2) * * PROC0 STK is obviously not linked as a page level. It just happens to be * caught between L4 and L3. * * (PROC0 STK + L4 + L3 + L2 + L1) is later referred to as BOOTSTRAP TABLES. * * ISA I/O MEM has no physical page allocated here, just virtual addresses. * * Important note: the kernel segments are properly 4k-aligned * (see kernel_x86.ld), so there's no need to enforce alignment. */ /* Find end of kernel image; brings us on (1). */ movl $RELOC(__kernel_end),%edi /* Align up for BOOTSTRAP TABLES. */ movl %edi,%esi addl $PGOFSET,%esi andl $~PGOFSET,%esi /* We are on the BOOTSTRAP TABLES. Save L4's physical address. */ movl $RELOC(L4paddr),%ebp movl %esi,(%ebp) movl $0,4(%ebp) /* Now, zero out the BOOTSTRAP TABLES (before filling them in). */ movl %esi,%edi xorl %eax,%eax cld movl $TABLESIZE,%ecx shrl $2,%ecx rep stosl /* copy eax -> edi */ /* * Build the page tables and levels. We go from L1 to L4, and link the levels * together. Note: RELOC computes &addr - KERNBASE in 32 bits; the value can't * be > 4G, or we can't deal with it anyway, since we are in 32bit mode. */ /* * Build L1. */ leal (PROC0_PTP1_OFF)(%esi),%ebx /* Skip the area below the kernel text. */ movl $(KERNTEXTOFF_LO - KERNBASE_LO),%ecx shrl $PGSHIFT,%ecx fillkpt_blank /* Map the kernel code RX. */ movl $(KERNTEXTOFF_LO - KERNBASE_LO),%eax /* start of TEXT */ movl $RELOC(__kernel_data_start),%ecx subl %eax,%ecx shrl $PGSHIFT,%ecx orl $(PG_V|PG_KR|PG_G),%eax fillkpt /* Map the kernel data RW. */ movl $RELOC(__kernel_data_start),%eax movl $RELOC(__kernel_end),%ecx subl %eax,%ecx shrl $PGSHIFT,%ecx orl $(PG_V|PG_KW|PG_G),%eax fillkpt_nox /* Map the BOOTSTRAP TABLES RW. */ movl $RELOC(__kernel_end),%eax /* start of BOOTSTRAP TABLES */ movl $TABLESIZE,%ecx /* length of BOOTSTRAP TABLES */ shrl $PGSHIFT,%ecx orl $(PG_V|PG_KW|PG_G),%eax fillkpt_nox /* We are on (2). Map ISA I/O MEM RW. */ movl $IOM_BEGIN,%eax movl $IOM_SIZE,%ecx /* size of ISA I/O MEM */ shrl $PGSHIFT,%ecx orl $(PG_V|PG_KW|PG_G),%eax fillkpt_nox /* * Build L2. Linked to L1. */ leal (PROC0_PTP2_OFF)(%esi),%ebx leal (PROC0_PTP1_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $(NKL2_KIMG_ENTRIES+1),%ecx fillkpt #if L2_SLOT_KERNBASE > 0 /* If needed, set up level 2 entries for actual kernel mapping */ leal (PROC0_PTP2_OFF + L2_SLOT_KERNBASE * PDE_SIZE)(%esi),%ebx leal (PROC0_PTP1_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $(NKL2_KIMG_ENTRIES+1),%ecx fillkpt #endif /* * Build L3. Linked to L2. */ leal (PROC0_PTP3_OFF)(%esi),%ebx leal (PROC0_PTP2_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $NKL3_KIMG_ENTRIES,%ecx fillkpt #if L3_SLOT_KERNBASE > 0 /* If needed, set up level 3 entries for actual kernel mapping */ leal (PROC0_PTP3_OFF + L3_SLOT_KERNBASE * PDE_SIZE)(%esi),%ebx leal (PROC0_PTP2_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $NKL3_KIMG_ENTRIES,%ecx fillkpt #endif /* * Build L4 for identity mapping. Linked to L3. */ leal (PROC0_PML4_OFF)(%esi),%ebx leal (PROC0_PTP3_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $NKL4_KIMG_ENTRIES,%ecx fillkpt /* Set up L4 entries for actual kernel mapping */ leal (PROC0_PML4_OFF + L4_SLOT_KERNBASE * PDE_SIZE)(%esi),%ebx leal (PROC0_PTP3_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $NKL4_KIMG_ENTRIES,%ecx fillkpt /* Install recursive top level PDE (one entry) */ leal (PROC0_PML4_OFF + PDIR_SLOT_PTE * PDE_SIZE)(%esi),%ebx leal (PROC0_PML4_OFF)(%esi),%eax orl $(PG_V|PG_KW),%eax movl $1,%ecx fillkpt_nox /* * Startup checklist: * 1. Enable PAE (and SSE while here). */ movl %cr4,%eax orl $(CR4_PAE|CR4_OSFXSR|CR4_OSXMMEXCPT|CR4_PGE),%eax movl %eax,%cr4 /* * 2. Set Long Mode Enable in EFER. Also enable the syscall extensions, * and NOX. */ movl $MSR_EFER,%ecx rdmsr xorl %eax,%eax /* XXX */ orl $(EFER_LME|EFER_SCE|EFER_NXE),%eax wrmsr /* * 3. Load %cr3 with pointer to PML4. */ movl %esi,%eax movl %eax,%cr3 /* * 4. Enable paging and the rest of it. */ movl %cr0,%eax orl $(CR0_PE|CR0_PG|CR0_NE|CR0_TS|CR0_MP|CR0_WP|CR0_AM),%eax andl $~(CR0_CD|CR0_NW),%eax orl $(CR0_EM),%eax /* FPU not implemented yet */ movl %eax,%cr0 jmp compat compat: /* * 5. Not quite done yet, we're now in a compatibility segment, in * legacy mode. We must jump to a long mode segment. Need to set up * a temporary GDT with a long mode segment in it to do that. */ movl $RELOC(gdt64_lo),%eax lgdt (%eax) movl $RELOC(farjmp64),%eax ljmp *(%eax) .code64 longmode: /* * 6. Finally, we're in long mode. However, we're still in the identity * mapped area (could not jump out of that earlier because it would * have been a > 32bit jump). We can do that now, so here we go. */ movabsq $longmode_hi,%rax jmp *%rax longmode_hi: /* * We left the identity mapped area. Base address of * the temporary gdt64 should now be in high memory. */ movq $RELOC(gdt64_hi),%rax lgdt (%rax) /* * We have arrived. There's no need anymore for the identity mapping in * low memory, remove it. */ movq $KERNBASE,%r8 #if L2_SLOT_KERNBASE > 0 movq $(NKL2_KIMG_ENTRIES+1),%rcx leaq (PROC0_PTP2_OFF)(%rsi),%rbx /* old, phys address */ addq %r8,%rbx /* new, virt address */ killkpt #endif #if L3_SLOT_KERNBASE > 0 movq $NKL3_KIMG_ENTRIES,%rcx leaq (PROC0_PTP3_OFF)(%rsi),%rbx /* old, phys address */ addq %r8,%rbx /* new, virt address */ killkpt #endif movq $NKL4_KIMG_ENTRIES,%rcx leaq (PROC0_PML4_OFF)(%rsi),%rbx /* old, phys address of PML4 */ addq %r8,%rbx /* new, virt address of PML4 */ killkpt /* Save the virtual address of ISA I/O MEM. */ movq $(TABLESIZE+KERNBASE),%rdx addq %rsi,%rdx movq %rdx,iom_base(%rip) /* Set up bootstrap stack. */ leaq (PROC0_STK_OFF)(%rsi),%rax addq %r8,%rax leaq (STKSIZE)(%rax),%rsp xorq %rbp,%rbp /* mark end of frames */ xorw %ax,%ax movw %ax,%gs movw %ax,%fs /* The first physical page available. */ leaq (TABLESIZE)(%rsi),%rdi call init_x86_64