移植FreeRTOS

1. 内存布局与移植代码架构

1.1 内存启动地址修改

之前我们通过OpenSBI划分了DomainHART7是留给FreeRTOS的,起始地址设置为了0xb0000000,我们修改一下,将FreeRTOS的起始地址设置为0xBF800000,第一个修改的代码为bootstart.s

基于opensbi为quard_star创建domain | TimerのBlog (yanglianoo.github.io)

//load trusted_fw.bin
//[0x20400000:0x20800000] --> [0xBF800000:0xBFC00000]
li a0, 0x204
slli a0, a0, 20 //a0 = 0x20400000
li a1, 0xbf8
slli a1, a1, 20 //a1 = 0xbf800000
li a2, 0xbfc
slli a2, a2, 20 //a2 = 0xbfc00000
load_data a0,a1,a2

trusted_fw.bin我们是写入到flash0x20400000地址处的,大小为4k

image-20240419174122744

然后需要对应修改设备树的下级跳转地址:

image-20240419174900634

可以看见在设备树中将trusted-domain的启动地址设置成了0xBF800000,启动的核为cpu7next-mode = 1代表下级程序启动的cpu模式为s模式,我们是将FreeRTOS移植到S态而不是M

1.2 移植文件架构

image-20240419175317425

原本trusted_domain目录下就只有statup.S和一个Makefile用于在uart2上输出一些字符,添加移植文件后文件夹中文件如上图:

  • driver:串口驱动代码
  • FreeRTOS-Kernel: FreeRTOS内核代码
  • riscv:和riscv架构相关的代码
  • FreeRTOSConfig.h:FreeRTOS的配置文件
  • main.c:主应用程序
  • Makefile,link.lds:编译trusted_domain编译规则和链接文件
  • startup.S:启动汇编,执行一些初始化工作然后跳转到FreeRTOS内核

2. 平台架构相关代码剖析

2.1 driver

在进行domain划分时,设备树文件有这样一个配置:

stdout-path = "/soc/uart0@10000000";

这里是告知OpenSBI标准输出将被重定向到位于内存地址 0x10000000 的UART设备上,这个uart设备我们在下面定义了:

uart0: uart0@10000000 {
interrupts = <0xa>;
interrupt-parent = <0x11>;
clock-frequency = <0x384000>;
reg = <0x0 0x10000000 0x0 0x100>;
compatible = "ns16550a";
};

cpu7trusted_domain的配置中,我们使用的是uart2,因此我们需要手动编写uart2的串口驱动代码而不是使用OpenSBI的标准输入输出串口,uart0已经被我们的TimerOS使用了,根据设备树可知,uart2ns16550a这款芯片,qemu默认的虚拟串口也是使用的这款芯片,我们先来看看这款串口芯片的芯片手册:

image-20240420150457253

image-20240420145859088

  • 总共有10个寄存器,但是有的寄存器是可以复用的,A2 A1 A0代表此寄存器的映射地址,实际去操作此寄存器的时候需要加上串口设备的偏移地址,这里是uart2 : 0x10002000,每个寄存器都是8个bit

ns16550a串口芯片的编程手册我放在了ref目录下,可以去看看这个视频:第7章(下)-Hello RVOS_哔哩哔哩_bilibili

接下来来看看driver目录下的代码:

image-20240420151441701

quard_star.h

#ifndef QUARD_STAR_H_
#define QUARD_STAR_H_

#ifdef __ASSEMBLER__
#define CONS(NUM, TYPE)NUM
#else
#define CONS(NUM, TYPE)NUM##TYPE
#endif /* __ASSEMBLER__ */

#define PRIM_HART 7 //cpu的启动核
#define CLINT_ADDR CONS(0x02000000, UL)
#define CLINT_MSIP CONS(0x0000, UL)
#define CLINT_MTIMECMP CONS(0x4000, UL)
#define CLINT_MTIME CONS(0xbff8, UL)
//NS16550 串口映射的地址
#define NS16550_ADDR CONS(0x10002000, UL)


#ifndef __ASSEMBLER__

#endif /* __ASSEMBLER__ */


#endif
  • 定义了一些宏定义的值,主要是#define NS16550_ADDR CONS(0x10002000, UL)定义了串口的映射地址

ns16550.c

#include <stdint.h>
#include "ns16550.h"
/* 寄存器定义 */
#define REG_RBR 0x00 /* Receiver buffer reg. */
#define REG_THR 0x00 /* Transmitter holding reg. */
#define REG_IER 0x01 /* Interrupt enable reg. */
#define REG_IIR 0x02 /* Interrupt ID reg. */
#define REG_FCR 0x02 /* FIFO control reg. */
#define REG_LCR 0x03 /* Line control reg. */
#define REG_MCR 0x04 /* Modem control reg. */
#define REG_LSR 0x05 /* Line status reg. */
#define REG_MSR 0x06 /* Modem status reg. */
#define REG_SCR 0x07 /* Scratch reg. */
#define REG_BRDL 0x00 /* Divisor latch (LSB) */
#define REG_BRDH 0x01 /* Divisor latch (MSB) */

/* Line status */
#define LSR_DR 0x01 /* Data ready */
#define LSR_OE 0x02 /* Overrun error */
#define LSR_PE 0x04 /* Parity error */
#define LSR_FE 0x08 /* Framing error */
#define LSR_BI 0x10 /* Break interrupt */
#define LSR_THRE 0x20 /* Transmitter holding register empty */
#define LSR_TEMT 0x40 /* Transmitter empty */
#define LSR_EIRF 0x80 /* Error in RCVR FIFO */

//从一个地址读入一个字节
static uint8_t readb( uintptr_t addr )
{
return *( (uint8_t *) addr );
}

//向一个地址写入一个字节
static void writeb( uint8_t b, uintptr_t addr )
{
*( (uint8_t *) addr ) = b;
}

void ns16550_tx(uintptr_t addr, unsigned char c)
{
//读数据和写数据用的同一个寄存器
while ((readb(addr + REG_LSR) & LSR_THRE) == 0){
//正在读,轮询等待
}

writeb(c, addr + REG_THR);

}
  • 定义了ns16550_tx函数来向串口写入一个字节的数据,NS16550的数据寄存器为RHRTHR,他们都在偏移地址为0处,可复用为读模式和写模式

  • LSR寄存器有8个bit位,用于反应串口的硬件状态,bit5具体代表“Transmitter Holding Register Empty”,当LSR_THRE为1时,它表示发送保持寄存器为空,UART准备好接受新的字节进行发送。因此在ns16550_tx发送数据时需要先判断此bit位,当此bit位为1时说明可以写入新的字节数据,THR8个bit刚好代表一个字节,写入的一个字节数据就放入了THR

debug_log.c

#include <FreeRTOS.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include "quard_star.h"
#include "ns16550.h"
#include "debug_log.h"

#define UART_LOG_BUFF_SIZE 1024

int _puts(char *str)
{
int counter = 0;
if (!str)
{
return 0;
}
while (*str && (counter < UART_LOG_BUFF_SIZE))
{
if(*str == '\n')
ns16550_tx( NS16550_ADDR, '\r' );
ns16550_tx( NS16550_ADDR, *str++ );
counter++;
}
return counter;
}

  • debug_log.c中定义了一个 _puts函数用于将一个字符串一个字节一个字节的写入到串口中进行输出,返回输出的字符数

2.2 riscv

image-20240420164504237

riscv目录下是和架构以及OpenSBI相关的代码,我们在移植FreeRTOS到cpu7的S态时需要用到OpenSBI的一些和中断相关的功能,因此需要首先添加调用OpenSBI服务的代码:sbi.csbi.h,这里我直接给出代码,和之前Timeros一样

#ifndef _ASM_RISCV_SBI_H
#define _ASM_RISCV_SBI_H

enum sbi_ext_id {
SBI_EXT_0_1_SET_TIMER = 0x0,
SBI_EXT_0_1_CONSOLE_PUTCHAR = 0x1,
SBI_EXT_0_1_CONSOLE_GETCHAR = 0x2,
SBI_EXT_0_1_CLEAR_IPI = 0x3,
SBI_EXT_0_1_SEND_IPI = 0x4,
SBI_EXT_0_1_REMOTE_FENCE_I = 0x5,
SBI_EXT_0_1_REMOTE_SFENCE_VMA = 0x6,
SBI_EXT_0_1_REMOTE_SFENCE_VMA_ASID = 0x7,
SBI_EXT_0_1_SHUTDOWN = 0x8,
SBI_EXT_BASE = 0x10,
SBI_EXT_TIME = 0x54494D45,
SBI_EXT_IPI = 0x735049,
SBI_EXT_RFENCE = 0x52464E43,
};


enum sbi_ext_base_fid {
SBI_EXT_BASE_GET_SPEC_VERSION = 0,
SBI_EXT_BASE_GET_IMP_ID,
SBI_EXT_BASE_GET_IMP_VERSION,
SBI_EXT_BASE_PROBE_EXT,
SBI_EXT_BASE_GET_MVENDORID,
SBI_EXT_BASE_GET_MARCHID,
SBI_EXT_BASE_GET_MIMPID,
};

enum sbi_ext_time_fid {
SBI_EXT_TIME_SET_TIMER = 0,
};

enum sbi_ext_ipi_fid {
SBI_EXT_IPI_SEND_IPI = 0,
};


enum sbi_ext_rfence_fid {
SBI_EXT_RFENCE_REMOTE_FENCE_I = 0,
SBI_EXT_RFENCE_REMOTE_SFENCE_VMA,
SBI_EXT_RFENCE_REMOTE_SFENCE_VMA_ASID,
};

#define SBI_EXT_SET_TIMER SBI_EXT_TIME
#define SBI_FID_SET_TIMER SBI_EXT_TIME_SET_TIMER
#define SBI_EXT_SEND_IPI SBI_EXT_IPI
#define SBI_FID_SEND_IPI SBI_EXT_IPI_SEND_IPI
#define SBI_EXT_REMOTE_FENCE_I SBI_EXT_RFENCE
#define SBI_FID_REMOTE_FENCE_I SBI_EXT_RFENCE_REMOTE_FENCE_I
#define SBI_EXT_REMOTE_SFENCE_VMA SBI_EXT_RFENCE
#define SBI_FID_REMOTE_SFENCE_VMA SBI_EXT_RFENCE_REMOTE_SFENCE_VMA
#define SBI_EXT_REMOTE_SFENCE_VMA_ASID SBI_EXT_RFENCE
#define SBI_FID_REMOTE_SFENCE_VMA_ASID SBI_EXT_RFENCE_REMOTE_SFENCE_VMA_ASID



/* SBI return error codes */
#define SBI_SUCCESS 0
#define SBI_ERR_FAILURE -1
#define SBI_ERR_NOT_SUPPORTED -2
#define SBI_ERR_INVALID_PARAM -3
#define SBI_ERR_DENIED -4
#define SBI_ERR_INVALID_ADDRESS -5

struct sbiret {
long error;
long value;
};

struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
unsigned long arg5);

void sbi_console_putchar(int ch);
int sbi_console_getchar(void);
void sbi_set_timer(uint64_t stime_value);
long sbi_get_spec_version(void);
int sbi_get_impl_id(void);
int sbi_probe_extension(int ext);


void sbi_clear_ipi(void);
void sbi_shutdown(void);
void sbi_send_ipi(const unsigned long *hart_mask);
void sbi_remote_fence_i(const unsigned long *hart_mask);
void sbi_remote_sfence_vma(const unsigned long *hart_mask,
unsigned long start,
unsigned long size);
void sbi_remote_sfence_vma_asid(const unsigned long *hart_mask,
unsigned long start,
unsigned long size,
unsigned long asid);


#endif
#include <stdint.h>
#include "sbi.h"


struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
unsigned long arg5)
{
struct sbiret ret;

//使用GCC的扩展语法,用于将一个值存储到RISC-V架构中的寄存器a0中。
register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0);
register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1);
register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2);
register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3);
register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4);
register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5);
register uintptr_t a6 asm ("a6") = (uintptr_t)(fid);
register uintptr_t a7 asm ("a7") = (uintptr_t)(ext);
asm volatile ("ecall"
: "+r" (a0), "+r" (a1)
: "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7)
: "memory");
ret.error = a0;
ret.value = a1;

return ret;
}


/**
* sbi_set_timer() - Program the timer for next timer event.
* @stime_value: The value after which next timer event should fire.
*
* Return: None
*/
void sbi_set_timer(uint64_t stime_value)
{
#if __riscv_xlen == 32
sbi_ecall(SBI_EXT_SET_TIMER, SBI_FID_SET_TIMER, stime_value,
stime_value >> 32, 0, 0, 0, 0);
#else
sbi_ecall(SBI_EXT_SET_TIMER, SBI_FID_SET_TIMER, stime_value,
0, 0, 0, 0, 0);
#endif
}


/**
* sbi_get_spec_version() - get current SBI specification version
* 获取SBI规范版本 (FID #0)
* Return: version id
*/
long sbi_get_spec_version(void)
{
struct sbiret ret;

ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_GET_SPEC_VERSION,
0, 0, 0, 0, 0, 0);
if (!ret.error)
if (ret.value)
return ret.value;

return -1;
}

/**
* sbi_get_impl_id() - get SBI implementation ID
* 获取SBI规范版本 (FID #0) 基本拓展 EID = 0x10
* Return: implementation ID
*/
int sbi_get_impl_id(void)
{
struct sbiret ret;

ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_GET_IMP_ID,
0, 0, 0, 0, 0, 0);
if (!ret.error)
if (ret.value)
return ret.value;

return -1;
}


/**
* sbi_probe_extension() - Check if an SBI extension ID is supported or not.
* @extid: The extension ID to be probed.
* 探测SBI扩展功能 (FID #3) 基本拓展 EID = 0x10
* Return: Extension specific nonzero value f yes, -1 otherwise.
*/
int sbi_probe_extension(int extid)
{
struct sbiret ret;

ret = sbi_ecall(SBI_EXT_BASE, SBI_EXT_BASE_PROBE_EXT, extid,
0, 0, 0, 0, 0);
if (!ret.error)
if (ret.value)
return ret.value;

return -1;
}


/**
* sbi_console_putchar() - Writes given character to the console device.
* @ch: The data to be written to the console.
*
* Return: None
*/
void sbi_console_putchar(int ch)
{
sbi_ecall(SBI_EXT_0_1_CONSOLE_PUTCHAR, 0, ch, 0, 0, 0, 0, 0);
}

/**
* sbi_console_getchar() - Reads a byte from console device.
*
* Returns the value read from console.
*/
int sbi_console_getchar(void)
{
struct sbiret ret;

ret = sbi_ecall(SBI_EXT_0_1_CONSOLE_GETCHAR, 0, 0, 0, 0, 0, 0, 0);

return ret.error;
}


/**
* sbi_clear_ipi() - Clear any pending IPIs for the calling hart.
* 清除IPI (EID #0x03) 清除任何挂起的IPI(处理器核间中断)。
* Return: None
*/
void sbi_clear_ipi(void)
{
sbi_ecall(SBI_EXT_0_1_CLEAR_IPI, 0, 0, 0, 0, 0, 0, 0);
}

