首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

GIC代码分析(6)

GIC代码分析(6)

(c)step (b)中获取了8个bit的cpu mask值,通过简单的copy,扩充为32个bit,每8个bit都是cpu mask的值,这么做是为了下一步设定所有IRQ(对于GIC而言就是SPI类型的中断)的CPU mask。
        (d)设定每个SPI类型的中断都是只送达该CPU。
        (e)配置GIC distributor的其他寄存器,代码如下:
                        void __init gic_dist_config(void __iomem *base, int gic_irqs,  void (*sync_access)(void))
{
    unsigned int i;       
                            /* Set all global interrupts to be level triggered, active low.    */
    for (i = 32; i < gic_irqs; i += 16)
        writel_relaxed(0, base + GIC_DIST_CONFIG + i / 4);       
                            /* Set priority on all global interrupts.   */
    for (i = 32; i < gic_irqs; i += 4)
        writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i);       
                            /* Disable all interrupts.  Leave the PPI and SGIs alone as they are enabled by redistributor registers.    */
    for (i = 32; i < gic_irqs; i += 32)
        writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i / 8);       
                            if (sync_access)
        sync_access();
}       
        程序的注释已经非常清楚了,这里就不细述了。需要注意的是:这里设定的都是缺省值,实际上,在各种driver的初始化过程中,还是有可能改动这些设置的(例如触发方式)。

        (2)CPU interface初始化,代码如下:
                        static void gic_cpu_init(struct gic_chip_data *gic)
{
    void __iomem *dist_base = gic_data_dist_base(gic);-------Distributor的基地址空间
    void __iomem *base = gic_data_cpu_base(gic);-------CPU interface的基地址空间
    unsigned int cpu_mask, cpu = smp_processor_id();------获取CPU的逻辑ID
    int i;       
                       
    cpu_mask = gic_get_cpumask(gic);-------------(a)
    gic_cpu_map[cpu] = cpu_mask;       
                       
    for (i = 0; i < NR_GIC_CPU_IF; i++)
        if (i != cpu)
            gic_cpu_map &= ~cpu_mask; ------------(b)       
                            gic_cpu_config(dist_base, NULL); --------------(c)       
                            writel_relaxed(0xf0, base + GIC_CPU_PRIMASK);-------(d)
    writel_relaxed(1, base + GIC_CPU_CTRL);-----------(e)
}       
        (a)系统软件实际上是使用CPU 逻辑ID这个概念的,通过smp_processor_id可以获得本CPU的逻辑ID。gic_cpu_map这个全部lookup table就是用CPU 逻辑ID作为所以,去寻找其cpu mask,后续通过cpu mask值来控制中断是否送达该CPU。在gic_init_bases函数中,我们将该lookup table中的值都初始化为0xff,也就是说不进行mask,送达所有的CPU。这里,我们会进行重新修正。
        (b)清除lookup table中其他entry中本cpu mask的那个bit。
        (c)设定SGI和PPI的初始值。具体代码如下:
                        void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
{
    int i;       
                            /* Deal with the banked PPI and SGI interrupts - disable all
     * PPI interrupts, ensure all SGI interrupts are enabled.     */
    writel_relaxed(0xffff0000, base + GIC_DIST_ENABLE_CLEAR);
    writel_relaxed(0x0000ffff, base + GIC_DIST_ENABLE_SET);       
                            /* Set priority on PPI and SGI interrupts    */
    for (i = 0; i < 32; i += 4)
        writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);       
                            if (sync_access)
        sync_access();
}       
        程序的注释已经非常清楚了,这里就不细述了。
        (d)通过Distributor中的寄存器可以控制送达CPU interface,中断来到了GIC的CPU interface是否可以真正送达CPU呢?也不一定,还有一道关卡,也就是CPU interface中的Interrupt Priority Mask Register。这个寄存器设定了一个中断优先级的值,只有中断优先级高过该值的中断请求才会被送到CPU上去。我们在前面初始化的时候,给每个interrupt ID设定的缺省优先级是0xa0,这里设定的priority filter的优先级值是0xf0。数值越小,优先级越过。因此,这样的设定就是让所有的interrupt source都可以送达CPU,在CPU interface这里不做控制了。
        (e)设定CPU interface的control register。enable了group 0的中断,disable了group 1的中断,group 0的interrupt source触发IRQ中断(而不是FIQ中断)。

        (3)GIC电源管理初始化,代码如下:
                        static void __init gic_pm_init(struct gic_chip_data *gic)
{
    gic->saved_ppi_enable = __alloc_percpu(DIV_ROUND_UP(32, 32) * 4, sizeof(u32));       
                            gic->saved_ppi_conf = __alloc_percpu(DIV_ROUND_UP(32, 16) * 4,  sizeof(u32));       
                            if (gic == &gic_data[0])
        cpu_pm_register_notifier(&gic_notifier_block);
}       
        这段代码前面主要是分配两个per cpu的内存。这些内存在系统进入sleep状态的时候保存PPI的寄存器状态信息,在resume的时候,写回寄存器。对于root GIC,需要注册一个和电源管理的事件通知callback函数。不得不吐槽一下gic_notifier_block和gic_notifier这两个符号的命名,看不出来和电源管理有任何关系。更优雅的名字应该包括pm这样的符号,以便让其他工程师看到名字就立刻知道是和电源管理相关的。

        四、GIC callback函数分析
        1、irq domain相关callback函数分析
        irq domain相关callback函数包括:
        (1)gic_irq_domain_map函数:创建IRQ number和GIC hw interrupt ID之间映射关系的时候,需要调用该回调函数。具体代码如下:
                        static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
    if (hw < 32) {------------------SGI或者PPI
        irq_set_percpu_devid(irq);--------------------------(a)
        irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);-------(b)
        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);--------------(c)
    } else {
        irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);----------(d)
        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);       
                                gic_routable_irq_domain_ops->map(d, irq, hw);----------------(e)
    }
    irq_set_chip_data(irq, d->host_data);-----设定irq chip的私有数据
    return 0;
}       
        (a)SGI或者PPI和SPI最大的不同是per cpu的,SPI是所有CPU共享的,因此需要分配per cpu的内存,设定一些per cpu的flag。
        (b)设定该中断描述符的irq chip和high level的handler
        (c)设定irq flag是有效的(因为已经设定好了chip和handler了),并且request后不是auto enable的。
        (d)对于SPI,设定的high level irq event handler是handle_fasteoi_irq。对于SPI,是可以probe,并且request后是auto enable的。
        (e)有些SOC会在各种外设中断和GIC之间增加cross bar(例如TI的OMAP芯片),这里是为那些ARM SOC准备的

        (2)gic_irq_domain_unmap是gic_irq_domain_map的逆过程也就是解除IRQ number和GIC hw interrupt ID之间映射关系的时候,需要调用该回调函数。
        (3)gic_irq_domain_xlate函数:除了标准的属性之外,各个具体的interrupt controller可以定义自己的device binding。这些device bindings都需在irq chip driver这个层面进行解析。要给定某个外设的device tree node 和interrupt specifier,该函数可以解码出该设备使用的hw interrupt ID和linux irq type value 。具体的代码如下:
                        static int gic_irq_domain_xlate(struct irq_domain *d,
                struct device_node *controller,
                const u32 *intspec, unsigned int intsize,--------输入参数
                unsigned long *out_hwirq, unsigned int *out_type)----输出参数
{
    unsigned long ret = 0;  
    *out_hwirq = intspec[1] + 16; ---------------------(a)       
                            *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; -----------(b)       
                            return ret;
}       
        (a)根据gic binding文档的描述,其interrupt specifier包括3个cell,分别是interrupt type(0 表示SPI,1表示PPI),interrupt number(对于PPI,范围是[0-15],对于SPI,范围是[0-987]),interrupt flag(触发方式)。GIC interrupt specifier中的interrupt number需要加上16(也就是加上SGI的那些ID号),才能转换成GIC的HW interrupt ID。
        (b)取出bits[3:0]的信息,这些bits保存了触发方式的信息
继承事业,薪火相传
返回列表