基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(2)

作者:彭东林

邮箱:[email protected]

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_gpio
1 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

完。

相关推荐