ARM&Linux基础
+ -

中断处理

2025-11-21 16 0

Linux中断分为上半部和下半部。

  • 上半部为中断函数。
  • 下半部可以有软中断、task_let,workt_struct,request_threaded_irq等。

中断

嵌入式Linux内核通过读取设备树中的中断属性信息来配置中断。
中断配置信息位于中断控制器模块节点中。

  • interrupt-controller,表示当前节点为中断控制器。
  • interrupt-cells,指定中断源的信息 cells 个数,即interrupts多少个数为一组。
  • interrupts,指定中断号,触发方式等。
  • interrupt-parent,指定父中断,也就是中断控制器。

实际在开发中,中断控制器不用写,官方已经写好了。我们只写使用在某个模块下使用那个芯片,并配置中断属性即可。
如设备树。

中断函数

编写驱动时需要使用中断号。当中断信息已写入设备树后,可通过 irq_of_parse_and_map 函数从 interrupts 属性中提取对应的中断号。该函数的原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

参数说明:

  • dev:设备节点
  • index:索引号。由于 interrupts 属性可能包含多条中断信息,需通过该参数指定要获取的信息

返回值: 对应的中断号

若使用 GPIO,也可通过 gpio_to_irq 函数获取 GPIO 对应的中断号,其函数原型为:

int gpio_to_irq(unsigned int gpio)

参数说明:

  • gpio:要获取中断号的 GPIO 编号

返回值: 该 GPIO 对应的中断号

示例

在iomuxc节点下的imx6ul-evk子节点下创建pinctrl_key子节点,用于配置IO管脚的复用信息

pinctrl_key: keygrp {
     fsl,pins = <
     MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
     >;
};

设备树下

key{
    pinctrl-0 = <&pinctrl_key>;

    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;  //giop1组,Pin18,低电平
    interrupt-parent = <&gpio1>;
    interrupts =<18 8>; //18表示管脚引号,8表示低电平触发IRQ_TYPE_LEVEL_LOW 
    status = "okay";
}

中断属性信息

enum {
    IRQ_TYPE_NONE = 0x00000000,
    IRQ_TYPE_EDGE_RISING = 0x00000001,
    IRQ_TYPE_EDGE_FALLING = 0x00000002,
    IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING |IRQ_TYPE_EDGE_RISING),
    IRQ_TYPE_LEVEL_HIGH = 0x00000004,
    IRQ_TYPE_LEVEL_LOW = 0x00000008,
    IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW |IRQ_TYPE_LEVEL_HIGH),
};

在代码中,使用of函数获取中断号等。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名        : imx6uirq.c
作者          : 左忠凯
版本           : V1.0
描述           : Linux中断驱动实验
其他           : 无
论坛            : www.openedv.com
日志           : 初版V1.0 2019/7/26 左忠凯创建
***************************************************************/
#define IMX6UIRQ_CNT        1            /* 设备号个数     */
#define IMX6UIRQ_NAME        "imx6uirq"    /* 名字         */
#define KEY0VALUE            0X01        /* KEY0按键值     */
#define INVAKEY                0XFF        /* 无效的按键值 */
#define KEY_NUM                1            /* 按键数量     */

/* 中断IO描述结构体 */
struct irq_keydesc {
    int gpio;                                /* gpio */
    int irqnum;                                /* 中断号     */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                            /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;        /* cdev     */
    struct class *class;    /* 类         */
    struct device *device;    /* 设备      */
    int major;                /* 主设备号      */
    int minor;                /* 次设备号   */
    struct device_node    *nd; /* 设备节点 */
    atomic_t keyvalue;        /* 有效的按键键值 */
    atomic_t releasekey;    /* 标记是否完成一次完成的按键,包括按下和释放 */
    struct timer_list timer;/* 定义一个定时器*/
    struct irq_keydesc irqkeydesc[KEY_NUM];    /* 按键描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */
};

struct imx6uirq_dev imx6uirq;    /* irq设备 */

/* @description        : 中断服务函数,开启定时器,延时10ms,
 *                        定时器用于按键消抖。
 * @param - irq     : 中断号 
 * @param - dev_id    : 设备结构。
 * @return             : 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));    /* 10ms定时 */
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description    : 定时器服务函数,用于按键消抖,定时器到了以后
 *                  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg    : 设备结构变量
 * @return         : 无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);     /* 读取IO值 */
    if(value == 0){                         /* 按下按键 */
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else{                                     /* 按键松开 */
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1);    /* 标记松开按键,即完成一次完整的按键过程 */            
    }    
}