/**
* sbi_shutdown() - Remove all the harts from executing supervisor code.
* 系统关闭 (EID #0x08)
* Return: None
*/
void sbi_shutdown(void)
{
sbi_ecall(SBI_EXT_0_1_SHUTDOWN, 0, 0, 0, 0, 0, 0, 0);
}


/**
* sbi_send_ipi() - Send an IPI to any hart.
* @hart_mask: A cpu mask containing all the target harts.
* 向 hart_mask 中定义的所有 hart 发送跨处理器中断。
* Return: None
*/
void sbi_send_ipi(const unsigned long *hart_mask)
{
sbi_ecall(SBI_EXT_SEND_IPI, SBI_FID_SEND_IPI, (unsigned long)hart_mask,
0, 0, 0, 0, 0);
}

/**
* sbi_remote_fence_i() - Execute FENCE.I instruction on given remote harts.
* @hart_mask: A cpu mask containing all the target harts.
* 远程指示harts执行FENCE.I指令
* Return: None
*/
void sbi_remote_fence_i(const unsigned long *hart_mask)
{
sbi_ecall(SBI_EXT_REMOTE_FENCE_I, SBI_FID_REMOTE_FENCE_I,
(unsigned long)hart_mask, 0, 0, 0, 0, 0);
}

/**
* sbi_remote_sfence_vma() - Execute SFENCE.VMA instructions on given remote
* harts for the specified virtual address range.
* @hart_mask: A cpu mask containing all the target harts.
* @start: Start of the virtual address
* @size: Total size of the virtual address range.
* 指示远程hart执行一个或多个SFENCE.VMA指令,覆盖从start到size的虚拟地址范围内的地址。
* Return: None
*/
void sbi_remote_sfence_vma(const unsigned long *hart_mask,
unsigned long start,
unsigned long size)
{
sbi_ecall(SBI_EXT_REMOTE_SFENCE_VMA, SBI_FID_REMOTE_SFENCE_VMA,
(unsigned long)hart_mask, start, size, 0, 0, 0);
}


/**
* sbi_remote_sfence_vma_asid() - Execute SFENCE.VMA instructions on given
* remote harts for a virtual address range belonging to a specific ASID.
*
* @hart_mask: A cpu mask containing all the target harts.
* @start: Start of the virtual address
* @size: Total size of the virtual address range.
* @asid: The value of address space identifier (ASID).
* 指示远程hart执行一个或多个SFENCE.VMA指令,覆盖从start到size的虚拟地址范围内的地址。这仅涵盖给定的ASID。
* Return: None
*/
void sbi_remote_sfence_vma_asid(const unsigned long *hart_mask,
unsigned long start,
unsigned long size,
unsigned long asid)
{
sbi_ecall(SBI_EXT_REMOTE_SFENCE_VMA_ASID,
SBI_FID_REMOTE_SFENCE_VMA_ASID,
(unsigned long)hart_mask, start, size, asid, 0, 0);
}

riscv_reg.h

#ifndef RISCV_REG_H_
#define RISCV_REG_H_

//__riscv_xlen 是编译器预定义的宏
#if __riscv_xlen == 32
#define REGSIZE 4
#define REGSHIFT 2
#define LOAD lw
#define STOR sw
#elif __riscv_xlen == 64
#define REGSIZE 8
#define REGSHIFT 3
#define LOAD ld
#define STOR sd
#endif /* __riscv_xlen */

#endif
  • __riscv_xlen 是编译器预定义的宏,使用宏定义的方式来控制在32位和64位平台上编译的统一性
  • 定义了加载和存储指令以及寄存器大小

sbi_const.h

#ifndef __SBI_CONST_H__
#define __SBI_CONST_H__

/*提供一种在汇编和C代码之间共享常量定义的便捷方法*/
#ifdef __ASSEMBLER__
#define _AC(X,Y) X
#define _AT(T,X) X
#else
#define __AC(X,Y) (X##Y)
#define _AC(X,Y) __AC(X,Y)
#define _AT(T,X) ((T)(X))
#endif

//添加 UL后缀,定义无符号长整型和无符号长长整型常量
#define _UL(x) (_AC(x, UL))
#define _ULL(x) (_AC(x, ULL))

//用于生成位掩码,方便进行位操作,如设置、清除或检查某位。
#define _BITUL(x) (_UL(1) << (x))
#define _BITULL(x) (_ULL(1) << (x))

#define UL(x) (_UL(x))
#define ULL(x) (_ULL(x))

//将宏参数转换成字符串
#define __STR(s) #s
#define STRINGIFY(s) __STR(s)

#endif
  • 定义了一些宏用于添加符号后缀、生成bitmask、宏参数转换成字符串等功能
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2019 Western Digital Corporation or its affiliates.
*
* Authors:
* Anup Patel <anup.patel@wdc.com>
*/

#ifndef __RISCV_ENCODING_H__
#define __RISCV_ENCODING_H__

#include "sbi_const.h"

/* clang-format off */
#define MSTATUS_SIE _UL(0x00000002)
#define MSTATUS_MIE _UL(0x00000008)
#define MSTATUS_SPIE_SHIFT 5
#define MSTATUS_SPIE (_UL(1) << MSTATUS_SPIE_SHIFT)
#define MSTATUS_UBE _UL(0x00000040)
#define MSTATUS_MPIE _UL(0x00000080)
#define MSTATUS_SPP_SHIFT 8
#define MSTATUS_SPP (_UL(1) << MSTATUS_SPP_SHIFT)
#define MSTATUS_MPP_SHIFT 11
#define MSTATUS_MPP (_UL(3) << MSTATUS_MPP_SHIFT)
#define MSTATUS_FS _UL(0x00006000)
#define MSTATUS_XS _UL(0x00018000)
#define MSTATUS_VS _UL(0x01800000)
#define MSTATUS_MPRV _UL(0x00020000)
#define MSTATUS_SUM _UL(0x00040000)
#define MSTATUS_MXR _UL(0x00080000)
#define MSTATUS_TVM _UL(0x00100000)
#define MSTATUS_TW _UL(0x00200000)
#define MSTATUS_TSR _UL(0x00400000)
#define MSTATUS32_SD _UL(0x80000000)
#if __riscv_xlen == 64
#define MSTATUS_UXL _ULL(0x0000000300000000)
#define MSTATUS_SXL _ULL(0x0000000C00000000)
#define MSTATUS_SBE _ULL(0x0000001000000000)
#define MSTATUS_MBE _ULL(0x0000002000000000)
#define MSTATUS_MPV _ULL(0x0000008000000000)
#else
#define MSTATUSH_SBE _UL(0x00000010)
#define MSTATUSH_MBE _UL(0x00000020)
#define MSTATUSH_MPV _UL(0x00000080)
#endif
#define MSTATUS32_SD _UL(0x80000000)
#define MSTATUS64_SD _ULL(0x8000000000000000)

#define SSTATUS_SIE MSTATUS_SIE
#define SSTATUS_SPIE_SHIFT MSTATUS_SPIE_SHIFT
#define SSTATUS_SPIE MSTATUS_SPIE
#define SSTATUS_SPP_SHIFT MSTATUS_SPP_SHIFT
#define SSTATUS_SPP MSTATUS_SPP
#define SSTATUS_FS MSTATUS_FS
#define SSTATUS_XS MSTATUS_XS
#define SSTATUS_VS MSTATUS_VS
#define SSTATUS_SUM MSTATUS_SUM
#define SSTATUS_MXR MSTATUS_MXR
#define SSTATUS32_SD MSTATUS32_SD
#define SSTATUS64_UXL MSTATUS_UXL
#define SSTATUS64_SD MSTATUS64_SD

#if __riscv_xlen == 64
#define HSTATUS_VSXL _UL(0x300000000)
#define HSTATUS_VSXL_SHIFT 32
#endif
#define HSTATUS_VTSR _UL(0x00400000)
#define HSTATUS_VTW _UL(0x00200000)
#define HSTATUS_VTVM _UL(0x00100000)
#define HSTATUS_VGEIN _UL(0x0003f000)
#define HSTATUS_VGEIN_SHIFT 12
#define HSTATUS_HU _UL(0x00000200)
#define HSTATUS_SPVP _UL(0x00000100)
#define HSTATUS_SPV _UL(0x00000080)
#define HSTATUS_GVA _UL(0x00000040)
#define HSTATUS_VSBE _UL(0x00000020)

#define IRQ_S_SOFT 1
#define IRQ_VS_SOFT 2
#define IRQ_M_SOFT 3
#define IRQ_S_TIMER 5
#define IRQ_VS_TIMER 6
#define IRQ_M_TIMER 7
#define IRQ_S_EXT 9
#define IRQ_VS_EXT 10
#define IRQ_M_EXT 11
#define IRQ_S_GEXT 12

#define MIP_SSIP (_UL(1) << IRQ_S_SOFT)
#define MIP_VSSIP (_UL(1) << IRQ_VS_SOFT)
#define MIP_MSIP (_UL(1) << IRQ_M_SOFT)
#define MIP_STIP (_UL(1) << IRQ_S_TIMER)
#define MIP_VSTIP (_UL(1) << IRQ_VS_TIMER)
#define MIP_MTIP (_UL(1) << IRQ_M_TIMER)
#define MIP_SEIP (_UL(1) << IRQ_S_EXT)
#define MIP_VSEIP (_UL(1) << IRQ_VS_EXT)
#define MIP_MEIP (_UL(1) << IRQ_M_EXT)
#define MIP_SGEIP (_UL(1) << IRQ_S_GEXT)

#define SIP_SSIP MIP_SSIP
#define SIP_STIP MIP_STIP

#define PRV_U _UL(0)
#define PRV_S _UL(1)
#define PRV_M _UL(3)

#define SATP32_MODE _UL(0x80000000)
#define SATP32_ASID _UL(0x7FC00000)
#define SATP32_PPN _UL(0x003FFFFF)
#define SATP64_MODE _ULL(0xF000000000000000)
#define SATP64_ASID _ULL(0x0FFFF00000000000)
#define SATP64_PPN _ULL(0x00000FFFFFFFFFFF)

#define SATP_MODE_OFF _UL(0)
#define SATP_MODE_SV32 _UL(1)
#define SATP_MODE_SV39 _UL(8)
#define SATP_MODE_SV48 _UL(9)
#define SATP_MODE_SV57 _UL(10)
#define SATP_MODE_SV64 _UL(11)

#define HGATP_MODE_OFF _UL(0)
#define HGATP_MODE_SV32X4 _UL(1)
#define HGATP_MODE_SV39X4 _UL(8)
#define HGATP_MODE_SV48X4 _UL(9)

#define HGATP32_MODE_SHIFT 31
#define HGATP32_VMID_SHIFT 22
#define HGATP32_VMID_MASK _UL(0x1FC00000)
#define HGATP32_PPN _UL(0x003FFFFF)

#define HGATP64_MODE_SHIFT 60
#define HGATP64_VMID_SHIFT 44
#define HGATP64_VMID_MASK _ULL(0x03FFF00000000000)
#define HGATP64_PPN _ULL(0x00000FFFFFFFFFFF)

#define PMP_R _UL(0x01)
#define PMP_W _UL(0x02)
#define PMP_X _UL(0x04)
#define PMP_A _UL(0x18)
#define PMP_A_TOR _UL(0x08)
#define PMP_A_NA4 _UL(0x10)
#define PMP_A_NAPOT _UL(0x18)
#define PMP_L _UL(0x80)

#define PMP_SHIFT 2
#define PMP_COUNT 64
#if __riscv_xlen == 64
#define PMP_ADDR_MASK ((_ULL(0x1) << 54) - 1)
#else
#define PMP_ADDR_MASK _UL(0xFFFFFFFF)
#endif

#if __riscv_xlen == 64
#define MSTATUS_SD MSTATUS64_SD
#define SSTATUS_SD SSTATUS64_SD
#define SATP_MODE SATP64_MODE

#define HGATP_PPN HGATP64_PPN
#define HGATP_VMID_SHIFT HGATP64_VMID_SHIFT
#define HGATP_VMID_MASK HGATP64_VMID_MASK
#define HGATP_MODE_SHIFT HGATP64_MODE_SHIFT
#else
#define MSTATUS_SD MSTATUS32_SD
#define SSTATUS_SD SSTATUS32_SD
#define SATP_MODE SATP32_MODE

#define HGATP_PPN HGATP32_PPN
#define HGATP_VMID_SHIFT HGATP32_VMID_SHIFT
#define HGATP_VMID_MASK HGATP32_VMID_MASK
#define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT
#endif

/* ===== User-level CSRs ===== */

/* User Trap Setup (N-extension) */
#define CSR_USTATUS 0x000
#define CSR_UIE 0x004
#define CSR_UTVEC 0x005

/* User Trap Handling (N-extension) */
#define CSR_USCRATCH 0x040
#define CSR_UEPC 0x041
#define CSR_UCAUSE 0x042
#define CSR_UTVAL 0x043
#define CSR_UIP 0x044

/* User Floating-point CSRs */
#define CSR_FFLAGS 0x001
#define CSR_FRM 0x002
#define CSR_FCSR 0x003

/* User Counters/Timers */
#define CSR_CYCLE 0xc00
#define CSR_TIME 0xc01
#define CSR_INSTRET 0xc02
#define CSR_HPMCOUNTER3 0xc03
#define CSR_HPMCOUNTER4 0xc04
#define CSR_HPMCOUNTER5 0xc05
#define CSR_HPMCOUNTER6 0xc06
#define CSR_HPMCOUNTER7 0xc07
#define CSR_HPMCOUNTER8 0xc08
#define CSR_HPMCOUNTER9 0xc09
#define CSR_HPMCOUNTER10 0xc0a
#define CSR_HPMCOUNTER11 0xc0b
#define CSR_HPMCOUNTER12 0xc0c
#define CSR_HPMCOUNTER13 0xc0d
#define CSR_HPMCOUNTER14 0xc0e
#define CSR_HPMCOUNTER15 0xc0f
#define CSR_HPMCOUNTER16 0xc10
#define CSR_HPMCOUNTER17 0xc11
#define CSR_HPMCOUNTER18 0xc12
#define CSR_HPMCOUNTER19 0xc13
#define CSR_HPMCOUNTER20 0xc14
#define CSR_HPMCOUNTER21 0xc15
#define CSR_HPMCOUNTER22 0xc16
#define CSR_HPMCOUNTER23 0xc17
#define CSR_HPMCOUNTER24 0xc18
#define CSR_HPMCOUNTER25 0xc19
#define CSR_HPMCOUNTER26 0xc1a
#define CSR_HPMCOUNTER27 0xc1b
#define CSR_HPMCOUNTER28 0xc1c
#define CSR_HPMCOUNTER29 0xc1d
#define CSR_HPMCOUNTER30 0xc1e
#define CSR_HPMCOUNTER31 0xc1f
#define CSR_CYCLEH 0xc80
#define CSR_TIMEH 0xc81
#define CSR_INSTRETH 0xc82
#define CSR_HPMCOUNTER3H 0xc83
#define CSR_HPMCOUNTER4H 0xc84
#define CSR_HPMCOUNTER5H 0xc85
#define CSR_HPMCOUNTER6H 0xc86
#define CSR_HPMCOUNTER7H 0xc87
#define CSR_HPMCOUNTER8H 0xc88
#define CSR_HPMCOUNTER9H 0xc89
#define CSR_HPMCOUNTER10H 0xc8a
#define CSR_HPMCOUNTER11H 0xc8b
#define CSR_HPMCOUNTER12H 0xc8c
#define CSR_HPMCOUNTER13H 0xc8d
#define CSR_HPMCOUNTER14H 0xc8e
#define CSR_HPMCOUNTER15H 0xc8f
#define CSR_HPMCOUNTER16H 0xc90
#define CSR_HPMCOUNTER17H 0xc91
#define CSR_HPMCOUNTER18H 0xc92
#define CSR_HPMCOUNTER19H 0xc93
#define CSR_HPMCOUNTER20H 0xc94
#define CSR_HPMCOUNTER21H 0xc95
#define CSR_HPMCOUNTER22H 0xc96
#define CSR_HPMCOUNTER23H 0xc97
#define CSR_HPMCOUNTER24H 0xc98
#define CSR_HPMCOUNTER25H 0xc99
#define CSR_HPMCOUNTER26H 0xc9a
#define CSR_HPMCOUNTER27H 0xc9b
#define CSR_HPMCOUNTER28H 0xc9c
#define CSR_HPMCOUNTER29H 0xc9d
#define CSR_HPMCOUNTER30H 0xc9e
#define CSR_HPMCOUNTER31H 0xc9f

