This commit is contained in:
ccckmit
2020-11-14 11:31:33 +08:00
commit 5eec97bef9
69 changed files with 2775 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: start.s sys.s lib.c timer.c os.c
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET)
@qemu-system-riscv32 -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
$(QEMU) $(QFLAGS) -kernel os.elf
clean:
rm -f *.elf

View File

@@ -0,0 +1,25 @@
# 04-TimerInterrupt
## Build & Run
```
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
$ make clean
rm -f *.elf
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -T os.ld -o os.elf start.s sys.s lib.c timer.c os.c
user@DESKTOP-96FRN6B MINGW64 /d/ccc109/sp/11-os/mini-riscv-os/04-TimerInterrupt (master)
$ make qemu
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel os.elf
OS start
timer_handler: 1
timer_handler: 2
timer_handler: 3
timer_handler: 4
timer_handler: 5
QEMU: Terminated
```

146
04-TimerInterrupt/lib.c Normal file
View File

@@ -0,0 +1,146 @@
#include "lib.h"
void lib_delay(volatile int count)
{
count *= 50000;
while (count--);
}
int lib_putc(char ch) {
while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0);
return *UART_THR = ch;
}
void lib_puts(char *s) {
while (*s) lib_putc(*s++);
}
int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl)
{
int format = 0;
int longarg = 0;
size_t pos = 0;
for( ; *s; s++) {
if (format) {
switch(*s) {
case 'l': {
longarg = 1;
break;
}
case 'p': {
longarg = 1;
if (out && pos < n) {
out[pos] = '0';
}
pos++;
if (out && pos < n) {
out[pos] = 'x';
}
pos++;
}
case 'x': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1;
for(int i = hexdigits; i >= 0; i--) {
int d = (num >> (4*i)) & 0xF;
if (out && pos < n) {
out[pos] = (d < 10 ? '0'+d : 'a'+d-10);
}
pos++;
}
longarg = 0;
format = 0;
break;
}
case 'd': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
if (num < 0) {
num = -num;
if (out && pos < n) {
out[pos] = '-';
}
pos++;
}
long digits = 1;
for (long nn = num; nn /= 10; digits++)
;
for (int i = digits-1; i >= 0; i--) {
if (out && pos + i < n) {
out[pos + i] = '0' + (num % 10);
}
num /= 10;
}
pos += digits;
longarg = 0;
format = 0;
break;
}
case 's': {
const char* s2 = va_arg(vl, const char*);
while (*s2) {
if (out && pos < n) {
out[pos] = *s2;
}
pos++;
s2++;
}
longarg = 0;
format = 0;
break;
}
case 'c': {
if (out && pos < n) {
out[pos] = (char)va_arg(vl,int);
}
pos++;
longarg = 0;
format = 0;
break;
}
default:
break;
}
}
else if(*s == '%') {
format = 1;
}
else {
if (out && pos < n) {
out[pos] = *s;
}
pos++;
}
}
if (out && pos < n) {
out[pos] = 0;
}
else if (out && n) {
out[n-1] = 0;
}
return pos;
}
static char out_buf[1000]; // buffer for lib_vprintf()
int lib_vprintf(const char* s, va_list vl)
{
int res = lib_vsnprintf(NULL, -1, s, vl);
if (res+1 >= sizeof(out_buf)) {
lib_puts("error: lib_vprintf() output string size overflow\n");
while(1) {}
}
lib_vsnprintf(out_buf, res + 1, s, vl);
lib_puts(out_buf);
return res;
}
int lib_printf(const char* s, ...)
{
int res = 0;
va_list vl;
va_start(vl, s);
res = lib_vprintf(s, vl);
va_end(vl);
return res;
}

17
04-TimerInterrupt/lib.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __LIB_H__
#define __LIB_H__
#include "riscv.h"
#include <stddef.h>
#include <stdarg.h>
#define lib_error(...) { lib_printf(__VA_ARGS__); while(1) {} } }
extern void lib_delay(volatile int count);
extern int lib_putc(char ch);
extern void lib_puts(char *s);
extern int lib_printf(const char* s, ...);
extern int lib_vprintf(const char* s, va_list vl);
extern int lib_vsnprintf(char * out, size_t n, const char* s, va_list vl);
#endif

15
04-TimerInterrupt/os.c Normal file
View File

@@ -0,0 +1,15 @@
#include "os.h"
void os_start() {
lib_puts("OS start\n");
// user_init();
timer_init(); // start timer interrupt ...
}
int os_main(void)
{
os_start();
while (1) {} // stop here !
return 0;
}

12
04-TimerInterrupt/os.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
#include "timer.h"
extern void user_init();
extern void os_kernel();
extern int os_main(void);
#endif

46
04-TimerInterrupt/os.ld Normal file
View File

