首页 > 代码库 > 基于设备树的TQ2440的中断(1)

基于设备树的TQ2440的中断(1)

作者

姓名:彭东林

E-mail:pengdonglin137@163.com

QQ:405728433

 

平台

板子:TQ2440

内核:Linux-4.9

u-boot: 2015.04

工具链: arm-none-linux-gnueabi-gcc 4.8.3

 

概述

在博文讓TQ2440也用上設備樹(1)将支持devicetree的Linux4.9移植到了tq2440上面,而在基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)中介绍了最新的Linux下中断的知识,下面我们再结合TQ2440来分析一下。

 

正文

一、基础知识

关于这部分请参考S3C2440的芯片手册或者博文TQ2440中断系统

下面简单介绍:

技术分享

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

技术分享

寄存器功能介绍:

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个主中断:

技术分享
图一

15个子中断:

技术分享
图二
 
技术分享
图三

外部中断:

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。
技术分享
 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))  //如果参数个数不能小于415         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 = http://www.mamicode.com/&intc->irqs[intspec[2]];33                 // 对于子中断,第二个参数才有意义,表示该子中断所隶属的主中断的硬件中断号34         irq_data->parent_irq = intspec[1];35         parent_irq_data = http://www.mamicode.com/&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_demux47                 // 从函数名称中就可以看出, 这个函数会再次进行检测该主中断的哪个子中断被请求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
初始化入口:
技术分享
 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:
技术分享
 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_of15     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_intc23         intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);24                 // 主和子中断控制器共享一个irq_domain25         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; // SUBSRCPND32             intc->reg_mask = reg_base + ctrl->offset + 0x4; // INTSUBMSK33                         34                         // 由于先处理的是s3c2410_ctrl[0],所以在处理子中断的时候,*(ctrl->parent)非空35                         // 是主中断控制器对应的s3c_irq_intc36             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;  //SRCPND47             intc->reg_mask = reg_base + ctrl->offset + 0x08; //INTMSK48             intc->reg_intpnd = reg_base + ctrl->offset + 0x10; //INTPND49         }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_irq56     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了:

技术分享
 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 = http://www.mamicode.com/&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:

技术分享
 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 =http://www.mamicode.com/ 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 case11      * and one big domain for the dt case where the subintc12      * 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 = http://www.mamicode.com/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找到virq30         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
下面解释一下上面这些参数的含义:
技术分享
下面我们在中断处理程序中加入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)

 

完。

基于设备树的TQ2440的中断(1)