【Linux基础系列之】中断系统(1)-框架

  本文分为两篇,第一篇主要描述中断控制器和中断处理流程;第二篇主要讲述中断的下半部分处理机制包括softirq,tasklet,workqueue;

Linux中断系统(1)-中断框架
Linux中断系统(2)-中断下半部


(一)中断综述

  中断硬件系统主要有各个外设、中断控制器(Interrupt Controller)和CPU组成。各个外设提供irq request line连接到中断控制器,在发生中断事件的时候,通过irq request line上的电气信号向CPU系统请求处理,Interrupt Controller是连接外设中断系统和CPU系统的桥梁。CPU的主要功能是运算,而Interrupt Controller来负责处理来自irq request line的电气信号;

根据中断控制器处理的类型对中断分类:

  1. SGI(Software Generated Interrupt),Interrupt IDs 0-15。系统一般用其来实现 IPI 中断。
  2. PPI(Private Peripheral Interrupt),Interrupt IDs16-31。私有中断,这种中断对每个 CPU 都是独立一份的,比如 per-core timer 中断。
  3. SPI(Shared Peripheral Interrupt),Interrupt numbers 32-1020。最常用的外设中断,中断可以发给一个或者多个 CPU。
  4. LPI(Locality-specific Peripheral Interrupt)。基于 message 的中断,GICv2 和 GICv1 中不支持;

  linux中断框架:底层部分为跟硬件相关联的cpu部分和中断控制器部分,这两部分跟平台关联,cpu部分负责中断产生时的上下文切换,中断控制器部分完成中断控制器初始化,同时管理HW irq number,代码位于kernel-3.18\drivers\irqchip;

  通用处理模块负责对不用平台硬件抽象,提供IRQ 相关API给到驱动使用,代码位于kernel-3.18\kernel\irq:

  最上层为驱动层,负责不同的外设设备的中断申请和下半部份的处理;


(二)中断准备

(1) 中断控制器申明和设备匹配

  在kernel启动函数start_kernel(kernel-3.18/init/main.c)通过调用arch平台init_IRQ()来实现中断的初始化,以arm64为例:
  kernel-3.18/arch/arm64/kernel/irq.c:

 53 void __init init_IRQ(void)
 54 {
 55     irqchip_init();
 56     if (!handle_arch_irq)
 57         panic("No interrupt controller found.");
 58 }

  of_irq_init在所有的device node中寻找中断控制器节点,形成树状结构。之后,从root interrupt controller节点开始,对于每一个interrupt controller的device node,扫描irq chip table,进行匹配,一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为参数传递给irq chip driver;

  kernel-3.18/drivers/irqchip/irqchip.c :

 24 extern struct of_device_id __irqchip_of_table[];
 25 
 26 void __init irqchip_init(void)
 27 {
 28     of_irq_init(__irqchip_of_table);
 29 }

  kernel-3.18/drivers/of/irq.c:

484 void __init of_irq_init(const struct of_device_id *matches)
485 {

540             pr_debug("of_irq_init: init %s @ %p, parent %p\n",
541                  match->compatible,
542                  desc->dev, desc->interrupt_parent);
543             irq_init_cb = (of_irq_init_cb_t)match->data;
544             ret = irq_init_cb(desc->dev, desc->interrupt_parent);//调用mt_gic_of_init;

  以通用的irq-gic.c为例:通过IRQCHIP_DECLARE定义若干个静态的struct of_device_id常量,编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(__irqchip_of_table),我们称这个特殊的section叫做irq chip table,这个table也就保存了kernel支持的所有的中断控制器的ID信息;
msm-3.18/drivers/irqchip/irq-gic.c:

1531 IRQCHIP_DECLARE(mt_gic, "mediatek,mt6735-gic", mt_gic_of_init);
//使用IRQCHIP_DECLARE来定义了若干个静态的struct of_device_id常量;
//gic_of_init : gic初始化函数指针;
//"mediatek,mt6735-gic" : 要匹配的device node的名字;
 865 #define _OF_DECLARE(table, name, compat, fn, fn_type)           \
 866     static const struct of_device_id __of_table_##name      \
 867         __used __section(__##table##_of_table)          \
 868          = { .compatible = compat,              \
 869              .data = (fn == (fn_type)NULL) ? fn : fn  }

  这个of_device_id主要被用来进行Device node和driver模块进行匹配用的,这里在drvier里面先声明,然后我们看下dts里面device node:

  kernel-3.18/arch/arm64/boot/dts/mt6755.dtsi:

 305     gic: interrupt-controller@10230000 {
 306         compatible = "mediatek,mt6735-gic";
         //该中断控制器用多少个cell(一个cell就是一个32-bit的单元)
         //描述一个外设的interrupt request line;
 308         #address-cells = <0>;
 309         interrupt-controller;//表明该device node就是一个中断控制器;
 310         reg = <0 0x10231000 0 0x1000>, 
 311               <0 0x10232000 0 0x1000>,
 312               <0 0x10200620 0 0x1000>;
 313     };

  以i2c0中断声明为例:

  kernel-3.18/arch/arm64/boot/dts/mt6755.dtsi

1124         i2c0: i2c@11007000 {
1125             compatible = "mediatek,mt6755-i2c", "mediatek,i2c0";
1126             cell-index = <0>;
1127             reg = <0x11007000 0x1000>,
1128                   <0x11000100 0x80>;
1129             interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_LOW>;
 //这个属性描述了具体该外设产生的interrupt的细节信息(也就是
 //interrupt specifier)。例如:HW interrupt ID(由该外设的
 //device node中的interrupt-parent指向的interrupt controller
 //解析)、interrupt触发类型等。
1130             clock-div = <10>;
1131             clocks = <&infrasys INFRA_I2C0>, <&infrasys INFRA_AP_DMA>;
1132             clock-names = "main", "dma";
1133             #address-cells = <1>;
1134             #size-cells = <0>;
1135         };
 //三个cell的情况:第一个值:表示中断类型(SPI,PPI,SPI);第二个是中断号;第三个是中断触发条件;

(2) 中断控制器初始化

  通过注册的mt_gic_of_init函数来完成控制器的初始化,kernel-3.18/drivers/irqchip/irq-mt-gic.c:

1474 int __init mt_gic_of_init(struct device_node *node, struct device_node *parent)
1475 {
1476     void __iomem *cpu_base;
1477     void __iomem *dist_base;
1478     void __iomem *pol_base;
1479     u32 percpu_offset;
1480     int irq;
1481     struct resource res;
1485 
1486     if (WARN_ON(!node))
1487         return -ENODEV;
1488 
1489     spin_lock_init(&irq_lock);
1490 
1491     dist_base = of_iomap(node, 0);
        ///*映射GIC Distributor的寄存器地址空间,Distributor负
        //责连接系统中所有的中断源,通过寄存器可以独立的配置每个中断的
        //属性:priority、state、security、outing  
        //information、enable status;定义哪些中断可以转发到 CPU core。*/
1492     WARN(!dist_base, "unable to map gic dist registers\n");
1493     GIC_DIST_BASE = dist_base;
1494 
1495     cpu_base = of_iomap(node, 1);
//映射GIC CPU interface的寄存器地址空间;CPU core 用来接收中断,寄
//存器主要提供的功能:mask、 identify 、control states of //interrupts forwarded to that core。每个 CPU core 拥有自己的 
//CPU interface。
1496     WARN(!cpu_base, "unable to map gic cpu registers\n");
1497     GIC_CPU_BASE = cpu_base;
1498 
1499     pol_base = of_iomap(node, 2);
1500     WARN(!pol_base, "unable to map pol registers\n");
1501     INT_POL_CTL0 = pol_base;
1502     if (of_address_to_resource(node, 2, &res))
1503         WARN(!pol_base, "unable to map pol registers\n");
1504 
1505     INT_POL_CTL0_phys = res.start;
1506 
1507     if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
1508         percpu_offset = 0;
1509 
1510     mt_gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
1511 
1512     if (parent) {//处理interrupt级联;
1513         irq = irq_of_parse_and_map(node, 0);
1514         mt_gic_cascade_irq(gic_cnt, irq);
1515     }
1516     gic_cnt++;
1517 
1527 
1528     return 0;
1529 }

  函数mt_gic_init_bases():

855 void __init mt_gic_init_bases(unsigned int gic_nr, int irq_start,
 856           void __iomem *dist_base, void __iomem *cpu_base,
 857           u32 percpu_offset, struct device_node *node)
 858 {
 859     irq_hw_number_t hwirq_base;
 860     struct gic_chip_data *gic;
 861     int gic_irqs, irq_base, i;
 862 
 863     BUG_ON(gic_nr >= MAX_GIC_NR);
 864 
 865     gic = &gic_data[gic_nr];
 887     {
 888         /* Normal, sane GIC... */
 889         WARN(percpu_offset,
 890              "GIC_NON_BANKED not enabled, ignoring %08x offset!", percpu_offset);
 891         gic->dist_base.common_base = dist_base;
 892         gic->cpu_base.common_base = cpu_base;
 893         gic_set_base_accessor(gic, gic_get_common_base);
 894     }
 895 
 896     /*
 897      * Initialize the CPU interface map to all CPUs.
 898      * It will be refined as each CPU probes its ID.
 899      */
 900     for (i = 0; i < NR_GIC_CPU_IF; i++)
 901         gic_cpu_map[i] = 0xff;
 902 
 903     /*
 904      * For primary GICs, skip over SGIs.
 905      * For secondary GICs, skip over PPIs, too.
 906      */
 907     if (gic_nr == 0 && (irq_start & 31) > 0) {
 908         hwirq_base = 16;
 909         if (irq_start != -1)
 910             irq_start = (irq_start & ~31) + 16;
 911     } else {
 912         hwirq_base = 32;
 913     }
 914 
 915     /*
 916      * Find out how many interrupts are supported.
 917      * The GIC only supports up to 1020 interrupt sources.
 918      */
 919     gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;//从寄存器读出支持最大的中断数目;
 920     gic_irqs = (gic_irqs + 1) * 32;
 921     if (gic_irqs > 1020)
 922         gic_irqs = 1020;
 923     gic->gic_irqs = gic_irqs;
 924 
 925     gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
     //之所以减去16主要是因为root GIC的0~15号HW interrupt 是for IPI的,因此要去掉。也正因为如此hwirq_base从16开始;
 926     irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());//分配中断描述符;
 927     if (IS_ERR_VALUE(irq_base)) {
 928         WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", irq_start);
 929         irq_base = irq_start;
 930     }
 931     gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
 932                         hwirq_base, &mt_gic_irq_domain_ops, gic);//生成改irq chip的irq domain; 
 933     if (WARN_ON(!gic->domain))
 934         return;
 935 
 936 #ifdef CONFIG_SMP
 937     set_smp_cross_call(mt_gic_raise_softirq);
 938     register_cpu_notifier(&gic_cpu_notifier);
 939 #endif
 940 
 941     set_handle_irq(gic_handle_irq);//设置arch关联的中断处理函数,当中断发生的时候会通过异常向量表调用这个处理函数;
 942 
 943     gic_dist_init(gic);//配置GIC distributor相关寄存器并初始化;
 944     gic_cpu_init(gic);//设定CPU interface相关寄存器;
 945     gic_pm_init(gic);//电源管理相关初始化;
 946 }