@@ -0,0 +1,46 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss :{
PROVIDE(_bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
}

116
04-TimerInterrupt/riscv.h Normal file
View File

@@ -0,0 +1,116 @@
#ifndef __RISCV_H__
#define __RISCV_H__
#include <stdint.h>
#define reg_t uint32_t // RISCV32: register is 32bits
// define reg_t as uint64_t // RISCV64: register is 64bits
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000
#define UART_THR (uint8_t*)(UART+0x00) // THR:transmitter holding register
#define UART_LSR (uint8_t*)(UART+0x05) // LSR:line status register
#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty
// Saved registers for kernel context switches.
struct context {
reg_t ra;
reg_t sp;
// callee-saved
reg_t s0;
reg_t s1;
reg_t s2;
reg_t s3;
reg_t s4;
reg_t s5;
reg_t s6;
reg_t s7;
reg_t s8;
reg_t s9;
reg_t s10;
reg_t s11;
};
// ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h
//
// local interrupt controller, which contains the timer.
// ================== Timer Interrput ====================
#define NCPU 8 // maximum number of CPUs
#define CLINT 0x2000000
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.
// which hart (core) is this?
static inline reg_t r_mhartid()
{
reg_t x;
asm volatile("csrr %0, mhartid" : "=r" (x) );
return x;
}
// Machine Status Register, mstatus
#define MSTATUS_MPP_MASK (3 << 11) // previous mode.
#define MSTATUS_MPP_M (3 << 11)
#define MSTATUS_MPP_S (1 << 11)
#define MSTATUS_MPP_U (0 << 11)
#define MSTATUS_MIE (1 << 3) // machine-mode interrupt enable.
static inline reg_t r_mstatus()
{
reg_t x;
asm volatile("csrr %0, mstatus" : "=r" (x) );
return x;
}
static inline void w_mstatus(reg_t x)
{
asm volatile("csrw mstatus, %0" : : "r" (x));
}
// machine exception program counter, holds the
// instruction address to which a return from
// exception will go.
static inline void w_mepc(reg_t x)
{
asm volatile("csrw mepc, %0" : : "r" (x));
}
static inline reg_t r_mepc()
{
reg_t x;
asm volatile("csrr %0, mepc" : "=r" (x));
return x;
}
// Machine Scratch register, for early trap handler
static inline void w_mscratch(reg_t x)
{
asm volatile("csrw mscratch, %0" : : "r" (x));
}
// Machine-mode interrupt vector
static inline void w_mtvec(reg_t x)
{
asm volatile("csrw mtvec, %0" : : "r" (x));
}
// Machine-mode Interrupt Enable
#define MIE_MEIE (1 << 11) // external
#define MIE_MTIE (1 << 7) // timer
#define MIE_MSIE (1 << 3) // software
static inline reg_t r_mie()
{
reg_t x;
asm volatile("csrr %0, mie" : "=r" (x) );
return x;
}
static inline void w_mie(reg_t x)
{
asm volatile("csrw mie, %0" : : "r" (x));
}
#endif

26
04-TimerInterrupt/start.s Normal file
View File

@@ -0,0 +1,26 @@
.equ STACK_SIZE, 8192
.global _start
_start:
# setup stacks per hart
csrr t0, mhartid # read current hart id
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
# park harts with id != 0
csrr a0, mhartid # read current hart id
bnez a0, park # if we're not on the hart 0
# we park the hart
j os_main # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * 4 # allocate space for the harts stacks

8
04-TimerInterrupt/sys.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __SYS_H__
#define __SYS_H__
#include "riscv.h"
extern void sys_timer();
extern void sys_switch(struct context *ctx_old, struct context *ctx_new);
#endif

163
04-TimerInterrupt/sys.s Normal file
View File

@@ -0,0 +1,163 @@
# This Code derived from xv6-riscv (64bit)
# -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S
# ============ MACRO ==================
.macro ctx_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw s0, 8(\base)
sw s1, 12(\base)
sw s2, 16(\base)
sw s3, 20(\base)
sw s4, 24(\base)
sw s5, 28(\base)
sw s6, 32(\base)
sw s7, 36(\base)
sw s8, 40(\base)
sw s9, 44(\base)
sw s10, 48(\base)
sw s11, 52(\base)
.endm
.macro ctx_load base
lw ra, 0(\base)
lw sp, 4(\base)
lw s0, 8(\base)
lw s1, 12(\base)
lw s2, 16(\base)
lw s3, 20(\base)
lw s4, 24(\base)
lw s5, 28(\base)
lw s6, 32(\base)
lw s7, 36(\base)
lw s8, 40(\base)
lw s9, 44(\base)
lw s10, 48(\base)
lw s11, 52(\base)
.endm
.macro reg_save base
# save the registers.
sw ra, 0(\base)
sw sp, 4(\base)
sw gp, 8(\base)
sw tp, 12(\base)
sw t0, 16(\base)
sw t1, 20(\base)
sw t2, 24(\base)
sw s0, 28(\base)
sw s1, 32(\base)
sw a0, 36(\base)
sw a1, 40(\base)
sw a2, 44(\base)
sw a3, 48(\base)
sw a4, 52(\base)
sw a5, 56(\base)
sw a6, 60(\base)
sw a7, 64(\base)
sw s2, 68(\base)
sw s3, 72(\base)
sw s4, 76(\base)
sw s5, 80(\base)
sw s6, 84(\base)
sw s7, 88(\base)
sw s8, 92(\base)
sw s9, 96(\base)
sw s10, 100(\base)
sw s11, 104(\base)
sw t3, 108(\base)
sw t4, 112(\base)
sw t5, 116(\base)
sw t6, 120(\base)
.endm
.macro reg_load base
# restore registers.
lw ra, 0(\base)
lw sp, 4(\base)
lw gp, 8(\base)
# not this, in case we moved CPUs: lw tp, 12(\base)
lw t0, 16(\base)
lw t1, 20(\base)
lw t2, 24(\base)
lw s0, 28(\base)
lw s1, 32(\base)
lw a0, 36(\base)
lw a1, 40(\base)
lw a2, 44(\base)
lw a3, 48(\base)
lw a4, 52(\base)
lw a5, 56(\base)
lw a6, 60(\base)
lw a7, 64(\base)
lw s2, 68(\base)
lw s3, 72(\base)
lw s4, 76(\base)
lw s5, 80(\base)
lw s6, 84(\base)
lw s7, 88(\base)
lw s8, 92(\base)
lw s9, 96(\base)
lw s10, 100(\base)
lw s11, 104(\base)
lw t3, 108(\base)
lw t4, 112(\base)
lw t5, 116(\base)
lw t6, 120(\base)
.endm
# ============ Macro END ==================
# Context switch
#
# void sys_switch(struct context *old, struct context *new);
#
# Save current registers in old. Load from new.
.globl sys_switch
.align 4
sys_switch:
ctx_save a0 # a0 => struct context *old
ctx_load a1 # a1 => struct context *new
ret # pc=ra; swtch to new task (new->ra)
.globl sys_kernel
.align 4
sys_kernel:
addi sp, sp, -128
reg_save sp
call timer_handler # context switch ...
reg_load sp
addi sp, sp, 128
jr a7 # jump to a7=mepc , return to timer break point
.globl sys_timer
.align 4
sys_timer:
# timer_init() has set up the memory that mscratch points to:
# scratch[0,4,8] : register save area.
# scratch[12] : address of CLINT's MTIMECMP register.
# scratch[16] : desired interval between interrupts.
csrrw a0, mscratch, a0 # exchange(mscratch,a0)
sw a1, 0(a0)
sw a2, 4(a0)
sw a3, 8(a0)
# schedule the next timer interrupt
# by adding interval to mtimecmp.
lw a1, 12(a0) # CLINT_MTIMECMP(hart)
lw a2, 16(a0) # interval
lw a3, 0(a1) # a3 = CLINT_MTIMECMP(hart)
add a3, a3, a2 # a3 += interval
sw a3, 0(a1) # CLINT_MTIMECMP(hart) = a3
csrr a7, mepc # a7 = mepc, for sys_kernel jump back to interrupted point
la a1, sys_kernel # mepc = sys_kernel
csrw mepc, a1 # mret : will jump to sys_kernel
lw a3, 8(a0)
lw a2, 4(a0)
lw a1, 0(a0)
csrrw a0, mscratch, a0 # exchange(mscratch,a0)
mret # jump to mepc (=sys_kernel)

41
04-TimerInterrupt/timer.c Normal file
View File

@@ -0,0 +1,41 @@
#include "timer.h"
extern void os_kernel();
// a scratch area per CPU for machine-mode timer interrupts.
reg_t timer_scratch[NCPU][5];
void timer_init()
{
// each CPU has a separate source of timer interrupts.
int id = r_mhartid();
// ask the CLINT for a timer interrupt.
int interval = 10000000; // cycles; about 1 second in qemu.
*(reg_t*)CLINT_MTIMECMP(id) = *(reg_t*)CLINT_MTIME + interval;
// prepare information in scratch[] for timervec.
// scratch[0..2] : space for timervec to save registers.
// scratch[3] : address of CLINT MTIMECMP register.
// scratch[4] : desired interval (in cycles) between timer interrupts.
reg_t *scratch = &timer_scratch[id][0];
scratch[3] = CLINT_MTIMECMP(id);
scratch[4] = interval;
w_mscratch((reg_t)scratch);
// set the machine-mode trap handler.
w_mtvec((reg_t)sys_timer);
// enable machine-mode interrupts.
w_mstatus(r_mstatus() | MSTATUS_MIE);
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
}
static int timer_count = 0;
void timer_handler() {
lib_printf("timer_handler: %d\n", ++timer_count);
// os_kernel();
}

11
04-TimerInterrupt/timer.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef __TIMER_H__
#define __TIMER_H__
#include "riscv.h"
#include "sys.h"
#include "lib.h"
extern void timer_handler();
extern void timer_init();
#endif