• 了解IRQ Domain(中断控制器)

1.如何理解中断号?

  每个IRQ同时有"irq"和"hwirq"两个编号。

  • "hwirq"是硬件中断号(物理中断号),即芯片手册上写的号码,Interrupt controller用hwirq来标识外设中断。在interrupt controller级联的情况下,仅仅用hwirq已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不同的Interrupt controller上是会重复编码的)。
  • "irq"是软件使用的中断号,或者说是逻辑/虚拟中断号。CPU需要为每一个外设中断编号,它是一个虚拟interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。

  如果芯片中有多个中断控制器,假设它们各自都对应16个IRQ,这16个IRQ的编号都是从0到15,那CPU收到中断后,软件单凭中断号是分不清这个中断到底是从哪个中断控制器派发过来的,它需要的是中断控制器自身的编号加上IRQ的编号。

2.IRQ Domain

  早期的系统,只有一个中断控制器,接入中断控制器的物理中断号都是不同的。但是随着计算机系统的发展,系统中可以挂接更多的中断控制器。特别是嵌入式系统的出现,类似GPIO这种也可以视作一种中断控制器。每个中断控制器都有自己中断线的物理编号,且这些物理编号会有重复。此时,Linux Kernel发展出了IRQ Domain的概念,来区分这些相同的物理中断编号。

  IRQ Domain,顾名思义,即中断控制域。每个中断控制器都有自己的struct irq_domain结构体,以及自己下属的物理中断号,也不用担心物理中断号重复无法区分的问题。以下图为例,IRQ Controller 1及其下属的几根中断输入线作为一个中断控制域,而IRQ Controller 2作为另外一个中断控制域。
在这里插入图片描述
  对于驱动工程师而言,只希望得到一个IRQ number,而不关系具体是哪个interrupt controller上的哪个HW interrupt ID。因此kernel中的中断子系统需要提供一个将HW interrupt ID映射到IRQ number上来的机制。

  从kernel的角度来看,任何外部设备的中断都是一个异步事件,kernel都需要识别这个事件。内核用IRQ number来标识某一个设备的某个interrupt request。有了IRQ number就可以定位到该中断的描述符(struct irq_desc)。但是,对于中断控制器而言,它不并知道IRQ number,它只是知道HW interrupt number(中断控制器会为其支持的interrupt source进行编码,这个编码被称为Hardware interrupt number )。不同的软件模块用不同的ID来识别interrupt source,这样就需要映射了。如何将Hardware interrupt number 映射到IRQ number呢?这需要一个translation object,因此irq domain应用而生

2.1.结构体

1).struct irq_domain(抽象为中断控制器)

struct irq_domain { 
    struct list_head link;    //用于将irq_domain连接到全局链表irq_domain_list中
    const char *name;         //中断控制器名称
    const struct irq_domain_ops *ops; //irq domain映射操作使用的方法集合
    void *host_data;        //指向一个struct gic_chip_data的数据结构

    /* Optional data */ 
    struct device_node *of_node;          //该interrupt domain对应的interrupt controller的device node 
    struct irq_domain_chip_generic *gc;   //generic irq chip

    /* reverse map data. The linear map gets appended to the irq_domain */ 
    irq_hw_number_t hwirq_max;            //该irq domain支持中断数量的最大值。 
    unsigned int revmap_direct_max_irq; 
    unsigned int revmap_size;               //线性映射的大小
    struct radix_tree_root revmap_tree;     //Radix Tree map使用到的radix tree root node 
    unsigned int linear_revmap[];           //线性映射使用的lookup table 
};

  在初始化中断控制器时,解析DTS信息中定义了几个中断控制器,每个中断控制器都注册一个struct irq_domain数据结构

  对GIC中断控制器而言,host_data成员指向一个struct gic_chip_data的数据结构,该结构体通过gic_init_bases(drivers/irqchip/irq-gic-v3.c)初始化。