/*
 * @description    : 按键IO初始化
 * @param         : 无
 * @return         : 无
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    imx6uirq.nd = of_find_node_by_path("/key");
    if (imx6uirq.nd== NULL){
        printk("key node not find!\r\n");
        return -EINVAL;
    } 

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
        if (imx6uirq.irqkeydesc[i].gpio < 0) {
            printk("can't get key%d\r\n", i);
        }
    }

    /* 初始化key所使用的IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));    /* 缓冲区清零 */
        sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);        /* 组合名字 */
        gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);    
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\n",i, imx6uirq.irqkeydesc[i].gpio, 
                                         imx6uirq.irqkeydesc[i].irqnum);
    }
    /* 申请中断 */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;

    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
        if(ret < 0){
            printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 创建定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;
    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;    /* 设置私有数据 */
    return 0;
}

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) { /* 有按键按下 */    
        if (keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
    } else {
        goto data_error;
    }
    return 0;

data_error:
    return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*
 * @description    : 驱动入口函数
 * @param         : 无
 * @return         : 无
 */
static int __init imx6uirq_init(void)
{
    /* 1、构建设备号 */
    if (imx6uirq.major) {
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2、注册字符设备 */
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
    cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

    /* 3、创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class)) {
        return PTR_ERR(imx6uirq.class);
    }

    /* 4、创建设备 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device)) {
        return PTR_ERR(imx6uirq.device);
    }

    /* 5、初始化按键 */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);
    keyio_init();
    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;
    /* 删除定时器 */
    del_timer_sync(&imx6uirq.timer);    /* 删除定时器 */

    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

软中断

task_let

tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和tasklet之间,建议使用tasklet。

/* 定义 taselet */
struct tasklet_struct testtasklet;

/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
    /* tasklet 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 tasklet */
    tasklet_schedule(&testtasklet);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 tasklet */
    tasklet_init(&testtasklet, testtasklet_func, data);

    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

work_struct

工作队列将要推后的工作交给一个内核线程去执行,因此工作队列允许睡眠或重新调度。

/* 定义工作(work) */
struct work_struct testwork;

void testwork_func_t(struct work_struct *work);
{
    /* work 具体处理内容 */
}

irqreturn_t test_handler(int irq, void *dev_id)
{
    ......
    /* 调度 work */
    schedule_work(&testwork);
    ......
}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{
    ......
    /* 初始化 work */
    INIT_WORK(&testwork, testwork_func_t);

    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    ......
}

0 篇笔记 写笔记

PCIE的资源-IO资源、内存资源和中断资源
在PCIe系统中,对于每一个PCIe设备,都具有三种类型的资源。具有了资源,PCIe设备才具有了被访问、被使用的基本能力。这三种资源分别是:IO资源仅适用于X86架构中内存资源即设备具备哪些可以提供给外部或内部使用的内存。中断资源中断资资可以为INT中断,MSI中断和MSI-X中断。如我们在......
PCI总线的中断和错误处理
PCI总线使用INTA#、INTB#、INTC#和INTD#信号向处理器发出中断请求。这些中断请求信号为低电平有效,并与处理器的中断控制器连接。在PCI体系结构中,这些中断信号属于边带信号(Sideband Signals),PCI总线规范并没有明确规定在一个处理器系统中如何使用这些信号,因为这些信......
PCIe 两种中断传递方式
为了能够让一些优先级高的事务得到优先处理,PCI总线支持外设中断用以提高总线性能。PCIe总线继承了PCI总线的所有中断特性(包括INTx和MSI/MSI-X),以兼容早期的一些PCI应用层软件。本次连载的文章只是简单的介绍PCIe中断的一些基本概念和特性,如需深入了解PCI/PCIe总线的中断内容......
PCIe 中断机制介绍(INTx)
一个简单的PCI总线INTx中断实现流程,如下图所示。首先,PCI设备通过INTx边带信号产生中断请求,经过中断控制器(Interrupt Controller,PIC)后,转换为INTR信号,并直接发送至CPU;CPU收到INTR信号置位后,意识到了中断请求的发生,但是此时并不知道是什么中断请......
PCIe 中断机制介绍(MSI)
前面的文章中介绍过,MSI本质上是一种Memory Write,和PCIe总线中的Message概念半毛钱关系都没有。并且,MSI的Data Payload也是固定的,始终为1DW。由于MSI也是从PCI总线继承而来的,因此MSI相关的寄存器也存在于配置空间中的PCI兼容部分(前256个字节)。如......
PCIe 中断机制介绍(MSI-X)
PCI总线自3.0版本开始支持MSI-X机制,对MSI做出了一些升级和改进,以克服MSI机制的三个主要的缺陷:随着系统的发展,对于特定的大型应用,32个中断向量不够用了(参考前一篇文章);只有一个目标地址使得多核CPU情况下的,静态中断分配变得困难。如果能够使每个向量对应不同的唯一的地址,便会灵......
PCIE-MSI中断LINUX驱动-imt_driv.h
#ifndef __IMT_TEST__H__ #define __IMT_TEST__H__#include #include #include "linux/pci.h"#incl......
PCIE-MSI中断LINUX驱动-imt_driv.c
#include #include #include #include #include
all: make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules#make -C $(KDIR) M=$(PWD) modulesclean: make -C /lib/modules/$(shell una......
#include #include #include ///<支持的ioctrl 命令#define GDMA_TEST_IOC_SUBMIT _IOW('p', ......
#include #include #include #include #include "gdma_app.h"#include <......
Linux中断分为上半部和下半部。上半部为中断函数。下半部可以有软中断、task_let,workt_struct,request_threaded_irq等。中断嵌入式Linux内核通过读取设备树中的中断属性信息来配置中断中断配置信息位于中断控制器模块节点中。interrupt-con......
关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!