RT-thread移植指南-RISC-V

2023-05-16

目录

RT-thread移植指南-RISC-V

1. 概述

1.1 移植资料参考

1.2 移植开发环境准备

2. 移植步骤

2.1 全局中断开关函数

2.2 线程上下文切换函数

2.3 线程栈的初始化

2.4 时钟节拍的配置

2.5 中断函数(中断时现场保护、中断注册和使能)

2.5.1 interrupt_gcc.S:

2.5.2 中断注册、使能、和分发

2.6 RT-thread启动流程

2.7 配置宏、全局符号的适配

2.7.1 宏适配

2.7.2 全局符号适配

3. SMP移植

3.1 自旋锁

3.2 为不同core准备不同栈

3.3 核间通信ipi中断

3.4 次级cpu的启动代码

3.4.1 rt_hw_secondary_cpu_up()

3.4.2 secondary_cpu_c_start()

3.4.3 rt_hw_secondary_cpu_idle_exec()

4. debug问题及解决


RT-thread移植指南-RISC-V

文档中附件及其完整word资源https://download.csdn.net/download/ty1121466568/24399940

1. 概述

本文主要记录将RT-thread标准版(v4.0.3)移植到risc-v双核U74的移植内容及其步骤。本文主要目的是记录移植步骤方便有需要者能快速了解移植的几个步骤和工作内容以便快速开展。由于环境和笔者对该系统的了解有限,已有参考的实现该文不再赘述。移植步骤为先将RT-thread在单核环境上跑通,再支持SMP,以便调试问题,提高效率。对于内核部分的移植工作,单核主要参考RT-Thread 已支持的e310,多核主要参考k210的移植。

1.1 移植资料参考

RT-thread 官网含有丰富切详尽的资料,建议先阅读相关文档,再进行下一步动作。这里我们选择移植标准板,故参考文档选择标准版文档。

文档官网:https://www.rt-thread.org/document/site/#/

内核移植章节:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/porting/porting

SMP移植章节:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/smp/smp

RT-thread启动流程:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic?id=rt-thread-%e5%90%af%e5%8a%a8%e6%b5%81%e7%a8%8b

1.2 移植开发环境准备

  1. 硬件:RISC-V 开发板+jtag工具+串口工具
  2. 软件:RISC-V编译环境+sons
  3. 代码:国内镜像云:rt-thread: RT-Thread是一个来自中国的开源物联网操作系统,它提供了非常强的可伸缩能力:从一个可以运行在ARM Cortex-M0芯片上的极小内核,到中等的ARM Cortex-M3/4/7系统,甚至是运行于MIPS32、ARM Cortex-A系列处理器上功能丰富系统 - Gitee.comhttps://gitee.com/rtthread/rt-thread/tree/v4.0.3/

2. 移植步骤

本次移植最终目标是将RT-thread标准板移植到双核U74上。通过官方文档和代码可以得出,要想将RT-thread移植到单核U74上,最少工作量为只移植内核+串口(方便调试)。本次移植采用策略为先将RT-thread在单核环境上跑通,再支持SMP,以便调试问题,提高效率。对于此内核部分的移植工作,单核主要参考RT-Thread 已支持的e310,多核主要参考k210的移植。

内核移植工作拆分:

  1. 全局中断开关函数
  2. 线程上下文切换函数
  3. 线程栈的初始化
  4. 时钟节拍的配置
  5. 中断函数(中断时现场保护、中断注册和使能)
  6. RT-thread启动流程
  7. 配置宏、全局符号的适配

2.1 全局中断开关函数

对于U74,这部分代码可以直接使用libcpu/risc-v/common目录下的context_gcc.S已有的相关实现,不再需要重新实现。

2.2 线程上下文切换函数

RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。

libcpu 移植相关 API

函数和变量

描述

rt_base_t rt_hw_interrupt_disable(void);

关闭全局中断

void rt_hw_interrupt_enable(rt_base_t level);

打开全局中断

rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit);

线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数

void rt_hw_context_switch_to(rt_uint32 to);

没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用

void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);

from 线程切换到 to 线程,用于线程和线程之间的切换

void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to);

from 线程切换到 to 线程,用于中断里面进行切换的时候使用

rt_uint32_t rt_thread_switch_interrupt_flag;

表示需要在中断里进行切换的标志

rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread;

在线程进行上下文切换时候,用来保存 from to 线程

对于U74,这部分代码可以直接使用libcpu/risc-v/common目录下已有的context_gcc.S相关实现,不再需要重新实现。只需要在rtconfig.h 中定义宏ARCH_CPU_64BIT、ARCH_RISCV_FPU、ARCH_RISCV_FPU_D。

2.3 线程栈的初始化

对于U74,这部分代码可以直接使用libcpu/risc-v/common目录下已有的context_gcc.S相关实现,不再需要重新实现。

2.4 时钟节拍的配置

有了开关全局中断和上下文切换功能的基础,RTOS 就可以进行线程的创建、运行、调度等功能了。有了时钟节拍支持,RT-Thread 可以实现对相同优先级的线程采用时间片轮转的方式来调度,实现定时器功能,实现 rt_thread_delay() 延时函数等等。

