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

时间:2023-03-09 17:07:20
基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(2)

作者:彭东林

邮箱:pengdonglin137@163.com

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

 static int samsung_gpiolib_register(struct platform_device *pdev,
struct samsung_pinctrl_drv_data *drvdata)
{
struct samsung_pin_bank *bank = drvdata->pin_banks;
struct gpio_chip *gc;
int ret;
int i;
for (i = ; i < drvdata->nr_banks; ++i, ++bank) { // 遍历pinctrl@11000000下的所有bank,我们关心的是gpx3这个bank
bank->gpio_chip = samsung_gpiolib_chip; // gpio_chip
gc = &bank->gpio_chip;
// 这个bank的gpio在系统中的逻辑起始号, 其中drvdata->pin_base是pinctrl@11000000的在系统中的逻辑gpio起始号,
// 而bank->pin_base是这个bank在pinctrl@11000000中的逻辑起始号(从0开始)
gc->base = drvdata->pin_base + bank->pin_base;
gc->ngpio = bank->nr_pins; // 这个bank中含有的gpio的个数
gc->parent = &pdev->dev;
gc->of_node = bank->of_node; //对于gpx3来说,就是gpx3那个节点的node
gc->label = bank->name;
ret = gpiochip_add_data(gc, bank);
...
}
return ;
...
}

---> gpiochip_add_data(struct gpio_chip *chip, void *data)

 int gpiochip_add_data(struct gpio_chip *chip, void *data)
{
unsigned long flags;
int status = ;
unsigned i;
int base = chip->base;
struct gpio_device *gdev;
// 每一个bank都都应一个唯一的gpio_device和gpio_chip
gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
gdev->dev.bus = &gpio_bus_type;
gdev->chip = chip;
chip->gpiodev = gdev;
... ...
if (chip->of_node)
gdev->dev.of_node = chip->of_node; // 分配一个唯一的id
gdev->id = ida_simple_get(&gpio_ida, , , GFP_KERNEL);
dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);
... ...
// 为这个chip下的每一个gpio都要分配一个gpio_desc结构体
gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[]), GFP_KERNEL);
... ...
// 这个chip中含有的gpio的个数
gdev->ngpio = chip->ngpio;
// gpx3 这个bank
gdev->data = data;
... ...
// base表示的是这个bank在系统中的逻辑gpio号
gdev->base = base;
// 将这个bank对应的gpio_device添加到全局链表gpio_devices中
// 在添加的时候会根据gdev->base和ngpio在gpio_devices链表中找到合适的位置
status = gpiodev_add_to_list(gdev);
... ...
for (i = ; i < chip->ngpio; i++) {
struct gpio_desc *desc = &gdev->descs[i];
desc->gdev = gdev;
... ...
}
... ...
// 默认这个chip下的所有gpio都是可以产生中断
status = gpiochip_irqchip_init_valid_mask(chip);
status = of_gpiochip_add(chip);
... ...
return ;
... ...
}

---> of_gpiochip_add(struct gpio_chip *chip)

 int of_gpiochip_add(struct gpio_chip *chip)
{
int status;
... ...
if (!chip->of_xlate) {
chip->of_gpio_n_cells = ;
chip->of_xlate = of_gpio_simple_xlate;
}
... ...
}

这里需要看一下of_gpio_simple_xlate的实现,这个在下面的分析中会被回调

 int of_gpio_simple_xlate(struct gpio_chip *gc,
const struct of_phandle_args *gpiospec, u32 *flags)
{
.. ...
if (flags) // 第二个参数表示的是flag
*flags = gpiospec->args[];
// 第一个参数表示的是gpio号
return gpiospec->args[];
}

从上面的分析中我们知道了如下几点:

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

