本文分爲兩篇,第一篇主要描述中斷控制器和中斷處理流程;第二篇主要講述中斷的下半部分處理機制包括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;//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)
參數含義:
- 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 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, ¶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 #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 }
總結整個中斷處理過程:
參考文檔: