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_idlegic_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. 故障排查建议

要解决此问题,请重点检查驱动程序源代码:

  1. 返回值检查:确保 gpio_irq_handler 只有在确定处理了该中断时才返回 IRQ_HANDLED。如果因为某种原因(如寄存器状态未清除)导致处理函数返回了 IRQ_NONE,内核就会认为这个中断是误触发。

  2. 寄存器清除:确认在中断处理函数中是否正确清除了 GPIO 控制器的中断挂起位(Pending bit)。如果没有清除,电平触发的中断会立即再次触发,而此时处理函数可能认为没有新数据,从而返回 IRQ_NONE

  3. 共享中断冲突:日志中提到了 gpio_shared_irq。如果是共享中断,请确认该线上是否有其他设备也在产生中断,而您的驱动程序错误地忽略了它们。

  4. 临时恢复:作为测试,您可以通过向 /proc/irq/444/enable 重新写入来尝试开启中断,但如果根本逻辑不改,它很快会再次被禁用。

总结: 您的系统由于 gpio_irq_handler 频繁返回“未处理”状态,触发了内核的自我保护机制,导致 IRQ #444 被强制关闭。您需要修正驱动程序对中断状态的判断逻辑 。

gpio中断驱动代码如下,如果Gemini解释是正确的,那就是出现异常中断或者驱动中的中断函数没有及时返回IRQ_HANDLED,运行时间长了就会出现累积。

#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"); // 指定模块描述

修改后的驱动代码如下:

#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(高级配置和电源接口)相关函数
#include<linux/ktime.h> 				 //


#include <linux/types.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/rcupdate.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/kobject.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/hrtimer.h>
#include <linux/cpu.h>
#include <linux/irq.h>
#include <linux/bitops.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/signal.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>
#include <linux/poll.h>
#include <linux/pid.h>
#include <linux/uaccess.h>
#include <linux/sched/signal.h>
#include <linux/pid_namespace.h>
#include <linux/interrupt.h>

// GPIO基地址宏定义
#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

#define GPIO_INT_B_BASE 0x94190000
#define OFFSETS_ARRAY_SIZE 2
#define OFFSET_D4 0xD4
#define OFFSET_DC 0xDC

#define FUNC_INT 0x4

#define LPC_SIGNAL1  40
#define LPC_SIGNAL2  41
#define LPC_SIGNAL3  42
#define LPC_SIGNAL4  43
#define LPC_SIGNAL5  44
#define LPC_SIGNAL6  45
#define LPC_SIGNAL7  46
#define LPC_SIGNAL8  47

unsigned long offsets[OFFSETS_ARRAY_SIZE] = {OFFSET_D4,OFFSET_DC};
static void __iomem *gpio_int_base_b;
static void __iomem *device_regs;


static int g_pid = 0;
static int major = 0;
static struct class *buttondev_class = NULL;
static struct device *buttons_device = NULL;

// GPIO信息结构体定义
struct gpio_info {
	void __iomem *base;          // GPIO基地址
	int irq;                     // GPIO中断号
	int status_bit;              // GPIO状态位
	int virq;                    // 虚拟中断号
};

// GPIO数组,初始化GPIO信息
static struct gpio_info gpios[] = {
	{ .base = (void __iomem *)GPIO_B_BASE, .irq = 126, .status_bit = 13 },//CPU GPIO13 <--> CPLD GPIO7
};

static unsigned long bit_cnt[8];

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
    u8 status;
    struct task_struct *task = NULL;

    /* 读中断状态 */
    status = readb(device_regs);

    /* 找用户进程(失败也不影响 ACK) */
    if (g_pid)
        task = pid_task(find_vpid(g_pid), PIDTYPE_PID);

    if (task && status) {
        if (status & 0x01) { send_sig_info(LPC_SIGNAL1, SEND_SIG_FORCED, task); bit_cnt[0]++; }
        if (status & 0x02) { send_sig_info(LPC_SIGNAL2, SEND_SIG_FORCED, task); bit_cnt[1]++; }
        if (status & 0x04) { send_sig_info(LPC_SIGNAL3, SEND_SIG_FORCED, task); bit_cnt[2]++; }
        if (status & 0x08) { send_sig_info(LPC_SIGNAL4, SEND_SIG_FORCED, task); bit_cnt[3]++; }
        if (status & 0x10) { send_sig_info(LPC_SIGNAL5, SEND_SIG_FORCED, task); bit_cnt[4]++; }
        if (status & 0x20) { send_sig_info(LPC_SIGNAL6, SEND_SIG_FORCED, task); bit_cnt[5]++; }
        if (status & 0x40) { send_sig_info(LPC_SIGNAL7, SEND_SIG_FORCED, task); bit_cnt[6]++; }
        if (status & 0x80) { send_sig_info(LPC_SIGNAL8, SEND_SIG_FORCED, task); bit_cnt[7]++; }
    }



    return IRQ_HANDLED;
}

static long cpld_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    if (cmd == 0x6B09)
        return copy_from_user(&g_pid, (void __user *)arg, sizeof(int));

    if (cmd >= 0x6B01 && cmd <= 0x6B08) {
        unsigned long v = bit_cnt[cmd - 0x6B01];
        return copy_to_user((void __user *)arg, &v, sizeof(v));
    }
    return -EINVAL;
}

static int cpld_open(struct inode * inode, struct file * filp)
{
	return 0;
}
 
static int cpld_release(struct inode *inode, struct file *file)
{
	return 0;
}


/* File operations struct for character device */
static const struct file_operations buttons_dev_fops = {
	.owner = THIS_MODULE,
	.open = cpld_open,
	.release = cpld_release,
	.unlocked_ioctl = cpld_ioctl,
};

// 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;
	return 0;
}

//功能复用为中断
static int __init gpio_reuse(void)
{
	unsigned i;
	void __iomem *base_b, *base_a, *gpio_offset;
	pr_info("[Info] gpio_reuse.\n");

	// 映射GPIO内存区域
	base_b = ioremap(GPIO_INT_B_BASE, GPIO_SIZE);
	if (!base_b) {
		printk(KERN_ERR "Failed to remap GPIO inteerupt baseB\n"); // 映射失败
		return -ENOMEM;
	}
	gpio_int_base_b = base_b;

	for(i = 0; i < OFFSETS_ARRAY_SIZE; i++){

		gpio_offset = base_b + offsets[i];
		writel(FUNC_INT,gpio_offset);
	}

	return 0;
}


int __init gpio_irq_init(void)
{
	unsigned i;

	// 映射GPIO内存区域
	pr_info("[Info] hisi_irq_init.\n");

	if (gpio_reuse() != 0) {
		return -1;
	}

	if (gpio_init() != 0) {
		return -1;
	}

    	device_regs = ioremap(0x800000e8,0x1);
    	if (!device_regs) {
        	pr_err("Failed to map device registers\n");
        	return -1;
    	}
	if ((major = register_chrdev(0, "buttons_dev", &buttons_dev_fops)) < 0)
	{
	   printk("failed to create dev entry.\n");
	   return (0);
	}

	buttondev_class = class_create(THIS_MODULE, "buttondev");
	if (IS_ERR(buttondev_class)) {

		printk("Could not create device class");
		unregister_chrdev(200, "buttondev");
		return EFAULT;
	}
	buttons_device = device_create(buttondev_class, NULL, MKDEV(major, 0), NULL, "buttons");
	printk("device to create ......%d\n",IS_ERR(buttons_device));


	// 遍历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);
		if (irq < 0) {
			printk(KERN_ERR "Failed to register GSI\n"); // 注册失败
			return irq;
		}
		gpios[i].virq = irq;

		// 请求GPIO中断
		if (request_irq(irq, gpio_irq_handler, IRQF_TRIGGER_RISING|IRQF_SHARED, "gpio_extint", &gpios[i])) {
			printk(KERN_ERR "Failed to request IRQ %i\n", gpios[i].irq); // 请求失败
			return -EIO;
		}
		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(gpio_int_base_b); // 取消内存映射
				  //
	if (device_regs){
        	iounmap(device_regs);
	}

	if (buttons_device)
		device_unregister(buttons_device);
	
	if (buttondev_class)	
		class_destroy(buttondev_class);	
	
	if (major)
		unregister_chrdev(major, "buttons_dev");
}

module_init(gpio_irq_init); // 注册模块初始化函数
module_exit(gpio_exit); // 注册模块退出函数
MODULE_VERSION("2.0");
MODULE_LICENSE("GPL v2"); // 指定许可证
MODULE_AUTHOR("Wuzhou Co.,Ltd."); // 指定作者
MODULE_DESCRIPTION("GPIO extint driver for Linux"); // 指定模块描述

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