点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
3.3 第一行之hard_local_irq_disable()
3.3.1 Linux中断的使能与屏蔽
3.3.1.1 中断使能与屏蔽的三重关卡
3.3.1.2 第一重关卡IMR
3.3.1.3 第二重关卡中断控制器的使能bit
3.3.1.4 第三重关卡CPU core异常掩码标志
3.3.2 IPIPE对Linux中断使能与屏蔽的改造
3.3.2.1 第一重关卡IMR的改造
3.3.2.2 第二重关卡中断控制器的使能bit的改造
3.3.2.3 第三重关卡的改造
3.3.2 IPIPE对Linux中断使能与屏蔽的改造
3.3.2.1 第一重关卡IMR的改造
如上文分析,设备的Interrupt Mask Register(IMR)是由各个设备的驱动自行控制的,IPIPE patch不会做任何修改。
如果想把某个设备交给Head域的实时内核(例如Cobalt)来使用,那么需要使用RTDM来重写设备的驱动程序。内核打上了Xenomai3 patch之后,在drivers/xenomai/目录下有很多在RTDM框架下写好的驱动。以/drivers/xenomai/spi/spi-bcm2835.c为例,它依然是直接读写IMR寄存器来控制自身的中断。
static int do_transfer_irq(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_bcm2835 *spim = to_master_bcm2835(slave);
int ret;
u32 cs;
cs = bcm2835_rd(spim, BCM2835_SPI_CS);
……
/* Enable interrupts last, wait for transfer completion. */
cs |= BCM2835_SPI_CS_INTR | BCM2835_SPI_CS_INTD;
bcm2835_wr(spim, BCM2835_SPI_CS, cs);
……
return 0;
}
3.3.2.2 第二重关卡中断控制器的使能bit的改造
如上文分析,默认情况下,disable_irq工作在UNLAZY模式。但是IPIPE是无法在UNLAZY模式下工作的,必须设置为DISABLE_UNLAZY模式。这个修正是通过__fixup_irq_handler函数来完成的。
kernel/irq/chip.c:
irq_flow_handler_t
__fixup_irq_handler(struct irq_desc *desc, irq_flow_handler_t handle, int is_chained)
{
……
/*
* We don't cope well with lazy disabling simply because we
* neither track nor update the descriptor state bits, which
* is badly wrong.
*/
irq_settings_clr_and_set(desc, 0, _IRQ_DISABLE_UNLAZY);
……
return handle;
}
那么__fixup_irq_handler函数是在什么时机被调用呢?是在中断分配过程中,对中断描述符irq_desc直接进行修正。具体内核的中断子系统初始化的过程,以后再详细分析。
3.3.2.3 第三重关卡的改造
如第一章的《1.2.1.2 虚拟中断标志Virtual interrupt flag》所阐述,为了保证头域中的out-of-band代码的实时性,根域中的in-band代码一定不能关闭CPU的物理中断。所以,运行在root域(根域)中的Linux只能管理和使用虚拟中断标志。当根域中的Linux调用local_irq_save/local_irq_disable/spin_lock_irqsave等函数关闭中断时,实际上是对虚拟中断标志进行关闭操作,并没有真正的关闭物理中断标志。此时,头域中的Cobalt依然能够接收物理中断,保证实时性。
具体是怎么做到的呢?以local_irq_disable为例说明一下。
local_irq_disable它最终调用的是arch_local_irq_disable,而arch_local_irq_disable定义在头文件<asm/irqflags.h>。在<asm/irqflags.h>,IPIPE patch为它多增加了一个头文件<asm/ipipe_hwirq.h>,正是这个新的头文件抢先定义了local_irq_disable最终调用的arch_local_irq_disable,把原先对DAIF寄存器的操作变成了对虚拟中断标志的操作ipipe_stall_root<kernel/ipipe/core.c>。来看一下它的代码。
它里面最核心的是第395行__set_bit(IPIPE_STALL_FLAG, &__ipipe_root_status),将虚拟中断标志位进行置位操作(stall操作),即关闭虚拟中断。
既然Linux不再操作物理中断,那IPIPE是如何接手的呢?回顾一下咱们第3.3章节的主题,大家就能猜出来了。是的,IPIPE重新定义一套函数来操作物理中断标志位DAIF,前缀是hard_local_irq_xxx,同样是两对函数。
这两对函数定义在<arch/arm64/include/asm/hwirq.h>,为简化分析,默认没有定义CONFIG_IPIPE_TRACE_IRQSOFF。
arch/arm64/include/asm/hwirq.h:
#define hard_local_irq_disable hard_local_irq_disable_notrace
#define hard_local_irq_enable hard_local_irq_enable_notrace
#define hard_local_irq_save hard_local_irq_save_notrace
#define hard_local_irq_restore hard_local_irq_restore_notrace
arch/arm64/include/asm/hwirq.h:
static inline void hard_local_irq_disable_notrace(void)
{
__asm__ __volatile__("msr daifset, #2" : : : "memory", "cc");
}
static inline void hard_local_irq_enable_notrace(void)
{
__asm__ __volatile__("msr daifclr, #2" : : : "memory", "cc");
}
#define hard_local_irq_restore_notrace(x) \
__asm__ __volatile__( \
"msr daif, %0" \
: \
: "r" (x) \
: "memory", "cc")
static inline unsigned long hard_local_irq_save_notrace(void)
{
unsigned long res;
__asm__ __volatile__(
"mrs %0, daif\n"
"msr daifset, #2"
: "=r" (res) : : "memory", "cc");
return res;
}
最后,呼应一下第3.3章节的主题哈。原先start_kernel调用local_irq_disable是为了屏蔽物理中断的。因为local_irq_disable已经变成了对虚拟中断标志位的操作,所以需要替换为IPIPE自己定义的hard_local_irq_disable。
说起来挺简单的,但是追寻背后的细节还是花费了不少功夫。接下来分析下一行初始化代码。