Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
269 lines
No EOL
9.1 KiB
ArmAsm
269 lines
No EOL
9.1 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 "arch/mmu.h"
|
|
#include "arch/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
|
|
|
|
# `call`, not `jmp`, and the difference is an ABI requirement rather than a
|
|
# style preference.
|
|
#
|
|
# x86-64 says rsp is 16-byte aligned immediately before a `call`, so a
|
|
# function starts with rsp ending in 8 -- the return address the call just
|
|
# pushed -- and its `push %rbp; mov %rsp,%rbp` prologue therefore leaves
|
|
# %rbp 16-byte aligned. Compilers rely on that: an aligned SSE store to a
|
|
# frame slot is `movaps`, and `movaps` to a misaligned address is #GP.
|
|
#
|
|
# This was a `jmp` into a 16-byte-aligned stack, so every frame on every
|
|
# application processor was off by eight, for the life of the machine. It
|
|
# cost nothing for as long as no function the APs ran happened to need an
|
|
# aligned vector store -- and then one did, deep in the console, and CPU 1
|
|
# took a #GP the moment it announced itself on a verbose boot.
|
|
#
|
|
# mpenter never returns, so the pushed address is only ever a backtrace
|
|
# entry. Eight bytes for an ABI the whole toolchain assumes is a bargain.
|
|
call mpenter
|
|
jmp __deadloop
|
|
|
|
.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 |