2).struct gic_chip_data

    50 struct gic_chip_data {                                                                                
    51     struct fwnode_handle    *fwnode;
    52     void __iomem        *dist_base;
    53     struct redist_region    *redist_regions;
    54     struct rdists       rdists;
    55     struct irq_domain   *domain;
    56     u64         redist_stride;
    57     u32         nr_redist_regions;
    58     bool            has_rss;
    59     unsigned int        irq_nr;
    60     struct partition_desc   *ppi_descs[16];
    61 }; 

  在注册GIC irq domain时还有一个重要的数据结构irq_domain_ops,对于GIC,其irq domain的操作函数是gic_irq_domain_ops,定义如下:

  1036 static const struct irq_domain_ops gic_irq_domain_ops = {                                             
  1037     .translate = gic_irq_domain_translate,
  1038     .alloc = gic_irq_domain_alloc,
  1039     .free = gic_irq_domain_free,
  1040     .select = gic_irq_domain_select,
  1041 };

3).struct irq_domain_ops:

struct irq_domain_ops {
//如果irq_domain不是HIERARCHY的,那么使用下面的函数
    int (*match)(struct irq_domain *d, struct device_node *node,
             enum irq_domain_bus_token bus_token);
    int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);----使hwirq映射到virq
    void (*unmap)(struct irq_domain *d, unsigned int virq);---------------------解除映射关系
    int (*xlate)(struct irq_domain *d, struct device_node *node,
             const u32 *intspec, unsigned int intsize,
             unsigned long *out_hwirq, unsigned int *out_type);-----------------得到hwirq

//相反,如果是HIERARCHY的,那么使用下面的函数
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
    /* extended V2 interfaces to support hierarchy irq_domains */
    int (*alloc)(struct irq_domain *d, unsigned int virq,
             unsigned int nr_irqs, void *arg);                    //同map的作用
    void (*free)(struct irq_domain *d, unsigned int virq,
             unsigned int nr_irqs);
    void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
    void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
    int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
             unsigned long *out_hwirq, unsigned int *out_type);        //同unmap的作用
#endif
};

CONFIG_IRQ_DOMAIN_HIERARCHY:
  We plan to use hierarchy irqdomain to suppport CPU vector assignment, interrupt remapping controller, IO-APIC controller, MSI interrupt and hypertransport interrupt etc on x86 platforms. So extend irqdomain interfaces to support hierarchy irqdomain. There are already many clients of current irqdomain interfaces. To minimize the changes, we choose to introduce new version 2 interfaces to support hierarchy instead of extending existing irqdomain interfaces.
  According to Thomas’s suggestion, the most important design decision is to build hierarchy struct irq_data to support hierarchy irqdomain, so hierarchy irqdomain related data could be saved in struct irq_data.With support of hierarchy irq_data, we could also support stacked irq_chips. This is most useful in case of set_affinity().

  • 非层次函数接口

    • xlate函数,在DTS文件中,各个使用中断的device node会通过一些属性(例如interrupts和interrupt-parent属性)来提供中断信息给kernel以便kernel可以正确的进行driver的初始化动作。这里,interrupts属性所表示的interrupt specifier只能由具体的interrupt controller(也就是irq domain)来解析。而xlate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)。

    • match是判断一个指定的interrupt controller(node参数)是否和一个irq domain匹配(d参数),如果匹配的话,返回1。实际上,内核中很少定义这个callback函数,实际上struct irq_domain中有一个of_node指向了对应的interrupt controller的device node,因此,如果不提供该函数,那么default的匹配函数其实就是判断irq domain的of_node成员是否等于传入的node参数。

    • map和unmap,irq_domain_associate()调用map函数。

  这些设定不适合由具体的硬件驱动来设定,因此在Interrupt controller,也就是irq domain的callback函数中设定。

2.2.GIC代码分析

