blob: be196e346d4b1832cfba17d893f8bf6630f557f3 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <arch/defines.h>
#include <arch/x86/asm.h>
#include <arch/x86/descriptor.h>
#include <arch/x86/mmu.h>
#include <arch/x86/registers.h>
#include <asm.h>
#include <zircon/tls.h>
#include <mexec.h>
#define CODE_SEGMENT_SELECTOR (1 << 3)
#define DATA_SEGMENT_SELECTOR (2 << 3)
.text
FUNCTION_LABEL(mexec_asm)
// Make sure interrupts are disabled.
cli
/* Stash all the arguments passed in registers R8 - R13 */
mov %r9, %r13 /* Unused Arg */
mov %r8, %r12 /* Memmove Ops */
mov %rcx, %r11 /* Unused Arg */
mov %rdx, %r10 /* ENTRY64_ADDR */
mov %rsi, %r9 /* CR3 for Safe page tables */
mov %rdi, %r8 /* Bootimage Address */
// The old SP is in the old kernel virtual address space, so don't use it.
xor %esp, %esp
// Make sure old PGE mappings from the kernel address space are not
// still in the TLB. Having them there masked the previous bug wherein
// this code relied on using the incoming stack pointer.
mov %cr4, %rax
and $~X86_CR4_PGE, %rax
mov %rax, %cr4
// Switch to the safe identity mapped page tables.
mov %r9, %cr3
// Load our little GDT defined below. The current GDT is somewhere
// that might be overwritten when we copy in the new kernel below.
lea mexec_gdt(%rip), %rax
mov %rax, mexec_gdt_pointer(%rip)
lgdt mexec_gdt_descriptor(%rip)
// Switch to the new data segment.
mov $DATA_SEGMENT_SELECTOR, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
// Switch to the new code segment.
// Note that ljmp accepts only a 32-bit address (aka "offset").
// That's fine here, since we know we're running in the low 4G here.
leal .Lnew_cs(%rip), %eax
movl %eax, mexec_ljmp_descriptor(%rip)
ljmp *mexec_ljmp_descriptor(%rip)
.Lnew_cs:
.Lstart_copy_page:
movq $MAX_OPS_PER_PAGE, %r14
.Lload_and_copy:
/* Load the kernel relocation op into ram */
mov MEMMOV_OPS_DST_OFFSET (%r12), %rdi
mov MEMMOV_OPS_SRC_OFFSET (%r12), %rsi
mov MEMMOV_OPS_LEN_OFFSET (%r12), %rcx
/* If src == dst == len == 0, we're at the end of the list and we can quit */
xorq %rdx, %rdx
orq %rdi, %rdx
orq %rsi, %rdx
orq %rcx, %rdx
jz .Lfinished /* (SRC | DST | COUNT) == 0? Jump to finished. */
/* Test to see if we've reached the end of the page of memmov entries. */
/* If so advance to the next page. */
testq %r14, %r14
jnz .Lmore_entries_in_current_page
movq %rdi, %r12 /* Advance the linked list */
jmp .Lstart_copy_page
.Lmore_entries_in_current_page:
/* Move RCX bytes from RSI to RDI */
cld /* Clear the direction flag so that we're copying forward */
/* by default when we start */
cmp %rsi, %rdi /* Compare the src and dst registers to see if we need to */
/* copy forwards or backwards */
jbe .Ldo_copy /* if dst is greater than src, go ahead and do the copy */
/* forwards */
mov %rcx, %rax /* rcx and rax contain the number of bytes to be copied */
sub $1, %rax /* Move rsi and rdi to the end of their respective buffers */
add %rax, %rdi
add %rax, %rsi
std /* Set the direction flag to 1. This will ensure that the */
/* copy happens from the back of the buffers to the front */
.Ldo_copy:
rep movsb /* copy RCX bytes from RSI to RDI */
cld /* Clear the direction flag since we may have polluted it */
/* if we did a copy backwards */
addq $(MEMMOV_OPS_STRUCT_LEN), %r12
subq $1, %r14 /* Decrement the number of entires remaining on the current page */
jmp .Lload_and_copy
.Lfinished:
/* Move the address of the bootdata into the appropriate register */
mov %r8, %rsi
/* Zero out some registers */
xor %ebx, %ebx
xor %edi, %edi
xor %ebp, %ebp
/* Grab 64bit entrypoint from provided location */
mov (%r10), %rax
// TODO(fxbug.dev/32255): In the legacy format, the entry address is
// absolute so we use the value as is. In the modern format, the entry
// address is relative to the (arbitrary) load address. But this code
// still always uses the fixed load address, so add that in.
cmp $0x100000, %rax
ja 0f
add $0x100000, %rax
0:
/* See you on the other side! */
jmp *%rax
/* Crash, we should never reach here */
ud2
END_DATA(mexec_asm)
.balign 8
LOCAL_DATA(mexec_gdt)
// Null entry.
.int 0
.int 0
// 64-bit code segment.
.short 0xffff // limit 15:00
.short 0x0000 // base 15:00
.byte 0x00 // base 23:16
.byte 0b10011010 // P(1) DPL(00) S(1) 1 C(0) R(1) A(0)
.byte 0b10101111 // G(1) D(0) L(1) AVL(0) limit 19:16
.byte 0x0 // base 31:24
// Data segment.
.short 0xffff // limit 15:00
.short 0x0000 // base 15:00
.byte 0x00 // base 23:16
.byte 0b10010010 // P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
.byte 0b11001111 // G(1) B(1) 0 0 limit 19:16
.byte 0x0 // base 31:24
END_DATA(mexec_gdt)
DATA(mexec_gdt_end)
.balign 8
LOCAL_DATA(mexec_gdt_descriptor)
.short mexec_gdt_end - mexec_gdt - 1
LOCAL_DATA(mexec_gdt_pointer)
.quad 0 // Filled in at runtime.
END_DATA(mexec_gdt_descriptor)
.balign 8
LOCAL_DATA(mexec_ljmp_descriptor)
.long 0 // Filled in at runtime.
.short CODE_SEGMENT_SELECTOR
END_DATA(mexec_ljmp_descriptor)
DATA(mexec_asm_end)