Add initial barebones kernel and cross compiler container image

This commit is contained in:
2024-07-29 23:59:10 +02:00
commit 30e0d6d9f8
19 changed files with 899 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build/
*.iso

59
Makefile Normal file
View File

@@ -0,0 +1,59 @@
.SUFFIXES:
.PHONY: all clean
SOURCE_DIR := src
BUILD_DIR := build
CONTAINER_CMD := podman run -v "$(shell pwd)":"/scratch" \
--workdir="/scratch" \
-e TERM \
-t \
cc-i686:latest
CC := $(CONTAINER_CMD) i686-elf-gcc
LD := $(CONTAINER_CMD) i686-elf-ld
AS := $(CONTAINER_CMD) i686-elf-as
AR := $(CONTAINER_CMD) i686-elf-ar
C_SOURCES := $(shell find $(SOURCE_DIR) -name '*.c')
ASM_SOURCES := $(shell find $(SOURCE_DIR) -name '*.S')
OBJECTS := $(patsubst $(SOURCE_DIR)/%, $(BUILD_DIR)/%, $(C_SOURCES:.c=.o) $(ASM_SOURCES:.S=.o))
DEPENDS := $(patsubst $(SOURCE_DIR)/%, $(BUILD_DIR)/%, $(C_SOURCES:.c=.d))
CFLAGS := -MMD -ffreestanding -O0 -Wall -Wextra -Werror -std=c2x -I$(SOURCE_DIR)/include -no-pie -fstack-protector-strong
ASFLAGS :=
$(info C_SOURCES is $(C_SOURCES))
$(info ASM_SOURCES is $(ASM_SOURCES))
$(info OBJECTS is $(OBJECTS))
$(info DEPENDS is $(DEPENDS))
all: myos.iso
run: myos.iso
qemu-system-i386 -cdrom myos.iso
cross-compiler: cross-compiler-image/Dockerfile
podman build cross-compiler-image -t cc-i686
myos.iso: $(BUILD_DIR)/myos.bin
@mkdir -p $(BUILD_DIR)/image/boot/grub
cp -v $< $(BUILD_DIR)/image/boot/
cp -v grub.cfg $(BUILD_DIR)/image/boot/grub/grub.cfg
grub-mkrescue -o $@ $(BUILD_DIR)/image
$(BUILD_DIR)/myos.bin: $(OBJECTS)
mkdir -p $(BUILD_DIR)
$(CC) -T linker.ld -o $@ $(CFLAGS) -nostdlib $^ -lgcc
-include $(DEPENDS)
$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c Makefile
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.S Makefile
@mkdir -p $(@D)
$(AS) $(ASFLAGS) $< -o $@

View File

@@ -0,0 +1,67 @@
FROM ubuntu:latest
RUN apt-get update
COPY packages.txt .
RUN apt-get install -y --no-install-recommends --no-install-suggests $(cat packages.txt)
COPY gnu-keyring.gpg /gnu-keyring.gpg
RUN gpg --import /gnu-keyring.gpg
#ARG BINUTILS_MIRROR="https://ftp.gnu.org/gnu/binutils/"
ARG BINUTILS_MIRROR="https://gnuftp.uib.no/binutils/"
ARG BINUTILS_NAME="binutils-2.42"
ARG BINUTILS_ARCHIVE=${BINUTILS_NAME}".tar.gz"
#ARG GCC_MIRROR="https://ftp.gnu.org/gnu/gcc/gcc-14.1.0/"
ARG GCC_MIRROR="https://gnuftp.uib.no/gcc/gcc-14.1.0/"
ARG GCC_NAME="gcc-14.1.0"
ARG GCC_ARCHIVE=${GCC_NAME}".tar.gz"
ARG PREFIX=/opt/cross/
ARG TARGET=i686-elf
ENV PATH=${PREFIX}bin:${PATH}
WORKDIR build
# download and build gcc and binutils source
RUN set -x \
&& wget --no-verbose ${BINUTILS_MIRROR}${BINUTILS_ARCHIVE} \
&& wget --no-verbose ${BINUTILS_MIRROR}${BINUTILS_ARCHIVE}.sig \
&& gpg --verify "${BINUTILS_ARCHIVE}.sig" \
&& mkdir binutils \
&& tar -xf ${BINUTILS_ARCHIVE} -C binutils \
&& mkdir binutils-build \
&& cd binutils-build \
&& ../binutils/${BINUTILS_NAME}/configure \
--target=$TARGET \
--prefix=$PREFIX \
--with-sysroot \
--disable-nls \
--disable-werror \
&& make -j 8 \
&& make install \
&& rm -rf binutils-build binutils ${BINUTLS_ARCHIVE}
RUN set -x \
&& wget --no-verbose ${GCC_MIRROR}${GCC_ARCHIVE} \
&& wget --no-verbose ${GCC_MIRROR}${GCC_ARCHIVE}.sig \
&& gpg --verify "${GCC_ARCHIVE}.sig" \
&& mkdir gcc \
&& tar -xf ${GCC_ARCHIVE} -C gcc \
&& mkdir gcc-build \
&& cd gcc-build \
&& ../gcc/${GCC_NAME}/configure \
--target=$TARGET \
--prefix=$PREFIX \
--disable-nls \
--enable-languages=c \
--without-headers \
&& make -j 8 all-gcc \
&& make -j 8 all-target-libgcc \
&& make install-gcc \
&& make install-target-libgcc \
&& rm -rf gcc-build gcc ${GCC_ARCHIVE}
WORKDIR /runtime

