GIC , SPI , PPI (窝窝科技的文章题目改了下)【转】

本文详细分析了Linux内核中GIC(Generic Interrupt Controller)中断控制器的驱动代码,包括GIC的硬件结构、DTS描述、驱动初始化过程以及硬件操作函数如中断使能、结束中断等。GIC通过AMBA总线连接到处理器,管理不同类型的中断,如外设中断、软件触发中断等。在Linux系统启动时,通过DTB调用GIC驱动的初始化函数,并建立IRQ号与硬件中断ID的映射。文章还介绍了GIC的中断处理流程和中断类型设置方法。
摘要由CSDN通过智能技术生成

转自:https://www.cnblogs.com/tureno/articles/6403408.html

转载于:  http://www.wowotech.net/irq_subsystem/gic-irq-chip-driver.html

 

GIC驱动代码分析(废弃)

这份文档状态是:废弃,新的文档请访问http://www.wowotech.net/linux_kenrel/gic_driver.html

一、前言

GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器。GIC通过AMBA(Advanced Microcontroller Bus Architecture)这样的片上总线连接到一个或者多个ARM processor上。本文主要分析了linux kernel中GIC中断控制器的驱动代码。

具体的分析方法是按照source code为索引,逐段分析。对于每一段分析的代码,力求做到每个细节都清清楚楚。这不可避免要引入很多对GIC的硬件描述,此外,具体GIC中断控制器的驱动代码和linux kernel中断子系统的交互也会描述,但本文不会描述linux kernel的generic interrupt subsystem。

本文以OMAP4460这款SOC为例,OMAP4460内部集成了GIC的功能。具体的linux kernel的版本是linux3.14.。

 

二、GIC DTS描述

1、中断系统概述

对于中断系统,主要有三个角色:

(1)processor。主要用于处理中断

(2)Interrupt Generating Device。通过硬件的interrupt line表明自身需要处理器的进一步处理(例如有数据到来、异常状态等)

(3)interrupt controller。负责收集各个外设的异步事件,用有序、可控的方式通知一个或者多个processor。

 

2、DTS如何描述Interrupt Generating Device

对于Interrupt Generating Device,我们需要定义下面两个属性:

(1)Interrupt属性。该属性主要描述了中断的HW interrupt ID以及类型。

(2)interrupt-parent 属性。该属性主要描述了该设备的interrupt request line连接到哪一个interrupt controller。

在OMAP4460系统中,我们以一个简单的串口为例子,具体的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:

uart3: serial@48020000 { 
            compatible = "ti,omap4-uart"; 
            reg = <0x48020000 0x100="">; 
            interrupts = <gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>; 
            ti,hwmods = "uart3"; 
            clock-frequency = <48000000>; 
        };

对于uart3,interrupts属性用3个cell(对于device tree,cell是指由32bit组成的一个信息单位)表示。GIC_SPI 描述了interrupt type。对于GIC,它可以管理4种类型的中断:

(1)外设中断(Peripheral interrupt)。根据目标CPU的不同,外设的中断可以分成PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI只能分配给一个确定的processor,而SPI可以由Distributor将中断分配给一组Processor中的一个进行处理。外设类型的中断一般通过一个interrupt request line的硬件信号线连接到中断控制器,可能是电平触发的(Level-sensitive),也可能是边缘触发的(Edge-triggered)。

(2)软件触发的中断(SGI,Software-generated interrupt)。软件可以通过写GICD_SGIR寄存器来触发一个中断事件,这样的中断,可以用于processor之间的通信。

(3)虚拟中断(Virtual interrupt)和Maintenance interrupt。这两种中断和本文无关,不再赘述。

在DTS中,外设的interrupt type有两种,一种是SPI,另外一种是PPI。SGI用于processor之间的通信,和外设无关。

uart3的interrupt属性中的74表示该外设使用的GIC interrupt ID号。GIC最大支持1020个HW interrupt ID,具体的ID分配情况如下:

(1)ID0~ID31是用于分发到一个特定的process的interrupt。标识这些interrupt不能仅仅依靠ID,因为各个interrupt source都用同样的ID0~ID31来标识,因此识别这些interrupt需要interrupt ID + CPU interface number。ID0~ID15用于SGI,ID16~ID31用于PPI。PPI类型的中断会送到指定的process上,和其他的process无关。SGI是通过写GICD_SGIR寄存器而触发的中断。Distributor通过processor source ID、中断ID和target processor ID来唯一识别一个SGI。

