summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarvin Borner2021-07-04 21:31:28 +0200
committerMarvin Borner2021-07-04 21:34:15 +0200
commit9b8698769535846d029c44247956eed9a21f1185 (patch)
tree294a17af4102805ab9863274339e8e030897804e
Initial commit
-rw-r--r--.github/workflows/build.yml29
-rw-r--r--.gitignore6
-rw-r--r--LICENSE21
-rw-r--r--makefile43
-rwxr-xr-xrun102
-rw-r--r--src/entry/bootsector.asm172
-rw-r--r--src/entry/definitions.asm26
-rw-r--r--src/loader/cpu.c26
-rw-r--r--src/loader/inc/cpu.h13
-rw-r--r--src/loader/inc/def.h28
-rw-r--r--src/loader/inc/lib.h14
-rw-r--r--src/loader/inc/log.h11
-rw-r--r--src/loader/lib.c85
-rw-r--r--src/loader/link.ld35
-rw-r--r--src/loader/log.c108
-rw-r--r--src/loader/main.c19
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7d20e31
--- /dev/null
+++ b/LICENSE
@@ -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)/*
diff --git a/run b/run
new file mode 100755
index 0000000..6e48550
--- /dev/null
+++ b/run
@@ -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;
+}