如何从 i2c io 扩展器 linux 请求 GPIO 中断(-EINVAL 失败)
How to request GPIO interrupt from i2c io expander linux (fails with -EINVAL)
我正在开发一个内核模块,需要使用 IO 扩展器 (MCP23008) 处理来自 4 个 GPIO 的中断。我正在使用内核 5.10.1-v7(buildroot 映像)开发 Raspberry Pi 3B+。当使用来自 40 针接头的“正常”GPIO 时,模块 (mymodule) 按预期工作。但是,当我尝试使用 pinctrl-mcp23s08 模块中的 GPIO 时,request_interrupt
函数失败并且 returns -22 (EINVAL).
我错过了什么?在我看来,从扩展的 GPIO 请求中断的一切都已准备就绪。其他类似的线程指向 request_interrupt
的第 5 个参数中与 NULL
一起使用的 IRQF_SHARED
标志,但我的情况并非如此。
这是相关的设备树片段:
/* for the io expander (mcp23s08) module */
&i2c1 { compatible = "brcm,bcm2835-i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status="okay";
mcp23008: mcp23008@20 {
compatible = "microchip,mcp23008";
reg=<0x20>; /* i2c slave address */
gpio-controller; /* mark the node as gpio-controller */
#gpio-cells=<2>; /* pin number and flags */
interrupt-parent = <&gpio>;
interrupt-controller; /* mark the node as interrupt-controller */
#interrupt-cells=<2>; /* pin number and flags */
interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
status="okay";
};
};
/* for mymodule */
/ {
compatible = "raspberrypi,3-model-b-plus", "brcm,bcm2837";
model = "Raspberry Pi 3 Model B+";
mymodule {
compatible = "myvendor,mymodule";
gpios = <&mcp23008 0 GPIO_ACTIVE_HIGH>,
<&mcp23008 1 GPIO_ACTIVE_HIGH>,
<&mcp23008 2 GPIO_ACTIVE_HIGH>,
<&mcp23008 3 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&mcp23008>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
<1 IRQ_TYPE_LEVEL_HIGH>,
<2 IRQ_TYPE_LEVEL_HIGH>,
<3 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
};
我知道 mcp23s08 模块在引导时正确插入,因为我可以从 sysfs 导出 GPIO 并读取它们的值。此外,在 mymodule 的 init 函数中,gpio_request_array
函数使用 mcp23s08 模块(gpio 496 到 499)给出的 GPIO 成功。最后,/proc/interrupts
中的这一行告诉我 mcp23s08 驱动程序(在 gpio 16 上)的中断已正确分配:
$ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
50: 5505 0 0 0 ARMCTRL-level 1 Edge 3f00b880.mailbox
51: 47 0 0 0 ARMCTRL-level 2 Edge VCHIQ doorbell
73: 0 0 0 0 ARMCTRL-level 48 Edge bcm2708_fb DMA
75: 12435 0 0 0 ARMCTRL-level 50 Edge DMA IRQ
77: 716 0 0 0 ARMCTRL-level 52 Edge DMA IRQ
78: 16 0 0 0 ARMCTRL-level 53 Edge DMA IRQ
81: 0 0 0 0 ARMCTRL-level 56 Edge DMA IRQ
86: 199 0 0 0 ARMCTRL-level 61 Edge ttyS0
89: 887966 0 0 0 ARMCTRL-level 64 Edge dwc_otg, dwc_otg_pcd, dwc_otg_hcd:usb1
110: 2 0 0 0 ARMCTRL-level 85 Edge 3f804000.i2c
111: 0 0 0 0 ARMCTRL-level 86 Edge 3f204000.spi
113: 119 0 0 0 ARMCTRL-level 88 Edge mmc0
119: 214231 0 0 0 ARMCTRL-level 94 Edge mmc1
194: 0 0 0 0 bcm2836-timer 0 Edge arch_timer
195: 91376 256895 932964 45993 bcm2836-timer 1 Edge arch_timer
198: 0 0 0 0 bcm2836-pmu 9 Edge arm-pmu
199: 1 0 0 0 pinctrl-bcm2835 4 Edge pps.-1
200: 0 0 0 0 pinctrl-bcm2835 16 Level 1-0020
205: 0 0 0 0 lan78xx-irqs 17 Edge usb-001:004:01
FIQ: usb_fiq
IPI0: 0 0 0 0 CPU wakeup interrupts
IPI1: 0 0 0 0 Timer broadcast interrupts
IPI2: 91 73 100 88 Rescheduling interrupts
IPI3: 4671 20748 12983 31858 Function call interrupts
IPI4: 0 0 0 0 CPU stop interrupts
IPI5: 0 0 0 0 IRQ work interrupts
IPI6: 0 0 0 0 completion interrupts
Err: 0
请求中断失败的相关初始化代码如下:
static int __init
mymodule_init(void)
{
int i;
int err;
/* [...] */
err = platform_driver_register(&gpio_driver);
if (err) {
printk(KERN_ERR "mymodule: failed to register platform driver\n");
goto fail_platform_driver_register;
}
err = gpio_request_array(gpios, COUNT_OF(gpios));
if (err != 0) {
printk(KERN_ERR "mymodule: failed to request gpio array: %d\n", err);
goto fail_gpio_request_array;
}
for (i = 0; i < COUNT_OF(irq_ids); ++i) {
err = platform_get_irq(dev.platform_device, i);
if (err > 0) {
irq_ids[i] = err;
printk(KERN_INFO "i=%d irq=%d\n", i, irq_ids[i]);
} else {
printk(KERN_ERR "mymodule: gpio_to_irq failed i = %d\n", i);
goto fail_platform_get_irq;
}
}
for (i = 0; i < COUNT_OF(irq_ids); ++i) {
err = request_irq(
irq_ids[i],
(irq_handler_t) mymodule_irq_handler,
IRQF_TRIGGER_HIGH,
"mymodule_irq_handler",
NULL
);
if (err) {
printk(KERN_ERR "mymodule: failed to request_irq at i=%d irq=%d: %d\n",
i, irq_ids[i], err);
goto fail_request_irq;
}
}
/* success, return here */
return 0;
/* ommited: error handling here */
}
最后,这里是 dmesg
输出:
[ 76.548689] i=0 irq=201
[ 76.554473] i=1 irq=202
[ 76.560189] i=2 irq=203
[ 76.566033] i=3 irq=204
[ 76.568525] mymodule: failed to request_irq at i=0 irq=201: -22
失败的至少一个原因是父中断控制器使用线程中断处理程序,因此子中断处理程序也需要线程化(这些将是“嵌套”线程中断处理程序)。
在“kernel/irq/manage.c”中的__setup_irq
中测试了不兼容性:
/*
* Check whether the interrupt nests into another interrupt
* thread.
*/
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
/*
* Replace the primary handler which was provided from
* the driver for non nested interrupt handling by the
* dummy function which warns when called.
*/
new->handler = irq_nested_primary_handler;
(new->thread_fn
在请求非线程 IRQ 处理程序时是 NULL
。上面的代码导致 request_irq()
的 return 值为 -EINVAL
. new->handler = irq_nested_primary_handler;
部分被描述为下面 request_threaded_irq
讨论的脚注。)
可以将原始代码中的 request_irq
调用更改为以下内容以使用线程化 IRQ 处理程序:
err = request_threaded_irq(
irq_ids[i],
NULL,
mymodule_irq_handler,
IRQF_TRIGGER_HIGH,
"mymodule_irq_handler",
NULL
);
主要区别是“硬 IRQ”处理程序已设置为 NULL
,因为它不用于纯线程 IRQ 处理程序(包括嵌套线程 IRQ 处理程序)。1 (我还从 (irq_handler_t) mymodule_irq_handler
中删除了类型转换运算符,因为如果 mymodule_irq_handler
未正确声明,那只会隐藏问题。)
作为使用 request_threaded_irq
的替代方法,可以使用 request_any_context_irq
函数,如下所示:
err = request_any_context_irq(
irq_ids[i],
mymodule_irq_handler,
IRQF_TRIGGER_HIGH,
"mymodule_irq_handler",
NULL
);
这将 select 硬 IRQ 处理程序或线程 IRQ 处理程序,自动处理那些不需要硬和线程部分的 IRQ 请求。
[如果你愿意,可以在这里停止阅读。]
―
1 如果指定了“硬 IRQ”处理程序函数 ,则无论如何都不会调用它,因为线程中断处理程序将是“嵌套”到父线程的中断处理程序中。如上面 __setup_irq
中的代码所示,指定的“硬 IRQ”处理程序将替换为 irq_nested_primary_handler
,但也不应调用该替换处理程序。它所做的只是记录一个警告:
/*
* Primary handler for nested threaded interrupts. Should never be
* called.
*/
static irqreturn_t irq_nested_primary_handler(int irq, void *dev_id)
{
WARN(1, "Primary handler called for nested irq %d\n", irq);
return IRQ_NONE;
}
在"drivers/pinctrl/pinctrl-mcp23s08.c":
中mcp23s08_probe_one
配置中断控制器为线程中断控制器
if (mcp->irq && mcp->irq_controller) {
struct gpio_irq_chip *girq = &mcp->chip.irq;
girq->chip = &mcp->irq_chip;
/* This will let us handle the parent IRQ in the driver */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_simple_irq;
girq->threaded = true;
}
(girq->threaded = true;
是相关行。)
每个子 IRQ 由“drivers/gpio/gpiolib.c”中的 gpiochip_irq_map
映射并标记为使用嵌套线程处理程序:
/* Chips that use nested thread handlers have them marked */
if (gc->irq.threaded)
irq_set_nested_thread(irq, 1);
父中断处理程序本身由 mcp23s08_irq_setup
在“drivers/pinctrl/pinctrl-mcp23s08.c”中设置为线程 IRQ 处理程序:
static int mcp23s08_irq_setup(struct mcp23s08 *mcp)
{
struct gpio_chip *chip = &mcp->chip;
int err;
unsigned long irqflags = IRQF_ONESHOT | IRQF_SHARED;
if (mcp->irq_active_high)
irqflags |= IRQF_TRIGGER_HIGH;
else
irqflags |= IRQF_TRIGGER_LOW;
err = devm_request_threaded_irq(chip->parent, mcp->irq, NULL,
mcp23s08_irq,
irqflags, dev_name(chip->parent), mcp);
if (err != 0) {
dev_err(chip->parent, "unable to request IRQ#%d: %d\n",
mcp->irq, err);
return err;
}
return 0;
}
“drivers/pinctrl/pinctrl-mcp23s08.c”中的父中断处理程序 mcp23s08_irq
调用 handle_nested_irq
来处理子中断处理程序:
child_irq = irq_find_mapping(mcp->chip.irq.domain, i);
handle_nested_irq(child_irq);
handle_nested_irq
in "kernel/irq/chip.c" 调用子 IRQ 的子嵌套中断处理程序:
for_each_action_of_desc(desc, action)
action_ret |= action->thread_fn(action->irq, action->dev_id);
我正在开发一个内核模块,需要使用 IO 扩展器 (MCP23008) 处理来自 4 个 GPIO 的中断。我正在使用内核 5.10.1-v7(buildroot 映像)开发 Raspberry Pi 3B+。当使用来自 40 针接头的“正常”GPIO 时,模块 (mymodule) 按预期工作。但是,当我尝试使用 pinctrl-mcp23s08 模块中的 GPIO 时,request_interrupt
函数失败并且 returns -22 (EINVAL).
我错过了什么?在我看来,从扩展的 GPIO 请求中断的一切都已准备就绪。其他类似的线程指向 request_interrupt
的第 5 个参数中与 NULL
一起使用的 IRQF_SHARED
标志,但我的情况并非如此。
这是相关的设备树片段:
/* for the io expander (mcp23s08) module */
&i2c1 { compatible = "brcm,bcm2835-i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status="okay";
mcp23008: mcp23008@20 {
compatible = "microchip,mcp23008";
reg=<0x20>; /* i2c slave address */
gpio-controller; /* mark the node as gpio-controller */
#gpio-cells=<2>; /* pin number and flags */
interrupt-parent = <&gpio>;
interrupt-controller; /* mark the node as interrupt-controller */
#interrupt-cells=<2>; /* pin number and flags */
interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
status="okay";
};
};
/* for mymodule */
/ {
compatible = "raspberrypi,3-model-b-plus", "brcm,bcm2837";
model = "Raspberry Pi 3 Model B+";
mymodule {
compatible = "myvendor,mymodule";
gpios = <&mcp23008 0 GPIO_ACTIVE_HIGH>,
<&mcp23008 1 GPIO_ACTIVE_HIGH>,
<&mcp23008 2 GPIO_ACTIVE_HIGH>,
<&mcp23008 3 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&mcp23008>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
<1 IRQ_TYPE_LEVEL_HIGH>,
<2 IRQ_TYPE_LEVEL_HIGH>,
<3 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
};
我知道 mcp23s08 模块在引导时正确插入,因为我可以从 sysfs 导出 GPIO 并读取它们的值。此外,在 mymodule 的 init 函数中,gpio_request_array
函数使用 mcp23s08 模块(gpio 496 到 499)给出的 GPIO 成功。最后,/proc/interrupts
中的这一行告诉我 mcp23s08 驱动程序(在 gpio 16 上)的中断已正确分配:
$ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
50: 5505 0 0 0 ARMCTRL-level 1 Edge 3f00b880.mailbox
51: 47 0 0 0 ARMCTRL-level 2 Edge VCHIQ doorbell
73: 0 0 0 0 ARMCTRL-level 48 Edge bcm2708_fb DMA
75: 12435 0 0 0 ARMCTRL-level 50 Edge DMA IRQ
77: 716 0 0 0 ARMCTRL-level 52 Edge DMA IRQ
78: 16 0 0 0 ARMCTRL-level 53 Edge DMA IRQ
81: 0 0 0 0 ARMCTRL-level 56 Edge DMA IRQ
86: 199 0 0 0 ARMCTRL-level 61 Edge ttyS0
89: 887966 0 0 0 ARMCTRL-level 64 Edge dwc_otg, dwc_otg_pcd, dwc_otg_hcd:usb1
110: 2 0 0 0 ARMCTRL-level 85 Edge 3f804000.i2c
111: 0 0 0 0 ARMCTRL-level 86 Edge 3f204000.spi
113: 119 0 0 0 ARMCTRL-level 88 Edge mmc0
119: 214231 0 0 0 ARMCTRL-level 94 Edge mmc1
194: 0 0 0 0 bcm2836-timer 0 Edge arch_timer
195: 91376 256895 932964 45993 bcm2836-timer 1 Edge arch_timer
198: 0 0 0 0 bcm2836-pmu 9 Edge arm-pmu
199: 1 0 0 0 pinctrl-bcm2835 4 Edge pps.-1
200: 0 0 0 0 pinctrl-bcm2835 16 Level 1-0020
205: 0 0 0 0 lan78xx-irqs 17 Edge usb-001:004:01
FIQ: usb_fiq
IPI0: 0 0 0 0 CPU wakeup interrupts
IPI1: 0 0 0 0 Timer broadcast interrupts
IPI2: 91 73 100 88 Rescheduling interrupts
IPI3: 4671 20748 12983 31858 Function call interrupts
IPI4: 0 0 0 0 CPU stop interrupts
IPI5: 0 0 0 0 IRQ work interrupts
IPI6: 0 0 0 0 completion interrupts
Err: 0
请求中断失败的相关初始化代码如下:
static int __init
mymodule_init(void)
{
int i;
int err;
/* [...] */
err = platform_driver_register(&gpio_driver);
if (err) {
printk(KERN_ERR "mymodule: failed to register platform driver\n");
goto fail_platform_driver_register;
}
err = gpio_request_array(gpios, COUNT_OF(gpios));
if (err != 0) {
printk(KERN_ERR "mymodule: failed to request gpio array: %d\n", err);
goto fail_gpio_request_array;
}
for (i = 0; i < COUNT_OF(irq_ids); ++i) {
err = platform_get_irq(dev.platform_device, i);
if (err > 0) {
irq_ids[i] = err;
printk(KERN_INFO "i=%d irq=%d\n", i, irq_ids[i]);
} else {
printk(KERN_ERR "mymodule: gpio_to_irq failed i = %d\n", i);
goto fail_platform_get_irq;
}
}
for (i = 0; i < COUNT_OF(irq_ids); ++i) {
err = request_irq(
irq_ids[i],
(irq_handler_t) mymodule_irq_handler,
IRQF_TRIGGER_HIGH,
"mymodule_irq_handler",
NULL
);
if (err) {
printk(KERN_ERR "mymodule: failed to request_irq at i=%d irq=%d: %d\n",
i, irq_ids[i], err);
goto fail_request_irq;
}
}
/* success, return here */
return 0;
/* ommited: error handling here */
}
最后,这里是 dmesg
输出:
[ 76.548689] i=0 irq=201
[ 76.554473] i=1 irq=202
[ 76.560189] i=2 irq=203
[ 76.566033] i=3 irq=204
[ 76.568525] mymodule: failed to request_irq at i=0 irq=201: -22
失败的至少一个原因是父中断控制器使用线程中断处理程序,因此子中断处理程序也需要线程化(这些将是“嵌套”线程中断处理程序)。
在“kernel/irq/manage.c”中的__setup_irq
中测试了不兼容性:
/*
* Check whether the interrupt nests into another interrupt
* thread.
*/
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
/*
* Replace the primary handler which was provided from
* the driver for non nested interrupt handling by the
* dummy function which warns when called.
*/
new->handler = irq_nested_primary_handler;
(new->thread_fn
在请求非线程 IRQ 处理程序时是 NULL
。上面的代码导致 request_irq()
的 return 值为 -EINVAL
. new->handler = irq_nested_primary_handler;
部分被描述为下面 request_threaded_irq
讨论的脚注。)
可以将原始代码中的 request_irq
调用更改为以下内容以使用线程化 IRQ 处理程序:
err = request_threaded_irq(
irq_ids[i],
NULL,
mymodule_irq_handler,
IRQF_TRIGGER_HIGH,
"mymodule_irq_handler",
NULL
);
主要区别是“硬 IRQ”处理程序已设置为 NULL
,因为它不用于纯线程 IRQ 处理程序(包括嵌套线程 IRQ 处理程序)。1 (我还从 (irq_handler_t) mymodule_irq_handler
中删除了类型转换运算符,因为如果 mymodule_irq_handler
未正确声明,那只会隐藏问题。)
作为使用 request_threaded_irq
的替代方法,可以使用 request_any_context_irq
函数,如下所示:
err = request_any_context_irq(
irq_ids[i],
mymodule_irq_handler,
IRQF_TRIGGER_HIGH,
"mymodule_irq_handler",
NULL
);
这将 select 硬 IRQ 处理程序或线程 IRQ 处理程序,自动处理那些不需要硬和线程部分的 IRQ 请求。
[如果你愿意,可以在这里停止阅读。]
―
1 如果指定了“硬 IRQ”处理程序函数 ,则无论如何都不会调用它,因为线程中断处理程序将是“嵌套”到父线程的中断处理程序中。如上面 __setup_irq
中的代码所示,指定的“硬 IRQ”处理程序将替换为 irq_nested_primary_handler
,但也不应调用该替换处理程序。它所做的只是记录一个警告:
/*
* Primary handler for nested threaded interrupts. Should never be
* called.
*/
static irqreturn_t irq_nested_primary_handler(int irq, void *dev_id)
{
WARN(1, "Primary handler called for nested irq %d\n", irq);
return IRQ_NONE;
}
在"drivers/pinctrl/pinctrl-mcp23s08.c":
中mcp23s08_probe_one
配置中断控制器为线程中断控制器
if (mcp->irq && mcp->irq_controller) {
struct gpio_irq_chip *girq = &mcp->chip.irq;
girq->chip = &mcp->irq_chip;
/* This will let us handle the parent IRQ in the driver */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_simple_irq;
girq->threaded = true;
}
(girq->threaded = true;
是相关行。)
每个子 IRQ 由“drivers/gpio/gpiolib.c”中的 gpiochip_irq_map
映射并标记为使用嵌套线程处理程序:
/* Chips that use nested thread handlers have them marked */
if (gc->irq.threaded)
irq_set_nested_thread(irq, 1);
父中断处理程序本身由 mcp23s08_irq_setup
在“drivers/pinctrl/pinctrl-mcp23s08.c”中设置为线程 IRQ 处理程序:
static int mcp23s08_irq_setup(struct mcp23s08 *mcp)
{
struct gpio_chip *chip = &mcp->chip;
int err;
unsigned long irqflags = IRQF_ONESHOT | IRQF_SHARED;
if (mcp->irq_active_high)
irqflags |= IRQF_TRIGGER_HIGH;
else
irqflags |= IRQF_TRIGGER_LOW;
err = devm_request_threaded_irq(chip->parent, mcp->irq, NULL,
mcp23s08_irq,
irqflags, dev_name(chip->parent), mcp);
if (err != 0) {
dev_err(chip->parent, "unable to request IRQ#%d: %d\n",
mcp->irq, err);
return err;
}
return 0;
}
“drivers/pinctrl/pinctrl-mcp23s08.c”中的父中断处理程序 mcp23s08_irq
调用 handle_nested_irq
来处理子中断处理程序:
child_irq = irq_find_mapping(mcp->chip.irq.domain, i);
handle_nested_irq(child_irq);
handle_nested_irq
in "kernel/irq/chip.c" 调用子 IRQ 的子嵌套中断处理程序:
for_each_action_of_desc(desc, action)
action_ret |= action->thread_fn(action->irq, action->dev_id);