Add the block device driver, but hasn't been tested yet.

This commit is contained in:
ianchen0119
2021-06-21 17:58:29 +08:00
parent 3db6f7ecf8
commit 07ab6ae0b3
22 changed files with 1544 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall
GDB = riscv64-unknown-elf-gdb
OBJ = \
start.s \
sys.s \
lib.c \
timer.c \
task.c \
os.c \
user.c \
trap.c \
lock.c \
plic.c \
virtio.c
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 1 -machine virt -bios none
OBJDUMP = riscv64-unknown-elf-objdump
all: os.elf
os.elf: $(OBJ)
$(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
.PHONY : debug
debug: all
@echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU"
@echo "-------------------------------------------------------"
@${QEMU} ${QFLAGS} -kernel os.elf -s -S &
@${GDB} os.elf -q -x ./gdbinit

View File

@@ -0,0 +1,74 @@
# 07-ExternInterrupt
## Build & Run
```sh
IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/07-ExterInterrupt (feat/getchar)
$ make
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c plic.c
IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/07-ExterInterrupt (feat/getchar)
$ 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
OS: Activate next task
Task0: Created!
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
external interruption!
j
Task0: Running...
Task0: Running...
external interruption!
k
Task0: Running...
Task0: Running...
Task0: Running...
external interruption!
j
Task0: Running...
external interruption!
k
external interruption!
j
Task0: Running...
timer interruption!
timer_handler: 1
OS: Back to OS
QEMU: Terminated
```
## Debug mode
```sh
make debug
riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c
Press Ctrl-C and then input 'quit' to exit GDB and QEMU
-------------------------------------------------------
Reading symbols from os.elf...
Breakpoint 1 at 0x80000000: file start.s, line 7.
0x00001000 in ?? ()
=> 0x00001000: 97 02 00 00 auipc t0,0x0
Thread 1 hit Breakpoint 1, _start () at start.s:7
7 csrr t0, mhartid # read current hart id
=> 0x80000000 <_start+0>: f3 22 40 f1 csrr t0,mhartid
(gdb)
```
### set the breakpoint
You can set the breakpoint in any c file:
```sh
(gdb) b trap.c:27
Breakpoint 2 at 0x80008f78: file trap.c, line 27.
(gdb)
```
As the example above, when process running on trap.c, line 27 (Timer Interrupt).
The process will be suspended automatically until you press the key `c` (continue) or `s` (step).

View File

@@ -0,0 +1,4 @@
set disassemble-next-line on
b _start
target remote : 1234
c

247
08-BlockDeviceDriver/lib.c Normal file
View File

@@ -0,0 +1,247 @@
#include "lib.h"
#define LSR_RX_READY (1 << 0)
#define EOF 0
void uart_init()
{
/* disable interrupts */
UART_REGW(UART_IER, 0x00);
/* Baud rate setting */
uint8_t lcr = UART_REGR(UART_LCR);
UART_REGW(UART_LCR, lcr | (1 << 7));
UART_REGW(UART_DLL, 0x03);
UART_REGW(UART_DLM, 0x00);
lcr = 0;
UART_REGW(UART_LCR, lcr | (3 << 0));
uint8_t ier = UART_REGR(UART_IER);
UART_REGW(UART_IER, ier | (1 << 0));
}
char *lib_gets(char *s)
{
int ch;
char *p = s;
while ((ch = lib_getc()) != '\n' && ch != EOF)
{
if (ch == -1)
{
continue;
}
*s = (char)ch;
s++;
}
*s = '\0';
return p;
}
int lib_getc(void)
{
if (*UART_LSR & LSR_RX_READY)
{
return *UART_RHR == '\r' ? '\n' : *UART_RHR;
}
else
{
return -1;
}
}
void lib_isr(void)
{
for (;;)
{
int c = lib_getc();
if (c == -1)
{
break;
}
else
{
lib_putc((char)c);
lib_putc('\n');
}
}
}
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;
}

View File

@@ -0,0 +1,28 @@
#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 char *lib_gets(char *);
extern void uart_init();
extern void lib_isr(void);
extern int lib_getc(void);
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

View File

