This commit is contained in:
Austin
2021-09-08 10:31:37 +08:00
parent 72bc6f247c
commit 8a8776cab8
35 changed files with 2138 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
10-SystemCall/.DS_Store vendored Normal file

Binary file not shown.

54
10-SystemCall/Makefile Normal file
View File

@@ -0,0 +1,54 @@
CC = riscv64-unknown-elf-gcc
CFLAGS = -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -D CONFIG_SYSCALL
GDB = riscv64-unknown-elf-gdb
SOURCE = src/
OBJ = \
$(SOURCE)start.s \
$(SOURCE)sys.s \
$(SOURCE)mem.s \
$(SOURCE)lib.c \
$(SOURCE)timer.c \
$(SOURCE)os.c \
$(SOURCE)task.c \
$(SOURCE)user.c \
$(SOURCE)trap.c \
$(SOURCE)lock.c \
$(SOURCE)plic.c \
$(SOURCE)virtio.c \
$(SOURCE)string.c \
$(SOURCE)alloc.c \
$(SOURCE)syscall.c \
$(SOURCE)usys.s \
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 4 -machine virt -bios none
QFLAGS += -drive if=none,format=raw,file=hdd.dsk,id=x0
QFLAGS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
OBJDUMP = riscv64-unknown-elf-objdump
all: clean os.elf hdd.dsk qemu
test: clean os.elf qemu
os.elf: $(OBJ)
$(CC) $(CFLAGS) -T os.ld -o os.elf $^
qemu: $(TARGET) hdd.dsk
@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 *.img
hdd.dsk:
dd if=/dev/urandom of=hdd.dsk bs=1048576 count=32
.PHONY : debug
debug: clean os.elf hdd.dsk
@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

83
10-SystemCall/README.md Normal file
View File

