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

178
doc/Background.md Normal file
View File

@@ -0,0 +1,178 @@
# 背景知識
file:///D:/ccc109/sp/10-riscv/pdf/RISC-V-Reader-Chinese-v2p1.pdf
101 頁
有三种标准的中断源:软件、时钟和外部来源。软件中断通过向内存映射寄存器中存数来触发,并通常用于由一个 hart 中断另一个 hart在其他架构中称为处理器间中断机制。当 hart 的时间比较器(一个名为 mtimecmp 的内存映射寄存器大于实时计数器mtime 时,会触发时钟中断。。外部中断由平台级中断控制器(大多数外部设备连接到这个中断控制器)引发。不同的硬件平台具有不同的内存映射并且需要中断控制器的不同特性,因此用于发出和消除这些中断的机制因平台而异。所有 RISC-V 系统的共同问题是如何处理异常和屏蔽中断,这是下一节的主题。
10.3 机器模式下的异常处理
八个控制状态寄存器CSR是机器模式下异常处理的必要部分
* mtvecMachine Trap Vector它保存发生异常时处理器需要跳转到的地址。
* mepcMachine Exception PC它指向发生异常的指令。
* mcauseMachine Exception Cause它指示发生异常的种类。
* mieMachine Interrupt Enable它指出处理器目前能处理和必须忽略的中断。
* mipMachine Interrupt Pending它列出目前正准备处理的中断。
* mtvalMachine Trap Value它保存了陷入trap的附加信息地址例外中出错的地址、发生非法指令例外的指令本身对于其他异常它的值为 0。
* mscratchMachine Scratch它暂时存放一个字大小的数据。
* mstatusMachine Status它保存全局中断使能以及许多其他的状态如图10.4 所示。
处理器在 M 模式下运行时,只有在全局中断使能位 mstatus.MIE 置 1 时才会产生中断.此外,每个中断在控制状态寄存器 mie 中都有自己的使能位。这些位在 mie 中的位置,对应于图 10.3 中的中断代码。例如mie[7]对应于 M 模式中的时钟中断。控制状态寄存器mip具有相同的布局并且它指示当前待处理的中断。将所有三个控制状态寄存器合在一起考虑如果 status.MIE = 1mie[7] = 1且 mip[7] = 1则可以处理机器的时钟中断。
当一个 hart 发生异常时,硬件自动经历如下的状态转换:
* 异常指令的 PC 被保存在 mepc 中PC 被设置为 mtvec。对于同步异常mepc 指向导致异常的指令;对于中断,它指向中断处理后应该恢复执行的位置。)
* mepc = PC; PC = mtvec
* 根据异常来源设置 mcause如图 10.3 所示),并将 mtval 设置为出错的地址或 者其它适用于特定异常的信息字。
* 把控制状态寄存器 mstatus 中的 MIE 位置零以禁用中断,并把先前的 MIE 值保留到 MPIE 中。
* 发生异常之前的权限模式保留在 mstatus 的 MPP 域中再把权限模式更改为M。图 10.5 显示了 MPP 域的编码(如果处理器仅实现 M 模式,则有效地跳过这个步骤)。
为避免覆盖整数寄存器中的内容,中断处理程序先在最开始用 mscratch 和整数寄存器(例如 a0中的值交换。通常软件会让 mscratch 包含指向附加临时内存空间的指针,处理程序用该指针来保存其主体中将会用到的整数寄存器。在主体执行之后,中断程序会恢复它保存到内存中的寄存器,然后再次使用 mscratch 和 a0 交换,将两个寄存器恢复到它们在发生异常之前的值。
最后,处理程序用 mret 指令M 模式特有的指令返回。mret 将 PC 设置为 mepc通过将 mstatus 的 MPIE 域复制到 MIE 来恢复之前的中断使能设置,并将权限模式设置为 mstatus 的 MPP 域中的值。这基本是前一段中描述的逆操作。
> mret: PC=mepc; mstatus:MIE=MPIE;
图 10.6 展示了遵循此模式的基本时钟中断处理程序的 RISC-V 汇编代码。它只对时间比较器执行了递增操作,然后继续执行之前的任务。更实际的时钟中断处理程序可能会调用调度程序,从而在任务之间切换。它是非抢占的,因此在处理程序的过程中中断会被禁用。不考虑这些限制条件的话,它就是一个只有一页的 RISC-V 中断处理程序的完整示例!
![](img/InterruptHandler.png)
图 10.6;简单的 RISC-V 时钟中断处理程序代码。代码中假定了全局中断已通过置位 mstatus.MIE 启
用;时钟中断已通过置位 mie[7]启用mtvec CSR 已设置为此处理程序的入口地址;而且 mscratch
CSR 已经设置为有 16 个字节用于保存寄存器的临时空间的地址。第一部分保存了五个寄存器,把 a0 保存在 mscratch 中a1 到 a4 保存在内存中。然后它检查 mcause 来读取异常的类别:如果 mcause<0 则是中断反之则是同步异常如果是中断就检查 mcause 的低位是否等于 7如果是就是 M 模式的时钟中断如果确定是时钟中断就给时间比较器加上 1000 个时钟周期于是下一个时钟中断会发生在大约 1000 个时钟周期之后最后一段恢复了 a0 a4 mscratch然后用 mret 指令返回
默认情况下发生所有异常不论在什么权限模式下的时候控制权都会被移交到M 模式的异常处理程序但是 Unix 系统中的大多数例外都应该进行 S 模式下的系统调用M 模式的异常处理程序可以将异常重新导向 S 模式但这些额外的操作会减慢大多数异常的处理速度因此RISC-V 提供了一种异常委托机制通过该机制可以选择性地将中断和同步异常交给 S 模式处理而完全绕过 M 模式
midelegMachine Interrupt Delegation机器中断委托CSR 控制将哪些中断委托给 S模式 mip mie 一样mideleg 中的每个位对应于图 10.3 中相同的异常例如mideleg[5]对应于 S 模式的时钟中断如果把它置位S 模式的时钟中断将会移交 S 模式的异常处理程序而不是 M 模式的异常处理程序
委托给 S 模式的任何中断都可以被 S 模式的软件屏蔽sieSupervisor InterruptEnable监管者中断使能 sipSupervisor Interrupt Pending监管者中断待处理CSR是 S 模式的控制状态寄存器他们是 mie mip 的子集它们有着和 M 模式下相同的布局但在 sie sip 中只有与由 mideleg 委托的中断对应的位才能读写那些没有被委派的中断对应的位始终为零
M 模式还可以通过 medeleg CSR 将同步异常委托给 S 模式该机制类似于刚才提到的中断委托 medeleg 中的位对应的不再是中断而是图 10.3 中的同步异常编码例如置上 medeleg[15]便会把 store page faultstore 过程中出现的缺页委托给 S 模式
请注意无论委派设置是怎样的发生异常时控制权都不会移交给权限更低的模式
M 模式下发生的异常总是在 M 模式下处理 S 模式下发生的异常根据具体的委派设置可能由 M 模式或 S 模式处理但永远不会由 U 模式处理
S 模式有几个异常处理 CSRsepcstvecscausesscratchstval sstatus它们执行与 10.2 中描述的 M 模式 CSR 相同的功能 10.9 显示了 sstatus 寄存器的布局
监管者异常返回指令 sret mret 的行为相同但它作用于 S 模式的异常处理 CSR而不是 M 模式的 CSR
S 模式处理例外的行为已和 M 模式非常相似如果 hart 接受了异常并且把它委派给了S 模式则硬件会原子地经历几个类似的状态转换其中用到了 S 模式而不是 M 模式的CSR
* 发生例外的指令的 PC 被存入 sepc PC 被设置为 stvec
* scause 按图 10.3 根据异常类型设置stval 被设置成出错的地址或者其它特定异常的信息字
* sstatus CSR 中的 SIE 置零屏蔽中断 SIE 之前的值被保存在 SPIE
* 发生例外时的权限模式被保存在 sstatus SPP 然后设置当前模式为 S 模式
##
* https://github.com/RISCV-on-Microsemi-FPGA/RVBM-BootLoader/blob/master/src/riscv_hal/startup.S
* https://github.com/RISCV-on-Microsemi-FPGA/RVBM-BootLoader/blob/master/src/riscv_hal/entry.S
* https://github.com/RISCV-on-Microsemi-FPGA/RVBM-BootLoader/blob/master/src/interrupts.c
```cpp
/******************************************************************************
* RISC-V interrupt handler for machine timer interrupts.
*****************************************************************************/
void handle_m_timer_interrupt(){
clear_csr(mie, MIP_MTIP);
add_10ms_to_mtimecmp();
SysTick_Handler();
// Re-enable the timer interrupt.
set_csr(mie, MIP_MTIP);
}
```
```cpp
/*------------------------------------------------------------------------------
* Count the number of elapsed milliseconds (SysTick_Handler is called every
* 10mS so the resolution will be 10ms). Rolls over every 49 days or so...
*
* Should be safe to read g_10ms_count from elsewhere.
*/
void SysTick_Handler(void)
{
g_10ms_count += 10;
/*
* For neatness, if we roll over, reset cleanly back to 0 so the count
* always goes up in proper 10s.
*/
if(g_10ms_count < 10)
g_10ms_count = 0;
}
```
```cpp
uintptr_t handle_trap(uintptr_t mcause, uintptr_t epc)
{
if (0){
// External Machine-Level Interrupt from PLIC
}else if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_EXT)) {
handle_m_ext_interrupt();
}else if ((mcause & MCAUSE_INT) && ((mcause & MCAUSE_CAUSE) == IRQ_M_TIMER)) {
handle_m_timer_interrupt();
}
else{
write(1, "trap\n", 5);
_exit(1 + mcause);
}
return epc;
}
```
start.s
```s
trap_entry:
addi sp, sp, -32*REGBYTES
...
csrr a0, mcause
csrr a1, mepc
mv a2, sp
jal handle_trap
csrw mepc, a0
# Remain in M-mode after mret
li t0, MSTATUS_MPP
csrs mstatus, t0
...
addi sp, sp, 32*REGBYTES
mret
```
* https://github.com/d0iasm/rvemu/blob/master/src/cpu.rs
```rust
// mret
// "The RISC-V Reader" book says:
// "Returns from a machine-mode exception handler. Sets the pc to
// CSRs[mepc], the privilege mode to CSRs[mstatus].MPP,
// CSRs[mstatus].MIE to CSRs[mstatus].MPIE, and CSRs[mstatus].MPIE
// to 1; and, if user mode is supported, sets CSRs[mstatus].MPP to 0".
```
* RISC-V-Reader-Chinese-v2p1.pdf
* mret ExceptionReturn(Machine)
* 机器模式异常返回(Machine-mode Exception Return). R-type, RV32I and RV64I 特权架构从机器模式异常处理程序返回 pc 设置为 CSRs[mepc], 将特权级设置成 CSRs[mstatus].MPP, CSRs[mstatus].MIE 置成 CSRs[mstatus].MPIE, 并且将 CSRs[mstatus].MPIE 1;并且如果支持用户模式则将 CSR [mstatus].MPP 设置为 0
```cpp
// set the machine-mode trap handler.
w_mtvec((reg_t)sys_timer);
```
```cpp
// Machine-mode interrupt vector
static inline void w_mtvec(reg_t x)
{
asm volatile("csrw mtvec, %0" : : "r" (x));
}
```

83
doc/Threads.md Normal file
View File

@@ -0,0 +1,83 @@
像是 07-thread 裏使用了 tcb_t
```cpp
/* Thread Control Block */
typedef struct {
void *stack;
void *orig_stack;
uint8_t in_use;
} tcb_t;
static tcb_t tasks[MAX_TASKS];
static int lastTask;
```
還有用了一大堆組合語言
```cpp
void __attribute__((naked)) thread_start()
{
lastTask = 0;
/* Reset APSR before context switch.
* Make sure we have a _clean_ PSR for the task.
*/
asm volatile("mov r0, #0\n"
"msr APSR_nzcvq, r0\n");
/* To bridge the variable in C and the register in ASM,
* move the task's stack pointer address into r0.
* http://www.ethernut.de/en/documents/arm-inline-asm.html
*/
asm volatile("mov r0, %0\n" : : "r" (tasks[lastTask].stack));
asm volatile("msr psp, r0\n"
"mov r0, #3\n"
"msr control, r0\n"
"isb\n");
/* This is how we simulate stack handling that pendsv_handler
* does. Thread_create sets 17 entries in stack, and the 9
* entries we pop here will be pushed back in pendsv_handler
* in the same order.
*
*
* pop {r4-r11, lr}
* ldr r0, [sp]
* stack
* offset -------
* | 16 | <- Reset value of PSR
* -------
* | 15 | <- Task entry
* -------
* | 14 | <- LR for task
* -------
* | ... | register
* ------- -------
* | 9 | <- Task argument ----> | r0 |
* psp after pop--< -------
* | 8 | <- EXC_RETURN ----> | lr |
* ------- -------
* | 7 | | r11 |
* ------- -------
* | ... | | ... |
* ------- -------
* | 0 | | r4 |
* psp -> ------- -------
*
* Instead of "pop {r0}", use "ldr r0, [sp]" to ensure consistent
* with the way how PendSV saves _old_ context[1].
*/
asm volatile("pop {r4-r11, lr}\n"
"ldr r0, [sp]\n");
/* Okay, we are ready to run first task, get address from
* stack[15]. We just pop 9 register so #24 comes from
* (15 - 9) * sizeof(entry of sp) = 6 * 4.
*/
asm volatile("ldr pc, [sp, #24]\n");
/* Never reach here */
while(1);
}
```

5
doc/Uart.md Normal file
View File

@@ -0,0 +1,5 @@
# Uart
* https://www.activexperts.com/serial-port-component/tutorials/uart/
* https://www.maxlinear.com/Files/Documents/Intro_To_UARTs.pdf
* https://twilco.github.io/riscv-from-scratch/2019/07/08/riscv-from-scratch-3.html

101
doc/freeRtosRef.md Normal file
View File

@@ -0,0 +1,101 @@
# FreeRTOS
* https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/portable/GCC/RISCV/port.c
```cpp
/*-----------------------------------------------------------*/
void vPortSysTickHandler( void )
{
prvSetNextTimerInterrupt();
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
vTaskSwitchContext();
}
}
/*-----------------------------------------------------------*/
```
* https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/portable/GCC/RISCV/portasm.S
```s
vPortYield:
/*
* This routine can be called from outside of interrupt handler. This means
* interrupts may be enabled at this point. This is probably okay for registers and
* stack. However, "mepc" will be overwritten by the interrupt handler if a timer
* interrupt happens during the yield. To avoid this, prevent interrupts before starting.
* The write to mstatus in the restore context routine will enable interrupts after the
* mret. A more fine-grain lock may be possible.
*/
csrci mstatus, 8
portSAVE_CONTEXT
portSAVE_RA
jal vTaskSwitchContext
portRESTORE_CONTEXT
```
* https://github.com/illustris/FreeRTOS-RISCV/blob/master/Source/tasks.c
```cpp
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
/* Add the amount of time the task has been running to the
accumulated time so far. The time the task started running was
stored in ulTaskSwitchedInTime. Note that there is no overflow
protection here so count values are only valid until the timer
overflows. The guard against negative values is to protect
against suspect run time stat counter implementations - which
are provided by the application, not the kernel. */
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif /* configGENERATE_RUN_TIME_STATS */
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
/* Select a new task to run using either the generic C or port
optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to this task. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

101
doc/xv6ref.md Normal file
View File

@@ -0,0 +1,101 @@
# xv6 參考
当 xv6 内核在 CPU 上执行时,可能会发生两种类型的陷阱:异常和设备中断。上一节概述了 CPU 对此类陷阱的响应。
当内核执行时,它指向汇编代码 kernelvec (kernel/kernelve.S10)。由于xv6已经在内核中因此kernelvec可以依赖于将`satp`设置为内核页表并依赖于引用有效内核堆栈的堆栈指针。kernelvec保存所有寄存器这样我们最终可以恢复中断的代码而不会干扰它。
***
kernelvec将寄存器保存在中断的内核线程的堆栈上这是有意义的因为寄存器值属于该线程。如果陷阱导致切换到不同的线程这一点尤其重要 - 在这种情况下,陷阱实际上会返回到新线程的堆栈上,将被中断线程的已保存寄存器安全地留在其堆栈上。
***
kernelvec在保存寄存器后跳转到kerneltrap(kernel/trap.c134)。kerneltrap为两种类型的陷阱做好准备设备中断和异常。它调用sdevintr(kernel/trap.c177)来检查和处理前者。如果陷阱不是设备中断,那么它就是一个异常,如果它发生在内核中,那总是一个致命的错误。
## supervisor mode & mret
File: start.c
```cpp
// entry.S jumps here in machine mode on stack0.
void
start()
{
// set M Previous Privilege mode to Supervisor, for mret.
unsigned long x = r_mstatus();
x &= ~MSTATUS_MPP_MASK;
x |= MSTATUS_MPP_S;
w_mstatus(x);
// set M Exception Program Counter to main, for mret.
// requires gcc -mcmodel=medany
w_mepc((uint64)main);
// disable paging for now.
w_satp(0);
// delegate all interrupts and exceptions to supervisor mode.
w_medeleg(0xffff);
w_mideleg(0xffff);
// ask for clock interrupts.
timerinit();
// keep each CPU's hartid in its tp register, for cpuid().
int id = r_mhartid();
w_tp(id);
// switch to supervisor mode and jump to main().
asm volatile("mret");
}
```
File: main.c
```cpp
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"
volatile static int started = 0;
// start() jumps here in supervisor mode on all CPUs.
void
main()
{
if(cpuid() == 0){
consoleinit();
printfinit();
printf("\n");
printf("xv6 kernel is booting\n");
printf("\n");
kinit(); // physical page allocator
kvminit(); // create kernel page table
kvminithart(); // turn on paging
procinit(); // process table
trapinit(); // trap vectors
trapinithart(); // install kernel trap vector
plicinit(); // set up interrupt controller
plicinithart(); // ask PLIC for device interrupts
binit(); // buffer cache
iinit(); // inode cache
fileinit(); // file table
virtio_disk_init(); // emulated hard disk
userinit(); // first user process
__sync_synchronize();
started = 1;
} else {
while(started == 0)
;
__sync_synchronize();
printf("hart %d starting\n", cpuid());
kvminithart(); // turn on paging
trapinithart(); // install kernel trap vector
plicinithart(); // ask PLIC for device interrupts
}
scheduler();
}
```