在RK3506平台上进行AMP核间通信测试时,RTT(RT-Thread)端出现了一个棘手的错误:
code复制Function[rt_mq_recv] shall not be used in ISR
(0) assertion failed at function:rt_mq_recv, line number:3085
这个错误表明系统错误地认为当前线程处于中断服务例程(ISR)中,而实际上我们是在正常的线程上下文中调用rt_mq_recv函数。让我们先看看出问题的代码段:
c复制ret = rpmsg_queue_recv(info->instance, remote_queue,
(uint32_t *)&master_ept_id,
g_rx_buffer,
sizeof(g_rx_buffer),
&msg_len,
RL_BLOCK);
if (ret == RL_SUCCESS) {
/* Echo back the message (Ping-Pong) */
rpmsg_lite_send(info->instance, info->ept, master_ept_id, g_rx_buffer, msg_len, RL_BLOCK);
}
调用链非常清晰:rpmsg_queue_recv → env_get_queue → rt_mq_recv。在rt_mq_recv中,系统通过RT_DEBUG_NOT_IN_INTERRUPT宏进行了上下文检查:
c复制#define RT_DEBUG_NOT_IN_INTERRUPT \
do { \
rt_base_t level; \
level = rt_hw_interrupt_disable(); \
if (rt_interrupt_get_nest() != 0) { \
rt_kprintf("Function[%s] shall not be used in ISR\n", __FUNCTION__); \
RT_ASSERT(0) \
} \
rt_hw_interrupt_enable(level); \
} while (0)
这个检查的核心逻辑是:如果中断嵌套计数(rt_interrupt_get_nest())不为0,就认为当前处于中断上下文。问题在于,我们的代码明明是在线程中运行,系统却错误地认为我们在中断中。
首先怀疑的是栈溢出导致寄存器值被破坏。我们检查了任务栈使用情况:
c复制rt_kprintf("Task stack size: %d, used: %d\n",
task->stack_size,
task->stack_size - rt_thread_self()->stack_size);
结果显示栈使用正常,没有溢出迹象。这个可能性被排除了。
我们在调用rpmsg_queue_recv前添加了中断状态检查:
c复制if (rt_interrupt_get_nest() > 0) {
rt_kprintf("rpmsgd WARNING: ISR Nest detected (%d) before recv! Aborting recv.\n",
rt_interrupt_get_nest());
continue;
}
输出结果令人困惑:
code复制rpmsgd WARNING: ISR Nest detected (1) before recv! Aborting recv.
rpmsgd WARNING: ISR Nest detected (1) before recv! Aborting recv.
...
rpmsFunction[sys_mutex_lock] shall not be used in ISR
(0) assertion failed at function:sys_mutex_lock, line number:258
这表明中断嵌套计数确实被错误地设置为1,而且后续还出现了sys_mutex_lock的类似错误。
通过grep查找sys_mutex_lock的使用情况:
bash复制grep -nr sys_mutex_lock
发现主要与LWIP网络组件相关。我们尝试移除LWIP组件后,sys_mutex_lock的错误消失了,但中断嵌套的问题依然存在:
code复制rpmsgd WARNING: ISR Nest detected (1) before recv! Aborting recv.
rpmsgd WARNING: ISR Nest detected (1) before recv! Aborting recv.
...
这说明LWIP是被影响者而非根源,我们需要继续深入。
我们在中断入口函数中添加了调试信息:
c复制void rt_hw_trap_irq(void) {
int int_ack = rt_hw_interrupt_get_irq();
int ir = int_ack & GIC_ACK_INTID_MASK;
rt_kprintf("IRQ:%d N:%d\n", ir, rt_interrupt_get_nest());
...
}
关键输出如下:
code复制IRQ:176 N:1
IRQ:30 N:2
rpmsgd WARNIRQ:30 N:2
ING: ISR NeIRQ:30 N:2
st detectedIRQ:30 N:2
(1) beforeIRQ:30 N:2
recv! AborIRQ:30 N:2
整理关键中断序列:
IRQ:176 N:1(Mailbox中断)触发IRQ:30(定时器中断)触发,嵌套计数变为2这表明Mailbox中断后,系统的中断嵌套计数没有正确清零,仍然保持为1,导致后续的定时器中断进入时计数变为2。
问题出在mailbox的中断处理函数中:
c复制static void rpmsg_mbox_isr(int irqn, void *param) {
HAL_MBOX_IrqHandler(irqn, (struct MBOX_REG *)param);
HAL_GIC_EndOfInterrupt(irqn); // 问题核心
}
RT-Thread的中断处理机制如下:
rt_hw_trap_irq调用用户ISR回调rt_hw_interrupt_ack(即GIC的EOI操作)c复制void rt_hw_interrupt_ack(int vector) {
HAL_GIC_EndOfInterrupt(vector);
}
问题在于rpmsg_mbox_isr手动调用了HAL_GIC_EndOfInterrupt,导致:
rt_interrupt_leave减少嵌套计数)时,CPU已经可以响应新的同级或低级中断rt_interrupt_nest计数的维护逻辑正确的做法是移除用户回调中的冗余EOI操作,完全交由内核统一处理:
c复制static void rpmsg_mbox_isr(int irqn, void *param) {
HAL_MBOX_IrqHandler(irqn, (struct MBOX_REG *)param);
// 移除 HAL_GIC_EndOfInterrupt(irqn);
}
修改后重新测试,关键观察点:
rpmsg_queue_recv是否能正常执行测试结果确认问题已解决,系统运行正常。
RT-Thread在Cortex-A架构上的完整中断处理流程:
rt_hw_trap_irq中断嵌套计数管理:
rt_interrupt_enter()增加计数rt_interrupt_leave()减少计数EOI操作时机:
中断上下文限制:
中断问题排查checklist:
实用调试手段:
rt_interrupt_get_nest()实时监控中断处理函数编写规范:
核间通信实现要点:
AMP系统注意事项:
RPMSG(Remote Processor Messaging)是核间通信的常用框架,主要组件:
在RK3506上的特殊考虑:
中断上下文有严格限制:
使用RT-Thread提供的API:
c复制rt_interrupt_get_nest() > 0 // 中断上下文
rt_thread_self() != RT_NULL // 线程上下文
通过这次调试经历,我深刻理解了RT-Thread中断机制的重要性。在嵌入式开发中,特别是涉及多核通信的场景,对中断处理的精确控制至关重要。以下是我的几点实践建议:
在RK3506这类多核平台上开发时,建议先充分验证核间通信基础功能,再构建上层应用。同时,保持对系统状态的监控能力,可以大大缩短类似问题的排查时间。