【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的电气信号;
根据中断控制器处理的类型对中断分类:
- SGI(Software Generated Interrupt),Interrupt IDs 0-15。系统一般用其来实现 IPI 中断。
- PPI(Private Peripheral Interrupt),Interrupt IDs16-31。私有中断,这种中断对每个 CPU 都是独立一份的,比如 per-core timer 中断。
- SPI(Shared Peripheral Interrupt),Interrupt numbers 32-1020。最常用的外设中断,中断可以发给一个或者多个 CPU。
- 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 ID和IRQ 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)
参数含义:
- irq:需要申请的irq编号,对于ARM体系,irq编号通常在平台级的代码中事先定义好,有时候也可以动态申请。
- handler:中断服务回调函数,该回调运行在中断上下文中,并且cpu的本地中断处于关闭状态,所以该回调函数应该只是执行需要快速响应的操作,执行时间应该尽可能短小,耗时的工作最好留给下面的thread_fn回调处理。
- thread_fn:如果该参数不为NULL,内核会为该irq创建一个内核线程,当中断发生时,如果handler回调返回值是IRQ_WAKE_THREAD,内核将会激活中断线程,在中断线程中,该回调函数将被调用,所以,该回调函数运行在进程上下文中,允许进行阻塞操作。
- flags:控制中断行为的位标志,IRQF_XXXX,例如:IRQF_TRIGGER_RISING,IRQF_TRIGGER_LOW,IRQF_SHARED等,在include/linux/interrupt.h中定义。
- devname:申请本中断服务的设备名称,同时也作为中断线程的名称,该名称可以在/proc/interrupts文件中显示。
- 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, ¶m); //调用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,硬件相关的寄存器map; 397 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)子系统
- 【Linux基础系列之】中断系统(1)-框架
- 【Linux基础系列之】中断系统(2)-下半部
- 【Linux基础系列之】gpio系统
- 【Linux基础系列之】pinctrl系统
- linux中断系列之中断简介(一)
- linux-arm中断系统之中断过程
- Linux内核之中断系统
- C6000系列之C6455DSP的中断系统
- 科普系列之Linux内核中断
- linux中断系列之中断重要的数据结构(二)
- linux中断系列之中断子系统初始化(三)
- Linux中断系列之中断或异常处理(四)
- Linux中断系列之中断接口函数(五)
- Linux内核中断系列之中断的下半部(八)
- Linux内核设计基础(一)之中断处理
- 51单片机系列知识6--中断系统(1)
- linux系统编程之信号(一):中断与信号
- linux系统编程之信号(一):中断与信号
- vs的2010版本vc不能直接添加web引用了
- JavaScript 常用功能总结
- mysql数据库常用操作命令集合之二
- Codeforces Round #415 (Div. 1) A Do you want a date?
- 计算机基础(一)——硬件架构
- 【Linux基础系列之】中断系统(1)-框架
- 第8章 指针
- 如何利用Google成为一个更好的程序员
- Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.Log
- Eclipse中Gradle插件安装
- 二维码的生成
- CATransform3D 特效详解
- HTML 超级链接详细讲解
- c语言中,关于随机函数的使用详解