正文

一、基础知识

下面简单介绍:


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux


从图中可以看到,中断主要分为两级,我们可以理解为是两个中断控制器的嵌套或者级联。S3C2440总共支持60个中断源,包含了主中断源和子中断源。

基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_02

寄存器功能介绍:

1、SRCPND 地址: 0x4A000000 功能:每一位代表一个主中断,置1表示有对应的主中断请求,对应位写入1可以清除中断

2、INTMOD 地址: 0x4A000004 功能:设置对应的主中断为IRQ还是FIQ, 置1表示FIQ

3、INTMSK 地址: 0x4A000008 功能:置1表示对应的主中断被屏蔽(不会影响SRCPND)

4、INTPND 地址: 0x4A000010 功能:表示对应的主中断被request,只可能有一位被置位,写入1可以清除中断

5、INTOFFSET 地址:0x4A000014 功能:存放的是发生中断请求的主中断号

6、SUBSRCPND 地址:0x4A000018 功能:每一位代表一个子中断,置一表示对应子中断请求,对应位写入1清除子中断请求

7、INTSUBMSK 地址:0x4A00001C 功能:置1表示对应的子中断被屏蔽

32个主中断:


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_03


图一


15个子中断:


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_TQ2440_04


图二


 


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_05


图三


外部中断:

EINT0~7对应的GPIO是GPF0~7

EINT8~23对应的GPIO是GPG0~15

二、设备树

1、中断控制器



intc:interrupt-controller@4a000000 {
compatible = "samsung,s3c2410-irq";
reg = <0x4a000000 0x100>;
interrupt-controller;
#interrupt-cells = <4>;
};


2、引用




serial@50000000 {
compatible = "samsung,s3c2440-uart";
reg = <0x50000000 0x4000>;
interrupts = <1 28 0 4>, <1 28 1 4>;
status = "okay";
clock-names = "uart";
clocks = <&clock PCLK_UART0>;
pinctrl-names = "default";
pinctrl-0 = <0x3>;
};

i2c:i2c@54000000 {
compatible = "samsung,s3c2410-i2c";
reg = <0x54000000 0x100>;
interrupts = <0 0 27 3>;
#address-cells = <1>;
#size-cells = <0>;
};


上面的serial和i2c设备比较典型,一个引用的是主中断,另一个引用的是子中断。从中断控制器的#interrupt-cells属性知道,要描述一个中断需要四个参数,每一个参数的含义需要由中断控制器的驱动来解释,具体是有中断控制器的irq_domain_ops中的xlate来解释,对于s3c2440就是drivers/irqchip/irq-s3c24xx.c中的s3c24xx_irq_xlate_of。



基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_06基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_07


1 /* Translate our of irq notation
2 * format: <ctrl_num ctrl_irq parent_irq type>
3 */
4 static int s3c24xx_irq_xlate_of(struct irq_domain *d, struct device_node *n,
5 const u32 *intspec, unsigned int intsize,
6 irq_hw_number_t *out_hwirq, unsigned int *out_type)
7 {
8 struct s3c_irq_intc *intc;
9 struct s3c_irq_intc *parent_intc;
10 struct s3c_irq_data *irq_data;
11 struct s3c_irq_data *parent_irq_data;
12 int irqno;
13
14 if (WARN_ON(intsize < 4)) //如果参数个数不能小于4
15 return -EINVAL;
16 // 从这里知道,第一个参数不能大于2,只能是0和1, 0表示主中断,2表示子中断
17 if (intspec[0] > 2 || !s3c_intc[intspec[0]]) {
18 pr_err("controller number %d invalid\n", intspec[0]);
19 return -EINVAL;
20 }
21 // s3c_intc[0]表示主中断控制器,s3c_intc[1]表示子中断控制器
22 intc = s3c_intc[intspec[0]];
23
24 // 第三个参数表示的是硬件中断号,从这里知道,主中断的硬件中断是0~31,子中断的硬件中断是32及其以上
25 *out_hwirq = intspec[0] * 32 + intspec[2];
26 // 第四个参数表示的是中断类型,可以查看IRQ_TYPE_SENSE_MASK定义,就知道含义:
27 // 1表示上升沿触发,2表示下降沿触发,3表示双边沿触发,4表示高电平触发,8表示低电平触发,12表示高低电平触发
28 *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK;
29 // 如果是主中断,则intc->parent为NULL, 否则非空
30 parent_intc = intc->parent;
31 if (parent_intc) { // 子中断
32 irq_data = &intc->irqs[intspec[2]];
33 // 对于子中断,第二个参数才有意义,表示该子中断所隶属的主中断的硬件中断号
34 irq_data->parent_irq = intspec[1];
35 parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];
36 parent_irq_data->sub_intc = intc;
37 // sub_bits中记录该主中断下的子中断被被使用的情况
38 parent_irq_data->sub_bits |= (1UL << intspec[2]);
39 // 将主中断号映射成虚拟中断号
40 /* parent_intc is always s3c_intc[0], so no offset */
41 irqno = irq_create_mapping(parent_intc->domain, intspec[1]);
42 if (irqno < 0) {
43 pr_err("irq: could not map parent interrupt\n");
44 return irqno;
45 }
46 // 这里设置irqno对应的irq_desc的handle_irq为s3c_irq_demux
47 // 从函数名称中就可以看出, 这个函数会再次进行检测该主中断的哪个子中断被请求
48 irq_set_chained_handler(irqno, s3c_irq_demux);
49 }
50
51 return 0;
52 }