@@ -0,0 +1,32 @@
#include "os.h"
void lock_init(lock_t *lock)
{
lock->locked = 0;
}
void lock_acquire(lock_t *lock)
{
for (;;)
{
if (!atomic_swap(lock))
{
break;
}
}
}
void lock_free(lock_t *lock)
{
lock->locked = 0;
}
void basic_lock()
{
w_mstatus(r_mstatus() & ~MSTATUS_MIE);
}
void basic_unlock()
{
w_mstatus(r_mstatus() | MSTATUS_MIE);
}

42
08-BlockDeviceDriver/os.c Normal file
View File

@@ -0,0 +1,42 @@
#include "os.h"
extern void trap_init(void);
void panic(char *s)
{
lib_puts(s);
for (;;)
{
}
}
void os_kernel()
{
task_os();
}
void os_start()
{
uart_init();
lib_puts("OS start\n");
user_init();
trap_init();
plic_init();
timer_init(); // start timer interrupt ...
}
int os_main(void)
{
os_start();
int current_task = 0;
while (1)
{
lib_puts("OS: Activate next task\n");
task_go(current_task);
lib_puts("OS: Back to OS\n");
current_task = (current_task + 1) % taskTop; // Round Robin Scheduling
lib_puts("\n");
}
return 0;
}

36
08-BlockDeviceDriver/os.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
#include "task.h"
#include "timer.h"
extern void panic(char *);
extern void user_init();
extern void os_kernel();
extern int os_main(void);
// PLIC
extern void plic_init();
extern int plic_claim();
extern void plic_complete(int);
// lock
extern void basic_lock();
extern void basic_unlock();
typedef struct lock
{
volatile int locked;
} lock_t;
extern int atomic_swap(lock_t *);
extern void lock_init(lock_t *lock);
extern void lock_acquire(lock_t *lock);
extern void lock_free(lock_t *lock);
#endif

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

View File

@@ -0,0 +1,45 @@
#include "os.h"
// ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h
// Intro: https://github.com/ianchen0119/AwesomeCS/wiki/2-5-RISC-V::%E4%B8%AD%E6%96%B7%E8%88%87%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86----PLIC-%E4%BB%8B%E7%B4%B9
#define PLIC_BASE 0x0c000000L
#define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4)
#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32))
#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80)
#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000)
#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000)
#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000)
void plic_init()
{
int hart = r_tp();
// QEMU Virt machine support 7 priority (1 - 7),
// The "0" is reserved, and the lowest priority is "1".
*(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1;
/* Enable UART0 */
*(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ);
/* Set priority threshold for UART0. */
*(uint32_t *)PLIC_MTHRESHOLD(hart) = 0;
/* enable machine-mode external interrupts. */
w_mie(r_mie() | MIE_MEIE);
// enable machine-mode interrupts.
w_mstatus(r_mstatus() | MSTATUS_MIE);
}
int plic_claim()
{
int hart = r_tp();
int irq = *(uint32_t *)PLIC_MCLAIM(hart);
return irq;
}
void plic_complete(int irq)
{
int hart = r_tp();
*(uint32_t *)PLIC_MCOMPLETE(hart) = irq;
}

View File

@@ -0,0 +1,162 @@
#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
#define PGSIZE 4096 // bytes per page
// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000L
#define UART_THR (volatile uint8_t *)(UART + 0x00) // THR:transmitter holding register
#define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR:Receive holding register
#define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of Divisor Latch (write mode)
#define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of Divisor Latch (write mode)
#define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register
#define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register
#define UART_LSR (volatile 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
#define UART_REGR(reg) (*(reg))
#define UART_REGW(reg, v) ((*reg) = (v))
// ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h
// enum {
// UART0_IRQ = 10,
// RTC_IRQ = 11,
// VIRTIO_IRQ = 1, /* 1 to 8 */
// VIRTIO_COUNT = 8,
// PCIE_IRQ = 0x20, /* 32 to 35 */
// VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */
// };
#define UART0_IRQ 10
#define VIRTIO_IRQ 1
// 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.
static inline reg_t r_tp()
{
reg_t x;
asm volatile("mv %0, tp"
: "=r"(x));
return x;
}
// 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

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

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

152
08-BlockDeviceDriver/sys.s Normal file
View File

@@ -0,0 +1,152 @@
# 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 atomic_swap
.align 4
atomic_swap:
li a5, 1
amoswap.w.aq a5, a5, 0(a0)
mv a0, a5
ret
.globl trap_vector
# the trap vector base address must always be aligned on a 4-byte boundary
.align 4
trap_vector:
# save context(registers).
csrrw t6, mscratch, t6 # swap t6 and mscratch
reg_save t6
csrw mscratch, t6
# call the C trap handler in trap.c
csrr a0, mepc
csrr a1, mcause
call trap_handler
# trap_handler will return the return address via a0.
csrw mepc, a0
# load context(registers).
csrr t6, mscratch
reg_load t6
mret

View File

@@ -0,0 +1,32 @@
#include "task.h"
#include "lib.h"
uint8_t task_stack[MAX_TASK][STACK_SIZE];
struct context ctx_os;
struct context ctx_tasks[MAX_TASK];
struct context *ctx_now;
int taskTop = 0; // total number of task
// create a new task
int task_create(void (*task)(void))
{
int i = taskTop++;
ctx_tasks[i].ra = (reg_t)task;
ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1];
return i;
}
// switch to task[i]
void task_go(int i)
{
ctx_now = &ctx_tasks[i];
sys_switch(&ctx_os, &ctx_tasks[i]);
}
// switch back to os
void task_os()
{
struct context *ctx = ctx_now;
ctx_now = &ctx_os;
sys_switch(ctx, &ctx_os);
}