Binary file not shown.

View File

@@ -0,0 +1,13 @@
build-essential
bison
flex
libgmp3-dev
libmpc-dev
libmpfr-dev
texinfo
libisl-dev
wget
gpg
gpg-agent
gzip
ca-certificates

4
grub.cfg Normal file
View File

@@ -0,0 +1,4 @@
menuentry "myos" {
multiboot /boot/myos.bin
boot
}

65
linker.ld Normal file
View File

@@ -0,0 +1,65 @@
/*
* URL: https://wiki.osdev.org/Bare_Bones#Linking_the_Kernel
* Archive: https://web.archive.org/web/20240718110628/https://wiki.osdev.org/Bare_Bones#Linking_the_Kernel
* Date: 2024-07-29
*/
/* The bootloader will look at this image and start execution at the symbol
designated as the entry point. */
ENTRY(_start)
/* Tell where the various sections of the object files will be put in the final
kernel image. */
SECTIONS
{
/* It used to be universally recommended to use 1M as a start offset,
as it was effectively guaranteed to be available under BIOS systems.
However, UEFI has made things more complicated, and experimental data
strongly suggests that 2M is a safer place to load. In 2016, a new
feature was introduced to the multiboot2 spec to inform bootloaders
that a kernel can be loaded anywhere within a range of addresses and
will be able to relocate itself to run from such a loader-selected
address, in order to give the loader freedom in selecting a span of
memory which is verified to be available by the firmware, in order to
work around this issue. This does not use that feature, so 2M was
chosen as a safer option than the traditional 1M. */
. = 2M;
/* First put the multiboot header, as it is required to be put very early
in the image or the bootloader won't recognize the file format.
Next we'll put the .text section. */
.text BLOCK(4K) : ALIGN(4K)
{
KEEP(*(.multiboot))
KEEP(*(.text))
}
/* explicitly put .note.gnu.build-id header after multiboot header */
.note.gnu.build-id BLOCK(4K) : ALIGN(4K)
{
KEEP(*(.note.gnu.build-id))
}
/* Read-only data. */
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
/* Read-write data (initialized) */
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
/* Read-write data (uninitialized) and stack */
.bss BLOCK(4K) : ALIGN(4K)
{
*(COMMON)
*(.bss)
}
/* The compiler may produce other sections, by default it will put them in
a segment with the same name. Simply add stuff here as needed. */
}

109
src/boot.S Normal file
View File

