Allwinner A40i GPIO中断异常重复触发问题排查请求
问题描述
我使用Allwinner A40i处理器(内核版本3.10),外接STM32每8ms产生一次IO上升沿中断信号,A40i通过GPIO直接采集该信号。示波器抓取波形正常,但偶尔会出现8ms内多次触发中断的异常情况。
相关代码与日志
设备树片段
spiio{ compatible = "dobot, spiio"; spiio_gpio = <&pio PI 10 6 default default default>; /*PI 10, 0->input/6->EINT, 1->level, 2->down pull, 1->data*/ clocks = <&clk_pio>; };
spiio驱动代码片段
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/sys_config.h> #include <linux/major.h> #include <linux/seq_file.h> #include <linux/device.h> #include <linux/sched.h> #include <linux/interrupt.h> #include <linux/poll.h> #include <linux/wait.h> #include <linux/delay.h> #include <linux/pinctrl/pinconf-sunxi.h> static int MajorDevNum = 0; static int SpiioNum = 0; static int IrqNum = 0; static struct class *SpiioClass; static int IrqOccurred = 0; static wait_queue_head_t WaitQueue; static spinlock_t RWLock; static void SetIrqOccurred(int newIrqOccurred) { spin_lock_irq(&RWLock); IrqOccurred = newIrqOccurred; spin_unlock_irq(&RWLock); } static irqreturn_t spiio_irq_handler(int irq, void *dev_id) { // pr_debug("spiio irq ocurred on IRQ %d\n", irq); if (irq == IrqNum) { SetIrqOccurred(1); wake_up_interruptible(&WaitQueue); } return IRQ_HANDLED; } static int spiio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { // pr_info("start read\n"); #if 1 int ret = 0; // pr_info("this is read. IrqOccurred = %d\n", IrqOccurred); // ret = wait_event_interruptible(WaitQueue, IrqOccurred); ret = wait_event_interruptible_timeout(WaitQueue, IrqOccurred, msecs_to_jiffies(10)); pr_info("wait end-timeout, ret = %d, IrqOccurred = %d\n", ret, IrqOccurred); if (IrqOccurred) SetIrqOccurred(0); if (ret > 0) { return 1; } else if (ret == 0){ return 0; //timeout } else { pr_err("wait_event_intrruptible_timeout return %d\n", ret); return -1; } #endif } /* write(fd, &val, 1); */ static int spiio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { return -1; } static int spiio_drv_open(struct inode *node, struct file *file) { int err = 0; pr_info("this is open\n"); #if 0 spin_lock_init(&RWLock); init_waitqueue_head(&WaitQueue); IrqNum = gpio_to_irq(SpiioNum); if (IS_ERR(IrqNum)) { pr_err("irqNum is err\n"); free_irq(IrqNum, NULL); return -1; } //if ((err = request_irq(IrqNum, spiio_irq_handler, IRQF_SHARED | IRQF_TRIGGER_RISING, "spiio_irq", &pdev->dev)) < 0) if ((err = request_irq(IrqNum, spiio_irq_handler, IRQF_TRIGGER_RISING, "spiio_irq", NULL)) < 0) { pr_err("requst_irq failed, err = %d\n", err); free_irq(IrqNum, NULL); return -1; } pr_info("add irq %d on pin %d success.\n", IrqNum, SpiioNum); #endif return 0; } static int spiio_drv_close(struct inode *node, struct file *file) { #if 0 free_irq(IrqNum, NULL); #endif return 0; } static long spiio_drv_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return 0; } //static unsigned int spiio_drv_poll(struct file *file, struct poll_table_struct *wait) static unsigned int spiio_drv_poll(struct file *file, struct poll_table *wait) { #if 0 // pr_info("this is poll.\n"); int ret = 0; poll_wait(file, &WaitQueue, wait); if (IrqOccurred) { mutex_lock(&RWLock); IrqOccurred = 0; //need lock mutex_unlock(&RWLock); // pr_debug(" an irq occurred. return %d\n", POLLIN); return POLLIN; } else { // pr_debug("no irq occurred, IrqOccurred = %d\n", IrqOccurred); return 0; } #endif #if 0 if (irq_set_affinity_hint(irqNum, cpumask_of(1)) < 0) { pr_info("failed to set affinity\n"); } #endif return 0; } static struct file_operations spiio_drv = { .owner = THIS_MODULE, .open = spiio_drv_open, .read = spiio_drv_read, .write = spiio_drv_write, .release = spiio_drv_close, .unlocked_ioctl = spiio_drv_ioctl, .poll = spiio_drv_poll, }; int spiio_probe(struct platform_device *pdev) { int err = 0; struct gpio_config pinConfig; char pinName[32] = {0}; unsigned int config = 0; struct device_node *nd = pdev->dev.of_node; if (!nd) { pr_err("no found device tree node\n"); return -1; } if ((of_gpio_named_count(nd, "spiio_gpio") <= 0)) { pr_err("no found spiio_gpio node\n"); return -1; } SpiioNum = of_get_named_gpio_flags(nd, "spiio_gpio", 0, (enum of_gpio_flags *)&pinConfig); if (!gpio_is_valid(SpiioNum)) { pr_err("gpio isn't valid\n"); return -1; } if (gpio_request(SpiioNum, pdev->name) < 0) { pr_err("gpio request failed, GPIO:%d\n", SpiioNum); gpio_free(SpiioNum); return -1; } sunxi_gpio_to_name(pinConfig.gpio, pinName); pr_info("pinName = %s\n", pinName); pr_info("index = %d\n", pinConfig.gpio); pr_info("mul_sel = %d\n", pinConfig.mul_sel); pr_info("pull = %d\n", pinConfig.pull); pr_info("drv_level = %d\n", pinConfig.drv_level); pr_info("data = %d\n", pinConfig.data); config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_FUNC, pinConfig.mul_sel); pin_config_set(SUNXI_PINCTRL, pinName, config); if (pinConfig.pull != GPIO_PULL_DEFAULT) { config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, pinConfig.pull); pin_config_set(SUNXI_PINCTRL, pinName, config); pr_info("set pull:%d\n", pinConfig.pull); } if (pinConfig.drv_level != GPIO_DRVLVL_DEFAULT) { config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV, pinConfig.drv_level); pin_config_set(SUNXI_PINCTRL, pinName, config); pr_info("set drv_level:%d\n", pinConfig.drv_level); } if (pinConfig.data != GPIO_DATA_DEFAULT) { config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT, pinConfig.data); pin_config_set(SUNXI_PINCTRL, pinName, config); pr_info("set data:%d\n", pinConfig.data); } if (gpio_direction_input(SpiioNum) < 0) { pr_err("gpio_direction_input failed %d\n", SpiioNum); gpio_free(SpiioNum); return -1; } MajorDevNum = register_chrdev(0, "spiio", &spiio_drv); /* /dev/spiio */ if (MajorDevNum < 0) { pr_err("failed to get major device number\n"); gpio_free(SpiioNum); return -1; } SpiioClass = class_create(THIS_MODULE, "spiio_class"); if (IS_ERR(SpiioClass)) { pr_err("spiio class has an error\n"); unregister_chrdev(MajorDevNum, "spiio"); gpio_free(SpiioNum); return PTR_ERR(SpiioClass); } device_create(SpiioClass, NULL, MKDEV(MajorDevNum, 0), NULL, "spiio%d", 0); /* /dev/100ask_spiio0 */ pr_info("gpio is ok, spiio num is %d\n", SpiioNum); #if 1 spin_lock_init(&RWLock); init_waitqueue_head(&WaitQueue); IrqNum = gpio_to_irq(SpiioNum); if (IS_ERR(IrqNum)) { pr_err("irqNum is err\n"); free_irq(IrqNum, NULL); gpio_free(SpiioNum); unregister_chrdev(MajorDevNum, "spiio"); return -1; } //if ((err = request_irq(IrqNum, spiio_irq_handler, IRQF_SHARED | IRQF_TRIGGER_RISING, "spiio_irq", &pdev->dev)) < 0) if ((err = request_irq(IrqNum, spiio_irq_handler, IRQF_TRIGGER_RISING, "spiio_irq", NULL)) < 0) { pr_err("requst_irq failed, err = %d\n", err); free_irq(IrqNum, NULL); gpio_free(SpiioNum); unregister_chrdev(MajorDevNum, "spiio"); return -1; } pr_info("add irq %d on pin %d success.\n", IrqNum, SpiioNum); #endif #if 0 if (irq_set_affinity_hint(irqNum, cpumask_of(1)) < 0) { pr_info("failed to set affinity\n"); } #endif return 0; } int spiio_remove(struct platform_device *pdev) { #if 1 free_irq(IrqNum, NULL); #endif device_destroy(SpiioClass, MKDEV(MajorDevNum, 0)); class_destroy(SpiioClass); unregister_chrdev(MajorDevNum, "spiio"); gpio_free(SpiioNum); pr_info("gpio remove ...\n"); return 0; } struct of_device_id ids[] = { {.compatible = "dobot, spiio"}, {}, }; static struct platform_driver chip_demo_gpio_driver = { .probe = spiio_probe, .remove = spiio_remove, /* .shutdown = spiio_remove,*/ .driver = { .name = "spiio", .of_match_table = ids, }, }; static int __init spiio_init(void) { int err; err = platform_driver_register(&chip_demo_gpio_driver); return err; } static void __exit spiio_exit(void) { platform_driver_unregister(&chip_demo_gpio_driver); } module_init(spiio_init); module_exit(spiio_exit); MODULE_LICENSE("GPL");
dmesg日志片段
[ 6568.744425] this is open [ 6568.745230] wait end-timeout, ret = 10, IrqOccurred = 1 [ 6568.746052] wait end-timeout, ret = 9, IrqOccurred = 1
异常原因分析
- 中断标志位的非原子竞争:
在spiio_drv_read函数中,先判断IrqOccurred是否为1,再调用SetIrqOccurred(0)清除标志,这两步操作不是原子的。如果在这两步之间再次触发中断,IrqOccurred会被重新设为1,导致下一次读取直接返回,造成“8ms内多次触发”的假象。从日志看,两次wait end-timeout间隔仅0.8ms,完全符合这种竞争场景。 - 中断处理未屏蔽同引脚中断:
当前中断处理函数返回IRQ_HANDLED但未在处理期间屏蔽该引脚的中断,若信号存在极短毛刺(示波器可能因采样率问题未捕捉到),会导致硬件连续触发中断。 - 等待队列唤醒与标志位清除时序错误:
wait_event_interruptible_timeout返回后立即清除IrqOccurred,但此时如果中断再次触发,标志位会被覆盖,导致读取逻辑误判为新的中断事件。
修复方案
- 原子化处理标志位读取与清除:
修改spiio_drv_read中的逻辑,将标志位的读取和清除合并为原子操作,避免竞争:static int spiio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { int ret = 0; int occurred = 0; ret = wait_event_interruptible_timeout(WaitQueue, IrqOccurred, msecs_to_jiffies(10)); // 原子读取并清除标志位 spin_lock_irq(&RWLock); occurred = IrqOccurred; if (occurred) IrqOccurred = 0; spin_unlock_irq(&RWLock); pr_info("wait end-timeout, ret = %d, IrqOccurred = %d\n", ret, occurred); if (ret > 0 && occurred) { return 1; } else if (ret == 0){ return 0; //timeout } else { pr_err("wait_event_intrruptible_timeout return %d\n", ret); return -1; } } - 启用中断消抖或屏蔽同引脚中断:
申请中断时添加IRQF_DISABLED标志,确保中断处理期间屏蔽该引脚的中断,避免重复触发:
若硬件支持,也可通过设备树配置GPIO的消抖功能,过滤毛刺信号。request_irq(IrqNum, spiio_irq_handler, IRQF_TRIGGER_RISING | IRQF_DISABLED, "spiio_irq", NULL); - 优化中断处理逻辑:
在中断处理函数中,添加GPIO电平校验,确认确实是有效上升沿后再设置标志位,避免误触发:static irqreturn_t spiio_irq_handler(int irq, void *dev_id) { if (irq == IrqNum && gpio_get_value(SpiioNum) == 1) { // 校验当前电平为高 Set</think_never_used_51bce0c785ca2f68081bfa7d91973934>
相关产品推荐
相关产品推荐