2.2.1.gic_init_bases()

 drivers/irqchip/irq-gic-v3.c:
 1071 static int __init gic_init_bases(void __iomem *dist_base,
  1072                  struct redist_region *rdist_regs,
  1073                  u32 nr_redist_regions,
  1074                  u64 redist_stride,
  1075                  struct fwnode_handle *handle)
  1076 {
  1077     u32 typer;
  1078     int gic_irqs;
  1079     int err;
  1080
  1081    //初始化struct gic_chip_data gic_data;
  1087     gic_data.fwnode = handle;
  1088     gic_data.dist_base = dist_base;
  1089     gic_data.redist_regions = rdist_regs;
  1090     gic_data.nr_redist_regions = nr_redist_regions;
  1091     gic_data.redist_stride = redist_stride;
  1092 
  1093     /*
  1094      * Find out how many interrupts are supported.
  1095      * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI)
  1096      */
  1097     typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
  1098     gic_data.rdists.gicd_typer = typer;
  1099     gic_irqs = GICD_TYPER_IRQS(typer);
  1100     if (gic_irqs > 1020)
  1101         gic_irqs = 1020;
  1102     gic_data.irq_nr = gic_irqs;
  1103 
  1104     gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,&gic_data);
  1106     irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
  1107     gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
  1108     gic_data.rdists.has_vlpis = true;
  1109     gic_data.rdists.has_direct_lpi = true;
  1116     gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
  1119 
  1120     if (typer & GICD_TYPER_MBIS) {
  1121         err = mbi_init(handle, gic_data.domain);
  1122         if (err)
  1123             pr_err("Failed to initialize MBIs\n");
  1124     }
  1125                                                                    
  1126     set_handle_irq(gic_handle_irq);
  1128     gic_update_vlpi_properties();
  1129 
  1130     if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
  1131         its_init(handle, &gic_data.rdists, gic_data.domain);
  1132 
  1133     gic_smp_init();
  1134     gic_dist_init();
  1135     gic_cpu_init();
  1136     gic_cpu_pm_init();
  1138     return 0;
  1145 }

  分配一个irq domain的数据结构,并调用irq_domain_create_tree进行注册,添加到链表irq_domain_list。每个interrupt controller都会填充为一个irq domain,负责解析其下游的interrut source。如果interrupt controller有级联的情况,那么一个非root interrupt controller的中断控制器也是其parent irq domain的一个普通的interrupt source。

  gic_init_bases函数重点分析:

  • 确认支持SPI 中断号最大的值为多少,GICv3最多支持1020个中断(SPI+SGI+SPI).GICD_TYPER寄存器bit[4:0], 如果该字段的值为N,则最大SPI INTID为32(N + 1)-1。 例如,0x00011指定最大SPI INTID为127。

  • 调用irq_domain_create_tree 向系统中注册一个irq domain的数据结构,该函数的重要两个参数为gic_irq_domain_ops,gic_data。irq_domain主要作用是将硬件中断号映射到IRQ number。

  • 用于区分MSI域。 比如一个域用作PCI/MSI, 一个域用作wired IRQS.

  • 判断GICD 是否支持rss, rss(Range Selector Support)表示SGI中断亲和性的范围 GICD_TYPER寄存器bit[26], 如果该字段为0,表示中断路由(IRI) 支持affinity 0-15的SGI,如果该字段为1, 表示支持affinity 0 - 255的SGI

  • 判断是否支持通过写GICD寄存器生成消息中断。GICD_TYPER寄存器bit[16]

  • 设置handle_arch_irq为gic_handle_irq。在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,从而进入GIC驱动,进行后续的中断处理。

  • 更新vlpi相关配置。gic虚拟化相关。

  • 初始化ITS。 Interrupt Translation Service, 用来解析LPI中断。 初始化之前需要先判断GIC是否支持LPI,该功能在ARM里是可选的。

  • gic_smp_init主要包含两个作用。

    • 触发SGI中断,用于CPU之间通信。当一个CPU core上的软件控制行为需要传递到其他的CPU上的时候,就会调用这个callback函数(例如在某一个CPU上运行的进程调用了系统调用进行reboot)。对于GIC v3,这个callback定义为gic_raise_softirq.
    • 设置CPU 上下线流程中和GIC相关的状态机。
  • 初始化GICD。

  • 初始化CPU interface.

  • 初始化GIC电源管理。

