diff --git a/Makefile b/Makefile index 1496c7f..50ea96b 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ CFLAGS += -ffreestanding CFLAGS += -fno-common CFLAGS += -nostdlib CFLAGS += -mno-relax +CFLAGS += -std=gnu99 CFLAGS += -fno-stack-protector # Prevents code that needs libc / runtime support CFLAGS += -MD # Generate header dependency files (.d) @@ -44,7 +45,8 @@ KERNEL_OBJ := \ kern/libkern/panic.o \ kern/libkern/memory.o \ kern/libkern/spinlock.o \ - kern/libkern/mini-printf.o + kern/libkern/mini-printf.o \ + kern/libkern/stdio.o kern/kernel.elf: $(KERNEL_OBJ) @echo LD $@ diff --git a/kern/kalloc.c b/kern/kalloc.c index 17d65c7..88238ee 100644 --- a/kern/kalloc.c +++ b/kern/kalloc.c @@ -44,7 +44,7 @@ void kfree(void *pa) { // Assert that page is a ligned to a page boundary and that its correctly // sized if (((u64)pa % PGSIZE) != 0 || (char *)pa < kernel_end || (u64)pa >= PHYSTOP) - panic("kfree"); + PANIC("kfree"); // Fill with junk to catch dangling refs. memset(pa, 1, PGSIZE); diff --git a/kern/libkern/panic.c b/kern/libkern/panic.c index f0df22e..3671b04 100644 --- a/kern/libkern/panic.c +++ b/kern/libkern/panic.c @@ -1,8 +1,18 @@ +#include "stdbool.h" +#include +#include +#include +#include #include -volatile int panicked; -void panic(char *s) { - panicked = 1; - uart_puts(s); - while (1); +volatile int panicked = false; + +__attribute__((visibility("hidden"))) +void __panic(const char *restrict fmt, ...) { + va_list ap; + va_start(ap, fmt); + (void)mini_vpprintf(stdout_puts, NULL, fmt, ap); + va_end(ap); + panicked = true; + while (true) asm volatile("wfi"); } diff --git a/kern/libkern/panic.h b/kern/libkern/panic.h index 77cd640..53dc283 100644 --- a/kern/libkern/panic.h +++ b/kern/libkern/panic.h @@ -1,6 +1,8 @@ #ifndef KERNEL_PANIC_H #define KERNEL_PANIC_H -void panic(char *s); +#define PANIC(fmt, ...) __panic("[%s:%d %s] \n" fmt, __FILE__, __LINE__, __func__) + +void __panic(const char *restrict fmt, ...); #endif diff --git a/kern/libkern/proc.c b/kern/libkern/proc.c index 66bca67..bebf1a5 100644 --- a/kern/libkern/proc.c +++ b/kern/libkern/proc.c @@ -1,6 +1,6 @@ #include -struct Cpu cpus[NCPU]; +Cpu cpus[NCPU]; /** * Must be called with interrupts disabled, to prevent race with process being @@ -14,8 +14,8 @@ int cpuid() { /** * Return this CPU's cpu struct. Interrupts must be disabled. */ -struct Cpu *mycpu(void) { - int id = cpuid(); - struct Cpu *c = &cpus[id]; +Cpu *mycpu(void) { + int id = cpuid(); + Cpu *c = &cpus[id]; return c; } diff --git a/kern/libkern/spinlock.c b/kern/libkern/spinlock.c index f3b194a..7dc6658 100644 --- a/kern/libkern/spinlock.c +++ b/kern/libkern/spinlock.c @@ -3,8 +3,6 @@ * (Not mutexes as these are spinning locks). */ -// #include -#include "string.h" #include #include #include @@ -41,86 +39,42 @@ * 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. +/* + * These are from the original xv6 implementation, with only slight modifications on their return type. + * + * 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 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(); +uint32_t push_off(void) { + int old = intr_get(); + Cpu *cpu = mycpu(); intr_off(); - if (mycpu()->noff == 0) - mycpu()->intena = old; - mycpu()->noff += 1; + if (cpu->noff == 0) + cpu->intena = old; + + cpu->noff += 1; + return cpu->noff; } -void pop_off(void) { - struct Cpu *c = mycpu(); +uint32_t pop_off(void) { + Cpu *cpu = mycpu(); + if (intr_get()) - panic("pop_off - interruptible"); - if (c->noff < 1) { - { - // TODO: Remove this block when fixed - char amt[100]; - itoa(c->noff, amt, 10); - uart_puts(amt); - } - panic("pop_off"); - } - c->noff -= 1; - if (c->noff == 0 && c->intena) + PANIC("pop_off - interruptible"); + + if (cpu->noff < 1) + PANIC("pop_off"); + + cpu->noff -= 1; + + if (cpu->noff == 0 && cpu->intena) intr_on(); + + return cpu->noff; } void spinlock_init(spinlock_t *l) { @@ -135,34 +89,47 @@ __attribute__((warn_unused_result)) bool spin_trylock(spinlock_t *l) { } void spin_unlock(spinlock_t *l) { + // if (!spin_is_holding(l)) + // panic("spin_unlock"); + + l->cpu = 0; + // Release: store 0 with .rl ordering. uint32_t dummy; __asm__ volatile("amoswap.w.rl %0, %2, (%1)\n" : "=&r"(dummy) : "r"(&l->v), "r"(0u) : "memory"); + + // __sync_synchronize(); // No loads/stores after this point + // __sync_lock_release(&lk->locked); // Essentially lk->locked = 0 + + // pop_off(); } -// Optional: tiny pause/backoff (works even if Zihintpause isn't present). -// See: https://github.com/riscv/riscv-isa-manual/blob/main/src/zihintpause.adoc -static inline void cpu_relax(void) { -#if defined(__riscv_zihintpause) - __asm__ volatile("pause"); -#else - __asm__ volatile("nop"); -#endif -} - -// Test-and-test-and-set acquire with polite spinning + exponential backoff. +/** + * Test-and-test-and-set acquire with polite spinning + exponential backoff. + */ void spin_lock(spinlock_t *l) { - unsigned backoff = 1; + uint32_t backoff = 1; for (;;) { if (spin_trylock(l)) return; - // Contended: spin on plain loads (no AMO) until it looks free. while (__atomic_load_n(&l->v, __ATOMIC_RELAXED) != 0) { - for (unsigned i = 0; i < backoff; ++i) cpu_relax(); + for (uint32_t i = 0; i < backoff; ++i) + __asm__ volatile("nop"); /* NOTE: Pause can be used here if supported */ if (backoff < 1u << 12) backoff <<= 1; } - // Try again; loop. } + + l->cpu = mycpu(); +} + +/** + * Check whether this cpu is holding the lock. + * Interrupts must be off. + */ +bool spin_is_holding(spinlock_t *l) { + int r; + r = (l->v && l->cpu == mycpu()); + return r; } diff --git a/kern/libkern/spinlock.h b/kern/libkern/spinlock.h index 5561b98..de6091e 100644 --- a/kern/libkern/spinlock.h +++ b/kern/libkern/spinlock.h @@ -1,60 +1,22 @@ #ifndef KERNEL_Spinlock_H #define KERNEL_Spinlock_H +#include +#include #include -/** 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); - typedef struct { volatile uint32_t v; // 0 = unlocked, 1 = locked + Cpu *cpu; } spinlock_t; +uint32_t push_off(void); +uint32_t pop_off(void); + void spinlock_init(spinlock_t *l); bool spin_trylock(spinlock_t *l); void spin_unlock(spinlock_t *l); +bool spin_is_holding(spinlock_t *l); void spin_lock(spinlock_t *l); #endif diff --git a/kern/libkern/stddef.h b/kern/libkern/stddef.h new file mode 100644 index 0000000..ab8a2b5 --- /dev/null +++ b/kern/libkern/stddef.h @@ -0,0 +1,9 @@ +#ifndef STDDEF_H +#define STDDEF_H + +#ifndef NULL +#define NULL ((void*)0) +#endif + +#endif // STDDEF_H + diff --git a/kern/libkern/stdio.c b/kern/libkern/stdio.c new file mode 100644 index 0000000..d02e59b --- /dev/null +++ b/kern/libkern/stdio.c @@ -0,0 +1,20 @@ +#include +#include +#include + +int stdout_puts(char *s, int len, void *unused) { + (void)unused; + // Example: UART write loop + for (int i = 0; i < len; i++) { + uart_putc(s[i]); // <-- your low-level "put char" routine + } + return len; +} + +int kprintf(const char *restrict fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = mini_vpprintf(stdout_puts, NULL, fmt, ap); + va_end(ap); + return ret; +} diff --git a/kern/libkern/stdio.h b/kern/libkern/stdio.h new file mode 100644 index 0000000..2602c87 --- /dev/null +++ b/kern/libkern/stdio.h @@ -0,0 +1,18 @@ +#ifndef STDIO_H +#define STDIO_H + +int stdout_puts(char *s, int len, void *unused); +int kprintf(const char *restrict format, ...); + +// int fprintf(FILE *restrict stream, const char *restrict format, ...); +// int dprintf(int fd, const char *restrict format, ...); +// int sprintf(char *restrict str, const char *restrict format, ...); +// int snprintf(char str[restrict.size], size_t size, const char *restrict format, ...); + +// int vprintf(const char *restrict format, va_list ap); +// int vfprintf(FILE *restrict stream, const char *restrict format, va_list ap); +// int vdprintf(int fd, const char *restrict format, va_list ap); +// int vsprintf(char *restrict str, const char *restrict format, va_list ap); +// int vsnprintf(char str[restrict.size], size_t size, const char *restrict format, va_list ap); + +#endif // STDIO_H diff --git a/kern/libkern/uart.c b/kern/libkern/uart.c index bb753f3..df0af5d 100644 --- a/kern/libkern/uart.c +++ b/kern/libkern/uart.c @@ -4,7 +4,3 @@ void uart_putc(char c) { *UART_BASE = c; } - -void uart_puts(const char *s) { - while (*s) uart_putc(*s++); -} diff --git a/kern/libkern/uart.h b/kern/libkern/uart.h index 953c5f6..8f08f1a 100644 --- a/kern/libkern/uart.h +++ b/kern/libkern/uart.h @@ -4,7 +4,4 @@ /** Send a single character to the UART device */ void uart_putc(char c); -/** Send a **NULL TERMINATED** string to the UART device */ -void uart_puts(const char *s); - #endif diff --git a/kern/proc.h b/kern/proc.h index 37ab333..ac5a46d 100644 --- a/kern/proc.h +++ b/kern/proc.h @@ -1,6 +1,8 @@ +#ifndef PROC_H +#define PROC_H + #include #include -#include #include typedef enum { @@ -13,7 +15,7 @@ typedef enum { } ProcessState; /** Saved registers for kernel context switches. */ -struct Context { +typedef struct Context { uint64_t ra; uint64_t sp; @@ -30,18 +32,18 @@ struct Context { uint64_t s9; uint64_t s10; uint64_t s11; -}; +} Context; /** Per-CPU state. */ -struct Cpu { +typedef struct cpu_t { struct Process *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()? -}; +} Cpu; /** Saved registers for kernel context switches. */ -typedef struct { +typedef struct TrapFrame_t { /* 0 */ uint64_t kernel_satp; // kernel page table /* 8 */ uint64_t kernel_sp; // top of process's kernel stack /* 16 */ uint64_t kernel_trap; // usertrap() @@ -78,11 +80,13 @@ typedef struct { /* 264 */ uint64_t t4; /* 272 */ uint64_t t5; /* 280 */ uint64_t t6; -} TrapFrame_t; +} TrapFrame; -struct Cpu *mycpu(void); +Cpu *mycpu(void); -extern struct Cpu cpus[NCPU]; +extern Cpu cpus[NCPU]; /** Per-process state */ struct Proc {}; + +#endif diff --git a/kern/start.c b/kern/start.c index e717064..15a224f 100644 --- a/kern/start.c +++ b/kern/start.c @@ -1,10 +1,12 @@ #include #include #include +#include #include #include #include #include +#include #include /** @@ -37,30 +39,24 @@ void start() { if (id == 0) { /* Here we will do a bunch of initialization steps */ kalloc_init(); - uart_puts("Hello Neptune!\n"); spinlock_init(&sl); + kprintf("Hello Neptune!\n"); + + __sync_synchronize(); hold = 0; } else { while (hold); } - // spin_lock(&sl); - // - // uart_puts("Hart number: "); - // uart_putc(id + '0'); - // uart_putc('\n'); - // - // spin_unlock(&sl); - if (id == 0) { spin_lock(&sl); - uart_puts("Core count: "); - uart_putc(max_hart + '0'); - uart_putc('\n'); - if (max_hart == NCPU) { - uart_puts("All cores up!"); - uart_putc('\n'); - } + kprintf("Core count: %d\n", max_hart); + + if (max_hart == NCPU) + kprintf("All cores up!\n"); + else + PANIC("Some cores seem to have been enumerated incorrectly!\n"); + spin_unlock(&sl); }