mirror of
https://github.com/plctlab/riscv-operating-system-mooc.git
synced 2025-12-26 09:09:11 +00:00
initial versioin
This commit is contained in:
61
code/os/03-contextswitch/Makefile
Normal file
61
code/os/03-contextswitch/Makefile
Normal file
@@ -0,0 +1,61 @@
|
||||
CROSS_COMPILE = riscv64-unknown-elf-
|
||||
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall
|
||||
|
||||
QEMU = qemu-system-riscv32
|
||||
QFLAGS = -nographic -smp 1 -machine virt -bios none
|
||||
|
||||
GDB = ${CROSS_COMPILE}gdb
|
||||
CC = ${CROSS_COMPILE}gcc
|
||||
OBJCOPY = ${CROSS_COMPILE}objcopy
|
||||
OBJDUMP = ${CROSS_COMPILE}objdump
|
||||
|
||||
SRCS_ASM = \
|
||||
start.S \
|
||||
mem.S \
|
||||
entry.S \
|
||||
|
||||
SRCS_C = \
|
||||
kernel.c \
|
||||
uart.c \
|
||||
printf.c \
|
||||
page.c \
|
||||
sched.c \
|
||||
|
||||
OBJS = $(SRCS_ASM:.S=.o)
|
||||
OBJS += $(SRCS_C:.c=.o)
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
all: os.elf
|
||||
|
||||
# start.o must be the first in dependency!
|
||||
os.elf: ${OBJS}
|
||||
${CC} $(CFLAGS) -T os.ld -o os.elf $^
|
||||
${OBJCOPY} -O binary os.elf os.bin
|
||||
|
||||
%.o : %.c
|
||||
${CC} ${CFLAGS} -c -o $@ $<
|
||||
|
||||
%.o : %.S
|
||||
${CC} ${CFLAGS} -c -o $@ $<
|
||||
|
||||
run: all
|
||||
@${QEMU} -M ? | grep virt >/dev/null || exit
|
||||
@echo "Press Ctrl-A and then X to exit QEMU"
|
||||
@echo "------------------------------------"
|
||||
@${QEMU} ${QFLAGS} -kernel os.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
|
||||
|
||||
.PHONY : code
|
||||
code: all
|
||||
@${OBJDUMP} -S os.elf | less
|
||||
|
||||
.PHONY : clean
|
||||
clean:
|
||||
rm -rf *.o *.bin *.elf
|
||||
|
||||
106
code/os/03-contextswitch/entry.S
Normal file
106
code/os/03-contextswitch/entry.S
Normal file
@@ -0,0 +1,106 @@
|
||||
# save all General-Purpose(GP) registers to context
|
||||
# struct context *base = &ctx_task;
|
||||
# base->ra = ra;
|
||||
# ......
|
||||
.macro reg_save base
|
||||
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
|
||||
|
||||
# restore all General-Purpose(GP) registers from the context
|
||||
# struct context *base = &ctx_task;
|
||||
# ra = base->ra;
|
||||
# ......
|
||||
.macro reg_restore base
|
||||
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
|
||||
|
||||
# Something to note about save/restore:
|
||||
# - We use mscratch to hold a pointer to context of previous task
|
||||
# - We use t6 as the 'base' for reg_save/reg_restore, because it is the
|
||||
# very bottom register (x31) and would not be overwritten during loading.
|
||||
|
||||
.text
|
||||
|
||||
# void switch_to(struct context *next);
|
||||
# a0: pointer to the context of the next task
|
||||
.globl switch_to
|
||||
.align 4
|
||||
switch_to:
|
||||
csrrw t6, mscratch, t6 # swap t6 and mscratch
|
||||
beqz t6, 1f # Notice: previous task may be NULL
|
||||
reg_save t6 # save context of prev task
|
||||
|
||||
1:
|
||||
# switch mscratch to point to the context of the next task
|
||||
csrw mscratch, a0
|
||||
|
||||
# Restore all GP registers
|
||||
# Use t6 to point to the context of the new task
|
||||
mv t6, a0
|
||||
reg_restore t6
|
||||
|
||||
# Do actual context switching.
|
||||
ret
|
||||
|
||||
.end
|
||||
|
||||
26
code/os/03-contextswitch/kernel.c
Normal file
26
code/os/03-contextswitch/kernel.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "os.h"
|
||||
|
||||
/*
|
||||
* Following functions SHOULD be called ONLY ONE time here,
|
||||
* so just declared here ONCE and NOT included in file os.h.
|
||||
*/
|
||||
extern void uart_init(void);
|
||||
extern void page_init(void);
|
||||
extern void sched_init(void);
|
||||
extern void schedule(void);
|
||||
|
||||
void start_kernel(void)
|
||||
{
|
||||
uart_init();
|
||||
uart_puts("Hello, RVOS!\n");
|
||||
|
||||
page_init();
|
||||
|
||||
sched_init();
|
||||
|
||||
schedule();
|
||||
|
||||
uart_puts("Would not go here!\n");
|
||||
while (1) {}; // stop here!
|
||||
}
|
||||
|
||||
30
code/os/03-contextswitch/mem.S
Normal file
30
code/os/03-contextswitch/mem.S
Normal 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
|
||||
61
code/os/03-contextswitch/os.h
Normal file
61
code/os/03-contextswitch/os.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef __OS_H__
|
||||
#define __OS_H__
|
||||
|
||||
#include "types.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* uart */
|
||||
extern int uart_putc(char ch);
|
||||
extern void uart_puts(char *s);
|
||||
|
||||
/* printf */
|
||||
extern int printf(const char* s, ...);
|
||||
extern void panic(char *s);
|
||||
|
||||
/* memory management */
|
||||
extern void *page_alloc(int npages);
|
||||
extern void page_free(void *p);
|
||||
|
||||
/* task management */
|
||||
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;
|
||||
};
|
||||
|
||||
extern int task_create(void (*task)(void));
|
||||
extern void task_delay(volatile int count);
|
||||
|
||||
#endif /* __OS_H__ */
|
||||
136
code/os/03-contextswitch/os.ld
Normal file
136
code/os/03-contextswitch/os.ld
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* rvos.ld
|
||||
* Linker script for outputting to RVOS
|
||||
*/
|
||||
|
||||
/*
|
||||
* https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html
|
||||
* OUTPUT_ARCH command specifies a particular output machine architecture.
|
||||
* "riscv" is the name of the architecture for both 64-bit and 32-bit
|
||||
* RISC-V target. We will further refine this by using -march=rv32ima
|
||||
* and -mabi=ilp32 when calling gcc.
|
||||
*/
|
||||
OUTPUT_ARCH( "riscv" )
|
||||
|
||||
/*
|
||||
* https://sourceware.org/binutils/docs/ld/Entry-Point.html
|
||||
* ENTRY command is used to set the "entry point", which is the first instruction
|
||||
* to execute in a program.
|
||||
* The argument of ENTRY command is a symbol name, here is "_start" which is
|
||||
* defined in start.S.
|
||||
*/
|
||||
ENTRY( _start )
|
||||
|
||||
/*
|
||||
* https://sourceware.org/binutils/docs/ld/MEMORY.html
|
||||
* The MEMORY command describes the location and size of blocks of memory in
|
||||
* the target.
|
||||
* The syntax for MEMORY is:
|
||||
* MEMORY
|
||||
* {
|
||||
* name [(attr)] : ORIGIN = origin, LENGTH = len
|
||||
* ......
|
||||
* }
|
||||
* Each line defines a memory region.
|
||||
* Each memory region must have a distinct name within the MEMORY command. Here
|
||||
* we only define one region named as "ram".
|
||||
* The "attr" string is an optional list of attributes that specify whether to
|
||||
* use a particular memory region for an input section which is not explicitly
|
||||
* mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable),
|
||||
* and 'a' (allocatable). We use '!' to invert 'r' (read-only) and
|
||||
* 'i' (initialized).
|
||||
* The "ORIGIN" is used to set the start address of the memory region. Here we
|
||||
* place it right at the beginning of 0x8000_0000 because this is where the
|
||||
* QEMU-virt machine will start executing.
|
||||
* Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
|
||||
* The linker will double check this to make sure everything can fit.
|
||||
*/
|
||||
MEMORY
|
||||
{
|
||||
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
|
||||
}
|
||||
|
||||
/*
|
||||
* https://sourceware.org/binutils/docs/ld/SECTIONS.html
|
||||
* The SECTIONS command tells the linker how to map input sections into output
|
||||
* sections, and how to place the output sections in memory.
|
||||
* The format of the SECTIONS command is:
|
||||
* SECTIONS
|
||||
* {
|
||||
* sections-command
|
||||
* sections-command
|
||||
* ......
|
||||
* }
|
||||
*
|
||||
* Each sections-command may of be one of the following:
|
||||
* (1) an ENTRY command
|
||||
* (2) a symbol assignment
|
||||
* (3) an output section description
|
||||
* (4) an overlay description
|
||||
* We here only demo (2) & (3).
|
||||
*
|
||||
* We use PROVIDE command to define symbols.
|
||||
* https://sourceware.org/binutils/docs/ld/PROVIDE.html
|
||||
* The PROVIDE keyword may be used to define a symbol.
|
||||
* The syntax is PROVIDE(symbol = expression).
|
||||
* Such symbols as "_text_start", "_text_end" ... will be used in mem.S.
|
||||
* Notice the period '.' tells the linker to set symbol(e.g. _text_start) to
|
||||
* the CURRENT location ('.' = current memory location). This current memory
|
||||
* location moves as we add things.
|
||||
*/
|
||||
SECTIONS
|
||||
{
|
||||
/*
|
||||
* We are going to layout all text sections in .text output section,
|
||||
* starting with .text. The asterisk("*") in front of the
|
||||
* parentheses means to match the .text section of ANY object file.
|
||||
*/
|
||||
.text : {
|
||||
PROVIDE(_text_start = .);
|
||||
*(.text .text.*)
|
||||
PROVIDE(_text_end = .);
|
||||
} >ram
|
||||
|
||||
.rodata : {
|
||||
PROVIDE(_rodata_start = .);
|
||||
*(.rodata .rodata.*)
|
||||
PROVIDE(_rodata_end = .);
|
||||
} >ram
|
||||
|
||||
.data : {
|
||||
/*
|
||||
* . = ALIGN(4096) tells the linker to align the current memory
|
||||
* location to 4096 bytes. This will insert padding bytes until
|
||||
* current location becomes aligned on 4096-byte boundary.
|
||||
* This is because our paging system's resolution is 4,096 bytes.
|
||||
*/
|
||||
. = ALIGN(4096);
|
||||
PROVIDE(_data_start = .);
|
||||
/*
|
||||
* sdata and data are essentially the same thing. We do not need
|
||||
* to distinguish sdata from data.
|
||||
*/
|
||||
*(.sdata .sdata.*)
|
||||
*(.data .data.*)
|
||||
PROVIDE(_data_end = .);
|
||||
} >ram
|
||||
|
||||
.bss :{
|
||||
/*
|
||||
* https://sourceware.org/binutils/docs/ld/Input-Section-Common.html
|
||||
* In most cases, common symbols in input files will be placed
|
||||
* in the ‘.bss’ section in the output file.
|
||||
*/
|
||||
PROVIDE(_bss_start = .);
|
||||
*(.sbss .sbss.*)
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
PROVIDE(_bss_end = .);
|
||||
} >ram
|
||||
|
||||
PROVIDE(_memory_start = ORIGIN(ram));
|
||||
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
|
||||
|
||||
PROVIDE(_heap_start = _bss_end);
|
||||
PROVIDE(_heap_size = _memory_end - _heap_start);
|
||||
}
|
||||
189
code/os/03-contextswitch/page.c
Normal file
189
code/os/03-contextswitch/page.c
Normal file
@@ -0,0 +1,189 @@
|
||||
#include "os.h"
|
||||
|
||||
/*
|
||||
* Following global vars are defined in mem.S
|
||||
*/
|
||||
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 4096
|
||||
#define PAGE_ORDER 12
|
||||
|
||||
#define PAGE_TAKEN (uint8_t)(1 << 0)
|
||||
#define PAGE_LAST (uint8_t)(1 << 1)
|
||||
|
||||
/*
|
||||
* Page Descriptor
|
||||
* flags:
|
||||
* - bit 0: flag if this page is taken(allocated)
|
||||
* - bit 1: flag if this page 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(4K)
|
||||
*/
|
||||
static inline uint32_t _align_page(uint32_t address)
|
||||
{
|
||||
uint32_t order = (1 << PAGE_ORDER) - 1;
|
||||
return (address + order) & (~order);
|
||||
}
|
||||
|
||||
void page_init()
|
||||
{
|
||||
/*
|
||||
* We reserved 8 Page (8 x 4096) to hold the Page structures.
|
||||
* It should be enough to manage at most 128 MB (8 x 4096 x 4096)
|
||||
*/
|
||||
_num_pages = (HEAP_SIZE / PAGE_SIZE) - 8;
|
||||
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 + 8 * PAGE_SIZE);
|
||||
_alloc_end = _alloc_start + (PAGE_SIZE * _num_pages);
|
||||
|
||||
printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END);
|
||||
printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END);
|
||||
printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END);
|
||||
printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END);
|
||||
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 *page_alloc(int npages)
|
||||
{
|
||||
/* Note we are searching the page descriptor bitmaps. */
|
||||
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 page_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 = page_alloc(2);
|
||||
printf("p = 0x%x\n", p);
|
||||
//page_free(p);
|
||||
|
||||
void *p2 = page_alloc(7);
|
||||
printf("p2 = 0x%x\n", p2);
|
||||
page_free(p2);
|
||||
|
||||
void *p3 = page_alloc(4);
|
||||
printf("p3 = 0x%x\n", p3);
|
||||
}
|
||||
|
||||
29
code/os/03-contextswitch/platform.h
Normal file
29
code/os/03-contextswitch/platform.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef __PLATFORM_H__
|
||||
#define __PLATFORM_H__
|
||||
|
||||
/*
|
||||
* QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO
|
||||
*/
|
||||
|
||||
/*
|
||||
* maximum number of CPUs
|
||||
* see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h
|
||||
* #define VIRT_CPUS_MAX 8
|
||||
*/
|
||||
#define MAXNUM_CPU 8
|
||||
|
||||
/*
|
||||
* MemoryMap
|
||||
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
|
||||
* 0x00001000 -- boot ROM, provided by qemu
|
||||
* 0x02000000 -- CLINT
|
||||
* 0x0C000000 -- PLIC
|
||||
* 0x10000000 -- UART0
|
||||
* 0x10001000 -- virtio disk
|
||||
* 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel
|
||||
*/
|
||||
|
||||
/* This machine puts UART registers here in physical memory. */
|
||||
#define UART0 0x10000000L
|
||||
|
||||
#endif /* __PLATFORM_H__ */
|
||||
138
code/os/03-contextswitch/printf.c
Normal file
138
code/os/03-contextswitch/printf.c
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "os.h"
|
||||
|
||||
/*
|
||||
* ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c
|
||||
*/
|
||||
|
||||
static int _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 _vprintf()
|
||||
|
||||
static int _vprintf(const char* s, va_list vl)
|
||||
{
|
||||
int res = _vsnprintf(NULL, -1, s, vl);
|
||||
if (res+1 >= sizeof(out_buf)) {
|
||||
uart_puts("error: output string size overflow\n");
|
||||
while(1) {}
|
||||
}
|
||||
_vsnprintf(out_buf, res + 1, s, vl);
|
||||
uart_puts(out_buf);
|
||||
return res;
|
||||
}
|
||||
|
||||
int printf(const char* s, ...)
|
||||
{
|
||||
int res = 0;
|
||||
va_list vl;
|
||||
va_start(vl, s);
|
||||
res = _vprintf(s, vl);
|
||||
va_end(vl);
|
||||
return res;
|
||||
}
|
||||
|
||||
void panic(char *s)
|
||||
{
|
||||
printf("panic: ");
|
||||
printf(s);
|
||||
printf("\n");
|
||||
while(1){};
|
||||
}
|
||||
48
code/os/03-contextswitch/sched.c
Normal file
48
code/os/03-contextswitch/sched.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "os.h"
|
||||
|
||||
/* defined in entry.S */
|
||||
extern void switch_to(struct context *next);
|
||||
|
||||
#define STACK_SIZE 1024
|
||||
uint8_t task_stack[STACK_SIZE];
|
||||
struct context ctx_task;
|
||||
|
||||
static void w_mscratch(reg_t x)
|
||||
{
|
||||
asm volatile("csrw mscratch, %0" : : "r" (x));
|
||||
}
|
||||
|
||||
void user_task0(void);
|
||||
void sched_init()
|
||||
{
|
||||
w_mscratch(0);
|
||||
|
||||
ctx_task.sp = (reg_t) &task_stack[STACK_SIZE - 1];
|
||||
ctx_task.ra = (reg_t) user_task0;
|
||||
}
|
||||
|
||||
void schedule()
|
||||
{
|
||||
struct context *next = &ctx_task;
|
||||
switch_to(next);
|
||||
}
|
||||
|
||||
/*
|
||||
* a very rough implementaion, just to consume the cpu
|
||||
*/
|
||||
void task_delay(volatile int count)
|
||||
{
|
||||
count *= 50000;
|
||||
while (count--);
|
||||
}
|
||||
|
||||
|
||||
void user_task0(void)
|
||||
{
|
||||
uart_puts("Task 0: Created!\n");
|
||||
while (1) {
|
||||
uart_puts("Task 0: Running...\n");
|
||||
task_delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
41
code/os/03-contextswitch/start.S
Normal file
41
code/os/03-contextswitch/start.S
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "platform.h"
|
||||
|
||||
.equ STACK_SIZE, 8192
|
||||
|
||||
.global _start
|
||||
|
||||
.text
|
||||
_start:
|
||||
# park harts with id != 0
|
||||
csrr t0, mhartid # read current hart id
|
||||
mv tp, t0 # keep CPU's hartid in its tp for later usage.
|
||||
bnez t0, park # if we're not on the hart 0
|
||||
# we park the hart
|
||||
|
||||
# Set all bytes in the BSS section to zero.
|
||||
la a0, _bss_start
|
||||
la a1, _bss_end
|
||||
bgeu a0, a1, 2f
|
||||
1:
|
||||
sw zero, (a0)
|
||||
addi a0, a0, 4
|
||||
bltu a0, a1, 1b
|
||||
2:
|
||||
# Setup stacks, the stack grows from bottom to top, so we put the
|
||||
# stack pointer to the very end of the stack range.
|
||||
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
|
||||
|
||||
j start_kernel # hart 0 jump to c
|
||||
|
||||
park:
|
||||
wfi
|
||||
j park
|
||||
|
||||
stacks:
|
||||
.skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks
|
||||
|
||||
.end # End of file
|
||||
14
code/os/03-contextswitch/types.h
Normal file
14
code/os/03-contextswitch/types.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef __TYPES_H__
|
||||
#define __TYPES_H__
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned long long uint64_t;
|
||||
|
||||
/*
|
||||
* RISCV32: register is 32bits width
|
||||
*/
|
||||
typedef uint32_t reg_t;
|
||||
|
||||
#endif /* __TYPES_H__ */
|
||||
119
code/os/03-contextswitch/uart.c
Normal file
119
code/os/03-contextswitch/uart.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "os.h"
|
||||
|
||||
/*
|
||||
* The UART control registers are memory-mapped at address UART0.
|
||||
* This macro returns the address of one of the registers.
|
||||
*/
|
||||
#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg))
|
||||
|
||||
/*
|
||||
* Reference
|
||||
* [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html
|
||||
*/
|
||||
|
||||
/*
|
||||
* UART control registers map. see [1] "PROGRAMMING TABLE"
|
||||
* note some are reused by multiple functions
|
||||
* 0 (write mode): THR/DLL
|
||||
* 1 (write mode): IER/DLM
|
||||
*/
|
||||
#define RHR 0 // Receive Holding Register (read mode)
|
||||
#define THR 0 // Transmit Holding Register (write mode)
|
||||
#define DLL 0 // LSB of Divisor Latch (write mode)
|
||||
#define IER 1 // Interrupt Enable Register (write mode)
|
||||
#define DLM 1 // MSB of Divisor Latch (write mode)
|
||||
#define FCR 2 // FIFO Control Register (write mode)
|
||||
#define ISR 2 // Interrupt Status Register (read mode)
|
||||
#define LCR 3 // Line Control Register
|
||||
#define MCR 4 // Modem Control Register
|
||||
#define LSR 5 // Line Status Register
|
||||
#define MSR 6 // Modem Status Register
|
||||
#define SPR 7 // ScratchPad Register
|
||||
|
||||
/*
|
||||
* POWER UP DEFAULTS
|
||||
* IER = 0: TX/RX holding register interrupts are bith disabled
|
||||
* ISR = 1: no interrupt penting
|
||||
* LCR = 0
|
||||
* MCR = 0
|
||||
* LSR = 60 HEX
|
||||
* MSR = BITS 0-3 = 0, BITS 4-7 = inputs
|
||||
* FCR = 0
|
||||
* TX = High
|
||||
* OP1 = High
|
||||
* OP2 = High
|
||||
* RTS = High
|
||||
* DTR = High
|
||||
* RXRDY = High
|
||||
* TXRDY = Low
|
||||
* INT = Low
|
||||
*/
|
||||
|
||||
/*
|
||||
* LINE STATUS REGISTER (LSR)
|
||||
* LSR BIT 0:
|
||||
* 0 = no data in receive holding register or FIFO.
|
||||
* 1 = data has been receive and saved in the receive holding register or FIFO.
|
||||
* ......
|
||||
* LSR BIT 5:
|
||||
* 0 = transmit holding register is full. 16550 will not accept any data for transmission.
|
||||
* 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.
|
||||
* ......
|
||||
*/
|
||||
#define LSR_RX_READY (1 << 0)
|
||||
#define LSR_TX_IDLE (1 << 5)
|
||||
|
||||
#define uart_read_reg(reg) (*(UART_REG(reg)))
|
||||
#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v))
|
||||
|
||||
void uart_init()
|
||||
{
|
||||
/* disable interrupts. */
|
||||
uart_write_reg(IER, 0x00);
|
||||
|
||||
/*
|
||||
* Setting baud rate. Just a demo here if we care about the divisor,
|
||||
* but for our purpose [QEMU-virt], this doesn't really do anything.
|
||||
*
|
||||
* Notice that the divisor register DLL (divisor latch least) and DLM (divisor
|
||||
* latch most) have the same base address as the receiver/transmitter and the
|
||||
* interrupt enable register. To change what the base address points to, we
|
||||
* open the "divisor latch" by writing 1 into the Divisor Latch Access Bit
|
||||
* (DLAB), which is bit index 7 of the Line Control Register (LCR).
|
||||
*
|
||||
* Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE".
|
||||
* We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3.
|
||||
* And due to the divisor register is two bytes (16 bits), so we need to
|
||||
* split the value of 3(0x0003) into two bytes, DLL stores the low byte,
|
||||
* DLM stores the high byte.
|
||||
*/
|
||||
uint8_t lcr = uart_read_reg(LCR);
|
||||
uart_write_reg(LCR, lcr | (1 << 7));
|
||||
uart_write_reg(DLL, 0x03);
|
||||
uart_write_reg(DLM, 0x00);
|
||||
|
||||
/*
|
||||
* Continue setting the asynchronous data communication format.
|
||||
* - number of the word length: 8 bits
|
||||
* - number of stop bits:1 bit when word length is 8 bits
|
||||
* - no parity
|
||||
* - no break control
|
||||
* - disabled baud latch
|
||||
*/
|
||||
lcr = 0;
|
||||
uart_write_reg(LCR, lcr | (3 << 0));
|
||||
}
|
||||
|
||||
int uart_putc(char ch)
|
||||
{
|
||||
while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
|
||||
return uart_write_reg(THR, ch);
|
||||
}
|
||||
|
||||
void uart_puts(char *s)
|
||||
{
|
||||
while (*s) {
|
||||
uart_putc(*s++);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user