GIC-V2 在 Linux 层的初始化分析

 

1、GIC 的 device node和GIC irq chip driver的匹配过程


(1)irq chip driver中的声明

在 drivers\irqchip\irqchip.h文件中定义了 IRQCHIP_DECLARE :

#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)


#define OF_DECLARE_2(table, name, compat, fn) \ 
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)


#define _OF_DECLARE(table, name, compat, fn, fn_type)            \ 
    static const struct of_device_id __of_table_##name        \ 
        __used __section(__##table##_of_table)            \ 
         = { .compatible = compat,                \ 
             .data = (fn == (fn_type)NULL) ? fn : fn  }

这个宏其实就是初始化了一个struct of_device_id的静态常量,并放置在__irqchip_of_table section中。irq-gic.c 文件中使用IRQCHIP_DECLARE来定义了若干个静态的struct of_device_id常量,如下

IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init); 
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init); 
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init); 
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init); 
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init); 
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

兼容GIC-V2的GIC实现有很多,不过其初始化函数都是一个。在linux kernel编译的时候,你可以配置多个irq chip进入内核,编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(section name是__irqchip_of_table),我们称这个特殊的section叫做irq chip table。这个table也就保存了kernel支持的所有的中断控制器的ID信息(最重要的是驱动代码初始化函数和DT compatible string)。我们来看看struct of_device_id的定义

struct of_device_id 
{ 
    char    name[32];------要匹配的device node的名字 
    char    type[32];-------要匹配的device node的类型 
    char    compatible[128];---匹配字符串(DT compatible string),用来匹配适合的 device node 
    const void *data;--------对于GIC,这里是初始化函数指针 
};

这个数据结构主要被用来进行Device node和driver模块进行匹配用的。从该数据结构的定义可以看出,在匹配过程中,device name、device type和DT compatible string都是考虑的因素。更细节的内容请参考__of_device_is_compatible函数。

 

(2)device node

不同的GIC-V2的实现总会有一些不同,这些信息可以通过Device tree的机制来传递。Device node中定义了各种属性,其中就包括了memory资源,IRQ描述等信息,这些信息需要在初始化的时候传递给具体的驱动,因此需要一个Device node和driver模块的匹配过程。在Device Tree模块中会包括系统中所有的device node,如果我们的系统使用了GIC-400,那么系统的device node数据库中会有一个node是GIC-400的,一个示例性的GIC-400的 device node:

gic: interrupt-controller@ffc01000 { 
    compatible = "arm,gic-400"; 
    interrupt-controller; 
    #interrupt-cells = <3>; 
    #address-cells = <0>;

    reg = <0xffc01000 0x1000="">,----Distributor address range 
          <0xffc02000 0x1000="">,-----CPU interface address range 
          <0xffc04000 0x2000="">,-----Virtual interface control block 
          <0xffc06000 0x2000="">;-----Virtual CPU interfaces 
    interrupts = ; 
};

(3)device node和irq chip driver的匹配

在machine driver初始化的时候会调用irqchip_init函数进行irq chip driver的初始化。在driver/irqchip/irqchip.c文件中定义了irqchip_init函数,如下:

void __init irqchip_init(void) 
{ 
    of_irq_init(__irqchip_begin); 
}

__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的ID信息(用于和device node的匹配)。of_irq_init函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点都已经形成了一个树状结构,每个节点代表一个设备的device node。of_irq_init是在所有的device node中寻找中断控制器节点,形成树状结构(系统可以有多个interrupt controller,之所以形成中断控制器的树状结构,是为了让系统中所有的中断控制器驱动按照一定的顺序进行初始化)。之后,从root interrupt controller节点开始,对于每一个interrupt controller的device node,扫描irq chip table,进行匹配,一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为参数传递给irq chip driver。具体的匹配过程的代码属于Device Tree模块的内容.

 

2、GIC driver 初始化代码分析


(1)gic_of_init 的代码如下:

int __init gic_of_init(struct device_node *node, struct device_node *parent) 
{ 
    void __iomem *cpu_base; 
    void __iomem *dist_base; 
    u32 percpu_offset; 
    int irq;

    dist_base = of_iomap(node, 0);----------------映射GIC Distributor的寄存器地址空间

    cpu_base = of_iomap(node, 1);----------------映射GIC CPU interface的寄存器地址空间

    if (of_property_read_u32(node, "cpu-offset", &percpu_offset))--------处理cpu-offset属性。 
        percpu_offset = 0;

    gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);))-----主处理过程,后面详述 
    if (!gic_cnt) 
        gic_init_physaddr(node); -----对于不支持big.LITTLE switcher(CONFIG_BL_SWITCHER)的系统,该函数为空。

    if (parent) {---------------------------------处理interrupt级联 
        irq = irq_of_parse_and_map(node, 0); -----解析second GIC的interrupts属性,并进行mapping,返回IRQ number 
        gic_cascade_irq(gic_cnt, irq); 
    } 
    gic_cnt++; 
    return 0; 
}