@@ -0,0 +1,83 @@
# 09-MemoryAllocator
## Build & Run
```sh
IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/09-MemoryAllocator (feat/memoryAlloc)
$ make all
rm -f *.elf *.img
riscv64-unknown-elf-gcc -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -T os.ld -o os.elf src/start.s src/sys.s src/mem.s src/lib.c src/timer.c src/task.c src/os.c src/user.c src/trap.c src/lock.c src/plic.c src/virtio.c src/string.c src/alloc.c
Press Ctrl-A and then X to exit QEMU
qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf
HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 521967
TEXT: 0x80000000 -> 0x8000ac78
RODATA: 0x8000ac78 -> 0x8000b09f
DATA: 0x8000c000 -> 0x8000c004
BSS: 0x8000d000 -> 0x8001100c
HEAP: 0x80091100 -> 0x88000000
OS start
Disk init work is success!
buffer init...
block read...
Virtio IRQ
000000fd
000000af
000000f8
000000ab
00000088
00000042
000000cc
00000017
00000022
0000008e
p = 0x80091700
p2 = 0x80091300
p3 = 0x80091100
OS: Activate next task
Task0: Created!
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
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
10-SystemCall/gdbinit Normal file
View File

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

BIN
10-SystemCall/include/.DS_Store vendored Normal file

Binary file not shown.

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,42 @@
#ifndef __OS_H__
#define __OS_H__
#include "riscv.h"
#include "lib.h"
#include "task.h"
#include "timer.h"
#include "string.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);
// memory allocator
extern void *malloc(size_t size);
extern void free(void *p);
#endif

View File

@@ -0,0 +1,183 @@
#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
{
/* ignore x0 */
reg_t ra;
reg_t sp;
reg_t gp;
reg_t tp;
reg_t t0;
reg_t t1;
reg_t t2;
reg_t s0;
reg_t s1;
reg_t a0;
reg_t a1;
reg_t a2;
reg_t a3;
reg_t a4;
reg_t a5;
reg_t a6;
reg_t a7;
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;
reg_t t3;
reg_t t4;
reg_t t5;
reg_t t6;
// upon is trap frame
// save the pc to run in next schedule cycle
// reg_t pc; // offset: 31 *4 = 124
};
// 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 (3 << 11)
#define MSTATUS_SPP (1 << 8)
#define MSTATUS_MPIE (1 << 7)
#define MSTATUS_SPIE (1 << 5)
#define MSTATUS_UPIE (1 << 4)
#define MSTATUS_MIE (1 << 3)
#define MSTATUS_SIE (1 << 1)
#define MSTATUS_UIE (1 << 0)
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));
}
static inline reg_t r_mcause()
{
reg_t x;
asm volatile("csrr %0, mcause" : "=r" (x) );
return x;
}
#endif /* __RISCV_H__ */

View File

@@ -0,0 +1,10 @@
#ifndef __STRING_H__
#define __STRING_H__
void *memset(void *, int, unsigned int);
void *
memcpy(void *dst, const void *src, unsigned int n);
void *
memmove(void *dst, const void *src, unsigned int n);
#endif

View File

@@ -0,0 +1,9 @@
#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);
extern void switch_to(struct context *ctx_next);
#endif

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,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,13 @@
#ifndef __TYPES_H__
#define __TYPES_H__
typedef unsigned int uint;
typedef unsigned short ushort;
typedef unsigned char uchar;
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef unsigned long long uint64;
#endif

View File

@@ -0,0 +1,7 @@
#ifndef __USER_API_H__
#define __USER_API_H__
/* user mode syscall APIs */
extern int gethid(unsigned int *hid);
#endif /* __USER_API_H__ */

View File

@@ -0,0 +1,129 @@
#include "types.h"
/*
* 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_VENDOR_ID 0x00c // 0x554d4551
#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
#define VIRTIO_MMIO_QueueDescLow 0x080
#define VIRTIO_MMIO_QueueDescHigh 0x084
#define VIRTIO_MMIO_QueueAvailLow 0x090
#define VIRTIO_MMIO_QueueAvailHigh 0x094
#define VIRTIO_MMIO_QueueUsedLow 0x0a0
#define VIRTIO_MMIO_QueueUsedHigh 0x0a4
// 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 addr;
uint32 len;
uint16 flags;
uint16 next;
} virtq_desc_t;
#define VRING_DESC_F_NEXT 1 // chained with another descriptor
#define VRING_DESC_F_WRITE 2 // device writes (vs read)
#define VRING_DESC_F_INDIRECT 4 // buffer contains a list of buffer descriptors
/*
* 用來存放 descriptor 的索引,當 Device 收到通知時,它會檢查 AvailableRing 確認需要讀取哪些 Descriptor 。
* 注意: Descriptor 和 AvailableRing 都存儲在 RAM 中。
*/
typedef struct virtq_avail
{
uint16 flags; // always zero
uint16 idx; // driver will write ring[idx] next
uint16 ring[NUM]; // descriptor numbers of chain heads
} virtq_avail_t;
/*
* 當內部的 Index 與 UsedRing 的 Index 相等,代表所有資料都已經被讀取,這個 Device 是唯一需要被寫進 Index 的。
*/
typedef struct virtq_used_elem
{
uint32 id; // index of start of completed descriptor chain
uint32 len;
} virtq_used_elem_t;
/*
* Device 可以使用 UsedRing 向 OS 發送訊息。
* Device 通常使用它來告知 OS 它已完成先前通知的請求。
* AvailableRing 與 UsedRing 非常相似,差異在於: OS 需要查看 UsedRing 得知哪個 Descriptor 已經被服務。
*/
typedef struct virtq_used
{
uint16 flags; // always zero
uint16 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 type; // VIRTIO_BLK_T_IN or ..._OUT
uint32 reserved; // 將 Header 擴充到 16-byte ,並將 64-bit sector 移到正確的位置。
uint64 sector;
} virtio_blk_req_t;

49
10-SystemCall/os.ld Normal file
View File

@@ -0,0 +1,49 @@
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));
PROVIDE(_heap_start = _bss_end);
PROVIDE(_heap_size = _memory_end - _heap_start);
}

BIN
10-SystemCall/src/.DS_Store vendored Normal file

Binary file not shown.

215
10-SystemCall/src/alloc.c Normal file
View File

@@ -0,0 +1,215 @@
/* This Code derived from RVOS
* -- https://github.com/plctlab/riscv-operating-system-mooc
*/
#include "os.h"
extern uint32_t TEXT_START;
extern uint32_t TEXT_END;
extern uint32_t DATA_START;
extern uint32_t DATA_END;
extern uint32_t RODATA_START;
extern uint32_t RODATA_END;
extern uint32_t BSS_START;
extern uint32_t BSS_END;
extern uint32_t HEAP_START;
extern uint32_t HEAP_SIZE;
/*
* _alloc_start points to the actual start address of heap pool
* _alloc_end points to the actual end address of heap pool
* _num_pages holds the actual max number of pages we can allocate.
*/
static uint32_t _alloc_start = 0;
static uint32_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 256
#define PAGE_ORDER 8
#define PAGE_TAKEN (uint8_t)(1 << 0)
#define PAGE_LAST (uint8_t)(1 << 1)
#define pageNum(x) ((x) / ((PAGE_SIZE) + 1)) + 1
/*
* Page Descriptor
* flags:
* - 00: This means this page hasn't been allocated
* - 01: This means this page was allocated
* - 11: This means this page was allocated and is the last page of the memory block allocated
*/
struct Page
{
uint8_t flags;
};
static inline void _clear(struct Page *page)
{
page->flags = 0;
}
static inline int _is_free(struct Page *page)
{
if (page->flags & PAGE_TAKEN)
{
return 0;
}
else
{
return 1;
}
}
static inline void _set_flag(struct Page *page, uint8_t flags)
{
page->flags |= flags;
}
static inline int _is_last(struct Page *page)
{
if (page->flags & PAGE_LAST)
{
return 1;
}
else
{
return 0;
}
}
/*
* align the address to the border of page (256)
*/
static inline uint32_t _align_page(uint32_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
void page_init()
{
_num_pages = (HEAP_SIZE / PAGE_SIZE) - 2048;
lib_printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages);
struct Page *page = (struct Page *)HEAP_START;
for (int i = 0; i < _num_pages; i++)
{
_clear(page);
page++;
}
_alloc_start = _align_page(HEAP_START + 2048 * PAGE_SIZE);
_alloc_end = _alloc_start + (PAGE_SIZE * _num_pages);
lib_printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END);
lib_printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END);
lib_printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END);
lib_printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END);
lib_printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end);
}
/*
* Allocate a memory block which is composed of contiguous physical pages
* - npages: the number of PAGE_SIZE pages to allocate
*/
void *malloc(size_t size)
{
int npages = pageNum(size);
int found = 0;
struct Page *page_i = (struct Page *)HEAP_START;
for (int i = 0; i < (_num_pages - npages); i++)
{
if (_is_free(page_i))
{
found = 1;
/*
* meet a free page, continue to check if following
* (npages - 1) pages are also unallocated.
*/
struct Page *page_j = page_i;
for (int j = i; j < (i + npages); j++)
{
if (!_is_free(page_j))
{
found = 0;
break;
}
page_j++;
}
/*
* get a memory block which is good enough for us,
* take housekeeping, then return the actual start
* address of the first page of this memory block
*/
if (found)
{
struct Page *page_k = page_i;
for (int k = i; k < (i + npages); k++)
{
_set_flag(page_k, PAGE_TAKEN);
page_k++;
}
page_k--;
_set_flag(page_k, PAGE_LAST);
return (void *)(_alloc_start + i * PAGE_SIZE);
}
}
page_i++;
}
return NULL;
}
/*
* Free the memory block
* - p: start address of the memory block
*/
void free(void *p)
{
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end)
{
return;
}
/* get the first page descriptor of this memory block */
struct Page *page = (struct Page *)HEAP_START;
page += ((uint32_t)p - _alloc_start) / PAGE_SIZE;
/* loop and clear all the page descriptors of the memory block */
while (!_is_free(page))
{
if (_is_last(page))
{
_clear(page);
break;
}
else
{
_clear(page);
page++;
;
}
}
}
void page_test()
{
void *p = malloc(1024);
lib_printf("p = 0x%x\n", p);
void *p2 = malloc(512);
lib_printf("p2 = 0x%x\n", p2);
void *p3 = malloc(sizeof(int));
lib_printf("p3 = 0x%x\n", p3);
free(p);
free(p2);
free(p3);
}

247
10-SystemCall/src/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;
}

32
10-SystemCall/src/lock.c Normal file
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);
}

30
10-SystemCall/src/mem.s Normal file
View File

@@ -0,0 +1,30 @@
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
.global TEXT_START
TEXT_START: .word _text_start
.global TEXT_END
TEXT_END: .word _text_end
.global DATA_START
DATA_START: .word _data_start
.global DATA_END
DATA_END: .word _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
.global BSS_START
BSS_START: .word _bss_start
.global BSS_END
BSS_END: .word _bss_end

52
10-SystemCall/src/os.c Normal file
View File

@@ -0,0 +1,52 @@
#include "os.h"
#include "riscv.h"
extern void page_init(void);
extern void trap_init(void);
void panic(char *s)
{
lib_puts(s);
for (;;)
{
}
}
void os_kernel()
{
task_os();
}
void disk_read()
{
virtio_tester(0);
}
void os_start()
{
uart_init();
page_init();
lib_puts("OS start\n");
user_init();
trap_init();
plic_init();
virtio_disk_init();
timer_init(); // start timer interrupt ...
}
int os_main(void)
{
os_start();
disk_read();
page_test();
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;
}

48
10-SystemCall/src/plic.c Normal file
View File

@@ -0,0 +1,48 @@
#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;
*(uint32_t *)PLIC_PRIORITY(VIRTIO_IRQ) = 1;
/* Enable UART0 and VIRTIO */
*(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO_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;
}

34
10-SystemCall/src/start.s Normal file
View File

@@ -0,0 +1,34 @@
.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
# li t0, 1 << 7
# li t0, 0xffffffff
# csrw pmpaddr0, t0
# li t0, 0xf
# csrw pmpcfg0, t0
# csrr a1, mstatus
# or t0, t0, a1
# csrw mstatus, t0
# 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,40 @@
#include "string.h"
void *memset(void *dst, int c, unsigned int n)
{
char *cdst = (char *)dst;
int i;
for (i = 0; i < n; i++)
{
cdst[i] = c;
}
return dst;
}
void *
memcpy(void *dst, const void *src, unsigned int n)
{
return memmove(dst, src, n);
}
void *
memmove(void *dst, const void *src, unsigned int n)
{
const char *s;
char *d;
s = src;
d = dst;
if (s < d && s + n > d)
{
s += n;
d += n;
while (n-- > 0)
*--d = *--s;
}
else
while (n-- > 0)
*d++ = *s++;
return dst;
}

185
10-SystemCall/src/sys.s Normal file
View File

@@ -0,0 +1,185 @@
# 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)
sw a0, 56(\base)
sw a7, 60(\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)
lw a0, 56(\base)
lw a7, 60(\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)
.endm
.macro reg_load base
# restore registers.
lw ra, 0(\base)
lw sp, 4(\base)
lw gp, 8(\base)
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.
.text
.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
# save mepc to context of current task
csrr a0, mepc
sw a0, 124(t6)
# call the C trap handler in trap.c
csrr a0, mepc
csrr a1, mcause
csrr a2, mscratch
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
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
switch_to:
# switch mscratch to point to the context of the next task
csrw mscratch, a0
# set mepc to the pc of the next task
lw a1, 124(a0)
csrw mepc, a1
# Restore all GP registers
# Use t6 to point to the context of the new task
mv t6, a0
reg_load t6
# Do actual context switching.
# Notice this will enable global interrupt
mret
.end

View File

@@ -0,0 +1,30 @@
#include "os.h"
int sys_gethid(unsigned int *ptr_hid)
{
lib_printf("--> sys_gethid, arg0 = 0x%x\n", ptr_hid);
if (ptr_hid == NULL) {
lib_printf("ptr_hid == NULL\n");
return -1;
} else {
lib_printf("ptr_hid != NULL\n");
*ptr_hid = r_mhartid();
return 0;
}
}
void do_syscall(struct context *ctx)
{
uint32_t syscall_num = ctx->a7;
lib_printf("syscall_num: %d\n", syscall_num);
switch (syscall_num) {
case 1:
ctx->a0 = sys_gethid((unsigned int *)(ctx->a0));
break;
default:
lib_printf("Unknown syscall no: %d\n", syscall_num);
ctx->a0 = -1;
}
return;
}

35
10-SystemCall/src/task.c Normal file
View File

@@ -0,0 +1,35 @@
#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].pc = (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];
// switch_to(ctx_now);
sys_switch(&ctx_os, &ctx_tasks[i]);
}
// switch back to os
void task_os()
{
struct context *ctx = ctx_now;
ctx_now = &ctx_os;
// switch_to(&ctx_os);
sys_switch(ctx, &ctx_os);
}

39
10-SystemCall/src/timer.c Normal file
View File

@@ -0,0 +1,39 @@
#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();
//lib_printf("hid: %d\n", id);
*(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval;
}

101
10-SystemCall/src/trap.c Normal file
View File

@@ -0,0 +1,101 @@
#include "os.h"
#include "riscv.h"
extern void trap_vector();
extern void virtio_disk_isr();
extern void do_syscall(struct context *ctx);
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 == VIRTIO_IRQ)
{
lib_puts("Virtio IRQ\n");
virtio_disk_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, struct context *ctx)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
if (cause & 0x80000000)
{
/* Asynchronous trap - interrupt */
switch (cause_code)
{
case 3:
/* software interruption */
break;
case 7:
/* timer interruption */
// 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:
/* external interruption */
external_handler();
break;
default:
lib_puts("unknown async exception!\n");
break;
}
}
else
{
switch (cause_code)
{
case 2:
lib_puts("Illegal instruction!\n");
break;
case 5:
lib_puts("Fault load!\n");
break;
case 7:
lib_puts("Fault store!\n");
break;
case 8:
lib_puts("Environment call from U-mode!\n");
do_syscall(ctx);
return_pc += 4;
break;
case 11:
lib_puts("Environment call from M-mode!\n");
do_syscall(ctx);
return_pc += 4;
break;
default:
/* Synchronous trap - exception */
lib_printf("Sync exceptions! cause code: %d\n", cause_code);
break;
}
// for (;;)
// {
/* code */
// }
}
return return_pc;
}

91
10-SystemCall/src/user.c Normal file
View File

@@ -0,0 +1,91 @@
#include "os.h"
#include "user_api.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_task4(void)
{
lib_puts("Task4: Created!\n");
unsigned int hid = -1;
/*
* if syscall is supported, this will trigger exception,
* code = 2 (Illegal instruction)
*/
// hid = r_mhartid();
// lib_printf("hart id is %d\n", hid);
// perform system call from the user mode
int ret = -1;
ret = gethid(&hid);
// ret = gethid(NULL);
if (!ret) {
lib_printf("system call returned!, hart id is %d\n", hid);
} else {
lib_printf("gethid() failed, return: %d\n", ret);
}
while (1)
{
lib_puts("Task4: Running...\n");
lib_delay(1000);
}
}
void user_init()
{
task_create(&user_task0);
task_create(&user_task4);
// task_create(&user_task1);
}

6
10-SystemCall/src/usys.s Normal file
View File

@@ -0,0 +1,6 @@
.global gethid
gethid:
li a7, 1
ecall
ret

312
10-SystemCall/src/virtio.c Normal file
View File

@@ -0,0 +1,312 @@
/* This Code derived from xv6-riscv (64bit)
* -- https://github.com/mit-pdos/xv6-riscv/
*/
#include "virtio.h"
#include "os.h"
#define R(addr) ((volatile uint32 *)(VIRTIO_MMIO_BASE + (addr)))
#define BSIZE 512 // block size
#define PGSHIFT 12
struct blk
{
uint32 dev;
uint32 blockno;
lock_t lock;
int disk;
unsigned char data[BSIZE];
};
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];
struct
{
struct blk *b;
char status;
} info[NUM];
uint16 used_idx;
/* Disk command headers */
virtio_blk_req_t ops[NUM];
struct lock vdisk_lock;
} __attribute__((aligned(PGSIZE))) disk;
struct blk b[3];
void virtio_tester(int write)
{
if (!b[0].dev)
{
lib_puts("buffer init...\n");
for (size_t i = 0; i < 1; i++)
{
b[i].dev = 1; // always is 1
b[i].blockno = 1;
for (size_t j = 0; j < BSIZE; j++)
{
b[i].data[j] = 0;
}
lock_init(&(b[i].lock));
}
}
lib_puts("block read...\n");
virtio_disk_rw(&b[0], write);
size_t i = 0;
for (; i < 10; i++)
{
lib_printf("%x \n", b[0].data[i]);
}
lib_puts("\n");
}
void virtio_disk_init()
{
uint32 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");
}
/* Reset the device */
*R(VIRTIO_MMIO_STATUS) = status;
/* 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 */
uint32 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)
panic("virtio disk has no queue 0\n");
if (max < NUM)
panic("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) = ((uint32)disk.pages) / PGSIZE;
*R(VIRTIO_MMIO_QUEUE_ALIGN) = PGSIZE;
*R(VIRTIO_MMIO_QUEUE_READY) = 1;
// 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(virtq_desc_t));
disk.used = (struct virtq_used *)(disk.pages + PGSIZE);
*R(VIRTIO_MMIO_QueueDescLow) = disk.desc;
*R(VIRTIO_MMIO_QueueAvailLow) = disk.avail;
*R(VIRTIO_MMIO_QueueUsedLow) = disk.used;
// 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;
}
// 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 blk *b, int write)
{
uint64 sector = b->blockno * (BSIZE / 512);
lock_acquire(&disk.vdisk_lock);
// 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.
virtio_blk_req_t *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; // The reserved portion is used to pad the header to 16 bytes and move the 32-bit sector field to the correct place.
buf0->sector = sector; // specify the sector that we wanna modified.
disk.desc[idx[0]].addr = 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 = ((uint32)b->data) & 0xffffffff;
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 = 0;
disk.desc[idx[2]].addr = (uint32)&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;
__sync_synchronize();
// 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 ...
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // The device starts immediately when we write 0 to queue_notify.
while (b->disk == 1)
{
}
disk.info[idx[0]].b = 0;
free_chain(idx[0]);
lock_free(&disk.vdisk_lock);
}
void virtio_disk_isr()
{
//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 blk *b = disk.info[id].b;
b->disk = 0;
disk.used_idx += 1;
}
//lock_free(&disk.vdisk_lock);
}

View File

@@ -45,7 +45,8 @@ And you should start your git-bash to build the project. (It works for me in vsc
- Learning VirtIO Protocol & Device driver implementation - Learning VirtIO Protocol & Device driver implementation
- [09-MemoryAllocator](09-MemoryAllocator) - [09-MemoryAllocator](09-MemoryAllocator)
- Understanding how to write the linker script & how the heap works - Understanding how to write the linker script & how the heap works
- [10-SystemCall](10-SystemCall)
- Invoking a mini ecall from machine mode.
## Building and Verification ## Building and Verification
- Changes the current working directory to the specified one and then - Changes the current working directory to the specified one and then