(2)ID32~ID1019用于SPI。

uart3的interrupt属性中的IRQ_TYPE_LEVEL_HIGH用来描述触发类型。

很奇怪,uart3并没有定义interrupt-parent属性,这里定义interrupt-parent属性的是root node,具体的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:

/ { 
    compatible = "ti,omap4430", "ti,omap4"; 
    interrupt-parent = <&gic>;

略去无关内容

}

难道root node会产生中断到interrupt controller吗?当然不会,只不过如果一个能够产生中断的device node没有定义interrupt-parent的话,其interrupt-parent属性就是跟随parent node。因此,与其在所有的下游设备中定义interrupt-parent,不如统一在root node中定义了。

3、DTS如何描述GIC

linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中,

    gic: interrupt-controller@48241000 { 
        compatible = "arm,cortex-a9-gic"; 
        interrupt-controller; 
        #interrupt-cells = <3>; 
        reg = <0x48241000 0x1000="">, 
              <0x48240100 0x0100="">; 
    };

compatible属性用来描述GIC的programming model。该属性的值是string list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个driver来驱动该设备。假设定义该属性:compatible = “a厂商,p产品”, “标准bbb类型设备”。那么linux kernel可能首先使用“a厂商,p产品”来匹配适合的driver,如果没有匹配到,那么使用字符串“标准bbb类型设备”来继续寻找适合的driver。compatible属性有两个应用场景:

(1)对于root node,compatible属性是用来匹配machine type的(参考Device Tree相关文档)

(2)对于普通的HW block的节点,例如interrupt-controller,compatible属性是用来匹配适合的driver的。

interrupt-controller这个没有定义value的属性用来表明本设备节点就是一个interrupt controller。理解#interrupt-cells这个属性需要理解interrupt specifier和interrupt domain这两个概念。interrupt specifier其实就是外设interrupt的属性值,对于uart3而言,其interrupt specifier就是<gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,也就是说,interrupt specifier定义了一个外设产生中断的规格(HW interrupt ID + interrupt type)。具体如何解析interrupt specifier?这个需要限定在一定的上下文中,不同的interrupt controller会有不同的解释。因此,对于一个包含多个interrupt controller的系统,每个interrupt controller及其相连的外设组成一个interrupt domain,各个外设的interrupt specifier只能在属于它的那个interrupt domain中得到解析。#interrupt-cells定义了在该interrupt domain中,用多少个cell来描述一个外设的interrupt specifier。

reg属性定义了GIC的memory map的地址,有两段,分别描述了CPU interface和Distributor的地址段。理解CPU interface和Distributor这两个术语需要GIC的一些基本的硬件知识,参考下节描述。

 

三、GIC的HW block diagram描述

1、GIC HW概述

GIC的block diagram如下图所示:

gic

GIC可以清晰的划分成两个block,一个block是Distributor(上图的左边的block),一个是CPU interface。CPU interface有两种,一种就是和普通processor接口,另外一种是和虚拟机接口的。Virtual CPU interface在本文中不会详细描述。

 

2、Distributor

Distributor的主要的作用是检测各个interrupt source的状态,控制各个interrupt source的行为,分发各个interrupt source产生的中断事件到各个processor。Distributor对中断的控制包括:

(1)中断enable或者disable的控制。Distributor对中断的控制分成两个级别。一个是全局中断的控制。一旦disable了全局的中断,那么任何的interrupt source产生的interrupt event都不会被传递到CPU interface。另外一个级别是对针对各个interrupt source进行控制,disable某一个interrupt source会导致该interrupt event不会分发到CPU interface,但不影响其他interrupt source产生interrupt event的分发。

(2)控制中断事件分发到process。一个interrupt事件可以分发给一个process,也可以分发给若干个process。

(3)优先级控制。

(3)interrupt属性设定。例如是level-sensitive还是edge-triggered,是属于group 0还是group 1。

Distributor可以管理若干个interrupt source,这些interrupt source用ID来标识,我们称之interrupt ID。

 

2、CPU interface

CPU interface这个block主要用于和process进行接口。该block的主要功能包括:

(1)enable或者disable。对于ARM,CPU interface block和process之间的中断信号线是nIRQ和nFIQ这两个signal。如果disable了中断,那么即便是Distributor分发了一个中断事件到CPU interface,但是也不会assert指定的nIRQ或者nFIQ通知processor。

(2)ackowledging中断。processor会向CPU interface block应答中断,中断一旦被应答,Distributor就会把该中断的状态从pending状态修改成active。如果没有后续pending的中断,那么CPU interface就会deassert nIRQ或者nFIQ的signal。如果在这个过程中又产生了新的中断,那么Distributor就会把该中断的状态从pending状态修改成pending and active。这时候,CPU interface仍然会保持nIRQ或者nFIQ信号的asserted状态,也就是向processor signal下一个中断。

(3)中断处理完毕的通知。当interrupt handler处理完了一个中断的时候,会向写CPU interface的寄存器从而通知GIC CPU已经处理完该中断。做这个动作一方面是通知Distributor将中断状态修改为deactive,另外一方面,如果一个中断没有完成处理,那么后续比该中断优先级低的中断不会assert到processor。一旦标记中断处理完成,被block掉的那些比当前优先级低的中断就会递交给processor。

(4)设定priority mask。通过priority mask,可以mask掉一些优先级比较低的中断,这些中断不会通知到CPU。

(5)设定preemption的策略

(6)在多个中断事件同时到来的时候,选择一个优先级最高的通知processor

 

四、系统启动过程中,如何调用GIC driver的初始化函数

在linux-3.14\drivers\irqchip目录下保存在各种不同的中断控制器的驱动代码,irq-gic.c就是GIC的驱动代码。

1、IRQCHIP_DECLARE宏定义

在linux-3.14\drivers\irqchip目录下的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 }

这个宏就是被各个irq chip driver用来声明其DT compatible string和初始化函数的对应关系的。IRQCHIP_DECLARE中的compstr就是DT compatible string,fun就是初始化函数。irq-gic.c文件中声明的对应关系包括:

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);

从上面的定义可以看出来,A9和A15的gic初始化函数都是gic_of_init。另外两个定义是和高通的CPU相关,我猜测是高通使用了GIC,但是自己又做了一些简单的修改,但无论如何,初始化函数都是一个,那就是gic_of_init。

在linux kernel编译的时候,你可以配置多个irq chip进入内核,编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(section name是__irqchip_of_table),我们称这个特殊的section叫做irq chip table。这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和DT compatible string的对应关系。具体执行哪一个初始化函数是由bootloader传递给kernel的DTB决定的。

2、OMAP4460的machine定义

在linux-3.14/arch/arm/mach-omap2目录下的board-generic.c的文件中定义了OMAP4460的machine如下:

DT_MACHINE_START(OMAP4_DT, "Generic OMAP4 (Flattened Device Tree)") 
  。。。。。。删除无关代码 
    .init_irq    = omap_gic_of_init, 
。。。。。。删除无关代码 
MACHINE_END

在系统初始化的时候,会调用start_kernel->init_IRQ->machine_desc->init_irq()函数。

 

3、omap_gic_of_init过程分析

omap_gic_of_init的代码如下(删除了无关代码):

void __init omap_gic_of_init(void) 

    irqchip_init(); 
}

在driver/irqchip/irqchip.c文件中定义了irqchip_init函数,如下:

void __init irqchip_init(void) 

    of_irq_init(__irqchip_begin); 
}

__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的驱动代码初始化函数和DT compatible string的对应关系。of_irq_init函数scan bootloader传递来的DTB,找到所有的中断控制器节点,并形成树状结构(系统可以有多个interrupt controller)。之后,从root interrupt controller开始,对于每一个interrupt controller,scan irq chip table,匹配DT compatible string,一旦匹配到,就调用该interrupt controller的初始化函数。具体的代码分析可以参考Device Tree代码分析文档

 

五、GIC driver初始化代码分析

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;

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

    dist_base = of_iomap(node, 0);----------------映射GIC Distributor的寄存器地址空间 
    WARN(!dist_base, "unable to map gic dist registers\n");

    cpu_base = of_iomap(node, 1);----------------映射GIC CPU interface的寄存器地址空间 
    WARN(!cpu_base, "unable to map gic cpu registers\n");

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

    gic_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值