The 32-bit entry sets EFER.SCE on its way into long mode. entry64uefi skips that bootstrap entirely, because firmware has already done the mode switch -- and with it skipped the one bit in there that is not about paging. UEFI has no reason to set SCE and does not, so the kernel came up with SYSCALL disabled. Nothing noticed until the UEFI path first got far enough to run a user program, at which point the first sysretq raised #UD: "unexpected trap 6 ... rip <syscall_trapret>" in pid 1, at zero ticks. Read-modify-write, because LME and LMA are already set by firmware and have to stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
250 lines
No EOL
8 KiB
ArmAsm
250 lines
No EOL
8 KiB
ArmAsm
/* entry.S
|
|
*
|
|
* Copyright (c) 2013 Brian Swetland
|
|
* Modified by Nexoniarz
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#define mboot_magic 0x1badb002
|
|
#define mboot_flags 0x00010000
|
|
#include "mmu.h"
|
|
#include "schboot.h"
|
|
|
|
/* A second entry point, for firmware that has already put us in long mode.
|
|
*
|
|
* The multiboot header below is entered in 32-bit protected mode, and
|
|
* mboot_entry builds the page tables and makes the long-mode transition
|
|
* itself. UEFI firmware hands a loader long mode with paging already on, so
|
|
* that path would mean dropping back to protected mode only to climb out
|
|
* again -- a mode switch whose failure mode is a triple fault with no console.
|
|
*
|
|
* Instead the 64-bit entry is published where a loader can find it, by the
|
|
* same trick the BIOS loader already uses to find the multiboot header: scan
|
|
* the first 16 KB for a magic. boot/uefi/ looks for SCH64_MAGIC and jumps to
|
|
* the physical address recorded here.
|
|
*
|
|
* The contract at that address, which boot/uefi/main.c must satisfy:
|
|
* - long mode, interrupts off
|
|
* - CR3 pointing at page tables that identity-map the low 4 GB *and* map
|
|
* the same 4 GB at 0xffff800000000000 -- entry64low's first act is a
|
|
* movabs to a high address, so a loader that only identity-maps triple
|
|
* faults on the very first instruction
|
|
* - RBX = 0, marking the boot processor rather than an AP
|
|
* Those are exactly the conditions mboot_entry has established by the time it
|
|
* reaches entry64low, which is why this points there and not somewhere new.
|
|
*/
|
|
.code64
|
|
.global mboot_header
|
|
.global mboot_entry
|
|
|
|
mboot_header:
|
|
.long mboot_magic
|
|
.long mboot_flags
|
|
.long (-mboot_magic -mboot_flags)
|
|
.long mboot_load_addr
|
|
.long mboot_load_addr
|
|
.long mboot_load_end
|
|
.long mboot_bss_end
|
|
.long mboot_entry_addr
|
|
|
|
/* After the multiboot header, never before it: the header's own header_addr
|
|
field says it lives at mboot_load_addr, and the BIOS loader computes the
|
|
kernel's file offset from (header_addr - load_addr). Sixteen bytes inserted
|
|
ahead of it makes that arithmetic wrong and loads the kernel shifted, which
|
|
fails as a triple fault rather than as anything that names the cause. */
|
|
.align 8
|
|
.global sch64_header
|
|
sch64_header:
|
|
.long SCH64_MAGIC0
|
|
.long SCH64_MAGIC1
|
|
.quad (entry64uefi - mboot_header + mboot_load_addr)
|
|
|
|
.code32
|
|
mboot_entry:
|
|
mov %eax, %ebp
|
|
|
|
# bootstrap page tables (0x1000 - 0x3000)
|
|
xor %eax, %eax
|
|
mov $0x1000, %edi
|
|
mov $0x2000, %ecx
|
|
rep stosb
|
|
|
|
# Bootstrap page tables (Identity mapping 4GB)
|
|
# PML4T[0] i PML4T[256] -> PDPT pod 0x2000
|
|
mov $(0x2000 | PTE_P | PTE_W), %edx
|
|
mov %edx, 0x1000
|
|
mov %edx, 0x1800
|
|
|
|
# PDPT mapping 4GB using 1GB huge pages
|
|
mov $(0x00000000 | PTE_P | PTE_PS | PTE_W), %edx
|
|
mov %edx, 0x2000
|
|
mov $(0x40000000 | PTE_P | PTE_PS | PTE_W), %edx
|
|
mov %edx, 0x2008
|
|
mov $(0x80000000 | PTE_P | PTE_PS | PTE_W), %edx
|
|
mov %edx, 0x2010
|
|
mov $(0xC0000000 | PTE_P | PTE_PS | PTE_W), %edx
|
|
mov %edx, 0x2018
|
|
|
|
mov %ebp, %ebx
|
|
|
|
.global entry32mp
|
|
entry32mp:
|
|
mov $0x1000, %eax
|
|
mov %eax, %cr3
|
|
|
|
lgdt (gdtr64 - mboot_header + mboot_load_addr)
|
|
|
|
mov %cr4, %eax
|
|
bts $5, %eax
|
|
mov %eax, %cr4
|
|
|
|
mov $MSR_EFER, %ecx
|
|
rdmsr
|
|
bts $0, %eax
|
|
bts $8, %eax
|
|
wrmsr
|
|
|
|
mov %cr0, %eax
|
|
orl $(CR0_PG | CR0_WP | CR0_MP), %eax
|
|
mov %eax, %cr0
|
|
|
|
ljmp $8, $(entry64low - mboot_header + mboot_load_addr)
|
|
|
|
.align 16
|
|
gdtr64:
|
|
.word gdt64_end - gdt64_begin - 1;
|
|
.quad gdt64_begin - mboot_header + mboot_load_addr
|
|
|
|
.align 16
|
|
gdt64_begin:
|
|
.long 0x00000000
|
|
.long 0x00000000
|
|
.long 0x00000000 # 64-bit code
|
|
.long 0x00209800
|
|
.long 0x00000000 # 64-bit data
|
|
.long 0x00009000
|
|
gdt64_end:
|
|
|
|
.align 16
|
|
.code64
|
|
|
|
/* Entered by boot/uefi/ with the firmware's GDT still loaded.
|
|
*
|
|
* mboot_entry reaches entry64low by way of entry32mp, which does the lgdt on
|
|
* the way past. A loader that arrives already in long mode skips all of that,
|
|
* so it arrives running on the firmware's descriptors -- OVMF's CS is 0x38 --
|
|
* and the first thing the kernel does that depends on its own selectors takes
|
|
* a #GP against a GDT the firmware no longer owns. It surfaced as
|
|
* "unexpected trap 13 ... cs=38", a long way from the cause.
|
|
*
|
|
* So load the kernel's GDT here and reload CS through it before going on.
|
|
* Doing it in the kernel rather than in the loader is deliberate: the GDT is
|
|
* the kernel's, and a loader that had to know its layout would have to be
|
|
* changed every time it moved.
|
|
*/
|
|
.global entry64uefi
|
|
entry64uefi:
|
|
/* Enable SYSCALL/SYSRET.
|
|
*
|
|
* The 32-bit entry above sets EFER.SCE on its way into long mode. This path
|
|
* skips all of that, because firmware has already done the mode switch -- and
|
|
* with it skips the one bit that is not about paging. UEFI has no reason to
|
|
* set SCE and does not, so the kernel came up with SYSCALL disabled and the
|
|
* first sysretq back to user space raised #UD: "unexpected trap 6 ... rip
|
|
* <syscall_trapret>" in pid 1, at zero ticks. It stayed hidden until this
|
|
* path first got far enough to run a user program.
|
|
*
|
|
* Read-modify-write: LME and LMA are already set by firmware and must stay.
|
|
*/
|
|
mov $MSR_EFER, %ecx
|
|
rdmsr
|
|
bts $0, %eax
|
|
wrmsr
|
|
|
|
lgdt (gdtr64 - mboot_header + mboot_load_addr)
|
|
movq $(entry64low - mboot_header + mboot_load_addr), %rax
|
|
pushq $8 /* the 64-bit code selector in gdt64 */
|
|
pushq %rax
|
|
lretq /* far return: reloads CS */
|
|
|
|
entry64low:
|
|
movabs $entry64high, %rax
|
|
jmp *%rax
|
|
|
|
.global _start
|
|
_start:
|
|
entry64high:
|
|
xor %rax, %rax
|
|
mov %ax, %ss
|
|
mov %ax, %ds
|
|
mov %ax, %es
|
|
mov %ax, %fs
|
|
mov %ax, %gs
|
|
|
|
# Is this an application processor, or the boot processor?
|
|
#
|
|
# This check used to be a comment saying it had not been written yet, and
|
|
# every AP fell straight through into the BSP path below: it set RSP to the
|
|
# *boot processor's* stack, passed its own flag to kmain() as a framebuffer
|
|
# address, and re-ran the entire bootstrap -- e820_init, kinit1, kvmalloc --
|
|
# on a second core sharing one stack with the first. That is the SMP triple
|
|
# fault. QEMU hid it for years by never reporting more than one CPU (see
|
|
# kernel/arch/x86_64/acpi.c); VMware did not.
|
|
#
|
|
# EBX is the discriminator, and entryother.S sets it to 1 immediately before
|
|
# entering long mode for exactly this purpose. It is safe: on the boot
|
|
# processor EBX carries the VESA framebuffer address from the loader, which is
|
|
# either zero or a physical MMIO address, and can never be 1.
|
|
cmp $1, %ebx
|
|
je entry64mp
|
|
|
|
# Setup initial stack
|
|
movabs $0xFFFF800000010000, %rax
|
|
mov %rax, %rsp
|
|
|
|
# Pass VESA address to main (1st argument in RDI)
|
|
mov %rbx, %rdi
|
|
|
|
call kmain
|
|
|
|
.global __deadloop
|
|
__deadloop:
|
|
jmp .
|
|
|
|
entry64mp:
|
|
mov $0x7000, %rax
|
|
mov -16(%rax), %rsp
|
|
jmp mpenter
|
|
|
|
.global wrmsr
|
|
wrmsr:
|
|
mov %rdi, %rcx
|
|
mov %rsi, %rax
|
|
shr $32, %rsi
|
|
mov %rsi, %rdx
|
|
wrmsr
|
|
retq
|
|
|
|
.global ignore_sysret
|
|
ignore_sysret:
|
|
mov $-38, %rax
|
|
sysretq |