2020-01-12 23:22:35 m0_37786046 阅读数 5

GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器。

#define GIC_SPI 0 // 共享中断
#define GIC_PPI 1 // 每个处理器拥有独立中断

中断系统,主要有三个角色:
1.processor 主要用于处理中断
2.Interrupt Generating Device 通过硬件的interrupt line表明自身需要处理器的进一步处理
3.interrupt controller 负责收集各个外设的异步事件,用有序、可控的方式通知一个或多个processor

interrupt-parent = <&gic>;    //描述该设备的inerrupt request line连接到哪一个interrupt controller
//那些节点上没有interrupt-parent的则从父节点中继承该属性。(rk3399中直接在dtsi中定义以上属性,其它节点均未设置)

        u2phy0: usb2-phy@e450 {
            compatible = "rockchip,rk3399-usb2phy";    //对于root node,该属性是用来匹配machine type。 对于普通的节点,则是用来匹配对应的driver
            reg = <0xe450 0x10>;                    //内存映射(memory map)地址
            clocks = <&cru SCLK_USB2PHY0_REF>;
            clock-names = "phyclk";
            #clock-cells = <0>;
            clock-output-names = "clk_usbphy0_480m";
            status = "disabled";

            u2phy0_otg: otg-port {
                #phy-cells = <0>;
                interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH 0>,    //该属性主要描述中断的HW interrupt ID以及类型。用3个cell(对于device tree,cell是指由32bit组成的一个信息单位)表示。
                         <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH 0>,        //GIC_SPI 描述了interrupt type
                         <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH 0>;
                interrupt-names = "otg-bvalid", "otg-id",
                          "linestate";
                status = "disabled";
            };

            u2phy0_host: host-port {
                #phy-cells = <0>;
                interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH 0>;
                interrupt-names = "linestate";
                status = "disabled";
            };
        };
        #define IRQ_TYPE_NONE  0    内核不改变它,开机或uboot设置它是什么样就什么样。
        #define IRQ_TYPE_EDGE_RISING 1     上升沿触发
        #define IRQ_TYPE_EDGE_FALLING 2    下降沿触发
        #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)    双边沿触发
        #define IRQ_TYPE_LEVEL_HIGH 4    电平触发-高电平
        #define IRQ_TYPE_LEVEL_LOW 8    电平触发-低电平
        

对于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)
4)维护中断(Maintenance interrupt)


Distributor的主要的作用是检测各个interrupt source的状态,控制各个interrupt source的行为,分发各个interrupt source产生的中断事件到各个processor。
1)中断enable或者disable的控制。
    一个是全局中断的控制。一旦disable了全局的中断,那么任何的interrupt source产生的interrupt event都不会被传递到CPU interface。
    一个级别是对针对各个interrupt source进行控制,disable某一个interrupt source会导致该interrupt event不会分发到CPU interface,但不影响其他interrupt source产生interrupt event的分发。
    
2)控制中断事件分发到processor。
    一个interrupt事件可以分发给一个processor,也可以分发给若干个processor。
    
3)优先级控制。
4)interrupt属性设定。例如是level-sensitive还是edge-triggered,是属于group 0还是group 1。

2016-05-30 15:31:02 rikeyone 阅读数 3689

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。

GIC
2018-09-29 16:37:12 Xia_Lazy 阅读数 87

Interrupt types

A device that implements this GIC architecture can manage the following types of interrupt:

  1. Peripheral interrupt
    This is an interrupt asserted by a signal to the GIC. The GIC architecture defines the This is an interrupt asserted by a signal to the GIC. The GIC architecture defines the
    following types of peripheral interrupt:

    • Private Peripheral Interrupt (PPI)
      This is a peripheral interrupt that is specific to a single processor.
    • Shared Peripheral Interrupt (SPI)
      This is a peripheral interrupt that the Distributor can route to any of a specified
      combination of processors.
  2. Software-generated interrupt (SGI)
    This is an interrupt generated by software writing to a GICD_SGIR register in the GIC. The
    system uses SGIs for interprocessor communication.
    An SGI has edge-triggered properties. The software triggering of the interrupt is equivalent
    to the edge transition of the interrupt request signal.
    When an SGI occurs in a multiprocessor implementation, the CPUID field in the Interrupt
    Acknowledge Register, GICC_IAR, or the Aliased Interrupt Acknowledge Register,
    GICC_AIAR, identifies the processor that requested the interrupt.

Models for handling interrupts

In a multiprocessor implementation, there are two models for handling interrupts:

  • 1-N model
    Only one processor handles this interrupt. The system must implement a mechanism to determine
    which processor handles an interrupt that is programmed to target more than one processor
  • N-N model
    All processors receive the interrupt independently. When a processor acknowledges the interrupt,
    the interrupt pending state is cleared only for that processor. The interrupt remains pending for the
    other processors.

GIC logical partioning

在这里插入图片描述
The GIC architecture splits logically into a Distributor block and one or more CPU interface blocks. The GIC
Virtualization Extensions add one or more virtual CPU interfaces to the GIC. Therefore, as Figure 2-1 on page 2-23
shows, the logical partitioning of the GIC is as follows:

  • Distributor
    The Distributor block performs interrupt prioritization and distribution to the CPU interface
    blocks that connect to the processors in the system.
    The Distributor block registers are identified by the GICD_ prefix.
  • CPU interfaces
    Each CPU interface block performs priority masking and preemption handling for a
    connected processor in the system.
    CPU interface block registers are identified by the GICC_ prefix.
    When describing a GIC that includes the GIC Virtualization Extensions, a CPU interface is
    sometimes called a physical CPU interface, to avoid possible confusion with a virtual CPU
    interface.

Implications of the 1-N model

In a multiprocessor implementation, the GIC uses the GIC 1-N model, described in Models for handling interrupts
on page 1-19, to handle peripheral interrupts that target more than one processor, that is, SPIs. This means that when
the GIC recognizes an interrupt acknowledge from one of the target processors it clears the pending state of the
interrupt on all the other targeted processors.
A GIC implementation must ensure that any interrupt being handled
using the 1-N model is only acknowledged by one CPU interface, and that all other interfaces return a spurious
interrupt ID.

Interrupt prioritization

Software configures interrupt prioritization in the GIC by assigning a priority value to each interrupt source. Priority
values are 8-bit unsigned binary.
In the GIC prioritization scheme, lower numbers have higher priority, that is, the lower the assigned priority value
the higher the priority of the interrupt
. Priority field value 0 always indicates the highest possible interrupt priority,
and the lowest priority value depends on the number of implemented priority levels, as Table 3-1 shows.
The GICD_IPRIORITYRn registers hold the priority value for each supported interrupt.

Preemption

A CPU interface supports signaling of higher priority pending interrupts to a target processor before an active
interrupt completes. A pending interrupt is only signaled if both:

  • Its priority is higher than the priority mask for that CPU interface, see Priority masking.
  • Its group priority is higher than that of the Running priority on the CPU interface, see Priority grouping and
    Running Priority Register, GICC_RPR on page 4-142.

Preemption occurs at the time when the processor acknowledges the new interrupt, and starts to service it in
preference to the previously active interrupt or the currently running process. When this occurs, the initial active
interrupt is said to have been preempted. Starting to service an interrupt while another interrupt is still active is
sometimes described as interrupt nesting.

Priority masking

The GICC_PMR for a CPU interface defines a priority threshold for the target processor. The GIC only signals
pending interrupts with a higher priority than this threshold value to the target processor. A value of zero, the register
reset value, masks all interrupts from being signaled to the associated processor.

Priority grouping

Priority grouping uses the Binary Point Register, GICC_BPR, to split a priority value into two fields, the group
priority and the subpriority. When determining preemption, all interrupts with the same group priority are
considered to have equal priority, regardless of the subpriority. This means that there can only be one interrupt active
at each group priority.
The GIC uses the group priority field to determine whether a pending interrupt has sufficient priority to preempt an
active interrupt, as follows:
• For a pending interrupt to preempt an active interrupt, its group priority must be higher than the group priority
of the active interrupt. That is, the value of the group priority field for the new interrupt must be less than the
value of the group priority field of the Running priority.
• If there are no active interrupts on the CPU interface, the highest priority pending interrupt can be signaled
to a processor, regardless of the group priority.

2017-02-15 09:39:08 FA99999 阅读数 786

1、概述

本篇文档主要介绍IMX6UL平台上基于SylixOS集成开发环境中GIC通用中断控制器的实现流程和方法。

2GIC控制器基地址获取

GIC控制器基地址通过调用armPrivatePeriphBaseGet数获得。如图 2.1所示,Ctrl+h局搜索armPrivatePeriphBaseGet数,搜索结果如 2.2所示。

2.1全局搜索armPrivatePeriphBaseGet函数

 

2.2 armPrivatePeriphBaseGet搜索结果

