如何将TencentOS tiny移植到RISC-V(二)
书接上文,先把TencentOS tiny代码拉下来
git clone https://github.com/Tencent/TencentOS-tiny.git
因为写这篇文章的时候RISC-V相关的代码已经合入,如果只是想了解一下的可以直接看arch/risc-v
目录下的代码。如果想要体验亲手移植,就只保留kernel
目录下的代码,其它部分直接删除。再新创建几个目录
mkdir -p risc-v/common/include
mkdir -p risc-v/rv32i
mkdir -p demo
最后目录效果如下
.
├── demo
├── kernel
│ ├── core
│ │ └── include
│ ├── evtdrv
│ │ └── include
│ ├── hal
│ │ └── include
│ └── pm
│ └── include
└── risc-v
├── common
│ └── include
└── rv32i
我们将在risc-v/common
目录下填上RISC-V通用初始化代码,在risc-v/rv32i
中填写risc-v 32位架构的移植代码,在demo
中编写用户任务,启动内核。
这几项工作做完后,我们将上文提到的start.S和link.lds拷贝到demo目录下
在start.S代码里再添加一行call main
// start.S
.global _start
.extern main
.section .text
.align 2
_start:
nop
nop
nop
call main
再在该目录下新建个main.c
// demo/main.c
int main(void) {
return 0;
}
最后在该目录下创建一个Makefile
TARGET = demo
DEBUG = 1
OPT = -O0
TOP_DIR = ../
BUILD_DIR = build
# C sources
KERNEL_SRC = \
C_SOURCES += $(KERNEL_SRC)
ARCH_SRC = \
${wildcard $(TOP_DIR)/risc-v/rv32i/*.c} \
${wildcard $(TOP_DIR)/risc-v/common/*.c}
C_SOURCES += $(ARCH_SRC)
HAL_DRIVER_SRC = \
$(TOP_DIR)/demo/main.c
C_SOURCES += $(HAL_DRIVER_SRC)
# ASM sources
ASM_SOURCES =
ASM_SOURCES_S = \
start.S
#######################################
# binaries
#######################################
PREFIX = riscv-none-embed-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
#######################################
# CFLAGS
#######################################
# cpu
CPU = -march=rv32imac
# fpu
FPU =
# float-abi
FLOAT-ABI =
# mcu
MCU = $(CPU) $(FPU) $(FLOAT-ABI)
# macros for gcc
# AS defines
AS_DEFS =
# C defines
C_DEFS =
# AS includes
AS_INCLUDES =
# C includes
KERNEL_INC = \
-I $(TOP_DIR)/risc-v/common/include \
-I $(TOP_DIR)/risc-v/rv32i/
C_INCLUDES += $(KERNEL_INC)
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -mabi=ilp32 -msmall-data-limit=8 -mno-save-restore
CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -mabi=ilp32 -msmall-data-limit=8 -mno-save-restore -std=gnu11 --specs=nosys.specs -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections
ifeq ($(DEBUG), 1)
CFLAGS += -g
endif
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = link.lds
# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -nostartfiles
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES_S:.S=.o)))
vpath %.S $(sort $(dir $(ASM_SOURCES_S)))
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
$(BUILD_DIR):
mkdir $@
#######################################
# clean up
#######################################
clean:
-rm -fR $(BUILD_DIR)
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***
Makefile内容就不细讲了,相信各位都有能力看明白。
现在执行make,如果一切正常,就会在demo目录下生成build/demo.elf
二进制文件。然后就可以开启qemu+gdb模拟调试,具体方法参见上文,后续不再赘述。
Breakpoint 1 at 0x80000000: file start.S, line 6.
Breakpoint 1, _start () at start.S:6
6 nop
(gdb) disas
Dump of assembler code for function _start:
=> 0x80000000 <+0>: nop
0x80000002 <+2>: nop
0x80000004 <+4>: nop
0x80000006 <+6>: jal 0x80000008 <main>
End of assembler dump.
(gdb) ni
7 nop
(gdb)
8 nop
(gdb)
9 call main
(gdb) si
main () at main.c:1
1 int main(void ) {
(gdb) disas
Dump of assembler code for function main:
=> 0x80000008 <+0>: addi sp,sp,-16
0x8000000a <+2>: sw s0,12(sp)
0x8000000c <+4>: addi s0,sp,16
0x8000000e <+6>: li a5,0
0x80000010 <+8>: mv a0,a5
0x80000012 <+10>: lw s0,12(sp)
0x80000014 <+12>: addi sp,sp,16
0x80000016 <+14>: ret
End of assembler dump.
(gdb)
这个代码执行到sw s0,12(sp)
这一句是会出问题的,因为我们并没有初始化栈的位置,所以引导程序需要重新设计一下。
我们先来确定一下引导程序start.S
需要实现哪些功能:
- 禁止全局中断
- 禁止时钟中断
- 为内核指定中断处理函数总入口地址
- 初始化程序的栈空间
- 初始化全局数据
- 初始化bss区
禁止全局中断
我们的程序始终运行在机器模式下(下同),因此我们只需要关注mstatus
寄存器的MIE
位,参见《riscv-privileged.pdf》,具体内容如下:
因此禁止全局中断代码如下
csrc mstatus, 0x00000008
禁止时钟中断
时钟中断的禁用由mie
寄存器的MTIE
位控制
因此我们只需要将这位清0即可,因为其它位也用不到,所以直接简写如下
csrw mie, 0
指定中断处理服务入口
中为服务入口地址由mtvec
寄存器指定
.extern trap_entry
la t0, trap_entry
csrw mtvec, t0
trap_entry
的具体实现后续再讲,现在都还处在关中断阶段,暂时用不到。
链接脚本的改写
栈空间,全局数据,bss段的初始化离不开链接脚本的配合,因此我们重新编写链接脚本。
/* 指定架构 */
OUTPUT_ARCH( "riscv" )
/* 指定入口地址 */
ENTRY( _start )
MEMORY
{
/* 参考qemu-4.1.0/hw/riscv/spike.c的spike_memmap相关代码 */
ROM (rxai!w) : ORIGIN = 0x80000000, LENGTH = 512K
RAM (wxa!ri) : ORIGIN = 0x84000000, LENGTH = 128K
}
SECTIONS
{
.text : {
PROVIDE( _text = . );
*(.text.entry) /* 将start.S的引导程序排在最前 */
*(.text)
*(.rodata)
PROVIDE( _etext = . );
} >ROM AT>ROM
. = ALIGN(4);
_load_data = LOADADDR(.data); /* data段的起始LMA */
. = ALIGN(4);
.data : {
PROVIDE( _data = . ); /* data段的起始VMA */
*(.data)
. = ALIGN(4);
} >RAM AT>ROM
. = ALIGN(4);
PROVIDE( _edata = . ); /* data段的终止VMA */
. = ALIGN(0x1000);
PROVIDE( _bss = . ); /* bss段的起始VMA */
.bss : {
*(.bss)
} >RAM AT>RAM
. = ALIGN(4);
PROVIDE( _ebss = . ); /* bss段的终止VMA */
. = ALIGN(8);
PROVIDE( end = . );
/* 初始栈空间为128个字节 */
_stack_size = 128;
.stack ORIGIN(RAM) + LENGTH(RAM) - _stack_size :
{
. = _stack_size;
PROVIDE( _stack_top = . );
} >RAM AT>RAM
_end = .;
}
如果对链接脚本不熟悉,可以参考https://www.cnblogs.com/li-hao/p/4107964.html
栈、data段、bss段初始化
la sp, _stack_top
/* Load data section */
la a0, _load_data
la a1, _data
la a2, _edata
bgeu a1, a2, begin_clear_bss
clear_data:
lw t0, (a0)
sw t0, (a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, clear_data
begin_clear_bss:
// clear bss section
la a0, _bss
la a1, _ebss
bgeu a0, a1, init_finish
clear_bss:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, clear_bss
经过整理,加上调用main函数的代码,可得完整的代码如下:
#define MSTATUS_MIE 0x00000008
.section .text.entry
.globl _start
.type _start,@function
.extern trap_entry
_start:
csrc mstatus, MSTATUS_MIE
csrw mie, 0
la t0, trap_entry
csrw mtvec, t0
la sp, _stack_top
/* Load data section */
la a0, _load_data
la a1, _data
la a2, _edata
bgeu a1, a2, begin_clear_bss
clear_data:
lw t0, (a0)
sw t0, (a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, clear_data
begin_clear_bss:
// clear bss section
la a0, _bss
la a1, _ebss
bgeu a0, a1, init_finish
clear_bss:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, clear_bss
init_finish:
call main
__die:
j __die
引导程序的主要工作已经基本完成,但现在编译还不能成功,链接器找不到trap_enry
,因此我们暂时需要写个简单的函数,欺骗一下编译器。
在risc-v/rv32i/
下创建一个port_s.S文件,编写如下代码
// port_s.S
.align 2
.global trap_entry
trap_entry:
mret
在Makefile的ASM_SOURCES_S
中加入port_s.S
ASM_SOURCES_S = \
$(TOP_DIR)/risc-v/rv32i/port_s.S \
start.S
然后再make生成二进制,启动qemu+gdb调试
Breakpoint 1, _start () at start.S:8
8 csrc mstatus, MSTATUS_MIE
(gdb) b main
Breakpoint 2 at 0x80000078: file main.c, line 2.
(gdb) c
Continuing.
Breakpoint 2, main () at main.c:2
2 return 0;
(gdb) disas
Dump of assembler code for function main:
0x80000072 <+0>: addi sp,sp,-16
0x80000074 <+2>: sw s0,12(sp)
0x80000076 <+4>: addi s0,sp,16
=> 0x80000078 <+6>: li a5,0
0x8000007a <+8>: mv a0,a5
0x8000007c <+10>: lw s0,12(sp)
0x8000007e <+12>: addi sp,sp,16
0x80000080 <+14>: ret
End of assembler dump.
(gdb) ni
3 }
(gdb)
0x8000007c 3 }
(gdb)
0x8000007e in main () at main.c:3
3 }
(gdb)
0x80000080 in main () at main.c:3
3 }
(gdb) disas
Dump of assembler code for function main:
0x80000072 <+0>: addi sp,sp,-16
0x80000074 <+2>: sw s0,12(sp)
0x80000076 <+4>: addi s0,sp,16
0x80000078 <+6>: li a5,0
0x8000007a <+8>: mv a0,a5
0x8000007c <+10>: lw s0,12(sp)
0x8000007e <+12>: addi sp,sp,16
=> 0x80000080 <+14>: ret
End of assembler dump.
(gdb)
可以看到已经能正常执行完main函数了。
现在我们的main函数是一个空函数,而一个正常的程序是需要在main函数里初始化内核并启动内核运行的,而这是有一个模板写法,因此我们把这个main函数重写一遍
// main.c
#include "tos.h"
int main(void) {
// 内核初始化
tos_knl_init()
// 创建一些初始任务
// 暂时用不到,先不写
// 启动内核
// 其实就是跳到某一个任务执行
// 通过时钟中断高优先级抢占、或同优先级时间片轮转、或任务主动放弃CPU来切换任务执行
tos_knl_start();
// 以下代码,正常情况下永远执行不到
while (1) {
asm("wfi;");
}
return 0;
}
在成功编译这个还需要,添加一系列文件,这些文件基本都是程序化添加,主要是配置内核,另外就是实现一些接口。
添加tos_cpu_def.h
// risc-v/common/include/tos_cpu_def.h
#ifndef _TOS_CPU_DEF_H_
#define _TOS_CPU_DEF_H_
enum CPU_WORD_SIZE {
CPU_WORD_SIZE_08,
CPU_WORD_SIZE_16,
CPU_WORD_SIZE_32,
CPU_WORD_SIZE_64,
};
enum CPU_STK_GROWTH {
CPU_STK_GROWTH_ASCENDING,
CPU_STK_GROWTH_DESCENDING,
};
#endif /* _TOS_CPU_DEF_H_ */
添加tos_cpu_types.h
// risc-v/common/include/tos_cpu_types.h
#ifndef _TOS_CPU_TYPES_H_
#define _TOS_CPU_TYPES_H_
/* CPU address type based on address bus size. */
#if (TOS_CFG_CPU_ADDR_SIZE == CPU_WORD_SIZE_32)
typedef uint32_t cpu_addr_t;
#elif (TOS_CFG_CPU_ADDR_SIZE == CPU_WORD_SIZE_16)
typedef uint16_t cpu_addr_t;
#else
typedef uint8_t cpu_addr_t;
#endif
/* CPU data type based on data bus size. */
#if (TOS_CFG_CPU_DATA_SIZE == CPU_WORD_SIZE_32)
typedef uint32_t cpu_data_t;
#elif (TOS_CFG_CPU_DATA_SIZE == CPU_WORD_SIZE_16)
typedef uint16_t cpu_data_t;
#else
typedef uint8_t cpu_data_t;
#endif
#if (TOS_CFG_CPU_HRTIMER_EN > 0)
#if (TOS_CFG_CPU_HRTIMER_SIZE == CPU_WORD_SIZE_08)
typedef uint8_t cpu_hrtimer_t;
#elif (TOS_CFG_CPU_HRTIMER_SIZE == CPU_WORD_SIZE_16)
typedef uint16_t cpu_hrtimer_t;
#elif (TOS_CFG_CPU_HRTIMER_SIZE == CPU_WORD_SIZE_64)
typedef uint64_t cpu_hrtimer_t;
#else
typedef uint32_t cpu_hrtimer_t;
#endif
#else
typedef uint32_t cpu_hrtimer_t;
#endif
//typedef cpu_addr_t size_t;
typedef cpu_addr_t cpu_cpsr_t;
#endif
添加tos_cpu.h
// risc-v/common/include/tos_cpu.h
#ifndef _TOS_CPU_H_
#define _TOS_CPU_H_
// 参考RISC-V的寄存器
// 用作任务栈初始化
// 保存恢复现场
typedef struct cpu_context_st {
cpu_data_t epc;
cpu_data_t mstatus;
union { cpu_data_t x1, ra; };
union { cpu_data_t x3, gp; };
union { cpu_data_t x4, tp; };
union { cpu_data_t x5, t0; };
union { cpu_data_t x6, t1; };
union { cpu_data_t x7, t2; };
union { cpu_data_t x8, s0, fp; };
union { cpu_data_t x9, s1; };
union { cpu_data_t x10, a0; };
union { cpu_data_t x11, a1; };
union { cpu_data_t x12, a2; };
union { cpu_data_t x13, a3; };
union { cpu_data_t x14, a4; };
union { cpu_data_t x15, a5; };
union { cpu_data_t x16, a6; };
union { cpu_data_t x17, a7; };
union { cpu_data_t x18, s2; };
union { cpu_data_t x19, s3; };
union { cpu_data_t x20, s4; };
union { cpu_data_t x21, s5; };
union { cpu_data_t x22, s6; };
union { cpu_data_t x23, s7; };
union { cpu_data_t x24, s8; };
union { cpu_data_t x25, s9; };
union { cpu_data_t x26, s10; };
union { cpu_data_t x27, s11; };
union { cpu_data_t x28, t3; };
union { cpu_data_t x29, t4; };
union { cpu_data_t x30, t5; };
union { cpu_data_t x31, t6; };
} cpu_context_t;
// 计算一个整数的二进制的前导0个数
// 这个函数应该放在内核里,不应该放到移植层来实现,不过当前暂时不能改变
// 所以先勉为其难地实现一个
__API__ uint32_t tos_cpu_clz(uint32_t val);
// 开关中断,应该没有用到,可以先不实现
__API__ void tos_cpu_int_disable(void);
__API__ void tos_cpu_int_enable(void);
// current program status register
// 当前程序状态的保存和恢复
// 这个很重要,必需要实现
__API__ cpu_cpsr_t tos_cpu_cpsr_save(void);
__API__ void tos_cpu_cpsr_restore(cpu_cpsr_t cpsr);
__KERNEL__ void cpu_init(void);
// 配置时钟中断
__KERNEL__ void cpu_systick_init(k_cycle_t cycle_per_tick);
// 跳到第一个任务
// 如果事先没有创建任务,则跳到内核自带的idle任务执行
__KERNEL__ void cpu_sched_start(void);
// 任务切换
__KERNEL__ void cpu_context_switch(void);
__KERNEL__ void cpu_irq_context_switch(void);
// 任务栈初始化
__KERNEL__ k_stack_t *cpu_task_stk_init(void *entry,
void *arg,
void *exit,
k_stack_t *stk_base,
size_t stk_size);
#endif // _TOS_CPU_H
添加tos_fault.h
// risc-v/common/include/tos_fault.h
#ifndef _TOS_FAULT_H_
#define _TOS_FAULT_H_
#if TOS_CFG_FAULT_BACKTRACE_EN > 0u
#error "unsupport now"
#endif
#endif /* _TOS_FAULT_H_ */
添加port_config.h
// risc-v/rv32i/
#ifndef _PORT_CONFIG_H_
#define _PORT_CONFIG_H_
#define TOS_CFG_CPU_ADDR_SIZE CPU_WORD_SIZE_32
#define TOS_CFG_CPU_DATA_SIZE CPU_WORD_SIZE_32
#define TOS_CFG_CPU_STK_GROWTH CPU_STK_GROWTH_DESCENDING
#define TOS_CFG_CPU_HRTIMER_EN 0u
#define TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT 0u
#endif /* _PORT_CONFIG_H_ */
添加port.h
// risc-v/rv32i/port.h
#ifndef _PORT_H_
#define _PORT_H_
__PORT__ void port_int_disable(void);
__PORT__ void port_int_enable(void);
__PORT__ cpu_cpsr_t port_cpsr_save(void);
__PORT__ void port_cpsr_restore(cpu_cpsr_t cpsr);
__PORT__ void port_cpu_reset(void);
__PORT__ void port_sched_start(void) __NO_RETURN__;
__PORT__ void port_context_switch(void);
__PORT__ void port_irq_context_switch(void);
__PORT__ void port_systick_config(uint32_t cycle_per_tick);
__PORT__ void port_systick_priority_set(uint32_t prio);
#endif /* _PORT_H_ */
添加tos_config.h
// demo/tos_config.h
#ifndef INC_TOS_CONFIG_H_
#define INC_TOS_CONFIG_H_
#include "stddef.h"
#define TOS_CFG_TASK_PRIO_MAX 10u // 配置TencentOS tiny默认支持的最大优先级数量
#define TOS_CFG_ROUND_ROBIN_EN 0u // 配置TencentOS tiny的内核是否开启时间片轮转
#define TOS_CFG_OBJECT_VERIFY 0u // 配置TencentOS tiny是否校验指针合法
#define TOS_CFG_EVENT_EN 1u // TencentOS tiny 事件模块功能宏
#define TOS_CFG_MMHEAP_EN 1u // 配置TencentOS tiny是否开启动态内存模块
#define TOS_CFG_MMHEAP_POOL_SIZE 8192 // 配置TencentOS tiny动态内存池大小
#define TOS_CFG_MUTEX_EN 1u // 配置TencentOS tiny是否开启互斥锁模块
#define TOS_CFG_QUEUE_EN 1u // 配置TencentOS tiny是否开启队列模块
#define TOS_CFG_TIMER_EN 0u // 配置TencentOS tiny是否开启软件定时器模块
#define TOS_CFG_SEM_EN 1u // 配置TencentOS tiny是否开启信号量模块
#if (TOS_CFG_QUEUE_EN > 0u)
#define TOS_CFG_MSG_EN 1u
#else
#define TOS_CFG_MSG_EN 0u
#endif
#define TOS_CFG_MSG_POOL_SIZE 10u // 配置TencentOS tiny消息队列大小
#define TOS_CFG_IDLE_TASK_STK_SIZE 512u // 配置TencentOS tiny空闲任务栈大小
#define TOS_CFG_CPU_TICK_PER_SECOND 1000u // 配置TencentOS tiny的tick频率
#define TOS_CFG_CPU_CLOCK 108000000 // 配置TencentOS tiny CPU频率
#define TOS_CFG_TIMER_AS_PROC 1u // 配置是否将TIMER配置成函数模式
#define TOS_CFG_VFS_EN 1u
#define TOS_CFG_MMBLK_EN 1u
#endif /* INC_TOS_CONFIG_H_ */
为了编译通过,我们暂时添加tos_cpu.c
// risc-v/common/tos_cpu.c
#include
__API__ uint32_t tos_cpu_clz(uint32_t val){
return 0;
}
__API__ void tos_cpu_int_disable(void){
}
__API__ void tos_cpu_int_enable(void){
}
__API__ cpu_cpsr_t tos_cpu_cpsr_save(void){
return 0;
}
__API__ void tos_cpu_cpsr_restore(cpu_cpsr_t cpsr){
}
__KERNEL__ void cpu_init(void){
}
__KERNEL__ void cpu_systick_init(k_cycle_t cycle_per_tick){
}
__KERNEL__ void cpu_sched_start(void){
}
__KERNEL__ void cpu_context_switch(void){
}
__KERNEL__ void cpu_irq_context_switch(void){
}
__KERNEL__ k_stack_t *cpu_task_stk_init(void *entry,
void *arg,
void *exit,
k_stack_t *stk_base,
size_t stk_size){
return 0;
}
修改Makefile
KERNEL_SRC = \
${wildcard $(TOP_DIR)/kernel/core/*.c}
C_SOURCES += $(KERNEL_SRC)
# C includes
KERNEL_INC = \
-I $(TOP_DIR)/kernel/core/include \
-I $(TOP_DIR)/kernel/pm/include \
-I $(TOP_DIR)/kernel/hal/include \
-I $(TOP_DIR)/risc-v/common/include \
-I $(TOP_DIR)/risc-v/rv32i/ \
-I $(TOP_DIR)/demo
C_INCLUDES += $(KERNEL_INC)
至此,整体框架已经搭建完毕,下一章就可以进行实际的移植代码编写。