diff options
author | Marvin Borner | 2021-07-04 21:31:28 +0200 |
---|---|---|
committer | Marvin Borner | 2021-07-04 21:34:15 +0200 |
commit | 9b8698769535846d029c44247956eed9a21f1185 (patch) | |
tree | 294a17af4102805ab9863274339e8e030897804e |
Initial commit
-rw-r--r-- | .github/workflows/build.yml | 29 | ||||
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | makefile | 43 | ||||
-rwxr-xr-x | run | 102 | ||||
-rw-r--r-- | src/entry/bootsector.asm | 172 | ||||
-rw-r--r-- | src/entry/definitions.asm | 26 | ||||
-rw-r--r-- | src/loader/cpu.c | 26 | ||||
-rw-r--r-- | src/loader/inc/cpu.h | 13 | ||||
-rw-r--r-- | src/loader/inc/def.h | 28 | ||||
-rw-r--r-- | src/loader/inc/lib.h | 14 | ||||
-rw-r--r-- | src/loader/inc/log.h | 11 | ||||
-rw-r--r-- | src/loader/lib.c | 85 | ||||
-rw-r--r-- | src/loader/link.ld | 35 | ||||
-rw-r--r-- | src/loader/log.c | 108 | ||||
-rw-r--r-- | src/loader/main.c | 19 |
16 files changed, 738 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..e81e302 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +cross/ +build/ + +*.o +*.bin +*.img @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 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..62acb87 --- /dev/null +++ b/makefile @@ -0,0 +1,43 @@ +# MIT License, Copyright (c) 2021 Marvin Borner + +# Obviously needs cross compiler +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 + +BLD = $(PWD)/build/ +SRC = $(PWD)/src/ + +SRCS = $(wildcard $(SRC)/loader/*.c) +OBJS = $(patsubst $(SRC)/%.c,$(BLD)/%.o,$(SRCS)) + +# Enable many warnings for less bugs :) +WARNINGS = -Wall -Wextra -Wshadow -Wpointer-arith -Wwrite-strings -Wredundant-decls -Wnested-externs -Wformat=2 -Wmissing-declarations -Wstrict-prototypes -Wmissing-prototypes -Wcast-qual -Wswitch-default -Wswitch-enum -Wunreachable-code -Wundef -Wold-style-definition -Wvla -pedantic-errors + +# Disable some GCC features and boilerplate generation +CFLAGS = $(WARNINGS) -std=c99 -m32 -nostdlib -nostdinc -ffunction-sections -fno-builtin -fno-profile-generate -fno-omit-frame-pointer -fno-common -fno-asynchronous-unwind-tables -fno-stack-protector -fno-pie -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -Ofast -ffreestanding -Wl,-estart -I$(SRC)/loader/inc/ + +ASFLAGS = -f elf32 + +all: dir $(BLD)/boot.bin + +dir: + @mkdir -p $(BLD)/entry/ + @mkdir -p $(BLD)/loader/ + +$(BLD)/boot.bin: $(BLD)/loader.bin + @$(AS) -f bin $(SRC)/entry/bootsector.asm -o $@ + +$(BLD)/loader.o: $(OBJS) + @$(LD) -N -z max-page-size=0x1000 -estart -T$(SRC)/loader/link.ld -o $@ $^ + +$(BLD)/loader.bin: $(BLD)/loader.o + @$(OC) -O binary $^ $@ + +$(OBJS): $(BLD)/%.o : $(SRC)/%.c + @$(CC) -c $(CFLAGS) $< -o $@ + +clean: + @rm -rf $(BLD)/* @@ -0,0 +1,102 @@ +#!/usr/bin/env sh +# MIT License, Copyright (c) 2021 Marvin Borner + +set -e + +cd "$(dirname "$0")" + +MAKE=make +NPROC=nproc +SUDO=sudo +TAGS=ctags + +mode="${1}" +no_ask="${2}" + +build_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.36.1.tar.gz" >binutils.tar.gz + tar xzf binutils.tar.gz + curl "https://ftp.gnu.org/gnu/gcc/gcc-11.1.0/gcc-11.1.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" + + # Compile binutils + mkdir "${DIR}/src/build-binutils" && cd "${DIR}/src/build-binutils" + ../binutils-2.36.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-11.1.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 + + cd "${DIR}/.." + fi +} + +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 + DEV=$($SUDO losetup --find --partscan --show build/disk.img) + PART="p1" + $SUDO parted -s "$DEV" mklabel msdos mkpart primary ext2 32k 100% -a minimal set 1 boot on + $SUDO mke2fs -b 1024 -q "$DEV$PART" + $SUDO dd if=build/boot.bin of="$DEV" conv=notrunc status=none + + # Mount disk and copy files + #mkdir -p mnt/ + #$SUDO mount "$DEV$PART" mnt/ + #$SUDO mkdir -p mnt/boot/ + #$SUDO umount mnt/ || (sync && $SUDO umount mnt/) + #rm -rf mnt/ + + $SUDO losetup -d "$DEV" +} + +emulate() { + 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 + build_cross +elif [ "${mode}" = "build" ]; then + build_cross + $MAKE clean + build +else + build_cross + $MAKE clean + build + emulate +fi diff --git a/src/entry/bootsector.asm b/src/entry/bootsector.asm new file mode 100644 index 0000000..1beb850 --- /dev/null +++ b/src/entry/bootsector.asm @@ -0,0 +1,172 @@ +; MIT License, Copyright (c) 2021 Marvin Borner + +; This file includes definitions to reduce magic numbers +%include "src/entry/definitions.asm" + +bits 16 ; Real mode is 16 Bit +org LOCATION ; Bootsector location + +global _start +_start: + jmp .skip_bpb ; Some BIOSes override the BPB area + dd "START" + nop + times 87 db 0 ; Fill BPB area with 0 +.skip_bpb: + + ; Clear registers (some BIOSes are weird) + xor bx, bx + mov ds, bx + mov es, bx + mov ss, bx + ; Other registers may contain relevant data + + mov sp, LOCATION ; Set stack (top) pointer - grows downwards + + mov ax, SCREEN_CLEAR ; Clear screen command + int SCREEN_INT ; CLEAR! + + ; Print friendly welcome message + mov si, hello_msg + call print + + call disk_support ; Is disk supported? Implicitly verifies that this is a 386+ architecture + mov esp, LOCATION ; Clear upper 16 Bit of esp, now that 16 Bit support is guaranteed + cli ; Disable interrupts + + jmp load_stage ; Load and execute main stage + +; Print function uses si as string pointer +print: + ; a and b regs get partially destroyed - push for restoration + push bx + push ax + + mov ah, SCREEN_OUT ; Set VGA command + xor bh, bh ; Clear b register (according to BIOS spec) +.putch: + lodsb ; Load next string byte (using ds:si) + test al, al ; Test loaded byte + jz .end ; End if al is zero (using previous test); NULL-termination + int SCREEN_INT ; WRITE! + jmp .putch ; Continue +.end: + ; Restore ax/bx + pop ax + pop bx + ret + +; Check if disk is supported using ID checks and LBA test +disk_support: + ; BIOS puts the disk id into dl + cmp dl, 0x80 + jb disk_error ; Error if below 0x80 - probably floppy disk + cmp dl, 0x8f + ja disk_error ; Error if above 0x8f - invalid + + ; Check if int 0x13 and LBA are supported + mov ah, DISK_EXT_CHECK ; Set needed interrupt values + mov bx, DISK_EXT_CHECK_REQ + int DISK_INT ; CHECK! + jc disk_error ; Carry means something went wrong + cmp bx, DISK_EXT_CHECK_RESP + jne disk_error ; Response is incorrect => error! + ret + +; Read sectors from disk using dap information +disk_read: + mov si, dap + mov ah, DISK_READ + int DISK_INT + jc error_loop + ret + +; Loads the main stage (main.c) +load_stage: + mov bx, loader + mov [dap.dest], bx + call disk_read + + lgdt [gdt] ; Load GDT + + ; Set protected mode (32 Bit mode) + mov eax, cr0 + or ax, 1 ; Set PE (Protection Enable) Bit + mov cr0, eax + + jmp 0x08:protected_mode + +bits 32 +protected_mode: + ; Set segment registers + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + call loader + jmp error_loop +bits 16 + +; Error handling +error_loop: + cli ; Disable interrupts + hlt ; Wait for interrupt; should wait forever + jmp error_loop ; Loop if returns anyway + +disk_error: + mov si, disk_msg + call print + jmp error_loop + +; Messages (NULL-terminated strings with newline/return) +hello_msg db "Hello world! Booting...", NEWLINE, RETURN, NULL +disk_msg db "Disk is invalid or unsupported", NEWLINE, RETURN, NULL + +; Disk configuration data for DISK_READ interrupt (filled according to spec) +dap: ; Disk Address Packet + db 0x10 ; Disk address packet size + db 0 ; Always 0 +.count: + dw 64 ; Number of sectors (512B each) to read ; 0x8000 +.dest: + dw 0 ; Destination offset + dw 0 ; Destination segment +.lba: + dd 1 ; LBA number ; Inter stage is directly after first LBA (0) + dd 0 ; More storage bytes + +; Global Descriptor Table (GDT) +gdt: + dw .size - 1 + 8 ; GDT size + dd .start - 8 ; GDT start address +.start: + ; Code + dw 0xffff ; Limit + dw 0x0000 ; Base (low 16 bits) + db 0x00 ; Base (mid 8 bits) + db 10011010b ; Access + db 11001111b ; Granularity + db 0x00 ; Base (high 8 bits) + + ; Data + dw 0xffff ; Limit + dw 0x0000 ; Base (low 16 bits) + db 0x00 ; Base (mid 8 bits) + db 10010010b ; Access + db 11001111b ; Granularity + db 0x00 ; Base (high 8 bits) +.end: +.size: equ .end - .start + +times SECTOR_SIZE - 2 - ($ - $$) db 0 ; Fill until 512 (SECTOR_SIZE) - 2 bytes; -2 because of 2B signature +dw SECTOR_END_SIG ; Bootsector end signature + +loader: +incbin "build/loader.bin" + +; Limit to 0x8000 due to ext2 superblock start at 1024 on partition 1 +times 0x8000 - ($ - $$) db 0 +superblock: diff --git a/src/entry/definitions.asm b/src/entry/definitions.asm new file mode 100644 index 0000000..dcaa141 --- /dev/null +++ b/src/entry/definitions.asm @@ -0,0 +1,26 @@ +; MIT License, Copyright (c) 2021 Marvin Borner +; This file includes definitions to reduce magic numbers + +; Boot constants +%define LOCATION 0x7c00 ; Bootloader location +%define SECTOR_END_SIG 0xaa55 ; Bootsector end signature +%define SECTOR_SIZE 512 ; Bootsector size + +; Interrupts +%define SCREEN_INT 0x10 ; Screen/video BIOS interrupt +%define DISK_INT 0x13 ; Disk BIOS interrupt + +; Characters +%define NEWLINE 0x0A ; Newline character (\n) +%define RETURN 0x0D ; Return character (\r) +%define NULL 0x00 ; NULL character (\0) + +; Screen/video commands (for SCREEN_INT; using VGA interface) +%define SCREEN_CLEAR 0x03 ; Clear screen command +%define SCREEN_OUT 0x0e ; Screen output command + +; Disk commands +%define DISK_EXT_CHECK 0x41 ; Disk extension check command +%define DISK_EXT_CHECK_REQ 0x55aa ; First extension check signature (request) +%define DISK_EXT_CHECK_RESP 0xaa55 ; Second extension check signature (response) +%define DISK_READ 0x42 ; Disk extended read command diff --git a/src/loader/cpu.c b/src/loader/cpu.c new file mode 100644 index 0000000..a12d2fa --- /dev/null +++ b/src/loader/cpu.c @@ -0,0 +1,26 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#include <cpu.h> + +/** + * CPU IO + */ + +u8 inb(u16 port) +{ + u8 value; + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +u16 inw(u16 port) +{ + u16 value; + __asm__ volatile("inw %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +void outb(u16 port, u8 data) +{ + __asm__ volatile("outb %0, %1" ::"a"(data), "Nd"(port)); +} diff --git a/src/loader/inc/cpu.h b/src/loader/inc/cpu.h new file mode 100644 index 0000000..88db34b --- /dev/null +++ b/src/loader/inc/cpu.h @@ -0,0 +1,13 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#ifndef CPU_H +#define CPU_H + +#include <def.h> + +u8 inb(u16 port); +u16 inw(u16 port); + +void outb(u16 port, u8 data); + +#endif diff --git a/src/loader/inc/def.h b/src/loader/inc/def.h new file mode 100644 index 0000000..8c2f9aa --- /dev/null +++ b/src/loader/inc/def.h @@ -0,0 +1,28 @@ +// MIT License, Copyright (c) 2021 Marvin Borner +// Useful macros/types + +#ifndef DEF_H +#define DEF_H + +typedef signed char s8; +typedef unsigned char u8; + +typedef signed short s16; +typedef unsigned short u16; + +typedef signed int s32; +typedef unsigned int u32; + +typedef __builtin_va_list va_list; +#define va_start __builtin_va_start +#define va_end __builtin_va_end +#define va_arg __builtin_va_arg + +#define NULL ((void *)0) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define ABS(a) (((a) < 0) ? -(a) : (a)) + +#define PACKED __attribute__((packed)) + +#endif diff --git a/src/loader/inc/lib.h b/src/loader/inc/lib.h new file mode 100644 index 0000000..997db67 --- /dev/null +++ b/src/loader/inc/lib.h @@ -0,0 +1,14 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#ifndef LIB_H +#define LIB_H + +#include <def.h> + +u32 strlen(const char *str); +u32 strnlen(const char *s, u32 max); +u32 strlcpy(char *dst, const char *src, u32 size); + +int itoa(s32 value, char *buffer, u32 base); + +#endif diff --git a/src/loader/inc/log.h b/src/loader/inc/log.h new file mode 100644 index 0000000..2d65a76 --- /dev/null +++ b/src/loader/inc/log.h @@ -0,0 +1,11 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#ifndef LOG_H +#define LOG_H + +void serial_install(void); +void serial_print(const char *data); + +void log(const char *format, ...); + +#endif diff --git a/src/loader/lib.c b/src/loader/lib.c new file mode 100644 index 0000000..c54ef14 --- /dev/null +++ b/src/loader/lib.c @@ -0,0 +1,85 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#include <lib.h> + +/** + * Common string functions + */ + +u32 strlen(const char *str) +{ + const char *s = str; + while (*s) + s++; + return s - str; +} + +u32 strnlen(const char *str, u32 max) +{ + const char *s = str; + while (max && *s) { + s++; + max--; + } + return s - str; +} + +u32 strlcpy(char *dst, const char *src, u32 size) +{ + const char *orig = src; + u32 left = size; + + if (left) + while (--left) + if (!(*dst++ = *src++)) + break; + + if (!left) { + if (size) + *dst = 0; + while (*src++) + ; + } + + return src - orig - 1; +} + +/** + * Conversion + */ + +int itoa(s32 value, char *buffer, u32 base) +{ + char tmp[16]; + char *tp = tmp; + int i; + unsigned v; + + int sign = (base == 10 && value < 0); + if (sign) + v = -value; + else + v = (unsigned)value; + + while (v || tp == tmp) { + i = v % base; + v /= base; + if (i < 10) + *tp++ = i + '0'; + else + *tp++ = i + 'a' - 10; + } + + int len = tp - tmp; + + if (sign) { + *buffer++ = '-'; + len++; + } + + while (tp > tmp) + *buffer++ = *--tp; + *buffer = '\0'; + + return len; +} diff --git a/src/loader/link.ld b/src/loader/link.ld new file mode 100644 index 0000000..aa97f49 --- /dev/null +++ b/src/loader/link.ld @@ -0,0 +1,35 @@ +/* MIT License, Copyright (c) 2021 Marvin Borner */ + +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(start) +phys = 0x7e00; + +SECTIONS +{ + . = phys; + + .text : + { + *(.text.start) + *(.text*) + } + + .rodata : + { + *(.rodata*) + } + + .data : + { + *(.data*) + } + + .bss : + { + *(COMMON) + *(.bss) + } + + end = .; +} diff --git a/src/loader/log.c b/src/loader/log.c new file mode 100644 index 0000000..a2c05f0 --- /dev/null +++ b/src/loader/log.c @@ -0,0 +1,108 @@ +#include <cpu.h> +#include <lib.h> +#include <log.h> + +/** + * Formatting + */ + +static u32 vsnprintf(char *str, u32 size, const char *format, va_list ap) +{ + u32 length = 0; + + int temp_int; + char temp_ch; + char *temp_str; + + char buffer[64] = { 0 }; + + // TODO: Fix potential memory overflows because of str[length++]=xxx + char ch; + while ((ch = *format++)) { + if (ch == '%') { + switch (*format++) { + case '%': + str[length++] = '%'; + break; + case 'c': + temp_ch = va_arg(ap, int); + str[length++] = temp_ch; + break; + case 's': + temp_str = va_arg(ap, char *); + length += strlcpy(&str[length], temp_str, size - length); + break; + case 'b': + temp_int = va_arg(ap, int); + itoa(temp_int, buffer, 2); + length += strlcpy(&str[length], buffer, size - length); + break; + case 'd': + temp_int = va_arg(ap, int); + itoa(temp_int, buffer, 10); + length += strlcpy(&str[length], buffer, size - length); + break; + case 'x': + temp_int = va_arg(ap, int); + itoa(temp_int, buffer, 16); + length += strlcpy(&str[length], buffer, size - length); + break; + default: + serial_print("Unknown printf format\n"); + } + } else { + str[length++] = ch; + } + } + + return length; +} + +/** + * Serial + */ + +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); +} + +void serial_print(const char *data) +{ + for (const char *p = data; *p; p++) + serial_put(*p); +} + +/** + * Universal print function + */ + +void log(const char *format, ...) +{ + char buf[1024] = { 0 }; + + va_list ap; + va_start(ap, format); + vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + + serial_print(buf); +} diff --git a/src/loader/main.c b/src/loader/main.c new file mode 100644 index 0000000..54b5122 --- /dev/null +++ b/src/loader/main.c @@ -0,0 +1,19 @@ +// MIT License, Copyright (c) 2021 Marvin Borner + +#include <def.h> +#include <log.h> + +/** + * Entry + */ + +int start(void); +int start(void) +{ + serial_install(); + log("Hello %d\n", 42); + + while (1) + ; + return 0; +} |