这里还是先把设备树中涉及到的节点列在这里:

 / {
interrupt-parent = <&gic>;
#address-cells = <0x1>;
#size-cells = <0x1>;
compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
model = "FriendlyARM TINY4412 board based on Exynos4412";
aliases {
pinctrl1 = "/pinctrl@11000000";
};
gic: interrupt-controller@ {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <0x3>;
interrupt-controller;
reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
cpu-offset = <0x4000>;
};
pinctrl@ {
compatible = "samsung,exynos4x12-pinctrl";
reg = <0x11000000 0x1000>;
interrupts = <0x0 0x2e 0x0>;
gpx3: gpx3 {
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
wakeup-interrupt-controller {
compatible = "samsung,exynos4210-wakeup-eint";
interrupt-parent = <0x1>;
interrupts = <0x0 0x20 0x0>;
};
};
interrupt_xeint26: interrupt_xeint26 {
compatible = "tiny4412,interrupt_xeint26";
int-gpio = <&gpx3 GPIO_ACTIVE_HIGH>;
};
};

上面的节点interrupt_xeint26中引用了gpx3_2,而且在驱动中打算将这个gpio当作中断引脚来使用。

下面是对应的驱动程序:

 #include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
typedef struct
{
int gpio;
int irq;
char name[];
}xeint26_data_t;
static irqreturn_t xeint26_isr(int irq, void *dev_id)
{
xeint26_data_t *data = dev_id;
printk("%s enter, %s: gpio:%d, irq: %d\n", __func__, data->name, data->gpio, data->irq);
return IRQ_HANDLED;
}
static int xeint26_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
int irq_gpio = -;
int irq = -;
int ret = ;
int i = ;
xeint26_data_t *data = NULL;
printk("%s enter.\n", __func__);
if (!dev->of_node) {
dev_err(dev, "no platform data.\n");
goto err1;
}
data = devm_kmalloc(dev, sizeof(*data)*, GFP_KERNEL);
if (!data) {
dev_err(dev, "no memory.\n");
goto err0;
}
for (i = ; i < ; i++) {
sprintf(data[i].name, "int-gpio");
irq_gpio = of_get_named_gpio(dev->of_node,
data[i].name, );
if (irq_gpio < ) {
dev_err(dev, "Looking up %s property in node %s failed %d\n",
data[i].name, dev->of_node->full_name, irq_gpio);
goto err1;
}
data[i].gpio = irq_gpio;
irq = gpio_to_irq(irq_gpio);
if (irq < ) {
dev_err(dev,
"Unable to get irq number for GPIO %d, error %d\n",
irq_gpio, irq);
goto err1;
}
data[i].irq = irq;
printk("%s: gpio: %d ---> irq (%d)\n", __func__, irq_gpio, irq);
ret = devm_request_any_context_irq(dev, irq,
xeint26_isr, IRQF_TRIGGER_FALLING, data[i].name, data+i);
if (ret < ) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
irq, ret);
goto err1;
}
}
return ;
err1:
devm_kfree(dev, data);
err0:
return -EINVAL;
}
static int xeint26_remove(struct platform_device *pdev) {
printk("%s enter.\n", __func__);
return ;
}
static const struct of_device_id xeint26_dt_ids[] = {
{ .compatible = "tiny4412,interrupt_xeint26", },
{},
};
MODULE_DEVICE_TABLE(of, xeint26_dt_ids);
static struct platform_driver xeint26_driver = {
.driver = {
.name = "interrupt_xeint26",
.of_match_table = of_match_ptr(xeint26_dt_ids),
},
.probe = xeint26_probe,
.remove = xeint26_remove,
};
static int __init xeint26_init(void)
{
int ret;
ret = platform_driver_register(&xeint26_driver);
if (ret)
printk(KERN_ERR "xeint26: probe failed: %d\n", ret);
return ret;
}
module_init(xeint26_init);
static void __exit xeint26_exit(void)
{
platform_driver_unregister(&xeint26_driver);
}
module_exit(xeint26_exit);
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
 int desc_to_gpio(const struct gpio_desc *desc)
{
// 获得这个gpio_desc对应的gpio在系统中的逻辑gpio号
return desc->gdev->base + (desc - &desc->gdev->descs[]);
}
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中。

实验

加载驱动

 [root@tiny4412 mnt]# insmod xeint26.ko
[ 152.084809] xeint26_probe enter.
[ 152.085104] of_get_named_gpiod_flags: parsed 'int-gpio' property of node '/interrupt_xeint26[0]' - status ()
[ 152.085286] irq: irq_create_mapping(0xef205d00, 0x2)
[ 152.085423] irq: -> using domain @ef205d00
[ 152.085590] __irq_alloc_descs: alloc virq: , cnt:
[ 152.090160] irq: irq on domain /pinctrl@/gpx3 mapped to virtual irq
[ 152.097376] xeint26_probe: gpio: ---> irq ()

可以看到在加载驱动的时候才创建了hwirq和virq之间的映射,分配到的virq是100.此时可以去按下tiny4412上面的key1,可以看到中断处理函数中打印出来的log

 [root@tiny4412 mnt]# [  170.718118] xeint26_isr enter, int-gpio: gpio:, irq:
[ 170.910928] xeint26_isr enter, int-gpio: gpio:, irq:

可以看看当前的中断触发情况:

 [root@tiny4412 mnt]# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
: GIC- Edge mct_comp_irq
: GIC- Edge MCT
: GIC- Edge mmc0
: GIC- Edge .hsotg, .hsotg, dwc2_hsotg:usb1
: GIC- Edge ehci_hcd:usb2, ohci_hcd:usb3
: GIC- Edge .serial
: GIC- Edge .pdma
: GIC- Edge .pdma
: GIC- Edge .mdma
: GIC- Edge .sss
: GIC- Edge .pinctrl
: GIC- Edge .pinctrl
: COMBINER Edge .pinctrl
: GIC- Edge 106e0000.pinctrl
100: 2 0 0 0 exynos4210_wkup_irq_chip 2 Edge int-gpio
IPI0: CPU wakeup interrupts
IPI1: Timer broadcast interrupts
IPI2: Rescheduling interrupts
IPI3: Function call interrupts
IPI4: CPU stop interrupts
IPI5: IRQ work interrupts
IPI6: completion interrupts
Err:

完。