(3)向系统注册irq domain

  IRQ number表示每一个外设中断编号,HW interrupt ID表示每一个interrupt request line的一个标识;随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,系统中至少有两个中断控制器了,一个传统意义的中断控制器,一个是GPIO controller type的中断控制器;所以就需要irq domain模块来管理IRQ number与HW interrupt ID的映射;

(1)线性映射。其实就是一个lookup table,HW interrupt ID作为index,通过查表可以获取对应的IRQ number。对于Linear map而言,interrupt controller对其HW interrupt ID进行编码的时候要满足一定的条件:hw ID不能过大,而且ID排列最好是紧密的。对于线性映射,其接口API为irq_domain_add_linear();

(2)Radix Tree map。建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系,其接口APIirq_domain_add_tree();

(3)不需要进行映射,我们直接把IRQ number写入HW interrupt ID配置寄存器就OK了;

  kernel-3.18/drivers/irqchip/irq-mt-gic.c:

 171 struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
 172                      unsigned int size,//该interrupt domain支持多少IRQ;
 173                      unsigned int first_irq,
 174                      irq_hw_number_t first_hwirq,
 175                      const struct irq_domain_ops *ops,//mt_gic_irq_domain_ops
 176                      void *host_data)
 177 {
 178     struct irq_domain *domain;
 179     
 180     domain = __irq_domain_add(of_node, first_hwirq + size,
 181                   first_hwirq + size, 0, ops, host_data);
                 //挂入irq_domain_list的全局列表;
 182     if (domain)
 183         irq_domain_associate_many(domain, first_irq, first_hwirq, size);//创建映射
 184 
 185     return domain;
 186 }   
 187 EXPORT_SYMBOL_GPL(irq_domain_add_legacy);

struct irq_domain_ops :

 850 const struct irq_domain_ops mt_gic_irq_domain_ops = {
 851     .map = gic_irq_domain_map,
 852     .xlate = gic_irq_domain_xlate,
 853 };

  map函数:创建IRQ number和GIC hw interrupt ID之间映射关系的时候;

  xlate函数:个使用中断的device node会通过一些属性(例如interrupts和interrupt-parent属性)来提供中断信息给kernel以便kernel可以正确的进行driver的初始化动作。xlate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)。

  映射函数kernel-3.18/kernel/irq/irqdomain.c:irq_domain_associate_many()

 333     pr_debug("%s(%s, irqbase=%i, hwbase=%i, count=%i)\n", __func__,
 334         of_node_full_name(domain->of_node), irq_base, (int)hwirq_base, count);
 336     for (i = 0; i < count; i++) {
 337         irq_domain_associate(domain, irq_base + i, hwirq_base + i);
 338     }