2.2.2.__irq_domain_add()

  第1104行函数irq_domain_create_tree调用__irq_domain_add,分配并初始化struct irq_domain,并将创建好的struct irq_domain加入全局链表irq_domain_list

struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
                    irq_hw_number_t hwirq_max, int direct_max,
                    const struct irq_domain_ops *ops,
                    void *host_data)
{
    struct device_node *of_node = to_of_node(fwnode);
    struct irq_domain *domain;

    domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                  GFP_KERNEL, of_node_to_nid(of_node));
    of_node_get(of_node);

    /* Fill structure */
    INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
    domain->ops = ops;
    domain->host_data = host_data;
    domain->fwnode = fwnode;
    domain->hwirq_max = hwirq_max;
    domain->revmap_size = size;
    domain->revmap_direct_max_irq = direct_max;
    irq_domain_check_hierarchy(domain);

    mutex_lock(&irq_domain_mutex);
    list_add(&domain->link, &irq_domain_list);
    mutex_unlock(&irq_domain_mutex);

    return domain;
}

3.Types of irq_domain mappings

  注册中断处理函数是唯一的中断号是 Kernel分配的全局唯一虚拟中断号,Linux通过一定方式将其和中断控制域的物理中断号关联。对每个中断控制域来讲,这种映射/关联主要有以下三类:

  • 线性映射(Linear):固定大小的数组映射(物理中断号为数组索引)。当该中断控制域的物理中断号较连续且数量不大时使用。
  • 基树映射(Radix Tree): 与线性映射相反,物理中断号比较大,或者不太连续时使用。
  • 直接映射(No Map/Diret): 有些中断控制器支持物理中断号编程,其被分配到的虚拟中断号可以被直接设定到中断控制器。
  • Legacy映射(传统映射): 特殊的一种映射模式。当需要固定分配一部分中断编号范围时使用。设备驱动可以直接使用约定的虚拟中断号来进行IRQ操作。

3.1.Linear

  The linear reverse map maintains a fixed size table indexed by the hwirq number. When a hwirq is mapped, an irq_desc is allocated for the hwirq, and the IRQ number is stored in the table.

  The Linear map is a good choice when the maximum number of hwirqs is fixed and a relatively small number (~ < 256).

  • The advantages of this map are fixed time lookup for IRQ numbers, and irq_descs are only allocated for in-use IRQs.
  • The disadvantage is that the table must be as large as the largest possible hwirq number.

  The majority of drivers should use the linear map.

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

irq_domain_add_linear()
irq_domain_create_linear()  

3.2.Tree

  The irq_domain maintains a radix tree map from hwirq numbers to Linux IRQs. When an hwirq is mapped, an irq_desc is allocated and the hwirq is used as the lookup key for the radix tree.

  The tree map is a good choice if the hwirq number can be very large since it doesn’t need to allocate a table as large as the largest hwirq number. The disadvantage is that hwirq to IRQ number lookup is dependent on how many entries are in the table.

  Very few drivers should need this mapping.

  建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。如果的确不能满足线性映射的条件,可以考虑Radix Tree map。对于Radix Tree map,其接口API如下:

irq_domain_add_tree()
irq_domain_create_tree()

3.3.No Map

  The No Map mapping is to be used when the hwirq number is programmable in the hardware. In this case it is best to program the Linux IRQ number into the hardware itself so that no mapping is required. Calling irq_create_direct_mapping() will allocate a Linux IRQ number and call the .map() callback so that driver can program the Linux IRQ number into the hardware.

  Most drivers cannot use this mapping.

  有些中断控制器很强,可以通过寄存器配置HW interrupt ID而不是由物理连接决定的。例如PowerPC 系统使用的MPIC (Multi-Processor Interrupt Controller)。在这种情况下,不需要进行映射,我们直接把IRQ number写入HW interrupt ID配置寄存器就OK了,这时候,生成的HW interrupt ID就是IRQ number,也就不需要进行mapping了。

irq_domain_add_nomap()