View File

@@ -0,0 +1,16 @@
#ifndef __TASK_H__
#define __TASK_H__
#include "riscv.h"
#include "sys.h"
#define MAX_TASK 10
#define STACK_SIZE 1024
extern int taskTop;
extern int task_create(void (*task)(void));
extern void task_go(int i);
extern void task_os();
#endif

View File

@@ -0,0 +1,38 @@
#include "timer.h"
// a scratch area per CPU for machine-mode timer interrupts.
reg_t timer_scratch[NCPU][5];
#define interval 20000000 // cycles; about 2 second in qemu.
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 = 1000000; // cycles; about 1/10th 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);
// 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);
int id = r_mhartid();
*(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval;
}

View File

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

View File

@@ -0,0 +1,69 @@
#include "os.h"
extern void trap_vector();
void trap_init()
{
// set the machine-mode trap handler.
w_mtvec((reg_t)trap_vector);
}
void external_handler()
{
int irq = plic_claim();
if (irq == UART0_IRQ)
{
lib_isr();
}
else if (irq)
{
lib_printf("unexpected interrupt irq = %d\n", irq);
}
if (irq)
{
plic_complete(irq);
}
}
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
if (cause & 0x80000000)
{
/* Asynchronous trap - interrupt */
switch (cause_code)
{
case 3:
lib_puts("software interruption!\n");
break;
case 7:
lib_puts("timer interruption!\n");
// disable machine-mode timer interrupts.
w_mie(r_mie() & ~(1 << 7));
timer_handler();
return_pc = (reg_t)&os_kernel;
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
break;
case 11:
lib_puts("external interruption!\n");
external_handler();
break;
default:
lib_puts("unknown async exception!\n");
break;
}
}
else
{
/* Synchronous trap - exception */
lib_puts("Sync exceptions!\n");
while (1)
{
/* code */
}
}
return return_pc;
}

View File

@@ -0,0 +1,63 @@
#include "os.h"
int shared_var = 500;
lock_t lock;
void user_task0(void)
{
lib_puts("Task0: Created!\n");
while (1)
{
lib_puts("Task0: Running...\n");
lib_delay(1000);
}
}
void user_task1(void)
{
lib_puts("Task1: Created!\n");
while (1)
{
lib_puts("Task1: Running...\n");
lib_delay(1000);
}
}
void user_task2(void)
{
lib_puts("Task2: Created!\n");
while (1)
{
for (int i = 0; i < 50; i++)
{
lock_acquire(&lock);
shared_var++;
lock_free(&lock);
lib_delay(100);
}
lib_printf("The value of shared_var is: %d \n", shared_var);
}
}
void user_task3(void)
{
lib_puts("Task3: Created!\n");
while (1)
{
lib_puts("Trying to get the lock... \n");
lock_acquire(&lock);
lib_puts("Get the lock!\n");
lock_free(&lock);
lib_delay(1000);
}
}
void user_init()
{
lock_init(&lock);
task_create(&user_task0);
task_create(&user_task1);
task_create(&user_task2);
task_create(&user_task3);
}