273 int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
 274              irq_hw_number_t hwirq)
 275 {
 276     struct irq_data *irq_data = irq_get_irq_data(virq);
 277     int ret;
 278 
 287     mutex_lock(&irq_domain_mutex);
 288     irq_data->hwirq = hwirq;
 289     irq_data->domain = domain;
 290     if (domain->ops->map) {
 291         ret = domain->ops->map(domain, virq, hwirq);//调用irq domain的map callback函数;
 292         if (ret != 0) {
 298             if (ret != -EPERM) {
 299                 pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
 300                        domain->name, hwirq, virq, ret);
 301             }
 302             irq_data->domain = NULL;
 303             irq_data->hwirq = 0;
 304             mutex_unlock(&irq_domain_mutex);
 305             return ret;
 306         }
 307 
 308         /* If not already assigned, give the domain the chip's name */
 309         if (!domain->name && irq_data->chip)
 310             domain->name = irq_data->chip->name;
 311     }
 312 
 313     if (hwirq < domain->revmap_size) {
 314         domain->linear_revmap[hwirq] = virq;
         //填写线性映射lookup table的数据;
 315     } else {
 316         mutex_lock(&revmap_trees_mutex);
 317         radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
             //向radix tree插入一个node ;
 318         mutex_unlock(&revmap_trees_mutex);
 319     }
 320     mutex_unlock(&irq_domain_mutex);
 321 
 322     irq_clear_status_flags(virq, IRQ_NOREQUEST);
 323     //该IRQ已经可以申请了,因此clear相关flag ;
 324     return 0;
 325 }
 326 EXPORT_SYMBOL_GPL(irq_domain_associate);

  map函数:

 785 static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
 786 { 
 787     if (hw < 32) { //SGI或者PPI 
 788         irq_set_percpu_devid(irq); //设定一些per cpu的flag;    
 789         irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq); //设定该中断描述符的irq chip和high level的handler ,最终赋值给中断描述符的handle_irq;
 790         set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
 791     } else {//SPI类型
 792         irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);
 793         set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
 794     }
 795     irq_set_chip_data(irq, d->host_data);
 796     return 0;
 797 }

  同时设置gic_chip操作接口,通过这些操作接口集可以操作中断控制器,kernel-3.18/drivers/irqchip/irq-mt-gic.c:

 491 static struct irq_chip gic_chip = {
 492     .name           = "GIC",
 493     .irq_mask       = gic_mask_irq, //屏蔽该irq
 494     .irq_unmask     = gic_unmask_irq,//取消屏蔽该irq
 495     .irq_eoi        = gic_eoi_irq, //有些中断控制器需要在cpu处理完该irq后发出eoi信号,该回调就是用于这个目的;
 496     .irq_set_type       = gic_set_type,//设置irq的电气触发条件,例如IRQ_TYPE_LEVEL_HIGH或IRQ_TYPE_EDGE_RISING;
 497     .irq_retrigger      = gic_retrigger,//重新触发一次中断,一般用在中断丢失的场景下。如果硬件不支持retrigger,可以使用软件的方法。
 498 #ifdef CONFIG_SMP
 499     .irq_set_affinity   = gic_set_affinity,//用于设置该
 //irq和cpu之间的亲缘关系,就是通知中断控制器,该irq发生时,那些cpu有
 //权响应该irq。当然,中断控制器会在软件的配合下,最终只会让一个cpu处
 //理本次请求。
 500 #endif
 501     .irq_disable        = gic_disable_irq,//禁止该irq,
 //通常是直接调用irq_mask,严格意义上,他俩其实代表不同的意义,
 //disable表示中断控制器根本就不响应该irq,而mask时,中断控制器可能
 //响应该irq,只是不通知CPU,这时,该irq处于pending状态。类似的区别
 //也适用于enable和unmask。
 502     .irq_set_wake       = gic_set_wake,//通知电源管理子系统,该irq是否可以用作系统的唤醒源。
 503 };

(4)HW interrupt ID和IRQ number的映射

  该设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该device node中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系,具体代码如下:
kernel-3.18/drivers/of/irq.c:

 37 unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
 38 {
 39     struct of_phandle_args oirq;
 40 
 41     if (of_irq_parse_one(dev, index, &oirq))
 42         return 0;//解析给device node的interrupts等中断属性,并封装到oirq中;
 43 
 44     return irq_create_of_mapping(&oirq);//建立映射
 45 }
 46 EXPORT_SYMBOL_GPL(irq_of_parse_and_map);

  函数irq_create_of_mapping:

467 unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
 468 {
 469     struct irq_domain *domain;
 470     irq_hw_number_t hwirq;
 471     unsigned int type = IRQ_TYPE_NONE;
 472     int virq;
 473 
 474     domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;//根据指向了外设对应的interrupt controller的device node 找到对应的irq_domain; 
 475     if (!domain) {
 476         pr_warn("no irq domain found for %s !\n",
 477             of_node_full_name(irq_data->np));
 478         return 0;
 479     }
 480 
 481     /* If domain has no translation, then we assume interrupt line */
 482     if (domain->ops->xlate == NULL)
 483         hwirq = irq_data->args[0];
 484     else {
 485         if (domain->ops->xlate(domain, irq_data->np, irq_data->args,
 486                     irq_data->args_count, &hwirq, &type))
         //调用irq domain的xlate函数翻译解析interrupt属性;
 487             return 0;
 488     }
 489 
 490     if (irq_domain_is_hierarchy(domain)) {
 491         /*
 492          * If we've already configured this interrupt,
 493          * don't do it again, or hell will break loose.
 494          */
 495         virq = irq_find_mapping(domain, hwirq);
 496         if (virq)
 497             return virq;
 498 
 499         virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);
 500         if (virq <= 0)
 501             return 0;
 502     } else {
 503         /* Create mapping */
 504         virq = irq_create_mapping(domain, hwirq);
 505         if (!virq)//创建HW interrupt IDIRQ number的映射关系,回IRQ numbe 就是所说的virq;
 506             return virq;
 507     }
 508 
 509     /* Set type if specified and different than the current one */
 510     if (type != IRQ_TYPE_NONE &&
 511         type != irq_get_trigger_type(virq))
 512         irq_set_irq_type(virq, type);//调用irq_set_irq_type函数设定trigger type 这个type也是从interrupts属性解析出来的;
 513     return virq;
 514 }
 515 EXPORT_SYMBOL_GPL(irq_create_of_mapping);

  映射函数irq_create_mapping(),传入解析出来的hwirq number;