@@ -0,0 +1,109 @@
/* Declare constants for the multiboot header. */
.set ALIGN, 1<<0 /* align loaded modules on page boundaries */
.set MEMINFO, 1<<1 /* provide memory map */
.set FLAGS, (ALIGN | MEMINFO) /* this is the Multiboot 'flag' field */
.set MAGIC, 0x1BADB002 /* 'magic number' lets bootloader find the header */
.set CHECKSUM, -(MAGIC + FLAGS) /* checksum of above, to prove we are multiboot */
/*
Declare a multiboot header that marks the program as a kernel. These are magic
values that are documented in the multiboot standard. The bootloader will
search for this signature in the first 8 KiB of the kernel file, aligned at a
32-bit boundary. The signature is in its own section so the header can be
forced to be within the first 8 KiB of the kernel file.
*/
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
/*
The multiboot standard does not define the value of the stack pointer register
(esp) and it is up to the kernel to provide a stack. This allocates room for a
small stack by creating a symbol at the bottom of it, then allocating 16384
bytes for it, and finally creating a symbol at the top. The stack grows
downwards on x86. The stack is in its own section so it can be marked nobits,
which means the kernel file is smaller because it does not contain an
uninitialized stack. The stack on x86 must be 16-byte aligned according to the
System V ABI standard and de-facto extensions. The compiler will assume the
stack is properly aligned and failure to align the stack will result in
undefined behavior.
*/
.section .bss
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:
/*
The linker script specifies _start as the entry point to the kernel and the
bootloader will jump to this position once the kernel has been loaded. It
doesn't make sense to return from this function as the bootloader is gone.
*/
.section .text
.global _start
.type _start, @function
_start:
/*
The bootloader has loaded us into 32-bit protected mode on a x86
machine. Interrupts are disabled. Paging is disabled. The processor
state is as defined in the multiboot standard. The kernel has full
control of the CPU. The kernel can only make use of hardware features
and any code it provides as part of itself. There's no printf
function, unless the kernel provides its own <stdio.h> header and a
printf implementation. There are no security restrictions, no
safeguards, no debugging mechanisms, only what the kernel provides
itself. It has absolute and complete power over the
machine.
*/
/*
To set up a stack, we set the esp register to point to the top of the
stack (as it grows downwards on x86 systems). This is necessarily done
in assembly as languages such as C cannot function without a stack.
*/
mov $stack_top, %esp
/*
This is a good place to initialize crucial processor state before the
high-level kernel is entered. It's best to minimize the early
environment where crucial features are offline. Note that the
processor is not fully initialized yet: Features such as floating
point instructions and instruction set extensions are not initialized
yet. The GDT should be loaded here. Paging should be enabled here.
C++ features such as global constructors and exceptions will require
runtime support to work as well.
*/
/*
Enter the high-level kernel. The ABI requires the stack is 16-byte
aligned at the time of the call instruction (which afterwards pushes
the return pointer of size 4 bytes). The stack was originally 16-byte
aligned above and we've pushed a multiple of 16 bytes to the
stack since (pushed 0 bytes so far), so the alignment has thus been
preserved and the call is well defined.
*/
call kernel_main
/*
If the system has nothing more to do, put the computer into an
infinite loop. To do that:
1) Disable interrupts with cli (clear interrupt enable in eflags).
They are already disabled by the bootloader, so this is not needed.
Mind that you might later enable interrupts and return from
kernel_main (which is sort of nonsensical to do).
2) Wait for the next interrupt to arrive with hlt (halt instruction).
Since they are disabled, this will lock up the computer.
3) Jump to the hlt instruction if it ever wakes up due to a
non-maskable interrupt occurring or due to system management mode.
*/
cli
1: hlt
jmp 1b
/*
Set the size of the _start symbol to the current location '.' minus its start.
This is useful when debugging or when you implement call tracing.
*/
.size _start, . - _start

View File