/* ===== Supervisor-level CSRs ===== */

/* Supervisor Trap Setup */
#define CSR_SSTATUS 0x100
#define CSR_SEDELEG 0x102
#define CSR_SIDELEG 0x103
#define CSR_SIE 0x104
#define CSR_STVEC 0x105
#define CSR_SCOUNTEREN 0x106

/* Supervisor Trap Handling */
#define CSR_SSCRATCH 0x140
#define CSR_SEPC 0x141
#define CSR_SCAUSE 0x142
#define CSR_STVAL 0x143
#define CSR_SIP 0x144

/* Supervisor Protection and Translation */
#define CSR_SATP 0x180

/* ===== Hypervisor-level CSRs ===== */

/* Hypervisor Trap Setup (H-extension) */
#define CSR_HSTATUS 0x600
#define CSR_HEDELEG 0x602
#define CSR_HIDELEG 0x603
#define CSR_HIE 0x604
#define CSR_HCOUNTEREN 0x606
#define CSR_HGEIE 0x607

/* Hypervisor Trap Handling (H-extension) */
#define CSR_HTVAL 0x643
#define CSR_HIP 0x644
#define CSR_HVIP 0x645
#define CSR_HTINST 0x64a
#define CSR_HGEIP 0xe12

/* Hypervisor Protection and Translation (H-extension) */
#define CSR_HGATP 0x680

/* Hypervisor Counter/Timer Virtualization Registers (H-extension) */
#define CSR_HTIMEDELTA 0x605
#define CSR_HTIMEDELTAH 0x615

/* Virtual Supervisor Registers (H-extension) */
#define CSR_VSSTATUS 0x200
#define CSR_VSIE 0x204
#define CSR_VSTVEC 0x205
#define CSR_VSSCRATCH 0x240
#define CSR_VSEPC 0x241
#define CSR_VSCAUSE 0x242
#define CSR_VSTVAL 0x243
#define CSR_VSIP 0x244
#define CSR_VSATP 0x280

/* ===== Machine-level CSRs ===== */

/* Machine Information Registers */
#define CSR_MVENDORID 0xf11
#define CSR_MARCHID 0xf12
#define CSR_MIMPID 0xf13
#define CSR_MHARTID 0xf14

/* Machine Trap Setup */
#define CSR_MSTATUS 0x300
#define CSR_MISA 0x301
#define CSR_MEDELEG 0x302
#define CSR_MIDELEG 0x303
#define CSR_MIE 0x304
#define CSR_MTVEC 0x305
#define CSR_MCOUNTEREN 0x306
#define CSR_MSTATUSH 0x310

/* Machine Trap Handling */
#define CSR_MSCRATCH 0x340
#define CSR_MEPC 0x341
#define CSR_MCAUSE 0x342
#define CSR_MTVAL 0x343
#define CSR_MIP 0x344
#define CSR_MTINST 0x34a
#define CSR_MTVAL2 0x34b

/* Machine Memory Protection */
#define CSR_PMPCFG0 0x3a0
#define CSR_PMPCFG1 0x3a1
#define CSR_PMPCFG2 0x3a2
#define CSR_PMPCFG3 0x3a3
#define CSR_PMPCFG4 0x3a4
#define CSR_PMPCFG5 0x3a5
#define CSR_PMPCFG6 0x3a6
#define CSR_PMPCFG7 0x3a7
#define CSR_PMPCFG8 0x3a8
#define CSR_PMPCFG9 0x3a9
#define CSR_PMPCFG10 0x3aa
#define CSR_PMPCFG11 0x3ab
#define CSR_PMPCFG12 0x3ac
#define CSR_PMPCFG13 0x3ad
#define CSR_PMPCFG14 0x3ae
#define CSR_PMPCFG15 0x3af
#define CSR_PMPADDR0 0x3b0
#define CSR_PMPADDR1 0x3b1
#define CSR_PMPADDR2 0x3b2
#define CSR_PMPADDR3 0x3b3
#define CSR_PMPADDR4 0x3b4
#define CSR_PMPADDR5 0x3b5
#define CSR_PMPADDR6 0x3b6
#define CSR_PMPADDR7 0x3b7
#define CSR_PMPADDR8 0x3b8
#define CSR_PMPADDR9 0x3b9
#define CSR_PMPADDR10 0x3ba
#define CSR_PMPADDR11 0x3bb
#define CSR_PMPADDR12 0x3bc
#define CSR_PMPADDR13 0x3bd
#define CSR_PMPADDR14 0x3be
#define CSR_PMPADDR15 0x3bf
#define CSR_PMPADDR16 0x3c0
#define CSR_PMPADDR17 0x3c1
#define CSR_PMPADDR18 0x3c2
#define CSR_PMPADDR19 0x3c3
#define CSR_PMPADDR20 0x3c4
#define CSR_PMPADDR21 0x3c5
#define CSR_PMPADDR22 0x3c6
#define CSR_PMPADDR23 0x3c7
#define CSR_PMPADDR24 0x3c8
#define CSR_PMPADDR25 0x3c9
#define CSR_PMPADDR26 0x3ca
#define CSR_PMPADDR27 0x3cb
#define CSR_PMPADDR28 0x3cc
#define CSR_PMPADDR29 0x3cd
#define CSR_PMPADDR30 0x3ce
#define CSR_PMPADDR31 0x3cf
#define CSR_PMPADDR32 0x3d0
#define CSR_PMPADDR33 0x3d1
#define CSR_PMPADDR34 0x3d2
#define CSR_PMPADDR35 0x3d3
#define CSR_PMPADDR36 0x3d4
#define CSR_PMPADDR37 0x3d5
#define CSR_PMPADDR38 0x3d6
#define CSR_PMPADDR39 0x3d7
#define CSR_PMPADDR40 0x3d8
#define CSR_PMPADDR41 0x3d9
#define CSR_PMPADDR42 0x3da
#define CSR_PMPADDR43 0x3db
#define CSR_PMPADDR44 0x3dc
#define CSR_PMPADDR45 0x3dd
#define CSR_PMPADDR46 0x3de
#define CSR_PMPADDR47 0x3df
#define CSR_PMPADDR48 0x3e0
#define CSR_PMPADDR49 0x3e1
#define CSR_PMPADDR50 0x3e2
#define CSR_PMPADDR51 0x3e3
#define CSR_PMPADDR52 0x3e4
#define CSR_PMPADDR53 0x3e5
#define CSR_PMPADDR54 0x3e6
#define CSR_PMPADDR55 0x3e7
#define CSR_PMPADDR56 0x3e8
#define CSR_PMPADDR57 0x3e9
#define CSR_PMPADDR58 0x3ea
#define CSR_PMPADDR59 0x3eb
#define CSR_PMPADDR60 0x3ec
#define CSR_PMPADDR61 0x3ed
#define CSR_PMPADDR62 0x3ee
#define CSR_PMPADDR63 0x3ef

/* Machine Counters/Timers */
#define CSR_MCYCLE 0xb00
#define CSR_MINSTRET 0xb02
#define CSR_MHPMCOUNTER3 0xb03
#define CSR_MHPMCOUNTER4 0xb04
#define CSR_MHPMCOUNTER5 0xb05
#define CSR_MHPMCOUNTER6 0xb06
#define CSR_MHPMCOUNTER7 0xb07
#define CSR_MHPMCOUNTER8 0xb08
#define CSR_MHPMCOUNTER9 0xb09
#define CSR_MHPMCOUNTER10 0xb0a
#define CSR_MHPMCOUNTER11 0xb0b
#define CSR_MHPMCOUNTER12 0xb0c
#define CSR_MHPMCOUNTER13 0xb0d
#define CSR_MHPMCOUNTER14 0xb0e
#define CSR_MHPMCOUNTER15 0xb0f
#define CSR_MHPMCOUNTER16 0xb10
#define CSR_MHPMCOUNTER17 0xb11
#define CSR_MHPMCOUNTER18 0xb12
#define CSR_MHPMCOUNTER19 0xb13
#define CSR_MHPMCOUNTER20 0xb14
#define CSR_MHPMCOUNTER21 0xb15
#define CSR_MHPMCOUNTER22 0xb16
#define CSR_MHPMCOUNTER23 0xb17
#define CSR_MHPMCOUNTER24 0xb18
#define CSR_MHPMCOUNTER25 0xb19
#define CSR_MHPMCOUNTER26 0xb1a
#define CSR_MHPMCOUNTER27 0xb1b
#define CSR_MHPMCOUNTER28 0xb1c
#define CSR_MHPMCOUNTER29 0xb1d
#define CSR_MHPMCOUNTER30 0xb1e
#define CSR_MHPMCOUNTER31 0xb1f
#define CSR_MCYCLEH 0xb80
#define CSR_MINSTRETH 0xb82
#define CSR_MHPMCOUNTER3H 0xb83
#define CSR_MHPMCOUNTER4H 0xb84
#define CSR_MHPMCOUNTER5H 0xb85
#define CSR_MHPMCOUNTER6H 0xb86
#define CSR_MHPMCOUNTER7H 0xb87
#define CSR_MHPMCOUNTER8H 0xb88
#define CSR_MHPMCOUNTER9H 0xb89
#define CSR_MHPMCOUNTER10H 0xb8a
#define CSR_MHPMCOUNTER11H 0xb8b
#define CSR_MHPMCOUNTER12H 0xb8c
#define CSR_MHPMCOUNTER13H 0xb8d
#define CSR_MHPMCOUNTER14H 0xb8e
#define CSR_MHPMCOUNTER15H 0xb8f
#define CSR_MHPMCOUNTER16H 0xb90
#define CSR_MHPMCOUNTER17H 0xb91
#define CSR_MHPMCOUNTER18H 0xb92
#define CSR_MHPMCOUNTER19H 0xb93
#define CSR_MHPMCOUNTER20H 0xb94
#define CSR_MHPMCOUNTER21H 0xb95
#define CSR_MHPMCOUNTER22H 0xb96
#define CSR_MHPMCOUNTER23H 0xb97
#define CSR_MHPMCOUNTER24H 0xb98
#define CSR_MHPMCOUNTER25H 0xb99
#define CSR_MHPMCOUNTER26H 0xb9a
#define CSR_MHPMCOUNTER27H 0xb9b
#define CSR_MHPMCOUNTER28H 0xb9c
#define CSR_MHPMCOUNTER29H 0xb9d
#define CSR_MHPMCOUNTER30H 0xb9e
#define CSR_MHPMCOUNTER31H 0xb9f

/* Machine Counter Setup */
#define CSR_MCOUNTINHIBIT 0x320
#define CSR_MHPMEVENT3 0x323
#define CSR_MHPMEVENT4 0x324
#define CSR_MHPMEVENT5 0x325
#define CSR_MHPMEVENT6 0x326
#define CSR_MHPMEVENT7 0x327
#define CSR_MHPMEVENT8 0x328
#define CSR_MHPMEVENT9 0x329
#define CSR_MHPMEVENT10 0x32a
#define CSR_MHPMEVENT11 0x32b
#define CSR_MHPMEVENT12 0x32c
#define CSR_MHPMEVENT13 0x32d
#define CSR_MHPMEVENT14 0x32e
#define CSR_MHPMEVENT15 0x32f
#define CSR_MHPMEVENT16 0x330
#define CSR_MHPMEVENT17 0x331
#define CSR_MHPMEVENT18 0x332
#define CSR_MHPMEVENT19 0x333
#define CSR_MHPMEVENT20 0x334
#define CSR_MHPMEVENT21 0x335
#define CSR_MHPMEVENT22 0x336
#define CSR_MHPMEVENT23 0x337
#define CSR_MHPMEVENT24 0x338
#define CSR_MHPMEVENT25 0x339
#define CSR_MHPMEVENT26 0x33a
#define CSR_MHPMEVENT27 0x33b
#define CSR_MHPMEVENT28 0x33c
#define CSR_MHPMEVENT29 0x33d
#define CSR_MHPMEVENT30 0x33e
#define CSR_MHPMEVENT31 0x33f

/* Debug/Trace Registers */
#define CSR_TSELECT 0x7a0
#define CSR_TDATA1 0x7a1
#define CSR_TDATA2 0x7a2
#define CSR_TDATA3 0x7a3

/* Debug Mode Registers */
#define CSR_DCSR 0x7b0
#define CSR_DPC 0x7b1
#define CSR_DSCRATCH0 0x7b2
#define CSR_DSCRATCH1 0x7b3

/* ===== Trap/Exception Causes ===== */

#define CAUSE_MISALIGNED_FETCH 0x0
#define CAUSE_FETCH_ACCESS 0x1
#define CAUSE_ILLEGAL_INSTRUCTION 0x2
#define CAUSE_BREAKPOINT 0x3
#define CAUSE_MISALIGNED_LOAD 0x4
#define CAUSE_LOAD_ACCESS 0x5
#define CAUSE_MISALIGNED_STORE 0x6
#define CAUSE_STORE_ACCESS 0x7
#define CAUSE_USER_ECALL 0x8
#define CAUSE_SUPERVISOR_ECALL 0x9
#define CAUSE_VIRTUAL_SUPERVISOR_ECALL 0xa
#define CAUSE_MACHINE_ECALL 0xb
#define CAUSE_FETCH_PAGE_FAULT 0xc
#define CAUSE_LOAD_PAGE_FAULT 0xd
#define CAUSE_STORE_PAGE_FAULT 0xf
#define CAUSE_FETCH_GUEST_PAGE_FAULT 0x14
#define CAUSE_LOAD_GUEST_PAGE_FAULT 0x15
#define CAUSE_VIRTUAL_INST_FAULT 0x16
#define CAUSE_STORE_GUEST_PAGE_FAULT 0x17

/* ===== Instruction Encodings ===== */