3.4.Legacy

  The Legacy mapping is a special case for drivers that already have a range of irq_descs allocated for the hwirqs. It is used when the driver cannot be immediately converted to use the linear mapping. For example, many embedded system board support files use a set of #defines for IRQ numbers that are passed to struct device registrations. In that case the Linux IRQ numbers cannot be dynamically assigned and the legacy mapping should be used.

  The legacy map assumes a contiguous range of IRQ numbers has already been allocated for the controller and that the IRQ number can be calculated by adding a fixed offset to the hwirq number, and visa-versa. The disadvantage is that it requires the interrupt controller to manage IRQ allocations and it requires an irq_desc to be allocated for every hwirq, even if it is unused.

  The legacy map should only be used if fixed IRQ mappings must be
supported. For example, ISA controllers would use the legacy map for
mapping Linux IRQs 0-15 so that existing ISA drivers get the correct IRQ
numbers.

  • irq_domain_add_simple()
  • irq_domain_add_legacy()
  • irq_domain_add_legacy_isa()

如下图所示:
在这里插入图片描述
在这里插入图片描述
  下图是针对3个控制器,结合nomap、linear、tree等3种映射方法,组成“irq_domain”的例子。
在这里插入图片描述

4.创建中断号映射

//创建HWIRQ映射,返回对应的虚拟中断号
extern unsigned int irq_create_mapping(struct irq_domain *host,
				       irq_hw_number_t hwirq);
				       
//根据Firmware Spec创建映射,一般与设备树DTS解析的信息有关
extern unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec);

//创建Direct Mapping
extern unsigned int irq_create_direct_mapping(struct irq_domain *host);

//创建固定映射
extern int irq_create_strict_mappings(struct irq_domain *domain,
				      unsigned int irq_base,
				      irq_hw_number_t hwirq_base,
                                      int count);

4.1.irq_create_mapping

  驱动调用该函数的时候必须提供HW interrupt ID,也就是意味着driver知道自己使用的HW interrupt ID,而一般情况下,HW interrupt ID其实对具体的driver应该是不可见的,不过有些场景比较特殊,例如GPIO类型的中断,它的HW interrupt ID和GPIO有着特定的关系,driver知道自己使用哪个GPIO,也就是知道使用哪一个HW interrupt ID了。
在这里插入图片描述
4.2.irq_create_strict_mappings

  用来为一组HW interrupt ID建立映射。

4.3.irq_create_of_mapping

  利用device tree进行映射关系的建立。

4.4.irq_create_direct_mapping

  给no map那种类型interrupt controller使用。

5.Mapping 建立

5.1.概述

  系统中HW interrupt ID和IRQ number的mapping 是在整个系统初始化的过程中建立起来的,过程如下:

  • DTS文件描述了系统中的interrupt controller以及外设IRQ的拓扑结构,在linux kernel启动的时候,由bootloader传递给kernel(实际传递的是DTB);

  • 在Device Tree初始化的时候,形成了系统内所有的device node的树状结构,当然其中包括所有和中断拓扑相关的数据结构(所有的interrupt controller的node和使用中断的外设node);

  • 在machine driver初始化的时候会调用of_irq_init函数,在该函数中会扫描所有interrupt controller的节点,并调用适合的interrupt controller driver进行初始化。毫无疑问,初始化需要注意顺序,首先初始化root,然后first level,second level,最好是leaf node。在初始化的过程中,一般会调用上节中的接口函数向系统增加irq domain。有些interrupt controller会在其driver初始化的过程中创建映射;

  • 在各个driver初始化的过程中,创建映射。

5.2. device node转化为platform_device(重点分析irq)

相关代码:
drivers/of/platform.c

  当系统启动到board文件时,会调用.init_machine,各个平台定义该函数,接着调用of_platform_populate()接口,加载平台总线和平台设备。函数调用关系:

