# 基于qemu-riscv从0开始构建嵌入式linux系统ch21. 实时操作系统FreeRTOS移植RISCV-S模式 ### FreeRTOS FreeRTOS在嵌入式行业内是非常出名了,这么多节过去了,我们不能忘记除了7个core上运行的linux系统外,我们还有个trusted_domain,今天我们就移植一个实时操作系统FreeRTOS到这个域中。注意这里的FreeRTOS运行在S模式上而非完全的裸机环境M模式上,而截止笔者撰写本文官方还没给出S模式的移植port文件,我也没搜索到相关内容,那我们就自己动手。 ### 下载源码 FreeRTOS[官网](https://www.freertos.org/)可以下载最新lts版本的代码,这里我们下载到V10.4.3版本。将代码拷贝到trusted_domain/FreeRTOS-Kernel目录,删除portable下大部分文件,仅保留portable/MemMang/heap_4.c文件。 ### 编写RISCV-S模式port层 创建portable/GCC/RISC-V目录,然后创建port.c、portASM.S、portmacro.h文件 #### port.c port.c文件主要实现RTOS的tick初始化,这里直接调用sbi请求下一次tick到来时间戳,相比M模式下需要自己配置寄存器简单不少。 ```c /* Scheduler includes. */ #include "FreeRTOS.h" #include "task.h" #include "portmacro.h" #include "riscv_asm.h" #include "sbi.h" #include "ns16550.h" /* Standard includes. */ #include "string.h" /* Let the user override the pre-loading of the initial LR with the address of prvTaskExitError() in case it messes up unwinding of the stack in the debugger. */ #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 )); /*-----------------------------------------------------------*/ /* Tick时间间隔 */ 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 ) ); 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 ); #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; } /*-----------------------------------------------------------*/ 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( ;; ); } ``` #### portASM.S portASM用于处理中断和异常,所有的异常和中断入口均从freertos_risc_v_trap_handler进入。进行相关的压栈操作后检查scause寄存器,根据寄存器相关bit可以区分异常,timer中断,ipi中断和其他外部中断。在ipi中断中我们来处理一次任务上下文调度,而在timer中断调用xTaskIncrementTick增加tick。 ```assembly #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 portWORD_SIZE 4 #define store_x sw #define load_x lw #else #error Assembler did not define __riscv_xlen #endif /* Only the standard core registers are stored by default. */ #if __riscv_flen == 64 #define portCONTEXT_SIZE ( 62 * portWORD_SIZE ) #define store_fx fsd #define load_fx fld #elif __riscv_xlen == 32 #define portCONTEXT_SIZE ( 62 * portWORD_SIZE ) #define store_fx fsw #define load_fx flw #else #define portCONTEXT_SIZE ( 30 * portWORD_SIZE ) #endif .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 prvTaskExitError /*-----------------------------------------------------------*/ .align 8 .func freertos_risc_v_trap_handler: 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 ) #ifdef __riscv_flen csrr t2, sstatus li t0, SSTATUS_FS and t1,t2,t0 bne t1,t0,1f store_fx f0, 30 * portWORD_SIZE( sp ) store_fx f1, 31 * portWORD_SIZE( sp ) store_fx f2, 32 * portWORD_SIZE( sp ) store_fx f3, 33 * portWORD_SIZE( sp ) store_fx f4, 34 * portWORD_SIZE( sp ) store_fx f5, 35 * portWORD_SIZE( sp ) store_fx f6, 36 * portWORD_SIZE( sp ) store_fx f7, 37 * portWORD_SIZE( sp ) store_fx f8, 38 * portWORD_SIZE( sp ) store_fx f9, 39 * portWORD_SIZE( sp ) store_fx f10, 40 * portWORD_SIZE( sp ) store_fx f11, 41 * portWORD_SIZE( sp ) store_fx f12, 42 * portWORD_SIZE( sp ) store_fx f13, 43 * portWORD_SIZE( sp ) store_fx f14, 44 * portWORD_SIZE( sp ) store_fx f15, 45 * portWORD_SIZE( sp ) store_fx f16, 46 * portWORD_SIZE( sp ) store_fx f17, 47 * portWORD_SIZE( sp ) store_fx f18, 48 * portWORD_SIZE( sp ) store_fx f19, 49 * portWORD_SIZE( sp ) store_fx f20, 50 * portWORD_SIZE( sp ) store_fx f21, 51 * portWORD_SIZE( sp ) store_fx f22, 52 * portWORD_SIZE( sp ) store_fx f23, 53 * portWORD_SIZE( sp ) store_fx f24, 54 * portWORD_SIZE( sp ) store_fx f25, 55 * portWORD_SIZE( sp ) store_fx f26, 56 * portWORD_SIZE( sp ) store_fx f27, 57 * portWORD_SIZE( sp ) store_fx f28, 58 * portWORD_SIZE( sp ) store_fx f29, 59 * portWORD_SIZE( sp ) store_fx f30, 60 * portWORD_SIZE( sp ) store_fx f31, 61 * portWORD_SIZE( sp ) 1: li t0, (0x1 << 14) csrs sstatus,t0 li t0, (0x1 << 13) csrc sstatus,t0 #endif csrr t0, sepc store_x t0, 0 * portWORD_SIZE( sp ) load_x t0, pxCurrentTCB /* Load pxCurrentTCB. */ store_x sp, 0( t0 ) /* Write sp to first TCB member. */ csrr a0, scause csrr a1, sepc test_if_asynchronous: 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: 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 load_x sp, xISRStackTop /* Switch to ISR stack before function call. */ 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 /* Switch to ISR stack before function call. */ 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 * portWORD_SIZE( sp ) csrw sepc, t0 #ifdef __riscv_flen csrr t2, sstatus #endif /* Load mstatus with the interrupt enable bits used by the task. */ load_x t0, 29 * portWORD_SIZE( sp ) csrw sstatus, t0 /* Required for MPIE bit. */ #ifdef __riscv_flen li t0, SSTATUS_FS and t1,t2,t0 bne t1,t0,1f load_fx f0, 30 * portWORD_SIZE( sp ) load_fx f1, 31 * portWORD_SIZE( sp ) load_fx f2, 32 * portWORD_SIZE( sp ) load_fx f3, 33 * portWORD_SIZE( sp ) load_fx f4, 34 * portWORD_SIZE( sp ) load_fx f5, 35 * portWORD_SIZE( sp ) load_fx f6, 36 * portWORD_SIZE( sp ) load_fx f7, 37 * portWORD_SIZE( sp ) load_fx f8, 38 * portWORD_SIZE( sp ) load_fx f9, 39 * portWORD_SIZE( sp ) load_fx f10, 40 * portWORD_SIZE( sp ) load_fx f11, 41 * portWORD_SIZE( sp ) load_fx f12, 42 * portWORD_SIZE( sp ) load_fx f13, 43 * portWORD_SIZE( sp ) load_fx f14, 44 * portWORD_SIZE( sp ) load_fx f15, 45 * portWORD_SIZE( sp ) load_fx f16, 46 * portWORD_SIZE( sp ) load_fx f17, 47 * portWORD_SIZE( sp ) load_fx f18, 48 * portWORD_SIZE( sp ) load_fx f19, 49 * portWORD_SIZE( sp ) load_fx f20, 50 * portWORD_SIZE( sp ) load_fx f21, 51 * portWORD_SIZE( sp ) load_fx f22, 52 * portWORD_SIZE( sp ) load_fx f23, 53 * portWORD_SIZE( sp ) load_fx f24, 54 * portWORD_SIZE( sp ) load_fx f25, 55 * portWORD_SIZE( sp ) load_fx f26, 56 * portWORD_SIZE( sp ) load_fx f27, 57 * portWORD_SIZE( sp ) load_fx f28, 58 * portWORD_SIZE( sp ) load_fx f29, 59 * portWORD_SIZE( sp ) load_fx f30, 60 * portWORD_SIZE( sp ) load_fx f31, 61 * portWORD_SIZE( sp ) 1: #endif 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: /* If there is a clint then interrupts can branch directly to the FreeRTOS trap handler. Otherwise the interrupt controller will need to be configured outside of this file. */ la t0, freertos_risc_v_trap_handler csrw stvec, t0 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. */ #ifdef __riscv_flen load_fx f0, 30 * portWORD_SIZE( sp ) load_fx f1, 31 * portWORD_SIZE( sp ) load_fx f2, 32 * portWORD_SIZE( sp ) load_fx f3, 33 * portWORD_SIZE( sp ) load_fx f4, 34 * portWORD_SIZE( sp ) load_fx f5, 35 * portWORD_SIZE( sp ) load_fx f6, 36 * portWORD_SIZE( sp ) load_fx f7, 37 * portWORD_SIZE( sp ) load_fx f8, 38 * portWORD_SIZE( sp ) load_fx f9, 39 * portWORD_SIZE( sp ) load_fx f10, 40 * portWORD_SIZE( sp ) load_fx f11, 41 * portWORD_SIZE( sp ) load_fx f12, 42 * portWORD_SIZE( sp ) load_fx f13, 43 * portWORD_SIZE( sp ) load_fx f14, 44 * portWORD_SIZE( sp ) load_fx f15, 45 * portWORD_SIZE( sp ) load_fx f16, 46 * portWORD_SIZE( sp ) load_fx f17, 47 * portWORD_SIZE( sp ) load_fx f18, 48 * portWORD_SIZE( sp ) load_fx f19, 49 * portWORD_SIZE( sp ) load_fx f20, 50 * portWORD_SIZE( sp ) load_fx f21, 51 * portWORD_SIZE( sp ) load_fx f22, 52 * portWORD_SIZE( sp ) load_fx f23, 53 * portWORD_SIZE( sp ) load_fx f24, 54 * portWORD_SIZE( sp ) load_fx f25, 55 * portWORD_SIZE( sp ) load_fx f26, 56 * portWORD_SIZE( sp ) load_fx f27, 57 * portWORD_SIZE( sp ) load_fx f28, 58 * portWORD_SIZE( sp ) load_fx f29, 59 * portWORD_SIZE( sp ) load_fx f30, 60 * portWORD_SIZE( sp ) load_fx f31, 61 * portWORD_SIZE( sp ) #endif 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: csrr t0, sstatus /* Obtain current sstatus value. */ la t1, sstatus_init lw t2, 0(t1) or t0, t0, t2 #ifdef __riscv_flen lw t2, 4(t1) and t0, t0, t2 #endif 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. */ #ifdef __riscv_flen addi a0, a0, -(32 * portWORD_SIZE) /* Space for registers x11-x31. */ #endif 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 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 sstatus_init: .word (~SSTATUS_FS) #ifdef __riscv_flen .word (0x1 << 13) #endif .endfunc /*-----------------------------------------------------------*/ ``` #### portmacro.h portmacro.h定义一些移植宏,以及最重要的portYIEL调用send_ipi。 ```c #ifndef PORTMACRO_H #define PORTMACRO_H #ifdef __cplusplus extern "C" { #endif #include "quard_star.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 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. */ #define portTICK_TYPE_IS_ATOMIC 1 /*-----------------------------------------------------------*/ /* Architecture specifics. */ #define portSTACK_GROWTH ( -1 ) #define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ ) #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 #endif /*-----------------------------------------------------------*/ /* Scheduler utilities. */ extern void vTaskSwitchContext( void ); /* 通过sbi触发自己核心的软中断 */ extern void sbi_send_ipi(const unsigned long *hart_mask); #define portYIELD() sbi_send_ipi((const unsigned long *)(0x1< 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 */ ``` ### 编写测试代码 最后,编写RTOS的配置config文件和测试主函数。 #### FreeRTOSConfig.h 主要配置系统tick时钟和栈大小以及需要启用的ipc组件。 ```c #ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H #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 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( 10000000 ) #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) #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 */ ``` #### main.c 测试代码创建两个任务。 ```c #include #include #include "debug_log.h" #include "sbi.h" #include "riscv_asm.h" static void task1(void *p_arg) { int time = 0; for(;;) { debug_log("task1 0x%x\n",time++); vTaskDelay(pdMS_TO_TICKS(1000)); } } static void task2(void *p_arg) { int time = 0; for(;;) { debug_log("task2 0x%x\n",time++); vTaskDelay(pdMS_TO_TICKS(1000)); } } static void vTaskCreate(void *p_arg) { debug_log("vTaskCreate\n"); xTaskCreate(task1,"task1",2048,NULL,4,NULL); xTaskCreate(task2,"task2",2048,NULL,4,NULL); vTaskDelete(NULL); } int main(void) { debug_log("Hello FreeRTOS!\n"); debug_log_init(); xTaskCreate(vTaskCreate,"task creat",256,NULL,4,NULL); vTaskStartScheduler(); return 0; } ``` #### startup.S 启动文件配置栈指针,加载data段,初始化bss段,初始化newlibc库(当前没有代newlib的编译器可以跳过这个步骤),跳转main ```assembly #include "riscv_encoding.h" #include "quard_star.h" .section .init .globl _start .type _start,@function _start: .cfi_startproc .cfi_undefined ra .option push .option norelax la gp, __global_pointer$ .option pop // Continue primary hart li a1, PRIM_HART bne a0, a1, secondary csrw sie, 0 csrw sip, 0 /* set to disable FPU */ li t0, SSTATUS_FS csrc sstatus, t0 #ifdef __riscv_flen /* set to init FPU */ li t0, (0x1 << 13) csrs sstatus, t0 #endif li t0, SSTATUS_SUM // SUM in sstatus csrs sstatus, t0 // Primary hart la sp, _sp csrw sscratch, sp // Load data section la a0, _data_lma la a1, _data la a2, _edata bgeu a1, a2, 2f 1: LOAD t0, (a0) STOR t0, (a1) addi a0, a0, REGSIZE addi a1, a1, REGSIZE bltu a1, a2, 1b 2: // Clear bss section la a0, __bss_start la a1, __bss_end bgeu a0, a1, 2f 1: STOR zero, (a0) addi a0, a0, REGSIZE bltu a0, a1, 1b 2: /* Call global constructors */ la a0, __libc_fini_array call atexit call __libc_init_array // 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 ``` #### link.lds 链接脚本要进行大量修改,合理规划data段的lma和vma地址,且定义符号方便启动文件初始化data段和bss段。 ``` 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 { __stack_size = DEFINED(__stack_size) ? __stack_size : 2K; .init : { KEEP (*(SORT_NONE(.init))) } >rom AT>rom .ilalign : { . = ALIGN(8); PROVIDE( _ilm_lma = . ); } >rom AT>rom .ialign : { PROVIDE( _ilm = . ); } >rom AT>rom .text : { *(.rodata .rodata.*) *(.text.unlikely .text.unlikely.*) *(.text.startup .text.startup.*) *(.text .text.*) *(.gnu.linkonce.t.*) } >rom AT>rom .fini : { KEEP (*(SORT_NONE(.fini))) } >rom AT>rom . = ALIGN(8); PROVIDE (__etext = .); PROVIDE (_etext = .); PROVIDE (etext = .); PROVIDE( _eilm = . ); .preinit_array : { PROVIDE_HIDDEN (__preinit_array_start = .); KEEP (*(.preinit_array)) PROVIDE_HIDDEN (__preinit_array_end = .); } >rom AT>rom .init_array : { PROVIDE_HIDDEN (__init_array_start = .); KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) PROVIDE_HIDDEN (__init_array_end = .); } >rom AT>rom .fini_array : { PROVIDE_HIDDEN (__fini_array_start = .); KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) PROVIDE_HIDDEN (__fini_array_end = .); } >rom AT>rom .ctors : { /* gcc uses crtbegin.o to find the start of the constructors, so we make sure it is first. Because this is a wildcard, it doesn't matter if the user does not actually link against crtbegin.o; the linker won't look for a file to match a wildcard. The wildcard also means that it doesn't matter which directory crtbegin.o is in. */ KEEP (*crtbegin.o(.ctors)) KEEP (*crtbegin?.o(.ctors)) /* We don't want to include the .ctor section from the crtend.o file until after the sorted ctors. The .ctor section from the crtend file contains the end of ctors marker and it must be last */ KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) KEEP (*(SORT(.ctors.*))) KEEP (*(.ctors)) } >rom AT>rom .dtors : { KEEP (*crtbegin.o(.dtors)) KEEP (*crtbegin?.o(.dtors)) KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) KEEP (*(SORT(.dtors.*))) KEEP (*(.dtors)) } >rom AT>rom . = ALIGN(8); PROVIDE( _eilm = . ); .lalign : { . = ALIGN(8); PROVIDE( _data_lma = . ); } >rom AT>rom .dalign : { . = ALIGN(8); PROVIDE( _data = . ); } >ram AT>rom .data : { *(.rdata) *(.gnu.linkonce.r.*) *(.data .data.*) *(.gnu.linkonce.d.*) . = ALIGN(8); PROVIDE( __global_pointer$ = . + 0x800); *(.sdata .sdata.*) *(.gnu.linkonce.s.*) . = ALIGN(8); *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) . = ALIGN(8); } >ram AT>rom . = ALIGN(8); PROVIDE( _edata = . ); PROVIDE( edata = . ); PROVIDE( _fbss = . ); PROVIDE( __bss_start = . ); .bss : { *(.sbss*) *(.gnu.linkonce.sb.*) *(.bss .bss.*) *(.gnu.linkonce.b.*) *(COMMON) . = ALIGN(8); PROVIDE( __bss_end = . ); } >ram AT>ram . = ALIGN(8); PROVIDE( _end = . ); PROVIDE( end = . ); PROVIDE( heap_start = . ); .stack ORIGIN(ram) + LENGTH(ram) - __stack_size : { PROVIDE( heap_end = . ); . = ALIGN(16); . = __stack_size; . = ALIGN(16); PROVIDE( _sp = . ); } >ram AT>ram } ``` ### makefile文件 注意我们目前使用的编译器自带了glib的C库,这是在linux系统上使用的,因此其中大部分c函数无法使用,我们本节不会使用任何标准C函数,后面我们改用newlib的C库才能比较好的在freertos的环境中使用。最后编译器务必使用-mcmodel=medany选项,才能允许程序运行在0x80000000之后的高地址,否则就只能打开mmu将程序映射到低地址去。这也是我们不能使用这个版本的glibc,因为glibc一般都使用-mcmodel=medlow来编译。 ```Makefile ########################################################################################################################## # 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 = riscv- 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)/stubs/*.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=rv64imafdc -mabi=lp64d -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)/stubs \ -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) -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) -x $(BUILD_DIR)/../gdb.sh ####################################### # dependencies ####################################### #-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*) # *** EOF *** ``` ok,到这里移植差不多完成了,来编译运行看看效果吧。 ![ch21-0](./img/ch21-0.png) > 本教程的
github仓库:https://github.com/QQxiaoming/quard_star_tutorial
gitee仓库:https://gitee.com/QQxiaoming/quard_star_tutorial
本节所在tag:ch21