linux gic驱动

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。

时间: 2024-10-12 04:47:02

linux gic驱动的相关文章

《Linux设备驱动开发详解(第3版)》海量更新总结

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]添加关于QEMU模拟vexpress板的描述 第2章 <驱动设计的硬件基础> [N]增加关于SoC的介绍:[N]增加关于eFuse的内容:[D]删除ISA总线的内容了:[N]增加关于SP

《Linux设备驱动开发具体解释(第3版)》进展同步更新

本博实时更新<Linux设备驱动开发具体解释(第3版)>的最新进展. 2015.2.26 差点儿完毕初稿. 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTEX-A9平台. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]加入关于QEMU模拟vexpress板的描写叙述 第2章 <驱动设计的硬件基础> [

linux无线网卡驱动安装

环境  在笔记本里的虚拟机10.0版本,centos-6.5 无线网卡fast-fw300um 第一步要查看芯片  lsusb  当你得到芯片之后接下来查看内核,如果内核已经有芯片模块就不用再装了,如果不支持的话,那么接下来就到芯片官网 下载Linux驱动 http://www.realtek.com.tw/default.aspx  **虽然我的无线网卡是fast 生产的 ,但是他并没有给我们Linux的驱动,反倒是芯片商提供有驱动,所以要到芯片官网下载驱动** 首先到官网上下载无线网卡的驱动

linux设备驱动第五篇:驱动中的并发与竟态

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争. 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时.并行被执行.而并发的执行单元对共享资源(硬件资源和软件上的全局.静态变量)的访问则容易导致竞态(race conditions).可能导致并发和竟态的情况有: SMP(Symmetric Multi-Processing),对称多处理结构.SMP是一种紧耦合.共享存储的系统模型,它的特点是多个CPU使用共

Linux LCD驱动(三)--图形显示

3.  BMP和JPEG图形显示程序3.1  在LCD上显示BMP或JPEG图片的主流程图首先,在程序开始前.要在nfs/dev目录下创建LCD的设备结点,设备名fb0,设备类型为字符设备,主设备号为29,次设备号为0.命令如下:mknod fb0 c 29 0在LCD上显示图象的主流程图如图3.1所示.程序一开始要调用open函数打开设备,然后调用ioctl获取设备相关信息,接下来就是读取图形文件数据,把图象的RGB值映射到显存中,这部分是图象显示的核心.对于JPEG格式的图片,要先经过JPE

Linux LCD驱动(四)--驱动的实现

目录(?)[-] 基本原理 写 framebuffer 驱动程序要做什么 LCD 模块 驱动程序 控制器 什么是 frame buffer 设备 Linux Frame Buffer 驱动程序层次结构 数据结构 接口 一个 LCD controller 驱动程序 分配系统内存作为显存 实现 fb_ops 结构 基本原理 通过 framebuffer ,应用程序用 mmap 把显存映射到应用程序虚拟地址空间,将要显示的数据写入这个内存空间就可以在屏幕上显示出来: 驱动程序分配系统内存作为显存:实现

Linux设备驱动中的阻塞和非阻塞I/O

[基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足. 2.非阻塞 非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止. 当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的

Smart210学习记录------linux串口驱动

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=327609 一.核心数据结构 串口驱动有3个核心数据结构,它们都定义在<#include linux/serial_core.h> 1.uart_driver uart_driver包含了串口设备名.串口驱动名.主次设备号.串口控制台(可选)等信息,还封装了tty_driver(底层串口驱动无需关心tty_driver). struct

linux网卡驱动移植

这里重要的是物理层PHY receiver,MAC(media access control)层,这里与软件中的协议栈不同,在硬件上MAC是PHY的下一层.DM9000A将MAC和PHY做到一起,也可以像IIS设备那样,SOC内有IIS的控制器,而声卡UDA1341放在片外.网卡当然也有这种设计,它是把PHY的下层MAC放入SOC内,片外的是PHY,当然我暂时还没见过这种的.DM9000A的输入是并行的总线,可以和CPU直接IO.而IIS那种需要通过:CPU CORE BUS->I2S控制器->