Linux4.x之Gpio分析(三)ARM通用中断控制器

在(二)中我们走到通用中断控制器和GPC时就停下来了,我们只浏览了中断控制器匹配部分的流程还未进入真正核心的区域。因为水平有限,这里之分析GIC的部分,况且GPC和我们的主体GPIO想去甚远。

GIC介绍

首先,我们需要了解GIC的内部结构才能真正理解,中断是如何被处理的,最终才能理解代码。

关于IMX6芯片手册关于GIC的描述只有:

The Global Interrupt Controller (GIC) collects up to 128 interrupt requests from all i.MX
6Dual/6Quad sources and provides an interface to each of the CPU cores
和IRQ的硬件编号从32-159的对应表格,有点吝啬。

我们在设备树中知道使用的是 arm,cortex-a9-gic 的这个IP.

ARM平台上一般把中断分为三种类型,分别是

  • SGI(software generated interrupts)
  • PPI(per processor interrupts)
  • SPI(shared processor interrupts)

硬件中断号的分配:

  • ID0~ID31
    是用于分发到一个特定的process的interrupt。
    标识这些interrupt不能仅仅依靠ID,还必须指定process的ID,因此识别这些interrupt需要interrupt ID + CPU interface number。
  1. ID0~ID15属于SGI中断,SGI是通过软件写入GIC的GICD_SGIR寄存器而触发的中断,它可以用于processor之间的通信。 GIC通过processor source ID、中断ID和target processor ID来唯一识别一个SGI。
  2. ID16~ID31属于PPI中断,PPI类型的中断和SPI一样属于外设的中断,区别就是它会被送到其私有的process上,而和其他的process无关。
  • ID32~ID1019用于SPI。 这是GIC规范的最大范围,实际上Cortex-A15和A9上的GIC最多支持224个SPI。

这里IMX6 仅使用32-159

源码跟踪

我们在之前的分析中知道,初始化代码是gic_of_init
IRQCHIP_DECLARE(cortex_a9_gic, “arm,cortex-a9-gic”, gic_of_init);
接着我们先去代码中窥探一下:

为方便起见设备树被列一下:

1
2
3
4
5
6
7
8
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a00100 0x100>;
interrupt-parent = <&intc>;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static 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);//0x00a01000 -0x00a01100
WARN(!dist_base, "unable to map gic dist registers\n");

cpu_base = of_iomap(node, 1);//0x00a00100 -0x00a00200
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);

//这里本身GIC就是主GIC所以这个不需要做irq_map这个过程已经推迟外设的驱动中去
// interrupt-parent = <&intc>; 这句话也相当于脱裤子放屁多次一举。
if (parent) {
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;
}

Linux IRQ domain

这里我们先引入linux的irq domain 概念。
为什么引入这个概念呢,就是因为我们可能存在多个GIC控制器,相对于过去片上的中断控制器的一一对应关系显得复杂很多。
引入IRQ domain的目的就是为了简化这一层逻辑关系。这里我们将一个控制器作为一共domian.
这一个domain连接的可能是外设也可能是连接另外一个控制器,同时本身也可能直接连接到CPU或者是根控制器。

这样每一个domain维护自己的连接逻辑,上一层就不必操心这些问题。
最后将这些复杂的逻辑简化为一共简单的列表项。

IRQ number 对应的 HW irq 这样的简单逻辑关系。

IRQ domian注册步骤

那么我们在这个系统的上,需要建立这种关系我们需要做的事情
1.将中断控制器加入到irq domain
2.为irq domain建立映射关系

这里将控制器加入到 irq doman是控制器驱动做的事
而建立irq domain的映射关系分散在各个驱动中。为什么呢?

比如GPIO1在硬件连线中已经设定了HW中断号是89,然后应该由于GPIO1控制器的驱动去更新
这个映射表,我是HW 89号中断请求分配一个IRQ number!

