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 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 diff --git a/Makefile b/Makefile index 2a2aade..8d0e4a2 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 @@ -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 lib/proc.o lib/spinlock.o lib/proc.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 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/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 diff --git a/link.ld b/kernel.ld similarity index 100% rename from link.ld rename to kernel.ld 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 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 diff --git a/riscv.h b/riscv.h new file mode 100644 index 0000000..b614112 --- /dev/null +++ b/riscv.h @@ -0,0 +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 diff --git a/start.c b/start.c index 46c6bfc..0a9ca9b 100644 --- a/start.c +++ b/start.c @@ -1,11 +1,10 @@ -/* - * 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 +#include +#include +#include +#include /* 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; } @@ -23,9 +22,27 @@ 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() { - uart_puts("Hello Neptune!\n"); + + u64 a = r_mhartid(); + + acquire(&sl); + + if (!greeted) { + uart_puts("Hello Neptune!\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 */ 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;