View Code

从这里我们知道,interrupts属性中的四个参数中的含义:


以串口的<1 28 0 4>和i2c的<0 0 27 3>为例:


<0 0 27 3>:第1个0表示的是主中断,第2个数字0没啥用,第3个数字27表示硬件中断号,第4个数字3表示双边沿触发。从上面的图一中可以看到I2C的硬件中断号是27


<1 28 0 4>:第1个数字1表示的是子中断,第2个数字28表示的是uart0的主中断,从上面的图一中可以看到uart0的主中断是28,第3个数字0表示的是子中断的硬件中断号,也就是图二中INT_RXD0的位号0, 第4个数字4表示的是高电平触发


三、中断控制器驱动


中断控制器驱动:drivers/irqchip/irq-s3c24xx.c


初始化入口:



基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_06基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_07


1 static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = {
2 { // 主中断
3 .name = "intc",
4 .offset = 0,
5 }, { // 子中断
6 .name = "subintc",
7 .offset = 0x18, // 寄存器地址偏移
8 .parent = &s3c_intc[0],
9 }
10 };
11
12 int __init s3c2410_init_intc_of(struct device_node *np,
13 struct device_node *interrupt_parent)
14 {
15 return s3c_init_intc_of(np, interrupt_parent,
16 s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
17 }
18 IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

View Code

在内核启动的时候,函数s3c2410_init_intc_of会被调用。


下面我们分析函数s3c_init_intc_of:



基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_06基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_07


1 static int __init s3c_init_intc_of(struct device_node *np,
2 struct device_node *interrupt_parent,
3 struct s3c24xx_irq_of_ctrl *s3c_ctrl, int num_ctrl)
4 {
5 struct s3c_irq_intc *intc;
6 struct s3c24xx_irq_of_ctrl *ctrl;
7 struct irq_domain *domain;
8 void __iomem *reg_base;
9 int i;
10
11 // 对中断控制器的reg属性所表示地址空间进行映射
12 reg_base = of_iomap(np, 0);
13
14 // 为该中断控制器创建irq_domain,一共支持64个中断,对应的irq_domain_ops是s3c24xx_irq_ops_of
15 domain = irq_domain_add_linear(np, num_ctrl * 32,
16 &s3c24xx_irq_ops_of, NULL);
17 // 依次处理两个中断控制器
18 for (i = 0; i < num_ctrl; i++) {
19 ctrl = &s3c_ctrl[i];
20
21 pr_debug("irq: found controller %s\n", ctrl->name);
22 // 主和子中断控制器各分配一个s3c_irq_intc
23 intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);
24 // 主和子中断控制器共享一个irq_domain
25 intc->domain = domain;
26 // 为主和子中断控制器下的每个中断各分配一个s3c_irq_data结构体
27 intc->irqs = kzalloc(sizeof(struct s3c_irq_data) * 32,
28 GFP_KERNEL);
29
30 if (ctrl->parent) { // 子中断控制器
31 intc->reg_pending = reg_base + ctrl->offset; // SUBSRCPND
32 intc->reg_mask = reg_base + ctrl->offset + 0x4; // INTSUBMSK
33
34 // 由于先处理的是s3c2410_ctrl[0],所以在处理子中断的时候,*(ctrl->parent)非空
35 // 是主中断控制器对应的s3c_irq_intc
36 if (*(ctrl->parent)) {
37 intc->parent = *(ctrl->parent);
38 } else {
39 pr_warn("irq: parent of %s missing\n",
40 ctrl->name);
41 kfree(intc->irqs);
42 kfree(intc);
43 continue;
44 }
45 } else { // 主中断控制器
46 intc->reg_pending = reg_base + ctrl->offset; //SRCPND
47 intc->reg_mask = reg_base + ctrl->offset + 0x08; //INTMSK
48 intc->reg_intpnd = reg_base + ctrl->offset + 0x10; //INTPND
49 }
50
51 s3c24xx_clear_intc(intc); // 清除中断
52 s3c_intc[i] = intc;
53 }
54 // 将handle_arch_irq设置为s3c24xx_handle_irq, 这样在发生中断后,会从汇编entry-armv.S中
55 // 调转到s3c24xx_handle_irq
56 set_handle_irq(s3c24xx_handle_irq);
57
58 return 0;
59 }

