Linux4.x之Gpio分析(四)Gpio中断

上一篇我们大致的浏览了一下GIC的注册流程。接下来我们希望能将我们的GPIO的中断和它建立起联系,这样我们就能放心大胆的使用GPIO模块提供给我们的中断服务了。

好!我们回到Gplib库的代码,之前分析是跳过了中断注册相关的内容,这次就捡起来。
试着给之前的分析搭上关系, 废话不多说了,回到代码

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
89
90
91
92
93
94
95
96
97
98
99
100
// drivers/gpio/gpio-mxc.c
static int mxc_gpio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct mxc_gpio_port *port;
struct resource *iores;
int irq_base;
int err;

mxc_gpio_get_hw(pdev); //1

port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;

//从设备树中获取寄存器地址,IOREMAP就是将物理地址转化称虚拟地址
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
port->base = devm_ioremap_resource(&pdev->dev, iores);
if (IS_ERR(port->base))
return PTR_ERR(port->base);
//从设备树中获取中断号,上面也解释了为啥是2个。
port->irq_high = platform_get_irq(pdev, 1);
port->irq = platform_get_irq(pdev, 0);
if (port->irq < 0)
return port->irq;

//关中断,清理中断触发状态。
//中断屏蔽位的作用就是是否要关心这个中断
//中断状态位的作用就是,当中断发生了,我们需要查一下是谁发生了中断。
/* disable the interrupt and clear the status */
writel(0, port->base + GPIO_IMR);
writel(~0, port->base + GPIO_ISR);

if (mxc_gpio_hwtype == IMX21_GPIO) {
/*
* Setup one handler for all GPIO interrupts. Actually setting
* the handler is needed only once, but doing it for every port
* is more robust and easier.
*/
irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
} else {
/* setup one handler for each entry */
//2
//函数修改父中断的流控函数,当发生中断,中断控制器会去调用这个函数
irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
irq_set_handler_data(port->irq, port);
if (port->irq_high > 0) {
/* setup handler for GPIO 16 to 31 */
irq_set_chained_handler(port->irq_high,mx3_gpio_irq_handler);
irq_set_handler_data(port->irq_high, port);
}
}

//3
err = bgpio_init(&port->bgc, &pdev->dev, 4,
port->base + GPIO_PSR,
port->base + GPIO_DR, NULL,
port->base + GPIO_GDIR, NULL, 0);
if (err)
goto out_bgio;
//设置gpio和软件中断号映射的关系
port->bgc.gc.to_irq = mxc_gpio_to_irq;
//设置gpio的编号基数
port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
pdev->id * 32;
//将GPIO控制器计入到gpiolib驱动的链表中,也就是之后我们可以gpio_direction去操作io了
err = gpiochip_add(&port->bgc.gc);
if (err)
goto out_bgpio_remove;
//申请系统IRQ number资源,一共32个,这里从0开始搜索可用的资源。
irq_base = `irq_alloc_descs`(-1, 0, 32, numa_node_id());
if (irq_base < 0) {
err = irq_base;
goto out_gpiochip_remove;
}
//向中断控制器注册并创建映射关系
port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
&irq_domain_simple_ops, NULL);
if (!port->domain) {
err = -ENODEV;
goto out_irqdesc_free;
}
//初始化gpio中断控制器
/* gpio-mxc can be a generic irq chip */
mxc_gpio_init_gc(port, irq_base);

list_add_tail(&port->node, &mxc_gpio_ports);

return 0;

out_irqdesc_free:
irq_free_descs(irq_base, 32);
out_gpiochip_remove:
gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
bgpio_remove(&port->bgc);
out_bgio:
dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
return err;
}

在之前分析我们知道每一组gpio使用两个中断号,好吧还是回去看一眼dts,机型不行,看interrupts结点。好!接着看代码。

1
2
3
4
5
6
7
8
9
10
11