391 unsigned int irq_create_mapping(struct irq_domain *domain,
 392                 irq_hw_number_t hwirq)
 393 {
 394     int virq;
 395 
 396     pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq);
 397 
 398     /* Look for default domain if nececssary */
 399     if (domain == NULL)
 400         domain = irq_default_domain;
 401     if (domain == NULL) {
 402         WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
 403         return 0;
 404     }
 405     pr_debug("-> using domain @%p\n", domain);
 406 
 407     /* Check if mapping already exists */
 408     virq = irq_find_mapping(domain, hwirq);
 409     if (virq) {//如果映射已经存在,那么不需要映射;
 410         pr_debug("-> existing mapping on virq %d\n", virq);
 411         return virq;
 412     }
 413 
 414     /* Allocate a virtual interrupt number */
 415     virq = irq_domain_alloc_descs(-1, 1, hwirq,
 416                       of_node_to_nid(domain->of_node));
         //分配一个IRQ 描述符以及对应的irq number ;
 417     if (virq <= 0) {
 418         pr_debug("-> virq allocation failed\n");
 419         return 0;
 420     }
 421 
 422     if (irq_domain_associate(domain, virq, hwirq)) {
         //建立mapping ;
 423         irq_free_desc(virq);
 424         return 0;
 425     }
 426 
 427     pr_debug("irq %lu on domain %s mapped to virtual irq %u\n",
 428         hwirq, of_node_full_name(domain->of_node), virq);
 429 
 430     return virq;
 431 }
 432 EXPORT_SYMBOL_GPL(irq_create_mapping);

  在这里就已经为拥有中断属性(interrupts)的device node分配好了中断描述符,并且设置好了触发类型;对于一个使用Device tree的普通驱动程序,基本上初始化需要调用irq_of_parse_and_map获取IRQ number,然后调用request_threaded_irq申请中断handler;

(5)中断号和中断描述符

  系统启动阶段,取决于内核的配置,内核会通过数组或基数树分配好足够多的irq_desc结构,系统中每一个irq都对应着一个irq_desc结构:
include/linux/irqdesc.h:

 49 struct irq_desc {
 50     struct irq_data     irq_data;
 51     unsigned int __percpu   *kstat_irqs;
 52     irq_flow_handler_t  handle_irq;
     /*注册的流控处理函数会根据中断类型复制多种函数如:
    handle_fasteoi_irq()
    handle_simple_irq()
    handle_edge_irq()
    handle_level_irq()
    handle_percpu_irq()
    */
 53 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
 54     irq_preflow_handler_t   preflow_handler;
 55 #endif
 56     struct irqaction    *action;    /* IRQ action list */ //指向一个struct irqaction的链表,当一个irq被触发时,内核会遍历
 //该链表,调用action结构中的回调handler或者激活其中的中断线程,之所
 //以实现为一个链表,是为了实现中断的共享,多个设备共享同一个irq,这在
 //外围设备中是普遍存在的。
 57     unsigned int        status_use_accessors;
 58     unsigned int        core_internal_state__do_not_mess_with_it;
 59     unsigned int        depth;      /* nested irq disables */ //用于管理enable_irq()/disable_irq()这两个API的嵌
 //套深度管理,每次enable_irq时该值减去1,每次disable_irq时该值加
 //1,只有depth==0时才真正向硬件封装层发出关闭irq的调用,只有
 //depth==1时才会向硬件封装层发出打开irq的调用。
 60     unsigned int        wake_depth; /* nested wake enables */
 61     unsigned int        irq_count;  /* For detecting broken IRQs */
 62     unsigned long       last_unhandled; /* Aging timer for unhandled count */
 63     unsigned int        irqs_unhandled;
 64     atomic_t        threads_handled;
 65     int         threads_handled_last;
 66     raw_spinlock_t      lock;
 67     struct cpumask      *percpu_enabled;//描述该IRQ在各个CPU上是否enable;
 68 #ifdef CONFIG_SMP
 69     const struct cpumask    *affinity_hint;
 70     struct irq_affinity_notify *affinity_notify;
 71 #ifdef CONFIG_GENERIC_PENDING_IRQ
 72     cpumask_var_t       pending_mask;
 73 #endif
 74 #endif
 75     unsigned long       threads_oneshot;
 76     atomic_t        threads_active;
 77     wait_queue_head_t       wait_for_threads;
 78 #ifdef CONFIG_PM_SLEEP
 79     unsigned int        nr_actions;
 80     unsigned int        no_suspend_depth;
 81     unsigned int        force_resume_depth; 
 82 #endif
 83 #ifdef CONFIG_PROC_FS
 84     struct proc_dir_entry   *dir;//该IRQ对应的proc接口
 85 #endif
 86     int         parent_irq;
 87     struct module       *owner;
 88     const char      *name;
 89 } ____cacheline_internodealigned_in_smp;
