diff options
author | Frans Kaashoek <[email protected]> | 2018-09-23 08:24:42 -0400 |
---|---|---|
committer | Frans Kaashoek <[email protected]> | 2018-09-23 08:35:30 -0400 |
commit | ab0db651af6f1ffa8fe96909ce16ae314d65c3fb (patch) | |
tree | c429f8ee36fa7da1e25f564a160b031613ca05e9 /entry.S | |
parent | b818915f793cd20c5d1e24f668534a9d690f3cc8 (diff) | |
download | xv6-labs-ab0db651af6f1ffa8fe96909ce16ae314d65c3fb.tar.gz xv6-labs-ab0db651af6f1ffa8fe96909ce16ae314d65c3fb.tar.bz2 xv6-labs-ab0db651af6f1ffa8fe96909ce16ae314d65c3fb.zip |
Checkpoint port of xv6 to x86-64. Passed usertests on 2 processors a few times.
The x86-64 doesn't just add two levels to page tables to support 64 bit
addresses, but is a different processor. For example, calling conventions,
system calls, and segmentation are different from 32-bit x86. Segmentation is
basically gone, but gs/fs in combination with MSRs can be used to hold a
per-core pointer. In general, x86-64 is more straightforward than 32-bit
x86. The port uses code from sv6 and the xv6 "rsc-amd64" branch.
A summary of the changes is as follows:
- Booting: switch to grub instead of xv6's bootloader (pass -kernel to qemu),
because xv6's boot loader doesn't understand 64bit ELF files. And, we don't
care anymore about booting.
- Makefile: use -m64 instead of -m32 flag for gcc, delete boot loader, xv6.img,
bochs, and memfs. For now dont' use -O2, since usertests with -O2 is bigger than
MAXFILE!
- Update gdb.tmpl to be for i386 or x86-64
- Console/printf: use stdarg.h and treat 64-bit addresses different from ints
(32-bit)
- Update elfhdr to be 64 bit
- entry.S/entryother.S: add code to switch to 64-bit mode: build a simple page
table in 32-bit mode before switching to 64-bit mode, share code for entering
boot processor and APs, and tweak boot gdt. The boot gdt is the gdt that the
kernel proper also uses. (In 64-bit mode, the gdt/segmentation and task state
mostly disappear.)
- exec.c: fix passing argv (64-bit now instead of 32-bit).
- initcode.c: use syscall instead of int.
- kernel.ld: load kernel very high, in top terabyte. 64 bits is a lot of
address space!
- proc.c: initial return is through new syscall path instead of trapret.
- proc.h: update struct cpu to have some scratch space since syscall saves less
state than int, update struct context to reflect x86-64 calling conventions.
- swtch: simplify for x86-64 calling conventions.
- syscall: add fetcharg to handle x86-64 calling convetions (6 arguments are
passed through registers), and fetchaddr to read a 64-bit value from user space.
- sysfile: update to handle pointers from user space (e.g., sys_exec), which are
64 bits.
- trap.c: no special trap vector for sys calls, because x86-64 has a different
plan for system calls.
- trapasm: one plan for syscalls and one plan for traps (interrupt and
exceptions). On x86-64, the kernel is responsible for switching user/kernel
stacks. To do, xv6 keeps some scratch space in the cpu structure, and uses MSR
GS_KERN_BASE to point to the core's cpu structure (using swapgs).
- types.h: add uint64, and change pde_t to uint64
- usertests: exit() when fork fails, which helped in tracking down one of the
bugs in the switch from 32-bit to 64-bit
- vectors: update to make them 64 bits
- vm.c: use bootgdt in kernel too, program MSRs for syscalls and core-local
state (for swapgs), walk 4 levels in walkpgdir, add DEVSPACETOP, use task
segment to set kernel stack for interrupts (but simpler than in 32-bit mode),
add an extra argument to freevm (size of user part of address space) to avoid
checking all entries till KERNBASE (there are MANY TB before the top 1TB).
- x86: update trapframe to have 64-bit entries, which is what the processor
pushes on syscalls and traps. simplify lgdt and lidt, using struct desctr,
which needs the gcc directives packed and aligned.
TODO:
- use int32 instead of int?
- simplify curproc(). xv6 has per-cpu state again, but this time it must have it.
- avoid repetition in walkpgdir
- fix validateint() in usertests.c
- fix bugs (e.g., observed one a case of entering kernel with invalid gs or proc
Diffstat (limited to 'entry.S')
-rw-r--r-- | entry.S | 273 |
1 files changed, 214 insertions, 59 deletions
@@ -1,68 +1,223 @@ -# The xv6 kernel starts executing in this file. This file is linked with -# the kernel C code, so it can refer to kernel symbols such as main(). -# The boot block (bootasm.S and bootmain.c) jumps to entry below. - -# Multiboot header, for multiboot boot loaders like GNU Grub. +# x86-64 bootstrap, assuming load by MultiBoot-compliant loader. +# The MutliBoot specification is at: # http://www.gnu.org/software/grub/manual/multiboot/multiboot.html -# -# Using GRUB 2, you can boot xv6 from a file stored in a -# Linux file system by copying kernel or kernelmemfs to /boot -# and then adding this menu entry: -# -# menuentry "xv6" { -# insmod ext2 -# set root='(hd0,msdos1)' -# set kernel='/boot/kernel' -# echo "Loading ${kernel}..." -# multiboot ${kernel} ${kernel} -# boot -# } - -#include "asm.h" -#include "memlayout.h" +# GRUB is a MultiBoot loader, as is qemu's -kernel option. + #include "mmu.h" -#include "param.h" +#include "memlayout.h" + +# STACK is the size of the bootstrap stack. +#define STACK 8192 -# Multiboot header. Data to direct multiboot loader. -.p2align 2 +# MultiBoot header. +# http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Header-layout +.align 4 .text .globl multiboot_header multiboot_header: #define magic 0x1badb002 - #define flags 0 + #define flags (1<<16 | 1<<0) .long magic .long flags - .long (-magic-flags) - -# By convention, the _start symbol specifies the ELF entry point. -# Since we haven't set up virtual memory yet, our entry point is -# the physical address of 'entry'. -.globl _start -_start = V2P_WO(entry) - -# Entering xv6 on boot processor, with paging off. -.globl entry -entry: - # Turn on page size extension for 4Mbyte pages - movl %cr4, %eax - orl $(CR4_PSE), %eax - movl %eax, %cr4 - # Set page directory - movl $(V2P_WO(entrypgdir)), %eax - movl %eax, %cr3 - # Turn on paging. - movl %cr0, %eax - orl $(CR0_PG|CR0_WP), %eax - movl %eax, %cr0 - - # Set up the stack pointer. - movl $(stack + KSTACKSIZE), %esp - - # Jump to main(), and switch to executing at - # high addresses. The indirect call is needed because - # the assembler produces a PC-relative instruction - # for a direct jump. - mov $main, %eax - jmp *%eax - -.comm stack, KSTACKSIZE + .long (- magic - flags) # checksum + .long V2P_WO(multiboot_header) # header address + .long V2P_WO(multiboot_header) # load address + .long V2P_WO(edata) # load end address + .long V2P_WO(end) # bss end address + .long V2P_WO(start) # entry address + +# Entry point jumped to by boot loader. Running in 32-bit mode. +# http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Machine-state +# +# EAX = 0x2badb002 +# EBX = address of multiboot information structure +# CS = 32-bit read/execute code segment with identity map +# DS, ES, FS, GS, SS = 32-bit read/write data segment with identity map +# A20 gate = enabled +# CR0 = PE set, PG clear +# EFLAGS = VM clear, IF clear +# +.code32 +.globl start +start: + # Tell BIOS to do "warm reboot" when we shut down. + movw $0x1234, 0x472 + + # Set up multiboot arguments for main. + movl %eax, %edi + movl %ebx, %esi + + # Initialize stack. + movl $V2P_WO(stack+STACK), %esp + + # Zero bss. QEMU's MultiBoot seems not to. + # It's possible that the header above is not right, but it looks right. + # %edi is holding multiboot argument, so save in another register. + # (The stack is in the bss.) + movl %edi, %edx + movl $V2P_WO(edata), %edi + movl $V2P_WO(end), %ecx + subl $V2P_WO(edata), %ecx + movl $0, %eax + cld + rep stosb + movl %edx, %edi + + call loadgdt + + # Enter new 32-bit code segment (already in 32-bit mode). + ljmp $KCSEG32, $V2P_WO(start32) // code32 segment selector + +start32: + # Initialize page table. + call initpagetables + call init32e + + movl $V2P_WO(start64), %eax + # Enter 64-bit mode. + ljmp $KCSEG, $V2P_WO(tramp64) // code64 segment selector + +.code64 +start64: + # Load VA of stack + movabsq $(stack+STACK), %rsp + # Clear frame pointer for stack walks + movl $0, %ebp + # Call into C code. + call bpmain + # should not return from bpmain + jmp . + +.code32 +.global apstart +apstart: + call loadgdt + ljmp $KCSEG32, $V2P_WO(apstart32) // code32 segment selector + +apstart32: + call init32e + movl $V2P_WO(apstart64), %eax + ljmp $KCSEG, $V2P_WO(tramp64) // code64 segment selector + +.code64 +apstart64: + # Remember (from bootothers), that our kernel stack pointer is + # at the top of our temporary stack. + popq %rax + movq %rax, %rsp + movq $0, %rbp + call apmain +1: jmp 1b + +.code64 +tramp64: + # The linker thinks we are running at tramp64, but we're actually + # running at PADDR(tramp64), so use an explicit calculation to + # load and jump to the correct address. %rax should hold the + # physical address of the jmp target. + movq $KERNBASE, %r11 + addq %r11, %rax + jmp *%rax + +# Initial stack +.comm stack, STACK + +# Page tables. See section 4.5 of 253668.pdf. +# We map the first GB of physical memory at 0 and at 1 TB (not GB) before +# the end of virtual memory. At boot time we are using the mapping at 0 +# but during ordinary execution we use the high mapping. +# The intent is that after bootstrap the kernel can expand this mapping +# to cover all the available physical memory. +# This would be easier if we could use the PS bit to create GB-sized entries +# and skip the pdt table, but not all chips support it, and QEMU doesn't. +.align 4096 +pml4: + .quad V2P_WO(pdpt) + PTE_P + PTE_W // present, read/write + .quad 0 + .space 4096 - 2*16 + .quad V2P_WO(pdpt) + PTE_P + PTE_W + .quad 0 + +.align 4096 +pdpt: + .quad V2P_WO(pdt) + PTE_P + PTE_W + .space 4096 - 8 + +.align 4096 +pdt: + // Filled in below. + .space 4096 + +.code32 +initpagetables: + pushl %edi + pushl %ecx + pushl %eax + + // Set up 64-bit entry in %edx:%eax. + // Base address 0, present, read/write, large page. + movl $(0 | PTE_P | PTE_W | PTE_PS), %eax + movl $0, %edx + + // Fill in 512 entries at pdt. + movl $V2P_WO(pdt), %edi + movl $512, %ecx +1: + // Write this 64-bit entry. + movl %eax, 0(%edi) + movl %edx, 4(%edi) + addl $8, %edi + // 64-bit add to prepare address for next entry. + // Because this is a large page entry, it covers 512 4k pages (2 MB). + add $(512*4096), %eax + adc $0, %edx + loop 1b + + popl %eax + popl %ecx + popl %edi + ret + +# Initialize IA-32e mode. See section 9.8.5 of 253668.pdf. +init32e: + # Set CR4.PAE and CR4.PSE = 1. + movl %cr4, %eax + orl $0x30, %eax + movl %eax, %cr4 + + # Load CR3 with physical base address of level 4 page table. + movl $V2P_WO(pml4), %eax + movl %eax, %cr3 + + # Enable IA-32e mode by setting IA32_EFER.LME = 1. + # Also turn on IA32_EFER.SCE (syscall enable). + movl $0xc0000080, %ecx + rdmsr + orl $0x101, %eax + wrmsr + + # Enable paging by setting CR0.PG = 1. + movl %cr0, %eax + orl $0x80000000, %eax + movl %eax, %cr0 + nop + nop + + ret + +loadgdt: + subl $8, %esp + movl $V2P_WO(bootgdt), 4(%esp) + movw $(8*NSEGS-1), 2(%esp) + lgdt 2(%esp) + addl $8, %esp + + movl $KDSEG, %eax // data segment selector + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + movl $0, %eax // null segment selector + movw %ax, %fs + movw %ax, %gs + + ret |