View Code

中断控制启动的初始化,结构体s3c24xx_irq_ops_of中的map和xlate还没有分析,放到下面分析。


四、interrupts属性的解析


在内核启动期间解析设备树,将device_node转换为platform_device的时候会处理节点中的interrupts属性,将其转换为irq resource,同时在所属的irq domain中建立起hwirq到virq的映射,下面列出主要的函数调用,期间就会调用上面中断控制器的irq_domain的s3c24xx_irq_ops_of中的xlate和map:


of_platform_default_populate_init


    ---> of_platform_default_populate


---> of_platform_populate


            ---> of_platform_bus_create


                ---> of_platform_device_create_pdata


                    ---> of_device_alloc


                        ---> of_irq_to_resource_table


                            ---> of_irq_to_resource


                                ---> irq_of_parse_and_map

                                    ---> of_irq_parse_one

                                    ---> irq_create_of_mapping

                                        ---> irq_create_fwspec_mapping

                                            ---> irq_domain_translate // 解析参数

                                                ---> s3c24xx_irq_xlate_of

                                            ---> irq_create_mapping // 创建hwirq到virq的映射

                                                ---> irq_domain_associate

                                                    ---> s3c24xx_irq_map_of

详细分析请参考:​​基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)​

这里我们只看看s3c24xx_irq_map_of。

在调用这个函数时,已经创建号hwirq对应的virq了:


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_06基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_07


1 static int s3c24xx_irq_map_of(struct irq_domain *h, unsigned int virq,
2 irq_hw_number_t hw)
3 {
4 unsigned int ctrl_num = hw / 32; //判断该hwirq属于主还是子中断控制器
5 unsigned int intc_hw = hw % 32; // 得到跟寄存器对应的硬件中断号
6 struct s3c_irq_intc *intc = s3c_intc[ctrl_num]; // 获得中断控制器对应的结构体
7 struct s3c_irq_intc *parent_intc = intc->parent;
8 struct s3c_irq_data *irq_data = &intc->irqs[intc_hw]; // 每个中断都会有一个s3c_irq_data结构体
9
10 /* attach controller pointer to irq_data */
11 irq_data->intc = intc;
12 irq_data->offset = intc_hw; //跟硬件寄存器对应的硬件中断号,也就是对应的位号
13
14 // 将virq对应的irq_desc的handle_irq初始化为handle_edge_irq,
15 // 下面在s3c_irq_type中会根据中断触发类型再次修改
16 if (!parent_intc) // 主中断控制器
17 irq_set_chip_and_handler(virq, &s3c_irq_chip, handle_edge_irq);
18 else // 子中断控制器
19 irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
20 handle_edge_irq);
21
22 irq_set_chip_data(virq, irq_data);
23
24 return 0;
25 }

View Code

上面主和子中断控制虽然使用的时同一个irq domain但是对应的irq_chip却是各自的。


主irq_chip:




1 static struct irq_chip s3c_irq_chip = {
2 .name= "s3c",
3 .irq_ack= s3c_irq_ack, // 清除中断
4 .irq_mask= s3c_irq_mask, // 屏蔽中断
5 .irq_unmask= s3c_irq_unmask, // 打开中断
6 .irq_set_type= s3c_irq_type, //根据不同的触发类型设置不同的处理函数
7 .irq_set_wake= s3c_irq_wake //
8 };


子irq_chip:



1 static struct irq_chip s3c_irq_level_chip = {
2 .name= "s3c-level",
3 .irq_mask= s3c_irq_mask,
4 .irq_unmask= s3c_irq_unmask,
5 .irq_ack= s3c_irq_ack,
6 .irq_set_type= s3c_irq_type,
7 };


 在s3c_irq_mask中屏蔽中断的时候,如果屏蔽的是子中断,还需要判断目前该子中断所隶属的主中断是不是还有其他子中断在使用,如果没有的话,也会把该主中断也给屏蔽了。


同样,s3c_irq_unmask在打开子中断时也会将其所隶属的主中断也打开。

五、子中断分发s3c_irq_demux

在含有子中断的主中断被触发后,会执行主中断对应的virq的irq_desc的handle_irq,也就是这里的s3c_irq_demux:


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_linux_06基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_07


1 static void s3c_irq_demux(struct irq_desc *desc) // 这里的desc是主中断的virq的irq_desc
2 {
3 struct irq_chip *chip = irq_desc_get_chip(desc); //获得主中断的irq_chip
4 struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc);
5 struct s3c_irq_intc *intc = irq_data->intc; // 参考s3c24xx_irq_xlate_of函数
6 struct s3c_irq_intc *sub_intc = irq_data->sub_intc; // 参考s3c24xx_irq_xlate_of函数
7 unsigned int n, offset, irq;
8 unsigned long src, msk;
9
10 /* we're using individual domains for the non-dt case
11 * and one big domain for the dt case where the subintc
12 * starts at hwirq number 32.
13 */
14 // 由于我们使用的是设备树,所以这里的offset是32,也就是子中断的起始号
15 offset = irq_domain_get_of_node(intc->domain) ? 32 : 0;
16
17 // 屏蔽该主中断,并清中断
18 chained_irq_enter(chip, desc);
19 // 读取子中断的pending寄存器,看那些子中断被触发了
20 src = readl_relaxed(sub_intc->reg_pending);
21 msk = readl_relaxed(sub_intc->reg_mask);
22
23 src &= ~msk; // 去掉被屏蔽的
24 src &= irq_data->sub_bits; // 去掉没有初始化的
25 // 此时src中存放的就是将要被处理的子中断,下面会一一处理
26 while (src) {
27 n = __ffs(src);
28 src &= ~(1 << n);
29 irq = irq_find_mapping(sub_intc->domain, offset + n); //根据hwirq找到virq
30 generic_handle_irq(irq); // 处理
31 }
32 // 打开该主中断
33 chained_irq_exit(chip, desc);
34 }

View Code

六、设备驱动申请中断

1、串口驱动

drivers/tty/serial/samsung.c

函数调用:

s3c24xx_serial_probe

    ---> s3c24xx_serial_init_port

在函数s3c24xx_serial_init_port会申请中断

ret = platform_get_irq(platdev, 0); // UART0 receive interrupt

ret = platform_get_irq(platdev, 1); // UART0 transmit interrupt

2、I2C控制器

drivers/i2c/busses/i2c-s3c2410.c

在函数s3c24xx_i2c_probe中会申请中断:

i2c->irq = ret = platform_get_irq(pdev, 0)

七、查看系统信息

开机后,可以从/proc/interrupts中看到当前的中断资源申请信息:



[root@tq2440 ]# cat /proc/interrupts 
CPU0
7: 926 s3c-eint 7 Edge eth0
8: 0 s3c 8 Edge s3c2410-rtc tick
13: 567308 s3c 13 Edge samsung_time_irq
26: 0 s3c 26 Edge ohci_hcd:usb1
27: 4 s3c 27 Edge 54000000.i2c //I2C
30: 0 s3c 30 Edge s3c2410-rtc alarm
32: 53 s3c-level 32 Level 50000000.serial //UART0 RXD
33: 230 s3c-level 33 Level 50000000.serial //UART0 TXD
59: 0 s3c-level 59 Edge 53000000.watchdog