#define INSN_MATCH_LB 0x3
#define INSN_MASK_LB 0x707f
#define INSN_MATCH_LH 0x1003
#define INSN_MASK_LH 0x707f
#define INSN_MATCH_LW 0x2003
#define INSN_MASK_LW 0x707f
#define INSN_MATCH_LD 0x3003
#define INSN_MASK_LD 0x707f
#define INSN_MATCH_LBU 0x4003
#define INSN_MASK_LBU 0x707f
#define INSN_MATCH_LHU 0x5003
#define INSN_MASK_LHU 0x707f
#define INSN_MATCH_LWU 0x6003
#define INSN_MASK_LWU 0x707f
#define INSN_MATCH_SB 0x23
#define INSN_MASK_SB 0x707f
#define INSN_MATCH_SH 0x1023
#define INSN_MASK_SH 0x707f
#define INSN_MATCH_SW 0x2023
#define INSN_MASK_SW 0x707f
#define INSN_MATCH_SD 0x3023
#define INSN_MASK_SD 0x707f

#define INSN_MATCH_FLW 0x2007
#define INSN_MASK_FLW 0x707f
#define INSN_MATCH_FLD 0x3007
#define INSN_MASK_FLD 0x707f
#define INSN_MATCH_FLQ 0x4007
#define INSN_MASK_FLQ 0x707f
#define INSN_MATCH_FSW 0x2027
#define INSN_MASK_FSW 0x707f
#define INSN_MATCH_FSD 0x3027
#define INSN_MASK_FSD 0x707f
#define INSN_MATCH_FSQ 0x4027
#define INSN_MASK_FSQ 0x707f

#define INSN_MATCH_C_LD 0x6000
#define INSN_MASK_C_LD 0xe003
#define INSN_MATCH_C_SD 0xe000
#define INSN_MASK_C_SD 0xe003
#define INSN_MATCH_C_LW 0x4000
#define INSN_MASK_C_LW 0xe003
#define INSN_MATCH_C_SW 0xc000
#define INSN_MASK_C_SW 0xe003
#define INSN_MATCH_C_LDSP 0x6002
#define INSN_MASK_C_LDSP 0xe003
#define INSN_MATCH_C_SDSP 0xe002
#define INSN_MASK_C_SDSP 0xe003
#define INSN_MATCH_C_LWSP 0x4002
#define INSN_MASK_C_LWSP 0xe003
#define INSN_MATCH_C_SWSP 0xc002
#define INSN_MASK_C_SWSP 0xe003

#define INSN_MATCH_C_FLD 0x2000
#define INSN_MASK_C_FLD 0xe003
#define INSN_MATCH_C_FLW 0x6000
#define INSN_MASK_C_FLW 0xe003
#define INSN_MATCH_C_FSD 0xa000
#define INSN_MASK_C_FSD 0xe003
#define INSN_MATCH_C_FSW 0xe000
#define INSN_MASK_C_FSW 0xe003
#define INSN_MATCH_C_FLDSP 0x2002
#define INSN_MASK_C_FLDSP 0xe003
#define INSN_MATCH_C_FSDSP 0xa002
#define INSN_MASK_C_FSDSP 0xe003
#define INSN_MATCH_C_FLWSP 0x6002
#define INSN_MASK_C_FLWSP 0xe003
#define INSN_MATCH_C_FSWSP 0xe002
#define INSN_MASK_C_FSWSP 0xe003

#define INSN_MASK_WFI 0xffffff00
#define INSN_MATCH_WFI 0x10500000

#define INSN_16BIT_MASK 0x3
#define INSN_32BIT_MASK 0x1c

#define INSN_IS_16BIT(insn) \
(((insn) & INSN_16BIT_MASK) != INSN_16BIT_MASK)
#define INSN_IS_32BIT(insn) \
(((insn) & INSN_16BIT_MASK) == INSN_16BIT_MASK && \
((insn) & INSN_32BIT_MASK) != INSN_32BIT_MASK)

#define INSN_LEN(insn) (INSN_IS_16BIT(insn) ? 2 : 4)

#if __riscv_xlen == 64
#define LOG_REGBYTES 3
#else
#define LOG_REGBYTES 2
#endif
#define REGBYTES (1 << LOG_REGBYTES)

#define SH_RD 7
#define SH_RS1 15
#define SH_RS2 20
#define SH_RS2C 2

#define RV_X(x, s, n) (((x) >> (s)) & ((1 << (n)) - 1))
#define RVC_LW_IMM(x) ((RV_X(x, 6, 1) << 2) | \
(RV_X(x, 10, 3) << 3) | \
(RV_X(x, 5, 1) << 6))
#define RVC_LD_IMM(x) ((RV_X(x, 10, 3) << 3) | \
(RV_X(x, 5, 2) << 6))
#define RVC_LWSP_IMM(x) ((RV_X(x, 4, 3) << 2) | \
(RV_X(x, 12, 1) << 5) | \
(RV_X(x, 2, 2) << 6))
#define RVC_LDSP_IMM(x) ((RV_X(x, 5, 2) << 3) | \
(RV_X(x, 12, 1) << 5) | \
(RV_X(x, 2, 3) << 6))
#define RVC_SWSP_IMM(x) ((RV_X(x, 9, 4) << 2) | \
(RV_X(x, 7, 2) << 6))
#define RVC_SDSP_IMM(x) ((RV_X(x, 10, 3) << 3) | \
(RV_X(x, 7, 3) << 6))
#define RVC_RS1S(insn) (8 + RV_X(insn, SH_RD, 3))
#define RVC_RS2S(insn) (8 + RV_X(insn, SH_RS2C, 3))
#define RVC_RS2(insn) RV_X(insn, SH_RS2C, 5)

#define SHIFT_RIGHT(x, y) \
((y) < 0 ? ((x) << -(y)) : ((x) >> (y)))

#define REG_MASK \
((1 << (5 + LOG_REGBYTES)) - (1 << LOG_REGBYTES))

#define REG_OFFSET(insn, pos) \
(SHIFT_RIGHT((insn), (pos) - LOG_REGBYTES) & REG_MASK)

#define REG_PTR(insn, pos, regs) \
(ulong *)((ulong)(regs) + REG_OFFSET(insn, pos))

#define GET_RM(insn) (((insn) >> 12) & 7)

#define GET_RS1(insn, regs) (*REG_PTR(insn, SH_RS1, regs))
#define GET_RS2(insn, regs) (*REG_PTR(insn, SH_RS2, regs))
#define GET_RS1S(insn, regs) (*REG_PTR(RVC_RS1S(insn), 0, regs))
#define GET_RS2S(insn, regs) (*REG_PTR(RVC_RS2S(insn), 0, regs))
#define GET_RS2C(insn, regs) (*REG_PTR(insn, SH_RS2C, regs))
#define GET_SP(regs) (*REG_PTR(2, 0, regs))
#define SET_RD(insn, regs, val) (*REG_PTR(insn, SH_RD, regs) = (val))
#define IMM_I(insn) ((s32)(insn) >> 20)
#define IMM_S(insn) (((s32)(insn) >> 25 << 5) | \
(s32)(((insn) >> 7) & 0x1f))
#define MASK_FUNCT3 0x7000

/* clang-format on */

#endif
  • 定义了和riscv架构寄存器相关的代码,在riscv架构中,各个特权级的控制寄存器是被统一映射到了一个确定的地址,可以直接操作这个地址来操作csr,可以在官方文档中找到The RISC-V Instruction Set Manual,我将此文档放在了ref目录下,举个例子

  • image-20240420173015169

  • 可以看见上面文档中定义了控制寄存器对应的映射地址,在riscv_encoding.h做了对应的定义:

    /* ===== Supervisor-level CSRs ===== */

    /* Supervisor Trap Setup */
    #define CSR_SSTATUS 0x100
    #define CSR_SEDELEG 0x102
    #define CSR_SIDELEG 0x103
    #define CSR_SIE 0x104
    #define CSR_STVEC 0x105
    #define CSR_SCOUNTEREN 0x106

    /* Supervisor Trap Handling */
    #define CSR_SSCRATCH 0x140
    #define CSR_SEPC 0x141
    #define CSR_SCAUSE 0x142
    #define CSR_STVAL 0x143
    #define CSR_SIP 0x144

    /* Supervisor Protection and Translation */
    #define CSR_SATP 0x180

riscv_encoding.h

#ifndef __RISCV_ASM_H__
#define __RISCV_ASM_H__
#include "riscv_encoding.h"

/* clang-format off */


#ifdef __ASSEMBLY__
#define __ASM_STR(x) x
#else
#define __ASM_STR(x) #x
#endif

#if __riscv_xlen == 64
#define __REG_SEL(a, b) __ASM_STR(a)
#elif __riscv_xlen == 32
#define __REG_SEL(a, b) __ASM_STR(b)
#else
#error "Unexpected __riscv_xlen"
#endif

#define PAGE_SHIFT (12)
#define PAGE_SIZE (_AC(1, UL) << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE - 1))

#define REG_L __REG_SEL(ld, lw)
#define REG_S __REG_SEL(sd, sw)
#define SZREG __REG_SEL(8, 4)
#define LGREG __REG_SEL(3, 2)

#if __SIZEOF_POINTER__ == 8
#ifdef __ASSEMBLY__
#define RISCV_PTR .dword
#define RISCV_SZPTR 8
#define RISCV_LGPTR 3
#else
#define RISCV_PTR ".dword"
#define RISCV_SZPTR "8"
#define RISCV_LGPTR "3"
#endif
#elif __SIZEOF_POINTER__ == 4
#ifdef __ASSEMBLY__
#define RISCV_PTR .word
#define RISCV_SZPTR 4
#define RISCV_LGPTR 2
#else
#define RISCV_PTR ".word"
#define RISCV_SZPTR "4"
#define RISCV_LGPTR "2"
#endif
#else
#error "Unexpected __SIZEOF_POINTER__"
#endif

#if (__SIZEOF_INT__ == 4)
#define RISCV_INT __ASM_STR(.word)
#define RISCV_SZINT __ASM_STR(4)
#define RISCV_LGINT __ASM_STR(2)
#else
#error "Unexpected __SIZEOF_INT__"
#endif

#if (__SIZEOF_SHORT__ == 2)
#define RISCV_SHORT __ASM_STR(.half)
#define RISCV_SZSHORT __ASM_STR(2)
#define RISCV_LGSHORT __ASM_STR(1)
#else
#error "Unexpected __SIZEOF_SHORT__"
#endif

/* clang-format on */

#ifndef __ASSEMBLY__

//交换 csr 和 val 的值,csr的值读出后保存在变量 __v 中
#define csr_swap(csr, val) \
({ \
unsigned long __v = (unsigned long)(val); \
__asm__ __volatile__("csrrw %0, " __ASM_STR(csr) ", %1" \
: "=r"(__v) \
: "rK"(__v) \
: "memory"); \
__v; \
})


#define csr_read(csr) \
({ \
register unsigned long __v; \
__asm__ __volatile__("csrr %0, " __ASM_STR(csr) \
: "=r"(__v) \
: \
: "memory"); \
__v; \
})

#define csr_write(csr, val) \
({ \
unsigned long __v = (unsigned long)(val); \
__asm__ __volatile__("csrw " __ASM_STR(csr) ", %0" \
: \
: "rK"(__v) \
: "memory"); \
})

#define csr_read_set(csr, val) \
({ \
unsigned long __v = (unsigned long)(val); \
__asm__ __volatile__("csrrs %0, " __ASM_STR(csr) ", %1" \
: "=r"(__v) \
: "rK"(__v) \
: "memory"); \
__v; \
})

#define csr_set(csr, val) \
({ \
unsigned long __v = (unsigned long)(val); \
__asm__ __volatile__("csrs " __ASM_STR(csr) ", %0" \
: \
: "rK"(__v) \
: "memory"); \
})

#define csr_read_clear(csr, val) \
({ \
unsigned long __v = (unsigned long)(val); \
__asm__ __volatile__("csrrc %0, " __ASM_STR(csr) ", %1" \
: "=r"(__v) \
: "rK"(__v) \
: "memory"); \
__v; \
})

#define csr_clear(csr, val) \
({ \
unsigned long __v = (unsigned long)(val); \
__asm__ __volatile__("csrc " __ASM_STR(csr) ", %0" \
: \
: "rK"(__v) \
: "memory"); \
})



/*wfi(Wait For Interrupt)指令告诉处理器停止执行并等待直到一个中断或异常发生。
在这个状态下,处理器可以降低功耗,直到被外部事件(如中断)唤醒。*/
#define wfi() \
do { \
__asm__ __volatile__("wfi" ::: "memory"); \
} while (0)

/* Get current HART id */
#define current_hartid() ((unsigned int)csr_read(CSR_MHARTID))



#endif /* !__ASSEMBLY__ */

#endif
  • 定义了一些读取控制寄存器的函数,使用内联汇编的方式来实现

3. FreeRTOS移植相关代码

3.1 FreeRTOS源码组织

移植之前先看一下FreeRTOS的源码组织架构:

image-20240421141951413

  • Demo为各个架构平台的示例代码,可以整个删除掉
  • source/portable:此文件夹下的文件就是FreeRTOS适配不同平台的代码

STM32为例子,源码目录构建如下

image-20240421142343638

我们的目标是为riscv做移植,因此只需要实现portable文件夹下所需文件,核心源文件和include文件架下的头文件复制即可,我们使用的GCC编译,不是使用keilIAR这样的IDE平台,因此如下图portable文件夹下只需要保留两个文件夹的内容即可,其他全部删除

image-20240421142732182

GCC目录下也有很多平台的实现,我们全部删除,新建一个RISC-V目录,我们移植相关的代码就放在此文件夹下,在MenMang目录下有5个用于分配内存的源文件,我们使用一个就行,保留heap_4.c,其余删除。剪枝后trusted_domain的代码目录为:

image-20240421143248805

3.2 FreeRTOS配置

FreeRTOS是支持很多配置的,需要实现定义一些宏,用于开启和关闭 FreeRTOS内核的某些功能,这些宏定义在FreeRTOSConfig.h

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html.
*----------------------------------------------------------*/

/* See https://www.freertos.org/Using-FreeRTOS-on-RISC-V.html */
#define configMTIME_BASE_ADDRESS ( CLINT_ADDR + CLINT_MTIME )
#define configMTIMECMP_BASE_ADDRESS ( CLINT_ADDR + CLINT_MTIMECMP )

#define configUSE_PREEMPTION 1 // 任务抢占
#define configUSE_IDLE_HOOK 0 // IDLE 任务钩子函数
#define configUSE_TICK_HOOK 0 // 时钟中断 钩子函数
#define configCPU_CLOCK_HZ ( 10000000 ) //RTC时钟频率
#define configTICK_RATE_HZ ( ( TickType_t ) 100 ) //时钟中断频率
#define configMAX_PRIORITIES ( 7 ) //最高优先级
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 512 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) 64500 )
#define configMAX_TASK_NAME_LEN ( 16 ) //任务名的长度
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 0
#define configUSE_MUTEXES 1 //启用互斥锁
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_RECURSIVE_MUTEXES 1 //启动递归互斥锁
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1 //启用信号量
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
#define configTIMER_QUEUE_LENGTH 6
#define configTIMER_TASK_STACK_DEPTH ( 110 )

/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1