参考DDI0464F_cortex_a7_mpcore_r0p5_trm.pdf手册,该指令将协处理器 P15 的寄存器中的数据传送到 ARM处理器寄存器R0中,最终通过armPrivatePeriphBaseGet函数获取。如图 2.3和图 2.4所示。

2.3 CP15协处理器操作码

2.4 CP15读取基地址指令

MRC指令的格式为:

MRC{条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2

MRC指令用于将协处理器寄存器中的数据传送到ARM处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。

指令示例:

MRC P3 3 R0 C4 C5 6该指令将协处理器 P3的寄存器中的数据传送到 ARM处理器寄存器中。

3GIC控制器结构体说明

GIC控制器中分发器端寄存器结构体定义如图 3.1所示,CPU接口端结构体定义如图 3.2所示。

3.1分发器端寄存器结构体定义

3.2 CPU接口端结构体定义

4GIC相关函数实现

4.1 GIC控制器初始化

4.1.1分发器初始化

distributor结构体中成员进行操作,关闭所有pending中断并禁能所有中断,同时将所有中断设置为安全模式。具体实现如程序清单 4.1所示。

程序清单 4.1分发器初始化

VOID armGicInit (VOID)
{
    REGISTER     GIC_DISTRIBUTOR_REG *pDistributor = armGicIntDistributorGet();
    REGISTER INT iCnt;
 
    armGicEnable(LW_FALSE);                                       /* First disable the distributor */
 
    for (iCnt = 0; iCnt < GIC_INT_NUM; iCnt++) {                  /* Clear all pending interrupts  */
        write32(~0, (addr_t)&pDistributor->uiICPENDRn[iCnt]);
    }
 
    for (iCnt = 0; iCnt < GIC_INT_NUM; iCnt++) {                  /* Disable all interrupts.       */
        write32(~0, (addr_t)&pDistributor->uiICENABLERn[iCnt]);
    }
 
    for (iCnt = 0; iCnt < GIC_INT_GRP_NUM; iCnt++) {              /* Set all interrupts to secure  */
        write32(0, (addr_t)&pDistributor->uiIGROUPRn[iCnt]);
    }
 
    armGicEnable(LW_TRUE);                                        /* Now enable the distributor    */
}

4.1.2 CPU 接口初始化

cpu interface端结构体成员进行操作,设置当前 CPU的优先级掩码255,禁止中断抢占,并使能当前 CPU GIC的接口。具体实现如程序清单 4.2所示。

程序清单 4.2 CPU接口初始化

VOID armGicCpuInit (BOOL  bPreemption, UINT32  uiPriority)
{
    REGISTER  GIC_CPU_INTERFACE_REGS *pInterface = armGicCpuInterfaceGet();
 
    armGicCpuPriorityMaskSet(uiPriority);                                  /* Init the GIC CPU interface */
 
    if (bPreemption) {
    /*
     * Enable preemption.
     */
    write32(GIC_CPU_INTERFACE_PREEM_EN, (addr_t)&pInterface->uiBPR);
    } else {
    /*
     * Disable preemption.
     */    
    write32(GIC_CPU_INTERFACE_PREEM_DISEN, (addr_t)&pInterface->uiBPR);
    }
 
    armGicCpuEnable(LW_TRUE);                                              /* Enable signaling the CPU */
}

4.2 中断向量基地址设置

如图 4.1和图 4.2所示,Ctrl+h全局搜索armVectorBaseAddrSet数。

4.1全局搜索armVectorBaseAddrSet

4.2 armVectorBaseAddrSet搜索结果

参照DDI0464F_cortex_a7_mpcore_r0p5_trm.pdf手册,如图 4.3所示

4.3中断向量基地址介绍

根据提示,再参照DDI0406C_C_arm_architecture_reference_manual.pdf手册如下图 4.4所示。

4.4中断向量基地址说明

可知该寄存器保存的是中断向量表基址参照bspMap.h文件,中断向量表物理地址即为BSP_CFG_RAM_BASE0x80000000)。

4.3实现系统中断控制器

系统上电的过程中需要初始化中断处理器,否则后续使用到中断的程序都不能正常运行。

startup.S中可以看到如图 4.5所示,系统只响应了irq中断,对fiq中断不做处理。

4.5 irq中断入口地址

关于中断需实现函数如图 4.6所示。

4.6函数列表

4.4中断服务程序

通过读取硬件寄存器,得到当前产生的中断向量号,调用系统接口archIntHandle进行处理,处理完成后,通知GIC中断已经处理完成。