gpio1: gpio@0209c000 {
compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
<0 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

22、23行我们把硬件中断号放到mxc_gpio_port 结构 port的 irq和irq_high成员中。哎,为什么是是两个呢,人家就是这么设计,就不要较真干嘛不只用一个解决了!
31、32 这里把中断状态寄存器和中断屏蔽寄存器
45、46和49、50行分别为两个中断号,设置了中断处理函数和设置了中断处理函数的参数。
irq_set_chained_handler
irq_set_handler_data
接着62行设置GPIO模块的中断查找函数 mxc_gpio_to_irq,也就是这两个中断发生后我们通过中断状态字寄存器去确认到底是哪个管脚被设,也就知道了哪个管脚发生了中断。
77行使用了irq_domain_add_legacy来向注册中断寄存器并建立映射关系,这里和我们之前说的哪个注册和映射函数不一样,我们一会儿展开来看看把。

85行,初始化GPIO的中断控制器 mxc_gpio_init_gc

这样大致看看有点谱了,和我们之前想的比较接近。
1.初始化中断控制器
2.注册中断处理程序

接着我们接下来按照上面列的行号来品味一下细节。
前两项没什么好说的。接着我们先看看中断处理函数mx3_gpio_irq_handler 的实现,具体的设置函数我们留到Linux中断的实现再来分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
{
while (irq_stat != 0) {
//fls或去最高有效位(从低位往左数最后的有效bit位)
//这里就是得到哪个管脚触发的中断,一一处理,而且优先级就是按照这个规则来了。
int irqoffset = fls(irq_stat) - 1;

//这里的做法是为了让没有双边沿触发的芯片,用轮流高低电平触发的方式解决。
//imx6芯片忽略就好,也看了半天才看清楚原来是这样。
if (port->both_edges & (1 << irqoffset))
mxc_flip_edge(port, irqoffset);
//irq_find_mapping 这个再说吧,先记一下
generic_handle_irq(irq_find_mapping(port->domain, irqoffset));

irq_stat &= ~(1 << irqoffset);
}
}

这个代码对我们之前已经看过一次,逻辑很简单,查看终端状态字和屏蔽字寄存器,考录是否要响应这个中断,最后调用generic_handle_irq函数也就是我们最终设备驱动程序中注册的真正意义的中断服务程序了。
这里 irq_find_mapping 是我们第二次见了,在(三)中,我看到了这个函数的作用就是找到中断号,这里先MARK一下,我们看完中断的映射自然就知道了。

这里62行的这个函数mxc_gpio_to_irq同样是用irq_find_map这个把戏:

1
2
3
4
5
6
7
8
static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
struct bgpio_chip *bgc = to_bgpio_chip(gc);
struct mxc_gpio_port *port =
container_of(bgc, struct mxc_gpio_port, bgc);

return irq_find_mapping(port->domain, offset);
}

终于到了:irq_domain_add_legacy 这里就直接进去,废话不多,毕竟还有两个函数等着它。

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
//kernel/irq/irqdomain.c
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
unsigned int size //32,
unsigned int first_irq ,
//irq_alloc_descs(-1, 0, 32, numa_node_id())
irq_hw_number_t first_hwirq, //0
const struct irq_domain_ops *ops,
//irq_domain_simple_ops
void *host_data //NULL)
{
struct irq_domain *domain;

domain = __irq_domain_add(of_node, first_hwirq + size,
first_hwirq + size, 0, ops, host_data);
if (domain)
irq_domain_associate_many(domain, first_irq, first_hwirq, size);

return domain;
}

struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,//32
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irq_domain *domain;

domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
if (WARN_ON(!domain))
return NULL;

/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->of_node = of_node_get(of_node);
domain->hwirq_max = hwirq_max;//HW最大的值
domain->revmap_size = size;//线性映射数组长度,如果是0的话使用radix_tree
domain->revmap_direct_max_irq = direct_max;//0 不是用直接映射
irq_domain_check_hierarchy(domain);

mutex_lock(&irq_domain_mutex);
list_add(&domain->link, &irq_domain_list);
mutex_unlock(&irq_domain_mutex);

pr_debug("Added domain %s\n", domain->name);
return domain;
}

