From 8959bc87b84b14f398df9846eac197a57bfca2ea Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:04:34 +0200 Subject: [PATCH 01/14] clangd: .clangd file that seems to make clangd behave --- .clangd | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..a2642ad --- /dev/null +++ b/.clangd @@ -0,0 +1,18 @@ +CompileFlags: + Add: + - --target=riscv64-unknown-elf + - -mcmodel=medany + - -march=rv64gc + - -mabi=lp64 + - -ffreestanding + - -fno-common + - -nostdlib + - -mno-relax + - -I. + - -Ilib + - -fno-stack-protector + - -fno-pie + - -no-pie + - -ggdb + - -gdwarf-2 + - -fno-omit-frame-pointer From e2a8bf287fa5c730f18b0449486d2b436f577791 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:05:03 +0200 Subject: [PATCH 02/14] UART_BASE is now volatile, preventing unexpected optimizations --- start.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.c b/start.c index 46c6bfc..3b923f8 100644 --- a/start.c +++ b/start.c @@ -5,7 +5,7 @@ #define NCPU 3 /* QEMU memory maps a UART device here. */ -#define UART_BASE ((char *)0x10000000) +#define UART_BASE ((volatile char *)0x10000000) /** Send a single character to the UART device */ void uart_putc(char c) { *UART_BASE = c; } From 567e79a4e8b61a53d313eb42e5f6891d3aad999b Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:19:49 +0200 Subject: [PATCH 03/14] Some shorthand typedef used around the kernel --- types.h | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 types.h diff --git a/types.h b/types.h new file mode 100644 index 0000000..f0780a4 --- /dev/null +++ b/types.h @@ -0,0 +1,6 @@ +#pragma once + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long u64; From 50a3c8d1d9ab2cd54905349280f1d994af432a93 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:21:51 +0200 Subject: [PATCH 04/14] riscv.h architecture specific routines, startcode now branches hartid=0 for initialziation and hangs the other cores --- riscv.h | 8 ++++++++ start.c | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 riscv.h diff --git a/riscv.h b/riscv.h new file mode 100644 index 0000000..6bf3df9 --- /dev/null +++ b/riscv.h @@ -0,0 +1,8 @@ +#include + +/** Returns the current hart id */ +static inline u64 r_mhartid() { + u64 x; + asm volatile("csrr %0, mhartid" : "=r"(x)); + return x; +} diff --git a/start.c b/start.c index 3b923f8..e07633f 100644 --- a/start.c +++ b/start.c @@ -1,3 +1,6 @@ +#include +#include + /* * Number of CPU's For now, this is hard-coded here. It will likely be in a * header, or dynamically discovered in the future @@ -25,7 +28,14 @@ char stack0[4096 * NCPU] __attribute__((aligned(16))); /* This is where entry.S drops us of. All cores land here */ void start() { - uart_puts("Hello Neptune!\n"); + + u64 a = r_mhartid(); + if(a == 0) { + uart_puts("Hello Neptune!\n"); + uart_puts("Core number: "); + uart_putc(a + '0'); + uart_putc('\n'); + } /* Here we will do a bunch of initialization steps */ From 4512a9324973caa7f7eb3e57591d29f1eee3c22f Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:23:31 +0200 Subject: [PATCH 05/14] First draft of a kernel side library, string.h & string.c implementing itoa --- lib/string.c | 39 +++++++++++++++++++++++++++++++++++++++ lib/string.h | 7 +++++++ 2 files changed, 46 insertions(+) create mode 100644 lib/string.c create mode 100644 lib/string.h diff --git a/lib/string.c b/lib/string.c new file mode 100644 index 0000000..7647d1f --- /dev/null +++ b/lib/string.c @@ -0,0 +1,39 @@ +char *itoa(int value, char *str, int base) { + char *p = str; + char *p1, *p2; + unsigned int uvalue = value; + int negative = 0; + + if (base < 2 || base > 36) { + *str = '\0'; + return str; + } + + if (value < 0 && base == 10) { + negative = 1; + uvalue = -value; + } + + // Convert to string + do { + int digit = uvalue % base; + *p++ = (digit < 10) ? '0' + digit : 'a' + (digit - 10); + uvalue /= base; + } while (uvalue); + + if (negative) + *p++ = '-'; + + *p = '\0'; + + // Reverse string + p1 = str; + p2 = p - 1; + while (p1 < p2) { + char tmp = *p1; + *p1++ = *p2; + *p2-- = tmp; + } + + return str; +} diff --git a/lib/string.h b/lib/string.h new file mode 100644 index 0000000..ceeb2a8 --- /dev/null +++ b/lib/string.h @@ -0,0 +1,7 @@ +#ifndef KERNEL_STRING_H +#define KERNEL_STRING_H + +/** Integer to ascii */ +char *itoa(int value, char *str, int base); + +#endif From bdf4228a1828db7e6b13b38c353bbc961fb100ae Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:23:45 +0200 Subject: [PATCH 06/14] Kernel now links string --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 2a2aade..118ccb9 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ CFLAGS += -march=rv64gc -mabi=lp64 CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax CFLAGS += -I. +CFLAGS += -Ilib CFLAGS += -fno-stack-protector # Prevents code that needs libc / runtime support CFLAGS += -MD # Generate header dependency files (.d) @@ -26,7 +27,7 @@ CFLAGS += -fno-omit-frame-pointer # More reliable backtraces in GDB all: kernel.elf -kernel.elf: entry.o start.o +kernel.elf: entry.o start.o lib/string.o @echo LD $@ @$(LD) $(LDFLAGS) -o $@ $^ @@ -39,10 +40,10 @@ kernel.elf: entry.o start.o @$(AS) $(ASFLAGS) -o $@ $< qemu: kernel.elf - @echo QEMU $@ - @qemu-system-riscv64 -machine virt -bios none -nographic -kernel kernel.elf + @echo QEMU $< + @qemu-system-riscv64 -machine virt -bios none -nographic -m 128M -smp 4 -kernel kernel.elf clean: - rm -f *.o *.elf *.d + rm -f *.o *.elf *.d lib/*.o lib/*.d -include *.d From ca717914407fc5842094fc539a976f5b917a6894 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 04:25:37 +0200 Subject: [PATCH 07/14] Removing debug prints from entry routine. After stacks are set up correctly, this is better handled in C. --- entry.S | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/entry.S b/entry.S index 6d7ff99..b77ef37 100644 --- a/entry.S +++ b/entry.S @@ -6,20 +6,6 @@ _entry: call _clear continue: - li t0, 0x10000000 # UART base address - li t1, 'E' # Character to print - sb t1, 0(t0) - li t1, 'n' - sb t1, 0(t0) - li t1, 't' - sb t1, 0(t0) - li t1, 'r' - sb t1, 0(t0) - li t1, 'y' - sb t1, 0(t0) - li t1, '\n' - sb t1, 0(t0) - # Set up a stack for C. la sp, stack0 li a0, 1024*4 # a0 = 4096 From 7ca69e391ad78a110ce7b0f4e0ae5adf659b7940 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:24:09 +0200 Subject: [PATCH 08/14] Moved: link.ld -> kernel.ld --- link.ld => kernel.ld | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename link.ld => kernel.ld (100%) diff --git a/link.ld b/kernel.ld similarity index 100% rename from link.ld rename to kernel.ld From 28485acc8f3c107297d7e9a5984f91d1d92c584e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:27:50 +0200 Subject: [PATCH 09/14] Makefile updated to reflect linker script rename --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 118ccb9..9a024a6 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ OBJDUMP = $(TOOLPREFIX)-objdump ASFLAGS = -march=rv64gc -mabi=lp64 -LDFLAGS = -Tlink.ld +LDFLAGS = -Tkernel.ld LDFLAGS += -m elf64lriscv CFLAGS = -Wall -Werror -O From eb0800c7422926d625d77957e2c16c5942a5921e Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:55:59 +0200 Subject: [PATCH 10/14] Clang format alignment --- .clang-format | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.clang-format b/.clang-format index 41e7b1a..5ff1461 100644 --- a/.clang-format +++ b/.clang-format @@ -6,3 +6,10 @@ ColumnLimit: 80 # Wrap lines after 80 characters AllowShortLoopsOnASingleLine: true AlwaysBreakTemplateDeclarations: true BreakConstructorInitializers: BeforeComma +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false From ff3ad1e719c2328976c92246e42518537a4692a6 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:56:15 +0200 Subject: [PATCH 11/14] Spinlocks and initial proc implementation --- lib/proc.c | 19 ++++++++ lib/proc.h | 23 ++++++++++ lib/spinlock.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/spinlock.h | 51 ++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 lib/proc.c create mode 100644 lib/proc.h create mode 100644 lib/spinlock.c create mode 100644 lib/spinlock.h diff --git a/lib/proc.c b/lib/proc.c new file mode 100644 index 0000000..aa28a41 --- /dev/null +++ b/lib/proc.c @@ -0,0 +1,19 @@ +#include + +struct Cpu cpus[NCPU]; + +// Must be called with interrupts disabled, +// to prevent race with process being moved +// to a different CPU. +int cpuid() { + int id = r_tp(); + return id; +} + +// Return this CPU's cpu struct. +// Interrupts must be disabled. +struct Cpu *mycpu(void) { + int id = cpuid(); + struct Cpu *c = &cpus[id]; + return c; +} diff --git a/lib/proc.h b/lib/proc.h new file mode 100644 index 0000000..67749a2 --- /dev/null +++ b/lib/proc.h @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +int cpuid(void); +struct Cpu *mycpu(void); + +/** Saved registers for kernel context switches. */ +struct Context {}; + +/** Per-CPU state. */ +struct Cpu { + struct Proc *proc; // The process running on this cpu, or null. + struct Context context; // swtch() here to enter scheduler(). + int noff; // Depth of push_off() nesting. + int intena; // Were interrupts enabled before push_off()? +}; + +extern struct Cpu cpus[NCPU]; + +/** Per-process state */ +struct Proc {}; diff --git a/lib/spinlock.c b/lib/spinlock.c new file mode 100644 index 0000000..7e7418d --- /dev/null +++ b/lib/spinlock.c @@ -0,0 +1,116 @@ +/** + * Mutual exclusion spin locks. + * (Not mutexes as these are spinning locks). + */ + +// #include +#include +#include +#include + +// void panic(char *s) { for (;;); } +void panic(char *s) {} + +/** + * The aquire() and release() functions control ownership of the lock. + * To perform these operations, modern CPU's provide atomic instructions + * that prevent the cores from stepping on each other's toes, otherwise known + * as a deadlock. + * + * GCC provides a set of built-in functions that allow you to use atomic + * instructions in an architecture-independent way. These functions are + * defined in the GCC manual: + * + * See: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html + * See: https://en.wikipedia.org/wiki/Memory_barrier + * + * On RISC-V, sync_lock_test_and_set turns into an atomic swap: + * a5 = 1 + * s1 = &lk->locked + * amoswap.w.aq a5, a5, (s1) + * + * On RISC-V, sync_lock_release turns into an atomic swap: + * s1 = &lk->locked + * amoswap.w zero, zero, (s1) + * + * __sync_synchronize(); + * + * This function tells the C compiler and the processor to not move loads or + * stores past this point, to ensure that the critical section's memory + * references happen strictly after the lock is acquired/locked. + * On RISC-V, this emits a fence instruction. + */ + +/** Initialize spinlock */ +void initlock(struct spinlock *lk, char *name) { + lk->name = name; + lk->locked = 0; + lk->cpu = 0; +} + +/** + * Acquire the lock. + * Loops (spins) until the lock is acquired. + * Panics if the lock is already held by this cpu. + */ +void acquire(struct spinlock *lk) { + push_off(); // disable interrupts to avoid deadlock. + + if (holding(lk)) // If the lock is already held, panic. + panic("acquire"); + + // Spin until aquired. See file header for details + while (__sync_lock_test_and_set(&lk->locked, 1) != 0) { + } + __sync_synchronize(); // No loads/stores after this point + + // Record info about lock acquisition for holding() and debugging. + lk->cpu = mycpu(); +} + +/** + * Release the lock. + * Panics if the lock is not held. + */ +void release(struct spinlock *lk) { + if (!holding(lk)) // If the lock is not held, panic. + panic("release"); + + lk->cpu = 0; // 0 means unheld + __sync_synchronize(); // No loads/stores after this point + __sync_lock_release(&lk->locked); // Essentially lk->locked = 0 + + pop_off(); +} + +// Check whether this cpu is holding the lock. +// Interrupts must be off. +int holding(struct spinlock *lk) { + int r; + r = (lk->locked && lk->cpu == mycpu()); + return r; +} + +// push_off/pop_off are like intr_off()/intr_on() except that they are matched: +// it takes two pop_off()s to undo two push_off()s. Also, if interrupts +// are initially off, then push_off, pop_off leaves them off. + +void push_off(void) { + int old = intr_get(); + + intr_off(); + if (mycpu()->noff == 0) + mycpu()->intena = old; + mycpu()->noff += 1; +} + +void pop_off(void) { + struct Cpu *c = mycpu(); + if (intr_get()) + panic("pop_off - interruptible"); + if (c->noff < 1) + panic("pop_off"); + c->noff -= 1; + if (c->noff == 0 && c->intena) + intr_on(); +} diff --git a/lib/spinlock.h b/lib/spinlock.h new file mode 100644 index 0000000..48bcb27 --- /dev/null +++ b/lib/spinlock.h @@ -0,0 +1,51 @@ +#ifndef KERNEL_SPINLOCK_H +#define KERNEL_SPINLOCK_H + +#include "types.h" + +/** Mutual exclusion spin lock */ +struct spinlock { + u32 locked; // Is the lock held? + + // NOTE: Perhaps feature gate this? + + // For debugging: + char *name; // Name of lock. + struct Cpu *cpu; // The cpu holding the lock. +}; + +/** + * Acquire the lock. + * Loops (spins) until the lock is acquired. + * Panics if the lock is already held by this cpu. + */ +void acquire(struct spinlock *); + +/** + * Check whether this cpu is holding the lock. + * Interrupts must be off. + */ +int holding(struct spinlock *); + +/** + * Initialize spinlock + */ +void initlock(struct spinlock *, char *); + +/** + * Release the lock. + * Panics if the lock is not held. + */ +void release(struct spinlock *); + +/** + * @brief push_off/pop_off are like intr_off()/intr_on() except that they are + * matched: it takes two pop_off()s to undo two push_off()s. Also, if + * interrupts are initially off, then push_off, pop_off leaves them off. + */ +void push_off(void); + +/** @copydoc pop_off */ +void pop_off(void); + +#endif From cda703873bb15e795d2f902a6676ce73abab87a8 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:56:41 +0200 Subject: [PATCH 12/14] Move some config options from start into config.h --- config.h | 8 ++++++++ start.c | 6 ------ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 config.h diff --git a/config.h b/config.h new file mode 100644 index 0000000..5c778ee --- /dev/null +++ b/config.h @@ -0,0 +1,8 @@ +/* + * Number of CPU's For now, this is hard-coded here. It will likely be + * dynamically discovered in the future. + */ +#define NCPU 3 + +/* Maximum number of files open */ +#define NOFILE 10 diff --git a/start.c b/start.c index e07633f..27ef439 100644 --- a/start.c +++ b/start.c @@ -1,12 +1,6 @@ #include #include -/* - * Number of CPU's For now, this is hard-coded here. It will likely be in a - * header, or dynamically discovered in the future - */ -#define NCPU 3 - /* QEMU memory maps a UART device here. */ #define UART_BASE ((volatile char *)0x10000000) From a9f7cb8cf76706de8e8314ba70c3ee7b8538d987 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:57:05 +0200 Subject: [PATCH 13/14] Some machine specific code for reading and writing registers --- riscv.h | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/riscv.h b/riscv.h index 6bf3df9..b614112 100644 --- a/riscv.h +++ b/riscv.h @@ -1,8 +1,73 @@ +#ifndef RISCV_KERNEL_H +#define RISCV_KERNEL_H + #include +// Supervisor Status Register, sstatus + +/** Supervisor Previous Privilege */ +#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User + +/** Supervisor Previous Interrupt Enable */ +#define SSTATUS_SPIE (1L << 5) + +/** User Previous Interrupt Enable */ +#define SSTATUS_UPIE (1L << 4) + +/** Supervisor Interrupt Enable */ +#define SSTATUS_SIE (1L << 1) + +/** User Interrupt Enable */ +#define SSTATUS_UIE (1L << 0) + +/** Page Table Entry Type */ +typedef u64 pte_t; + +/** Page Table Type */ +typedef u64 *pagetable_t; // 512 PTEs + /** Returns the current hart id */ static inline u64 r_mhartid() { u64 x; asm volatile("csrr %0, mhartid" : "=r"(x)); return x; } + +/** Read thread pointer */ +static inline u64 r_tp() { + u64 x; + asm volatile("mv %0, tp" : "=r"(x)); + return x; +} + +/** + * Read the value of the sstatus register. + * (Supervisor Status Register) + */ +static inline u64 r_sstatus() { + u64 x; + asm volatile("csrr %0, sstatus" : "=r"(x)); + return x; +} + +/** + * Write a value to the sstatus register. + * (Supervisor Status Register) + */ +static inline void w_sstatus(u64 x) { + asm volatile("csrw sstatus, %0" : : "r"(x)); +} + +/** Enable device interrupts */ +static inline void intr_on() { w_sstatus(r_sstatus() | SSTATUS_SIE); } + +/** Disable device interrupts */ +static inline void intr_off() { w_sstatus(r_sstatus() & ~SSTATUS_SIE); } + +/** Are device interrupts enabled? */ +static inline int intr_get() { + u64 x = r_sstatus(); + return (x & SSTATUS_SIE) != 0; +} + +#endif From 5948d6c8e8b09cef1f4e21a298a8ff1abf17b364 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Thu, 26 Jun 2025 05:57:35 +0200 Subject: [PATCH 14/14] Some prettier init code --- Makefile | 2 +- start.c | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9a024a6..8d0e4a2 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ CFLAGS += -fno-omit-frame-pointer # More reliable backtraces in GDB all: kernel.elf -kernel.elf: entry.o start.o lib/string.o +kernel.elf: entry.o start.o lib/string.o lib/proc.o lib/spinlock.o lib/proc.o @echo LD $@ @$(LD) $(LDFLAGS) -o $@ $^ diff --git a/start.c b/start.c index 27ef439..0a9ca9b 100644 --- a/start.c +++ b/start.c @@ -1,4 +1,6 @@ +#include #include +#include #include /* QEMU memory maps a UART device here. */ @@ -20,17 +22,28 @@ void uart_puts(const char *s) { */ char stack0[4096 * NCPU] __attribute__((aligned(16))); +/* Keep this here and sync on it until we have synchronized printf */ +struct spinlock sl = {0}; +volatile int greeted = 0; + /* This is where entry.S drops us of. All cores land here */ void start() { u64 a = r_mhartid(); - if(a == 0) { + + acquire(&sl); + + if (!greeted) { uart_puts("Hello Neptune!\n"); - uart_puts("Core number: "); - uart_putc(a + '0'); - uart_putc('\n'); + greeted = 1; } + uart_puts("Hart number: "); + uart_putc(a + '0'); + uart_putc('\n'); + + release(&sl); + /* Here we will do a bunch of initialization steps */ // We should not arrive here, but if we do, hang in a while on wfi.