4.4.1读取寄存器GICC_IAR获取中断号,如程序清单 4.3所示。

程序清单 4.3获取中断号

UINT32 armGicIrqReadAck (VOID)
{
    REGISTER GIC_CPU_INTERFACE_REGS *pInterface = armGicCpuInterfaceGet();
    return (read32((addr_t)&pInterface->uiIAR));
}

4.4.2GICC_EOIR写入中断号通知GIC该中断处理完成,如程序清单 4.4所示。

程序清单 4.4通知GIC该中断处理完成

VOID armGicIrqWriteDone (UINT32  uiIrqID)
{
    REGISTER GIC_CPU_INTERFACE_REGS *pInterface = armGicCpuInterfaceGet();
    write32(uiIrqID, (addr_t)&pInterface->uiEOIR);
}

4.5使能指定中断

通过设置硬件寄存器,使能指定的中断向量。具体实现如程序清单 4.5所示。

程序清单 4.5使能指定中断

VOID armGicIntVecterEnable (ULONG  ulVector, BOOL  bSecurity, ULONG  ulPriority, ULONG  uiCpuMask)
{
    armGicIrqEnable(ulVector, LW_FALSE);
    armGicIrqSecuritySet(ulVector, bSecurity);
    armGicIrqPrioritySet(ulVector, ulPriority);
    armGicIrqTargetSet(ulVector, GIC_CPU_MASK, LW_FALSE);
    armGicIrqTargetSet(ulVector, uiCpuMask, LW_TRUE);
    armGicIrqEnable(ulVector, LW_TRUE);
}

:由于imx6ul是单核处理器,理论上是不需要进行目标CPU选择的,但由于使用了GIC中断控制器,所以需给其目标寄存器组配置为0,即默认选择CPU0

4.6禁能指定中断

通过设置硬件寄存器,禁能指定中断。具体实现如程序清单 4.6所示。

程序清单 4.6禁能指定中断

VOID armGicIntVecterDisable (ULONG  ulVector)
{
    armGicIrqEnable(ulVector, LW_FALSE);
    armGicIrqTargetSet(ulVector, GIC_CPU_MASK, LW_FALSE);
}

4.7判断中断是否使能

通过读取硬件寄存器,检查指定的中断向量是否使能。具体实现如程序清单 4.7所示。

程序清单 4.7判断指定中断向量是否使能

BOOL armGicIrqIsEnable (UINT32  uiIrqID)
{
    REGISTER GIC_DISTRIBUTOR_REG *pDistributor = armGicIntDistributorGet();
    REGISTER UINT32              uiReg         = armGicIntRegOffsGet(uiIrqID);
    REGISTER UINT32              uiMask        = armGicIntBitMaskGet(uiIrqID);
 
    return ((read32((addr_t)&pDistributor->uiICENABLERn[uiReg]) & uiMask) ? LW_TRUE : LW_FALSE);
}

4.8获取/设置中断优先级

该项由工程默认生成,未实现,如程序清单 4.8所示。

程序清单 4.8获取/设置中断优先级

ULONG bspIntVectorSetPriority (ULONG  ulVector, UINT  uiPrio)
{
    return (ERROR_NONE);
}
ULONG bspIntVectorGetPriority (ULONG  ulVector, UINT *puiPrio)
{
    *puiPrio = 0;
    return (ERROR_NONE);
}

4.9获取/设置中断目标CPU

由于imx6ul为单核处理器,虽然用到了GIC通用中断控制器,但默认给其选择CPU0

bsplib.c中具体实现如程序清单 4.9所示。

程序清单 4.9获取/设置中断目标CPU

ULONG bspIntVectorSetTarget (ULONG  ulVector, size_t  stSize, const PLW_CLASS_CPUSET  pcpuset)
{
    return (ERROR_NONE);
}
ULONG bspIntVectorGetTarget (ULONG  ulVector, size_t  stSize, PLW_CLASS_CPUSET  pcpuset)
{
    LW_CPU_ZERO(pcpuset);
    LW_CPU_SET(0, pcpuset);
 
    return (ERROR_NONE);
}

5、填充bsplib.c中断相关内容

将对应的函数功能添加到bspLib.c中,如程序清单 5.1所示。

程序清单 5.1 bspLib.c中断相关填充

