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

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]);
}
}