@@ -0,0 +1,35 @@
#include "ps2-keyboard.h"
const struct str ps2_cmd_str[PS2CMD_COUNT] = {
[PS2CMD_SET_LEDS ] = str_attach("PS2CMD_SET_LEDS"),
[PS2CMD_ECHO ] = str_attach("PS2CMD_ECHO"),
[PS2CMD_GET_SCAN ] = str_attach("PS2CMD_GET_SCAN"),
[PS2CMD_IDENTIFY_KEYBOARD ] = str_attach("PS2CMD_IDENTIFY_KEYBOARD"),
[PS2CMD_SET_TYPEMATIC_RATE ] = str_attach("PS2CMD_SET_TYPEMATIC_RATE"),
[PS2CMD_ENABLE_SCANNING ] = str_attach("PS2CMD_ENABLE_SCANNING"),
[PS2CMD_DISABLE_SCANNING ] = str_attach("PS2CMD_DISABLE_SCANNING"),
[PS2CMD_SET_DEFAULT_PARAMETERS ] = str_attach("PS2CMD_SET_DEFAULT_PARAMETERS"),
[PS2CMD_SET_ALL_KEYS_TO_TA ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_TA"),
[PS2CMD_SET_ALL_KEYS_TO_MR ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_MR"),
[PS2CMD_SET_ALL_KEYS_TO_M ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_M"),
[PS2CMD_SET_ALL_KEYS_TO_TAMR ] = str_attach("PS2CMD_SET_ALL_KEYS_TO_TAMR"),
[PS2CMD_SET_KEY_TO_TA ] = str_attach("PS2CMD_SET_KEY_TO_TA"),
[PS2CMD_SET_KEY_TO_MR ] = str_attach("PS2CMD_SET_KEY_TO_MR"),
[PS2CMD_SET_KEY_TO_M ] = str_attach("PS2CMD_SET_KEY_TO_M"),
[PS2CMD_RESEND_LAST_BYTE ] = str_attach("PS2CMD_RESEND_LAST_BYTE"),
[PS2CMD_RESEND_RESET_AND_SELF_TEST] = str_attach("PS2CMD_RESEND_RESET_AND_SELF_TEST"),
};
const struct str ps2_response_str[PS2RESPONSE_COUNT] = {
[PS2RESPONSE_ERROR_1 ] = str_attach("PS2RESPONSE_ERROR_1"),
[PS2RESPONSE_ERROR_2 ] = str_attach("PS2RESPONSE_ERROR_2"),
[PS2RESPONSE_SELF_TEST_PASSED ] = str_attach("PS2RESPONSE_SELF_TEST_PASSED"),
[PS2RESPONSE_ECHO_RESPONSE ] = str_attach("PS2RESPONSE_ECHO_RESPONSE"),
[PS2RESPONSE_ACK ] = str_attach("PS2RESPONSE_ACK"),
[PS2RESPONSE_SELF_TEST_FAILED_1] = str_attach("PS2RESPONSE_SELF_TEST_FAILED_1"),
[PS2RESPONSE_SELF_TEST_FAILED_2] = str_attach("PS2RESPONSE_SELF_TEST_FAILED_2"),
[PS2RESPONSE_RESEND ] = str_attach("PS2RESPONSE_RESEND"),
};

14
src/include/libc.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
#include "str.h"
#include "tty.h"
__attribute__((noreturn)) void panic(struct str s);
void* memmove(void *dest, const void *src, size_t n);
void* memset(void *s, int c, size_t n);
void* memcpy(void *dest, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
int printf(struct str format, ...);

0
src/include/printf.h Normal file
View File

View File

@@ -0,0 +1,91 @@
#pragma once
#include "str.h"
/* commands that the system sends to the keyboard */
enum ps2_cmd {
/* Set LEDs */
PS2CMD_SET_LEDS = 0xED,
/* Echo (for diagnostic purposes, and useful for device removal detection) */
PS2CMD_ECHO = 0xEE,
/* Get/set current scan code set */
PS2CMD_GET_SCAN = 0xF0,
/* Identify keyboard */
PS2CMD_IDENTIFY_KEYBOARD = 0xF2,
/* Set typematic rate and delay */
PS2CMD_SET_TYPEMATIC_RATE = 0xF3,
/* Enable scanning (keyboard will send scan codes) */
PS2CMD_ENABLE_SCANNING = 0xF4,
/* Disable scanning (keyboard won't send scan codes)*/
/* Note: May also restore default parameters */
PS2CMD_DISABLE_SCANNING = 0xF5,
/* Set default parameters */
PS2CMD_SET_DEFAULT_PARAMETERS = 0xF6,
/* Set all keys to typematic/autorepeat only (scancode set 3 only) */
PS2CMD_SET_ALL_KEYS_TO_TA = 0xF7,
/* Set all keys to make/release (scancode set 3 only) */
PS2CMD_SET_ALL_KEYS_TO_MR = 0xF8,
/* Set all keys to make only (scancode set 3 only) */
PS2CMD_SET_ALL_KEYS_TO_M = 0xF9,
/* Set all keys to typematic/autorepeat/make/release (scancode set 3 only) */
PS2CMD_SET_ALL_KEYS_TO_TAMR = 0xFA,
/* Set specific key to typematic/autorepeat only (scancode set 3 only) */
PS2CMD_SET_KEY_TO_TA = 0xFB,
/* Set specific key to make/release (scancode set 3 only) */
PS2CMD_SET_KEY_TO_MR = 0xFC,
/* Set specific key to make only (scancode set 3 only) */
PS2CMD_SET_KEY_TO_M = 0xFD,
/* Resend last byte */
PS2CMD_RESEND_LAST_BYTE = 0xFE,
/* Reset and start self-test */
PS2CMD_RESEND_RESET_AND_SELF_TEST = 0xFF,
PS2CMD_COUNT,
};
/* commands that the keyboard sends to the system */
enum ps2_response {
/* Self test passed (sent after "0xFF (reset)" command or keyboard power up) */
PS2RESPONSE_SELF_TEST_PASSED = 0xAA,
/* Response to "0xEE (echo)" command */
PS2RESPONSE_ECHO_RESPONSE = 0xEE,
/* Command acknowledged (ACK) */
PS2RESPONSE_ACK = 0xFA,
/* Self test failed (sent after "0xFF (reset)" command or keyboard power up) */
PS2RESPONSE_SELF_TEST_FAILED_1 = 0xFC,
PS2RESPONSE_SELF_TEST_FAILED_2 = 0xFD,
/* Resend (keyboard wants controller to repeat last command it sent) */
PS2RESPONSE_RESEND = 0xFE,
/* Key detection error or internal buffer overrun */
PS2RESPONSE_ERROR_1 = 0x00,
PS2RESPONSE_ERROR_2 = 0xFF,
PS2RESPONSE_COUNT,
};
extern const struct str ps2_cmd_str[PS2CMD_COUNT];
extern const struct str ps2_response_str[PS2RESPONSE_COUNT];

10
src/include/str.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <stddef.h>
struct str {
char* data;
size_t len;
};
#define str_attach(cstr) (struct str){.data = cstr, .len = sizeof(cstr)-1}

57
src/include/tty.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Inspired by OSDev - Bare Bones
* URL: https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C
* Archive: https://web.archive.org/web/20240718110628/https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C
* Date: 2024-07-29
*/
#pragma once
#include <stdint.h>
#include "str.h"
static constexpr size_t VGA_WIDTH = 80;
static constexpr size_t VGA_HEIGHT = 25;
static uint16_t* const terminal_buf = (uint16_t*)0xB8000;
struct [[nodiscard]] terminal_state {
size_t row;
size_t column;
uint8_t color;
uint16_t* const buf;
};
/* Hardware text mode color constants. */
enum vga_color : uint8_t {
VGA_COLOR_BLACK = 0,
VGA_COLOR_BLUE = 1,
VGA_COLOR_GREEN = 2,
VGA_COLOR_CYAN = 3,
VGA_COLOR_RED = 4,
VGA_COLOR_MAGENTA = 5,
VGA_COLOR_BROWN = 6,
VGA_COLOR_LIGHT_GREY = 7,
VGA_COLOR_DARK_GREY = 8,
VGA_COLOR_LIGHT_BLUE = 9,
VGA_COLOR_LIGHT_GREEN = 10,
VGA_COLOR_LIGHT_CYAN = 11,
VGA_COLOR_LIGHT_RED = 12,
VGA_COLOR_LIGHT_MAGENTA = 13,
VGA_COLOR_LIGHT_BROWN = 14,
VGA_COLOR_WHITE = 15,
};
#define vga_color(fg, bg) (fg | bg << 4)
void terminal_clear();
void terminal_set_color(uint8_t fg, uint8_t bg);
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y);
void terminal_scroll(int n);
void terminal_putchar(int c);
void terminal_write(struct str str);

16
src/kernel/kernel.c Normal file
View File

@@ -0,0 +1,16 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "libc.h"
#include "tty.h"
void kernel_main(void)
{
terminal_clear();
for (size_t i = 0; i < 15; i++) {
printf(str_attach("hello {u32}!\n"), i);
}
}

96
src/lib/libc.c Normal file
View File

@@ -0,0 +1,96 @@
/*
* This file is for any library function that require no system calls. In
* other words it's for freestanding functions.
* */
#include <stddef.h>
#include <stdint.h>
#include "tty.h"
#include "str.h"
/*
* Error handling
* ==============
* */
__attribute__((noreturn))
void panic(struct str s)
{
terminal_clear();
terminal_set_color(VGA_COLOR_WHITE, VGA_COLOR_RED);
terminal_write(s);
__asm__ volatile("hlt");
__builtin_unreachable();
}
/*
* Stack smashing protection
* =========================
* */
uintptr_t __stack_chk_guard = 0x00112233; // this is just a random value
__attribute__((noreturn))
void __stack_chk_fail(void)
{
panic(str_attach("Stack smashing detected"));
}
/*
* State-enforced suffering
* ========================
* GCC and Clang reserve the right to generate calls to the following
* 4 functions even if they are not directly called.
* Implement them as the C specification mandates.
* DO NOT remove or rename these functions, or stuff will eventually break!
* They CAN be moved to a different .c file.
* */
void* memcpy(void *dest, const void *src, size_t n) {
uint8_t *pdest = dest;
const uint8_t *psrc = src;
for (size_t i = 0; i < n; i++) {
pdest[i] = psrc[i];
}
return dest;
}
void* memset(void *s, int c, size_t n) {
uint8_t *p = s;
for (size_t i = 0; i < n; i++) {
p[i] = (uint8_t)c;
}
return s;
}
void* memmove(void *dest, const void *src, size_t n) {
uint8_t *pdest = dest;
const uint8_t *psrc = src;
if (src > dest) {
for (size_t i = 0; i < n; i++) {
pdest[i] = psrc[i];
}
} else if (src < dest) {
for (size_t i = n; i > 0; i--) {
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
int memcmp(const void *s1, const void *s2, size_t n) {
const uint8_t *p1 = s1;
const uint8_t *p2 = s2;
for (size_t i = 0; i < n; i++) {
if (p1[i] != p2[i]) {
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}

171
src/lib/printf.c Normal file
View File

@@ -0,0 +1,171 @@
#include <stdarg.h>
#include <limits.h>
#include "libc.h"
typedef int (*printf_function)(struct printf_state* s, void* data);
constexpr int EOF = -1;
struct printf_state {
struct str str;
va_list ap;
size_t i;
int written;
};
static inline int ps_peek(struct printf_state* s)
{
if (s->i == s->str.len) {
return EOF;
}
return s->str.data[s->i];
}
static inline int ps_get(struct printf_state* s)
{
if (s->i == s->str.len) {
return EOF;
}
return s->str.data[s->i++];
}
static int print_long(unsigned long n, struct str alphabet, bool is_signed)
{
constexpr size_t BUF_SZ = 21;
char buf[BUF_SZ];
size_t i = 0;
if (n == 0) {
terminal_putchar('0');
return 1;
}
bool is_negative = false;
if (is_signed) {
signed long signed_n = (signed long)n;
if (signed_n < 0) {
is_negative = true;
n = -signed_n;
}
}
if (is_negative)
terminal_putchar('-');
while (n) {
i += 1;
buf[BUF_SZ-i] = alphabet.data[n % alphabet.len];
n /= alphabet.len;
}
terminal_write((struct str){.data = &buf[BUF_SZ-i], .len = i});
return i;
}
static int print_i16(struct printf_state* s)
{
// int16_t is promoted to 'int' when passed through '...'
long n = va_arg(s->ap, int32_t);
return print_long(n, str_attach("0123456789"), true);
}
static int print_i32(struct printf_state* s)
{
long n = va_arg(s->ap, int32_t);
return print_long(n, str_attach("0123456789"), true);
}
static int print_i64(struct printf_state* s)
{
panic(str_attach("print_i64 not implemented on i686"));
long n = va_arg(s->ap, int64_t);
return print_long(n, str_attach("0123456789"), true);
}
static int print_u16(struct printf_state* s)
{
// uint16_t is promoted to 'unsigned int' when passed through '...'
unsigned long n = va_arg(s->ap, unsigned int);
return print_long(n, str_attach("0123456789"), false);
}
static int print_u32(struct printf_state* s)
{
unsigned long n = va_arg(s->ap, uint32_t);
return print_long(n, str_attach("0123456789"), false);
}
static int print_u64(struct printf_state* s)
{
panic(str_attach("print_u64 not implemented on i686"));
unsigned long n = va_arg(s->ap, uint64_t);
return print_long(n, str_attach("0123456789"), false);
}
#pragma GCC diagnostic ignored "-Wmultichar"
static int parse_format_cmd(struct printf_state* s)
{
int c;
uint32_t cmd = 0;
constexpr uint32_t CHAR_MASK = (1<<CHAR_BIT)-1;
while (c = ps_get(s), c != EOF && c != '}') {
cmd <<= CHAR_BIT;
cmd |= c & CHAR_MASK;
}
if (c == EOF)
return -1;
switch (cmd) {
case 'i16': return print_i16(s);
case 'i32': return print_i32(s);
case 'i64': return print_i64(s);
case 'u16': return print_u16(s);
case 'u32': return print_u32(s);
case 'u64': return print_u64(s);
default:
terminal_write(str_attach("\nunknown cmd: "));
while (cmd) {
terminal_putchar(cmd & CHAR_MASK);
cmd >>= CHAR_BIT;
}
terminal_putchar('\n');
return -1;
}
}
int printf(struct str format, ...)
{
int c;
struct printf_state s = {
.str = format,
.i = 0,
.written = 0,
};
va_start(s.ap, format);
while ((c = ps_get(&s)) != EOF) {
switch (c) {
case '{':
int ok = parse_format_cmd(&s);
if (ok == -1)
return -1;
s.written += ok;
break;
default:
terminal_putchar(c);
s.written += 1;
break;
}
}
return s.written;
}

0
src/lib/str.c Normal file
View File

90
src/lib/tty.c Normal file
View File

@@ -0,0 +1,90 @@
/*
* Inspired by OSDev - Bare Bones
* URL: https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C
* Archive: https://web.archive.org/web/20240718110628/https://wiki.osdev.org/Bare_Bones#Writing_a_kernel_in_C
* Date: 2024-07-29
*/
#include "tty.h"
#include "libc.h"
static struct terminal_state t = {
.row = 0,
.column = 0,
.color = vga_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK),
.buf = (uint16_t*)0xB8000,
};
static inline bool isprint(int c)
{
return (c >= ' ') && (c <= '~');
}
static inline uint16_t vga_entry(unsigned char uc, uint8_t color)
{
return (uint16_t) uc | (uint16_t) color << 8;
}
void terminal_clear()
{
t.row = 0,
t.column = 0,
t.color = vga_color(VGA_COLOR_LIGHT_GREY, VGA_COLOR_BLACK);
for (size_t i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
t.buf[i] = vga_entry(' ', t.color);
}
}
void terminal_set_color(uint8_t fg, uint8_t bg)
{
t.color = vga_color(fg, bg);
}
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y)
{
terminal_buf[y*VGA_WIDTH + x] = vga_entry(c, color);
}
void terminal_scroll(int n)
{
memmove(terminal_buf,
terminal_buf + VGA_WIDTH * n,
VGA_WIDTH * (VGA_HEIGHT-n));
for (size_t i = VGA_WIDTH * (VGA_HEIGHT-n); i < VGA_WIDTH * VGA_HEIGHT; i++) {
terminal_buf[i] = vga_entry(' ', t.color);
}
t.row -= n;
}
void terminal_putchar(int c)
{
// TODO: implement other control characters
switch (c) {
case '\n':
t.column = 0;
t.row += 1;
break;
default:
c = isprint(c) ? c : '?';
terminal_putentryat(c, t.color, t.column, t.row);
t.column += 1;
if (t.column == VGA_WIDTH) {
t.column = 0;
t.row += 1;
}
}
if (t.row == VGA_HEIGHT) {
terminal_scroll(1);
}
}
void terminal_write(struct str str)
{
for (size_t i = 0; i < str.len; i++) {
terminal_putchar(str.data[i]);
}
}