; 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