libcpu 的移植需要完成的工作,就是确保 rt_tick_increase() 函数会在时钟节拍的中断里被周期性的调用,调用周期取决于 rtconfig.h 的宏 RT_TICK_PER_SECOND 的值。

在 Cortex M 中,实现 SysTick 的中断处理函数即可实现时钟节拍功能。

void SysTick_Handler(void)

{

    /* enter interrupt */

    rt_interrupt_enter();

    rt_tick_increase();

    /* leave interrupt */

    rt_interrupt_leave();

}

在U74中,在rt_hw_board_init中调用rt_hw_timer_init对cpu定时器初始化,注册中断。在中断中调用rt_tick_increase()执行RTOS的操作并且重新设置定时定时值。

注:RT-thread 有一个小bug,rt_tick_increase()中会调用rt_timer_check函数检查定时器,在rt_timer_check中会判断链表rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]是否为空,而timer链表初始化在后续的rt_system_timer_init才执行,故若第一次定时器中断到来前该链表还未初始化,rt_timer_check会将链表错误地检查为不为空,然后进行后续操作,会导致程序挂死,故笔者第一次将cpu timer超时时间设置1s,让第一次timer中断来得晚一些。

/* system tick interrupt */

void handle_m_time_interrupt(int id, void *priv)

{

    int hartid = metal_cpu_get_current_hartid();

    rt_tick_increase();

    metal_cpu_set_mtimecmp(cpu[hartid], metal_cpu_get_mtime(cpu[hartid]) + TICK_COUNT);

}

static void rt_hw_timer_init(void)

{

    int hartid = metal_cpu_get_current_hartid();

    //FIXME: call rt_tick_increase must after rt_system_timer_init,so set more delay for first time

    metal_cpu_set_mtimecmp(cpu[hartid], metal_cpu_get_mtime(cpu[hartid]) + SF_CPU_RTC_TOGGLE_HZ);

    // /*  enable timer interrupt*/

    rt_hw_timer_irq_register(handle_m_time_interrupt, NULL);

    rt_hw_timer_irq_enable();

}

2.5 中断函数(中断时现场保护、中断注册和使能)

该部分主要实现进入中断时保存现场(栈极其相关寄存器的值)、中断控制器相关接口提供(plic中断注册和使能)。其中中断注册和使能接口主要是封装freedom-metal中的接口提供给rt-thread使用。所有中断第一入口为trap_entry,中断处理函数实现在libcpu/risc-v相关目录下的interrupt_gcc.S。

2.5.1 interrupt_gcc.S:

在单核情况下,该部分代码主要参考e310目录下interrupt_gcc.S的代码。

在多核情况下,该部分代码主要结合e310、k20目录下interrupt_gcc.S的代码。下面分析代码为多核代码。

该文件的代码主要做如下动作:

  1. 进入中断前保存栈信息、fpu寄存器f0-f32、risc-v通用寄存器x1-x32、mstatus、mepc、的值到线程栈中。切换当前栈为cpu 中断栈中(lds文件中预留,各cpu独立)
  2. 调用rt_interrupt_enter函数进行中断嵌套计数等操作。
  3. 调用自己实现的全局中断处理函数,进行中断分发和处理。在此处笔者调用freedom-metal的__metal_exception_handler函数来处理中断,在该函数中会调用笔者自己实现的中断分发函数(通过freedom-metal的接口注册)。然后在笔者自己的中断分发函数中通过查表通过调用RT-thread 中断注册api注册的中断。(注:freedom-metal的__metal_exception_handler函数使用了__attribute__((interrupt, aligned(128)))宏,该宏编译代码后会自动在该函数前后加入堆栈保持和恢复,和适配的代码冲突,故需要注释掉该宏,手动对堆栈操作。)
  4. 调用rt_interrupt_leave函数进行中断嵌套计数等操作。
  5. 如果使能了SMP,则调用rt_scheduler_do_irq_switch,该函数主要进行线程调度和切换。(若中断处理函数中需要进行线程调度和切换,会延后到此处进行,主要减小互斥锁等共享资源的占用时间,单核情况下该动作在前面的中断中执行)。
  6. 调用rt_hw_context_switch_exit,恢复线程栈中的环境,返回到中断前的状态。

interrupt_gcc.S实现: 

2.5.2 中断注册、使能、和分发

cpu的总中断注册由如下代码完成:

/*  config interrupt vector*/

    asm volatile(

        "la t0, trap_entry\n"

        "csrw mtvec, t0"

    );

中断注册:

/**

 * This function will install a interrupt service routine to a interrupt.

 * @param vector the interrupt number

 * @param handler the interrupt service routine to be installed

 * @param param the interrupt service function parameter

 * @param name the interrupt name

 * @return old handler

 */

rt_isr_handler_t rt_hw_interrupt_install(int vector, rt_isr_handler_t handler,

        void *param, const char *name)