/* RISC-V definitions. */
#define configISR_STACK_SIZE_WORDS 2048

/* Task priorities. Allow these to be overridden. */
#ifndef uartPRIMARY_PRIORITY
#define uartPRIMARY_PRIORITY ( configMAX_PRIORITIES - 3 )
#endif

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xTaskAbortDelay 1
#define INCLUDE_xTaskGetHandle 1
#define INCLUDE_xSemaphoreGetMutexHolder 1

#endif /* FREERTOS_CONFIG_H */


比较重要的几个配置为:

  • configCPU_CLOCK_HZ : RTC 的时钟频率
  • configTICK_RATE_HZ :时钟中断的频率
  • configUSE_PREEMPTION:开启抢占式任务调度

qemurtc的时钟频率为设备树中定义的换算成10进制即10000000,时钟中断的频率可以配置。

image-20240421144651536

关于时钟的使用和定义:分时多任务系统与抢占式调度 | TimerのBlog

3.3 portable目录下移植文件

明确我们的目标是移植FreeRTOS到RISCV的S态运行,M态运行的是OpenSBI,在官方的移植文件中:portable/GCC/RISC-V目录下有移植的Demo,但是官方是移植FreeRTOS到M态运行,我们参考着官方的移植代码来进行实现S态的移植,移植的文件涉及到三个:portmacro.hport.cportASM.S,我先直接给出这三个和架构相关的移植代码,在下一小节中我们从FreeRTOS的启动依次来详细描述内部的函数细节

portmacro.h



#ifndef PORTMACRO_H
#define PORTMACRO_H


#ifdef __cplusplus
extern "C" {
#endif

#include "quard_star.h"
#include "FreeRTOSConfig.h"
/* Type definitions. */
#if __riscv_xlen == 64
#define portSTACK_TYPE uint64_t
#define portBASE_TYPE int64_t
#define portUBASE_TYPE uint64_t
#define portMAX_DELAY ( TickType_t ) 0xffffffffffffffffUL
#define portPOINTER_SIZE_TYPE uint64_t
#elif __riscv_xlen == 32
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE int32_t
#define portUBASE_TYPE uint32_t
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#else
#error Assembler did not define __riscv_xlen
#endif

//数据类型定义,分为32位 和 64 位
typedef portSTACK_TYPE StackType_t;
typedef portBASE_TYPE BaseType_t;
typedef portUBASE_TYPE UBaseType_t;
typedef portUBASE_TYPE TickType_t;

/* Legacy type definitions. */
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short

/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
not need to be guarded with a critical section. */
/*portTICK_TYPE_IS_ATOMIC这个宏定义的设定通常用于指示系统的tick计数类型是
否可以在不使用临界区(critical section)的情况下安全地读取。*/
#define portTICK_TYPE_IS_ATOMIC 1
/*-----------------------------------------------------------*/

/* Architecture specifics. */
#define portSTACK_GROWTH ( -1 ) //定义了栈的增长方向
#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ ) //操作系统的tick周期(以毫秒为单位)
#ifdef __riscv64
#error This is the RV32 port that has not yet been adapted for 64.
#define portBYTE_ALIGNMENT 16
#else
#define portBYTE_ALIGNMENT 16 //所有数据结构都应该按16字节对齐
#endif
/*-----------------------------------------------------------*/


/* Scheduler utilities. */
extern void vTaskSwitchContext( void ); //上下文调度函数,不同架构不同
/* 通过sbi触发自己核心的软中断 */
extern void sbi_send_ipi(const unsigned long *hart_mask);
//#define portYIELD() __asm volatile( "ecall" ); 原本是在m态通过ecall触发软中断
#define portYIELD() sbi_send_ipi((const unsigned long *)(0x1<<PRIM_HART)) //向hart发送中断信号
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired ) vTaskSwitchContext()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
/*-----------------------------------------------------------*/


/* Critical section management. */
#define portCRITICAL_NESTING_IN_TCB 1
extern void vTaskEnterCritical( void ); //进入临界区
extern void vTaskExitCritical( void ); //退出临界区


#define portSET_INTERRUPT_MASK_FROM_ISR() 0
#define portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedStatusValue ) ( void ) uxSavedStatusValue


/* S模式下SIE全局中断控制 bit[1] 位SIE位用于控制s态中断*/
#define portDISABLE_INTERRUPTS() __asm volatile( "csrc sstatus, 2" ) //csr clear
#define portENABLE_INTERRUPTS() __asm volatile( "csrs sstatus, 2" ) //csr set
#define portENTER_CRITICAL() vTaskEnterCritical() //关中断
#define portEXIT_CRITICAL() vTaskExitCritical() //开中断

/*-----------------------------------------------------------*/

// #endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
/* Architecture specific optimisations. */
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif

#if( configUSE_PORT_OPTIMISED_TASK_SELECTION == 1 )

/* Check the configuration. */
#if( configMAX_PRIORITIES > 32 )
#error configUSE_PORT_OPTIMISED_TASK_SELECTION can only be set to 1 when configMAX_PRIORITIES is less than or equal to 32. It is very rare that a system requires more than 10 to 15 difference priorities as tasks that share a priority will time slice.
#endif

/* Store/clear the ready priorities in a bit map. */
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )

/*-----------------------------------------------------------*/
static inline uint32_t ucPortCountLeadingZeros(uint32_t x)
{
uint32_t numZeros;
if (!x)
return (sizeof(int) * 8);
numZeros = 0;
while (!(x & 0x80000000)) {
numZeros++;
x <<= 1;
}
return numZeros;
}
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) ucPortCountLeadingZeros( ( uxReadyPriorities ) ) )
#endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */

/*-----------------------------------------------------------*/

/* Task function macros as described on the FreeRTOS.org WEB site. These are
not necessary for to use this port. They are defined so the common demo files
(which build with all the ports) will build. */
#define portTASK_FUNCTION_PROTO( vFunction, pvParameters ) void vFunction( void *pvParameters )
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )

/*-----------------------------------------------------------*/

#define portNOP() __asm volatile ( " nop " ) //无操作指令

#define portINLINE __inline

#ifndef portFORCE_INLINE //强制内联
#define portFORCE_INLINE inline __attribute__(( always_inline))
#endif

//内存屏障指令
#define portMEMORY_BARRIER() __asm volatile( "" ::: "memory" )
/*-----------------------------------------------------------*/


#ifdef __cplusplus
}
#endif

#endif /* PORTMACRO_H */


这里说几个有点坑的地方:

  • 一是使用configUSE_PORT_OPTIMISED_TASK_SELECTION控制的使用一个32bit的位图来存储优先级的几个操作宏,如果位图中的某一位被置为了1,代表此优先级上有任务就绪

    • portRECORD_READY_PRIORITY(uxPriority, uxReadyPriorities):设置位图中相应优先级的位,表示该优先级有任务就绪。

    • portRESET_READY_PRIORITY(uxPriority, uxReadyPriorities):清除位图中相应优先级的位,表示该优先级没有任务就绪。

    • portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) 这个宏用于确定位图中的最高的就绪优先级,因为在FreeRTOS中,数字越大优先级越高,那么在位图中从高位向低位搜寻判断即可,因此调用了ucPortCountLeadingZeros(uint32_t x)去计算前导0的个数,然后使用31 减去前导0的个数就可以知道此时最高的就绪优先级,在官方的移植代码中是使用编译器的一个函数__builtin_clz来计算前导0的个数的:

      #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - __builtin_clz( uxReadyPriorities ) )

      但是不知道为啥,会有问题,估计是编译器的问题,于是自定义了一个计算前导0的函数

  • 二是任务主动请求调度的函数,在M态的话直接使用ecall指令即可,此时就会触发软中断,但是如果在S态使用ecall则会导致特权级切换,而且S态不能直接访问或者控制中断控制器如CLINT或者PLIC,所以需要M态固件介入来处理跨HART的中断请求。

    /* 通过sbi触发自己核心的软中断 */
    extern void sbi_send_ipi(const unsigned long *hart_mask);
    //#define portYIELD() __asm volatile( "ecall" ); 原本是在m态通过ecall触发软中断
    #define portYIELD() sbi_send_ipi((const unsigned long *)(0x1<<PRIM_HART)) //向hart发送中断信号
    • 在M态,可以通过设置M态的中断挂起寄存器(mip)中的SSIP位来请求S态的软件中断。这需要M态的代码(通常是固件或操作系统的更低层部分)显式进行。
    • SSIP位设置后,如果S态已经启用软中断(通过sie寄存器的SSIP位)且全局中断使能(通过mstatussstatus寄存器的全局中断使能位),则S态的软件中断被触发。
    • 这里我就有个问题了,多核间的寄存器访问核设置OpenSBI是如何实现的,后面搞明白了在叙述吧,总之这里就是通过sbi_send_ipi就可以实现触发(0x1<<PRIM_HART))这个核心的软中断,即促发cpu7的软中断从而实现主动的任务切换。

portASM.S

#include "riscv_encoding.h"

#if __riscv_xlen == 64
#define portWORD_SIZE 8
#define store_x sd
#define load_x ld
#elif __riscv_xlen == 32
#define store_x sw
#define load_x lw
#define portWORD_SIZE 4
#else
#error Assembler did not define __riscv_xlen
#endif


#define portCONTEXT_SIZE ( 30 * portWORD_SIZE )


.global xPortStartFirstTask
.global freertos_risc_v_trap_handler
.global pxPortInitialiseStack
.extern pxCurrentTCB
.extern ulPortTrapHandler
.extern vTaskSwitchContext
.extern xTaskIncrementTick
.extern Timer_IRQHandler
.extern pullMachineTimerCompareRegister
.extern pullNextTime
.extern uxTimerIncrementsForOneTick /* size_t type so 32-bit on 32-bit core and 64-bits on 64-bit core. */
.extern xISRStackTop
.extern portasmHANDLE_INTERRUPT

/*-----------------------------------------------------------*/

.align 8
.func
freertos_risc_v_trap_handler:
// x2 为sp ,不需要保存
// x3 为gp,全局指针,基本不变也不需要保存
// x4 为tp,线程指针,中断上下文不需要保存
addi sp, sp, -portCONTEXT_SIZE
store_x x1, 1 * portWORD_SIZE( sp )
store_x x5, 2 * portWORD_SIZE( sp )
store_x x6, 3 * portWORD_SIZE( sp )
store_x x7, 4 * portWORD_SIZE( sp )
store_x x8, 5 * portWORD_SIZE( sp )
store_x x9, 6 * portWORD_SIZE( sp )
store_x x10, 7 * portWORD_SIZE( sp )
store_x x11, 8 * portWORD_SIZE( sp )
store_x x12, 9 * portWORD_SIZE( sp )
store_x x13, 10 * portWORD_SIZE( sp )
store_x x14, 11 * portWORD_SIZE( sp )
store_x x15, 12 * portWORD_SIZE( sp )
store_x x16, 13 * portWORD_SIZE( sp )
store_x x17, 14 * portWORD_SIZE( sp )
store_x x18, 15 * portWORD_SIZE( sp )
store_x x19, 16 * portWORD_SIZE( sp )
store_x x20, 17 * portWORD_SIZE( sp )
store_x x21, 18 * portWORD_SIZE( sp )
store_x x22, 19 * portWORD_SIZE( sp )
store_x x23, 20 * portWORD_SIZE( sp )
store_x x24, 21 * portWORD_SIZE( sp )
store_x x25, 22 * portWORD_SIZE( sp )
store_x x26, 23 * portWORD_SIZE( sp )
store_x x27, 24 * portWORD_SIZE( sp )
store_x x28, 25 * portWORD_SIZE( sp )
store_x x29, 26 * portWORD_SIZE( sp )
store_x x30, 27 * portWORD_SIZE( sp )
store_x x31, 28 * portWORD_SIZE( sp )

csrr t0, sstatus /* Required for SPIE bit. */
store_x t0, 29 * portWORD_SIZE( sp )

csrr t0, sepc //sepc 存储了中断返回地址
store_x t0, 0 * portWORD_SIZE( sp )

load_x t0, pxCurrentTCB /* 加载当前任务的任务控制块(TCB)地址到t0寄存器。 */
store_x sp, 0( t0 ) /* 将当前的堆栈指针sp的值保存到TCB的第一个成员变量中. */

csrr a0, scause
csrr a1, sepc




//判断是中断还是异常
test_if_asynchronous: //参数为 scause的值,存放在a0寄存器中
srli a2, a0, __riscv_xlen - 1 /* MSB of mcause is 1 if handing an asynchronous interrupt - shift to LSB to clear other bits. */
beq a2, x0, handle_synchronous /* Branch past interrupt handing if not asynchronous. */
store_x a1, 0( sp ) /* Asynch so save unmodified exception return address. */

//异常
handle_asynchronous:


test_if_ipi: //参数为 scause的值,存放在a0寄存器中
addi t0, x0, 1

slli t0, t0, __riscv_xlen - 1 /* LSB is already set, shift into MSB. Shift 31 on 32-bit or 63 on 64-bit cores. */
addi t1, t0, 1 /* 0x8000[]0001 == Supervisor ipi interrupt. */
bne a0, t1, test_if_mtimer // 如果不是软件中断则跳转到 test_if_mtimer 处执行

load_x sp, xISRStackTop /* 切换到ISR(中断服务例程)专用的堆栈。 */
jal vPortClearIpiInterrupt
jal vTaskSwitchContext //
j processed_source

test_if_mtimer: /* If there is a CLINT then the mtimer is used to generate the tick interrupt. */
addi t1, t1, 4 /* 0x80000001 + 4 = 0x80000005 == Supervisor timer interrupt. */
bne a0, t1, test_if_external_interrupt
/* 处理时钟中断 */
load_x sp, xISRStackTop /* 切换到ISR(中断服务例程)专用的堆栈。 */
jal vPortSetupTimerInterrupt /* 设置定时器中断计数 */
jal xTaskIncrementTick
beqz a0, processed_source /* Don't switch context if incrementing tick didn't unblock a task. */
jal vTaskSwitchContext
j processed_source

/* 外部中断处理函数 */
test_if_external_interrupt: /* If there is a CLINT and the mtimer interrupt is not pending then check to see if an external interrupt is pending. */
addi t1, t1, 4 /* 0x80000005 + 4 = 0x80000009 == Supervisor external interrupt. */
bne a0, t1, processed_trap /* Something as yet unhandled. */
//处理外部中断
load_x sp, xISRStackTop /* Switch to ISR stack before function call. */
jal handle_interrupt /* Jump to the interrupt handler if there is no CLINT or if there is a CLINT and it has been determined that an external interrupt is pending. */
j processed_source

handle_synchronous:
addi a1, a1, 4 /* Synchronous so updated exception return address to the instruction after the instruction that generated the exeption. */
store_x a1, 0( sp ) /* Save updated exception return address. */

//其他中断
processed_trap:
csrr a0, scause
csrr a1, sepc
csrr a2, stval
mv a4, sp
load_x sp, xISRStackTop /* Switch to ISR stack before function call. */
jal handle_trap /* Jump to the interrupt handler if there is no CLINT or if there is a CLINT and it has been determined that an external interrupt is pending. */
j processed_source

//任务恢复
processed_source:
load_x t1, pxCurrentTCB /* Load pxCurrentTCB. */
load_x sp, 0( t1 ) /* Read sp from first TCB member. */

