1287 字
6 分钟
Linux中断机制

设备树中断信息节点#

IMX6ULL和IMX6UL的中断控制器呈兼容关系,即IMX6ULL继承IMX6UL的GIC节点信息。在imx6ul.dtsi中有:

intc: interrupt-controller@a01000 {
compatible = "arm,gic-400", "arm,cortex-a7-gic";
/* GIC_PPI:私有外设中断,非共享
9:中断号为9
GIC_CPU_MASK_SIMPLE(1):PPI只发送给CPU0处理
IRQ_TYPE_LEVEL_HIGH:电平触发,高电平有效 */
interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(1) | IRQ_TYPE_LEVEL_HIGH)>;
/* 中断描述需3个单元 */
#interrupt-cells = <3>;
/* 声明节点为中断控制器 */
interrupt-controller;
/* 父类为自身,说明是顶级GIC */
interrupt-parent = <&intc>;
/* GIC寄存器基地址和大小 */
reg = <0x00a01000 0x1000>, /* GIC分发器基地址 */
<0x00a02000 0x2000>, /* GIC CPU接口基地址 */
<0x00a04000 0x2000>, /* GIC虚拟CPU接口 */
<0x00a06000 0x2000>; /* GIC虚拟分发器 */
};

这里的interrupt-cells通常格式为<中断类型 中断号 标志位><中断号 标志位>,其中

  • 中断类型:
    • 0:SPI,共享外设中断
    • 1:PPI,私有外设中断
  • 中断号:设备中断ID,SPI中断号范围为0987,PPI中断号范围为015
  • 标志位:
    • IRQ_TYPE_NONE:Kernel不参与设置,跟随uboot
    • IRQ_TYPE_EDGE_RISING:上升沿触发
    • IRQ_TYPE_EDGE_FALLING:下降沿触发
    • IRQ_TYPE_EDGE_BOTH :双边沿触发
    • IRQ_TYPE_LEVEL_HIGH:高电平触发
    • IRQ_TYPE_LEVEL_LOW:低电平触发

GPIO也可以作为GIC:

gpio5: gpio@20ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
/* 共享中断,中断号74,高电平触发
共享中断,中断号75,高电平触发 */
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_GPIO5>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller; /* 声明为中断控制器 */
gpio-ranges = <&iomuxc 0 7 10>, <&iomuxc 10 5 2>;
}

中断需要在节点中配置,如imx6ul-14x14-evk.dtsii2c1节点下的:

fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>; /* GIC为GPIO5 */
interrupts = <0 8>; /* 中断号为0,低电平触发 */
};

简单来说,设备树节点中描述中断的信息有:

  • #interrupt-cells:中断源cell个数
  • interrupt-controller:声明当前节点为中断控制器
  • interrupts:指定中断号和中断触发模式
  • interrupt-parent:父中断(GIC)

用法#

  1. 修改设备树
  2. 设备结构体中声明irqreturn_t (*handler)(int, void *)(也可以不声明)和int irqnum(存放中断号)
  3. 通过irq_of_parse_and_map向设备树申请中断信息
unsigned int irq_of_parse_and_map(struct device_node *node, int index);

该函数返回申请到的中断号。 4. 初始化时向kernel申请中断:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
  • irq:中断号
  • handler:中断服务函数
  • flags:标志位,可选:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
  • *name:生成中断的设备名
  • *dev:Cookie,传递设备结构体地址
  1. 设备注销时释放中断:
void *free_irq(unsigned int irq, void *dev);
  • irq:中断号
  • *dev:Cookie,传递设备结构体地址

例程#

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/libata.h> /* 新版kernel不再支持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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
#define KEY0VALUE 0x01
#define INVAKEY 0xFF
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10];
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
struct irq_led {
int gpio;
};
struct imx6uirq_dev {
dev_t devid;
struct 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; /* 按键描述结构体 */
struct irq_led irqled;
};
struct imx6uirq_dev imx6uirq;
/* IRQ服务函数,延时10ms,消抖 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 延时10ms,消抖 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数,到期后再次读取按键值,如果还是按下表示按键有效 */
void timer_function(struct timer_list *t)
{
unsigned char value;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = from_timer(dev, t, timer);
keydesc = &dev->irqkeydesc;
value = gpio_get_value(keydesc->gpio); /* 读取IO电平 */
if (value == 0){ /* 如果按下 */
int sta = gpio_get_value(dev->irqled.gpio);
gpio_set_value(dev->irqled.gpio, !sta);
atomic_set(&dev->keyvalue, keydesc->value);
}else{ /* 如果松开 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
}
static int keyio_init(void)
{
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd == NULL){
printk("find node failed\r\n");
return -EINVAL;
}
imx6uirq.irqkeydesc.gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", 0);
if (imx6uirq.irqkeydesc.gpio < 0){
printk("unable get key gpio\r\n");
return -EINVAL;
}
imx6uirq.irqled.gpio = of_get_named_gpio(imx6uirq.nd, "led-gpio", 0);
if (imx6uirq.irqled.gpio < 0){
printk("unable get led gpio\r\n");
return -EINVAL;
}
sprintf(imx6uirq.irqkeydesc.name, "KEY0");
gpio_request(imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.name);
ret = gpio_direction_input(imx6uirq.irqkeydesc.gpio);
if (ret < 0){
printk("unable set key pin level\r\n");
return ret;
}
imx6uirq.irqkeydesc.irqnum = irq_of_parse_and_map(imx6uirq.nd, 0);
printk("key0: gpio = %d, irqnum = %d\r\n", imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.irqnum);
gpio_request(imx6uirq.irqled.gpio, "LED");
ret = gpio_direction_output(imx6uirq.irqled.gpio, 1);
if (ret < 0){
printk("unable set led pin level\r\n");
return ret;
}
imx6uirq.irqkeydesc.handler = key0_handler;
imx6uirq.irqkeydesc.value = KEY0VALUE;
ret = request_irq(imx6uirq.irqkeydesc.irqnum, imx6uirq.irqkeydesc.handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc.name, &imx6uirq);
if (ret < 0){
printk("IRQ request failed, error code: %d\r\n", ret);
}
timer_setup(&imx6uirq.timer, timer_function, 0);
return 0;
}
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq;
return 0;
}
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,
};
static int __init imx6uirq_init(void)
{
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);
}
printk("KEY reg ok, major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
imx6uirq.class = class_create(IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)){
return PTR_ERR(imx6uirq.class);
}
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)){
return PTR_ERR(imx6uirq.device);
}
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
static void __exit imx6uirq_exit(void)
{
del_timer_sync(&imx6uirq.timer);
free_irq(imx6uirq.irqkeydesc.irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc.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");
Linux中断机制
https://arkmostima.pages.dev/posts/linux中断机制/
作者
Aki
发布于
2025-03-26
许可协议
CC BY-NC-SA 4.0