diff options
-rw-r--r-- | .github/workflows/build.yml | 29 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | Makefile | 29 | ||||
-rw-r--r-- | README.md | 34 | ||||
-rw-r--r-- | entry.asm | 564 | ||||
-rw-r--r-- | example.c | 78 | ||||
-rw-r--r-- | load.c | 851 | ||||
-rwxr-xr-x | run | 138 |
9 files changed, 1748 insertions, 0 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d674b81 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,29 @@ +name: Project build and test + +on: push + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install + run: sudo apt-get update && sudo apt-get install -y build-essential bison flex libgmp3-dev libmpc-dev libmpfr-dev texinfo curl nasm grub-common qemu qemu-kvm mtools + - name: Get cross compiler + id: cache-cross + uses: actions/cache@v1 + with: + path: cross + key: toller-compiler + - name: Build cross compiler + if: steps.cache-cross.outputs.cache-hit != 'true' + run: sh run cross -y + - name: Build + run: sh run build -y + - name: Upload as artifact + uses: actions/upload-artifact@v2 + with: + name: disk-img + path: build/disk.img diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29e044f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +cross/ + +*.o @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Marvin Borner + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d0ddc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +# MIT License, Copyright (c) 2021 Marvin Borner + +CC = $(PWD)/cross/opt/bin/i686-elf-gcc +LD = $(PWD)/cross/opt/bin/i686-elf-ld +OC = $(PWD)/cross/opt/bin/i686-elf-objcopy +ST = $(PWD)/cross/opt/bin/i686-elf-strip +AS = nasm + +BUILD = $(PWD)/build/ + +CFLAGS = -Wall -Wextra -Werror -std=c99 -m32 -nostdlib -nostdinc -fno-builtin -fno-profile-generate -fno-omit-frame-pointer -fno-common -ffreestanding -Ofast + +ASFLAGS = -f elf32 + +all: bootloader example + +bootloader: + @mkdir -p $(BUILD) + @$(CC) -c $(CFLAGS) load.c -o $(BUILD)/load.o + @$(LD) -N -emain -Ttext 0x00009000 -o $(BUILD)/load.bin $(BUILD)/load.o --oformat binary + @$(AS) -f bin entry.asm -o $(BUILD)/boot.bin + +example: + @mkdir -p $(BUILD) + @$(CC) -c $(CFLAGS) example.c -o $(BUILD)/example.o + @$(LD) -N -ekernel_main -o $(BUILD)/example $(BUILD)/example.o + +clean: + @find . \( -name "*.o" -or -name "*.a" -or -name "*.elf" -or -name "*.bin" \) -type f -delete diff --git a/README.md b/README.md new file mode 100644 index 0000000..76d8dfd --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# extboot + +This repository is the home of my self-written i386 bootloader I initially created for my hobby operating system [Melvix](https://github.com/marvinborner/Melvix) (it uses other bootloaders nowadays). It supports ATA disk detection, EXT2 loading, basic ELF parsing/mapping and much more! + +## Disclaimer + +This bootloader will **not** work with any common operating system. Its main usecase is the booting of custom **hobby** operating systems. Even then, this bootloader doesn't support modern hardware features (AHCI, XHCI, APIC, ACPI, UEFI, ...). If you seek a bootloader supporting Linux, Windows or in general modern hardware, you may find Limine, Grub and similar alternatives more interesting. + +## Example usage + +This repository includes a very basic example. You can find a more advanced implementation [here (Melvix snapshot)](https://github.com/marvinborner/Melvix/tree/895a58b1b57a0ae8028576404a90f12e0133cf5f). You'll probably want to use GNU/Linux or OpenBSD if you want to try/build this example. + +### Test prebuilt by CI + +- Install the QEMU i386 emulator +- Download the `disk-img` artifact from [GitHub Workflow build](https://github.com/marvinborner/extboot/actions) +- Unzip `disk-img.zip` +- Run `qemu-system-i386 -serial stdio -m 256M -vga std -drive file=disk.img,format=raw,index=1,media=disk` + +### Build + +You can build and run it in the virtual machine QEMU using `./run example` (this will compile a 32 bit x86 cross compiler first and will take some time). You'll need the following build dependencies: + +- General template: `[pkg_manager] [install] git binutils gcc make bison flex gmp mpc mpfr texinfo curl nasm qemu` (package names may vary depending on your operating system) +- Ubuntu/Debian: `apt-get install -y build-essential bison flex libgmp3-dev libmpc-dev libmpfr-dev texinfo curl nasm grub-common qemu qemu-kvm mtools` +- OpenBSD: `pkg_add git gcc g++ gmake bison gmp libmpc mpfr texinfo curl nasm qemu e2fsprogs` + +## Execution sequence + +The bootloader starts in the first stage at `0x7c00` - which is the location of the MBR - by clearing the screen, finding the disk, checking for basic addressing support and finally loading the second stage. The first stage has a max size of 512 bytes, thanks to some ancient BIOS standards. That's why not much is happening in here. + +The second stage searches and loads the third stage (`load.c`) using a very minimal ext2 implementation (still in assembly!). The second stage also loads the memory map and the VESA video map into memory and sets the highest available video mode. After the A20 line has been enabled and the GDT and TSS have been set, it's time to jump to the protected mode. After the computer arrived in protected mode, the third stage gets executed, with the memory and VESA map as its parameters. Finally. + +The third stage (`load.c`) installs a basic serial connection for logging purposes and probes ATA disk drives. After a suitable disk has been found, the third stage will load the kernel binary into memory using an EXT2 driver and basic ELF parsing/mapping. Ultimately, the bootloader will jump to the kernel. The rest is history. 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: diff --git a/example.c b/example.c new file mode 100644 index 0000000..15388be --- /dev/null +++ b/example.c @@ -0,0 +1,78 @@ +// MIT License, Copyright (c) 2021 Marvin Borner +// Example kernel + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; + +struct vid_info { + u32 mode; + u32 *vbe; +}; + +enum mmap_type { + MEMORY_AVAILABLE = 1, + MEMORY_RESERVED, + MEMORY_ACPI, + MEMORY_NVS, + MEMORY_DEFECT, + MEMORY_DISABLED +}; + +struct mmap_boot { + u32 lbase; + u32 hbase; + u32 lsize; + u32 hsize; + u32 type; + u32 acpi; +}; + +struct mem_info { + struct mmap_boot *start; + u32 *end; + u32 size; +}; + +struct boot_info { + struct vid_info *vid; + struct mem_info *mem; + u32 tss; + u32 drive; +}; + +struct vbe_basic { + u8 stuff1[16]; + u16 pitch; + u16 width; + u16 height; + u8 stuff2[18]; + u8 *fb; + u8 stuff3[212]; +}; + +int kernel_main(struct boot_info *boot); +int kernel_main(struct boot_info *boot) +{ + // Fill screen with rgba colors + u32 colors[7] = { 0xff0000, 0xffa500, 0xffff00, 0x008000, 0x0000ff, 0x4b0082, 0xee82ee }; + struct vbe_basic *vbe = (void *)boot->vid->vbe; + u8 *draw = vbe->fb; + for (u32 y = 0; y < vbe->height; y++) { + u32 color = colors[y / (vbe->height / 7)]; + for (u32 x = 0; x < vbe->width; x++) { + draw[4 * x] = color >> 0 & 0xff; + draw[4 * x + 1] = color >> 8 & 0xff; + draw[4 * x + 2] = color >> 16 & 0xff; + draw[4 * x + 3] = 0xff; // Alpha is always 0xff in this example + } + draw += vbe->pitch; + } + + // Idle + __asm__ volatile("cli"); + while (1) + __asm__ volatile("hlt"); + + return 1; +} @@ -0,0 +1,851 @@ +// MIT License, Copyright (c) 2021 Marvin Borner +// Independent ext2 loader - mostly copied from Melvix kernel + +/** + * Some general definitions + */ + +typedef signed char s8; +typedef unsigned char u8; + +typedef signed short s16; +typedef unsigned short u16; + +typedef signed int s32; +typedef unsigned int u32; + +#define NULL ((void *)0) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#define PACKED __attribute__((packed)) + +#define assert(exp) \ + if (!(exp)) { \ + print(__FILE__); \ + print(": "); \ + print(__func__); \ + print(": Bootloader assertion '"); \ + print(#exp); \ + print("' failed.\n"); \ + __asm__ volatile("cli\nhlt"); \ + } + +/** + * ATA numbers + */ + +#define BLOCK_SIZE 1024 +#define BLOCK_COUNT 256 // BLOCK_SIZE / sizeof(u32) +#define SECTOR_SIZE 512 +#define SECTOR_COUNT (BLOCK_SIZE / SECTOR_SIZE) + +#define ATA_PRIMARY_IO 0x1f0 +#define ATA_SECONDARY_IO 0x170 + +#define ATA_PRIMARY 0x00 +#define ATA_SECONDARY 0x01 +#define ATA_READ 0x00 +#define ATA_WRITE 0x013 +#define ATA_MASTER 0x00 +#define ATA_SLAVE 0x01 +#define ATA_SR_BSY 0x80 +#define ATA_SR_DRDY 0x40 +#define ATA_SR_DF 0x20 +#define ATA_SR_DSC 0x10 +#define ATA_SR_DRQ 0x08 +#define ATA_SR_CORR 0x04 +#define ATA_SR_IDX 0x02 +#define ATA_SR_ERR 0x01 +#define ATA_REG_DATA 0x00 +#define ATA_REG_ERROR 0x01 +#define ATA_REG_FEATURES 0x01 +#define ATA_REG_SECCOUNT0 0x02 +#define ATA_REG_LBA0 0x03 +#define ATA_REG_LBA1 0x04 +#define ATA_REG_LBA2 0x05 +#define ATA_REG_HDDEVSEL 0x06 +#define ATA_REG_COMMAND 0x07 +#define ATA_REG_STATUS 0x07 +#define ATA_REG_SECCOUNT1 0x08 +#define ATA_REG_LBA3 0x09 +#define ATA_REG_LBA4 0x0a +#define ATA_REG_LBA5 0x0b +#define ATA_REG_CONTROL 0x0c +#define ATA_REG_ALTSTATUS 0x0c +#define ATA_REG_DEVADDRESS 0x0d +#define ATA_CMD_READ_PIO 0x20 +#define ATA_CMD_READ_PIO_EXT 0x24 +#define ATA_CMD_READ_DMA 0xc8 +#define ATA_CMD_READ_DMA_EXT 0x25 +#define ATA_CMD_WRITE_PIO 0x30 +#define ATA_CMD_WRITE_PIO_EXT 0x34 +#define ATA_CMD_WRITE_DMA 0xca +#define ATA_CMD_WRITE_DMA_EXT 0x35 +#define ATA_CMD_CACHE_FLUSH 0xe7 +#define ATA_CMD_CACHE_FLUSH_EXT 0xea +#define ATA_CMD_PACKET 0xa0 +#define ATA_CMD_IDENTIFY_PACKET 0xa1 +#define ATA_CMD_IDENTIFY 0xec +#define ATA_IDENT_DEVICETYPE 0 +#define ATA_IDENT_CYLINDERS 2 +#define ATA_IDENT_HEADS 6 +#define ATA_IDENT_SECTORS 12 +#define ATA_IDENT_SERIAL 20 +#define ATA_IDENT_MODEL 54 +#define ATA_IDENT_CAPABILITIES 98 +#define ATA_IDENT_FIELDVALID 106 +#define ATA_IDENT_MAX_LBA 120 +#define ATA_IDENT_COMMANDSETS 164 +#define ATA_IDENT_MAX_LBA_EXT 200 + +/** + * ELF stuff + */ + +#define ELF_MAG0 0x7F +#define ELF_MAG1 'E' +#define ELF_MAG2 'L' +#define ELF_MAG3 'F' + +#define ELF_IDENT_COUNT 16 +#define ELF_IDENT_MAG0 0 +#define ELF_IDENT_MAG1 1 +#define ELF_IDENT_MAG2 2 +#define ELF_IDENT_MAG3 3 + +#define ELF_IDENT_CLASS 4 +#define ELF_IDENT_CLASS_NONE 0 +#define ELF_IDENT_CLASS_32 1 +#define ELF_IDENT_CLASS_64 2 + +#define ELF_IDENT_DATA 5 +#define ELF_IDENT_DATA_NONE 0 +#define ELF_IDENT_DATA_LSB 1 +#define ELF_IDENT_DATA_MSB 2 + +#define ELF_IDENT_VERSION 6 +#define ELF_IDENT_OSABI 7 +#define ELF_IDENT_ABIVERSION 8 +#define ELF_IDENT_PAD 9 + +#define ELF_ETYPE_NONE 0 +#define ELF_ETYPE_REL 1 +#define ELF_ETYPE_EXEC 2 +#define ELF_ETYPE_DYN 3 +#define ELF_ETYPE_CORE 4 +#define ELF_ETYPE_NUM 5 + +#define ELF_MACHINE_NONE 0 +#define ELF_MACHINE_SPARC 2 +#define ELF_MACHINE_386 3 +#define ELF_MACHINE_SPARC32PLUS 18 +#define ELF_MACHINE_SPARCV9 43 +#define ELF_MACHINE_AMD64 62 + +#define ELF_PROGRAM_TYPE_NULL 0 +#define ELF_PROGRAM_TYPE_LOAD 1 +#define ELF_PROGRAM_TYPE_DYNAMIC 2 +#define ELF_PROGRAM_TYPE_INTERP 3 +#define ELF_PROGRAM_TYPE_NOTE 4 +#define ELF_PROGRAM_TYPE_SHLIB 5 +#define ELF_PROGRAM_TYPE_PHDR 6 +#define ELF_PROGRAM_TYPE_TLS 7 + +#define ELF_PROGRAM_FLAG_X 0x1 +#define ELF_PROGRAM_FLAG_W 0x2 +#define ELF_PROGRAM_FLAG_R 0x4 + +#define ELF_SECTION_TYPE_NULL 0 +#define ELF_SECTION_TYPE_PROGBITS 1 +#define ELF_SECTION_TYPE_SYMTAB 2 +#define ELF_SECTION_TYPE_STRTAB 3 +#define ELF_SECTION_TYPE_RELA 4 +#define ELF_SECTION_TYPE_HASH 5 +#define ELF_SECTION_TYPE_DYNAMIC 6 +#define ELF_SECTION_TYPE_NOTE 7 +#define ELF_SECTION_TYPE_NOBITS 8 +#define ELF_SECTION_TYPE_REL 9 +#define ELF_SECTION_TYPE_SHLIB 10 +#define ELF_SECTION_TYPE_DYNSYM 11 +#define ELF_SECTION_TYPE_COUNT 12 + +#define ELF_SECTION_FLAG_WRITE 0x1 +#define ELF_SECTION_FLAG_ALLOC 0x2 +#define ELF_SECTION_FLAG_EXEC 0x3 +#define ELF_SECTION_FLAG_MERGE 0x10 +#define ELF_SECTION_FLAG_STRINGS 0x20 +#define ELF_SECTION_FLAG_INFO_LINK 0x40 +#define ELF_SECTION_FLAG_LINK_ORDER 0x80 +#define ELF_SECTION_FLAG_OS_SPECIAL 0x100 +#define ELF_SECTION_FLAG_GROUP 0x200 +#define ELF_SECTION_FLAG_TLS 0x400 +#define ELF_SECTION_FLAG_COMPRESSED 0x800 + +#define ELF_BSS ".bss" +#define ELF_DATA ".data" +#define ELF_DEBUG ".debug" +#define ELF_DYNAMIC ".dynamic" +#define ELF_DYNSTR ".dynstr" +#define ELF_DYNSYM ".dynsym" +#define ELF_FINI ".fini" +#define ELF_GOT ".got" +#define ELF_HASH ".hash" +#define ELF_INIT ".init" +#define ELF_REL_DATA ".rel.data" +#define ELF_REL_FINI ".rel.fini" +#define ELF_REL_INIT ".rel.init" +#define ELF_REL_DYN ".rel.dyn" +#define ELF_REL_RODATA ".rel.rodata" +#define ELF_REL_TEXT ".rel.text" +#define ELF_RODATA ".rodata" +#define ELF_SHSTRTAB ".shstrtab" +#define ELF_STRTAB ".strtab" +#define ELF_SYMTAB ".symtab" +#define ELF_TEXT ".text" + +struct PACKED elf_header { + u8 ident[ELF_IDENT_COUNT]; + u16 type; + u16 machine; + u32 version; + u32 entry; + u32 phoff; + u32 shoff; + u32 flags; + u16 ehsize; + u16 phentsize; + u16 phnum; + u16 shentsize; + u16 shnum; + u16 shstrndx; +}; + +struct PACKED elf_program { + u32 type; + u32 offset; + u32 vaddr; + u32 paddr; + u32 filesz; + u32 memsz; + u32 flags; + u32 align; +}; + +struct PACKED elf_section { + u32 name; + u32 type; + u32 flags; + u32 addr; + u32 offset; + u32 size; + u32 link; + u32 info; + u32 addralign; + u32 entsize; +}; + +struct PACKED elf_symbol { + u32 name; + u32 value; + u32 size; + u8 info; + u8 other; + u16 shndx; +}; + +/** + * EXT2 numbers/structs + */ + +#define EXT2_BOOT 0 +#define EXT2_SUPER 1 +#define EXT2_ROOT 2 +#define EXT2_MAGIC 0x0000EF53 + +struct ext2_superblock { + u32 total_inodes; + u32 total_blocks; + u32 su_res_blocks; // Superuser reserved + u32 free_blocks; + u32 free_inodes; + u32 superblock_block_num; + u32 log2_block_size; + u32 log2_frag_size; + u32 blocks_per_group; + u32 frags_per_group; + u32 inodes_per_group; + u32 last_mount_time; + u32 last_write_time; + u16 mounts_since_fsck; + u16 max_mounts_since_fsck; + u16 magic; + u16 state; // 1 clean; 2 errors + u16 error_action; + u16 minor_version; + u32 last_fsck_time; + u32 max_time_since_fsck; + u32 creator_os_id; + u32 major_version; + u16 res_block_uid; + u16 res_block_gid; +}; + +struct ext2_bgd { + u32 block_bitmap; + u32 inode_bitmap; + u32 inode_table; + u16 free_blocks; + u16 free_inodes; + u16 used_dirs; + u16 pad; + u8 bg_reserved[12]; +}; + +struct ext2_inode { + u16 mode; + u16 uid; + u32 size; + + u32 last_access_time; + u32 creation_time; + u32 last_modification_time; + u32 deletion_time; + + u16 gid; + u16 link_count; + u32 blocks; + u32 flags; + u32 os_specific_val1; + u32 block[15]; + u32 generation; + + u32 reserved1; + u32 reserved2; + + u32 fragment_addr; + u8 os_specific_val2[12]; +}; + +#define EXT2_INODE_SIZE (sizeof(struct ext2_inode)) + +struct ext2_dirent { + u32 inode_num; + u16 total_len; + u8 name_len; + u8 type_indicator; + u8 name[]; +}; + +struct ext2_file { + struct ext2_inode inode; + u32 pos; + u8 block_index; + u8 *buf; + u32 curr_block_pos; +}; + +/** + * Memory + */ + +static u32 heap = 0x0000c000; + +static void *memcpy(void *dest, const void *src, u32 n) +{ + // Inspired by jgraef at osdev + u32 num_dwords = n / 4; + u32 num_bytes = n % 4; + u32 *dest32 = (u32 *)dest; + const u32 *src32 = (const u32 *)src; + u8 *dest8 = ((u8 *)dest) + num_dwords * 4; + const u8 *src8 = ((const u8 *)src) + num_dwords * 4; + + __asm__ volatile("rep movsl\n" + : "=S"(src32), "=D"(dest32), "=c"(num_dwords) + : "S"(src32), "D"(dest32), "c"(num_dwords) + : "memory"); + + for (u32 i = 0; i < num_bytes; i++) + dest8[i] = src8[i]; + + return dest; +} + +static void *memset(void *dest, u32 val, u32 n) +{ + // Inspired by jgraef at osdev + u32 uval = val; + u32 num_dwords = n / 4; + u32 num_bytes = n % 4; + u32 *dest32 = (u32 *)dest; + u8 *dest8 = ((u8 *)dest) + num_dwords * 4; + u8 val8 = (u8)val; + u32 val32 = uval | (uval << 8) | (uval << 16) | (uval << 24); + + __asm__ volatile("rep stosl\n" + : "=D"(dest32), "=c"(num_dwords) + : "D"(dest32), "c"(num_dwords), "a"(val32) + : "memory"); + + for (u32 i = 0; i < num_bytes; i++) + dest8[i] = val8; + + return dest; +} + +static void *malloc(u32 size) +{ + return (u32 *)(heap += size); +} + +static void *zalloc(u32 size) +{ + void *ret = malloc(size); + memset(ret, 0, size); + return ret; +} + +static void free(void *ptr) +{ + (void)ptr; +} + +/** + * String + */ + +static u32 strlen(const char *str) +{ + const char *s = str; + while (*s) + s++; + return s - str; +} + +static s32 strncmp(const char *s1, const char *s2, u32 n) +{ + const u8 *c1 = (const u8 *)s1; + const u8 *c2 = (const u8 *)s2; + u8 ch; + int d = 0; + + while (n--) { + d = (int)(ch = *c1++) - (int)*c2++; + if (d || !ch) + break; + } + + return d; +} + +static char *strdup(const char *s) +{ + int l = strlen(s) + 1; + char *d = malloc(l); + + memcpy(d, s, l); + + return d; +} + +/** + * CPU IO + */ + +static u8 inb(u16 port) +{ + u8 value; + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static u16 inw(u16 port) +{ + u16 value; + __asm__ volatile("inw %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static void outb(u16 port, u8 data) +{ + __asm__ volatile("outb %0, %1" ::"a"(data), "Nd"(port)); +} + +/** + * Serial + */ + +static void serial_install(void) +{ + outb(0x3f8 + 1, 0x00); + outb(0x3f8 + 3, 0x80); + outb(0x3f8 + 0, 0x03); + outb(0x3f8 + 1, 0x00); + outb(0x3f8 + 3, 0x03); + outb(0x3f8 + 2, 0xC7); + outb(0x3f8 + 4, 0x0B); +} + +static int is_transmit_empty(void) +{ + return inb(0x3f8 + 5) & 0x20; +} + +static void serial_put(char ch) +{ + while (is_transmit_empty() == 0) + ; + outb(0x3f8, (u8)ch); +} + +static void print(const char *data) +{ + for (u32 i = 0; i < strlen(data); i++) + serial_put(data[i]); +} + +/** + * IDE/ATA + */ + +static void ide_delay(u16 io) // 400ns +{ + for (int i = 0; i < 4; i++) + inb(io + ATA_REG_ALTSTATUS); +} + +static void ide_poll(u16 io) +{ + for (int i = 0; i < 4; i++) + inb(io + ATA_REG_ALTSTATUS); + + u8 status; + do { + status = inb(io + ATA_REG_STATUS); + } while (status & ATA_SR_BSY); + + do { + status = inb(io + ATA_REG_STATUS); + /* assert(!(status & ATA_SR_ERR)) */ + } while (!(status & ATA_SR_DRQ)); +} + +static u8 ata_read_one(u8 *buf, u32 lba, u8 drive) +{ + u16 io = (drive & ATA_PRIMARY << 1) == ATA_PRIMARY ? ATA_PRIMARY_IO : ATA_SECONDARY_IO; + drive = (drive & ATA_SLAVE) == ATA_SLAVE ? ATA_SLAVE : ATA_MASTER; + u8 cmd = drive == ATA_MASTER ? 0xe0 : 0xf0; + outb(io + ATA_REG_HDDEVSEL, (cmd | (u8)((lba >> 24 & 0x0f)))); + outb(io + 1, 0x00); + outb(io + ATA_REG_SECCOUNT0, 1); + outb(io + ATA_REG_LBA0, (u8)lba); + outb(io + ATA_REG_LBA1, (u8)(lba >> 8)); + outb(io + ATA_REG_LBA2, (u8)(lba >> 16)); + outb(io + ATA_REG_COMMAND, ATA_CMD_READ_PIO); + ide_poll(io); + + for (int i = 0; i < BLOCK_COUNT; i++) { + u16 data = inw(io + ATA_REG_DATA); + *(u16 *)(buf + i * 2) = data; + } + ide_delay(io); + return 1; +} + +static u32 ata_read(void *buf, u32 lba, u32 sector_count, u8 drive) +{ + u8 *b = buf; // I love bytes, yk + for (u32 i = 0; i < sector_count; i++) { + ata_read_one(b, lba + i, drive); + b += SECTOR_SIZE; + } + return sector_count; +} + +/** + * EXT2 + */ + +static void *buffer_read(u32 block, u8 drive) +{ + void *buf = zalloc(BLOCK_SIZE); + ata_read(buf, block * SECTOR_COUNT, SECTOR_COUNT, drive); + return buf; +} + +static struct ext2_superblock *get_superblock(u8 drive) +{ + struct ext2_superblock *sb = buffer_read(EXT2_SUPER, drive); + + assert(sb->magic == EXT2_MAGIC); + return sb; +} + +static struct ext2_bgd *get_bgd(u8 drive) +{ + return buffer_read(EXT2_SUPER + 1, drive); +} + +static struct ext2_inode *get_inode(u32 i, struct ext2_inode *in_buf, u8 drive) +{ + struct ext2_superblock *s = get_superblock(drive); + assert(s); + struct ext2_bgd *b = get_bgd(drive); + assert(b); + + u32 block_group = (i - 1) / s->inodes_per_group; + u32 index = (i - 1) % s->inodes_per_group; + u32 block = (index * EXT2_INODE_SIZE) / BLOCK_SIZE; + b += block_group; + + u32 *buf = buffer_read(b->inode_table + block, drive); + struct ext2_inode *in = + (struct ext2_inode *)((u32)buf + + (index % (BLOCK_SIZE / EXT2_INODE_SIZE)) * EXT2_INODE_SIZE); + + memcpy(in_buf, in, sizeof(*in_buf)); + free(buf); + free(s); + free(b - block_group); + + return in_buf; +} + +static u32 find_inode(const char *name, u32 dir_inode, u8 drive) +{ + if (!dir_inode) + return (unsigned)-1; + + struct ext2_inode i = { 0 }; + get_inode(dir_inode, &i, drive); + + char *buf = malloc(BLOCK_SIZE * i.blocks / 2); + memset(buf, 0, BLOCK_SIZE * i.blocks / 2); + + for (u32 q = 0; q < i.blocks / 2; q++) { + char *data = buffer_read(i.block[q], drive); + memcpy((u32 *)((u32)buf + q * BLOCK_SIZE), data, BLOCK_SIZE); + free(data); + } + + struct ext2_dirent *d = (struct ext2_dirent *)buf; + + u32 sum = 0; + do { + // Calculate the 4byte aligned size of each entry + sum += d->total_len; + if (strlen(name) == d->name_len && + strncmp((void *)d->name, name, d->name_len) == 0) { + free(buf); + return d->inode_num; + } + d = (struct ext2_dirent *)((u32)d + d->total_len); + + } while (sum < (1024 * i.blocks / 2)); + free(buf); + return (unsigned)-1; +} + +static u32 read_indirect(u32 indirect, u32 block_num, u8 drive) +{ + void *data = buffer_read(indirect, drive); + u32 ind = *(u32 *)((u32)data + block_num * sizeof(u32)); + free(data); + return ind; +} + +static s32 read_inode(struct ext2_inode *in, void *buf, u32 offset, u32 count, u8 drive) +{ + if (!in || !buf) + return -1; + + if (in->size == 0) + return 0; + + u32 num_blocks = in->blocks / (BLOCK_SIZE / SECTOR_SIZE) + 1; + + if (!num_blocks) + return -1; + + u32 first_block = offset / BLOCK_SIZE; + u32 last_block = (offset + count) / BLOCK_SIZE; + if (last_block >= num_blocks) + last_block = num_blocks - 1; + u32 first_block_offset = offset % BLOCK_SIZE; + + u32 remaining = MIN(count, in->size - offset); + u32 copied = 0; + + u32 indirect = 0; + u32 blocknum = 0; + + // TODO: Support triply indirect pointers + for (u32 i = first_block; i <= last_block; i++) { + if (i < 12) { + blocknum = in->block[i]; + } else if (i < BLOCK_COUNT + 12) { + indirect = in->block[12]; + blocknum = read_indirect(indirect, i - 12, drive); + } else { + indirect = in->block[13]; + blocknum = read_indirect(indirect, (i - (BLOCK_COUNT + 12)) / BLOCK_COUNT, + drive); + blocknum = read_indirect(blocknum, (i - (BLOCK_COUNT + 12)) % BLOCK_COUNT, + drive); + } + + char *data = buffer_read(blocknum, drive); + u32 block_offset = (i == first_block) ? first_block_offset : 0; + u32 byte_count = MIN(BLOCK_SIZE - block_offset, remaining); + + memcpy((u8 *)buf + copied, data + block_offset, byte_count); + + copied += byte_count; + remaining -= byte_count; + + free(data); + } + + return copied; +} + +static struct ext2_inode *find_inode_by_path(const char *path, struct ext2_inode *in_buf, u8 drive) +{ + char *path_cp = strdup(path); + char *init = path_cp; // For freeing + + if (path_cp[0] != '/') { + free(init); + return NULL; + } + + path_cp++; + u32 current_inode = EXT2_ROOT; + + u32 i = 0; + while (1) { + for (i = 0; path_cp[i] != '/' && path_cp[i] != '\0'; i++) + ; + + if (path_cp[i] == '\0') + break; + + path_cp[i] = '\0'; + current_inode = find_inode(path_cp, current_inode, drive); + path_cp[i] = '/'; + + if (current_inode == 0) { + free(init); + return NULL; + } + + path_cp += i + 1; + } + + u32 inode = find_inode(path_cp, current_inode, drive); + free(init); + if ((signed)inode <= 0) + return NULL; + + return get_inode(inode, in_buf, drive); +} + +static s32 read(const char *path, void *buf, u32 offset, u32 count, u8 drive) +{ + struct ext2_inode in = { 0 }; + if (find_inode_by_path(path, &in, drive) == &in) { + return read_inode(&in, buf, offset, count, drive); + } else { + print("Couldn't find kernel!\n"); + return -1; + } +} + +/** + * ELF + */ + +static s32 elf_load(const char *path, u8 drive) +{ + struct elf_header header = { 0 }; + s32 rd = read(path, &header, 0, sizeof(header), drive); + if (rd < 0) + return rd; + if (rd != sizeof(header)) + return -1; + + // Valid? + u8 *magic = header.ident; + u8 valid_magic = magic[ELF_IDENT_MAG0] == ELF_MAG0 && magic[ELF_IDENT_MAG1] == ELF_MAG1 && + magic[ELF_IDENT_MAG2] == ELF_MAG2 && magic[ELF_IDENT_MAG3] == ELF_MAG3 && + magic[ELF_IDENT_CLASS] == ELF_IDENT_CLASS_32 && + magic[ELF_IDENT_DATA] == ELF_IDENT_DATA_LSB; + if (!valid_magic || (header.type != ELF_ETYPE_REL && header.type != ELF_ETYPE_EXEC) || + header.version != 1 || header.machine != ELF_MACHINE_386) + return -1; + + // Loop through programs + for (u32 i = 0; i < header.phnum; i++) { + struct elf_program program = { 0 }; + + if (read(path, &program, header.phoff + header.phentsize * i, sizeof(program), + drive) != sizeof(program)) + return -1; + + if (program.type == ELF_PROGRAM_TYPE_INTERP) + return -1; + + if (program.vaddr == 0 || program.type != ELF_PROGRAM_TYPE_LOAD) + continue; + + if ((u32)read(path, (void *)program.vaddr, program.offset, program.filesz, drive) != + program.filesz) + return -1; + } + + // Find section string table + struct elf_section section_strings = { 0 }; + if (read(path, §ion_strings, header.shoff + header.shentsize * header.shstrndx, + sizeof(section_strings), drive) != sizeof(section_strings)) + return -1; + + if (section_strings.type != ELF_SECTION_TYPE_STRTAB) + return -1; + + return header.entry; +} + +/** + * Let's go! + */ + +struct boot_info { + u32 vid; + u32 mem; + u32 tss; + u32 drive; +}; + +int main(struct boot_info *boot) +{ + serial_install(); + print("Loaded bootloader!\n"); + + assert(boot->drive); + + s32 elf = elf_load("/kernel", boot->drive); + assert(elf > 0); + + void (*kernel)(void *); + *(void **)(&kernel) = (void *)elf; + + print("Loaded kernel!\n"); + kernel(boot); + + print("WTF, kernel returned!\n"); + + while (1) + ; + + return 0; +} @@ -0,0 +1,138 @@ +#!/usr/bin/env sh + +set -e + +cd "$(dirname "$0")" + +MAKE=make +NPROC=nproc +SUDO=sudo +TAGS=ctags +if [ "$(uname -s)" = "OpenBSD" ]; then + NPROC="sysctl -n hw.ncpuonline" + SUDO="doas" + TAGS="ectags" + export MAKE=gmake + export CC="egcc" + export CXX="eg++" + export LDFLAGS=-Wl,-z,notext +fi + +mode="${1}" +no_ask="${2}" + +make_cross() { + if [ ! -d "./cross/" ]; then + if [ "$no_ask" != "-y" ]; then + echo -n "Do you want to compile a cross compiler (this can take around 20 minutes)? [yn] " + read -r answer + if ! [ "$answer" != "${answer#[Yy]}" ]; then + echo "The compilation of melvix requires a cross compiler!" + exit 1 + fi + fi + + # Create directory + mkdir -p cross + cd cross + DIR=$(pwd) + + # Get sources + mkdir "${DIR}/src" && cd "${DIR}/src" + echo "Downloading..." + curl "https://ftp.gnu.org/gnu/binutils/binutils-2.33.1.tar.gz" >binutils.tar.gz + tar xzf binutils.tar.gz + curl "https://ftp.gnu.org/gnu/gcc/gcc-9.2.0/gcc-9.2.0.tar.gz" >gcc.tar.gz + tar xzf gcc.tar.gz + + # Prepare compiling + mkdir -p "${DIR}/opt/bin" + export PREFIX="${DIR}/opt" + export TARGET=i686-elf + export PATH="$PREFIX/bin:$PATH" + + if [ "$(uname -s)" = "OpenBSD" ]; then + export with_gmp=/usr/local + sed -i 's/-no-pie/-nopie/g' "${DIR}/src/gcc-9.2.0/gcc/configure" + fi + + # Compile binutils + mkdir "${DIR}/src/build-binutils" && cd "${DIR}/src/build-binutils" + ../binutils-2.33.1/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot --disable-nls --disable-werror + $MAKE -j $($NPROC) + $MAKE install + + # Compile GCC + mkdir "${DIR}/src/build-gcc" && cd "${DIR}/src/build-gcc" + ../gcc-9.2.0/configure --target="$TARGET" --prefix="$PREFIX" --disable-nls --enable-languages=c --without-headers + $MAKE -j $($NPROC) all-gcc all-target-libgcc + $MAKE install-gcc install-target-libgcc + + # Fix things + if [ "$(uname -s)" = "OpenBSD" ]; then + cd "${DIR}/opt/libexec/gcc/i686-elf/9.2.0/" && ln -sf liblto_plugin.so.0.0 liblto_plugin.so + fi + + cd "${DIR}/.." + fi +} + +make_build() { + mkdir -p build/ + + # Build + printf "\nBuilding...\n" + $MAKE -j $($NPROC) + + # Create disk image + dd if=/dev/zero of=build/disk.img bs=1k count=32k status=none + if [ "$(uname -s)" = "OpenBSD" ]; then + VND=$($SUDO vnconfig build/disk.img) + ( + echo "e 0" + echo 83 + echo n + echo 0 + echo "*" + echo "quit" + ) | $SUDO fdisk -e $VND >/dev/null + $SUDO mkfs.ext2 -F /dev/${VND}i >/dev/null + $SUDO dd if=build/boot.bin of=/dev/${VND}i conv=notrunc status=none + else + $SUDO mke2fs -b 1024 -q build/disk.img + dd if=build/boot.bin of=build/disk.img conv=notrunc status=none + fi + + # Mount disk and copy files + mkdir -p mnt/ + if [ "$(uname -s)" = "OpenBSD" ]; then + $SUDO mount -t ext2fs /dev/${VND}i mnt/ + else + $SUDO mount build/disk.img mnt/ + fi + $SUDO cp -r build/load.bin mnt/load + $SUDO cp -r build/example mnt/kernel + $SUDO umount mnt/ + rm -rf mnt/ + + if [ "$(uname -s)" = "OpenBSD" ]; then + $SUDO vnconfig -u $VND + fi +} + +make_example() { + qemu-system-i386 -d guest_errors -cpu max -serial stdio -m 256M -vga std -drive file=build/disk.img,format=raw,index=1,media=disk +} + +if [ "${mode}" = "cross" ]; then + make_cross +elif [ "${mode}" = "build" ]; then + make_cross + $MAKE clean + make_build +elif [ "${mode}" = "example" ]; then + make_cross + $MAKE clean + make_build + make_example +fi |