【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)子系統

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