/*********************************************************************************************************
中断相关
*********************************************************************************************************/
VOID bspIntInit (VOID)
{
    armGicInit();
    /*
     * 能够传递到当前 CPU 的中断的最低优先级(255 表示所有中断)
     */
    armGicCpuInit(LW_FALSE, 255);
    armVectorBaseAddrSet(BSP_CFG_RAM_BASE);
}
VOID bspIntHandle (VOID)
{
    REGISTER UINT32 uiAck       = armGicIrqReadAck();
    REGISTER UINT32 uiSourceCpu = (uiAck >> 10) & 0x7;
    REGISTER UINT32 uiVector    = uiAck & 0x1FF;
 
    (VOID)uiSourceCpu;
 
    archIntHandle((ULONG)uiVector, LW_FALSE);
 
    armGicIrqWriteDone(uiAck);
}
VOID bspIntVectorEnable (ULONG  ulVector)
{
    /*
     * 1 << 0 : 目标CPU为CPU0
     */
    armGicIntVecterEnable(ulVector, LW_FALSE, ARM_DEFAULT_INT_PRIORITY, 1 << 0);
}
VOID bspIntVectorDisable (ULONG  ulVector)
{
    armGicIntVecterDisable(ulVector);
}
BOOL bspIntVectorIsEnable (ULONG  ulVector)
{
    return (armGicIrqIsEnable(ulVector) ? LW_TRUE : LW_FALSE);
}
ULONG bspIntVectorSetPriority (ULONG  ulVector, UINT  uiPrio)
{
    return (ERROR_NONE);
}
ULONG bspIntVectorGetPriority (ULONG  ulVector, UINT *puiPrio)
{
    *puiPrio = 0;
    return (ERROR_NONE);
}
ULONG bspIntVectorSetTarget (ULONG  ulVector, size_t  stSize, const PLW_CLASS_CPUSET  pcpuset)
{
    return (ERROR_NONE);
}
ULONG bspIntVectorGetTarget (ULONG  ulVector, size_t  stSize, PLW_CLASS_CPUSET  pcpuset)
{
    LW_CPU_ZERO(pcpuset);
    LW_CPU_SET(0, pcpuset);
    return (ERROR_NONE);
}

至此,与中断控制器相关的内容填充完毕,之后在驱动中使用API_InterVectorConnect连接中断服务程序和中断号,使用API_InterVectorEnable使能中断等操作时就可以正常产生中断,并进入驱动对应的中断服务程序中了。

6、参考资料

文档类:

1 gic_architecture_specification.pdf

2. DDI0471B_gic400_r0p1_trm.pdf

3. IHI 0048B, ARM Generic Interrupt Controller Architecture Specification, Ver 2.0.pdf

网址链接:

1.http://www.wowotech.NET/linux_kenrel/gic_driver.html

2. http://blog.csdn.Net/velanjun/article/details/8757862

3. http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html

2018-02-18 21:54:23 weixin_38227420 阅读数 133

在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,每个中断控制器的drivver会使用如下:
handle_arch_irq = gic_handle_irq
将自己的中断入口处理函数赋值给这个指针,这里是gic的入口函数,从而进入GIC驱动,进行后续的中断处理

32位arm的入口c函数/arch/arm/kernel/entry-armv.S:43:
ldr r1, =handle_arch_irq
每个架构的控制器都需要实现这个入口函数,handle_arch_irq = xxxx;
老的方式都要实现在machine 结构体的填充里面实现handle_arch_irq = xxxx;

gic的实现方式:

set_handle_irq(gic_handle_irq);//gic_handle_irq即为gic实现的入口函数
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
        return;

    handle_arch_irq = handle_irq;
}

以下是dts方式的流程:
gic的代码初始化入口在

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

    /*
     * Disable split EOI/Deactivate if either HYP is not available
     * or the CPU interface is too small.
     */
    if (gic_cnt == 0 && !gic_check_eoimode(node, &cpu_base))
        static_key_slow_dec(&supports_deactivate);

    if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
        percpu_offset = 0;
    printk("enter %s %d\n",__FILE__,__LINE__);
    __gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset,//gic的初始化动作在这里发生
             &node->fwnode);
    if (!gic_cnt)
        gic_init_physaddr(node);

    if (parent) {//只有存在父节点的时候才会进入该分支,目前不需要进入
        printk("enter %s %d\n",__FILE__,__LINE__);
        irq = irq_of_parse_and_map(node, 0);
        gic_cascade_irq(gic_cnt, irq);
    }

    if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
        gicv2m_of_init(node, gic_data[gic_cnt].domain);

    gic_cnt++;
    return 0;
}

