essence-os/boot/x86/loader.s

1095 lines
20 KiB
ArmAsm

; This file is part of the Essence operating system.
; It is released under the terms of the MIT license -- see LICENSE.md.
; Written by: nakst.
[bits 16]
[org 0x1000]
; This is missing any file system specific macros.
%define vesa_info 0x7000
%define os_installation_identifier 0x7FF0
%define temporary_load_buffer 0x9000
%define page_directory 0x40000
%define page_directory_length 0x20000
%define memory_map 0x60000
%define indirect_block_buffer 0x64000
; %define magic_breakpoint xchg bx,bx
%define magic_breakpoint
start:
mov esp,0x7C00
; Save information passed from stage 1
mov [drive_number],dl
mov [partition_entry],si
mov [use_emu_read],dh
mov [max_sectors],bx
mov [max_heads],cx
; @FilesystemSpecific
FilesystemInitialise
check_pci:
; Check the computer has PCI
mov ax,0xB101
xor edi,edi
int 0x1A
mov si,error_no_pci
jc error
or ah,ah
jnz error
check_cpuid:
; Check the CPU has CPUID
mov dword [24],.no_cpuid
mov eax,0
cpuid
jmp .has_cpuid
.no_cpuid:
mov si,error_no_cpuid
jmp error
.has_cpuid:
check_msr:
; Check the CPU has MSRs
mov dword [24],.no_msr
mov ecx,0xC0000080
rdmsr
jmp .has_msr
.no_msr:
mov si,error_no_msr
jmp error
.has_msr:
enable_a20:
; Enable the A20 line, if necessary
cli
call check_a20
jc .a20_enabled
mov ax,0x2401
int 0x15
call check_a20
jc .a20_enabled
mov si,error_cannot_enable_a20_line
jmp error
.a20_enabled:
sti
identity_paging:
; Map the first 4MB to itself for the bootloader to do work in protected mode
mov eax,page_directory / 16
mov es,ax
; Clear the page directory
xor eax,eax
mov ecx,0x400
xor di,di
rep stosd
; Recursive map the directory
mov dword [es:0x3FF * 4],page_directory | 3
; Put the first table in the directory
mov dword [es:0],(page_directory + 0x1000) | 3
; Fill the table
mov edi,0x1000
mov cx,0x400
mov eax,3
.loop:
mov [es:edi],eax
add edi,4
add eax,0x1000
loop .loop
; Set the pointer to the page directory
mov eax,page_directory
mov cr3,eax
load_gdt:
; Load the GDT
lgdt [gdt_data.gdt]
inform_bios_mixed_mode:
mov eax,0xEC00
mov ebx,3 ; may switch between legacy and long mode
int 0x15
load_memory_map:
; Load the memory map
xor ebx,ebx
; Set FS to access the memory map
mov ax,0
mov es,ax
mov ax,memory_map / 16
mov fs,ax
; Loop through each memory map entry
.loop:
mov di,.entry
mov edx,0x534D4150
mov ecx,24
mov eax,0xE820
mov byte [.acpi],1
int 0x15
jc .finished
; Check the BIOS call worked
cmp eax,0x534D4150
jne .fail
; pusha
; mov di,.entry
; call .print_bytes
; popa
; Check if this is usable memory
cmp dword [.type],1
jne .try_next
cmp dword [.size],0
je .try_next
cmp dword [.acpi],0
je .try_next
; Check that the region is big enough
mov eax,[.size]
and eax,~0x3FFF
or eax,eax
jz .try_next
; Check that the base is above 1MB
cmp dword [.base + 4],0
jne .base_good
cmp dword [.base],0x100000
jl .try_next
.base_good:
; Align the base to the nearest page
mov eax,[.base]
and eax,0xFFF
or eax,eax
jz .base_aligned
mov eax,[.base]
and eax,~0xFFF
add eax,0x1000
mov [.base],eax
sub dword [.size],0x1000
sbb dword [.size + 4],0
.base_aligned:
; Align the size to the nearest page
mov eax,[.size]
and eax,~0xFFF
mov [.size],eax
; Convert the size from bytes to 4KB pages
mov eax,[.size]
shr eax,12
push ebx
mov ebx,[.size + 4]
shl ebx,20
add eax,ebx
pop ebx
mov [.size],eax
mov dword [.size + 4],0
; Store the entry
push ebx
mov ebx,[.pointer]
mov eax,[.base]
mov [fs:bx],eax
mov eax,[.base + 4]
mov [fs:bx + 4],eax
mov eax,[.size]
mov [fs:bx + 8],eax
add [.total_memory],eax
mov eax,[.size + 4]
adc [.total_memory + 4],eax
mov [fs:bx + 12],eax
add dword [.pointer],16
pop ebx
; Continue to the next entry
.try_next:
or ebx,ebx
jnz .loop
; Make sure that there were enough entries
.finished:
mov eax,[.pointer]
shr eax,4
or eax,eax
jz .fail
; Clear the base value for the entry after last
mov ebx,[.pointer]
mov dword [fs:bx],0
mov dword [fs:bx + 4],0
; Store the total memory
mov eax,[.total_memory]
mov dword [fs:bx + 8],eax
mov eax,[.total_memory + 4]
mov dword [fs:bx + 12],eax
; Load the kernel!
jmp load_kernel
; Display an error message if we could not load the memory map
.fail:
mov si,error_could_not_get_memory_map
jmp error
.pointer: dd 0
.entry:
.base: dq 0
.size: dq 0
.type: dd 0
.acpi: dd 0
.total_memory: dq 0
; @FilesystemSpecific
FilesystemGetKernelSize
allocate_kernel_buffer:
; Switch to protected mode
push ds
push es
push ss
cli
mov eax,cr0
or eax,0x80000001
mov cr0,eax
jmp 0x8:.pmode
; Set the data segment registers
[bits 32]
.pmode:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
; Work out the size of memory we'll allocate
mov ecx,[kernel_size]
shr ecx,12
inc ecx
mov edx,ecx
shl edx,12
; For every memory region
xor ebx,ebx
.memory_region_loop:
; Is this the region starting at 1MB?
mov eax,[ebx + memory_map + 4]
or eax,eax
jnz .try_next_memory_region
mov eax,[ebx + memory_map]
cmp eax,0x100000
jne .try_next_memory_region
; Check the region has enough pages remaining
mov eax,[ebx + memory_map + 8]
cmp eax,ecx
jl .try_next_memory_region
; Remove ECX pages from the region
mov eax,[ebx + memory_map + 0]
mov [kernel_buffer],eax
add eax,edx
mov [ebx + memory_map + 0],eax
sub dword [ebx + memory_map + 8],ecx
sbb dword [ebx + memory_map + 12],0
jmp .found_buffer
; Go to the next memory region
.try_next_memory_region:
add ebx,16
mov eax,[load_memory_map.pointer]
cmp ebx,eax
jne .memory_region_loop
mov si,error_no_memory
jmp error_32
.found_buffer:
; Switch to 16-bit mode
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
jmp 0x18:.rmode
; Switch to real mode
[bits 16]
.rmode:
mov eax,cr0
and eax,0x7FFFFFFE
mov cr0,eax
jmp 0x0:.finish
; Go to error
.finish:
pop ss
pop es
pop ds
; @FilesystemSpecific
FilesystemLoadKernelIntoBuffer
enable_video_mode:
call vbe_init
jmp enable_video_mode_done
%include "boot/x86/vbe.s"
enable_video_mode_done:
setup_elf:
; Switch to protected mode
cli
mov eax,cr0
or eax,0x80000001
mov cr0,eax
jmp 0x8:.pmode
; Set the data segment registers
[bits 32]
.pmode:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
; Check the ELF data is correct
mov ebx,[kernel_buffer]
mov esi,error_bad_kernel
cmp dword [ebx + 0],0x464C457F
jne error_32
cmp byte [ebx + 4],2
je setup_elf_64
cmp byte [ebx + 4],1
jne error_32
jmp setup_elf_32
setup_elf_32:
mov dword [page_table_allocation_location],0x2000
; Check the ELF data is correct
mov ebx,[kernel_buffer]
mov esi,error_bad_kernel
cmp byte [ebx + 5],1
jne error_32
cmp byte [ebx + 7],0
jne error_32
cmp byte [ebx + 16],2
jne error_32
cmp byte [ebx + 18],0x03
jne error_32
; Find the program headers
; EAX = ELF header, EBX = program headers
mov eax,ebx
mov ebx,[eax + 28]
add ebx,eax
; ECX = entries, EDX = size of entry
movzx ecx,word [eax + 44]
movzx edx,word [eax + 42]
; Loop through each program header
.loop_program_headers:
push eax
push ecx
push edx
push ebx
; Only deal with load segments
mov eax,[ebx]
cmp eax,1
jne .next_entry
; Work out how many physical pages we need to allocate
mov ecx,[ebx + 20]
shr ecx,12
inc ecx
; Get the starting virtual address
mov eax,[ebx + 8]
mov [.target_page],eax
; For every frame in the segment
.frame_loop:
xor ebx,ebx
; For every memory region
.memory_region_loop:
; Check the region has enough pages remaining
mov eax,[ebx + memory_map + 8]
or eax,eax
jz .try_next_memory_region
; Remove one page from the region
mov eax,[ebx + memory_map + 4]
or eax,eax
jnz .try_next_memory_region ; Reject 64-bit pages.
mov eax,[ebx + memory_map + 0]
mov [.physical_page],eax
add eax,0x1000
mov [ebx + memory_map + 0],eax
sub dword [ebx + memory_map + 8],1
jmp .found_physical_page
; Go to the next memory region
.try_next_memory_region:
add ebx,16
mov eax,[load_memory_map.pointer]
cmp ebx,eax
jne .memory_region_loop
mov esi,error_no_memory
jmp error_32
; Map the page into virtual memory
.found_physical_page:
; Make sure we have a page table
mov eax,[.target_page]
shr eax,22
shl eax,2
add eax,0xFFFFF000
mov ebx,[eax]
cmp ebx,0
jne .has_pt
mov ebx,[page_table_allocation_location]
add ebx,page_directory
or ebx,7
mov [eax],ebx
add dword [page_table_allocation_location],0x1000
mov eax,cr3
mov cr3,eax
.has_pt:
; Map the page!
mov eax,[.target_page]
shr eax,12
mov ebx,[.physical_page]
or ebx,0x103
shl eax,2
add eax,0xFFC00000
mov [eax],ebx
mov ebx,[.target_page]
invlpg [ebx]
; Go to the next frame
add dword [.target_page],0x1000
dec ecx
or ecx,ecx
jnz .frame_loop
; Restore the pointer to the segment
pop ebx
push ebx
; Clear the memory
mov ecx,[ebx + 20]
xor eax,eax
mov edi,[ebx + 8]
rep stosb
; Copy the memory
mov ecx,[ebx + 16]
mov esi,[ebx + 4]
add esi,[kernel_buffer]
mov edi,[ebx + 8]
rep movsb
; Go to the next entry
.next_entry:
pop ebx
pop edx
pop ecx
pop eax
add ebx,edx
dec ecx
or ecx,ecx
jnz .loop_program_headers
jmp run_kernel32
.target_page: dd 0
.physical_page: dd 0
run_kernel32:
; Let the kernel use the memory that was used to store the executable
xor eax,eax
mov ebx,[load_memory_map.pointer]
mov [memory_map + ebx + 4],eax
mov [memory_map + ebx + 12],eax
mov [memory_map + ebx + 16],eax
mov [memory_map + ebx + 20],eax
mov eax,[memory_map + ebx + 8]
mov [memory_map + ebx + 24],eax
mov eax,[memory_map + ebx + 12]
mov [memory_map + ebx + 28],eax
mov eax,[kernel_buffer]
mov [memory_map + ebx],eax
mov eax,[kernel_size]
shr eax,12
mov [memory_map + ebx + 8],eax
; Execute the kernel's _start function
mov ebx,[kernel_buffer]
mov ecx,[ebx + 24]
xor edi,edi
mov esi,1
jmp ecx
setup_elf_64:
; Check that the processor is 64-bit
mov ecx,0x80000001
cpuid
mov esi,error_no_long_mode
test eax,0x20000000
jnz error_32
; Disable paging
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
; Identity map the first 4MB
mov dword [page_table_allocation_location],0x5000
mov ecx,page_directory_length
xor eax,eax
mov edi,page_directory
rep stosb
mov dword [page_directory + 0x1000 - 16],page_directory | 3
mov dword [page_directory],(page_directory + 0x1000) | 7
mov dword [page_directory + 0x1000],(page_directory + 0x2000) | 7
mov dword [page_directory + 0x2000],(page_directory + 0x3000) | 7
mov dword [page_directory + 0x2000 + 8],(page_directory + 0x4000) | 7
mov edi,page_directory + 0x3000
mov eax,0x000003
mov ebx,0x200003
mov ecx,0x400
.identity_loop:
mov [edi],eax
add edi,8
add eax,0x1000
loop .identity_loop
mov eax,page_directory
mov cr3,eax
; Enable long mode
mov eax,cr4
or eax,32
mov cr4,eax
mov ecx,0xC0000080
rdmsr
or eax,256
wrmsr
mov eax,cr0
or eax,0x80000000
mov cr0,eax
; Go to 64-bit mode
jmp 0x48:.start_64_bit_mode
[bits 64]
.start_64_bit_mode:
mov rax,0x50
mov ds,rax
mov es,rax
mov ss,rax
; Check the ELF data is correct
mov rbx,[kernel_buffer]
mov rsi,error_bad_kernel
cmp byte [rbx + 5],1
jne error_64
cmp byte [rbx + 7],0
jne error_64
cmp byte [rbx + 16],2
jne error_64
cmp byte [rbx + 18],0x3E
jne error_64
; Find the program headers
; RAX = ELF header, RBX = program headers
mov rax,rbx
mov rbx,[rax + 32]
add rbx,rax
; ECX = entries, EDX = size of entry
movzx rcx,word [rax + 56]
movzx rdx,word [rax + 54]
; Loop through each program header
.loop_program_headers:
push rax
push rcx
push rdx
push rbx
; Only deal with load segments
mov eax,[rbx]
cmp eax,1
jne .next_entry
; Work out how many physical pages we need to allocate
mov rcx,[rbx + 40]
shr rcx,12
inc rcx
; Get the starting virtual address
mov rax,[rbx + 16]
shl rax,16
shr rax,16
mov [.target_page],rax
; For every frame in the segment
.frame_loop:
xor rbx,rbx
; For every memory region
.memory_region_loop:
; Check the region has enough pages remaining
mov rax,[rbx + memory_map + 8]
or rax,rax
jz .try_next_memory_region
; Remove one page from the region
mov rax,[rbx + memory_map + 0]
mov [.physical_page],rax
add rax,0x1000
mov [rbx + memory_map + 0],rax
sub qword [rbx + memory_map + 8],1
jmp .found_physical_page
; Go to the next memory region
.try_next_memory_region:
add rbx,16
mov eax,[load_memory_map.pointer]
cmp ebx,eax
jne .memory_region_loop
mov si,error_no_memory
jmp error_64
; Map the page into virtual memory
.found_physical_page:
; Make sure we have a PDP
mov rax,[.target_page]
shr rax,39
mov r8,0xFFFFFF7FBFDFE000
mov rbx,[r8 + rax * 8]
cmp rbx,0
jne .has_pdp
mov rbx,[page_table_allocation_location]
add rbx,page_directory
or rbx,7
mov [r8 + rax * 8],rbx
add qword [page_table_allocation_location],0x1000
mov rax,cr3
mov cr3,rax
.has_pdp:
; Make sure we have a PD
mov rax,[.target_page]
shr rax,30
mov r8,0xFFFFFF7FBFC00000
mov rbx,[r8 + rax * 8]
cmp rbx,0
jne .has_pd
mov rbx,[page_table_allocation_location]
add rbx,page_directory
or rbx,7
mov [r8 + rax * 8],rbx
add qword [page_table_allocation_location],0x1000
mov rax,cr3
mov cr3,rax
.has_pd:
; Make sure we have a PT
mov rax,[.target_page]
shr rax,21
mov r8,0xFFFFFF7F80000000
mov rbx,[r8 + rax * 8]
cmp rbx,0
jne .has_pt
mov rbx,[page_table_allocation_location]
add rbx,page_directory
or rbx,7
mov [r8 + rax * 8],rbx
add qword [page_table_allocation_location],0x1000
mov rax,cr3
mov cr3,rax
.has_pt:
; Map the page!
mov rax,[.target_page]
shr rax,12
mov rbx,[.physical_page]
or rbx,0x103
shl rax,3
xor r8,r8
mov r8,0xFFFFFF00
shl r8,32
add rax,r8
mov [rax],rbx
mov rbx,[.target_page]
invlpg [rbx]
; Go to the next frame
add qword [.target_page],0x1000
dec rcx
or rcx,rcx
jnz .frame_loop
; Restore the pointer to the segment
pop rbx
push rbx
; Clear the memory
mov rcx,[rbx + 40]
xor rax,rax
mov rdi,[rbx + 16]
rep stosb
; Copy the memory
mov rcx,[rbx + 32]
mov rsi,[rbx + 8]
add rsi,[kernel_buffer]
mov rdi,[ebx + 16]
rep movsb
; Go to the next entry
.next_entry:
pop rbx
pop rdx
pop rcx
pop rax
add rbx,rdx
dec rcx
or rcx,rcx
jnz .loop_program_headers
jmp run_kernel64
.target_page: dq 0
.physical_page: dq 0
run_kernel64:
; Get the start address of the kernel
mov rbx,[kernel_buffer]
mov rcx,[rbx + 24]
; Let the kernel use the memory that was used to store the executable
xor eax,eax
mov ebx,[load_memory_map.pointer]
mov [memory_map + ebx + 4],eax
mov [memory_map + ebx + 12],eax
mov [memory_map + ebx + 16],eax
mov [memory_map + ebx + 20],eax
mov eax,[memory_map + ebx + 8]
mov [memory_map + ebx + 24],eax
mov eax,[memory_map + ebx + 12]
mov [memory_map + ebx + 28],eax
mov eax,[kernel_buffer]
mov [memory_map + ebx],eax
mov eax,[kernel_size]
shr eax,12
mov [memory_map + ebx + 8],eax
; Map the first MB at 0xFFFFFE0000000000 --> 0xFFFFFE0000100000
mov rdi,0xFFFFFF7FBFDFE000
mov rax,[rdi]
mov rdi,0xFFFFFF7FBFDFEFE0
mov [rdi],rax
mov rax,cr3
mov cr3,rax
; Use the new linear address of the GDT
mov rax,0xFFFFFE0000000000
add qword [gdt_data.gdt2],rax
lgdt [gdt_data.gdt]
; Execute the kernel's _start function
xor rdi,rdi
mov rsi,1
jmp rcx
error_64:
jmp far [.error_32_ind]
.error_32_ind: dq error_32
dd 8
[bits 32]
error_32:
; Switch to 16-bit mode
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
jmp 0x18:.rmode
; Switch to real mode
[bits 16]
.rmode:
mov eax,cr0
and eax,0x7FFFFFFE
mov cr0,eax
jmp 0x0:.finish
; Go to error
.finish:
mov ax,0
mov ds,ax
mov es,ax
mov ss,ax
jmp error
check_a20:
; Set the carry flag if the A20 line is enabled
mov ax,0
mov es,ax
mov ax,0xFFFF
mov fs,ax
mov byte [es:0x600],0
mov byte [fs:0x610],0xFF
cmp byte [es:0x600],0xFF
je .enabled
stc
ret
.enabled:
clc
ret
error:
; Print an error message
lodsb
or al,al
jz .break
mov ah,0xE
int 0x10
jmp error
; Break indefinitely
.break:
cli
hlt
gdt_data:
.null_entry: dq 0
.code_entry: dd 0xFFFF ; 0x08
db 0
dw 0xCF9A
db 0
.data_entry: dd 0xFFFF ; 0x10
db 0
dw 0xCF92
db 0
.code_entry_16: dd 0xFFFF ; 0x18
db 0
dw 0x0F9A
db 0
.data_entry_16: dd 0xFFFF ; 0x20
db 0
dw 0x0F92
db 0
.user_code: dd 0xFFFF ; 0x2B
db 0
dw 0xCFFA
db 0
.user_data: dd 0xFFFF ; 0x33
db 0
dw 0xCFF2
db 0
.tss: dd 0x68 ; 0x38
db 0
dw 0xE9
db 0
dq 0
.code_entry64: dd 0xFFFF ; 0x48
db 0
dw 0xAF9A
db 0
.data_entry64: dd 0xFFFF ; 0x50
db 0
dw 0xAF92
db 0
.user_code64: dd 0xFFFF ; 0x5B
db 0
dw 0xAFFA
db 0
.user_data64: dd 0xFFFF ; 0x63
db 0
dw 0xAFF2
db 0
.user_code64c: dd 0xFFFF ; 0x6B
db 0
dw 0xAFFA
db 0
.gdt: dw (gdt_data.gdt - gdt_data - 1)
.gdt2: dq gdt_data
; @FilesystemSpecific
FilesystemSpecificCode
load_sectors:
; Load CX sectors from sector EAX to EDI
pushad
push edi
; Add the partition offset to EAX
mov bx,[partition_entry]
mov ebx,[bx + 8]
add eax,ebx
; Load 1 sector
mov [read_structure.lba],eax
mov ah,0x42
mov dl,[drive_number]
mov si,read_structure
cmp byte [use_emu_read],1
je .use_emu
int 0x13
jmp .done_read
.use_emu:
call load_sector_emu
.done_read:
; Check for error
mov si,error_cannot_read_disk
jc error
; Copy the data to its destination
pop edi
call move_sector_to_target
; Go to the next sector
popad
add edi,0x200
inc eax
loop load_sectors
ret
; Move data from the temporary load buffer to EDI
move_sector_to_target:
push ss
push ds
push es
; Switch to protected mode
cli
mov eax,cr0
or eax,0x80000001
mov cr0,eax
jmp 0x8:.pmode
; Set the data segment registers
[bits 32]
.pmode:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
; Copy the data
mov ecx,0x200
mov esi,temporary_load_buffer
rep movsb
; Switch to 16-bit mode
mov eax,cr0
and eax,0x7FFFFFFF
mov cr0,eax
jmp 0x18:.rmode
; Switch to real mode
[bits 16]
.rmode:
mov eax,cr0
and eax,0x7FFFFFFE
mov cr0,eax
jmp 0x0:.finish
; Return to load_sectors
.finish:
pop es
pop ds
pop ss
sti
ret
load_sector_emu:
mov di,[read_structure.lba]
xor ax,ax
mov es,ax
mov bx,0x9000
; Calculate cylinder and head.
mov ax,di
xor dx,dx
div word [max_sectors]
xor dx,dx
div word [max_heads]
push dx ; remainder - head
mov ch,al ; quotient - cylinder
shl ah,6
mov cl,ah
; Calculate sector.
mov ax,di
xor dx,dx
div word [max_sectors]
inc dx
or cl,dl
; Load the sector.
pop dx
mov dh,dl
mov dl,[drive_number]
mov ax,0x0201
int 0x13
ret
read_structure:
; Data for the extended read calls
dw 0x10
dw 1
dd temporary_load_buffer
.lba: dq 0
drive_number: db 0
use_emu_read: db 0
partition_entry: dw 0
max_sectors: dw 0
max_heads: dw 0
page_table_allocation_location: dq 0 ; Relative to page_directory.
kernel_buffer: dq 0
kernel_size: dq 0
error_cannot_enable_a20_line: db "Error: Cannot enable the A20 line",0
error_could_not_get_memory_map: db "Error: Could not get the memory map from the BIOS",0
error_no_pci: db "Error: Could not find the PCI bus",0
error_no_cpuid: db "Error: CPU does not have the CPUID instruction",0
error_no_msr: db "Error: CPU does not have the RDMSR instruction",0
error_cannot_find_file: db "Error: A file necessary for booting could not found",0
error_cannot_read_disk: db "Error: The disk could not be read.",0
error_file_too_large: db "Error: The file was too large to be loaded (more than 256KB).",0
error_kernel_too_large: db "Error: The kernel was too large for the 3MB buffer.",0
error_bad_kernel: db "Error: Invalid or unsupported kernel ELF format.",0
error_no_memory: db "Error: Not enough memory to load kernel",0
error_no_long_mode: db "Error: The kernel is compiled for a 64-bit processor but the current processor is 32-bit only.",0
error_could_not_set_video_mode: db "Error: Could not set video mode 1024x768x24.",0
reached_end: db "Reached end of stage 2 bootloader.",0