{

    rt_isr_handler_t old_handler = RT_NULL;

    if(vector < MAX_HANDLERS)

    {

        old_handler = irq_desc[vector].handler;

        if (handler != RT_NULL)

        {

            irq_desc[vector].handler = (rt_isr_handler_t)handler;

            irq_desc[vector].param = param;

#ifdef RT_USING_INTERRUPT_INFO

            rt_snprintf(irq_desc[vector].name, RT_NAME_MAX - 1, "%s", name);

            irq_desc[vector].counter = 0;

#endif

            metal_interrupt_register_handler(plic[plic_int_hart], PLIC_EXT_IRQ(vector), handle_m_ext_interrupt, NULL);

        }

    }

    return old_handler;

}

中断使能:

/**

 * This function will mask a interrupt.

 * @param vector the interrupt number

 */

void rt_hw_interrupt_mask(int irq)

{

    metal_interrupt_disable(plic[plic_int_hart], PLIC_EXT_IRQ(irq));

}

/**

 * This function will un-mask a interrupt.

 * @param vector the interrupt number

 */

void rt_hw_interrupt_unmask(int irq)

{

    metal_interrupt_enable(plic[plic_int_hart], PLIC_EXT_IRQ(irq));

}

中断分发处理:

/**

 * This function will be call when external machine-level

 * interrupt from PLIC occurred.

 */

static void handle_m_ext_interrupt(int id, void *priv)

{

    rt_isr_handler_t isr_func;

    rt_uint32_t irq;

    void *param;

    irq = REVEAL_PLIC_EXT_IRQ(id);

    /* get interrupt service routine */

    isr_func = irq_desc[irq].handler;

    param = irq_desc[irq].param;

    /* turn to interrupt service routine */

    isr_func(irq, param);

#ifdef RT_USING_INTERRUPT_INFO

    irq_desc[irq].counter ++;

#endif

}

2.6 RT-thread启动流程

具体启动流程可见:

https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic?id=rt-thread-%e5%90%af%e5%8a%a8%e6%b5%81%e7%a8%8b

启动流程可见下图:

RT-thread 是通过startup_xx.S 来调用entry函数(对于gcc),然后调用rtthread_startup函数,在该函数中大致进行如下动作:

(1)初始化与系统相关的硬件;

(2)初始化系统内核对象,例如定时器、调度器、信号;

