summaryrefslogtreecommitdiff
path: root/entry.S
diff options
context:
space:
mode:
authorFrans Kaashoek <[email protected]>2018-09-23 08:24:42 -0400
committerFrans Kaashoek <[email protected]>2018-09-23 08:35:30 -0400
commitab0db651af6f1ffa8fe96909ce16ae314d65c3fb (patch)
treec429f8ee36fa7da1e25f564a160b031613ca05e9 /entry.S
parentb818915f793cd20c5d1e24f668534a9d690f3cc8 (diff)
downloadxv6-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.S273
1 files changed, 214 insertions, 59 deletions
diff --git a/entry.S b/entry.S
index bc79bab..88ad92b 100644
--- a/entry.S
+++ b/entry.S
@@ -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