mirror of
https://github.com/cccriscv/mini-riscv-os.git
synced 2025-11-16 12:34:33 +00:00
Add the block device driver, but hasn't been tested yet.
This commit is contained in:
41
08-BlockDeviceDriver/Makefile
Normal file
41
08-BlockDeviceDriver/Makefile
Normal 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
|
||||
74
08-BlockDeviceDriver/README.md
Normal file
74
08-BlockDeviceDriver/README.md
Normal 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).
|
||||
4
08-BlockDeviceDriver/gdbinit
Normal file
4
08-BlockDeviceDriver/gdbinit
Normal file
@@ -0,0 +1,4 @@
|
||||
set disassemble-next-line on
|
||||
b _start
|
||||
target remote : 1234
|
||||
c
|
||||
247
08-BlockDeviceDriver/lib.c
Normal file
247
08-BlockDeviceDriver/lib.c
Normal 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;
|
||||
}
|
||||
28
08-BlockDeviceDriver/lib.h
Normal file
28
08-BlockDeviceDriver/lib.h
Normal 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
|
||||
32
08-BlockDeviceDriver/lock.c
Normal file
32
08-BlockDeviceDriver/lock.c
Normal 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
42
08-BlockDeviceDriver/os.c
Normal 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
36
08-BlockDeviceDriver/os.h
Normal 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
|
||||
46
08-BlockDeviceDriver/os.ld
Normal file
46
08-BlockDeviceDriver/os.ld
Normal 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));
|
||||
}
|
||||
45
08-BlockDeviceDriver/plic.c
Normal file
45
08-BlockDeviceDriver/plic.c
Normal 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;
|
||||
}
|
||||
162
08-BlockDeviceDriver/riscv.h
Normal file
162
08-BlockDeviceDriver/riscv.h
Normal 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
|
||||
26
08-BlockDeviceDriver/start.s
Normal file
26
08-BlockDeviceDriver/start.s
Normal 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
08-BlockDeviceDriver/sys.h
Normal file
8
08-BlockDeviceDriver/sys.h
Normal 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
152
08-BlockDeviceDriver/sys.s
Normal 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
|
||||
|
||||
32
08-BlockDeviceDriver/task.c
Normal file
32
08-BlockDeviceDriver/task.c
Normal 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);
|
||||
}
|
||||
16
08-BlockDeviceDriver/task.h
Normal file
16
08-BlockDeviceDriver/task.h
Normal 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
|
||||
38
08-BlockDeviceDriver/timer.c
Normal file
38
08-BlockDeviceDriver/timer.c
Normal 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;
|
||||
}
|
||||
12
08-BlockDeviceDriver/timer.h
Normal file
12
08-BlockDeviceDriver/timer.h
Normal 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
|
||||
69
08-BlockDeviceDriver/trap.c
Normal file
69
08-BlockDeviceDriver/trap.c
Normal 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;
|
||||
}
|
||||
63
08-BlockDeviceDriver/user.c
Normal file
63
08-BlockDeviceDriver/user.c
Normal 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);
|
||||
}
|
||||
254
08-BlockDeviceDriver/virtio.c
Normal file
254
08-BlockDeviceDriver/virtio.c
Normal 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);
|
||||
}
|
||||
117
08-BlockDeviceDriver/virtio.h
Normal file
117
08-BlockDeviceDriver/virtio.h
Normal 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;
|
||||
Reference in New Issue
Block a user