150 struct irq_data {
151     u32         mask;
152     unsigned int        irq;
153     unsigned long       hwirq;
154     unsigned int        node;
155     unsigned int        state_use_accessors;
156     struct irq_chip     *chip;//指向该irq所属的中断控制器的irq_chip结构指针
157     struct irq_domain   *domain;//domain
158 #ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
159     struct irq_data     *parent_data;
160 #endif
161     void            *handler_data;
162     void            *chip_data;
163     struct msi_desc     *msi_desc;//(Message Signaled Interrupts) 信息触发的中断;
164     cpumask_var_t       affinity;//记录该irq与cpu之间的亲缘关系,它其实是一个bit-mask,每一个bit代表一个cpu,置位后代表该cpu可能处理该irq。
165 };

  最终把该irq_chip实例注册到irq_desc.irq_data.chip字段中,这样各个irq中断描述符和中断控制器就进行了关联,只要知道irq编号,即可得到对应到irq_desc结构,进而可以通过chip指针访问中断控制器。如果设置了CONFIG_SPARSE_IRQ会实时分配中断描述符,代码如下:

  kernel/kernel/irq/irqdesc.c:

260 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { 
261     [0 ... NR_IRQS-1] = {//预先分配NR_IRQS个中断描述符; 
262         .handle_irq = handle_bad_irq,  
263         .depth      = 1,
264         .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
265     }
266 }; 
267    
268 int __init early_irq_init(void)
269 {  
270     int count, i, node = first_online_node;
271     struct irq_desc *desc;
272    
273     init_irq_default_affinity();   
274    
275     printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
276    
277     desc = irq_desc;      
278     count = ARRAY_SIZE(irq_desc);  
279    
280     for (i = 0; i < count; i++) { //遍历整个irq_desc table,对每一个进行初始化; 
281         desc[i].kstat_irqs = alloc_percpu(unsigned int);//分配per cpu的irq统计信息需要的内存;
282         alloc_masks(&desc[i], GFP_KERNEL, node);//分配中断描述符中需要的cpu mask内存;
283         raw_spin_lock_init(&desc[i].lock);//初始化spin lock;
284         lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
285         desc_set_defaults(i, &desc[i], node, NULL);//设定default值;
286     }
287     return arch_early_irq_init(); //调用arch相关的初始化函数;
288 } 

  当发生中断后,首先获取触发中断的HW interupt ID,然后通过irq domain翻译成IRQ number,然后通过IRQ number就可以获取对应的中断描述符,调用中断描述符中的highlevel irq-events handler来进行中断处理就可以了;


(三)申请注册中断


  通常我们需要先在驱动代码当中申请中断,request_irq()是request_threaded_irq的一个wrapper,只是将其中的thread_fn置为空,我们通常用一下request_threaded_irq申请中断服务,设置thread_fn,强制中断处理线程话:

1490 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1491              irq_handler_t thread_fn, unsigned long irqflags,
1492              const char *devname, void *dev_id)

参数含义:

  1. irq:需要申请的irq编号,对于ARM体系,irq编号通常在平台级的代码中事先定义好,有时候也可以动态申请。
  2. handler:中断服务回调函数,该回调运行在中断上下文中,并且cpu的本地中断处于关闭状态,所以该回调函数应该只是执行需要快速响应的操作,执行时间应该尽可能短小,耗时的工作最好留给下面的thread_fn回调处理。
  3. thread_fn:如果该参数不为NULL,内核会为该irq创建一个内核线程,当中断发生时,如果handler回调返回值是IRQ_WAKE_THREAD,内核将会激活中断线程,在中断线程中,该回调函数将被调用,所以,该回调函数运行在进程上下文中,允许进行阻塞操作。
  4. flags:控制中断行为的位标志,IRQF_XXXX,例如:IRQF_TRIGGER_RISING,IRQF_TRIGGER_LOW,IRQF_SHARED等,在include/linux/interrupt.h中定义。
  5. devname:申请本中断服务的设备名称,同时也作为中断线程的名称,该名称可以在/proc/interrupts文件中显示。
  6. dev_id:保存dev设备私有数据。

irq_handler_t handler和irq_handler_t thread_fn有如下四种组合:

handler thread_fn
NULL NULL:函数出错,返回-EINVAL;
设定 设定 :正常流程。中断处理被合理的分配到primary handler和threaded handler中。
设定 NULL :中断处理都是在primary handler中完成;
NULL 设定 :这种情况下,系统会帮忙设定一个default的primary handler:irq_default_primary_handler,协助唤醒threaded handler线程;

kernel-3.18/kernel/irq/manage.c:

1490 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1491              irq_handler_t thread_fn, unsigned long irqflags,
1492              const char *devname, void *dev_id)
1493 {
1494     struct irqaction *action;
1495     struct irq_desc *desc;
1496     int retval;
1497 
1498     /*
1499      * Sanity-check: shared interrupts must pass in a real dev-ID,
1500      * otherwise we'll have trouble later trying to figure out
1501      * which interrupt is which (messes up the interrupt freeing
1502      * logic etc).
1503      */
1504     if ((irqflags & IRQF_SHARED) && !dev_id)
1505         return -EINVAL;//对于打开IRQF_SHARED这个标志,表示多个设备共享该中断,同时我们需要dev_id来具体区分是那个设备;
1506 
1507     desc = irq_to_desc(irq);//传入的参数irq:一般在
//request之前先把改设备节点传入irq_of_parse_and_map()函数,通过
//of_irq_parse_one解析该节点,然后获得该中断所隶属的interrupt-
//controller的irq domain,利用该domain的xlate函数从前面表示第
//index个中断的参数中解析出hwirq和中断类型,最后从系统中为该hwriq分
//配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中;

1508     if (!desc)
1509         return -EINVAL;
1510 
1511     if (!irq_settings_can_request(desc) ||
1512         WARN_ON(irq_settings_is_per_cpu_devid(desc)))
1513         return -EINVAL;//并非系统中所有的IRQ number都可以
//request,有些中断描述符被标记为IRQ_NOREQUEST,标识该IRQ number
//不能被其他的驱动request,比如负责级联的irq number;
1514 
1515     if (!handler) {
1516         if (!thread_fn)
1517             return -EINVAL;
1518         handler = irq_default_primary_handler;
        //如果 action->handler 为空,走一个default handler
1519     }
1520 
1521     action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1522     if (!action)
1523         return -ENOMEM;

1524 
1525     action->handler = handler;//初始化irqaction结构的各字段;
1526     action->thread_fn = thread_fn;
1527     action->flags = irqflags;
1528     action->name = devname;
1529     action->dev_id = dev_id;
1530 
1531     chip_bus_lock(desc);
1532     retval = __setup_irq(irq, desc, action);//分配struct irqaction,赋值,调用__setup_irq进行实际的注册;
1533     chip_bus_sync_unlock(desc);
1534 
1535     if (retval)
1536         kfree(action);
1537 
1538 #ifdef CONFIG_DEBUG_SHIRQ_FIXME
          .....
1556 #endif
1557     return retval;
1558 }
1559 EXPORT_SYMBOL(request_threaded_irq);

传入已经被初始化的action到__setup_irq():

1007     if (new->thread_fn && !nested) {
1008         struct task_struct *t;
1009         static const struct sched_param param = {
1010             .sched_priority = MAX_USER_RT_PRIO/2,
1011         };
1012 
1013         t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
1014                    new->name);
1015         if (IS_ERR(t)) {
1016             ret = PTR_ERR(t);
1017             goto out_mput;
1018         }
1019 
1020         sched_setscheduler_nocheck(t, SCHED_FIFO, &param); //调用kthread_create来创建一个内核线程处理threaded 
//handler函数,并调用sched_setscheduler_nocheck来设定这个中断线
//程的调度策略和调度优先级;
1021        get_task_struct(t);
1039     }

kernel-3.18/kernel/irq/manage.c irq_thread():

 857 static int irq_thread(void *data)
 858 {
 859     struct callback_head on_exit_work;
 860     struct irqaction *action = data;//传入的action;
 861     struct irq_desc *desc = irq_to_desc(action->irq);
 862     irqreturn_t (*handler_fn)(struct irq_desc *desc,
 863             struct irqaction *action);
 864 
 865     if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
 866                     &action->thread_flags))
 867         handler_fn = irq_forced_thread_fn;
 868     else
 869         handler_fn = irq_thread_fn;
 870 
 871     init_task_work(&on_exit_work, irq_thread_dtor);
 872     task_work_add(current, &on_exit_work, false);
 873 
 874     irq_thread_check_affinity(desc, action);
 875 
 876     while (!irq_wait_for_interrupt(action)) {
         //test_and_clear_bit(IRQTF_RUNTHREAD,&action->thread_flags)等待中断唤醒,通过检测action的thread_flags标志来检测是否中断发生然后调度该线程的执行;
 877         irqreturn_t action_ret;
 878 
 879         irq_thread_check_affinity(desc, action);
 880 
 881         action_ret = handler_fn(desc, action);//调用action->thread_fn函数;
 882         if (action_ret == IRQ_HANDLED)
 883             atomic_inc(&desc->threads_handled);
 884 
 885         wake_threads_waitq(desc);
 886     }
 899 }

至此中断各个环节已经准备好了,各个结构体联系如下图:

这里写图片描述


(四)中断发生和处理

  参考我的ARMv8-中断处理接口 这篇文章中断触发了之后进入异常向量entry接口函数之后,会走人在irqchip设置好的gic_handle_irq函数接口,最后通过handle_domain_irq来处理:

  kernel-3.18/drivers/irqchip/irq-mt-gic.c:

 393 static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
 394 {
 395     u32 irqstat, irqnr;
 396     struct gic_chip_data *gic = &gic_data[0];//获取root GIC的gic_chip_data,硬件相关的寄存器map397     void __iomem *cpu_base = gic_data_cpu_base(gic);
 398     //获取root GIC mapping到CPU地址空间的信息;
 399     do {
 400         irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);//获取HW interrupt ID 就是hw irq;
 401         irqnr = irqstat & ~0x1c00;
 402 
 403         if (likely(irqnr > 15 && irqnr < 1021)) {//SPI和PPI的处理;
 404             handle_domain_irq(gic->domain, irqnr, regs);
 405             continue;
 406         }
 407         if (irqnr < 16) {//IPI类型的中断;
 408             writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
 409 #ifdef CONFIG_SMP
 410             handle_IPI(irqnr, regs);
 411 #endif
 412             continue;
 413         }
 414         break;
 415     } while (1);
 416 }

  kernel-3.18/kernel/irq/irqdesc.c __handle_domain_irq():

