关闭

linux gic驱动

标签: armgic驱动
2084人阅读 评论(0) 收藏 举报
分类:

GIC———-ARM Generic Interrupt Controller

一、GIC简介:
GIC是的ARM研发的一个通用的中断控制器,它在硬件上的实现形态分为两种:
一种是ARM体系中的半导体公司在研发自己的SOC的时候,向ARM公司购买GIC的IP,这些GIC的型号有:GIC-400,GIC-500等等。另一种形态是ARM vensor直接购买ARM公司已经集成了GIC的多核方案,比如Cortex A9或者A15的IP,Cortex A9或者A15中会包括了GIC的实现,这些实现是符合GIC V2的规格。
ARM SMP多核处理器一般都会搭载一个GIC来提供中断控制功能。本章是基于Cortex A9平台来做介绍。ARM平台上一般把中断分为三种类型,分别是PPI(per processor interrupts)、SPI(shared processor interrupts)和SGI(software generated interrupts)。
主GIC是直接连接到CPU上的,并且除了SPI,还拥有PPI和SGI中断。而第二个GIC是级联到主GIC上的,它只有SPI中断,没有PPI和SGI中断。

硬件中断号的分配:
(1)ID0~ID31
是用于分发到一个特定的process的interrupt。标识这些interrupt不能仅仅依靠ID,还必须指定process的ID,因此识别这些interrupt需要interrupt ID + CPU interface number。
(a)ID0~ID15属于SGI中断,SGI是通过软件写入GIC的GICD_SGIR寄存器而触发的中断,它可以用于processor之间的通信。 GIC通过processor source ID、中断ID和target processor ID来唯一识别一个SGI。
(b)ID16~ID31属于PPI中断,PPI类型的中断和SPI一样属于外设的中断,区别就是它会被送到其私有的process上,而和其他的process无关。

(2)ID32~ID1019用于SPI。 这是GIC规范的最大范围,实际上Cortex-A15和A9上的GIC最多支持224个SPI。

二、GIC驱动
在kernel/drivers/irqchip目录下保存在各种不同的中断控制器的驱动代码, irq-gic.c是GIC的驱动代码。

1、device node和irq chip driver的匹配过程
(1)irq chip driver中的声明
在irqchip.h文件中定义了IRQCHIP_DECLARE宏如下:


#define IRQCHIP_DECLARE(name,compstr,fn)                \
    static const struct of_device_id irqchip_of_match_##name    \
    __used __section(__irqchip_of_table)                \
    = { .compatible = compstr, .data = fn }
#endif

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

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(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

兼容GIC-V2的GIC实现有很多,不过其初始化函数都是gic_of_init。编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(section name是__irqchip_of_table),我们称这个特殊的section叫做irq chip table。这个table也就保存了kernel支持的所有的中断控制器的ID信息.

struct of_device_id 这个数据结构主要被用来进行Device node和driver模块进行匹配用的。从该数据结构的定义可以看出,在匹配过程中,device name、device type和DT compatible string都是考虑的因素。更细节的内容请参考__of_device_is_compatible函数。
(2)device node
不同的GIC-V2的实现总会有一些不同,这些信息可以通过Device tree的机制来传递。可以通过查看Documentation/devicetree/bindings/arm/gic.txt文件来确认配置规则。
以cortex-a9-gic为例。

Example:
intc: interrupt-controller@fff11000 {
        compatible = "arm,cortex-a9-gic";
        #interrupt-cells = <3>;
        #address-cells = <1>;
        interrupt-controller;
        reg = <0xfff11000 0x1000>,
              <0xfff10100 0x100>;
    }; 

(3)device node和irq chip driver的匹配
在系统启动machine初始化的时候会调用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支持的所有的中断控制器的of_device_id信息。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.驱动代码
当设备device node和irq chip driver匹配以后,我们就进入函数gic_of_init开始了GIC的初始化工作。

#ifdef CONFIG_OF
static int gic_cnt __initdata;

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;

    if (WARN_ON(!node))
        return -ENODEV;

    dist_base = of_iomap(node, 0);
    WARN(!dist_base, "unable to map gic dist registers\n");

    cpu_base = of_iomap(node, 1);
    WARN(!cpu_base, "unable to map gic cpu registers\n");

    if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
        percpu_offset = 0;

    gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
    if (!gic_cnt)
        gic_init_physaddr(node);

    if (parent) {
        irq = irq_of_parse_and_map(node, 0);
        gic_cascade_irq(gic_cnt, irq);
    }
    gic_cnt++;
    return 0;
}

