本帖主要描述raspi 3b上SMP实现,该实现主要基于现有raspi2 bsp代码基础上添加相关SMP支持,所需支持的功能主要包括:
多核启动
多核通信
多核时钟
多核中断上下文切换
其它rt-thread SMP必要接口
多核启动
rt-thread启动过程中,bootrom会让主核之外的其它核处于等待状态,直至主核将地址写入其它核对应的MAILBOX3寄存器中。
当前实现中,Raspi3b 多核从同一个入口:_reset 开始进行CPU核初始化。总体初始化流程如下:
首先判断当前CPU是否为主核(默认CPU0为主核):
mrc p15, 0, r0, c0, c0, 5 //read cpu id
ubfx r0, r0, #0, #2
cmp r0, #0
如果非主核,进入非主核初始化操作(该操作在主核唤醒非主核后执行):
bl secondary_cpu_start
b .
如果是主核,那么对各模式下栈指针,中断向量表,MMU,调度,组件等进行初始化,设置IPI中断处理函数,使能多核IPI通信中断,启动应用,并开始调度。
除初始化MMU,设置IPI中断函数,使能IPI中断外,其它基本为原raspi2单核启动内容。需要注意的是:MMU数据内存类型需要带SHARED属性(以支持后续spin_lock功能实现):
#define SHARED (1<<16) /* shareable */
主核在启动应用时会调用rt_hw_secondary_cpu_up接口,BSP需要将其它核启动地址写入对应核的MAILBOX3寄存器中唤醒其它核(当前启动地址和主核保持一致:_reset):
void rt_hw_secondary_cpu_up(void)
{
rt_cpu_dcache_clean_flush();
rt_cpu_icache_flush();
int i;
int retry,val;
//for(i=1; i< RT_CPUS_NR; i++ )
for(i=RT_CPUS_NR-1; i>0; i-- )
{
rt_kprintf("boot cpu:%d
", i);
CORE_MAILBOX3_SET(i) = _reset;
__SEV();
__DSB();
__ISB();
retry = 10;
rt_thread_delay(RT_TICK_PER_SECOND/1000);
do
{
val = CORE_MAILBOX3_CLEAR(i);
if(val == 0){
rt_kprintf("start OK: CPU %d
",i);
break;
}
rt_thread_delay(RT_TICK_PER_SECOND);
retry --;
if(retry <= 0)
{
rt_kprintf("can't start for CPU %d
",i);
break;
}
}while(1);
}
__DSB();
__SEV();
}
其它核对各模式下栈指针,MMU,中断向量表进行设置,开启时钟,使能多核IPI通信中断,并开始调度。当前实现中MMU和中断向量表与主核保持一致:
rt_hw_timer_init();
rt_hw_vector_init();
COREMB_INTCTL(rt_hw_cpu_id()) = IPI_MAILBOX_INT_MASK; //使能IPI中断
rt_hw_spin_lock(&_cpus_lock);
rt_system_scheduler_start();
多核通信
多核通信接口为:rt_hw_ipi_send。本实现中主要通过MAILBOX机制利用MAILBOX0寄存器来实现多核通信:
void rt_hw_ipi_send(int ipi_vector, unsigned int cpu_mask)
{
__DSB();
if(cpu_mask&0x1)
{
IPI_MAILBOX_SET(0) = 1 << ipi_vector;
}
if(cpu_mask&0x2)
{
IPI_MAILBOX_SET(1) = 1 << ipi_vector;
}
if(cpu_mask&0x4)
{
IPI_MAILBOX_SET(2) = 1 << ipi_vector;
}
if(cpu_mask&0x8)
{
IPI_MAILBOX_SET(3) = 1 << ipi_vector;
}
__DSB();
}
在Raspi 3b中,如果使能了对应MAILBOX中断,当MAILBOX寄存器值不为0时,可以触发对应核的IRQ中断。
当前多核通信主要实现多核调度功能:对于未
绑定CPU的新线程开始执行或者未绑定CPU的线程从某个核调度出来时,会调用IPI接口通知其它核进行调度:该功能主要利用了MAILBOX中断机制,通过写其它核MAILBOX0寄存器触发对应核IRQ中断,进入MAILBOX中断处理,然后调用rt_scheduler_ipi_handler接口来触发对应核进行调度。
#define IRQ_ARM_MAILBOX 65
board.c:
rt_hw_ipi_handler_install(IRQ_ARM_MAILBOX, ipi_handler);
rt_hw_interrupt_umask(IRQ_ARM_MAILBOX);
trap.c:
uint32_t int_source = CORE_IRQSOURCE(cpu_id);
if (int_source & 0xf0)
{
/*it's a ipi interrupt*/
if (mailbox_data & 0x1){
/* clear mailbox */
IPI_MAILBOX_CLEAR(cpu_id) = mailbox_data;
isr_func = isr_table[IRQ_ARM_MAILBOX].handler;
#ifdef RT_USING_INTERRUPT_INFO
isr_table[IRQ_ARM_MAILBOX].counter++;
#endif
if (isr_func)
{
param = isr_table[IRQ_ARM_MAILBOX].param;
isr_func(IRQ_ARM_MAILBOX, param);
}
}
else
CORE_MAILBOX3_CLEAR(cpu_id) = mailbox_data;
}
多核时钟
多核时钟实现主要是对单核时钟实现rt_hw_timer_init以及rt_hw_timer_isr进行了修改,每个核都利用自身的时钟进行核和线程相关tick更新,进而实现时间片调度功能:
int rt_hw_timer_init()
{
#ifndef RT_USING_SMP
/* timer_clock = apb_clock/(pre_divider + 1) /
ARM_TIMER_PREDIV = (250 - 1);
ARM_TIMER_RELOAD = 0;
ARM_TIMER_LOAD = 0;
ARM_TIMER_IRQCLR = 0;
ARM_TIMER_CTRL = 0;
ARM_TIMER_RELOAD = 10000;
ARM_TIMER_LOAD = 10000;
/ 23-bit counter, enable interrupt, enable timer /
ARM_TIMER_CTRL = (1 << 1) | (1 << 5) | (1 << 7);
#else
__DSB();
cntfrq = read_cntfrq();
cntfrq = cntfrq/(RT_TICK_PER_SECOND10);
write_cntv_tval(cntfrq);
enable_cntv();
__DSB();
CORETIMER_INTCTL(rt_hw_cpu_id()) = 0x8;
#endif
rt_hw_interrupt_install(IRQ_ARM_TIMER, rt_hw_timer_isr, RT_NULL, "tick");
rt_hw_interrupt_umask(IRQ_ARM_TIMER);
return 0;
}
void rt_hw_timer_isr(int vector, void *parameter)
{
#ifndef RT_USING_SMP
ARM_TIMER_IRQCLR = 0;
#else
mask_cntv();
__DSB();
write_cntv_tval(cntfrq);
__DSB();
unmask_cntv();
__DSB();
#endif
rt_tick_increase();
}
多核中断上下文切换
该功能相关代码出自libcpu cortex-a代码,每个CPU核都需要单独维护自身的中断上下文、中断嵌套等信息。
#ifdef RT_USING_SMP
#define rt_interrupt_nest rt_cpu_self()->irq_nest
#else
extern volatile rt_uint8_t rt_interrupt_nest;
#endif
其它rt-thread SMP必要接口
rt_hw_local_irq_enable/rt_hw_local_irq_disable
这两个接口实现中断使能和静止,在rasp3b中和单核中断使能/禁止无区别:
#ifdef RT_USING_SMP
#define rt_hw_interrupt_disable rt_hw_local_irq_disable
#define rt_hw_interrupt_enable rt_hw_local_irq_enable
#endif
rt_hw_cpu_id
该接口获取当前CPU的cpu id:
int rt_hw_cpu_id(void)
{
int cpu_id;
asm volatile (
"mrc p15, 0, %0, c0, c0, 5"
:"=r"(cpu_id)
);
cpu_id &= 0xf;
return cpu_id;
};
rt_hw_spin_lock/rt_hw_spin_unlock
这两个接口实现多核间互斥锁功能,主要利用ARM的ldrex/strex机制来实现:
asm volatile(
"1: ldrex %0, [%3]
"
" add %1, %0, %4
"
" strex %2, %1, [%3]
"
" teq %2, #0
"
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << 16)
: "cc")
rt_hw_secondary_cpu_idle_exec
该接口主要实现其它(非主)核idle线程执行的功能:
void rt_hw_secondary_cpu_idle_exec(void)
{
__WFE();
}
原作者:droid_angell
更多回帖