of_platform_default_populate_init
    ---> of_platform_default_populate
         ---> of_platform_populate
            ---> of_platform_bus_create
                ---> of_platform_device_create_pdata
                    ---> of_device_alloc
                        ---> of_irq_to_resource_table
                            ---> of_irq_to_resource
                            	--->of_irq_get
                                    ---> of_irq_parse_one
                                    ---> irq_create_of_mapping
                                        ---> irq_create_fwspec_mapping
                                            ---> irq_domain_translate //解析参数
                                                ---> d->ops->translate (gic_irq_domain_translate)
                                                ---> d->ops->xlate  
                                            ---> irq_domain_alloc_irqs
                  								---> gic_irq_domain_alloc  //执行软硬件的映射,并且根据中断类型设置struct irq_desc->handle_irq处理函数             

实现如下:

 1 struct platform_device *of_device_alloc(struct device_node *np,
 2                   const char *bus_id,
 3                   struct device *parent)
 4 {
 5     struct platform_device *dev;
 6     int rc, i, num_reg = 0, num_irq;
 7     struct resource *res, temp_res;
 8     dev = platform_device_alloc("", -1);
 9     /* count the io and irq resources */
10   ...
11     num_irq = of_irq_count(np);       // 统计这个节点的interrupts属性中描述了几个中断
12     /* Populate the resource table */
13     if (num_irq || num_reg) {
14         res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
15    ...
16         dev->num_resources = num_reg + num_irq;
17         dev->resource = res;
18     ...
19     of_irq_to_resource_table(np, res, num_irq)  // 解析interrupts属性,将每一个中断转化为resource结构体
20     }
21     ...
22 }
  • of_irq_count

    这个函数会解析interrupts属性,并统计其中描述了几个中断。

  • of_irq_to_resource_table

    得知interrupts中描述了几个中断,开始将这些中断转换为resource。

  453 int of_irq_to_resource_table(struct device_node *dev, struct resource *res,                            
  454         int nr_irqs)
  455 {
  456     int i;
  457 
  458     for (i = 0; i < nr_irqs; i++, res++)
  459         if (of_irq_to_resource(dev, i, res) <= 0)
  460             break;
  461 
  462     return i;
  463 }
  • of_irq_to_resource:构建resource
 350 int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
  351 {
  352     int irq = of_irq_get(dev, index);
  356 
  357     /* Only dereference the resource if both the
  358      * resource and the irq are valid. */
  359     if (r && irq) {
  360         const char *name = NULL;
  361 
  362         memset(r, 0, sizeof(*r));
  363         /*
  364          * Get optional "interrupt-names" property to add a name
  365          * to the resource.
  366          */
  367         of_property_read_string_index(dev, "interrupt-names", index,
  368                           &name);
  369 
  370         r->start = r->end = irq;
  371         r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));                      
  372         r->name = name ? name : of_node_full_name(dev);
  373     }
  374 
  375     return irq;
  376 }

  重点分析 of_irq_get,这个函数会获得device node,如pinctrl@11000000节点的interrupts属性的第index个中断的参数,这是通过of_irq_parse_one完成的,然后获得该中断所隶属的interrupt-controller的irq domain,利用该domain的of_xlate函数从前面表示第index个中断的参数中解析出hwirq和中断类型,最后从系统中为该hwriq分配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中,也就是gic的irq domain。

  结合kernel代码分析:

of_irq_get
  ->of_irq_parse_one
  -> irq_create_of_mapping
    --> irq_create_fwspec_mapping
      —>gic_irq_domain_translate

of_irq_get:

388 int of_irq_get(struct device_node *dev, int index)
  389 {
  390     int rc;
  391     struct of_phandle_args oirq;
  392     struct irq_domain *domain;
  393 
  394     rc = of_irq_parse_one(dev, index, &oirq);  // 获得interrupts的第index个中断参数,并封装到oirq中
  398     domain = irq_find_host(oirq.np);                                                              
  401 
  402     return irq_create_of_mapping(&oirq);  //返回映射到的virq
  403 }

