1、问题描述
目前项目中使用华为鲲鹏920s处理器,操作系统为统信V20,测试场景为连续接收外部GPIO中断,四种中断类型,周期分别为1s,50ms,10ms和500us。在连续拷机24小时后发现接收不到GPIO中断,系统下看到中断计数如下,不再增加:

此时输入dmesg,发现出错情况如下:

2、问题分析
将dmesg完整日志提交给Gemini,结论如下:
根据您提供的日志和背景信息,Linux 系统运行一段时间后收不到中断,其核心原因并非中断周期太短,而是触发了内核的不良中断处理机制(Spurious IRQ handling),导致系统主动禁用了该中断。
以下是详细的原因分析:
1. 根本原因:中断被系统强制禁用 (Disabling IRQ #444)
日志显示:
Disabling IRQ #444。这意味着内核在第 444 号中断上检测到了异常行为。
内核检测逻辑 :Linux 内核会监控每个中断。如果在短时间内(通常是 100,000 次中断内)有超过 99% 的中断被认为是"未处理"的(即中断发生了,但没有任何注册的处理函数宣称处理了它),内核会认为该中断线发生了"中断风暴"或硬件故障 。
保护机制 :为了防止未处理的中断持续抢占 CPU 资源导致系统卡死(即
dump_stack中显示的从do_idle到gic_handle_irq的路径),内核会通过note_interrupt函数强制关闭该中断线 。2. 为何中断计数不再增加?
一旦内核打印出
Disabling IRQ #444,它就会在硬件或中断控制器(GIC)级别屏蔽该中断 。此后,硬件产生的电信号无法再触发内核的中断服务例程(ISR),因此中断计数停止增长,系统表现为"收不到中断"。3. 是否与 500us 的中断周期有关?
中断周期短(高频率)本身不是原因,但它加速了问题的触发。
处理不当 :如果您的驱动程序
gpio_irq_handler(来自gpio_shared_irq模块)在被调用时,没有正确识别硬件状态并返回IRQ_HANDLED,而是返回了IRQ_NONE,那么每一次 500us 的脉冲都会被记为一次"不良中断" 。
- 触发速度 :在 500us 的周期下,每秒产生 2000 次中断。如果处理函数一直返回
IRQ_NONE,只需约 50 秒左右就会达到内核的错误阈值,从而导致中断被禁用。4. 故障排查建议
要解决此问题,请重点检查驱动程序源代码:
返回值检查 :确保
gpio_irq_handler只有在确定处理了该中断时才返回IRQ_HANDLED。如果因为某种原因(如寄存器状态未清除)导致处理函数返回了IRQ_NONE,内核就会认为这个中断是误触发。寄存器清除 :确认在中断处理函数中是否正确清除了 GPIO 控制器的中断挂起位(Pending bit)。如果没有清除,电平触发的中断会立即再次触发,而此时处理函数可能认为没有新数据,从而返回
IRQ_NONE。共享中断冲突 :日志中提到了
gpio_shared_irq。如果是共享中断,请确认该线上是否有其他设备也在产生中断,而您的驱动程序错误地忽略了它们。临时恢复 :作为测试,您可以通过向
/proc/irq/444/enable重新写入来尝试开启中断,但如果根本逻辑不改,它很快会再次被禁用。总结: 您的系统由于
gpio_irq_handler频繁返回"未处理"状态,触发了内核的自我保护机制,导致 IRQ #444 被强制关闭。您需要修正驱动程序对中断状态的判断逻辑 。
gpio中断驱动代码如下,如果Gemini解释是正确的,那就是出现异常中断或者驱动中的中断函数没有及时返回IRQ_HANDLED,运行时间长了就会出现累积。
cpp
#include <linux/percpu.h>
#include <linux/module.h> // 包含Linux内核模块的头文件
#include <linux/interrupt.h> // 包含中断处理相关的函数和宏定义
#include <linux/io.h> // 包含I/O内存映射函数
#include <linux/delay.h> // 包含延时函数
#include <linux/acpi.h> // 包含ACPI(高级配置和电源接口)相关函数
// GPIO基地址宏定义
#define GPIO_A_BASE 0x9C100000
#define GPIO_B_BASE 0x94100000
#define GPIO_SIZE 0x1000 // GPIO内存映射区域的大小
#define GPIO_STATUS_OFFSET 0x0 // GPIO状态寄存器的偏移量
#define GPIO_IO_CONTROL_OFFSET 0x4 // GPIO I/O控制寄存器的偏移量
#define MASK1 0x3E00
#define MASK2 0x6000
// GPIO信息结构体定义
struct gpio_info {
void __iomem *base; // GPIO基地址
int irq; // GPIO中断号
int status_bit; // GPIO状态位
int virq; // 虚拟中断号
//#define GPIO_REG_READ(reg_base, offset) (*(volatile u32 *)((reg_base) + (offset)))
//#define GPIO_REG_WRITE(reg_base, offset, value) (*(volatile u32 *)((reg_base) + (offset)) = (value))
};
// GPIO数组,初始化GPIO信息
static struct gpio_info gpios[] = {
{ .base = (void __iomem *)GPIO_B_BASE, .irq = 126, .status_bit = 13 },
{ .base = (void __iomem *)GPIO_B_BASE, .irq = 127, .status_bit = 14 },
{ .base = (void __iomem *)GPIO_A_BASE, .irq = 74, .status_bit = 9 },
{ .base = (void __iomem *)GPIO_A_BASE, .irq = 75, .status_bit = 10 },
{ .base = (void __iomem *)GPIO_A_BASE, .irq = 76, .status_bit = 11 },
{ .base = (void __iomem *)GPIO_A_BASE, .irq = 77, .status_bit = 12 },
{ .base = (void __iomem *)GPIO_A_BASE, .irq = 78, .status_bit = 13 },
};
// GPIO中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
struct gpio_info *gpio = dev_id;
u32 status;
// 检查中断号是否有效
if (irq > ARRAY_SIZE(gpios) || irq < 0 )
{
return IRQ_NONE;
}
// 读取GPIO状态寄存器
status = readl(gpio->base + GPIO_STATUS_OFFSET);
// 检查状态位是否被设置
if (status & (1 << gpio->status_bit))
{
printk(KERN_INFO "GPIO interrupt %d\n", gpio->irq); // 打印中断信息
writel(~(1 << gpio->status_bit), gpio->base + GPIO_STATUS_OFFSET); // 清除信号位
udelay(1); // 延时1微秒
writel((1 << gpio->status_bit), gpio->base + GPIO_STATUS_OFFSET); // 重置信号位
}
return IRQ_HANDLED; // 返回中断已处理
}
// GPIO初始化函数
static int __init gpio_init(void)
{
void __iomem *base;
pr_info("[Info] gpio_init.\n");
// 映射GPIO内存区域
base = ioremap(GPIO_B_BASE, GPIO_SIZE);
if (!base) {
printk(KERN_ERR "Failed to remap GPIO baseB\n"); // 映射失败
return -ENOMEM;
}
gpios[0].base = base;
gpios[1].base = base;
base = ioremap(GPIO_A_BASE, GPIO_SIZE);
if (!base) {
printk(KERN_ERR "Failed to remap GPIO baseA\n"); // 映射失败
iounmap(gpios[2].base);
return -ENOMEM;
}
gpios[2].base = base;
gpios[3].base = base;
gpios[4].base = base;
gpios[5].base = base;
gpios[6].base = base;
return 0;
}
int __init gpio_irq_init(void)
{
//u32 reg_val;
unsigned i;
volatile u32* reg1, *reg2, *reg3, *reg4;
// 映射GPIO内存区域
pr_info("[Info] hisi_irq_init.\n");
if (gpio_init() != 0) {
return -1;
}
pr_info("[Info] gpio extint test init.\n");
// 读取地址为 GPIO_A_BASE 的寄存器值并设置位
reg1 = (void*)(gpios[2].base + GPIO_IO_CONTROL_OFFSET);
reg2 = (void*)(gpios[2].base + GPIO_STATUS_OFFSET);
*reg1 |= MASK1;
*reg2 |= MASK1;
// 读取地址为 GPIO_B_BASE 的寄存器值并设置位
reg3 = (void*)(gpios[0].base + GPIO_IO_CONTROL_OFFSET);
reg4 = (void*)(gpios[0].base + GPIO_STATUS_OFFSET);
*reg3 |= MASK2;
*reg4 |= MASK2;
// 遍历GPIO数组,初始化每个GPIO
for (i = 0; i < ARRAY_SIZE(gpios); i++) {
int irq;
// 注册GPIO中断
irq = acpi_register_gsi(NULL, gpios[i].irq, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_LOW);
//irq = acpi_register_gsi(NULL, gpios[i].irq, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_LOW);
if (irq < 0) {
printk(KERN_ERR "Failed to register GSI\n"); // 注册失败
//continue;
return irq;//continue?
}
gpios[i].virq = irq;
// 请求GPIO中断
if (request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING, "gpio_extint", &gpios[i])) {
printk(KERN_ERR "Failed to request IRQ %i\n", gpios[i].irq); // 请求失败
//continue;
//continue;
return -EIO;//continue?
}
pr_info("[Info] gpio irq %i init.\n", irq);
}
return 0; // 初始化成功
}
// GPIO退出函数
static void __exit gpio_exit(void)
{
int i;
// 遍历GPIO数组,释放资源
for (i = 0; i < ARRAY_SIZE(gpios); i++) {
acpi_unregister_gsi(gpios[i].virq); // 注销中断
free_irq(gpios[i].virq, &gpios[i]); // 释放中断
}
iounmap(gpios[0].base); // 取消内存映射
iounmap(gpios[2].base); // 取消内存映射
}
module_init(gpio_irq_init); // 注册模块初始化函数
module_exit(gpio_exit); // 注册模块退出函数
MODULE_LICENSE("GPL v2"); // 指定许可证
MODULE_AUTHOR("Wuzhou Co.,Ltd."); // 指定作者
MODULE_DESCRIPTION("GPIO extint driver for Linux"); // 指定模块描述