前言
之前在使用Linux做开发时,无论在做验证还是后续debug的过程中,经常需要查看系统中断响应的记录,用以辅助验证、开发或者debug的过程。最近由于业务的需求,需要使用RTT作为开发的对象,在移植了标准版后,系统启动到MSH控制台,发现并没有查看中断相关的命令。于是自行做了list_interrupt命令,用来查询系统中断,可以查询当前系统已经响应过的中断以及中断所响应的次数。具体的过程如下,为大家做相关开发提供参考。以开源工程中thead-smart单板配置为例。
中断响应的过程
要想记录中断,首先需要搞清楚CPU中断响应的过程。无论是ARM系列还是本文为例子的平头哥Risc-V系列CPU,中断响应的过程其实大同小异。首先会定义中断向量表,一般在start.S文件中定义,中断向量表正和他的名字一样,其本质就是一张表格,从代码的角度来看,是一个数组。数组的偏移量为中断号,比如数组的第16个成员,代表是硬件中断号为16的中断,第16个成员的内容,存放的是16号中断对应的Handler函数。也就是说当CPU进入到16号中断时,就会跳入中断向量表对应的16号中断的Handler函数中。
thread-smart的中断向量表如下
__Vectors:
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long PendSV_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long SysTick_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
/* External interrupts */
.long USART_IRQHandler
.long Default_Handler
.long TIM0_IRQHandler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.long Default_Handler
.size __Vectors,.-__Vectors
可以看到,thead-master的异常向量表中,除了个别异常向量以外,其余异常向量都指向了Default_Handler。这里时开发者针对中断做了处理,无论时什么异常中断进来,都先进入Default_Handler中,在Default_Handler中会对中断再做分类处理,看是正常的中断还是cpu异常或者是高等级的中断,然后再做分派。整个Default_Handler的处理流程比较长,以下截取关键点进行流程分析。
Default_Handler:
/* Check for nmi /
addi sp, sp, -8
sw t0, 0x0(sp)
sw t1, 0x4(sp)
csrr t0, mcause //mcause中记录了本次异常是否为中断,如果是中断,会包含中断号。非中断会记录异常原因
andi t0, t0, 0x3FF //获取中断号记录在t0中
li t1, 24 //thead中将24号中断规定为nmi中断,为高等级中断。
beq t0, t1, .NMI_Handler //如果是nmi中断,跳入nmi中断处理流程。
lw t0, 0x0(sp)
lw t1, 0x4(sp)
addi sp, sp, 8
j trap //跳入trap流程
trap:
/ Check for interrupt */
j .
addi sp, sp, -4
sw t0, 0x0(sp)
csrr t0, mcause //判断本次异常原因标志,若为中断,则该值是中断号
blt t0, x0, .Lirq //判断异常为中断,则跳入Lirq,否则跳入trap_c,标志cpu异常
addi sp, sp, 4
la t0, g_trap_sp
addi t0, t0, -132
.... .... .... .... .... .....
csrr a0, mepc
sw a0, 124(t0)
csrr a0, mstatus
sw a0, 128(t0)
mv a0, t0
lw t0, -4(sp)
mv sp, a0
sw t0, 16(sp)
jal trap_c
.Lirq:
lw t0, 0x0(sp)
addi sp, sp, 4
j Default_IRQHandler //跳入Default_IRQHandler处理流程
Default_IRQHandler:
ipush
#ifdef __riscv_flen
csrr t1, mstatus
srli t1, t1, 13
andi t1, t1, 0x3
la t3, irq_mstatus_fs_flag
sw t1, (t3)
li t0, 0x3
bne t1, t0, .F_RegNotSave1
.... .... ..... .....
csrr t1, mcause //再次获取中断号
andi t1, t1, 0x3FF
slli t1, t1, 2
la t0, g_irqvector //g_irqvector指向中断向量表。将其地址存放于t0中
add t0, t0, t1 //t1为当前中断相对于中断向量表的偏移,即第几个成员。t0最终为该中断对应的Handler
lw t2, (t0) //将中断对应的Handler地址放于t2中
jalr t2 //跳至Handler中
li t0, MSTATUS_PRV1
csrs mstatus, t0
记录中断
由以上对thead-master中断响应的分析,决定在cpu跳入到对应中断前将其记录,这样做流程上面清晰且能确保响应过的中断全部都会被记录。跳入中断Handler的流程在Default_IRQHandler中,具体的代码细节如下:
csrr t1, mcause
andi t1, t1, 0x3FF
slli t1, t1, 2
la t0, g_irqvector
add t0, t0, t1
lw t2, (t0)
jalr t2
其中,g_irqvector为指向中断向量表的一个数组。记录中断可借鉴原生代码处理中断向量表的这个方法。定义名为g_vector_list的一个数组,数组的成员偏移即为中断号,比如g_vector_list[16]数组成员代表第16号中断,对应成员的值为中断响应的次数。记录的基本原理为每当一个中断执行完毕对应的Handler返回时,将g_vector_list数组对应的数组成员量加1即可。g_vector_list的定义如下:
int (*g_vector_list[255])(void);
记录流程如下:
csrr t1, mcause
andi t1, t1, 0x3FF
slli t1, t1, 2
mv t5, t1 //中断号存在t5中
la t0, g_irqvector
add t0, t0, t1
lw t2, (t0)
jalr t2 //跳入Handler中
li t0, MSTATUS_PRV1 //Handler执行完毕返回
csrs mstatus, t0
/** 记录中断 **/
la t4, g_vector_list //将g_vector_list的地址存放到t4
lrw t4,t4,t5,0 //获取该中断对应在g_vector_list中成员的值,存放于t4
addi t6,t4,1 //对应的成员内容加1,代表记录本次中断,记录于t6
la t4, g_vector_list
srw t6,t4,t5,0 //更新到g_vector_list中,完成记录
由以上流程,g_vector_list中就能存储各个中断在系统的的响应次数。
添加MSH指令
完成中断的记录流程后,剩下的就是添加MSH指令。选择将MSH指令加入到cmd.c中,与其他的指令放于同一文件中。代码如下:
long list_interrupt(void)
{
int maxlen;
int i = 0;
const char item_title = "irq num";
maxlen = RT_NAME_MAX;
rt_kprintf("%-.s active-times name\n", maxlen, item_title);//打印title的格式 中断号/中断对应响应次数/中断名称(未实现)
object_split(maxlen);
rt_kprintf(" ---------- --------------\n");
for(i=0; i<=255;i++) { //依次打印出系统已响应过的中断
if(g_vector_list[i]) {
rt_kprintf("%d %d name\n",
i, g_vector_list[i]);
rt_kprintf("\n");
}
}
return 0;
}
MSH_CMD_EXPORT(list_interrupt, list INTERRUPT in system);//声明为MSH指令
执行结果
本文的执行结果如下图:
图1:MSH指令样式
图2:执行结果样式
总结
目前该方法只是一个简单的demo,很多细节还没有实现,包括记录中断的name,响应中断的cpu编号等等。以此抛砖引玉。
原作者:小鱼儿