irq_create_fwspec_mapping:

 unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
 {
     struct irq_domain *domain;
     struct irq_data *irq_data;
     irq_hw_number_t hwirq;
     unsigned int type = IRQ_TYPE_NONE;
     int virq;
 // 根据中断控制器的device_node找到所对应的irq domain,在前面GIC驱动注册irq domian的时候,
 // 会将irq_domain的fwnode设置为中断控制器的device_node的fwnode成员
     if (fwspec->fwnode) {
         domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
         if (!domain)
             domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
     } else {
         domain = irq_default_domain;
     }
 // 对于GIC的irq domain来说,会调用d->ops->translate(d, fwspec, hwirq, type)
 // 也就是gic_irq_domain_translate,对于没有定义translate的irq_domain,会调用d->ops->xlate
 irq_domain_translate(domain, fwspec, &hwirq, &type);
	...
 // 从这个irq domain查询看该hwirq之前是否已经映射过,一般情况下都没有
     virq = irq_find_mapping(domain, hwirq);
     if (virq) {
	 ...
        return virq;
	 ...
     }
     
     if (irq_domain_is_hierarchy(domain)) {  // 对于GIC的irq domain这样定义了alloc的domain来说,走这个分支
         virq = irq_domain_alloc_irqs(domain, , NUMA_NO_NODE, fwspec);
     } else {  // 其他没有定义irq_domain->ops->alloc的domain,走这个分支
         /* Create mapping */
         virq = irq_create_mapping(domain, hwirq);
     }
     irq_data = irq_get_irq_data(virq);
     /* Store trigger type */
     irqd_set_trigger_type(irq_data, type);
     return virq; //返回映射到的virq
 }

gic irq domain的translate过程:

 1104 static int gic_irq_domain_translate(struct irq_domain *d,
  1105                     struct irq_fwspec *fwspec,
  1106                     unsigned long *hwirq,
  1107                     unsigned int *type)
  1108 {   // 检查描述中断的参数个数是否合法
  1109     if (is_of_node(fwspec->fwnode)) {                                                                 
  1110         if (fwspec->param_count < 3)
  1111             return -EINVAL;
  1112 
  1113         switch (fwspec->param[0]) {
  1114         case 0:         /* SPI */
  1115             *hwirq = fwspec->param[1] + 32;
  1116             break;
  1117         case 1:         /* PPI */
  1118         case GIC_IRQ_TYPE_PARTITION:
  1119             *hwirq = fwspec->param[1] + 16;
  1120             break;
  1121         case GIC_IRQ_TYPE_LPI:  /* LPI */
  1122             *hwirq = fwspec->param[1];
  1123             break;
  1124         default:
  1125             return -EINVAL;
  1126         }
  1127 
  1128         *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
  1130         /*
  1131          * Make it clear that broken DTs are... broken.
  1132          * Partitionned PPIs are an unfortunate exception.
  1133          */
  1134         WARN_ON(*type == IRQ_TYPE_NONE &&
  1135             fwspec->param[0] != GIC_IRQ_TYPE_PARTITION);
  1136         return 0;
  1137     }
  1138 
  1139     if (is_fwnode_irqchip(fwspec->fwnode)) {
  1140         if(fwspec->param_count != 2)
  1141             return -EINVAL;
  1142 
  1143         *hwirq = fwspec->param[0];
  1144         *type = fwspec->param[1];
  1145 
  1146         WARN_ON(*type == IRQ_TYPE_NONE);
  1147         return 0;
  1148     }                                                                                                 
  1149 
  1150     return -EINVAL;
  1151 }
 }

6.Debug

  CONFIG_IRQ_DOMAIN_DEBUG and CONFIG_GENERIC_IRQ_DEBUGFS is enabled. virq_debug_show and irq_debug_show functions show the debug information.

  • /sys/kernel/debug/irq_domain_mapping
  • /sys/kernel/irq
    • /sys/kernel/irq/irqs/
    • /sys/kernel/irq/domains
  • /proc/irq

6.1.代码

  • /kernel/irq/irqdomain.c
  • /kernel/irq/debugfs.c

refer to

  • https://l2h.site/2019/01/01/linux-interrupt-3/
  • https://www.cnblogs.com/pengdonglin137/p/6847685.html
  • https://blog.csdn.net/qq_33513098/article/details/88723282#s3c24xx_irq_map_of_441
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