基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(2)
作者:彭东林
QQ:405728433
平台
tiny4412 ADK
Linux-4.9
概述
前面一篇博文基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)结合示例分析了一下新版kernel引入设备树和irq domain后中断幕后的一些知识,其中的示例只是使用gpio中断的一种方式,此外,还有一种,就像博文
基於tiny4412的Linux內核移植--- 中斷和GPIO學習(1)中描述的那样,这种实现方式又是如何进行的呢?下面还是结合示例的方式分析。
正文
框图可以参考前一篇博文。
在前一篇博文的第三部分 GPIO控制器驱动中有一个函数我们没有分析,就是samsung_gpiolib_register,把这函数看懂了,后面的分析就顺了,下面的分析最好结合前一篇博文的第三部分 GPIO控制器驱动一块看。
这里还是以pinctrl@11000000这个节点为例分析。
samsung_gpiolib_register
1 static int samsung_gpiolib_register(struct platform_device *pdev,
2 struct samsung_pinctrl_drv_data *drvdata)
3 {
4 struct samsung_pin_bank *bank = drvdata->pin_banks;
5 struct gpio_chip *gc;
6 int ret;
7 int i;
8 for (i = 0; i < drvdata->nr_banks; ++i, ++bank) { // 遍历pinctrl@11000000下的所有bank,我们关心的是gpx3这个bank
9 bank->gpio_chip = samsung_gpiolib_chip; // gpio_chip
10 gc = &bank->gpio_chip;
11 // 这个bank的gpio在系统中的逻辑起始号, 其中drvdata->pin_base是pinctrl@11000000的在系统中的逻辑gpio起始号,
12 // 而bank->pin_base是这个bank在pinctrl@11000000中的逻辑起始号(从0开始)
13 gc->base = drvdata->pin_base + bank->pin_base;
14 gc->ngpio = bank->nr_pins; // 这个bank中含有的gpio的个数
15 gc->parent = &pdev->dev;
16 gc->of_node = bank->of_node; //对于gpx3来说,就是gpx3那个节点的node
17 gc->label = bank->name;
18 ret = gpiochip_add_data(gc, bank);
19 ...
20 }
21 return 0;
22 ...
23 } ---> gpiochip_add_data(struct gpio_chip *chip, void *data)
1 int gpiochip_add_data(struct gpio_chip *chip, void *data)
2 {
3 unsigned long flags;
4 int status = 0;
5 unsigned i;
6 int base = chip->base;
7 struct gpio_device *gdev;
8 // 每一个bank都都应一个唯一的gpio_device和gpio_chip
9 gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
10 gdev->dev.bus = &gpio_bus_type;
11 gdev->chip = chip;
12 chip->gpiodev = gdev;
13 ... ...
14 if (chip->of_node)
15 gdev->dev.of_node = chip->of_node;
16
17 // 分配一个唯一的id
18 gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL);
19 dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
20 ... ...
21 // 为这个chip下的每一个gpio都要分配一个gpio_desc结构体
22 gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL);
23 ... ...
24 // 这个chip中含有的gpio的个数
25 gdev->ngpio = chip->ngpio;
26 // gpx3 这个bank
27 gdev->data = data;
28 ... ...
29 // base表示的是这个bank在系统中的逻辑gpio号
30 gdev->base = base;
31 // 将这个bank对应的gpio_device添加到全局链表gpio_devices中
32 // 在添加的时候会根据gdev->base和ngpio在gpio_devices链表中找到合适的位置
33 status = gpiodev_add_to_list(gdev);
34 ... ...
35 for (i = 0; i < chip->ngpio; i++) {
36 struct gpio_desc *desc = &gdev->descs[i];
37 desc->gdev = gdev;
38 ... ...
39 }
40 ... ...
41 // 默认这个chip下的所有gpio都是可以产生中断
42 status = gpiochip_irqchip_init_valid_mask(chip);
43 status = of_gpiochip_add(chip);
44 ... ...
45 return 0;
46 ... ...
47 } ---> of_gpiochip_add(struct gpio_chip *chip)
1 int of_gpiochip_add(struct gpio_chip *chip)
2 {
3 int status;
4 ... ...
5 if (!chip->of_xlate) {
6 chip->of_gpio_n_cells = 2;
7 chip->of_xlate = of_gpio_simple_xlate;
8 }
9 ... ...
10 } 这里需要看一下of_gpio_simple_xlate的实现,这个在下面的分析中会被回调
1 int of_gpio_simple_xlate(struct gpio_chip *gc,
2 const struct of_phandle_args *gpiospec, u32 *flags)
3 {
4 .. ...
5 if (flags) // 第二个参数表示的是flag
6 *flags = gpiospec->args[1];
7 // 第一个参数表示的是gpio号
8 return gpiospec->args[0];
9 } 从上面的分析中我们知道了如下几点:
1. 每一个bank(如gpx3)都对应一个gpio_chip和gpio_device
2. 这个bank下的每一个gpio都会对应一个唯一的gpio_desc结构体,这些结构提的首地址存放在gpio_device的desc中
3. 上面的gpio_device会加入到全局gpio_devices链表中
4. gpio_chip的of_gpio_n_cells被赋值为2,表示引用一个gpio资源需要两个参数,负责解析这两个参数函数以的of_xlate函数为of_gpio_simple_xlate,其中第一个参数表示gpio号(在对应的bank中),第二个表示flag
这里还是先把设备树中涉及到的节点列在这里:
1 / {
2 interrupt-parent = <&gic>;
3 #address-cells = <0x1>;
4 #size-cells = <0x1>;
5 compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
6 model = "FriendlyARM TINY4412 board based on Exynos4412";
7 aliases {
8 pinctrl1 = "/pinctrl@11000000";
9 };
10 gic: interrupt-controller@10490000 {
11 compatible = "arm,cortex-a9-gic";
12 #interrupt-cells = <0x3>;
13 interrupt-controller;
14 reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
15 cpu-offset = <0x4000>;
16 };
17 pinctrl@11000000 {
18 compatible = "samsung,exynos4x12-pinctrl";
19 reg = <0x11000000 0x1000>;
20 interrupts = <0x0 0x2e 0x0>;
21 gpx3: gpx3 {
22 gpio-controller;
23 #gpio-cells = <0x2>;
24 interrupt-controller;
25 #interrupt-cells = <0x2>;
26 };
27 wakeup-interrupt-controller {
28 compatible = "samsung,exynos4210-wakeup-eint";
29 interrupt-parent = <0x1>;
30 interrupts = <0x0 0x20 0x0>;
31 };
32 };
33 interrupt_xeint26: interrupt_xeint26 {
34 compatible = "tiny4412,interrupt_xeint26";
35 int-gpio = <&gpx3 2 GPIO_ACTIVE_HIGH>;
36 };
37 }; 上面的节点interrupt_xeint26中引用了gpx3_2,而且在驱动中打算将这个gpio当作中断引脚来使用。
下面是对应的驱动程序:
1 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/platform_device.h>
4 #include <linux/gpio.h>
5 #include <linux/of.h>
6 #include <linux/of_gpio.h>
7 #include <linux/interrupt.h>
8 typedef struct
9 {
10 int gpio;
11 int irq;
12 char name[20];
13 }xeint26_data_t;
14 static irqreturn_t xeint26_isr(int irq, void *dev_id)
15 {
16 xeint26_data_t *data = dev_id;
17 printk("%s enter, %s: gpio:%d, irq: %d\n", __func__, data->name, data->gpio, data->irq);
18 return IRQ_HANDLED;
19 }
20 static int xeint26_probe(struct platform_device *pdev) {
21 struct device *dev = &pdev->dev;
22 int irq_gpio = -1;
23 int irq = -1;
24 int ret = 0;
25 int i = 0;
26 xeint26_data_t *data = NULL;
27 printk("%s enter.\n", __func__);
28 if (!dev->of_node) {
29 dev_err(dev, "no platform data.\n");
30 goto err1;
31 }
32 data = devm_kmalloc(dev, sizeof(*data)*1, GFP_KERNEL);
33 if (!data) {
34 dev_err(dev, "no memory.\n");
35 goto err0;
36 }
37 for (i = 0; i < 1; i++) {
38 sprintf(data[i].name, "int-gpio");
39 irq_gpio = of_get_named_gpio(dev->of_node,
40 data[i].name, 0);
41 if (irq_gpio < 0) {
42 dev_err(dev, "Looking up %s property in node %s failed %d\n",
43 data[i].name, dev->of_node->full_name, irq_gpio);
44 goto err1;
45 }
46 data[i].gpio = irq_gpio;
47 irq = gpio_to_irq(irq_gpio);
48 if (irq < 0) {
49 dev_err(dev,
50 "Unable to get irq number for GPIO %d, error %d\n",
51 irq_gpio, irq);
52 goto err1;
53 }
54 data[i].irq = irq;
55 printk("%s: gpio: %d ---> irq (%d)\n", __func__, irq_gpio, irq);
56 ret = devm_request_any_context_irq(dev, irq,
57 xeint26_isr, IRQF_TRIGGER_FALLING, data[i].name, data+i);
58 if (ret < 0) {
59 dev_err(dev, "Unable to claim irq %d; error %d\n",
60 irq, ret);
61 goto err1;
62 }
63 }
64 return 0;
65 err1:
66 devm_kfree(dev, data);
67 err0:
68 return -EINVAL;
69 }
70 static int xeint26_remove(struct platform_device *pdev) {
71 printk("%s enter.\n", __func__);
72 return 0;
73 }
74 static const struct of_device_id xeint26_dt_ids[] = {
75 { .compatible = "tiny4412,interrupt_xeint26", },
76 {},
77 };
78 MODULE_DEVICE_TABLE(of, xeint26_dt_ids);
79 static struct platform_driver xeint26_driver = {
80 .driver = {
81 .name = "interrupt_xeint26",
82 .of_match_table = of_match_ptr(xeint26_dt_ids),
83 },
84 .probe = xeint26_probe,
85 .remove = xeint26_remove,
86 };
87 static int __init xeint26_init(void)
88 {
89 int ret;
90 ret = platform_driver_register(&xeint26_driver);
91 if (ret)
92 printk(KERN_ERR "xeint26: probe failed: %d\n", ret);
93 return ret;
94 }
95 module_init(xeint26_init);
96 static void __exit xeint26_exit(void)
97 {
98 platform_driver_unregister(&xeint26_driver);
99 }
100 module_exit(xeint26_exit);
101 MODULE_LICENSE("GPL"); 其中我们只需要分析两个关键的函数:of_get_named_gpio 和 gpio_to_irq.
of_get_named_gpio
这个函数的作用是根据传递的属性的name和索引号,得到一个gpio号
of_get_named_gpio
---> of_get_named_gpio_flags(np, propname, index, NULL)
int of_get_named_gpio_flags(struct device_node *np, const char *list_name,
int index, enum of_gpio_flags *flags)
{
struct gpio_desc *desc;
desc = of_get_named_gpiod_flags(np, list_name, index, flags);
... ...
return desc_to_gpio(desc);
} ---> of_get_named_gpiod_flags
struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np,
const char *propname, int index, enum of_gpio_flags *flags)
{
struct of_phandle_args gpiospec;
struct gpio_chip *chip;
struct gpio_desc *desc;
int ret;
// 解析"int-gpio"属性中第index字段,将解析结果存放到gpiospec中
/*
struct of_phandle_args {
struct device_node *np; // int-gpio属性所引用的gpio-controller的node,对于'int-gpio'来说就是gpx3
int args_count; // gpx3这个gpio-controller的#gpio-cells属性的值
uint32_t args[MAX_PHANDLE_ARGS]; // 具体描述这个gpio属性的每一个参数
};
*/
ret = of_parse_phandle_with_args(np, propname, "#gpio-cells", index,
&gpiospec);
// 上面gpiospec的np存放的索引用的gpio-controller的node,
// 遍历gpio_devices链表,找到对应的gpio_device,也就找到了gpio_chip
chip = of_find_gpiochip_by_xlate(&gpiospec);
// 调用chip->of_xlate解析gpiospec,返回gpiospec的args中的第一个参数args[0],
// 也就是前面分析的在bank中的逻辑gpio号
// 知道了gpio号,就可以在gpio_device->desc中索引到对应的gpio_desc
desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
return desc;
} ---> desc_to_gpio1 int desc_to_gpio(const struct gpio_desc *desc)
2 {
3 // 获得这个gpio_desc对应的gpio在系统中的逻辑gpio号
4 return desc->gdev->base + (desc - &desc->gdev->descs[0]);
5 }gpio_to_irq
将这个gpio转换成对应的virq
gpio_to_irq(irq_gpio)
---> __gpio_to_irq(gpio)
---> gpiod_to_irq(gpio_to_desc(gpio))
这里调用了两个函数,函数gpio_to_desc根据传入的全局逻辑gpio号找到对应的gpio_desc,原理是:遍历gpio_devices链表,根据传入的逻辑gpio号,就可以定位到所属的gpio_device,前面说过,在将gpio_device加入到gpio_devices链表的时候,不是乱加的,而是根据gpio_device的base和ngpio找到一个合适的位置。找到了gpio_device,那么通过索引它的desc成员,就可以找到对应的gpio_desc
gpio_to_desc
struct gpio_desc *gpio_to_desc(unsigned gpio) // 传入的是全局逻辑gpio号
{
struct gpio_device *gdev;
unsigned long flags;
list_for_each_entry(gdev, &gpio_devices, list) {
if (gdev->base <= gpio &&
gdev->base + gdev->ngpio > gpio) {
return &gdev->descs[gpio - gdev->base]; // 获得gpio_desc
}
}
... ...
} gpiod_to_irq
int gpiod_to_irq(const struct gpio_desc *desc)
{
struct gpio_chip *chip;
int offset;
... ...
chip = desc->gdev->chip;
// 这个函数通过desc - &desc->gdev->descs[0]就可以计算出,对于gpx3_2,就是2
// 这个gpio_desc在所属的bank中的逻辑gpio号
offset = gpio_chip_hwgpio(desc);
int retirq = chip->to_irq(chip, offset);
... ...
return retirq;
... ...
} 上面的第11行就是前面samsung_gpiolib_register中设置的samsung_gpiolib_chip,其to_irq定义如下
static int samsung_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
struct samsung_pin_bank *bank = gpiochip_get_data(gc);
unsigned int virq;
.. ..
virq = irq_create_mapping(bank->irq_domain, offset);
return (virq) ? : -ENXIO;
}需要注意的是offset,比如对于gpx3_2,那么offset就是2, 结合前一篇的博文,这里的offset就是hwirq,调用irq_create_mapping可以为该hwirq在kernel中分配一个唯一的virq,同时将hwirq和virq的映射关系存放到bank->irq_domain中。
实验
加载驱动
1 [root@tiny4412 mnt]# insmod xeint26.ko 2 [ 152.084809] xeint26_probe enter. 3 [ 152.085104] of_get_named_gpiod_flags: parsed 'int-gpio' property of node '/interrupt_xeint26[0]' - status (0) 4 [ 152.085286] irq: irq_create_mapping(0xef205d00, 0x2) 5 [ 152.085423] irq: -> using domain @ef205d00 6 [ 152.085590] __irq_alloc_descs: alloc virq: 100, cnt: 1 7 [ 152.090160] irq: irq 2 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 100 8 [ 152.097376] xeint26_probe: gpio: 238 ---> irq (100)
可以看到在加载驱动的时候才创建了hwirq和virq之间的映射,分配到的virq是100.此时可以去按下tiny4412上面的key1,可以看到中断处理函数中打印出来的log
1 [root@tiny4412 mnt]# [ 170.718118] xeint26_isr enter, int-gpio: gpio:238, irq: 100 2 [ 170.910928] xeint26_isr enter, int-gpio: gpio:238, irq: 100
可以看看当前的中断触发情况:
1 [root@tiny4412 mnt]# cat /proc/interrupts 2 CPU0 CPU1 CPU2 CPU3 3 36: 0 0 0 0 GIC-0 89 Edge mct_comp_irq 4 37: 4702 2840 1176 787 GIC-0 28 Edge MCT 5 44: 34 0 0 0 GIC-0 107 Edge mmc0 6 45: 1 0 0 0 GIC-0 103 Edge 12480000.hsotg, 12480000.hsotg, dwc2_hsotg:usb1 7 46: 881 0 0 0 GIC-0 102 Edge ehci_hcd:usb2, ohci_hcd:usb3 8 48: 341 0 0 0 GIC-0 84 Edge 13800000.serial 9 52: 4 0 0 0 GIC-0 67 Edge 12680000.pdma 10 53: 0 0 0 0 GIC-0 68 Edge 12690000.pdma 11 54: 0 0 0 0 GIC-0 66 Edge 12850000.mdma 12 67: 0 0 0 0 GIC-0 144 Edge 10830000.sss 13 68: 0 0 0 0 GIC-0 79 Edge 11400000.pinctrl 14 69: 0 0 0 0 GIC-0 78 Edge 11000000.pinctrl 15 87: 0 0 0 0 COMBINER 80 Edge 3860000.pinctrl 16 88: 0 0 0 0 GIC-0 104 Edge 106e0000.pinctrl 17 100: 2 0 0 0 exynos4210_wkup_irq_chip 2 Edge int-gpio 18 IPI0: 0 1 1 1 CPU wakeup interrupts 19 IPI1: 0 0 0 0 Timer broadcast interrupts 20 IPI2: 896 894 374 149 Rescheduling interrupts 21 IPI3: 0 2 3 2 Function call interrupts 22 IPI4: 0 0 0 0 CPU stop interrupts 23 IPI5: 212 45 91 8 IRQ work interrupts 24 IPI6: 0 0 0 0 completion interrupts 25 Err: 0
完。