.set ALIGN, 1 << 0 .set MEMINFO, 1 << 1 .set VIDMODE, 1 << 2 .set FLAGS, ALIGN | MEMINFO .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) /* the amount of memory to allocate for bootstrap page directories. */ /* with buffer size 0x1000 (enough for exactly one page directory) /* we can map 1 GiB of virtual memory */ #define BOOTSTRAP_PDIR_SIZE 0x1000 .extern start_64 # defined in start_64.S .code32 .section .boot.rodata, "a", @progbits /******* ERROR MESSAGE STRINGS *******/ no_long_mode_err: .asciz "This system is not 64-bit. Press any key to reboot." /******* LONG MDOE BOOSTRAP GDT *******/ bootstrap_gdt: /* 0x00: null descriptor */ .long 0x0 .long 0x0 /* 0x08: kernel code segment */ .long 0x0000ffff .long 0x002f9a00 /* 0x10: kernel data segment */ .long 0x0000ffff .long 0x002f9200 bootstrap_gdt_ptr: .word 24 /* 8 bytes for each entry in bootstrap_gdt */ .long bootstrap_gdt /******* LONG MODE BOOTSTRAP PAGING STRUCTURES *******/ .section .boot.bss, "aw", @nobits bootstrap_pml4t: # a single PML4T .skip 0x1000 bootstrap_pdpt: # a single PDPT .skip 0x1000 bootstrap_pdir_buf: # a buffer of page directories. each pdir entry can be used to map 2MB of virtual memory. .skip BOOTSTRAP_PDIR_SIZE bootstrap_stack_bottom: .skip 0x4000 bootstrap_stack_top: /******* MULTIBOOT HEADER *******/ .section .boot.text, "ax", @progbits .align 4 .long MAGIC .long FLAGS .long CHECKSUM # Print an error message, wait for RETURN, and # reboot if the system's CPU doesn't support long mode. no_long_mode: # print error message mov $no_long_mode_err, %esi mov $0xb8000, %edi 1: movsb movb $12, (%edi) incl %edi cmpb $0, (%esi) jne 1b 2: inb $0x64, %al test $0x01, %al jz 3f inb $0x60, %al jmp 2b 3: inb $0x64, %al test $0x01, %al jz 3b int $0xff cli hlt .global init_page_tables .type init_page_tables, @function init_page_tables: /* STEP 1: add an entry in the PML4T pointing to the PDPT. NOTE that we actually add two entries in the PML4T that both point to the same PDPT. This allows us to map the kernel at both vaddr 0x0 AND vaddr 0xffffffff80000000 using the same PDPT */ mov $bootstrap_pml4t, %edi mov $bootstrap_pdpt, %esi # in %ESI, build a PML4T entry containing the base address of the PDPT and $0xFFFFF000, %esi or $0x3, %esi # add the PML4T entries at PML4T[0] and PML4T[511] mov %esi, (%edi) add $0xFF8, %edi mov %esi, (%edi) /* STEP 2: write the pgdir entries to the BEGINNING of the PDPT */ # BOOTSTRAP_PDIR_SIZE contains the total size of the page directory buffer. # use it to calculate the number of page directories we're using. mov $0, %edx mov $BOOTSTRAP_PDIR_SIZE, %eax mov $0x1000, %ebx idiv %ebx mov $bootstrap_pdpt, %edi mov $bootstrap_pdir_buf, %esi /* %ESI is a pointer into the pagedir buffer. %EDI is a pointer into the PDPT. %EAX is the total number of page directories. %EBX is where we build the PDPT entry. */ 1: mov %esi, %ebx and $0xFFFFF000, %ebx or $0x3, %ebx mov %ebx, (%edi) add $0x1000, %esi add $0x8, %edi sub $0x1, %eax cmp $0x0, %eax jne 1b /* STEP 3: write the pgdir entries to the END of the PDPT */ # BOOTSTRAP_PDIR_SIZE contains the total size of the page directory buffer. # use it to calculate the number of page directories we're using. mov $0, %edx mov $BOOTSTRAP_PDIR_SIZE, %eax mov $0x1000, %ebx idiv %ebx mov $bootstrap_pdpt, %edi mov $bootstrap_pdir_buf, %esi /* with the number of page directories in %EAX, calculate the starting index into the PDPT. we need to duplicate the pgdir entries here to map the kernel at vaddr 0xffffffff80000000 and up */ mov %eax, %ecx mov $0x8, %edx mul %edx xchg %eax, %ecx add $0x1000, %edi sub %ecx, %edi sub $0x08, %edi # subtract one from the index here, otherwise the mapping will be at 0xffffffffc0000000 # (each entry in the array is 8 bytes long) /* %ESI is a pointer into the pagedir buffer. %EDI is a pointer into the PDPT. %EAX is the total number of page directories. %EBX is where we build the PDPT entry. */ 2: mov %esi, %ebx and $0xFFFFF000, %ebx or $0x3, %ebx mov %ebx, (%edi) add $0x1000, %esi add $0x8, %edi sub $0x1, %eax cmp $0x0, %eax jne 2b /* STEP 4: initialise the page directories. Each page directory entry can be used to map 2MiB of virtual memory. The exact number of page directories in use is determined by BOOTSTRAP_PDIR_SIZE */ /* the total number of page directory entries is stored in %EAX */ mov $0, %edx mov $BOOTSTRAP_PDIR_SIZE, %eax mov $0x8, %ebx idiv %ebx mov $0x0, %esi mov $bootstrap_pdir_buf, %edi /* %EDI is a pointer into the pagedir buffer. %ESI is the physical address being mapped. must be a multiple of 2MiB. %EAX is the total number of page directories. %EBX is where we build the pagedir entry. */ 3: mov %esi, %ebx and $0xFFFFF000, %ebx or $0x83, %ebx mov %ebx, (%edi) add $0x200000, %esi add $0x8, %edi sub $0x1, %eax cmp $0x0, %eax jne 3b ret .global long_mode_switch .type long_mode_switch, @function long_mode_switch: /* load %CR3 with the physical address of the PML4T */ mov $bootstrap_pml4t, %eax mov %eax, %cr3 /* enable long mode and the syscall/sysret instructions */ movl $0xC0000080, %ecx rdmsr orl $0x00000101, %eax wrmsr lgdt (bootstrap_gdt_ptr) mov %cr4, %ecx orl $0x00000020, %ecx mov %ecx, %cr4 mov %cr0, %ecx orl $0x8000002a, %ecx mov %ecx, %cr0 mov $0x10, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs ljmpl $0x08, $start_64 cli hlt .global _start .type _start, @function _start: mov $bootstrap_stack_top, %esp push %ebx # store the pointer to the multiboot info block as a 64-bit value. push $0x00 # store the pointer to the multiboot info block as a 64-bit value. # check if long mode is supported movl $0x80000000, %eax # Extended-function 80000000h. cpuid # Is largest extended function cmpl $0x80000000, %eax # any function > 80000000h? jbe no_long_mode # If not, no long mode. movl $0x80000001, %eax # Extended-function 80000001h. cpuid # Now EDX = extended-features flags. btl $29, %edx # Test if long mode is supported. jnc no_long_mode # Exit if not supported. movl $1, %eax cpuid btl $3, %edx mov $0xb8000, %eax movb $76, (%eax) inc %eax movb $0xF, (%eax) call init_page_tables call long_mode_switch # calls start_64, does not return cli hlt