这里我们发现和我们之前看的流程区别在于domain_add的方式略微不同,建立映射调用的函数都是irq_domain_associate_many。
我们的参数列一下:

size =32 //线性映射且长度是32
hwirq_max=32
direct_max=0
ops=irq_domain_simple_ops
host_data=NULL
这里我们关注一下irq_domain_simple_ops 的实现,估计就是映射关系了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const struct irq_domain_ops irq_domain_simple_ops = {
.xlate = irq_domain_xlate_onetwocell,
};

int irq_domain_xlate_onetwocell(struct irq_domain *d,
struct device_node *ctrlr,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type)
{
if (WARN_ON(intsize < 1))
return -EINVAL;
*out_hwirq = intspec[0];
*out_type = (intsize > 1) ? intspec[1] : IRQ_TYPE_NONE;
return 0;
}

这个函数我们暂时没发现在哪里会调用,暂时先放一一边。。

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

void irq_domain_associate_many(struct irq_domain *domain,
unsigned int irq_base,//irq_base
irq_hw_number_t hwirq_base,//0
int count //32)
{
int i;
for (i = 0; i < count; i++) {
irq_domain_associate(domain, irq_base + i, hwirq_base + i);
}
}

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函数
}
//domain->revmap_size=32
//hwirq== 0-31
if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq;//填写线性映射lookup table的数据
} else { //domain->revmap_size==0是使用redix tree
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;
}

这里ops->map =NULL
hwirq < domain->revmap_size 都为32,所以采用的线性关系来做映射,接着我们看看 irq_find_mapping 是否如何找到这个映射关系的?

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
unsigned int irq_find_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
struct irq_data *data;

/* Look for default domain if nececssary */
if (domain == NULL)
domain = irq_default_domain;
if (domain == NULL)
return 0;
//直接映射
if (hwirq < domain->revmap_direct_max_irq) {
data = irq_domain_get_irq_data(domain, hwirq);
if (data && data->hwirq == hwirq)
return hwirq;
}

/* Check if the hwirq is in the linear revmap. */
//线性映射
if (hwirq < domain->revmap_size)
return domain->linear_revmap[hwirq];

rcu_read_lock();
//radix_tree映射
data = radix_tree_lookup(&domain->revmap_tree, hwirq);
rcu_read_unlock();
return data ? data->irq : 0;
}

这里我们使用的是线性映射,就从数组中找到了IRQ number.

最后看完mxc_gpio_init_gc函数结束此次分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void __init mxc_gpio_init_gc(struct mxc_gpio_port *port, int irq_base)
{
struct irq_chip_generic *gc;
struct irq_chip_type *ct;

gc = irq_alloc_generic_chip("gpio-mxc", 1, irq_base,
port->base, handle_level_irq);
gc->private = port;

ct = gc->chip_types;
ct->chip.irq_ack = irq_gc_ack_set_bit;
ct->chip.irq_mask = irq_gc_mask_clr_bit;
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->chip.irq_set_type = gpio_set_irq_type;
ct->chip.irq_set_wake = gpio_set_wake_irq;
ct->regs.ack = GPIO_ISR;
ct->regs.mask = GPIO_IMR;

irq_setup_generic_chip(gc, IRQ_MSK(32), IRQ_GC_INIT_NESTED_LOCK,
IRQ_NOREQUEST, 0);
}

这里的功能是设置中断的各个寄存器的控制函数,和每一个管脚触发电平变化时的控制函数:handle_level_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
26
27
28

void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc);

if (!irq_may_run(desc))
goto out_unlock;

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);

/*
* If its disabled or no action available
* keep it masked and get out of here
*/
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}

handle_irq_event(desc);

cond_unmask_irq(desc);

out_unlock:
raw_spin_unlock(&desc->lock);
}

handle_irq_event 便是上报给系统发生中断的消息。

这样我们就大致理清了思路。

  1. 注册irq_domain
  2. 增加映射关系
  3. GPIO中断器的初始化(如GPIO中断控制器的控制函数等)
客官,小的这记口碎大石值几个钱?