Featured image of post Zephyr 多级中断

Zephyr 多级中断

采用一种很巧妙的方式实现多级中断,为中断设计提供参考。

理解中断子系统有利于我们更好的理解操作系统的工作原理,每个OS实现中断子系统的方式各不相同。 Linux一般采用动态注册的方式可以在系统运行过程中把中断Handler注册给OS,而Zephyr系统为了减小image大小采用静态注册的方式,在编译过程中把所有IRQ_CONNECT解析为isr table。

一级中断

如果系统只需要一级中断,生成的isr table(中断向量表)看起来比较简单,编译后生成的路径在zephyr/isr_tables.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct _isr_table_entry {
    void *arg;
    void (*isr)(void *);
};

struct _isr_table_entry __sw_isr_table _sw_isr_table[160] = {
    {(void *)0x1979e0, (void *)0x114fc1},
    {(void *)0x1979ec, (void *)0x114fc1},
    {(void *)0x2, (void *)0x109e01},
    {(void *)0x3, (void *)0x109e01},
    {(void *)0x4, (void *)0x109e01},
    {(void *)0x5, (void *)0x109e01}
};

可以看到,在32位系统中isr table的每一个entry占用8 bytes,前4 bytes为参数,后4 bytes为handler。isr table的大小由CONFIG_NUM_IRQS决定,可以根据需要调整。

Zephyr系统启动时会把isr table地址设置给CPU的VTOR寄存器(以CM4为例),如果有中断触发,CPU可以根据中断号进入相应的中断服务程序处理。

多级中断

CPU的中断控制器能处理的中断数量往往有限制,ARM Cortex-M的中断控制器一般能外接64个IRQ和64个FIQ,对于外设较多的芯片可能会不够用。这时需要级连额外的中断控制器来扩展中断数量,这样新增的中断控制器(INTC)就称为2级中断控制器。如果一个2级中断控制器还不能满足系统需求,还可以使用多个2级中断控制器或者增加3级中断、4级中断。当然多级因为需要软件分发中断,级数越多效率也会越低。

相关宏定义

Zephyr系统的多级中断因为是采用静态生成的方式,实现起来会稍微复杂一点。首先,我们需要理解几个关于多级中断宏的含义:

  • CONFIG_MULTI_LEVEL_INTERRUPTS 多级中断总开关
  • CONFIG_2ND_LEVEL_INTERRUPTS 2级中断开关
  • CONFIG_3RD_LEVEL_INTERRUPTS 3级中断开关
  • CONFIG_2ND_LVL_ISR_TBL_OFFSET 2级中断向量表的偏移,即1级中断的数量,因为2级中断向量表是放在1级中断向量表的后面
  • CONFIG_NUM_2ND_LEVEL_AGGREGATORS 2级中断控制器的数量
  • CONFIG_MAX_IRQ_PER_AGGREGATOR 多级中断控制器上最多有多少个中断项,如果每个中断控制器的中断数量不一样就取最大值
  • CONFIG_2ND_LVL_INTR_00_OFFSET 第一个2级中断控制器的中断号
  • CONFIG_2ND_LVL_INTR_01_OFFSET 第二个2级中断控制器的中断号

这些宏用于配置多级中断,编译脚本会根据以上的宏生成中断向量表。

定义中断号

如果是一级中断,调用IRQ_CONNECT接口时传入的IRQ NUM即是相应的实际中断号。多级中断涉及到每一级中断的中断号,Zephyr系统采用每一级中断号占用1个byte的方式将多级中断号定义在一个DWORD中。

下图是官方文档的示例,用于展示如何定义中断号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
         9             2   0
   _ _ _ _ _ _ _ _ _ _ _ _ _          (LEVEL 1) 
         |         |   |
  5      |         A   |
_ _ _ _ _|_ _         _|_ _ _ _ _ _   (LEVEL 2)
  |   |                       |
  |   C                       B
 _|_ _ _ _ _ _                        (LEVEL 3)
         |
         D

根据示例,ABCD四个设备定义的中断号如下,可以看到除了第一级中断的起始数是从0开始外,从第二级开始所有中断号都是从1开始编号,因为0用于代表没有设备。

1
2
3
4
A -> 0x00000004
B -> 0x00000302
C -> 0x00000409
D -> 0x00030609

生成中断向量表

现假设上述宏的定义如下

1
2
3
4
5
6
7
8
#define CONFIG_MULTI_LEVEL_INTERRUPTS 1
#define CONFIG_2ND_LEVEL_INTERRUPTS 1
#define CONFIG_3ND_LEVEL_INTERRUPTS 1
#define CONFIG_2ND_LVL_ISR_TBL_OFFSET 64
#define CONFIG_NUM_2ND_LEVEL_AGGREGATORS 2
#define CONFIG_MAX_IRQ_PER_AGGREGATOR 32
#define CONFIG_2ND_LVL_INTR_00_OFFSET 2
#define CONFIG_2ND_LVL_INTR_01_OFFSET 9

系统将生成中断向量表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
isr_table
     +---------+----------+
     |  isr0   |          |
     |  isr1   |          |
     |  isr2   |          |
     |  isr3   | LEVEL 1  |
=> A |  isr4   |          |
     |   ...   |          |
     |  isr63  |          |
     +---------+----------+
     |  isr64  |          |
     |  isr65  |          |
=> B |  isr66  | LEVEL 2  |
     |   ...   |          |
     |  isr95  |          |
     +---------+----------+
     |  isr96  |          |
     |  isr97  |          |
     |  isr98  |          |
=> C |  isr99  | LEVEL 2  |
     |   ...   |          |
     |  isr127 |          |
     +---------+----------+
     |  isr128 |          |
     |  isr129 |          |
=> D |  isr130 | LEVEL 3  |
     |   ...   |          |
     |  isr159 |          |
     +---------+----------+

中断注册

定义了中断号后,注册多级中断和注册普通中断一样,例如我们要注册上面设备C的中断:

1
2
IRQ_CONNECT(0x00000409, DEVICE_C_IRQ_PRIO, uwp_ictl_isr, 0, 0);
irq_enable(0x00000409);

这里只是示例,所以直接写的数字,在实际代码中,硬件相关的参数应该都定义在device tree,再根据生成的宏定义来写代码。

中断处理

多级中断服务程序被触发后,需要根据中断状态寄存器对中断进行分发。分发函数需要当前的中断状态以及当前中断控制器在isr_table的起始位置,起始位置可以根据上图获取到,分发时按中断状态的每一位依次分发,保证每一个中断都可以被处理到。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static ALWAYS_INLINE void uwp_dispatch_child_isrs(u32_t intr_status,
                              u32_t isr_base_offset)
{
    u32_t intr_bitpos, intr_offset;

    /* Dispatch lower level ISRs depending upon the bit set */
    while (intr_status) {
        intr_bitpos = find_lsb_set(intr_status) - 1;
        intr_status &= ~(1 << intr_bitpos);
        intr_offset = isr_base_offset + intr_bitpos;
        _sw_isr_table[intr_offset].isr(
            _sw_isr_table[intr_offset].arg);
    }
}

static void uwp_ictl_isr(void *arg)
{
    struct device *dev = arg;

    struct uwp_ictl_config *config = DEV_CFG(dev);

    volatile struct uwp_intc *intc = INTC_STRUCT(dev);

    uwp_dispatch_child_isrs(uwp_intc_status(intc),
            config->isr_table_offset);
}

结束

通过对Zephyr系统多级中断流程的分析,不难看出其实Zephyr系统已经封闭的大部份的工作,只需要理清系统中中断控制器的拓扑结构,实现少量代码即可控制多级中断。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计