Compare commits
14 commits
36cc060411
...
5948d6c8e8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5948d6c8e8 | ||
![]() |
a9f7cb8cf7 | ||
![]() |
cda703873b | ||
![]() |
ff3ad1e719 | ||
![]() |
eb0800c742 | ||
![]() |
28485acc8f | ||
![]() |
7ca69e391a | ||
![]() |
ca71791440 | ||
![]() |
bdf4228a18 | ||
![]() |
4512a93249 | ||
![]() |
50a3c8d1d9 | ||
![]() |
567e79a4e8 | ||
![]() |
e2a8bf287f | ||
![]() |
8959bc87b8 |
15 changed files with 397 additions and 26 deletions
|
@ -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
18
.clangd
Normal 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
|
11
Makefile
11
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
|
||||
|
|
8
config.h
Normal file
8
config.h
Normal 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
14
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
|
||||
|
|
19
lib/proc.c
Normal file
19
lib/proc.c
Normal 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
23
lib/proc.h
Normal 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
116
lib/spinlock.c
Normal 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
51
lib/spinlock.h
Normal 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
39
lib/string.c
Normal 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
7
lib/string.h
Normal 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
73
riscv.h
Normal 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
31
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 <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
6
types.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long u64;
|
Loading…
Add table
Reference in a new issue