一、Linux中断
在裸机中断中我们需要做很多工作,比如关闭MMU,Cache等配置寄存器,使能中断。但是linux内核提供了完善的中断框架,我们只需要申请中断request_irq,然后注册中断处理函数即可。使用很方便。最后还应释放中断free_irq。
我们还会经常使用到使能禁能中断。
如果有两个任务,A-B。当A先禁能中断10s,B后禁能2s,B要使能以后就会打乱A的节奏。所以要需要查看一下当前的中断状态flags,B才会决定是否要禁能2s后使能,否则A就会将系统崩溃。
#include<linux/interrupt.h>
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev) //中断请求函数
void free_irq(unsigned int, void *); //中断释放函数
void disable_irq(unsigned int irq); //禁能中断
void enable_irq(unsigned int irq); //使能中断
void disable_irq_nosync(unsigned int irq);
local_irq_disable() //禁能当前处理器的中断
local_irq_save(flags) //保存中断状态
local_irq_restore(flags) //恢复中断状态
二、中断上下半部
中断分为上半部与下半部。一般我们所说的中断都是上半部的,中断要求快速执行。但是如果我们处理一些比较浪费时间的程序,就要使用中断下半部了。
注:irq_request函数是申请中断上半部的。
1.软中断
linux内核引入软中断与tasklet。软中断使用softirq_action结构体。软中断机制就是“谁触发,谁执行”。因此要想使用软中断,就要注册对应的软中断函数,使用void open_softirq函数去注册。注册以后触发函数。注册号后通过raise_softirq触发中断。
软中断在编译的时候要静态编译,使用**softirq_init(void)**初始化中断。
struct softirq_action
{
void (*action)(struct softirq_action *);
};
static struct softirq_action softirq_vec[NR_SOFTIRQS];
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
void open_softirq(int nr, void (*action)(struct softirq_action *));
void raise_softirq(unsigned int nr);
void softirq_init(void);
2.tasklet
tasklet_struct结构体用于定义中断下半部,然后初始化。初始化也可以使用DECLARE_TASKLET宏直接定义并且完成初始化。
tasklet_schedule调度中断下半部。
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
DECLARE_TASKLET(name, func, data);
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
static inline void tasklet_schedule(struct tasklet_struct *t);
3.工作队列
工作队列是一种中断下半部的执行方式。workqueue_struct结构体定义。也是初始化,调度等等。
三、设备树描述中断
首先compatible中有一些选项,我们的芯片是“arm,cotex-a7-gic”,因此选择这个。
interrupt-controller描述结点是中断控制器。
#interrupt-cells描述了中断源的由3个cells组成。第一个代表SPI,PPI还是;第二个代表中断号;第三个代表触发类型。
1.设备树文件描述
举个例子:gpio1中描述中断的信息是interrupts,它是SPI的中断,中断号为66,67,高电平触发中断类型。
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>; GPIO5_00 , 低电平触发
};
2.获取中断号
通用的获取中断号函数
unsigned int irq_of_parse_and_map(struct device_node *dev,int index);
对于GPIO也有API函数获取中断号,因为GPIO是经常使用的。返回中断号。
int gpio_to_irq(unsigned int gpio);
四、按键中断
1.修改设备树
主要是添加interrupt-parent与interrupts
/*
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)
*/
key{
#address-cells = <1>;
#size-cells = <1>;
compatible = "pintitus,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
key-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
interrupt-parent = <&gpio5>;
interrupts = <1 IRQ_TYPE_EDGE_BOTH>
};
2.中断配置程序
结构体
设备结构体,用于初始化按键
/*key结构体*/
struct irq_keydesc{
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*handler)(int , void*); /*中断处理函数*/
}
/*设备结构体*/
struct dev{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*设备的类*/
struct device *device;/*设备*/
struct device_node *nd;/*设备结点*/
int timeperiod; /*定时周期*/
struct timer_list timerdev; /*timer设备*/
struct irq_keydesc irqkey[key_CNT];
};
初始化按键中断
1.注册设备
2.按键初始化 , 定时器初始化
3.中断初始化
4.中断处理函数编写
static int __init key_module_init(void){
int ret = 0;
/* 1.注册字符设备 */
if(key.major){
key.devid = MKDEV(key.major , 0);
ret = register_chrdev_region(key.devid , key_CNT , key_NAME);
}else{
ret = alloc_chrdev_region(&key.devid , 0 , key_CNT , key_NAME);
key.major = MAJOR(key.devid);
key.minor = MINOR(key.devid);
}
if(ret < 0){
goto failed_devid;
}
printk("key.major = %d , key.minor = %d \r\n" ,key.major , key.minor );
/* 2.添加字符设备 */
key.cdev.owner = THIS_MODULE;
cdev_init( &key.cdev , &key_fops);
ret = cdev_add(&key.cdev , key.devid , key_CNT);
if(ret < 0){
goto failed_cdev;
}
/* 自动创建设备结点 */
/* 3.创建设备的类 */
key.class = class_create(THIS_MODULE , key_NAME);
if(IS_ERR(key.class)){
ret = PTR_ERR(key.class);
goto failed_class;
}
/* 4.创建设备 */
key.device = device_create(key.class , NULL , key.devid , NULL ,key_NAME);
if(IS_ERR(key.device)){
ret = PTR_ERR(key.device);
goto failed_device;
}
/* 按键初始化 */
key.nd = of_find_node_by_path("/key");
if(key.nd == NULL){
ret = -EINVAL;
goto failed_findnd;
}
key.irqkey[0].gpio = of_get_named_gpio(key.nd , "key-gpios" , 0);
memset(key.irqkey[0].name , 0 ,sizeof(key.irqkey[0].name));
key.irqkey[0].name[0] = "KEY";
gpio_request(key.irqkey[0].gpio ,key.irqkey[0].name);
gpio_direction_input(key.irqkey[0].gpio);
/* 获取中断号 */
#if 1
key.irqkey[0].irqnum = gpio_to_irq(key.irqkey[0].gpio);
#else
key.irqkey[0].irqnum = irq_of_parse_and_map(key.nd , 0);
#endif
/* 中断初始化 */
key.irqkey[0].handler = key_irqhandler;
key.irqkey[0].value = KEY_VALUE;
ret = request_irq(key.irqkey[0].irqnum , key.irqkey[0].handler ,
IRQF_TRIGGER_FALLING||IRQF_TRIGGER_RISING ,
key.irqkey[0].name , &key);
if(ret){
printk("irq %d request failed!\r\n" , key.irqkey[0].irqnum);
goto failed_irq;
}
/* 定时器初始化 */
init_timer(&key.timerdev);
key.timerdev.function = timer_handler;
return 0;
failed_irq:
failed_findnd:
device_destroy(key.class , key.devid);
printk("failed_findnd\r\n");
failed_device:
class_destroy(key.class);
printk("failed_device\r\n");
failed_class:
cdev_del(&key.cdev);
printk("failed_class\r\n");
failed_cdev:
unregister_chrdev_region(key.devid , key_CNT);
printk("failed_cdev\r\n");
failed_devid:
return ret;
}
按键中断函数编写
开启定时器用于消抖
/*
按键中断函数
*/
static irqreturn_t key_irqhandler(int irq, void *dev_id){
struct struct_dev *dev = dev_id;
//触发按键中断以后,给定消抖定时器参数
dev->timerdev.data = (volatile long)dev_id;
mod_timer(&dev->timerdev , jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
定时器处理函数
获取按键的IO值,输出打印信息。
/*
Timer处理函数
*/
static void timer_handler(unsigned long dummy){
int value = 0;
struct struct_dev *dev = (struct struct_dev*)dummy;
value = gpio_get_value(dev->irqkey[0].gpio);
if(value == 1){ /*按下*/
printk("Key Down! \r\n");
}else if(value == 0){
printk("Key Up! \r\n");
}
}
五、中断下半部使用
1.tasklet的使用
首先定义tasklet结构体,然后初始化tasklet。
初始化函数第一个参数传入tasklet结构体变量;第二个参数是tasklet处理函数,第三个参数是tasklet处理函数传入的参数。
tasklet_init(&key.tasklet , Key_irq_tasklet , &key);
然后在想要调用中断下半部去调度tasklet
tasklet_schedule(&dev->tasklet);
最后编写tasklet处理函数
/*
按键中断下半部
*/
static void Key_irq_tasklet(unsigned long *data){
struct struct_dev *dev = (struct struct_dev *)data;
dev->timerdev.data = (volatile long)data;
mod_timer(&dev->timerdev , jiffies+msecs_to_jiffies(10));
}
2.工作队列的使用
类似于tasklet。依旧是定义work_struct变量,初始化工作队列,调度函数,编写处理函数。
/*work*/
struct work_struct work;
INIT_WORK(&key.work , Key_irq_workqueue );
schedule_work(&dev.work);
/*
工作队列的处理函数
*/
static void Key_irq_workqueue(struct work_struct *work){
struct struct_dev *dev = container_of(work, struct struct_dev, work);
dev->timerdev.data = (unsigned long)dev;
mod_timer(&dev->timerdev , jiffies+msecs_to_jiffies(10));
}