Compare commits

...

14 commits

15 changed files with 397 additions and 26 deletions

View file

@ -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

18
.clangd Normal file
View file

@ -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

View file

@ -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

8
config.h Normal file
View file

@ -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

14
entry.S
View file

@ -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

View file

19
lib/proc.c Normal file
View file

@ -0,0 +1,19 @@
#include <proc.h>
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;
}

23
lib/proc.h Normal file
View file

@ -0,0 +1,23 @@
#include <config.h>
#include <lib/spinlock.h>
#include <riscv.h>
#include <types.h>
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 {};

116
lib/spinlock.c Normal file
View file

@ -0,0 +1,116 @@
/**
* Mutual exclusion spin locks.
* (Not mutexes as these are spinning locks).
*/
// #include <lib/stdio.h>
#include <proc.h>
#include <riscv.h>
#include <spinlock.h>
// 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();
}

51
lib/spinlock.h Normal file
View file

@ -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

39
lib/string.c Normal file
View file

@ -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;
}

7
lib/string.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef KERNEL_STRING_H
#define KERNEL_STRING_H
/** Integer to ascii */
char *itoa(int value, char *str, int base);
#endif

73
riscv.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef RISCV_KERNEL_H
#define RISCV_KERNEL_H
#include <types.h>
// 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

31
start.c
View file

@ -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 <config.h>
#include <riscv.h>
#include <spinlock.h>
#include <types.h>
/* 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 */

6
types.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long u64;