(2)gic_init_bases 的代码如下:

void __init gic_init_bases(unsigned int gic_nr, int irq_start, 
               void __iomem *dist_base, void __iomem *cpu_base, 
               u32 percpu_offset, struct device_node *node) 
{ 
    irq_hw_number_t hwirq_base; 
    struct gic_chip_data *gic; 
    int gic_irqs, irq_base, i;

    gic = &gic_data[gic_nr];  
    gic->dist_base.common_base = dist_base;
    gic->cpu_base.common_base = cpu_base;  
    gic_set_base_accessor(gic, gic_get_common_base);


    for (i = 0; i < NR_GIC_CPU_IF; i++)
        gic_cpu_map[i] = 0xff;


    if (gic_nr == 0 && (irq_start & 31) > 0) {
        hwirq_base = 16; 
        if (irq_start != -1) 
            irq_start = (irq_start & ~31) + 16; 
    } else { 
        hwirq_base = 32; 
    }


    gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; ----(a) 
    gic_irqs = (gic_irqs + 1) * 32; 
    if (gic_irqs > 1020) 
        gic_irqs = 1020; 
    gic->gic_irqs = gic_irqs;

    gic_irqs -= hwirq_base;
   

    if (of_property_read_u32(node, "arm,routable-irqs", 
                 &nr_routable_irqs)) { 
        irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,  numa_node_id());
        if (IS_ERR_VALUE(irq_base)) { 
            WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", 
                 irq_start); 
            irq_base = irq_start; 
        }

        gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, -------(b) 
                    hwirq_base, &gic_irq_domain_ops, gic); 
    } else { 
        gic->domain = irq_domain_add_linear(node, nr_routable_irqs, --------(b) 
                            &gic_irq_domain_ops, 
                            gic); 
    }

    if (gic_nr == 0) { ---只对root GIC操作,因为设定callback、注册Notifier只需要一次就OK了 
#ifdef CONFIG_SMP 
        set_smp_cross_call(gic_raise_softirq);
        register_cpu_notifier(&gic_cpu_notifier); 
#endif 
        set_handle_irq(gic_handle_irq); ---(c) 
    }

    gic_chip.flags |= gic_arch_extn.flags; 
    gic_dist_init(gic);--------- GIC Distributer   部分初始化
    gic_cpu_init(gic); --------- GIC CPU Interface 部分初始化
    gic_pm_init(gic);  --------- GIC PM            部分初始化
}

(a). 读取GIC 的最大支持的中断数目,从 GIC_DIST_CTR 寄存器(这是V1版本的寄存器名字,V2中是GICD_TYPER,Interrupt Controller Type Register,)的低五位ITLinesNumber获取的。如果ITLinesNumber等于N,那么最大支持的中断数目是32(N+1)。此外,GIC规范规定最大的中断数目不能超过1020,1020-1023是有特别用户的interrupt ID。

(b). 分配一个 irq_domain 的结构,一个 irq_domain 代表了一个 GIC 控制器

(c). 设置中断响应函数入口为:gic_handle_irq

 

(3). gic_init_bases->irq_domain_add_linear()->__irq_domain_add()

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

    domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                  GFP_KERNEL, of_node_to_nid(of_node));-------------domain大小为struct irq_domain加上gic_irqs个unsigned int。
    if (WARN_ON(!domain))
        return NULL;

    /* Fill structure */
    INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
    domain->ops = ops;
    domain->host_data = host_data;
    domain->of_node = of_node_get(of_node);
    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);----------------------将创建好的struct irq_domain加入全局链表irq_domain_list。
    mutex_unlock(&irq_domain_mutex);

    pr_debug("Added domain %s\n", domain->name);
    return domain;
}

这里分配了一个 irq_domain 数据结构来表示 GIC,并继续进行一些结构的赋值,这里关系一下 ops 和 host_data 数据。

static const struct irq_domain_ops gic_irq_domain_ops = { 
    .map = gic_irq_domain_map, 
    .unmap = gic_irq_domain_unmap, 
    .xlate = gic_irq_domain_xlate, 
};

host-data 传入的是 gic_chip_data 是:

    struct gic_chip_data *gic; 
    int gic_irqs, irq_base, i;

    gic = &gic_data[gic_nr];  

irq_domain_add_linear(node, nr_routable_irqs,
                            &gic_irq_domain_ops, 
                            gic); 

最后把这个 irq domain 加入到了 irq domain list 链表。

 

在 gic_init_bases 函数最后,完成了 GIC 的 硬件初始化:

    gic_dist_init(gic);--------- GIC Distributer   部分初始化
    gic_cpu_init(gic); --------- GIC CPU Interface 部分初始化
    gic_pm_init(gic);  --------- GIC PM            部分初始化

总的来说:

 

 

参考文档:

http://www.wowotech.net/linux_kenrel/irq-domain.html

http://www.wowotech.net/linux_kenrel/gic_driver.html

https://www.cnblogs.com/arnoldlu/p/8659981.html

Logo

更多推荐