372 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
373             bool lookup, struct pt_regs *regs)
374 {  
375     struct pt_regs *old_regs = set_irq_regs(regs);
376     unsigned int irq = hwirq;
377     int ret = 0;
381    
382     irq_enter(); //响应IRQ中断后,ARM会自动把CPSR中的I位置位,表明禁止新的IRQ请求;         
383    
384 #ifdef CONFIG_IRQ_DOMAIN  
385     if (lookup)           
386         irq = irq_find_mapping(domain, hwirq);//将HW interrupt ID转成IRQ number; 
387 #endif
396    
397     /*
398      * Some hardware gives randomly wrong interrupts.  Rather
399      * than crashing, do something sensible.
400      */
401     if (unlikely(!irq || irq >= nr_irqs)) {
402         ack_bad_irq(irq); 
403         ret = -EINVAL;    
404     } else {
405         generic_handle_irq(irq);       
406     }
413 
414     irq_exit();
415     set_irq_regs(old_regs);
416     return ret;
417 }
345 int generic_handle_irq(unsigned int irq)
346 {
347     struct irq_desc *desc = irq_to_desc(irq);//获取相应中断的描述符;
348 
349     if (!desc)
350         return -EINVAL;
351     generic_handle_irq_desc(irq, desc);//根据该描述符注册的handle函数,desc->handle_irq(irq, desc),调用中断流控制函数;
352     return 0;
353 }
354 EXPORT_SYMBOL_GPL(generic_handle_irq);

  通过调用gic_set_type来设置中断的触发类型,根据相应的类型设置中断流控制函数,以电平触发的handle_level_irq为例kernel-3.18/kernel/irq/chip.c:

436 void
437 handle_level_irq(unsigned int irq, struct irq_desc *desc)
438 {  
439     raw_spin_lock(&desc->lock);    
440     mask_ack_irq(desc);  //调用irq chip的irq_mask接口;屏蔽该IRQ;
441    
442     if (!irq_may_run(desc))//判断是否在处理该中断;
443         goto out_unlock;  
444    
445     desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); 
446     kstat_incr_irqs_this_cpu(irq, desc);
447    
448     /*
449      * If its disabled or no action available
450      * keep it masked and get out of here
451      */
452     if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
453         desc->istate |= IRQS_PENDING;  
454         goto out_unlock;  
455     }
456    
457     handle_irq_event(desc);
458    
459     cond_unmask_irq(desc);//调用irq chip的irq_unmask接口,取消屏蔽;
460    
461 out_unlock:
462     raw_spin_unlock(&desc->lock);  
463 }  
464 EXPORT_SYMBOL_GPL(handle_level_irq);
465    

  函数kernel-3.18/kernel/irq/handle.c : handle_irq_event():

268 irqreturn_t handle_irq_event(struct irq_desc *desc)
269 {   
270     struct irqaction *action = desc->action;
271     irqreturn_t ret;
272 
273     desc->istate &= ~IRQS_PENDING;//开始处理了,清除pending状态 ;
274     irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);//设置为正在处理状态;
275     raw_spin_unlock(&desc->lock);
276 
277     ret = handle_irq_event_percpu(desc, action);//遍历action list,调用handler;
278 
279     raw_spin_lock(&desc->lock);
280     irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);//清标志;
281     return ret;
282 }
208 irqreturn_t
209 handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
210 {
211     irqreturn_t retval = IRQ_NONE;
212     unsigned int flags = 0, irq = desc->irq_data.irq;
216 
217     do {
218         irqreturn_t res;
219 
224         res = action->handler(irq, action->dev_id);
            //这里的action->handler就是在request_irq的时候传递的handler处理函数;
235         switch (res) {
236         case IRQ_WAKE_THREAD:
237             /*
238              * Catch drivers which return WAKE_THREAD but
239              * did not set up a thread function
240              */
241             if (unlikely(!action->thread_fn)) {
242                 warn_no_thread(irq, action);
243                 break;
244             }
245 
246             __irq_wake_thread(desc, action);
247         //通过request_threaded_irq()申请注册强制线程中断,在申请的时候已经创建好了线程,通过这里来唤醒下半部处理线程来处理;调用wake_up_process(action->thread);
248             /* Fall through to add to randomness */
249         case IRQ_HANDLED:
250             flags |= action->flags;
251             break;
252 
253         default:
254             break;
255         }
256 
257         retval |= res;
258         action = action->next;//遍历该中断描述符的整个action list;
259     } while (action);
260 
261     add_interrupt_randomness(irq, flags);
262 
266 }

  总结整个中断处理过程:

这里写图片描述



参考文档:

www.wowotech.net/irq_subsystem/

Linux中断(interrupt)子系统

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章