(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化;

  1. 初始化定时器线程、空闲线程,并启动调度器。

对于笔者而言,由于移植RT-thread是基于之前的一个裸机U74工程,故没有从startup_xx.S启动,而是在之前裸机工程的汇编代码启动main函数处替换为entry函数。

2.7 配置宏、全局符号的适配

2.7.1 宏适配

RT-thread 主要配置宏是在bsp/xxxxx/rtconfig.h文件中。对于移植到U74,主要是在hifi1的配置文件上修改,主要修改栈大小,cpu位宽(ARCH_CPU_64BIT、ARCH_RISCV_FPU、ARCH_RISCV_FPU_D)、系统位宽(RT_ALIGN_SIZE)、SMP支持(RT_USING_SMP)、cpu数量(RT_CPUS_NR)、自带测试框架(使能RT_USING_TC,注释掉FINSH_USING_MSH_ONLY)、等。

注:笔者前期经常遇到cpu执行到某条指令挂死,主要原因就是cpu字长等宏没有定义正确。

配置文件:

2.7.2 全局符号适配

RT-thread 的一些自动初始化功能和组件需要在lds文件中预留符号表位置。RT-thread堆栈功能也依赖lds文件中的堆栈符号定义。

堆适配符号引用示例:

extern  void *metal_segment_heap_target_start;

extern  void *metal_segment_heap_target_end;

#define HEAP_BEGIN  &metal_segment_heap_target_start

#define HEAP_END    &metal_segment_heap_target_end

在lds中需要定义的符号:

.text         :

    {

        *(.text.unlikely .text.unlikely.*)

        *(.text.startup .text.startup.*)

        *(.text .text.*)

        *(.gnu.linkonce.t.*)

       

        /* section information for finsh shell */

        . = ALIGN(8);

        __fsymtab_start = .;

        KEEP(*(FSymTab))

        __fsymtab_end = .;

        . = ALIGN(8);

        __vsymtab_start = .;

        KEEP(*(VSymTab))

        __vsymtab_end = .;

        . = ALIGN(8);

        . = ALIGN(8);

        __rt_init_start = .;

        KEEP(*(SORT(.rti_fn*)))

        __rt_init_end = .;

        . = ALIGN(8);

        /* section information for modules */

        . = ALIGN(8);

        __rtmsymtab_start = .;

        KEEP(*(RTMSymTab))

        __rtmsymtab_end = .;

       

        /* section information for utest */

        . = ALIGN(8);

        __rt_utest_tc_tab_start = .;

        KEEP(*(UtestTcTab))

        __rt_utest_tc_tab_end = .;

       

        . = ALIGN(8);

        _etext = .;

    } >ram AT>ram :ram

在上述lds文件定义的符号中,__fsymtab_start,__fsymtab_end,__vsymtab_start, __vsymtab_end主要用于finsh的实现,在finsh_system_init函数中使用;.rti_fn*主要用于RT-thread 启动流程中的自动初始化功能,在INIT_EXPORT宏中使用;__rtmsymtab_start,__rtmsymtab_end主要用于libc模块的支持;__rt_utest_tc_tab_start,__rt_utest_tc_tab_end,UtestTcTab主要用于自带测试框架功能实现,在utest_init,UTEST_TC_EXPORT中使用。

3. SMP移植

参考官方文档,移植SMP主要有如下工作

  1. 自旋锁 spinlock
  2. 为不同core准备不同栈
  3. 核间通信ipi中断
  4. 次级cpu的启动代码

3.1 自旋锁

此处参考k20 自旋锁实现,使用GCC自带的原子操作函数__sync_lock_test_and_set(&lock->lock, -1)实现自旋锁。

自旋锁实现如下:

#define atomic_set(ptr, val) (*(volatile typeof(*(ptr))*)(ptr) = val)

#define atomic_read(ptr) (*(volatile typeof(*(ptr))*)(ptr))

#ifndef __riscv_atomic

#error "atomic extension is required."

#endif

#define atomic_add(ptr, inc) __sync_fetch_and_add(ptr, inc)

#define atomic_or(ptr, inc) __sync_fetch_and_or(ptr, inc)

#define atomic_swap(ptr, swp) __sync_lock_test_and_set(ptr, swp)

#define atomic_cas(ptr, cmp, swp) __sync_val_compare_and_swap(ptr, cmp, swp)

typedef struct _spinlock

{

    int lock;

} spinlock_t;

static inline int spinlock_trylock(spinlock_t *lock)

{

    int res = atomic_swap(&lock->lock, -1);

    /* Use memory barrier to keep coherency */

    mb();

    return res;

}

static inline void spinlock_lock(spinlock_t *lock)

{

    while (spinlock_trylock(lock));

}

static inline void spinlock_unlock(spinlock_t *lock)

{

    /* Use memory barrier to keep coherency */

    mb();

    atomic_set(&lock->lock, 0);

    asm volatile ("nop");

}

适配到RT-thread api 实现如下:

#include "atomic_support.h"

void rt_hw_spin_lock_init(rt_hw_spinlock_t *lock)

{

    ((spinlock_t *)lock)->lock = 0;

}

void rt_hw_spin_lock(rt_hw_spinlock_t *lock)

{

    spinlock_lock((spinlock_t *)lock);

}

void rt_hw_spin_unlock(rt_hw_spinlock_t *lock)

{

    spinlock_unlock((spinlock_t *)lock);

}

3.2 为不同core准备不同栈

多核情况下,不同core有不同中断函数栈,需在lds中分配。

__STACKSIZE__ = DEFINED(__STACKSIZE__) ? __STACKSIZE__ : 40M;

PROVIDE(__STACKSIZE__ = __STACKSIZE__);

.stack :

    {

        PROVIDE(metal_segment_stack_begin = .);

        __stack_start__ = .;

        . += __STACKSIZE__;

        __stack_cpu0 = .;

        . += __STACKSIZE__;

        __stack_cpu1 = .;

        PROVIDE(metal_segment_stack_end = .);

    } >ram AT>ram :ram

对于启动函数和中断函数,不同cpu需要使用不同栈。

中断函数中:

    /* switch interrupt stack of current cpu */

    la    sp, __stack_start__

    addi  t1, t0, 1

    la    t2, __STACKSIZE__

    mul   t1, t1, t2

    add   sp, sp, t1 /* sp = (cpuid + 1) * __STACKSIZE__ + __stack_start__ */

线程切换汇编文件libcpu/risc-v/commoncontex_gcc.S中,修改li    t2, __STACKSIZE__为la    t2, __STACKSIZE__    (注:此处为rt-thread riscv架构cpu的公共函数,理应不修改此处,但这里实现为主要适配k20,这个符号在头文件中进行宏定义和lds文件中进行符号定义,笔者觉得该符号定义过于冗余,故进行修改,只使用lds文件中的符号即可)

#ifdef RT_USING_SMP

#ifdef RT_USING_SIGNALS

    mv a0, sp

    csrr  t0, mhartid

    /* switch interrupt stack of current cpu */

    la    sp, __stack_start__

    addi  t1, t0, 1

    la    t2, __STACKSIZE__

    mul   t1, t1, t2

    add   sp, sp, t1 /* sp = (cpuid + 1) * __STACKSIZE__ + __stack_start__ */

    call rt_signal_check

    mv sp, a0

#endif

#endif

3.3 核间通信ipi中断

SMP调度需要核间通信,调整其他cpu进行任务调度。U74 ipi使用的是clint

的软件中断。

其ipi中断注册实现如下:

void rt_hw_ipi_irq_enable(void)

{

    int hartid = metal_cpu_get_current_hartid();

    metal_interrupt_enable(cpu_sw_ipi_intr[hartid], metal_cpu_software_get_interrupt_id(cpu[hartid]));

    return 0;

}

void rt_hw_ipi_irq_disable(void)

{

    int hartid = metal_cpu_get_current_hartid();

    metal_interrupt_disable(cpu_sw_ipi_intr[hartid], metal_cpu_software_get_interrupt_id(cpu[hartid]));

    return 0;

}

void rt_hw_ipi_handler_install(int ipi_vector, rt_isr_handler_t ipi_isr_handler)

{

    /* note: ipi_vector maybe different with irq_vector */

    int hartid = metal_cpu_get_current_hartid();

    metal_interrupt_register_handler(cpu_sw_ipi_intr[hartid], metal_cpu_software_get_interrupt_id(cpu[hartid]), ipi_isr_handler, (void*)NULL);

}

ipi中断处理函数:

void handle_hw_ipi_interrupt(int vector, void *param)

{

    int hartid = metal_cpu_get_current_hartid();

    metal_cpu_software_clear_ipi(cpu[hartid], hartid);

    rt_scheduler_ipi_handler(vector, param);

}

void rt_scheduler_ipi_handler(int vector, void *param)

{

    rt_schedule();

}

ipi 发送函数:

void rt_hw_ipi_send(int ipi_vector, unsigned int cpu_mask)

{

    int idx;

    for (idx = 0; idx < RT_CPUS_NR; idx ++)

    {

        if (cpu_mask & (1 << idx))

        {

            metal_cpu_software_set_ipi(cpu[idx] ,idx);

        }

    }

}

3.4 次级cpu的启动代码

按照文档,内核开发者需要提供以下三个函数:

  1. rt_hw_secondary_cpu_up(), 该函数设置次级 CPU 的启动入口地址为 secondary_cpu_start ,加电启动其它 CPU 核心;
  2. secondary_cpu_c_start(), 该函数用来初始化单个次级 CPU ,主要包括初始化中断控制器接口,设置中断向量表,以及当前 CPU 的 tick 中断。最后获取内核自旋锁 _cpus_lock ,并调用函数 rt_system_scheduler_start() 开启当前 CPU 的任务调度器;
  3.  rt_hw_secondary_cpu_idle_exec(), 该函数被次级 CPU 的 idle 线程循环调用,可用来做功耗相关的处理。

上述三个函数定义在文件 drivers/platsmp.c 中。其中,只有函数 rt_hw_secondary_cpu_up() 的功能实现与芯片密切相关,需要移植者根据芯片特性提供。如果芯片使用的不是 GIC 中断控制器和 Generic Timer 定时器,那么同样需要重新实现函数 secondary_cpu_c_start()。

3.4.1 rt_hw_secondary_cpu_up()

该函数用于启动次级cpu。未执行该函数前,次级cpu处于暂停状态。暂停和启动次级cpu有两种方案:a. 次级cpu轮询判断clint的MIP_MSIP,主cpu通过发送ipi启动次级cpu;b. 次级cpu轮询全局变量,为指定值时启动

这里笔者没有采用发送ipi中断的方式启动,因为RT-thread可能在执行启动次级cpu函数前就发送ipi中断了,影响启动时序。

次级cpu启动函数:

void rt_hw_secondary_cpu_up(void)

{

    mb();

    secondary_boot_flag = 0xa55a;

}

次级cpu暂停并和恢复实现:

secondary_main:

  addi sp, sp, -16

#if __riscv_xlen == 32

  sw ra, 4(sp)

#else

  sd ra, 8(sp)

#endif

  csrr t0, mhartid

  la t1, __metal_boot_hart

  beq t0, t1, 2f

1:

secondary_cpu_entry:

  la a0, secondary_boot_flag

  ld a0, 0(a0)

  li a1, 0xa55a

  //beq a0, a1, 1f

  beq a0, a1, .enter_secondary_cpu_c_start

  j secondary_cpu_entry

.enter_secondary_cpu_c_start:

  call secondary_cpu_c_start

 

2:

  call entry

在上述实现中,判断当前cpu id是否是定义的启动cpu,如果是则进入entry函数进而进入RT-thread 启动流程,否则改cpu为次级cpu,在此处循环,直到主cpu调用rt_hw_secondary_cpu_up函数启动次级cpu。

void rt_hw_ipi_send(int ipi_vector, unsigned int cpu_mask)

{

    int idx;

    for (idx = 0; idx < RT_CPUS_NR; idx ++)

    {

        if (cpu_mask & (1 << idx))

        {

            metal_cpu_software_set_ipi(cpu[idx] ,idx);

        }

    }

}

3.4.2 secondary_cpu_c_start()

secondary_cpu_c_start()实现如下:

void secondary_cpu_c_start(void)

{

    int hartid = metal_cpu_get_current_hartid();

    rt_hw_spin_lock(&_cpus_lock);

    /* initialize interrupt controller */

    rt_hw_secondary_interrupt_init();

    /* install IPI handle */

    /*note: in smp, to prevent the deadlock, shouled clear ipi before enable interrupt  */

    rt_hw_ipi_irq_disable();

    metal_cpu_software_clear_ipi(cpu[hartid], hartid);

    rt_hw_ipi_handler_install(RT_SCHEDULE_IPI, handle_hw_ipi_interrupt);

    rt_hw_ipi_irq_enable();

    rt_hw_timer_init();

    rt_system_scheduler_start();

}

注:此处打开ipi中断前之所以进行disable和clear操作是为了防止在进入该函数前ipi中断已置位,在这种情况下使能中断后会直接进入ipi中断处理函数,而ipi中断处理函数会进行线程调度,会获取自旋锁_cpus_lock,导致死锁。此处的自旋锁会在后续rt_hw_context_switch_to函数切换线程时调用rt_cpus_lock_status_restore函数解锁。

3.4.3 rt_hw_secondary_cpu_idle_exec()

rt_hw_secondary_cpu_idle_exec()函数主要实现次级cpu空闲任务,其实现如下:

void rt_hw_secondary_cpu_idle_exec(void)

{

    asm volatile ("wfi");

}

4. debug问题及解决

  1. cpu执行到某条指令就挂死,进入异常handle:

在移植过程中,遇到几个cpu执行到某条指令就挂死的问题,例ld、sd指令。这里进行汇总。一般出现该问题都是该条汇编指令的64位,而指令操作的对象内存没有对齐到64bit,导致指令异常。在本次移植中,对于该类型问题进行了如下修复动作:

  1. 在rtconfig.h中定义了宏ARCH_CPU_64BIT、ARCH_RISCV_FPU、ARCH_RISCV_FPU_D;
  2. 在rtconfig.h中定义宏:#define RT_ALIGN_SIZE 8,该宏未对齐到cpu字长会导致内置线程栈地址不对齐,进而导致指令错误;
  3. 在lds文件中,对于代码段中的一些符号起始地址进行对齐:. = ALIGN(8),若未对齐,同样会寻址错误,该原理对其他数据段同样适用;
  1. RT-thread 挂死在第一次定时器中断rt_tick_increase()函数中:

RT-thread有一个小bug,rt_tick_increase()中会调用rt_timer_check函数检查定时器,在rt_timer_check中会判断链表rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]是否为空,而timer链表初始化在后续的rt_system_timer_init才执行,故若第一次定时器中断到来前该链表还未初始化,rt_timer_check会将链表错误地检查为不为空,然后进行后续操作,会导致程序挂死,故笔者第一次将cpu timer超时时间设置1s,让第一次timer中断来得晚一些。

  1. RT-thread自带测试框架对SMP架构不适用:

笔者移植的是v4.0.3版本,自带测试框架中测例的主要设计思想是单核,对SMP不适用,例thread_detach测例可能会在A core上释放B core上正在运行的线程,导致无法释放,而A core又认为自己释放了,然后继续往下执行的情况发送。

  1. 某些线程运行着或者多切换几次就挂死:

该问题可优先检查是否是线程栈溢出,RT-thread只会在调度前检查栈溢出,而线程运行中栈增加无法检测,可能导致该问题。可调大栈的大小debug。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RT-thread移植指南-RISC-V 的相关文章

  • ubuntu20.04_ROS中运行gazebo控制机器人模型报错

    1 无法启动类型为 controller manager spawner 的节点 xff1a controller manager ERROR cannot launch node of type controller manager sp
  • Fast Planner——代码解读参考资料整理

    1 地图部分 1 1 EGO Swarm代码解读 地图部分 参数解读 主要函数解读 1 2 EGO Swarm代码阅读笔记之GridMap类 1 3 EGO PLANNER代码阅读 xff08 地图部分 xff09 1 4 欧几里得距离转换
  • FreeRTOS 10.4.3在RISCV(T-HEAD C906)平台上移植过程

    好记性不如烂笔头 记录点滴移植经历 一方面便于总结提炼 二是分享 让别人少走有些弯路 自己以后踩坑了也爬的利索点 首先梳理一下移植框架 FreeRTOS还是非常简单的 体量上要比RT Thread Nuttx等偏重型的系统轻量不少 这可能也
  • Web服务器CGI的配置

    Web服务器CGI的配置 CGI程序运行在Web服务器端 xff0c Web服务器可以是Apache Nginx等 GGI程序可以是Python Ruby Perl Shell C C 43 43 等 配置 apache默认加载cgi模块
  • 解决mac安装homebrew后报错-bash: brew: command not found

    参照官网上很简单的一句安装命令 xff0c usr bin ruby e 34 curl fsSL https raw githubusercontent com Homebrew install master install 34 安装完
  • 转行的辛苦

    我是2004年毕业的 xff0c 学的专业是市场营销 xff0c 毕业后来到深圳 xff0c 换了很多工作 xff0c 一直都无法找到令自己满意的工作 因为我非常喜欢计算机 xff0c 从中学到大学 xff0c 一直是班级里公认的计算机高手
  • 物联网之轻量级TCP/IP协议栈——Lwip

    简介 Lwip为轻量级的Tcp IP协议栈 xff0c 对于嵌入式设备资源比较友好 xff0c 占用RAM低 xff0c 基本上物联网wifi芯片都会集成此协议栈到SDK中 xff0c 其位于OSI的网路层往上 熟悉socket编程的能够很
  • github下载慢或报错“The-remote-end-hung-up-unexpectedly”解决办法

    github下载慢或报错 The remote end hung up unexpectedly 解决办法 xff1a 该问题往往因为内部网络限制等因素导致 因细节更新 xff0c 欢迎访问本文源站链接 xff1a https turboc
  • git 如何把单个文件回退到某一版本

    git 如何把单个文件回退到某一版本 概要四条命令git restoregit resetgit checkoutgit cherry pick 概要 应用场景 xff1a 在进行一次完整的提交后 xff0c 你可能有有这样的需求 xff1
  • Docker

    学习笔记 一 Docker概述 1 基本介绍 Docker是一个开源的应用容器引擎 xff0c 基于Go语言 xff0c 并遵从apache2 0协议开源 docker可以让开发者打包他们的应用以及依赖包到一个轻量级 可移植的容器中 xff
  • roslaunch 时出现resource 找不到的问题

    Resource not found roslaunch ROS path 0 61 opt ros noetic share ros ROS path 1 61 opt ros noetic share The traceback for
  • Linux多线程编程

    在传统的 UNIX 模型中 xff0c 当一个进程需要另一个实体来完成某事 xff0c 它就 fork 一个子进程并让子进程去处理 但是 fork 的调用有如下缺点 xff1a xff08 1 xff09 fork 的 代价是 昂贵的 fo
  • 控制工程实践(13)——滤波器的实现(之二)

    2 中值滤波算法 中值滤波算法 xff0c 通俗讲 xff0c 就是取一组数据的中间大小的值 运算过程 xff1a 对某一参数连续采样N次 xff0c 为方便选取 xff0c N设为奇数 xff1b 把N个采样值从小到大排序 xff1b 取
  • 控制工程实践(14)——滤波器的实现(之三)

    4 加权平均滤波算法 算术平均滤波算法 有平滑度和灵敏度的取舍矛盾 xff1a 取样信号个数小时 xff0c 灵敏度高 xff0c 但平滑度低 xff1b 取样信号个数大时 xff0c 平滑度高 xff0c 但灵敏度低 为了协调二者矛盾 x
  • 信号与系统 基础知识点整理 03(文末可下载PDF格式)

    接着前面的继续 xff1a 信号与系统 passage three Written 8 March 2022 Edited by Wang Ximing 一 xff08 信号的分解 xff09 xff08 1 xff09 直流分量与交流分量
  • mac系统如何生成SSH key与GitHub通信

    一 检查 SSH key 是否存在 在终端输入 xff1a ls al ssh 如果没有 xff0c 终端显示如下 xff1a No such file or directory 如果已经存在 xff0c 则会显示 id rsa 和 id
  • ROS-Industrial 硬件支持

    ROS Industrial硬件支持 ROS Industrial程序的目标是为许多不同种类的工业设备提供ROS接口 xff0c 包括PLC xff0c 机器人控制器 xff0c Servos xff0c 人机界面 工业机器人 下表总结了各
  • ROS依赖包查找安装

    当下载某个功能包到自己的空间 xff0c 在编译时 xff0c 出现依赖项有问题 xff0c 解决如下 xff1a roscd package name rosdep update rosdep package name rosdep即可安
  • 在Windows上使用ROS软件包

    1 二进制安装 如果有ROS软件包的二进制发行版 xff0c 可以使用Chocolatey安装 choco install ros melodic lt package name gt ROS软件包查询 https index ros or
  • EKF扩展卡尔曼滤波器 - CTRV运动模型 及其代码实现

    本文参考了Adam大佬的帖子 https blog csdn net AdamShan article details 78265754 原贴的公式有一点点错误 xff0c 这里已经修正了 xff0c 并给出了代码实现 CTRV模型 我们通

