基於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_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
完。