/* Load sret with the address of the next instruction in the task to run next. */
load_x t0, 0( sp )
csrw sepc, t0



/* Load mstatus with the interrupt enable bits used by the task. */
load_x t0, 29 * portWORD_SIZE( sp ) //读取保存在栈中的sstatus寄存器的值
csrw sstatus, t0 /* Required for SPIE bit. */


//从栈中恢复上下文寄存器
load_x x1, 1 * portWORD_SIZE( sp )
load_x x5, 2 * portWORD_SIZE( sp ) /* t0 */
load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */
load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */
load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */
load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */
load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */
addi sp, sp, portCONTEXT_SIZE

sret
.endfunc
/*-----------------------------------------------------------*/

/*-----------------------------------------------------------*/

.align 8
.weak handle_trap
.func
handle_trap:
j handle_trap
.endfunc
/*-----------------------------------------------------------*/

.align 8
.weak handle_interrupt
.func
handle_interrupt:
j handle_interrupt
ret
.endfunc
/*-----------------------------------------------------------*/

.align 8
.func
xPortStartFirstTask:


la t0, freertos_risc_v_trap_handler //设置异常处理地址
csrw stvec, t0

//加载任务的TCB指针
load_x sp, pxCurrentTCB /* Load pxCurrentTCB. */
//加载此任务的栈地址
load_x sp, 0( sp ) /* Read sp from first TCB member. */

load_x x1, 0( sp ) /* Note for starting the scheduler the exception return address is used as the function return address. */

load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */
load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */
load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */
load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */
load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */

load_x x5, 29 * portWORD_SIZE( sp ) /* Initial mstatus into x5 (t0) */
ori x5, x5, 0x2 /* Set SIE bit so the first task starts with interrupts enabled - required as returns with ret not eret. */
csrrw x0, sstatus, x5 /* Interrupts enabled from here! */
load_x x5, 2 * portWORD_SIZE( sp ) /* Initial x5 (t0) value. */

addi sp, sp, portCONTEXT_SIZE
ret
.endfunc
/*-----------------------------------------------------------*/

/*
* Unlike other ports pxPortInitialiseStack() is written in assembly code as it
* needs access to the portasmADDITIONAL_CONTEXT_SIZE constant. The prototype
* for the function is as per the other ports:
* StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters );
*
* As per the standard RISC-V ABI pxTopcOfStack is passed in in a0, pxCode in
* a1, and pvParameters in a2. The new top of stack is passed out in a0.
*
* RISC-V maps registers to ABI names as follows (X1 to X31 integer registers
* for the 'I' profile, X1 to X15 for the 'E' profile, currently I assumed).
*
* Register ABI Name Description Saver
* x0 zero Hard-wired zero -
* x1 ra Return address Caller
* x2 sp Stack pointer Callee
* x3 gp Global pointer -
* x4 tp Thread pointer -
* x5-7 t0-2 Temporaries Caller
* x8 s0/fp Saved register/Frame pointer Callee
* x9 s1 Saved register Callee
* x10-11 a0-1 Function Arguments/return values Caller
* x12-17 a2-7 Function arguments Caller
* x18-27 s2-11 Saved registers Callee
* x28-31 t3-6 Temporaries Caller
*
* The RISC-V context is saved t FreeRTOS tasks in the following stack frame,
* where the global and thread pointers are currently assumed to be constant so
* are not saved:
*
* mstatus
* x31
* x30
* x29
* x28
* x27
* x26
* x25
* x24
* x23
* x22
* x21
* x20
* x19
* x18
* x17
* x16
* x15
* x14
* x13
* x12
* x11
* pvParameters
* x9
* x8
* x7
* x6
* x5
* portTASK_RETURN_ADDRESS
* [chip specific registers go here]
* pxCode
*/

.align 8
.func
pxPortInitialiseStack: // 函数参数应该为栈的地址放入 a0 寄存器
csrr t0, sstatus /* t0 = sstatus 保存当前sstatus寄存器的值到t0寄存器中 */
andi t0, t0, ~0x2 /* Ensure interrupts are disabled when the stack is restored within an ISR. Required when a task is created after the schedulre has been started, otherwise interrupts would be disabled anyway. */
addi t1, x0, 0x120 /* Generate the value 0x120, which are the SPIE and SPP bits to set in mstatus. */
or t0, t0, t1 /* Set SPIE and SPP bits in sstatus value. */


addi a0, a0, -portWORD_SIZE
store_x t0, 0(a0) /* sstatus onto the stack. */
addi a0, a0, -(22 * portWORD_SIZE) /* Space for registers x11-x31. */
store_x a2, 0(a0) /* Task parameters (pvParameters parameter) goes into register X10/a0 on the stack. */
addi a0, a0, -(6 * portWORD_SIZE) /* Space for registers x5-x9. */
la t0, prvTaskExitError /* 加载prvTaskExitError函数的地址到 t0 */
store_x t0, 0(a0) /* Return address onto the stack, could be portTASK_RETURN_ADDRESS */

addi a0, a0, -portWORD_SIZE
store_x a1, 0(a0) /* mret value (pxCode parameter) onto the stack. */
ret

.endfunc
/*-----------------------------------------------------------*/

portASM.S是移植到riscv架构的核心文件,在FreeRTOS中,每个任务都有自己的栈,这是在创建任务的时候分配的,所谓的切换任务就是将当前任务的任务上下文即寄存器的值压入自己的栈中,然后取出下一个要运行的任务的地址,从此任务的栈中将任务上下文恢复,然后跳转执行。FreeRTOS和我们的TimerOS一样都是通过RTC中断来实现任务切换的,而不同架构由于寄存器组不一样因此需要在portASM.S实现任务压栈和出栈的汇编函数,以及和处理中断有关的函数

port.c


/*-----------------------------------------------------------
* Implementation of functions defined in portable.h for the RISC-V RV32 port.
*----------------------------------------------------------*/

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "portmacro.h"
#include "sbi.h"
#include "ns16550.h"
#include "riscv_asm.h"
/* Standard includes. */
#include "string.h"


#ifdef configTASK_RETURN_ADDRESS
#define portTASK_RETURN_ADDRESS configTASK_RETURN_ADDRESS
#else
#define portTASK_RETURN_ADDRESS prvTaskExitError
#endif

/* The stack used by interrupt service routines. Set configISR_STACK_SIZE_WORDS
to use a statically allocated array as the interrupt stack. Alternative leave
configISR_STACK_SIZE_WORDS undefined and update the linker script so that a
linker variable names __freertos_irq_stack_top has the same value as the top
of the stack used by main. Using the linker script method will repurpose the
stack that was used by main before the scheduler was started for use as the
interrupt stack after the scheduler has started. */
#ifdef configISR_STACK_SIZE_WORDS
static __attribute__ ((aligned(16))) StackType_t xISRStack[ configISR_STACK_SIZE_WORDS ] = { 0 };
const StackType_t xISRStackTop = ( StackType_t ) &( xISRStack[ configISR_STACK_SIZE_WORDS & ~portBYTE_ALIGNMENT_MASK ] );

/* Don't use 0xa5 as the stack fill bytes as that is used by the kernerl for
the task stacks, and so will legitimately appear in many positions within
the ISR stack. */
#define portISR_STACK_FILL_BYTE 0xee
#else
extern const uint32_t __freertos_irq_stack_top[];
const StackType_t xISRStackTop = ( StackType_t ) __freertos_irq_stack_top;
#endif

/*
* Setup the timer to generate the tick interrupts. The implementation in this
* file is weak to allow application writers to change the timer used to
* generate the tick interrupt.
*/
void vPortSetupTimerInterrupt( void ) __attribute__(( weak ));

/*-----------------------------------------------------------*/

/* Used to program the machine timer compare register. */

const size_t uxTimerIncrementsForOneTick = ( size_t ) ( ( configCPU_CLOCK_HZ ) / ( configTICK_RATE_HZ ) ); /* Assumes increment won't go over 32-bits. */


/* Set configCHECK_FOR_STACK_OVERFLOW to 3 to add ISR stack checking to task
stack checking. A problem in the ISR stack will trigger an assert, not call the
stack overflow hook function (because the stack overflow hook is specific to a
task stack, not the ISR stack). */
#if defined( configISR_STACK_SIZE_WORDS ) && ( configCHECK_FOR_STACK_OVERFLOW > 2 )
#warning This path not tested, or even compiled yet.

static const uint8_t ucExpectedStackBytes[] = {
portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, \
portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE, portISR_STACK_FILL_BYTE }; \

#define portCHECK_ISR_STACK() configASSERT( ( memcmp( ( void * ) xISRStack, ( void * ) ucExpectedStackBytes, sizeof( ucExpectedStackBytes ) ) == 0 ) )
#else
/* Define the function away. */
#define portCHECK_ISR_STACK()
#endif /* configCHECK_FOR_STACK_OVERFLOW > 2 */

/*-----------------------------------------------------------*/

static uint64_t get_ticks()
{
static volatile uint64_t time_elapsed = 0;
__asm__ __volatile__(
"rdtime %0"
: "=r"(time_elapsed));
return time_elapsed;
}

/*-----------------------------------------------------------*/

/*-----------------------------------------------------------*/
void vPortSetupTimerInterrupt( void )
{
/* 通过sbi设置下次tick中断 */
sbi_set_timer(get_ticks() + uxTimerIncrementsForOneTick);
}
/*-------------------------------------------------------*/

/*-----------------------------------------------------------*/

void vPortClearIpiInterrupt( void )
{
/* 通过sbi清除软中断,在软中断服务函数中调用 */
extern void sbi_clear_ipi(void);
sbi_clear_ipi();
}

/*-----------------------------------------------------------*/
BaseType_t xPortStartScheduler( void )
{
extern void xPortStartFirstTask( void );

#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t stvec = 0;

/* Check the least significant two bits of mtvec are 00 - indicating
single vector mode. */
__asm volatile( "csrr %0, stvec" : "=r"( stvec ) );
//检查 stvec 设置的是直接访问还是向量访问
configASSERT( ( stvec & 0x03UL ) == 0 );

/* Check alignment of the interrupt stack - which is the same as the
stack that was being used by main() prior to the scheduler being
started. */
//检查栈顶的地址是否是对齐了
configASSERT( ( xISRStackTop & portBYTE_ALIGNMENT_MASK ) == 0 );

//初始化栈中的数据为 0
#ifdef configISR_STACK_SIZE_WORDS
{
memset( ( void * ) xISRStack, portISR_STACK_FILL_BYTE, sizeof( xISRStack ) );
}
#endif /* configISR_STACK_SIZE_WORDS */
}
#endif /* configASSERT_DEFINED */

/* 通过sbi设置Timer为滴答时钟 */
vPortSetupTimerInterrupt();
_puts( "Debug timer!\n");
/* 使能SIE中S模式Timer中断和Soft中断,注意此处使能并不会立即响应
xPortStartFirstTask中将打开全局使能 */
csr_set(CSR_SIE, SIP_STIP);
csr_set(CSR_SIE, SIP_SSIP);

//启动第一个任务
xPortStartFirstTask();

/* Should not get here as after calling xPortStartFirstTask() only tasks
should be executing. */
return pdFAIL;
}
/*-----------------------------------------------------------*/

void prvTaskExitError( void )
{
/* A function that implements a task must not exit or attempt to return to
its caller as there is nothing to return to. If a task wants to exit it
should instead call vTaskDelete( NULL ).

Artificially force an assert() to be triggered if configASSERT() is
defined, then stop here so application writers can catch the error. */
configASSERT( ulPortInterruptNesting == ~0UL );
//关闭中断
portDISABLE_INTERRUPTS();
for( ;; );
}

void vPortEndScheduler( void )
{
/* Not implemented. */
for( ;; );
}

4. FreeRTOS启动过程

4.1 FreeRTOS使用实例

我在main.c中创建了三个任务,三个任务之间会使用消息队列进行通信:

#include <FreeRTOS.h>
#include <task.h>
#include "debug_log.h"
#include "sbi.h"
#include "riscv_asm.h"
#include "queue.h"
//消息队列控制权柄
QueueHandle_t xMyQueueHandle;

void task1(void *p_arg)
{
int time = 1;
const char* message = "task1\n";
for(;;)
{
_puts("task1 send\n");
xQueueSend(xMyQueueHandle,&time,0);

vTaskDelay(pdMS_TO_TICKS(1000));
}
}