static void __init __gic_init_bases(unsigned int gic_nr, int irq_start,
               void __iomem *dist_base, void __iomem *cpu_base,
               u32 percpu_offset, struct fwnode_handle *handle)
{
    irq_hw_number_t hwirq_base;
    struct gic_chip_data *gic;//描述gic控制器的结构体
    int gic_irqs, irq_base, i;

    BUG_ON(gic_nr >= MAX_GIC_NR);

    gic_check_cpu_features();

    gic = &gic_data[gic_nr];//一个成员就代表一个描述gic控制器的结构体
#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) {
            u32 mpidr = cpu_logical_map(cpu);
            u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
            unsigned long offset = percpu_offset * core_id;
            *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);
    }

    /*
     * 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控制器支持的最大中断线
    gic_irqs = (gic_irqs + 1) * 32;
    if (gic_irqs > 1020)
        gic_irqs = 1020;
    gic->gic_irqs = gic_irqs;
//如果使用了dts的方式probo gic driver,则走这个分支,else是legacy的方式
    if (handle) {       /* DT/ACPI */
        printk("%s %d\n",__FILE__,__LINE__);
        //这里主要是为当前的中断控制器创建一个domain,并且注册这个domain,其中gic_irq_domain_hierarchy_ops是domain的ops结构体,
    其成员有分配线性中断表,释放该表,以及从hw irq到vir irq的转化成员函数,这三个成员函数是doamin的核心实现
        gic->domain = irq_domain_create_linear(handle, gic_irqs,
                               &gic_irq_domain_hierarchy_ops,
                               gic);
    } else {        /* Legacy support */
        /*
         * For primary GICs, skip over SGIs.
         * For secondary GICs, skip over PPIs, too.
         */
         printk("enter %s %d\n",__FILE__,__LINE__);
        if (gic_nr == 0 && (irq_start & 31) > 0) {
            printk("enter %s %d\n",__FILE__,__LINE__);
            hwirq_base = 16;
            if (irq_start != -1)
                irq_start = (irq_start & ~31) + 16;
        } else {
        printk("enter %s %d\n",__FILE__,__LINE__);
            hwirq_base = 32;
        }

        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(NULL, gic_irqs, irq_base,
                    hwirq_base, &gic_irq_domain_ops, gic);
    }

    if (WARN_ON(!gic->domain))
        return;

    if (gic_nr == 0) {
//gic_nr ==0表示为root级gic控制器
        /*
         * Initialize the CPU interface map to all CPUs.
         * It will be refined as each CPU probes its ID.
         * This is only necessary for the primary GIC.
         */
         printk("enter %s %d\n",__FILE__,__LINE__);
        for (i = 0; i < NR_GIC_CPU_IF; i++)
            gic_cpu_map[i] = 0xff;
#ifdef CONFIG_SMP
        set_smp_cross_call(gic_raise_softirq);
        register_cpu_notifier(&gic_cpu_notifier);
#endif
        set_handle_irq(gic_handle_irq);//这一步很关键,这里指定了中断发生后进入的第一个中断c入口。当中断信号到来以后,/arch/arm/kernel/entry-armv.S:43:      ldr     r1, =handle_arch_irq 会跳转到该函数指针,这里将gic_handle_irq 赋值给该函数指针,所以当中断信号发生后,会跳转到gic_handle_irq这个函数中去识别硬件中断源。
        if (static_key_true(&supports_deactivate))
            pr_info("GIC: Using split EOI/Deactivate mode\n");
    }

    gic_dist_init(gic);
    gic_cpu_init(gic);
    gic_pm_init(gic);
}
//domain的三个核心成员函数,分别用于硬件中断号和系统中断号的转换,线性映射表的创建,线性映射表的撤销
static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
    .translate = gic_irq_domain_translate,
    .alloc = gic_irq_domain_alloc,
    .free = irq_domain_free_irqs_top,
};

此时初始化完成,在初始化的时候既没有给hwirq分配对应的virq,也没有建立二者之间的映射,这部分工作会到后面有人引用GIC上的某个中断时再分配和建立。
某个驱动再申请注册某个中断的时候,会先申请一个软件中断号,对于有dts支持的系统,此时才去映射一个软件中断,即这部分工作会到后面有人引用GIC上的某个中断时再分配和建立。

arm gic学习

阅读数 390

gic_dist_init做的主要工作

博文 来自: r7ronalshun

arm-gic

阅读数 240

ARM GIC

博文 来自: shenhuxi_yu

GIC ITS 学习笔记(一)

阅读数 1502

GIC ITS NOTE 1

博文 来自: scarecrow_byr
没有更多推荐了,返回首页