View File

@@ -0,0 +1,254 @@
#include "virtio.h"
#include "os.h"
#define R(addr) ((volatile uint32_t *)(VIRTIO_MMIO_BASE + (addr)))
static struct disk
{
char pages[2 * PGSIZE];
/* descriptor */
virtq_desc_t *desc;
/* AvailableRing */
virtq_avail_t *avail;
/* UsedRing */
virtq_used_t *used;
/* For decord each descriptor is free or not */
char free[NUM];
/* Disk command headers */
virtio_blk_req_t ops[NUM];
struct lock vdisk_lock;
} __attribute__((aligned(PGSIZE))) disk;
void virtio_disk_init(){
uint32_t status = 0;
lock_init(&disk.vdisk_lock);
if(*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 ||
*R(VIRTIO_MMIO_VERSION) != 1 ||
*R(VIRTIO_MMIO_DEVICE_ID) != 2 ||
*R(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551){
panic("could not find virtio disk");
}
/* Set the ACKNOWLEDGE status bit to the status register. */
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
*R(VIRTIO_MMIO_STATUS) = status;
/* Set the DRIVER status bit to the status register. */
status |= VIRTIO_CONFIG_S_DRIVER;
*R(VIRTIO_MMIO_STATUS) = status;
/* negotiate features */
uint64 features = *R(VIRTIO_MMIO_DEVICE_FEATURES);
features &= ~(1 << VIRTIO_BLK_F_RO);
features &= ~(1 << VIRTIO_BLK_F_SCSI);
features &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE);
features &= ~(1 << VIRTIO_BLK_F_MQ);
features &= ~(1 << VIRTIO_F_ANY_LAYOUT);
features &= ~(1 << VIRTIO_RING_F_EVENT_IDX);
features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC);
*R(VIRTIO_MMIO_DRIVER_FEATURES) = features;
/* tell device that feature negotiation is complete. */
status |= VIRTIO_CONFIG_S_FEATURES_OK;
*R(VIRTIO_MMIO_STATUS) = status;
/* tell device we're completely ready. */
status |= VIRTIO_CONFIG_S_DRIVER_OK;
*R(VIRTIO_MMIO_STATUS) = status;
*R(VIRTIO_MMIO_GUEST_PAGE_SIZE) = PGSIZE;
/* initialize queue 0. */
*R(VIRTIO_MMIO_QUEUE_SEL) = 0;
uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX);
if (max == 0)
lib_puts("virtio disk has no queue 0\n");
if (max < NUM)
lib_puts("virtio disk max queue too short\n");
*R(VIRTIO_MMIO_QUEUE_NUM) = NUM;
memset(disk.pages, 0, sizeof(disk.pages));
*R(VIRTIO_MMIO_QUEUE_PFN) = ((uint64)disk.pages) >> PGSHIFT;
// desc = pages -- num * virtq_desc
// avail = pages + 0x40 -- 2 * uint16, then num * uint16
// used = pages + 4096 -- 2 * uint16, then num * vRingUsedElem
disk.desc = (struct virtq_desc *)disk.pages;
disk.avail = (struct virtq_avail *)(disk.pages + NUM * sizeof(struct virtq_desc));
disk.used = (struct virtq_used *)(disk.pages + PGSIZE);
// all NUM descriptors start out unused.
for (int i = 0; i < NUM; i++)
disk.free[i] = 1;
lib_puts("Disk init work is success!\n");
}
// find a free descriptor, mark it non-free, return its index.
static int
alloc_desc()
{
for(int i = 0; i < NUM; i++){
if(disk.free[i]){
disk.free[i] = 0;
return i;
}
}
return -1;
}
// mark a descriptor as free.
static void
free_desc(int i)
{
if(i >= NUM)
panic("free_desc 1");
if(disk.free[i])
panic("free_desc 2");
disk.desc[i].addr = 0;
disk.desc[i].len = 0;
disk.desc[i].flags = 0;
disk.desc[i].next = 0;
disk.free[i] = 1;
wakeup(&disk.free[0]);
}
// free a chain of descriptors.
static void
free_chain(int i)
{
while(1){
int flag = disk.desc[i].flags;
int nxt = disk.desc[i].next;
free_desc(i);
if(flag & VRING_DESC_F_NEXT)
i = nxt;
else
break;
}
}
// allocate three descriptors (they need not be contiguous).
// disk transfers always use three descriptors.
static int
alloc3_desc(int *idx)
{
for(int i = 0; i < 3; i++){
idx[i] = alloc_desc();
if(idx[i] < 0){
for(int j = 0; j < i; j++)
free_desc(idx[j]);
return -1;
}
}
return 0;
}
void
virtio_disk_rw(struct buf *b, int write)
{
uint64 sector = b->blockno * (BSIZE / 512);
lock_acquire(&disk.vdisk_lock);
// the spec's Section 5.2 says that legacy block operations use
// three descriptors: one for type/reserved/sector, one for the
// data, one for a 1-byte status result.
// allocate the three descriptors.
int idx[3];
while(1){
if(alloc3_desc(idx) == 0) {
break;
}
}
// format the three descriptors.
// qemu's virtio-blk.c reads them.
struct virtio_blk_req *buf0 = &disk.ops[idx[0]];
if(write)
buf0->type = VIRTIO_BLK_T_OUT; // write the disk
else
buf0->type = VIRTIO_BLK_T_IN; // read the disk
buf0->reserved = 0;
buf0->sector = sector;
disk.desc[idx[0]].addr = (uint64) buf0;
disk.desc[idx[0]].len = sizeof(struct virtio_blk_req);
disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
disk.desc[idx[0]].next = idx[1];
disk.desc[idx[1]].addr = (uint64) b->data;
disk.desc[idx[1]].len = BSIZE;
if(write)
disk.desc[idx[1]].flags = 0; // device reads b->data
else
disk.desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data
disk.desc[idx[1]].flags |= VRING_DESC_F_NEXT;
disk.desc[idx[1]].next = idx[2];
disk.info[idx[0]].status = 0xff; // device writes 0 on success
disk.desc[idx[2]].addr = (uint64) &disk.info[idx[0]].status;
disk.desc[idx[2]].len = 1;
disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; // device writes the status
disk.desc[idx[2]].next = 0;
// record struct buf for virtio_disk_intr().
b->disk = 1;
disk.info[idx[0]].b = b;
// tell the device the first index in our chain of descriptors.
disk.avail->ring[disk.avail->idx % NUM] = idx[0];
__sync_synchronize();
// tell the device another avail ring entry is available.
disk.avail->idx += 1; // not % NUM ...
__sync_synchronize();
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number
// Wait for virtio_disk_intr() to say request has finished.
while(b->disk == 1) {
}
disk.info[idx[0]].b = 0;
free_chain(idx[0]);
lock_free(&disk.vdisk_lock);
}
void
virtio_disk_intr()
{
lock_acquire(&disk.vdisk_lock);
// the device won't raise another interrupt until we tell it
// we've seen this interrupt, which the following line does.
// this may race with the device writing new entries to
// the "used" ring, in which case we may process the new
// completion entries in this interrupt, and have nothing to do
// in the next interrupt, which is harmless.
*R(VIRTIO_MMIO_INTERRUPT_ACK) = *R(VIRTIO_MMIO_INTERRUPT_STATUS) & 0x3;
__sync_synchronize();
// the device increments disk.used->idx when it
// adds an entry to the used ring.
while(disk.used_idx != disk.used->idx){
__sync_synchronize();
int id = disk.used->ring[disk.used_idx % NUM].id;
if(disk.info[id].status != 0)
panic("virtio_disk_intr status");
struct buf *b = disk.info[id].b;
b->disk = 0; // disk is done with buf
wakeup(b);
disk.used_idx += 1;
}
lock_free(&disk.vdisk_lock);
}