下面解释一下上面这些参数的含义:


基于设备树的TQ2440的中断(1)讓TQ2440也用上設備樹(1)TQ2440中断系统_3c_16


下面我们在中断处理程序中加入log,看一下中断处理程序的调用栈:


1、 以I2C代表的主中断




[    1.851407] [<c035004c>] (s3c24xx_i2c_irq) from [<c00447f0>] (__handle_irq_event_percpu+0x3c/0x130)
[ 1.851457] [<c00447f0>] (__handle_irq_event_percpu) from [<c0044900>] (handle_irq_event_percpu+0x1c/0x54)
[ 1.851490] [<c0044900>] (handle_irq_event_percpu) from [<c0044960>] (handle_irq_event+0x28/0x3c)
[ 1.851526] [<c0044960>] (handle_irq_event) from [<c00476c0>] (handle_edge_irq+0xbc/0x190)
[ 1.851558] [<c00476c0>] (handle_edge_irq) from [<c00441c4>] (__handle_domain_irq+0x6c/0xcc)
[ 1.851589] [<c00441c4>] (__handle_domain_irq) from [<c0009444>] (s3c24xx_handle_irq+0x6c/0x12c)
[ 1.851622] [<c0009444>] (s3c24xx_handle_irq) from [<c000e5fc>] (__irq_svc+0x5c/0x78)


2、 以串口代表的子中断


TX:




[    2.393176] [<c0293410>] (s3c24xx_serial_tx_chars) from [<c00447f0>] (__handle_irq_event_percpu+0x3c/0x130)
[ 2.393218] [<c00447f0>] (__handle_irq_event_percpu) from [<c0044900>] (handle_irq_event_percpu+0x1c/0x54)
[ 2.393249] [<c0044900>] (handle_irq_event_percpu) from [<c0044960>] (handle_irq_event+0x28/0x3c)
[ 2.393283] [<c0044960>] (handle_irq_event) from [<c004740c>] (handle_level_irq+0x90/0x114)
[ 2.393312] [<c004740c>] (handle_level_irq) from [<c0043fd8>] (generic_handle_irq+0x2c/0x40)
[ 2.393379] [<c0043fd8>] (generic_handle_irq) from [<c0242868>] (s3c_irq_demux+0xc8/0x140)
[ 2.393424] [<c0242868>] (s3c_irq_demux) from [<c00441c4>] (__handle_domain_irq+0x6c/0xcc)
[ 2.393460] [<c00441c4>] (__handle_domain_irq) from [<c0009444>] (s3c24xx_handle_irq+0x6c/0x12c)
[ 2.393492] [<c0009444>] (s3c24xx_handle_irq) from [<c000e5fc>] (__irq_svc+0x5c/0x78)


RX:




[    9.223769] [<c0294570>] (s3c24xx_serial_rx_chars) from [<c00447f0>] (__handle_irq_event_percpu+0x3c/0x130)
[ 9.223817] [<c00447f0>] (__handle_irq_event_percpu) from [<c0044900>] (handle_irq_event_percpu+0x1c/0x54)
[ 9.223851] [<c0044900>] (handle_irq_event_percpu) from [<c0044960>] (handle_irq_event+0x28/0x3c)
[ 9.223883] [<c0044960>] (handle_irq_event) from [<c004740c>] (handle_level_irq+0x90/0x114)
[ 9.223915] [<c004740c>] (handle_level_irq) from [<c0043fd8>] (generic_handle_irq+0x2c/0x40)
[ 9.223978] [<c0043fd8>] (generic_handle_irq) from [<c0242868>] (s3c_irq_demux+0xc8/0x140)
[ 9.224017] [<c0242868>] (s3c_irq_demux) from [<c00441c4>] (__handle_domain_irq+0x6c/0xcc)
[ 9.224050] [<c00441c4>] (__handle_domain_irq) from [<c0009444>] (s3c24xx_handle_irq+0x6c/0x12c)
[ 9.224081] [<c0009444>] (s3c24xx_handle_irq) from [<c000e5fc>] (__irq_svc+0x5c/0x78)


 


完。