这个函数调用的最关键的函数就是gic_init_bases,它完成了主要的工作,其中就包括了irq domain的注册,通过irq_domain_add_legacy完成了注册过程,主要就是建立hwirq和内核中的irq num之间的映射关系。之后就是irq domain来负责对中断号进行转换并处理了。

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;

    BUG_ON(gic_nr >= MAX_GIC_NR);

    gic = &gic_data[gic_nr];
#ifdef CONFIG_GIC_NON_BANKED
    if (percpu_offset) { /* Frankein-GIC without banked registers... */
        unsigned int cpu;

        gic->dist_base.percpu_base = alloc_percpu(void __iomem *);
        gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);
        if (WARN_ON(!gic->dist_base.percpu_base ||
                !gic->cpu_base.percpu_base)) {
            free_percpu(gic->dist_base.percpu_base);
            free_percpu(gic->cpu_base.percpu_base);
            return;
        }

        for_each_possible_cpu(cpu) {
            unsigned long offset = percpu_offset * cpu_logical_map(cpu);
            *per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset;
            *per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset;
        }

        gic_set_base_accessor(gic, gic_get_percpu_base);
    } else
#endif
    {           /* Normal, sane GIC... */
        WARN(percpu_offset,
             "GIC_NON_BANKED not enabled, ignoring %08x offset!",
             percpu_offset);
        gic->dist_base.common_base = dist_base;
        gic->cpu_base.common_base = cpu_base;
        gic_set_base_accessor(gic, gic_get_common_base);
    }

    /*
     * Initialize the CPU interface map to all CPUs.
     * It will be refined as each CPU probes its ID.
     */
    for (i = 0; i < NR_GIC_CPU_IF; i++)
        gic_cpu_map[i] = 0xff;

    /*
     * For primary GICs, skip over SGIs.
     * For secondary GICs, skip over PPIs, too.
     */
    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;
    }

    /*
     * Find out how many interrupts are supported.
     * The GIC only supports up to 1020 interrupt sources.
     */
    gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
    gic_irqs = (gic_irqs + 1) * 32;
    if (gic_irqs > 1020)
        gic_irqs = 1020;
    gic->gic_irqs = gic_irqs;

    gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
    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,
                    hwirq_base, &gic_irq_domain_ops, gic);
    if (WARN_ON(!gic->domain))
        return;

#ifdef CONFIG_SMP
    set_smp_cross_call(gic_raise_softirq);
    register_cpu_notifier(&gic_cpu_notifier);
#endif

    set_handle_irq(gic_handle_irq);

    gic_chip.flags |= gic_arch_extn.flags;
    gic_dist_init(gic);
    gic_cpu_init(gic);
    gic_pm_init(gic);
}

作为一个interrupt controller,除了注册自己管理的irq_domain,还需要提供给上级使用的irq_handler,如果作为second GIC,上级是root GIC,那么就需要调用irq_set_chained_handler注册irq_handler到root GIC中;如果作为root GIC,上级是CPU,就需要调用set_handle_irq(gic_handle_irq)把这个irq_handler注册到平台的irq处理接口中,这条语句的功能就是,当CPU发生了中断最先调用的就是root GIC的gic_handle_irq,然后在此函数中进行gic的irq domain处理。

三、irq domain的注册
Irq_domain的注册,需要一个irq_domain_ops的结构体,我们来看一下他的定义:

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, 
};

对于struct irq_domain_ops,它有如下几个callback成员:

struct irq_domain_ops { 
    int (*match)(struct irq_domain *d, struct device_node *node); 
    int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); 
    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); 
}; 

xlate是负责翻译的回调函数,在dts文件中,各个设备通过一些属性,例如interrupts和interrupt-parent来提供中断信息给kernel和驱动,而xlate函数就是将指定的设备上若干个中断属性翻译成hwirq和trigger类型,比如对于#interrupt-cells = <3>;的中断控制器来说,描述该域中的一个interrupt需要三个cell来表示,那么这三个cell就是通过xlate来解析的。

match用来判断interrupt controller是否和一个irq domain匹配的,如果是就返回1。实际上,该callback函数很少被设置,内核中提供了默认的匹配函数,就是通过of node来进行匹配的(irq_domain结构体中会保存一个of node)。

map和unmap是映射和解除映射操作。Map回调函数是在创建hwirq到irq num关系的时候被调用的,注册irq domain只是一个空的关系表,而这个是实质上关系的创建是在irq_of_parse_and_map里面进行的。在map回调函数中,我们一般需要做如下几个操作:

irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
irq_set_chip_data(irq, d->host_data);

其中irq_set_chip_and_handler函数是用来设置irq chip和相应的上层irq handler的,一般内核中断子系统已经实现了相应的函数,我们只需要按需赋值即可,它负责对一个irq num调用所有通过irq_request注册的irq handler.我们称之为上层中断服务程序。

以上注册的回调函数的调用流程如下所示:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index) 
{ 
    struct of_phandle_args oirq; 
    if (of_irq_parse_one(dev, index, &oirq))----分析device node中的interrupt相关属性 
        return 0; 
return irq_create_of_mapping(&oirq);-----创建映射 
} 

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data) 
{ 
    struct irq_domain *domain; 
    irq_hw_number_t hwirq; 
    unsigned int type = IRQ_TYPE_NONE; 
    unsigned int virq; 
domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;
 if (!domain) { 
        return 0; 
    } 

    if (domain->ops->xlate == NULL) 
        hwirq = irq_data->args[0]; 
    else { 
        if (domain->ops->xlate(domain, irq_data->np, irq_data->args, 
                    irq_data->args_count, &hwirq, &type)) 
            return 0; 
    } 
    /* Create mapping */ 
    virq = irq_create_mapping(domain, hwirq); 
    if (!virq) 
        return virq; 
    /* Set type if specified and different than the current one */ 
    if (type != IRQ_TYPE_NONE && 
        type != irq_get_trigger_type(virq)) 
        irq_set_irq_type(virq, type); 
    return virq; 
} 

unsigned int irq_create_mapping(struct irq_domain *domain, 
                irq_hw_number_t hwirq) 
{ 
    unsigned int hint; 
    int virq; 

    virq = irq_find_mapping(domain, hwirq); //如果映射已经存在,那么不需要映射,直接返回
    if (virq) { 
        return virq; 
    } 

    hint = hwirq % nr_irqs;-------分配一个IRQ 描述符以及对应的irq number 
    if (hint == 0) 
        hint++; 
    virq = irq_alloc_desc_from(hint, of_node_to_nid(domain->of_node)); 
    if (virq <= 0) 
        virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node)); 
    if (virq <= 0) { 
        pr_debug("-> virq allocation failed\n"); 
        return 0; 
    } 
    if (irq_domain_associate(domain, virq, hwirq)) {---建立mapping 
        irq_free_desc(virq); 
        return 0; 
    } 
return virq; 
} 

int irq_domain_associate(struct irq_domain *domain, unsigned int virq, 
             irq_hw_number_t hwirq) 
{ 
    struct irq_data *irq_data = irq_get_irq_data(virq); 
    int ret; 
    mutex_lock(&irq_domain_mutex); 
    irq_data->hwirq = hwirq; 
    irq_data->domain = domain; 
    if (domain->ops->map) { 
        ret = domain->ops->map(domain, virq, hwirq);---调用irq domain的map callback函数 
    } 
    if (hwirq < domain->revmap_size) { 
        domain->linear_revmap[hwirq] = virq;----填写线性映射lookup table的数据 
    } else { 
        mutex_lock(&revmap_trees_mutex); 
        radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);--向radix tree插入一个node 
        mutex_unlock(&revmap_trees_mutex); 
    } 
    mutex_unlock(&irq_domain_mutex); 
    irq_clear_status_flags(virq, IRQ_NOREQUEST); ---该IRQ已经可以申请了,因此clear相关flag 
    return 0; 
} 

四、中断调用流程
上面已经提到,一个root gic驱动除了提供irq domain以外,还要注册到CPU中断服务程序入口,而这个中断服务的入口就是gic_handle_irq。


asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    do {
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        irqnr = irqstat & ~0x1c00;

        if (likely(irqnr > 15 && irqnr < 1021)) {
            irqnr = irq_find_mapping(gic->domain, irqnr);
            handle_IRQ(irqnr, regs);
            continue;
        }
        if (irqnr < 16) {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}

如上所示,中断来的时候会最先调用这个函数,它中会读取GIC寄存器获得hwirq,并且查找对应的irq num,irq_find_mapping是查找irq domain中映射关系的关键函数。然后会调用handle_IRQ来处理对应的irq num,紧接着会调用相应的上层irq handler。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:31126次
    • 积分:630
    • 等级:
    • 排名:千里之外
    • 原创:30篇
    • 转载:6篇
    • 译文:1篇
    • 评论:2条
    文章分类
    最新评论