summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Holman <dholman@gymli.org>2025-10-15 19:51:55 -0500
committerDanny Holman <dholman@gymli.org>2025-10-15 19:51:55 -0500
commit32ff44d9511bed86104698dff1907577dbd5a0f0 (patch)
treeeab2d9983529fbbbdda5b2d6d81ae78243023c79
parentbootstrap: stage1: update build configuration (diff)
downloadbox-32ff44d9511bed86104698dff1907577dbd5a0f0.tar.gz
box-32ff44d9511bed86104698dff1907577dbd5a0f0.tar.zst
box-32ff44d9511bed86104698dff1907577dbd5a0f0.zip
bootstrap: stage1: create a baseline environment
Create a baseline booting environment in the first stage. This should allow booting on UEFI-aware systems and load the second stage in. Signed-off-by: Danny Holman <dholman@gymli.org>
-rw-r--r--bootstrap/stage1/arch/x86_64/asm/__chkstk.s25
-rw-r--r--bootstrap/stage1/arch/x86_64/asm/entry.s25
-rw-r--r--bootstrap/stage1/firmware/common/bprintf.c95
-rw-r--r--bootstrap/stage1/firmware/common/string.c153
-rw-r--r--bootstrap/stage1/firmware/efi/alloc.c50
-rw-r--r--bootstrap/stage1/firmware/efi/efi.c210
-rw-r--r--bootstrap/stage1/firmware/efi/tty.c55
-rw-r--r--bootstrap/stage1/include/alloc.h28
-rw-r--r--bootstrap/stage1/include/bprintf.h28
-rw-r--r--bootstrap/stage1/include/tty.h38
10 files changed, 707 insertions, 0 deletions
diff --git a/bootstrap/stage1/arch/x86_64/asm/__chkstk.s b/bootstrap/stage1/arch/x86_64/asm/__chkstk.s
new file mode 100644
index 0000000..7d5f0c4
--- /dev/null
+++ b/bootstrap/stage1/arch/x86_64/asm/__chkstk.s
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+.section .text
+
+# FIXME: this causes GP faults under certain conditions
+.global __chkstk
+__chkstk:
+ ret
diff --git a/bootstrap/stage1/arch/x86_64/asm/entry.s b/bootstrap/stage1/arch/x86_64/asm/entry.s
new file mode 100644
index 0000000..05797ca
--- /dev/null
+++ b/bootstrap/stage1/arch/x86_64/asm/entry.s
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+.section .text
+
+.global _start
+_start:
+ call efi_main
+ ret
diff --git a/bootstrap/stage1/firmware/common/bprintf.c b/bootstrap/stage1/firmware/common/bprintf.c
new file mode 100644
index 0000000..9d42cb9
--- /dev/null
+++ b/bootstrap/stage1/firmware/common/bprintf.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <bprintf.h>
+#include <tty.h>
+#include <stdint.h>
+#include <string.h>
+
+char* _convert(uint64_t num, int base) {
+ static char rep[] = "0123456789ABCDEF";
+ static char buffer[50];
+ char *ptr;
+
+ ptr = &buffer[49];
+ *ptr = '\0';
+
+ do {
+ *--ptr = rep[num%base];
+ num /= base;
+ } while (num != 0);
+
+ return ptr;
+}
+
+int vbprintf(const char *fmt, va_list args) {
+ char *s;
+ uint64_t i;
+
+ char buffer[4096];
+ memset(buffer, 0, 4096);
+ for (size_t n = 0; n < strlen(fmt); n++) {
+ if (fmt[n] != '%') {
+ buffer[strlen(buffer)] = fmt[n];
+ continue;
+ } else {
+ n++;
+ }
+
+ switch (fmt[n]) {
+ case 'c':
+ i = va_arg(args, int);
+ buffer[strlen(buffer)] = i;
+ break;
+ case 's':
+ s = va_arg(args, char*);
+ strcat(buffer, s);
+ break;
+ case 'd':
+ i = va_arg(args, uint64_t);
+ if (i < 0) {
+ i = -i;
+ strcat(buffer, "-");
+ }
+ strcat(buffer, _convert(i, 10));
+ break;
+ case 'o':
+ i = va_arg(args, uint64_t);
+ strcat(buffer, _convert(i, 10));
+ break;
+ case 'x':
+ i = va_arg(args, uint64_t);
+ strcat(buffer, _convert(i, 16));
+ break;
+ }
+ }
+ tty_write(buffer, strlen(buffer));
+ return 0;
+}
+
+int bprintf(const char *fmt, ...) {
+ va_list args;
+ int done;
+
+ va_start(args, fmt);
+ done = vbprintf(fmt, args);
+ va_end(args);
+
+ return done;
+}
diff --git a/bootstrap/stage1/firmware/common/string.c b/bootstrap/stage1/firmware/common/string.c
new file mode 100644
index 0000000..2c367a4
--- /dev/null
+++ b/bootstrap/stage1/firmware/common/string.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stddef.h>
+
+int memcmp(const void *str1, const void *str2, size_t n) {
+ unsigned char const *p1 = str1;
+ unsigned char const *p2 = str2;
+ int ret = 0;
+
+ if (str1 == str2)
+ return 0;
+
+ while (n > 0) {
+ if (*p1 != *p2) {
+ ret = (*p1 > *p2)?1:-1;
+ break;
+ }
+ n--;
+ p1++;
+ p2++;
+ }
+ return ret;
+}
+
+void* memcpy(void* __restrict dest, const void* __restrict src, size_t n) {
+ if (dest == src)
+ return dest;
+
+ unsigned char *pdest = dest;
+ unsigned char const *psrc = src;
+ for (size_t i = 0; i < n; i++)
+ pdest[i] = psrc[i];
+ return dest;
+}
+
+char* strncpy(char* __restrict dest, const char* __restrict src, size_t n) {
+ for (size_t i = 0; i < n; i++) {
+ if (src[i] == '\0')
+ break;
+ dest[i] = src[i];
+ }
+ return dest;
+}
+
+char* strcpy(char* __restrict dest, const char* __restrict src) {
+ return (char*)memcpy(dest, src, strlen(src));
+}
+
+char* strcat(char* __restrict dest, const char* __restrict src) {
+ return (char*)memcpy(&dest[strlen(dest)], src, strlen(src));
+}
+
+char* strncat(char* __restrict dest, const char* __restrict src, size_t n) {
+ return (char*)memcpy(&dest[strlen(dest)], src, n);
+}
+
+void* memmove(void* __restrict dest, const void* __restrict src, size_t n) {
+ if (dest == src)
+ return dest;
+
+ unsigned char const *psrc = src;
+ unsigned char buffer[n];
+
+ for (size_t i = 0; i < n; i++)
+ buffer[i] = psrc[i];
+ return memcpy(dest, (void*)buffer, n);
+}
+
+void* memset(void *str, int c, size_t n) {
+ unsigned char *p = str;
+ for (size_t i = 0; i < n; i++)
+ p[i] = (unsigned char)c;
+ return str;
+}
+
+int strncmp(const char *str1, const char *str2, size_t n) {
+ return memcmp(str1, str2, n);
+}
+
+int strcmp(const char *str1, const char *str2) {
+ size_t str1_sz = strlen(str1);
+ size_t str2_sz = strlen(str2);
+ if (str1_sz > str2_sz)
+ return memcmp(str1, str2, str2_sz);
+ return memcmp(str1, str2, str1_sz);
+}
+
+size_t strlen(const char *str) {
+ size_t i = 0;
+ while (str[i] != '\0')
+ i++;
+ return i;
+}
+
+int _is_delim(char c, const char *delim) {
+ while (*delim != '\0') {
+ if (c == *delim)
+ return 1;
+ delim++;
+ }
+ return 0;
+}
+
+char* strtok(char* __restrict str, const char* __restrict delim) {
+ static char *old_str;
+
+ if (str == NULL)
+ str = old_str;
+
+ while (1) {
+ if (_is_delim(*str, delim)) {
+ str++;
+ continue;
+ }
+
+ if (*str == '\0')
+ return NULL;
+ break;
+ }
+
+ char *ret = str;
+ while (1) {
+ if (*str == '\0') {
+ old_str = str;
+ return ret;
+ }
+
+ if (_is_delim(*str, delim)) {
+ *str = '\0';
+ old_str = str + 1;
+ return ret;
+ }
+ str++;
+ }
+}
diff --git a/bootstrap/stage1/firmware/efi/alloc.c b/bootstrap/stage1/firmware/efi/alloc.c
new file mode 100644
index 0000000..d57e481
--- /dev/null
+++ b/bootstrap/stage1/firmware/efi/alloc.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <bootabi/efi.h>
+
+#ifndef NULL
+#define NULL (void*)(0)
+#endif
+
+extern uint64_t errno;
+extern efi_loaded_image_protocol_t *lip;
+
+void* balloc(size_t len) {
+ if (lip == NULL)
+ return NULL;
+
+ void *buffer;
+ uint64_t status = lip->system_table->bootsrv->allocate_pool(LoaderData, len, &buffer);
+ if (status != 0) {
+ errno = status;
+ return NULL;
+ }
+ return buffer;
+}
+
+void bfree(void *ptr) {
+ if (lip == NULL)
+ return;
+
+ if (ptr == NULL)
+ return;
+
+ lip->system_table->bootsrv->free_pool(ptr);
+}
diff --git a/bootstrap/stage1/firmware/efi/efi.c b/bootstrap/stage1/firmware/efi/efi.c
new file mode 100644
index 0000000..140a9a6
--- /dev/null
+++ b/bootstrap/stage1/firmware/efi/efi.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <bootabi/efi.h>
+#include <bootabi/bootparam.h>
+#include <bprintf.h>
+#include <alloc.h>
+#include <elf.h>
+#include <string.h>
+#include <stddef.h>
+
+uint64_t errno = EFI_SUCCESS;
+efi_loaded_image_protocol_t *lip = NULL;
+void (*stage2_entry)(bootparam_t *params);
+
+void print_error(uint64_t errno) {
+ bprintf("EFI error: %s\r\n", efi_status_str[(uint16_t)errno]);
+}
+
+efi_bootparam_t* load_mmap(efi_bootparam_t *params) {
+ if (lip == NULL) {
+ errno = EFIERR(EFI_NOT_READY);
+ return NULL;
+ }
+ efi_boot_t *bootsrv = lip->system_table->bootsrv;
+
+ uint64_t mmap_size = 0;
+ uint64_t mmap_key = 0;
+ uint64_t dsize = 0;
+ uint64_t status = bootsrv->get_memory_map(&mmap_size,
+ NULL,
+ &mmap_key,
+ &dsize,
+ NULL);
+ if (status != EFIERR(EFI_BUFFER_TOO_SMALL)) {
+ errno = status;
+ return NULL;
+ }
+
+ efi_memory_descriptor_t *mmap = balloc(mmap_size/dsize);
+ status = bootsrv->get_memory_map(&mmap_size,
+ mmap,
+ &mmap_key,
+ &dsize,
+ NULL);
+ if (status != 0) {
+ errno = status;
+ return NULL;
+ }
+
+ params->mmap = mmap;
+ params->mmap_size = mmap_size;
+ params->mmap_key = mmap_key;
+ params->mmap_dsize = dsize;
+ return params;
+}
+
+ssize_t read_stage2(char16_t *filename, uint8_t **filedata) {
+ if (lip == NULL)
+ return -1;
+ efi_boot_t *bootsrv = lip->system_table->bootsrv;
+
+ efi_guid_t sifs_id = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_ID;
+ efi_simple_file_system_protocol_t *sifs = NULL;
+ uint64_t status = bootsrv->locate_protocol(&sifs_id, NULL, (void**)&sifs);
+ if (status != 0) {
+ errno = status;
+ return -1;
+ }
+
+ efi_file_protocol_t *rootdir = NULL;
+ status = sifs->open_volume(sifs, &rootdir);
+ if (status != 0) {
+ errno = status;
+ return -1;
+ }
+
+ efi_file_protocol_t *stage2_file = NULL;
+ status = rootdir->open(rootdir, &stage2_file, filename, EFI_FILE_MODE_READ, 0);
+ if (status != 0) {
+ errno = status;
+ return -1;
+ }
+
+ stage2_file->set_position(stage2_file, UINT64_MAX);
+ uint64_t bufsz = 0;
+ stage2_file->get_position(stage2_file, &bufsz);
+ stage2_file->set_position(stage2_file, 0);
+
+ *filedata = balloc(bufsz);
+ memset(*filedata, 0, bufsz);
+
+ status = stage2_file->read(stage2_file, &bufsz, *filedata);
+ if (status != 0) {
+ errno = status;
+ return -1;
+ }
+
+ stage2_file->close(stage2_file);
+ return (size_t)bufsz;
+}
+
+void* alloc_segment(Elf64_Phdr *phdr, uint8_t *filedata) {
+ if (lip == NULL) {
+ errno = EFI_NOT_READY;
+ return NULL;
+ }
+ efi_boot_t *bootsrv = lip->system_table->bootsrv;
+
+ uintptr_t ret = (uintptr_t)phdr->p_paddr;
+ uint64_t status = bootsrv->allocate_pages(AllocateAddress,
+ LoaderData,
+ (phdr->p_memsz/4096)+1,
+ &ret);
+ if (status != 0) {
+ errno = status;
+ return NULL;
+ }
+ void *segment = (void*)filedata+phdr->p_offset;
+ memcpy((void*)ret, segment, phdr->p_memsz);
+ return (void*)ret;
+}
+
+void* load_stage2(uint8_t *filedata) {
+ Elf64_Ehdr *ehdr = (Elf64_Ehdr*)filedata;
+ if (memcmp(ehdr->e_ident, ELF_MAGIC, 4) != 0) {
+ errno = EFIERR(EFI_LOAD_ERROR);
+ return NULL;
+ }
+
+ Elf64_Phdr *phdrs = (Elf64_Phdr*)(filedata + ehdr->e_phoff);
+ for (uint16_t i = 0; i < ehdr->e_phnum; i++) {
+ Elf64_Phdr *segment = &phdrs[i];
+ if (segment->p_type != PT_LOAD)
+ continue;
+ if (alloc_segment(segment, filedata) == NULL)
+ return NULL;
+ }
+
+ return (void*)ehdr->e_entry;
+}
+
+uint64_t efi_main(void *handle, efi_system_t *st) {
+ efi_guid_t lip_id = EFI_LOADED_IMAGE_PROTOCOL_ID;
+ st->bootsrv->allocate_pool(LoaderData, sizeof(efi_loaded_image_protocol_t), (void**)&lip);
+ uint64_t status = st->bootsrv->handle_protocol(handle, &lip_id, (void**)&lip);
+ if (status != 0)
+ return 0;
+
+ efi_guid_t gop_id = EFI_GRAPHICS_OUTPUT_PROTOCOL_ID;
+ efi_gop_t *gop = NULL;
+ status = st->bootsrv->locate_protocol(&gop_id, NULL, (void**)&gop);
+ if (status != 0) {
+ print_error(status);
+ return 0;
+ }
+
+ uint8_t *stage2_image = NULL;
+ ssize_t stage2_size = read_stage2(L"bootstrap-stage2", &stage2_image);
+ if (stage2_size == -1) {
+ print_error(errno);
+ return 0;
+ }
+ stage2_entry = load_stage2(stage2_image);
+ if (stage2_entry == NULL) {
+ print_error(errno);
+ return 0;
+ }
+
+ bootparam_t *params = balloc(sizeof(bootparam_t));
+ params->magic = BOOTPARAM_MAGIC;
+ params->firmware_id = FIRMWARE_UEFI;
+ efi_bootparam_t *uefi_params = balloc(sizeof(efi_bootparam_t));
+ uefi_params->st = st;
+ uefi_params->gop_info = gop->mode->info;
+ params->stack_ptr = balloc(4096);
+ memset(params->stack_ptr, 0, 4096);
+ params->firmware_params = (void*)uefi_params;
+
+ if (load_mmap(uefi_params) == NULL) {
+ print_error(errno);
+ return 0;
+ }
+
+ status = st->bootsrv->exit_boot_services(handle, uefi_params->mmap_key);
+ if (status != 0) {
+ print_error(status);
+ return 0;
+ }
+ st->bootsrv = NULL;
+
+ (*stage2_entry)(params);
+ return 0;
+}
diff --git a/bootstrap/stage1/firmware/efi/tty.c b/bootstrap/stage1/firmware/efi/tty.c
new file mode 100644
index 0000000..9131d95
--- /dev/null
+++ b/bootstrap/stage1/firmware/efi/tty.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <bootabi/efi.h>
+
+#ifndef NULL
+#define NULL (void*)(0)
+#endif
+
+extern uint64_t errno;
+extern efi_loaded_image_protocol_t *lip;
+
+void tty_putchar(char c) {
+ if (lip == NULL)
+ return;
+
+ efi_system_t *st = lip->system_table;
+ char16_t new_line[3] = L"\r\n";
+ if (c == '\n') {
+ st->console_out->output_str(st->console_out, new_line);
+ return;
+ }
+ char16_t wc[2];
+ wc[0] = (char16_t)c;
+ wc[1] = L'\0';
+ st->console_out->output_str(st->console_out, wc);
+}
+
+char tty_getchar(void) {
+ if (lip == NULL)
+ return '\0';
+
+ efi_system_t *st = lip->system_table;
+ efi_input_key_t key;
+ st->console_in->read_key_stroke(st->console_in, &key);
+ if (key.unicode < 127)
+ return (char)key.unicode;
+ return '?';
+}
diff --git a/bootstrap/stage1/include/alloc.h b/bootstrap/stage1/include/alloc.h
new file mode 100644
index 0000000..088b389
--- /dev/null
+++ b/bootstrap/stage1/include/alloc.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef BOOTSTRAP_ALLOC_H
+#define BOOTSTRAP_ALLOC_H
+
+#include <stddef.h>
+
+void* balloc(size_t len);
+void bfree(void *ptr);
+
+#endif
diff --git a/bootstrap/stage1/include/bprintf.h b/bootstrap/stage1/include/bprintf.h
new file mode 100644
index 0000000..d67d518
--- /dev/null
+++ b/bootstrap/stage1/include/bprintf.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef BPRINTF_H
+#define BPRINTF_H
+
+#include <stdarg.h>
+
+int vbprintf(const char *fmt, va_list args);
+int bprintf(const char *fmt, ...);
+
+#endif
diff --git a/bootstrap/stage1/include/tty.h b/bootstrap/stage1/include/tty.h
new file mode 100644
index 0000000..af0d8f9
--- /dev/null
+++ b/bootstrap/stage1/include/tty.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 Danny Holman <dholman@gymli.org>
+ *
+ * This file is part of BoxOS, a free and open-source Unix-like operating
+ * system.
+ *
+ * BoxOS is free software; you can redistribute it and/or modify under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * BoxOS is distributed in the hope it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * BoxOS; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef BOOTSTRAP_TTY_H
+#define BOOTSTRAP_TTY_H
+
+#include <stdint.h>
+
+void tty_putchar(char c);
+char tty_getchar(void);
+
+static inline void tty_write(const char *data, size_t size) {
+ for (size_t i = 0; i < size; i++)
+ tty_putchar(data[i]);
+}
+
+static inline void tty_read(char *data, size_t size) {
+ for (size_t i = 0; i < size; i++)
+ data[i] = tty_getchar();
+}
+
+#endif