diff options
author | Marvin Borner | 2021-04-28 23:05:23 +0200 |
---|---|---|
committer | Marvin Borner | 2021-04-28 23:05:23 +0200 |
commit | 7a517df706a35e2e16637395d8ff6122b2129bd4 (patch) | |
tree | 3e14bb22db156c3d8fdae6f581adcf82cb4fc211 /entry.asm |
Diffstat (limited to 'entry.asm')
-rw-r--r-- | entry.asm | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/entry.asm b/entry.asm new file mode 100644 index 0000000..174b026 --- /dev/null +++ b/entry.asm @@ -0,0 +1,564 @@ +; Melvin's awesome ext2 bootloader +; I'm not really good at assembly, there are MANY ways to improve this! +; MIT License, Copyright (c) 2020 Marvin Borner + +; Definitions + +; General configurations +; TODO: Find out why 2560x1600 doesn't work +%define VIDEO_WIDTH 1920 +%define VIDEO_HEIGHT 1200 +%define VIDEO_BPP 4 + +; Boot constants +%define LOCATION 0x7c00 ; Bootloader location +%define SECTOR_END 0xaa55 ; Bootsector end signature +%define SECTOR_SIZE 510 ; 512 bytes minus signature + +; Interrupts +%define VIDEO_INT 0x10 ; Video BIOS Interrupt +%define DISK_INT 0x13 ; Disk BIOS Interrupt +%define MISC_INT 0x15 ; Miscellaneous services BIOS Interrupt + +; Characters +%define NEWLINE 0x0A ; Newline character (\n) +%define RETURN 0x0D ; Return character (\r) +%define NULL 0x00 ; NULL character (\0) + +; Video commands (VGA) +%define VIDEO_CLEAR 0x03 ; Clear screen command +%define VIDEO_OUT 0x0e ; Teletype output command + +; Disk commands +%define DISK_EXT_CHECK 0x41 ; Disk extension check command +%define DISK_EXT_CHECK_SIG1 0x55aa ; First extension check signature +%define DISK_EXT_CHECK_SIG2 0xaa55 ; Second extension check signature +%define DISK_ZERO 0x80 ; First disk - TODO: Disk detection +%define DISK_READ 0x42 ; Disk extended read command + +; EXT2 constants +%define EXT2_SB_SIZE 0x400 ; Superblock size +%define EXT2_SIG_OFFSET 0x38 ; Signature offset in superblock +%define EXT2_TABLE_OFFSET 0x08 ; Inode table offset after superblock +%define EXT2_INODE_TABLE_LOC 0x1000 ; New inode table location in memory +%define EXT2_ROOT_INODE 0x02 ; Root inode +%define EXT2_INODE_SIZE 0x80 ; Single inode size +%define EXT2_GET_ADDRESS(inode) (EXT2_INODE_TABLE_LOC + (inode - 1) * EXT2_INODE_SIZE) +%define EXT2_COUNT_OFFSET 0x1c ; Inode offset of number of data blocks +%define EXT2_POINTER_OFFSET 0x28 ; Inode offset of first data pointer +%define EXT2_IND_POINTER_OFFSET 0x2c ; Inode offset of singly indirect data pointer +%define EXT2_DIRECT_POINTER_COUNT 0x0c ; Direct pointer count +%define EXT2_ENTRY_LENGTH_OFFSET 0x04 ; Dirent offset of entry length +%define EXT2_FILENAME_OFFSET 0x08 ; Dirent offset of filename +%define EXT2_INODE_OFFSET 0x00 ; Dirent offset of inode number +%define EXT2_SIG 0xef53 ; Signature + +; Video constants (VESA) +%define VESA_START 0x2000 ; Struct starts at 0x2000 +%define VESA_END 0x3000 ; Struct ends at 0x3000 +%define VESA_GET_MODES 0x4f00 ; Get video modes (via 10h) +%define VESA_GET_INFO 0x4f01 ; Get video mode info (via 10h) +%define VESA_SET_MODE 0x4f02 ; Set video mode (via 10h) +%define VESA_SUCCESS_SIG 0x004f ; Returns if VBE call succeeded +%define VESA_MODE_OFFSET 0xe ; Offset to mode pointer +%define VESA_MODE_SEGMENT 0x10 ; Mode pointer segment +%define VESA_LIST_END 0xffff ; End of mode list +%define VESA_PITCH_OFFSET 0x10 ; Pitch offset in mode info +%define VESA_WIDTH_OFFSET 0x12 ; Width offset in mode info +%define VESA_HEIGHT_OFFSET 0x14 ; Height offset in mode info +%define VESA_BPP_OFFSET 0x19 ; Bytes Per Pixel (BPP) offset in mode info +%define VESA_FRAMEBUFFER_OFFSET 0x2a ; Framebuffer offset in mode info +%define VESA_LFB_FLAG 0x4000 ; Enable LFB flag + +; MMAP constants +%define MMAP_START 0x500 ; Starts at 0x500, ends at 0x600 +%define MMAP_SIZE 0x18 ; Struct size +%define MMAP_SIG 0x0534d4150 ; Signature ("SMAP") +%define MMAP_BIOS_MAGIC 0xe820 ; BIOS int 15h code to get address map + +; A20 constants +%define A20_GATE 0x92 ; Fast A20 gate +%define A20_ENABLED 0b10 ; Bit 1 defines whether A20 is enabled +%define A20_EXCLUDE_BIT 0xfe ; Bit 0 may be write-only, causing a crash + +; GDT constants (bitmap) +%define GDT_MAX_LIMIT 0xffff ; I just use the max limit lel +%define GDT_PRESENT 0b10000000 ; Is present +%define GDT_RING3 0b01100000 ; Privilege level 3 +%define GDT_DESCRIPTOR 0b00010000 ; Descriptor type, set for code/data +%define GDT_EXECUTABLE 0b00001000 ; Can be executed +%define GDT_READWRITE 0b00000010 ; Read/write access for code/data +%define GDT_ACCESSED 0b00000001 ; Whether segment is accessed +%define GDT_GRANULARITY (0x80 | 0x00) ; Page granularity (4KiB) +%define GDT_SIZE (0x40 | 0x00) ; Use 32 bit selectors +%define GDT_DATA_OFFSET 0x10 ; Offset to GDT data segment + +; Kernel loader constants +%define STACK_POINTER 0x00500000 ; The initial stack pointer in kernel mode +%define LOADER_POSITION 0x00009000 ; Kernel loader position in protected mode + +; ENOUGH, let's go! + +bits 16 +org LOCATION + +; This is the first stage. It prints some things, checks some things +; and jumps to the second stage. Nothing special. +global _start +_start: + ; Clear screen + mov ax, VIDEO_CLEAR + int VIDEO_INT + + ; Check LBA support + mov ah, DISK_EXT_CHECK + mov bx, DISK_EXT_CHECK_SIG1 + int DISK_INT + jc lba_error + cmp bx, DISK_EXT_CHECK_SIG2 + jnz lba_error + + ; Check disk and move dl + and dl, DISK_ZERO ; Use disk 0 + jz disk_error + mov [drive], dl + + ; Load stage two + mov bx, stage_two + mov [dest], bx + call disk_read + + ; JUMP + jmp stage_two + +print: + push bx + push ax + mov ah, VIDEO_OUT + xor bh, bh + print_ch: + lodsb + test al, al + jz print_end + int VIDEO_INT + jmp print_ch + print_end: + pop ax + pop bx + ret + +disk_read: + mov si, packet ; Address of dap + mov ah, DISK_READ ; Extended read + mov dl, [drive] ; Drive number + int DISK_INT + jc disk_error + ret + +; Errors +disk_error: + mov si, disk_error_msg + call print + jmp $ +lba_error: + mov si, lba_error_msg + call print + jmp $ + +; Now put some data and routines just before the end of the boot sector because +; we've still got some space left :) + +; Video map routine +video_map: + mov bx, VESA_START ; Set load address + mov di, bx + mov ax, VESA_GET_MODES ; Get video modes + int VIDEO_INT ; Ask BIOS for data! + + cmp ax, VESA_SUCCESS_SIG ; Check VBE support in response + jne .error ; Not supported :( + + mov si, [bx + VESA_MODE_OFFSET] ; Mode pointer offset + mov ax, [bx + VESA_MODE_SEGMENT] ; Mode pointer segment + mov es, ax + + mov di, VESA_END ; End of VBE struct +.loop: + mov bx, [es:si] ; Load bx with video mode + cmp bx, VESA_LIST_END ; Is this the end? + jae .done ; Yes, there aren't any modes left + + add si, 2 + mov [.mode], bx + + mov ax, VESA_GET_INFO ; Get mode information + mov cx, [.mode] ; Save in here + int VIDEO_INT ; BIOS interrupt! + cmp ax, VESA_SUCCESS_SIG ; Check if call succeeded + jne .error ; Nope, jump to error! + + mov ax, [es:di + VESA_FRAMEBUFFER_OFFSET] ; Save framebuffer + mov [.framebuffer], ax ; Move fb address to struct + + mov ax, [es:di + VESA_PITCH_OFFSET] ; Save pitch + mov bx, [es:di + VESA_WIDTH_OFFSET] ; Save width + mov cx, [es:di + VESA_HEIGHT_OFFSET] ; Save height + mov dx, [es:di + VESA_BPP_OFFSET] ; Save BPP + + mov [.bpp], dx ; Move bpp to struct (bigger bpp is always desired) + add di, 0x100 + + cmp ax, [.pitch] ; Compare with desired pitch + jne .loop ; Not equal, continue search! + cmp bx, [.width] ; Compare with desired height + jne .loop ; Not equal, continue search! + cmp cx, [.height] ; Compare with desired height + jne .loop ; Not equal, continue search! + + lea ax, [es:di - 0x100] + mov [vid_info.array], ax +.set_mode: + mov ax, VESA_SET_MODE ; Set VBE mode + mov bx, [.mode] ; Set mode address + mov [vid_info], bx ; Move mode information to array + or bx, VESA_LFB_FLAG ; Enable LFB + int VIDEO_INT ; SET! + cmp ax, VESA_SUCCESS_SIG ; Check if set succeeded + jne .error ; Nope, jump to error! +.done: + ret ; Finished loop and set! +.error: ; Something failed - print message and loop! + mov si, video_error_msg + call print + jmp $ + +; Video default data +.mode dw 0 +.width dw VIDEO_WIDTH +.height dw VIDEO_HEIGHT +.pitch dw (VIDEO_WIDTH * VIDEO_BPP) +.bpp dw VIDEO_BPP +.framebuffer dd 0 + +; Tries to load a memory map using BIOS INT 15h and e820h +memory_map: + xor ebx, ebx ; Must be 0 by spec + xor bp, bp + mov edx, MMAP_SIG ; "SMAP" in hex + mov eax, MMAP_BIOS_MAGIC ; Specify MMAP information + mov [es:di + 20], dword 1 ; Force a valid ACPI entry + mov ecx, MMAP_SIZE ; Request struct size + int MISC_INT ; BIOS interrupt + jc .fail ; Carry means "unsupported function" + mov edx, MMAP_SIG ; Mov for verification + cmp eax, edx ; Verification: Must be "SMAP" + jne .fail ; Result wasn't correct signature + test ebx, ebx ; Is size >1 + je .fail ; Nope, worthless :( + jmp .loop +.next: + mov eax, MMAP_BIOS_MAGIC ; Re-move because 0x15 clears or sth + mov [es:di + 20], dword 1 ; Force a valid ACPI entry + mov ecx, MMAP_SIZE ; Request struct size + int MISC_INT ; BIOS interrupt + jc .done ; Carry means "end of list already reached" + mov edx, MMAP_SIG ; Repair register (safety first!) +.loop: + jcxz .skip ; Skip 0-length entries + cmp cl, 20 ; Is the response correct ACPI spec (24 byte)? + jbe .notext ; Nope? Jump! + test byte [es:di + 20], 1 ; Is the "ignore this data" bit clear? + je .skip ; Yep? Skip! +.notext: + mov ecx, [es:di + 8] ; Get lower 32 bits of region + or ecx, [es:di + 12] ; "Or" with upper 32 bits to test for zero + jz .skip ; It's zero, skip! + inc bp + add di, MMAP_SIZE ; Else, next! +.skip: + test ebx, ebx ; If ebx is 0, list is complete + jne .next ; Else, next! +.done: + mov [mmap_cnt], bp + clc ; Clear carry + ret ; Finished! +.fail: + stc ; Set "unsupported function" + ret ; Finished! + +mmap_cnt: dd 0 + +; Variables +disk_error_msg db "Disk error!", NEWLINE, RETURN, NULL +lba_error_msg db "LBA error!", NEWLINE, RETURN, NULL +video_error_msg db "Video error!", NEWLINE, RETURN, NULL +found_msg db "Found file!", NEWLINE, RETURN, NULL + +; Filenames +loader_name db "load" +loader_name_len equ $ - loader_name + +drive db 0 + +; Video info struct +vid_info: +.mode dd 0 ; Mode info pointer +.array dd 0 ; Mode array pointer + +; Data +packet: + db 0x10 ; Packet size + db 0 ; Always 0 +count: + dw 4 ; Number of sectors to transfer +dest: + dw 0 ; Destination offset + dw 0 ; Destination segment +lba: + dd 1 ; LBA number + dd 0 ; More storage bytes + +; End of boot sector +times SECTOR_SIZE - ($ - $$) db 0 +dw SECTOR_END + +; This is the second stage. It tries to load the kernel loader into memory. +; To do this, it first checks the integrity of the ext2 fs. Then it has to find +; the address of the root inode (2), find the filename in it and load its contents into memory. +; After this is finished, the stage can jump into the protected mode, enable the +; A20 line and finally jump to the kernel loader! ez +stage_two: + ; Verify signature + mov ax, [superblock + EXT2_SIG_OFFSET] + cmp ax, EXT2_SIG + jne disk_error + + ; Load inode table + mov ax, [superblock + EXT2_SB_SIZE + EXT2_TABLE_OFFSET] ; Inode table + shl ax, 1 ; Multiply ax by 2 + mov [lba], ax ; Sector + ; TODO: This might only work with smaller inodes + ;mov ax, 4 + ;mov [count], ax ; Read 4kb + mov bx, EXT2_INODE_TABLE_LOC ; Copy data to 0x1000 + mov [dest], bx + call disk_read + + ; Load root directory + mov bx, EXT2_GET_ADDRESS(EXT2_ROOT_INODE) ; First block + mov ax, [bx + EXT2_POINTER_OFFSET] ; Address of first block pointer + shl ax, 1 ; Multiply ax by 2 + mov [lba], ax + mov bx, 0x3500 ; Load to this address + mov [dest], bx + call disk_read + +.search_loop: + lea si, [bx + EXT2_FILENAME_OFFSET] ; First comparison string + mov di, loader_name ; Second comparison string + mov cx, loader_name_len ; String length + rep cmpsb ; Compare strings + je .found ; Found loader! + add bx, EXT2_ENTRY_LENGTH_OFFSET ; Add dirent struct size + jmp .search_loop ; Next dirent! +.found: + mov si, found_msg + call print ; Print success message + mov ax, [bx + EXT2_INODE_OFFSET] ; Get inode number from dirent + ; Calculate address: (EXT2_INODE_TABLE_LOC + (inode - 1) * EXT2_INODE_SIZE) + dec ax ; (inode - 1) + mov cx, EXT2_INODE_SIZE ; Prepare for multiplication + mul cx ; Multiply inode number + mov bx, ax ; Transfer calculation + add bx, EXT2_INODE_TABLE_LOC ; bx is at the start of the inode now! + mov cx, [bx + EXT2_COUNT_OFFSET] ; Number of blocks for inode + cmp cx, 0 + je disk_error + cmp cx, 256 + 12 ; BLOCK_SIZE / sizeof(u32) = 256 + jge disk_error + lea di, [bx + EXT2_POINTER_OFFSET] ; Address of first block pointer + mov bx, 0x900 ; Load to this address (LOADER_POSITION >> 4) + mov [dest + 2], bx + mov bx, 0 ; Inode location = 0xF0000 + mov [dest], bx + call inode_load + + ; Load mmap + xor eax, eax + mov es, eax + mov edi, MMAP_START + push edi + call memory_map + push edi + + ; Set video mode + call video_map + + jmp protected_mode_enter + +inode_load: + mov ax, [di] ; Set ax = block pointer + shl ax, 1 ; Multiply ax by 2 + mov [lba], ax + mov [dest], bx + call disk_read + jmp .end + +.end: + add bx, 0x400 ; 1kb increase + add di, 0x4 ; Move to next block pointer + sub cx, 0x2 ; Read 2 blocks + jnz inode_load + ret + +protected_mode_enter: + cli ; Turn off interrupts + + ; TODO: Check A20 support? + ; TODO: 0x92 method may not work on every device + in al, A20_GATE + test al, A20_ENABLED + jnz .a20_enabled + or al, A20_ENABLED + and al, A20_EXCLUDE_BIT + out A20_GATE, al + .a20_enabled: + + lgdt [gdt_desc] ; Load GDT + + ; Set protected mode via cr0 + mov eax, cr0 + or eax, 1 ; Set bit 0 + mov cr0, eax + + jmp (gdt_code - gdt):protected_mode ; JUMP! + +bits 32 ; Woah, so big! +protected_mode: + mov ecx, [mmap_cnt] ; Get mmap entry count + mov [mem_info + 8], ecx ; Count of maps + pop ecx ; End of memory map + mov [mem_info + 4], ecx ; Ending boundary of struct + pop ecx ; Start of memory map + mov [mem_info], ecx ; Starting boundary of struct + + mov ax, GDT_DATA_OFFSET ; Data segment offset of GDT + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax ; Stack segment + + mov esp, STACK_POINTER ; Move stack pointer + + mov ax, (gdt_tss - gdt) | 0b11 ; Load TSS in ring 3 + ltr ax + + mov [boot_info], dword vid_info + mov [boot_info + 4], dword mem_info + mov [boot_info + 8], dword tss_entry + mov [boot_info + 12], dword drive + + mov eax, boot_info ; Pass boot_info to kernel loader + push eax ; Push as first kernel parameter + + mov edx, LOADER_POSITION + lea eax, [edx] + call eax + +; Memory map +align 16 +mem_info: + dd 0 ; Start address + dd 0 ; End address + dd 0 ; Count + +align 16 +boot_info: + dd 0 ; VBE + dd 0 ; MMAP + dd 0 ; TSS + dd 0 ; Drive + +; GDT +align 32 +gdt: ; GDTs start +gdt_null: ; Must be null + dd 0 + dd 0 +gdt_code: ; Code segment + dw GDT_MAX_LIMIT ; Limit + dw 0 ; First base + db 0 ; Second base + db (GDT_PRESENT | GDT_DESCRIPTOR | GDT_EXECUTABLE | GDT_READWRITE) ; Configuration + db (GDT_GRANULARITY | GDT_SIZE) ; Flags + db 0 ; Third base +gdt_data: ; Data segment + dw GDT_MAX_LIMIT ; Limit + dw 0 ; First base + db 0 ; Second base + db (GDT_PRESENT | GDT_DESCRIPTOR | GDT_READWRITE) ; Configuration + db (GDT_GRANULARITY | GDT_SIZE) ; Flags + db 0 ; Third base +gdt_user_code: ; User code segment + dw GDT_MAX_LIMIT ; Limit + dw 0 ; First base + db 0 ; Second base + db (GDT_PRESENT | GDT_RING3 | GDT_DESCRIPTOR | GDT_EXECUTABLE | GDT_READWRITE) ; Configuration + db (GDT_GRANULARITY | GDT_SIZE) ; Flags + db 0 ; Third base +gdt_user_data: ; Data segment + dw GDT_MAX_LIMIT ; Limit + dw 0 ; First base + db 0 ; Second base + db (GDT_PRESENT | GDT_RING3 | GDT_DESCRIPTOR | GDT_READWRITE) ; Configuration + db (GDT_GRANULARITY | GDT_SIZE) ; Flags + db 0 ; Third base +gdt_tss: ; TSS segment + dw tss_entry + (tss_entry_end - tss_entry) ; Limit + dw tss_entry ; First base + db 0 ; Second base + db (GDT_PRESENT | GDT_RING3 | GDT_EXECUTABLE | GDT_ACCESSED) ; Configuration + db GDT_SIZE ; Flags + db 0 ; Third base +gdt_end: +gdt_desc: + dw gdt_end - gdt - 1 + dd gdt + +; TSS +align 4 +tss_entry: + dd 0 ; Previous TSS + dd STACK_POINTER ; esp0 + dd gdt_data - gdt ; ss0 (data offset) + dd 0 ; esp1 + dd 0 ; ss1 + dd 0 ; esp2 + dd 0 ; ss2 + dd 0 ; cr3 + dd 0 ; eip + dd 0 ; eflags + dd 0 ; eax + dd 0 ; ecx + dd 0 ; edx + dd 0 ; ebx + dd 0 ; esp + dd 0 ; ebp + dd 0 ; esi + dd 0 ; edi + dd 0 ; es + dd 0 ; cs + dd 0 ; ss + dd 0 ; ds + dd 0 ; fs + dd 0 ; gs + dd 0 ; ldt + dw 0 ; trap + dw -1 ; iomap base +tss_entry_end: + +times 1024 - ($ - $$) db 0 + +; Start at LBA 2 +superblock: |