这样经过irq domain知道GPIO的中断是连接在89这个管脚,它不必告诉告诉其他人这个信息,当中断发生后,他只需要上报IRQ number即可。CPU便知道是GPIO1发生了中断。

这里我们先列出API:

  1. 向系统注册一个irq domain
  • irq_domain_add_linear
  • irq_domain_add_tree
  • irq_domain_add_nomap
    这里设定了irq_domain中的映射关系的类型。
  1. 在irq domain中建立一个映射关系
  • irq_create_mapping
  • irq_create_direct_mapping
  • irq_create_strict_mappings
  • irq_create_of_mapping

分别对应的解释为:
建立HW interrupt ID和IRQ number的映射关系
为一组HW interrupt ID建立映射关系
给no map那种类型的interrupt controller使用的
利用device tree进行映射关系的建立

通常,一个普通设备的device tree node已经描述了足够的中断信息,
在这种情况下,该设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该device node中和中断相关的内容

1
2
3
4
5
6
7
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);//创建映射,并返回对应的IRQ number
}

GIC注册流程

不过很遗憾我们的GIC的驱动程序并没有上述说明的函数来建立映射,接着我们来看看gic_init_bases中如何进行建立映射的步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//这里我们简化一下代码只列出我们关心的流程代码
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)
{
if(ndoe){
gic->domain = irq_domain_add_linear(node, gic_irqs,
&gic_irq_domain_hierarchy_ops,
gic);
}else{
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
}
#ifdef CONFIG_SMP
set_smp_cross_call(gic_raise_softirq);
register_cpu_notifier(&gic_cpu_notifier);
#endif

set_handle_irq(gic_handle_irq);
}


`

这里主要做了两件事

  1. irq_domain注册到系统。
  2. 提供上级使用的irq_handle函数.

作为一个interrupt controller,除了注册自己管理的irq_domain,还需要提供给上级使用的irq_handler。

  • 作为second GIC,需要调用irq_set_chained_handler注册irq_handler到root GIC中;
  • 作为root GIC,上级是CPU,调用set_handle_irq注册到平台的irq处理接口。

19行的功能,当CPU发生了中断最先调用的就是root GIC的 gic_handle_irq,然后在此函数中进行gic的irq domain处理。

然后:我们注册irq_domain时用到了一个ops:

1
2
3
4
5
6
7
8
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。实际上内核中提供了默认的匹配函数,就是通过of node来进行匹配的.

  • mapunmap 是映射和解除映射操作。

我们暂时不分析这些函数具体有什么作用,我们回到主代码发现了:
irq_of_parse_and_map ,也就是为irq domain建立映射关系的函数,
我们解析一下这个函数具体做了什么操作,虽然我们主GIC不会做任何操作。
我们还是看一眼创建映射关心代码的流程,这里我们不关注细节只看看调用流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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;
}

//先找到硬件的ID
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;
}

这里我们就看出了xlatemap的 重要性。于是呼我们也看一眼这两个函数怎么实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
} else {
irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
}
return 0;
}


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;

if (d->of_node != controller)
return -EINVAL;
if (intsize < 3)
return -EINVAL;

/* Get the interrupt number and add 16 to skip over SGIs */
*out_hwirq = intspec[1] + 16;

/* For SPIs, we need to add 16 more to get the GIC irq ID number */
if (!intspec[0])
*out_hwirq += 16;

*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;

return ret;
}

好了,我们只是简略的看了一眼,走了个流程,细节还是要对着GIC手册去慢慢填坑,暂时先连接那么多,目的是要把脉络弄清楚,陷入细节还不是现在的目的。

接着我们再看看刚才gic设置的中断处理程序:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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流程分析完毕了,
下面我们就应该回到GPIO的代码中看看,GPIO是如何把中断和GIC联系起来。
这里我认为适当的囫囵吞枣是有必要的,不然容易卡住下不去。我们先一窥究竟,然后在回头,即便好马不吃回头草。

客官,小的这记口碎大石值几个钱?