随机推荐

  • 安卓SDK和API是什么意思?

    安卓SDK和API是什么意思 xff1f 一 SDK SDK就是kit xff0c 通俗讲就是工具箱 一系列的工具组合在一起 xff0c 能实现补全代码 自动错误检查之类的功能 xff0c 比如点一下run xff0c 会调用编译器来自动编
  • 3D视觉(五):对极几何和三角测量

    3D视觉 五 xff1a 对极几何和三角测量 对极几何 xff08 Epipolar Geometry xff09 描述的是两幅视图之间的内在射影关系 xff0c 与外部场景无关 xff0c 只依赖于摄像机内参数和这两幅试图之间的的相对姿态
  • 关于产品的一些思考——写在前面的话

    自己是一个十足的Geek xff0c 喜欢使用各种新奇的东西 xff0c 包括软件 硬件 技术 xff0c 又因为自己一点点轻微的强迫症和完美主义 xff0c 在这个过程中总会有自己的一些思考 xff0c 又因为技术出身 xff0c 总会考
  • mybatis映射文件mapper.xml的写法。

    在学习mybatis的时候我们通常会在映射文件这样写 xff1a lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt DOCTYPE mapper PUBLIC 34 myba
  • layer的弹出层的简单的例子

    如果不了级的基本的清楚官网查看api网址为 http layer layui com 我用的是iframe 如果是iframe层 layer open type 2 content 39 http sentsin com 39 这里cont
  • 左链接Column 'id' in field list is ambiguous

    如题错误如左链接Column 39 id 39 in field list is ambiguous 今天在写sm的时候 xff0c 用到两个表的联合查询出现的如下的错误 xff0c 仔细查找才发现原来两个表的id重复了 xff0c use
  • 我所理解的人工智能

    很多人容易把人工智能理解为机器人 机器人是人工智能的一个实际体现 人工智能应用很广泛 下面我来谈谈我的理解 人工智能可分开理解为 人工 和 智能 xff0c 即人类创造出来的智能 xff0c 从广义上来讲只要人类创造出来 xff0c 能为人
  • maven出现:Failed to execute goal on project ...: Could not resolve dependencies for project ...

    1 我的项目结构是一个父项目 xff0c 多个子项目目录如下 xff1a 2 我这里就举个例子 xff0c 所以应用的也就是core和domain这两个项目 3 两个项目都继承父项目 4 在模块中domain依赖于core xff0c 在c
  • 有关Shiro中Principal的使用

    1 定义 principal代表什么那 xff1f 如果阅读官方文档或者源码你会得到如下的定义 xff1a 解释 xff1a 1 xff09 可以是uuid 2 xff09 数据库中的主键 3 xff09 LDAP UUID或静态DN 4
  • 关于shiro的 subject.getPrincipal()方法

    1 说明 上一篇文章说明了 principal xff0c 而subject getPrincipal 是用来干嘛的 xff0c 他就是来获取你存储的principal xff0c 内部是怎么获取的那 xff0c 多个principal怎么
  • CentOS7 64位安装solr7.2.0

    声明 xff1a 本人为学习solr的新手 xff0c 如编写过程中有部队的地方还请各位大佬指正 本文为原创 xff0c 如要转载请注明出处 你能学到 xff1a 1 linux上solr的安装部署 xff0c 官方给出的运行方式 2 添加
  • 阿里巴巴20121009 研发/算法工程师 笔试试题【修正】

    第19题 a i 在排序后的位置是 i k i 43 k xff0c a i 43 2k 在排序后的位置是 i 43 k i 43 3k xff0c 必然有a i lt 61 a i 43 2k 所以数组a里实际上有2k个各自有序的 交错的
  • printf() % lf出错

    printf 函数中不存在 lf xff0c 输入 double 用 lf 输出用 f
  • 奔腾系列的CPU 和酷睿系列的CPU

    以后奔腾要沦为中下层产品 奔腾D是接替奔腾4的型号 也是INTEL的第一代双核处理器 技术还比较粗糙 发热量控制的也不够好 至于酷睿系列 这可是INTEL的最新力作 性能上有绝对的优势 技术上也对老对手AMD保持了领先 而且功耗控制的也非常
  • 为什么神经网络被称为黑匣子

    数学层面 xff1a 由于网络参数与近似的数学函数之间缺乏明确的连接 xff0c 人工神经网络通常被称为 黑匣子
  • 第八弹 ROS发布者Publisher的编程实现

    1 话题模型 xff08 发布与订阅 xff09 2 创建功能包 catkin create pkg learning topic roscpp rospy std msgs geometry msgs turtlesim 建立一个名为le
  • TRIZ创新思维方法_简要复习

    一 TRIZ介绍 TRIZ理论成功地揭示了创造发明的内在规律和原理 xff0c 着力于澄清和强调系统中存在的矛盾 xff0c 其目标是完全解决矛盾 xff0c 获得最终的理想解 它不是采取折中或者妥协的做法 xff0c 而且它是基于技术的发
  • Generalized Focal Loss: Learning Qualified and Distributed BBoxes for Dense Object Detection论文翻译阅读

    Generalized Focal Loss Learning Qualified and Distributed Bounding Boxes for Dense Object Detection论文翻译阅读 论文下载地址 xff1a 点
  • ubuntu16.04对SD卡进行分区

    赶在2020年上半年的最后一天 xff0c 匆忙地写上一个博客 这篇博客是对自己的一个反思 xff0c 我的博客属于自己完全开辟的内容几很少 xff0c 有些博客大家随便在网上一搜就能找到 说实话 xff0c 有时候我会怀疑自己的智商有问题
  • RT-thread移植指南-RISC-V

    目录 RT thread移植指南 RISC V 1 概述 1 1 移植资料参考 1 2 移植开发环境准备 2 移植步骤 2 1 全局中断开关函数 2 2 线程上下文切换函数 2 3 线程栈的初始化 2 4 时钟节拍的配置 2 5 中断函数