void task2(void *p_arg)
{
int time = 2;
const char* message = "task2\n";

for(;;)
{
_puts("task2 send\n");
xQueueSend(xMyQueueHandle,&time,0);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

void task3(void *p_arg)
{
int time = 0;
for(;;)
{
xQueueReceive(xMyQueueHandle,&time,portMAX_DELAY);
if(time == 1){
_puts("task1 receive\n");
}
if(time == 2){
_puts("task2 receive\n");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

static void vTaskCreate ()
{

xTaskCreate(task1,"task1",1024,NULL,3,NULL);
xTaskCreate(task2,"task2",1024,NULL,4,NULL);
xTaskCreate(task3,"task2",1024,NULL,5,NULL);
}

int main( void )
{
_puts( "Hello FreeRTOS!\n");
const char * str = "taskn\n";
xMyQueueHandle = xQueueCreate(20,sizeof(int32_t));

vTaskCreate();
vTaskStartScheduler();
return 0;
}

4.2 编译和链接

首先来看MakefileMakefile会将trusted_domain下的源文件编译生成一个固件,会对FreeRTOS内核的代码以及我们自己编写的源文件进行编译:main.cstartup.Sriscv/**FreeRTOS-Kernel/**driver/**,编译后生成的固件会放在build目录下

##########################################################################################################################
# trusted_domain GCC compiler Makefile
##########################################################################################################################

# ------------------------------------------------
# Generic Makefile (based on gcc)
# ------------------------------------------------

######################################
# target
######################################
TARGET = trusted_fw
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og


PROJECTBASE = $(PWD)
override PROJECTBASE := $(abspath $(PROJECTBASE))
TOP_DIR = $(PROJECTBASE)

#######################################
# binaries
#######################################
CROSS_COMPILE = riscv64-unknown-elf-
CC = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)gcc -x assembler-with-cpp
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
AR = $(CROSS_COMPILE)ar
SZ = $(CROSS_COMPILE)size
LD = $(CROSS_COMPILE)ld
HEX = $(OBJCOPY) -O ihex
BIN = $(OBJCOPY) -O binary -S
GDB = $(CROSS_COMPILE)gdb

#######################################
# paths
#######################################
# firmware library path
PERIFLIB_PATH =

# Build path
BUILD_DIR = build
OBJ_DIR = $(BUILD_DIR)/obj

######################################
# source
######################################
# C sources
C_SOURCES = \
${wildcard $(TOP_DIR)/FreeRTOS-Kernel/*.c} \
${wildcard $(TOP_DIR)/FreeRTOS-Kernel/portable/GCC/RISC-V/*.c} \
${wildcard $(TOP_DIR)/FreeRTOS-Kernel/portable/MemMang/heap_4.c} \
${wildcard $(TOP_DIR)/driver/*.c} \
${wildcard $(TOP_DIR)/riscv/*.c} \
${wildcard $(TOP_DIR)/*.c}

# ASM sources
ASM_SOURCES = \
${wildcard $(TOP_DIR)/*.S} \
${wildcard $(TOP_DIR)/FreeRTOS-Kernel/portable/GCC/RISC-V/*.S}

######################################
# firmware library
######################################
PERIFLIB_SOURCES =


#######################################
# CFLAGS
#######################################
MCU = -march=rv64imad -mcmodel=medany -msmall-data-limit=8 -fmessage-length=0 -fsigned-char

# macros for gcc
# AS defines
AS_DEFS =

# C defines
C_DEFS =

# AS includes
AS_INCLUDES = \
-I $(TOP_DIR)/riscv \
-I $(TOP_DIR)/driver

# C includes
C_INCLUDES = \
-I $(TOP_DIR)/FreeRTOS-Kernel/include \
-I $(TOP_DIR)/FreeRTOS-Kernel/portable/GCC/RISC-V \
-I $(TOP_DIR)/riscv \
-I $(TOP_DIR)/driver \
-I $(TOP_DIR)


# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif

# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)"

#######################################
# LDFLAGS
#######################################
# link script
LD_FILE = link.lds
LDSCRIPT = $(PROJECTBASE)/$(LD_FILE)

# libraries
LIBS = -lm
LIBDIR =
LDFLAGS = $(MCU) -nostartfiles -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map -Wl,--gc-sections

# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin $(BUILD_DIR)/$(TARGET).lst


#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(OBJ_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(OBJ_DIR)/,$(notdir $(ASM_SOURCES:.S=.o)))
vpath %.S $(sort $(dir $(ASM_SOURCES)))

$(OBJ_DIR)/%.o: %.c Makefile | $(OBJ_DIR)
@echo CC $(notdir $@)
@$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(OBJ_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(OBJ_DIR)/%.o: %.S Makefile | $(OBJ_DIR)
@echo AS $(notdir $@)
@$(AS) -c $(ASFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) $(LDSCRIPT) Makefile
@echo LD $(notdir $@)
@$(CC) $(OBJECTS) $(LDFLAGS) -Wl,-Map=freertos.map -o $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
@echo OBJCOPY $(notdir $@)
@$(HEX) $< $@

$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
@echo OBJCOPY $(notdir $@)
@$(BIN) $< $@

$(BUILD_DIR)/%.lst: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
@echo OBJDUMP $(notdir $@)
@$(OBJDUMP) --source --demangle --disassemble --reloc --wide $< > $@
@$(SZ) --format=berkeley $<

$(BUILD_DIR):
mkdir $@

ifeq ($(OBJ_DIR), $(wildcard $(OBJ_DIR)))
else
$(OBJ_DIR):$(BUILD_DIR)
mkdir $@
endif

#######################################
# clean up
#######################################
clean:
-rm -fR $(BUILD_DIR)

#######################################
# use gdb debug
#######################################
debug:
$(GDB) $(BUILD_DIR)/trusted_fw.elf -x ./gdbinit

#######################################
# dependencies
#######################################
#-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)

# *** EOF ***

链接脚本如下:

OUTPUT_ARCH( "riscv" )

ENTRY( _start )

__stack_size = 0x4000;

MEMORY
{
/* Fake ROM area */
rom (rxa) : ORIGIN = 0xBF800000, LENGTH = 1M
ram (wxa) : ORIGIN = 0xBF900000, LENGTH = 6M
}
SECTIONS
{
.init :
{
_text = .;
KEEP (*(SORT_NONE(.init)))
} >rom AT>rom

.text :
{
*(.text.unlikely .text.unlikely.*)
*(.text.startup .text.startup.*)
*(.text .text.*)
*(.gnu.linkonce.t.*)
} >rom AT>rom

.fini :
{
KEEP (*(SORT_NONE(.fini)))
_etext = .;
} >rom AT>rom
/* 四字节对齐 */
.rodata.align :
{
. = ALIGN(4);
_rodata = .;
} >rom AT>rom

/* 标记 rodata的链接地址*/
.rodata.start :
{
_rodata_lma = LOADADDR(.rodata.start);
} >rom AT>rom

.data.align :
{
. = ALIGN(4);
_data = .;
} >ram AT>rom

.data.start :
{
_data_lma = LOADADDR(.data.start);
} >ram AT>rom

.data :
{
*(.data .data.*)
*(.gnu.linkonce.d.*)
. = ALIGN(8);
PROVIDE( __global_pointer$ = . + 0x800 );
*(.sdata .sdata.*)
*(.sdata2 .sdata2.*)
*(.gnu.linkonce.s.*)
. = ALIGN(8);
*(.srodata.cst16)
*(.srodata.cst8)
*(.srodata.cst4)
*(.srodata.cst2)
*(.srodata .srodata.*)

. = ALIGN(4);
_edata = .;
} >ram AT>rom

.bss.align :
{
. = ALIGN(4);
_bss = .;
} >ram AT>rom

.bss.start :
{
_bss_lma = LOADADDR(.bss.start);
} >ram AT>rom

.bss :
{
*(.sbss*)
*(.gnu.linkonce.sb.*)
*(.bss .bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)

. = ALIGN(4);
_ebss = .;
} >ram AT>rom


. = ALIGN(8);
_end = .;

.stack :
{
. = ALIGN(16);
. += __stack_size;
_stack_top = .;
} >ram AT>ram

}
  • 指定了程序入口地址为_startstart.S中的代码

  • 写入的内存区域有两部分:

    • rom (rxa) : ORIGIN = 0xBF800000, LENGTH = 1M:可读可执行,起始地址为0xBF800000,大小为1M
    • ram (wxa) : ORIGIN = 0xBF900000, LENGTH = 6M:可写可执行,起始地址为0xBF900000,大小为6M
  • 链接脚本中AT的是用来定义各个节应该在哪里被加载(LMA),而节的位置(放置位置,VMA)则是通过 > 指定的,例如

     .data.start :
    {
    _data_lma = LOADADDR(.data.start);
    } >ram AT>rom
    • .data 节包含了初始化的全局变量,它在运行时应位于 RAM(通过 >ram 指定)。
    • 然而,在程序加载时,这些数据的初始值存储在 ROM 中(通过 AT>rom 指定)。
    • 简单来说就是数据段的数据是放在rom中的,代码段的程序在运行时需要去rom中拿到数据后加载到ram处执行,.data段和.bss段都是这样的特性
  • 在链接脚本最后定义了内核程序使用的主栈栈顶起始地址_stack_top,栈大小为__stack_size = 0x4000

4.3 启动代码

startup.S

#include "riscv_encoding.h"
#include "quard_star.h"
#include "riscv_reg.h"


.section .init
.globl _start
.type _start,@function
_start: #程序入口地址
.cfi_startproc
.cfi_undefined ra
.option push
.option norelax #取消汇编器的"relaxation"优化
la gp, __global_pointer$ #设置全局指针
.option pop
// Continue primary hart
li a1, PRIM_HART #将核心号加载到a1寄存器中
bne a0, a1, secondary #比较核心号是否相等,如果不等,跳转到secondary处执行

csrw sie, 0 #sie寄存器用于控制中断,禁用所有中断
csrw sip, 0 #sip用于标识中断中断标志位,清楚所有的中断的标志位
/* set to disable FPU */
li t0, SSTATUS_FS
csrc sstatus, t0
li t0, SSTATUS_SUM // SUM in sstatus
csrs sstatus, t0

// Primary hart 设置主栈指针
la sp, _stack_top
csrw sscratch, sp //将栈指针写入sscratch寄存器

// Load data section
la a0, _data_lma //data数据在物理内存中的地址
la a1, _data //数据段起始地址
la a2, _edata //数据段结束地址
bgeu a1, a2, 2f //
1: //将数据段的数据从物理内存复制到dram
LOAD t0, (a0)
STOR t0, (a1)
addi a0, a0, REGSIZE
addi a1, a1, REGSIZE
bltu a1, a2, 1b //b代表向上寻找最近的同名标签

2:
// Clear bss section
la a0, _bss
la a1, _ebss
bgeu a0, a1, 2f //f代表向下寻找最近的同名标签
1: //清空bss段
STOR zero, (a0)
addi a0, a0, REGSIZE
bltu a0, a1, 1b
2:

// argc, argv, envp is 0
li a0, 0
li a1, 0
li a2, 0
jal main
1:
wfi
j 1b

ecall_err:
wfi
j ecall_err

secondary:
// TODO: Multicore is not supported
wfi
j secondary
.cfi_endproc

  • 启动代码主要做一些初始化工作:首先禁用所有中断,然后设置主栈指针,加载rom上的数据段到ram里,将bss段清零。bss段通常放的都是未初始化的全局变量,因此os手动清零
  • 跳转到main函数开始执行:jal main

4.4 FreeRTOS任务启动

main函数中可以看见当创建完成任务后,会调用vTaskStartScheduler()函数来开启任务调度:

image-20240421215337861

我们进入vTaskStartScheduler()内部,可以看见首先创建了一个IDLE任务,即空闲任务,如果我们用户没用手动创建任务,那么IDLE任务会一直运行,此任务被设置为最低优先级0

image-20240421215700843

然后就会开启时钟中断,然后启动第一个任务:

image-20240421220103518

  • 先调用portDISABLE_INTERRUPTS()这个宏来关闭中断,然后在xPortStartScheduler()函数中构建好第一个任务执行的上下文后,会打开中断,这两个和cpu架构有关的宏或者函数就是定义在之前portable文件夹下的

    image-20240421220331304

xPortStartScheduler()定义在port.c中,在此函数中会恢复第一个要执行任务的任务上下文,开启时钟中断,开始执行调度。

/*-----------------------------------------------------------*/
BaseType_t xPortStartScheduler( void )
{
extern void xPortStartFirstTask( void );

#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t stvec = 0;

/* Check the least significant two bits of mtvec are 00 - indicating
single vector mode. */
__asm volatile( "csrr %0, stvec" : "=r"( stvec ) );
//检查 stvec 设置的是直接访问还是向量访问
configASSERT( ( stvec & 0x03UL ) == 0 );

/* Check alignment of the interrupt stack - which is the same as the
stack that was being used by main() prior to the scheduler being
started. */
//检查栈顶的地址是否是对齐了
configASSERT( ( xISRStackTop & portBYTE_ALIGNMENT_MASK ) == 0 );

//初始化栈中的数据为 0
#ifdef configISR_STACK_SIZE_WORDS
{
memset( ( void * ) xISRStack, portISR_STACK_FILL_BYTE, sizeof( xISRStack ) );
}
#endif /* configISR_STACK_SIZE_WORDS */
}
#endif /* configASSERT_DEFINED */

/* 通过sbi设置Timer为滴答时钟 */
vPortSetupTimerInterrupt();
/* 使能SIE中S模式Timer中断和Soft中断,注意此处使能并不会立即响应
xPortStartFirstTask中将打开全局使能 */
csr_set(CSR_SIE, SIP_STIP);
csr_set(CSR_SIE, SIP_SSIP);

//启动第一个任务
xPortStartFirstTask();

/* Should not get here as after calling xPortStartFirstTask() only tasks
should be executing. */
return pdFAIL;
}
  • 调用vPortSetupTimerInterrupt();设置下次时钟中断的计数值

    const size_t uxTimerIncrementsForOneTick = ( size_t ) ( ( configCPU_CLOCK_HZ ) / ( configTICK_RATE_HZ ) ); /* Assumes increment won't go over 32-bits. */
    void vPortSetupTimerInterrupt( void )
    {
    /* 通过sbi设置下次tick中断 */
    sbi_set_timer(get_ticks() + uxTimerIncrementsForOneTick);
    }

    uxTimerIncrementsForOneTick为下次时钟中断到来时的计数值,通过时钟频率和在FreeRTOSConfig.h中定义的时钟中断频率来计算

  • 使能SIE中S模式Timer中断和Soft中断,此时用于没有开启S态的全局中断,所以不会立即响应,会在xPortStartFirstTask();函数中恢复第一个任务的上下文后才开启

xPortStartFirstTask()定义在portASM.S中:

.align 8
.func
xPortStartFirstTask:

la t0, freertos_risc_v_trap_handler //设置异常处理地址
csrw stvec, t0

//加载任务的TCB指针
load_x sp, pxCurrentTCB /* Load pxCurrentTCB. */
//加载此任务的栈地址
load_x sp, 0( sp ) /* Read sp from first TCB member. */

load_x x1, 0( sp ) /* Note for starting the scheduler the exception return address is used as the function return address. */

load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */
load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */
load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */
load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */
load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */

load_x x5, 29 * portWORD_SIZE( sp ) /* Initial mstatus into x5 (t0) */
ori x5, x5, 0x2 /* Set SIE bit so the first task starts with interrupts enabled - required as returns with ret not eret. */
csrrw x0, sstatus, x5 /* Interrupts enabled from here! */
load_x x5, 2 * portWORD_SIZE( sp ) /* Initial x5 (t0) value. */

addi sp, sp, portCONTEXT_SIZE
ret
.endfunc
  • 首先设置异常处理地址,即设置stvec寄存器的值

    la t0, freertos_risc_v_trap_handler    //设置异常处理地址
    csrw stvec, t0

    下一次trap来临时就会进入freertos_risc_v_trap_handler 这个地址去进行处理,和我们TimerOS实现类似,在freertos_risc_v_trap_handler函数内部会判断是什么类型的异常,比如时钟中断、外部中断、异常等等,如果是时钟中断,则会去执行任务切换

  • 然后是根据当前任务的TCB的指针,拿到此任务的栈地址,根据任务TCB的定义发现栈指针是放在TCB最开始的位置

    image-20240421223228120

  • 所以先取出TCB的指针,再从TCB的第一个地址取出任务栈,从此任务的栈中恢复任务上下文,pxCurrentTCBFreeRTOS中定义的一个全局指针,用来指向当前正在执行的任务的TCB,由于我们最后创建的任务是IDLE任务,所以此时pxCurrentTCB肯定是指向IDLE任务的TCB的,xTaskCreate内部会去调用prvAddNewTaskToReadyList来初始化设置pxCurrentTCB的值

    image-20240421222608370

  • 此时你可能有一个疑问,既然要从栈中恢复任务上下文,是不是在创建任务的时候需要先为每个任务初始化任务上下文,那就对了,xTaskCreate内部会去调用prvInitialiseNewTask函数,而中prvInitialiseNewTask会去调用pxPortInitialiseStack函数来初始化任务栈

    image-20240421222924490

pxPortInitialiseStack此函数用于初始化一个任务的任务栈,定义在portASM.S中:

.align 8
.func
pxPortInitialiseStack: // 函数参数应该为栈的地址放入 a0 寄存器
csrr t0, sstatus /* t0 = sstatus 保存当前sstatus寄存器的值到t0寄存器中 */
andi t0, t0, ~0x2 /* Ensure interrupts are disabled when the stack is restored within an ISR. Required when a task is created after the schedulre has been started, otherwise interrupts would be disabled anyway. */
addi t1, x0, 0x120 /* Generate the value 0x120, which are the SPIE and SPP bits to set in sstatus. */
or t0, t0, t1 /* Set SPIE and SPP bits in sstatus value. */


addi a0, a0, -portWORD_SIZE
store_x t0, 0(a0) /* sstatus onto the stack. */
addi a0, a0, -(22 * portWORD_SIZE) /* Space for registers x11-x31. */
store_x a2, 0(a0) /* Task parameters (pvParameters parameter) goes into register X10/a0 on the stack. */
addi a0, a0, -(6 * portWORD_SIZE) /* Space for registers x5-x9. */
la t0, prvTaskExitError /* 加载prvTaskExitError函数的地址到 t0 */
store_x t0, 0(a0) /* Return address onto the stack, could be portTASK_RETURN_ADDRESS */

addi a0, a0, -portWORD_SIZE
store_x a1, 0(a0) /* sret value (pxCode parameter) onto the stack. */
ret

.endfunc
  • 首先来看调用此函数的调用时传入的参数和返回值

    pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
    • pxTopOfStack:任务栈的栈顶地址,portSTACK_GROWTH这个宏用于判断栈的增长方向,此宏在portmacro.h中被定义成了-1,因此往地址增长,pxTopOfStack指向了栈顶地址,

      image-20240422120342433

      image-20240422121813632

      注意:pxTopOfStack是个临时变量,指向此时栈的最低地址,不是TCB结构体顶部的那个pxTopOfStack

    • pxTopOfStack会被放入a0寄存器中,pxTaskCode放入a1寄存器中,pvParameters放入a2寄存器中

  • pxPortInitialiseStack函数内部会依次压栈初始化任务上下文,栈中的数据如下:

    image-20240422130449205

  • 此时再来回看xPortStartFirstTask中的恢复第一个任务的上下文就很清晰了,上面提到xPortStartFirstTask函数内部会在恢复上下文后开启全局中断,这一步就发生在csrrw x0, sstatus, x5 /* Interrupts enabled from here! */,我们在初始化任务上下文时会构造一个sstatus的初始值,会去设置相应的bit位来开启S态的全局中断,这样将ssatus的值从栈中恢复时就会开启全局中断了,从而开启基于RTC中断的任务调度

    csrr t0, sstatus					/* t0 = sstatus 保存当前sstatus寄存器的值到t0寄存器中 */
    andi t0, t0, ~0x2 /* Ensure interrupts are disabled when the stack is restored within an ISR. Required when a task is created after the schedulre has been started, otherwise interrupts would be disabled anyway. */
    addi t1, x0, 0x120 /* Generate the value 0x120, which are the SPIE and SPP bits to set in sstatus. */
    or t0, t0, t1 /* Set SPIE and SPP bits in sstatus value. */

回顾一下整个启动流程:

image-20240422145110158

4.5 内核启动后的任务切换

​ 在调用xPortStartFirstTask()之后,此时会启动IDLE任务,同时开启了时钟中断,那么在下一次时钟中断到来时,我们需要去进行处理,在xPortStartFirstTask()函数的开头我们设置了stvec的值,当产生trap时会跳转到stvec寄存器指向的地址处执行,这个地址是freertos_risc_v_trap_handler,这个函数定义在portASM的开头:

.align 8
.func
freertos_risc_v_trap_handler:
// x2 为sp ,不需要保存
// x3 为gp,全局指针,基本不变也不需要保存
// x4 为tp,线程指针,不需要保存
addi sp, sp, -portCONTEXT_SIZE
store_x x1, 1 * portWORD_SIZE( sp )
store_x x5, 2 * portWORD_SIZE( sp )
store_x x6, 3 * portWORD_SIZE( sp )
store_x x7, 4 * portWORD_SIZE( sp )
store_x x8, 5 * portWORD_SIZE( sp )
store_x x9, 6 * portWORD_SIZE( sp )
store_x x10, 7 * portWORD_SIZE( sp )
store_x x11, 8 * portWORD_SIZE( sp )
store_x x12, 9 * portWORD_SIZE( sp )
store_x x13, 10 * portWORD_SIZE( sp )
store_x x14, 11 * portWORD_SIZE( sp )
store_x x15, 12 * portWORD_SIZE( sp )
store_x x16, 13 * portWORD_SIZE( sp )
store_x x17, 14 * portWORD_SIZE( sp )
store_x x18, 15 * portWORD_SIZE( sp )
store_x x19, 16 * portWORD_SIZE( sp )
store_x x20, 17 * portWORD_SIZE( sp )
store_x x21, 18 * portWORD_SIZE( sp )
store_x x22, 19 * portWORD_SIZE( sp )
store_x x23, 20 * portWORD_SIZE( sp )
store_x x24, 21 * portWORD_SIZE( sp )
store_x x25, 22 * portWORD_SIZE( sp )
store_x x26, 23 * portWORD_SIZE( sp )
store_x x27, 24 * portWORD_SIZE( sp )
store_x x28, 25 * portWORD_SIZE( sp )
store_x x29, 26 * portWORD_SIZE( sp )
store_x x30, 27 * portWORD_SIZE( sp )
store_x x31, 28 * portWORD_SIZE( sp )

csrr t0, sstatus /* Required for SPIE bit. */
store_x t0, 29 * portWORD_SIZE( sp )

csrr t0, sepc //sepc 存储了中断返回地址
store_x t0, 0 * portWORD_SIZE( sp )

load_x t0, pxCurrentTCB /* 加载当前任务的任务控制块(TCB)地址到t0寄存器。 */
store_x sp, 0( t0 ) /* 将当前的堆栈指针sp的值保存到TCB的第一个成员变量中. */

csrr a0, scause
csrr a1, sepc

//判断是中断还是异常
test_if_asynchronous: //参数为 scause的值,存放在a0寄存器中
srli a2, a0, __riscv_xlen - 1 /* MSB of mcause is 1 if handing an asynchronous interrupt - shift to LSB to clear other bits. */
beq a2, x0, handle_synchronous /* Branch past interrupt handing if not asynchronous. */
store_x a1, 0( sp ) /* Asynch so save unmodified exception return address. */

//异常
handle_asynchronous:


test_if_ipi: //参数为 scause的值,存放在a0寄存器中
addi t0, x0, 1

slli t0, t0, __riscv_xlen - 1 /* LSB is already set, shift into MSB. Shift 31 on 32-bit or 63 on 64-bit cores. */
addi t1, t0, 1 /* 0x8000[]0001 == Supervisor ipi interrupt. */
bne a0, t1, test_if_mtimer // 如果不是软件中断则跳转到 test_if_mtimer 处执行

load_x sp, xISRStackTop /* 切换到ISR(中断服务例程)专用的堆栈。 */
jal vPortClearIpiInterrupt
jal vTaskSwitchContext //
j processed_source

test_if_mtimer: /* If there is a CLINT then the mtimer is used to generate the tick interrupt. */
addi t1, t1, 4 /* 0x80000001 + 4 = 0x80000005 == Supervisor timer interrupt. */
bne a0, t1, test_if_external_interrupt
/* 处理时钟中断 */
load_x sp, xISRStackTop /* 切换到ISR(中断服务例程)专用的堆栈。 */
jal vPortSetupTimerInterrupt /* 设置定时器中断计数 */
jal xTaskIncrementTick
beqz a0, processed_source /* Don't switch context if incrementing tick didn't unblock a task. */
jal vTaskSwitchContext
j processed_source

/* 外部中断处理函数 */
test_if_external_interrupt: /* If there is a CLINT and the mtimer interrupt is not pending then check to see if an external interrupt is pending. */
addi t1, t1, 4 /* 0x80000005 + 4 = 0x80000009 == Supervisor external interrupt. */
bne a0, t1, processed_trap /* Something as yet unhandled. */
//处理外部中断
load_x sp, xISRStackTop /* Switch to ISR stack before function call. */
jal handle_interrupt /* Jump to the interrupt handler if there is no CLINT or if there is a CLINT and it has been determined that an external interrupt is pending. */
j processed_source

handle_synchronous:
addi a1, a1, 4 /* Synchronous so updated exception return address to the instruction after the instruction that generated the exeption. */
store_x a1, 0( sp ) /* Save updated exception return address. */

//其他中断
processed_trap:
csrr a0, scause
csrr a1, sepc
csrr a2, stval
mv a4, sp
load_x sp, xISRStackTop /* Switch to ISR stack before function call. */
jal handle_trap /* Jump to the interrupt handler if there is no CLINT or if there is a CLINT and it has been determined that an external interrupt is pending. */
j processed_source

//任务恢复
processed_source:
load_x t1, pxCurrentTCB /* Load pxCurrentTCB. */
load_x sp, 0( t1 ) /* Read sp from first TCB member. */

/* Load sret with the address of the next instruction in the task to run next. */
load_x t0, 0( sp )
csrw sepc, t0



/* Load mstatus with the interrupt enable bits used by the task. */
load_x t0, 29 * portWORD_SIZE( sp ) //读取保存在栈中的sstatus寄存器的值
csrw sstatus, t0 /* Required for SPIE bit. */


//从栈中恢复上下文寄存器
load_x x1, 1 * portWORD_SIZE( sp )
load_x x5, 2 * portWORD_SIZE( sp ) /* t0 */
load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */
load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */
load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */
load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */
load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */
addi sp, sp, portCONTEXT_SIZE

sret
.endfunc
  • 我当前有一个任务正在暂用cpu运行,此时时钟中断来临,会打断当前任务的执行,然后执行任务切换,因此在中断处理函数中,需要先保存当前任务的上下文,x2、x3、x4不需要保存,保存任务上下文时先增加sp的值,增加的大小为portCONTEXT_SIZE,使用store_x依次保存寄存器后,将此时任务的栈顶地址保存到pxNewTCB->pxTopOfStack中,用于在任务恢复时找到栈顶

    #define portCONTEXT_SIZE ( 30 * portWORD_SIZE )   //任务上下文占用内存大小
  • 保存完毕任务上下文后,将scause写入了a0寄存器,sepc寄存器写入了a1寄存器,用于后续针对不同类型的异常进行处理

    //判断是中断还是异常
    test_if_asynchronous: //参数为 scause的值,存放在a0寄存器中
    srli a2, a0, __riscv_xlen - 1 /* MSB of mcause is 1 if handing an asynchronous interrupt - shift to LSB to clear other bits. */
    beq a2, x0, handle_synchronous /* Branch past interrupt handing if not asynchronous. */
    store_x a1, 0( sp )
    • srli是右移位指令,scause的最高位表明了此次trap是中断还是异常,因此将a0寄存器右移__riscv_xlen - 1位后保存到a2寄存器中,此时a2寄存器中就保存了scause的最高位
    • 如果a2 == 0则表示是异常,跳转到handle_synchronous处执行可以看见为空,否则为中断,将sepc寄存器的值写入到栈顶位置
  • 如果为中断则会继续向下执行test_if_ipi标签处的代码,这里会根据scause的最后一位判断是软件中断还是其他中断,如果是软件中断则会执行:

    load_x sp, xISRStackTop			/* 切换到ISR(中断服务例程)专用的堆栈。 */
    jal vPortClearIpiInterrupt
    jal vTaskSwitchContext //
    j processed_source
    • 如果是软件中断会切换到ISR专用的栈:xISRStackTop,这是在port.c中定义的一片内存的最高地址。

      image-20240422152624416

    • 然后跳转到vPortClearIpiInterrupt函数处执行,此函数也是定义在port.c

      image-20240422152807737

    • 上面提到过任务可以通过调用portYIELD()来手动触发软中断,从而实现任务切换,此时就是这样的情况,在清楚软中断的标志位后,就会去执行任务切换,调用vTaskSwitchContext,这是FreeRTOS内部定义的选择下一个可执行任务上下文的函数,去看源码就发发现此函数从就绪链表中挑选出最高优先级的任务,然后将全局任务指针pxCurrentTCB指向此任务的TCB,然后就可以从此要执行任务的栈中恢复上下文了,然后跳转执行,恢复任务上下文的函数是processed_source

  • 如果不是主动触发的软中断,则就跳转到test_if_mtimer处执行,此时会判断是时钟中断还是其他外部中断,如果是时钟中断,此时也要进行任务切换,同样切换ISR专用栈,不过需要设置下一次时钟中断到来的计数值,通过调用vPortSetupTimerInterrupt函数实现,然后调用FreeRTOS内部定义的xTaskIncrementTick函数,此函数用于告知系统增加了依次时钟计数,其内部机制我们暂不剖析,主要是用于延时和任务调度相关的。然后调用vTaskSwitchContext选择下一个就绪任务,恢复任务上下文,跳转执行

    test_if_mtimer:						/* If there is a CLINT then the mtimer is used to generate the tick interrupt. */
    addi t1, t1, 4 /* 0x80000001 + 4 = 0x80000005 == Supervisor timer interrupt. */
    bne a0, t1, test_if_external_interrupt
    /* 处理时钟中断 */
    load_x sp, xISRStackTop /* 切换到ISR(中断服务例程)专用的堆栈。 */
    jal vPortSetupTimerInterrupt /* 设置定时器中断计数 */
    jal xTaskIncrementTick
    beqz a0, processed_source /* Don't switch context if incrementing tick didn't unblock a task. */
    jal vTaskSwitchContext
    j processed_source
  • 如果是外部中断的话,会跳转到handle_interrupt进行处理,如果是其他中断会跳转到processed_trap处理

  • 我们重点来看任务恢复函数processed_source,通过执行vTaskSwitchContext,将pxCurrentTCB指向了下一个要执行任务的TCB,通过pxCurrentTCB指针就可以访问到要执行任务的任务上下文的栈空间,因此恢复任务上下文,通过sret指令会跳转到sepc寄存器的地址执行,sepc寄存器在任务被中断后会自动设置,保存了中断返回后指令地址,在freertos_risc_v_trap_handler函数中进行了压栈,因此从栈中恢复后,sret指令就能跳转到正确的地址执行了

    processed_source:
    load_x t1, pxCurrentTCB /* Load pxCurrentTCB. */
    load_x sp, 0( t1 ) /* Read sp from first TCB member. */

    /* Load sret with the address of the next instruction in the task to run next. */
    load_x t0, 0( sp )
    csrw sepc, t0

    /* Load mstatus with the interrupt enable bits used by the task. */
    load_x t0, 29 * portWORD_SIZE( sp ) //读取保存在栈中的sstatus寄存器的值
    csrw sstatus, t0 /* Required for SPIE bit. */


    //从栈中恢复上下文寄存器
    load_x x1, 1 * portWORD_SIZE( sp )
    load_x x5, 2 * portWORD_SIZE( sp ) /* t0 */
    load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */
    load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */
    load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */
    load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */
    load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
    load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
    load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
    load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
    load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
    load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
    load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
    load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
    load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
    load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
    load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
    load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
    load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
    load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
    load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
    load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
    load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
    load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
    load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
    load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
    load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
    load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */
    addi sp, sp, portCONTEXT_SIZE

    sret
    .endfunc

5. 编译运行测试

build.sh中需要将执行编译trusted_domain,并将生成的固件打包,主要修改的地方为:

image-20240422155222221

固件打包的地方不用修改,还是写入到原本的地址

运行测试:如下三个任务,两个任务写队列,一个任务接收队列消息,运行成功

GIF 2024-4-22 15-55-01

参考链接