View File

@@ -0,0 +1,117 @@
/*
* References:
* 1. https://github.com/ianchen0119/xv6-riscv/blob/riscv/kernel/virtio.h
* 2. https://github.com/sgmarz/osblog/blob/master/risc_v/src/virtio.rs
*/
#define VIRTIO_MMIO_BASE 0x10001000
/* OFFSET */
#define VIRTIO_MMIO_MAGIC_VALUE 0x000 // Magic value must be 0x74726976
#define VIRTIO_MMIO_VERSION 0x004 // Version: 1 (Legacy)
/*
* Device ID:
* 1 (Network Device)
* 2 (Block Device)
* 4 (Random number generator Device)
* 16 (GPU Device)
* 18 (Input Device)
*/
#define VIRTIO_MMIO_DEVICE_ID 0x008
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 // page size for PFN, write-only
#define VIRTIO_MMIO_QUEUE_SEL 0x030 // select queue, write-only
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 // max size of current queue, read-only
#define VIRTIO_MMIO_QUEUE_NUM 0x038 // size of current queue, write-only
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c // used ring alignment, write-only
#define VIRTIO_MMIO_QUEUE_PFN 0x040 // physical page number for queue, read/write
#define VIRTIO_MMIO_QUEUE_READY 0x044 // ready bit
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 // write-only
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 // read-only
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 // write-only
#define VIRTIO_MMIO_STATUS 0x070 // read/write
// status register bits, from qemu virtio_config.h
#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
#define VIRTIO_CONFIG_S_DRIVER 2
#define VIRTIO_CONFIG_S_DRIVER_OK 4
#define VIRTIO_CONFIG_S_FEATURES_OK 8
// device feature bits
#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */
#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */
#define VIRTIO_BLK_F_CONFIG_WCE 11 /* Writeback mode available in config */
#define VIRTIO_BLK_F_MQ 12 /* support more than one vq */
#define VIRTIO_F_ANY_LAYOUT 27
#define VIRTIO_RING_F_INDIRECT_DESC 28
#define VIRTIO_RING_F_EVENT_IDX 29
// this many virtio descriptors.
#define NUM 8
// 描述符包含這些訊息: 地址,地址長度,某些標誌和其他信息。使用此描述符,我們可以將設備指向 RAM 中任何緩衝區的內存地址。
typedef struct virtq_desc
{
/*
* addr: 我們可以在 64-bit 內存地址內的任何位置告訴設備存儲位置。
* len: 讓 Device 知道有多少內存可用。
* flags: 用於控制 descriptor 。
* next: 告訴 Device 下一個描述符的 Index 。如果指定了 VIRTQ_DESC_F_NEXT Device 僅讀取該字段。否則無效。
*/
uint64_t addr;
uint32_t len;
uint16_t flags;
uint16_t next;
} virtq_desc_t;
#define VRING_DESC_F_NEXT 1 // chained with another descriptor
#define VRING_DESC_F_WRITE 2 // device writes (vs read)
/*
* 用來存放 descriptor 的索引,當 Device 收到通知時,它會檢查 AvailableRing 確認需要讀取哪些 Descriptor 。
* 注意: Descriptor 和 AvailableRing 都存儲在 RAM 中。
*/
typedef struct virtq_avail
{
uint16_t flags; // always zero
uint16_t idx; // driver will write ring[idx] next
uint16_t ring[NUM]; // descriptor numbers of chain heads
uint16_t unused;
} virtq_avail_t;
/*
* 當內部的 Index 與 UsedRing 的 Index 相等,代表所有資料都已經被讀取,這個 Device 是唯一需要被寫進 Index 的。
*/
typedef struct virtq_used_elem
{
uint32_t id; // index of start of completed descriptor chain
uint32_t len;
} virtq_used_elem_t;
/*
* Device 可以使用 UsedRing 向 OS 發送訊息。
* Device 通常使用它來告知 OS 它已完成先前通知的請求。
* AvailableRing 與 UsedRing 非常相似,差異在於: OS 需要查看 UsedRing 得知哪個 Descriptor 已經被服務。
*/
typedef struct virtq_used
{
uint16_t flags; // always zero
uint16_t idx; // device increments when it adds a ring[] entry
struct virtq_used_elem ring[NUM];
} virtq_used_t;
// these are specific to virtio block devices, e.g. disks,
// described in Section 5.2 of the spec.
#define VIRTIO_BLK_T_IN 0 // read the disk
#define VIRTIO_BLK_T_OUT 1 // write the disk
// the format of the first descriptor in a disk request.
// to be followed by two more descriptors containing
// the block, and a one-byte status.
typedef struct virtio_blk_req
{
uint32_t type; // VIRTIO_BLK_T_IN or ..._OUT
uint32_t reserved; // 將 Header 擴充到 16-byte ,並將 64-bit sector 移到正確的位置。
uint64_t sector;
} virtio_blk_req_t;