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

来源:互联网 发布:网线测试软件 编辑:程序博客网 时间:2024/06/06 11:36

  本文分为两篇,第一篇主要描述中断控制器和中断处理流程;第二篇主要讲述中断的下半部分处理机制包括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;//domain158 #ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY159     struct irq_data     *parent_data;160 #endif161     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 out1501      * which interrupt is which (messes up the interrupt freeing1502      * 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 handler1519     }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 #endif1557     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 #endif396    397     /*398      * Some hardware gives randomly wrong interrupts.  Rather399      * 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 void437 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 available450      * keep it masked and get out of here451      */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_t209 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 but239              * did not set up a thread function240              */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)子系统

原创粉丝点击