blob: 9740273c718b87dcec93ac44f3c6ccb4a23ff2d0 [file] [log] [blame]
/* Copyright (c) 2013 The Regents of the University of California
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details. */
#include <arch/mmu.h>
#include <arch/trap.h>
#include <arch/x86.h>
#include <kstack.h>
#define MULTIBOOT_PAGE_ALIGN (1<<0)
#define MULTIBOOT_MEMORY_INFO (1<<1)
#define MULTIBOOT_HEADER_MAGIC (0x1BADB002)
#define MULTIBOOT_HEADER_FLAGS (MULTIBOOT_MEMORY_INFO | MULTIBOOT_PAGE_ALIGN)
#define CHECKSUM (-(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS))
# The kernel bootstrap (this code) is linked and loaded at physical address
# 0x00100000 (1MB), which is the start of extended memory. (See kernel.ld)
# Flagging boottext to be text. Check out:
# http://sourceware.org/binutils/docs/as/Section.html
.section .boottext, "awx"
.code32
.align 4
multiboot_header:
.long MULTIBOOT_HEADER_MAGIC
.long MULTIBOOT_HEADER_FLAGS
.long CHECKSUM
# Calling convention for internal functions:
#
# my convention:
# callee saved ebp, ebx
# caller saves eax ecx edx esi edi
# args: a0 edi, a1 esi, a2 edx, a3 ecx, a4 eax, a5+ stack
# ret eax
#
# for reference, the normal convention:
# callee saved: esi, edi, ebp, ebx
# caller saved: eax, ecx, edx
# args on stack
# ret eax
/* Helper: creates count mappings in the PML3 for 1GB jumbo pages for the given
* vaddr to paddr range in physical memory. Then it puts that PML3's addr in
* the PML4's appropriate slot. Using a macro mostly to help with 64 bit
* argument marshalling.
*
* This will clobber ax, dx, cx, di, si.
*
* A few notes about the jumbo GB mapping:
* - PML3 is responsible for the 9 bits from 30-38, hence the >> 30 and mask
* - PML4 is responsible for the 9 bits from 47-39, hence the >> 39 and mask
* - We use the jumbo PTE_PS flag only on PML3 - can't do it for PML4.
* - PTEs are 8 bytes each, hence the scale = 8 in the indirect addressing
* - The top half of all of PML4's PTEs are set to 0. This includes the top 20
* bits of the physical address of the page tables - which are 0 in our case.
* - The paddr for the PML3 PTEs is split across two 32-byte halves of the PTE.
* We drop off the lower 30 bits, since we're dealing with 1GB pages. The 2
* LSBs go at the top of the first half of the PTE, and the remaining 30 are
* the lower 30 of the top half. */
#define MAP_GB_PAGES(pml3, vaddr, paddr, count) \
movl $(boot_pml4), %eax; \
push %eax; \
movl $(count), %eax; \
push %eax; \
movl $(pml3), %edi; \
movl $(vaddr >> 32), %esi; \
movl $(vaddr & 0xffffffff), %edx; \
movl $(paddr >> 32), %ecx; \
movl $(paddr & 0xffffffff), %eax; \
call map_gb_pages; \
add $0x8, %esp
# Maps count GBs (up to 512) of vaddr -> paddr using pml3 and pml4 in 1GB pages
#
# edi pml3, esi vaddr_hi, edx vaddr_lo, ecx paddr_hi, eax paddr_lo,
# stack: count, pml4
map_gb_pages:
push %ebx
movl 0x8(%esp), %ebx
# save these 3, need them for the next call
push %edi
push %esi
push %edx
# arg5 on stack. other args already in regs.
push %ebx
call fill_jpml3
add $0x4, %esp # pop arg5 frame
# restore our regs/args for next call
pop %edx
pop %esi
pop %edi
movl 0xc(%esp), %ecx
call insert_pml3
pop %ebx
ret
# Fills pml3 with "count" jumbo entries, mapping from vaddr -> paddr.
# pml3s are responsible for bits 38..30 of vaddr space and 30 bit paddr entries
#
# edi pml3, esi vaddr_hi, edx vaddr_lo, ecx paddr_hi, eax paddr_lo, stack count
fill_jpml3:
push %ebx
movl 0x8(%esp), %ebx
# want (vaddr >> 30) & 0x1ff into esi. append upper 2 bits of edx to
# esi.
shll $2, %esi
shrl $30, %edx
orl %edx, %esi
andl $0x1ff, %esi
# want (paddr >> 30) into ecx.
shll $2, %ecx
shrl $30, %eax
orl %eax, %ecx
1:
movl %ecx, %eax
shll $30, %eax # lower part of PTE ADDR
orl $(PTE_P | PTE_W | PTE_PS | PTE_G), %eax
movl %eax, (%edi, %esi, 8)
movl %ecx, %eax
shrl $2, %eax # upper part of PTE ADDR
movl %eax, 4(%edi, %esi, 8)
# prep for next loop
incl %esi
incl %ecx
decl %ebx
jnz 1b
pop %ebx
ret
#define MAP_2MB_PAGES(pml3, vaddr, paddr, count, pml2base) \
movl $(pml2base), %eax; \
push %eax; \
movl $(boot_pml4), %eax; \
push %eax; \
movl $(count), %eax; \
push %eax; \
movl $(pml3), %edi; \
movl $(vaddr >> 32), %esi; \
movl $(vaddr & 0xffffffff), %edx; \
movl $(paddr >> 32), %ecx; \
movl $(paddr & 0xffffffff), %eax; \
call map_2mb_pages; \
add $0xc, %esp
# Maps count GBs (up to 512) of vaddr -> paddr using pml3, pml4, and an array of
# pml2s in 2MB pages
#
# edi pml3, esi vaddr_hi, edx vaddr_lo, ecx paddr_hi, eax paddr_lo,
# stack: count, pml4, pml2_base
map_2mb_pages:
push %ebx
# save these 3, need them for the next call
push %edi
push %esi
push %edx
# arg5 and 7 on stack. other args already in regs.
movl 0x1c(%esp), %ebx # arg7: 4 pushes, 1 retaddr, arg 5, arg6
push %ebx
movl 0x18(%esp), %ebx # arg6: 5 pushes, 1 retaddr
push %ebx
call fill_pml3
add $0x8, %esp # pop args frame
# restore our regs/args for next call
pop %edx
pop %esi
pop %edi
movl 0xc(%esp), %ecx
call insert_pml3
pop %ebx
ret
# Fills pml3 with "count" pml2 entries, mapping from vaddr -> paddr.
# pml3s are responsible for bits 38..30 of vaddr space and 30 bit paddr entries
#
# edi pml3, esi vaddr_hi, edx vaddr_lo, ecx paddr_hi, eax paddr_lo,
# stack count, pml2base
fill_pml3:
push %ebx
push %ebp # scratch register
movl 0xc(%esp), %ebx
1:
push %edi # save edi = pml3
push %esi
push %edx
push %ecx
push %eax
movl $512, %ebp # count = 512 for PML2 (map it all)
push %ebp
# compute pml2 (pml2base + (total count - current count) * PGSIZE * 2)
movl 0x28(%esp), %ebp # pml2base (8 push, 1 ret, arg5)
movl 0x24(%esp), %edi # total count
subl %ebx, %edi
shll $13, %edi
addl %edi, %ebp
movl %ebp, %edi # arg0 for the func call
call fill_jpml2
add $0x4, %esp
pop %eax
pop %ecx
pop %edx
pop %esi
pop %edi
# re-save our register frame
push %edi
push %esi
push %edx
push %ecx
push %eax
# prep call to insert (ecx = pml3, edi = pml2)
movl %edi, %ecx
movl %ebp, %edi
call insert_pml2
pop %eax
pop %ecx
pop %edx
pop %esi
pop %edi
# prep for next loop. need to advance vaddr and paddr by 1GB
addl $(1 << 30), %edx
adcl $0, %esi
addl $(1 << 30), %eax
adcl $0, %ecx
decl %ebx
jnz 1b
pop %ebp
pop %ebx
ret
# Fills pml2 with "count" jumbo entries, mapping from vaddr -> paddr
# pml2s are responsible for bits 29..21 of vaddr space and 21 bit paddr entries
#
# edi pml2, esi vaddr_hi, edx vaddr_lo, ecx paddr_hi, eax paddr_lo, stack count
fill_jpml2:
push %ebx
movl 0x8(%esp), %ebx
# want (vaddr >> 21) & 0x1ff into esi.
shrl $21, %edx
movl %edx, %esi
andl $0x1ff, %esi
# want (paddr >> 21) into ecx.
shll $11, %ecx
shrl $21, %eax
orl %eax, %ecx
1:
movl %ecx, %eax
shll $21, %eax # lower part of PTE ADDR
orl $(PTE_P | PTE_W | PTE_PS | PTE_G), %eax
movl %eax, (%edi, %esi, 8)
movl %ecx, %eax
shrl $11, %eax # upper part of PTE ADDR
movl %eax, 4(%edi, %esi, 8)
# prep for next loop
incl %esi
incl %ecx
decl %ebx
jnz 1b
pop %ebx
ret
# Inserts a pml3 into pml4, so that it handles mapping for vaddr
#
# edi pml3, esi vaddr_hi, edx vaddr_lo, ecx pml4
insert_pml3:
shrl $7, %esi # want to shift vaddr >> 39
andl $0x1ff, %esi
orl $(PTE_P | PTE_W | PTE_G), %edi
movl %edi, (%ecx, %esi, 8)
movl $0x0, 4(%ecx, %esi, 8) # being clever, i know upper bits are 0
ret
# Inserts a pml2 into pml3, so that it handles mapping for vaddr
#
# edi pml2, esi vaddr_hi, edx vaddr_lo, ecx pml3
insert_pml2:
# want (vaddr >> 30) & 0x1ff into esi. append upper 2 bits of edx to
# esi.
shll $2, %esi
shrl $30, %edx
orl %edx, %esi
andl $0x1ff, %esi
orl $(PTE_P | PTE_W | PTE_G), %edi
movl %edi, (%ecx, %esi, 8)
movl $0x0, 4(%ecx, %esi, 8) # being clever, i know upper bits are 0
ret
.globl _start
_start:
movl $stack32top, %esp
push %ebx # save mulitboot info
movw $0x1234,0x472 # warm boot
movl $0x80000001, %eax
# some machines / VMs might not support long mode
cpuid
test $(1 << 29), %edx
jz err_no_long
# others don't support 1GB jumbo pages, which is a shame
test $(1 << 26), %edx
jz no_pml3ps
# build page table. need mappings for
# - current code/data at 0x00100000 -> 0x00100000
# - kernel load location: 0xffffffffc0000000 -> 0x0000000000000000
# - kernbase: 0xffff80000000 -> 0x0000000000000000
# we'll need one table for the PML4, and three PML3 (PDPE)'s. 1GB will
# suffice for lo and hi (til we do the VPT and LAPIC mappings). For
# kernbase, we'll do all 512 PML3 entries (covers 512GB)
MAP_GB_PAGES(boot_pml3_lo, 0x0000000000000000, 0x0, 1)
MAP_GB_PAGES(boot_pml3_hi, 0xffffffffc0000000, 0x0, 1)
MAP_GB_PAGES(boot_pml3_kb, 0xffff800000000000, 0x0, 512)
jmp post_mapping
no_pml3ps:
MAP_2MB_PAGES(boot_pml3_lo, 0x0000000000000000, 0x0, 1, boot_pml2_lo)
MAP_2MB_PAGES(boot_pml3_hi, 0xffffffffc0000000, 0x0, 1, boot_pml2_hi)
MAP_2MB_PAGES(boot_pml3_kb, 0xffff800000000000, 0x0, 512, boot_pml2_kb)
post_mapping:
# load cr3 - note that in long mode, cr3 is 64 bits wide. our boot
# pml4 is in lower memory, so it'll be fine if the HW 0 extends.
movl $boot_pml4, %eax
movl %eax, %cr3
# turn on paging option in cr4. note we assume PSE support. if we
# didn't have it, then our jumbo page mappings are going to fail. we
# also want global pages (for performance). PAE is the basics needed
# for long paging
movl %cr4, %eax
orl $(CR4_PSE | CR4_PGE | CR4_PAE), %eax
movl %eax, %cr4
# Turn on the IA32E enabled bit.
# rd/wrmsr use ecx for the addr, and eax as the in/out register.
movl $IA32_EFER_MSR, %ecx
rdmsr
orl $IA32_EFER_IA32E_EN, %eax
wrmsr
# Setup cr0. PE and PG are critical for now. The others are similar to
# what we want in general (-AM with 64 bit, it's useless).
movl %cr0, %eax
orl $(CR0_PE | CR0_PG | CR0_WP | CR0_NE | CR0_MP), %eax
andl $(~(CR0_AM | CR0_TS | CR0_EM | CR0_CD | CR0_NW)), %eax
movl %eax, %cr0
pop %ebx # restore multiboot info
# load the 64bit GDT and jump to long mode
lgdt gdt64desc
ljmp $0x08, $long_mode
# these are error handlers, we're jumping over these
err_no_long:
mov $no_long_string, %esi
jmp printstring
err_no_pml3ps:
mov $no_pml3ps_string, %esi
jmp printstring
printstring:
mov $0xb8a00, %edi # assuming CGA buffer, 16 lines down
mov $0, %ecx
1:
movb (%esi, %ecx), %bl
test %bl, %bl
je printdone
# print to the console (0x07 is white letters on black background)
mov $0x07, %bh
mov %bx, (%edi, %ecx, 2)
# print to serial
mov $(0x3f8 + 5), %edx # assuming COM1
2:
inb %dx, %al
test $0x20, %al # ready check
jz 2b
mov $0x3f8, %edx # assuming COM1
mov %bl, %al
outb %al, %dx
# advance the loop
inc %ecx
jmp 1b
printdone:
hlt
jmp printdone
.code64
long_mode:
# zero the data segments. Not sure if this is legit or not.
xor %rax, %rax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov %ax, %fs
mov %ax, %gs
lldt %ax
# paging is on, and our code is still running at 0x00100000.
# do some miscellaneous OS setup.
# set up gs to point to our pcpu info (both GS base and KERN GS base)
movabs $(per_cpu_info), %rdx
movq %rdx, %rax
shrq $32, %rdx
andl $0xffffffff, %eax
movl $MSR_GS_BASE, %ecx
wrmsr
movl $MSR_KERN_GS_BASE, %ecx
wrmsr
# Clear the frame pointer for proper backtraces
movq $0x0, %rbp
# We can use the bootstack from the BSS, but we need the KERNBASE addr
movabs $(bootstacktop + KERNBASE), %rsp
# Pass multiboot info to kernel_init (%rdi == arg1)
movq %rbx, %rdi
movabs $(kernel_init), %rax
call *%rax
# Should never get here, but in case we do, just spin.
spin: jmp spin
.section .bootdata, "aw"
.p2align 2 # force 4 byte alignment
.globl gdt64
gdt64:
# keep the number of these in sync with SEG_COUNT
SEG_NULL
SEG_CODE_64(0) # kernel code segment
SEG_DATA_64(0) # kernel data segment
SEG_DATA_64(3) # user data segment
SEG_CODE_64(3) # user code segment
SEG_NULL # these two nulls are a placeholder for the TSS
SEG_NULL # these two nulls are a placeholder for the TSS
.globl gdt64desc
gdt64desc:
.word (gdt64desc - gdt64 - 1) # sizeof(gdt64) - 1
.long gdt64 # HW 0-extends this to 64 bit when loading (i think)
no_long_string:
.string "Unable to boot: long mode not supported"
no_pml3ps_string:
.string "Unable to boot: 1 GB pages not supported"
# boot page tables
.section .bootbss, "w",@nobits
.align PGSIZE
.globl boot_pml4
boot_pml4:
.space PGSIZE * 2
boot_pml3_lo:
.space PGSIZE * 2
boot_pml3_hi:
.space PGSIZE * 2
boot_pml3_kb:
.space PGSIZE * 2
stack32:
.space PGSIZE
stack32top:
# Could make all of the no-jumbo stuff a config var
boot_pml2_lo: # one pml2 (1GB in the lo pml3)
.space PGSIZE * 2
boot_pml2_hi: # one pml2 (1GB in the hi pml3)
.space PGSIZE * 2
boot_pml2_kb: # 512 pml2s (+ epts) in the kb pml3
.space PGSIZE * 512 * 2
# From here down is linked for KERNBASE
.text
.globl get_boot_pml4
get_boot_pml4:
movabs $(boot_pml4), %rax
ret
.globl get_gdt64
get_gdt64:
movabs $(gdt64), %rax
ret
.section .bootbss, "w",@nobits
.p2align PGSHIFT # force page alignment
.globl bootstack
bootstack:
.space KSTKSIZE
.globl bootstacktop
bootstacktop: