本实验目的是驱动正点原子阿尔法开发板上的KEY0按键,采用中断的方式,并且采用定时器来实现按键消抖,应用程序APP读取按键值并且打印出来。
实验前需要了解的知识:
什么是中断:
中断使得硬件得以发出通知给处理器。中断本质上是一种特殊的电信号,由硬件设备发向处理器,不同设备对应的中断不同,而每个中断都通过一个唯一的数字标志。
什么是中断处理函数:
中断处理函数是被内核调用来响应中断的,而他们运行于我们称之为中断上下文的特殊上下文中
什么是中断上下文(中断上下半部)
当执行一个中断处理函数时,内核处于中断上下文中。中断上下文和进程并没有什么瓜葛,中断上下文不可以睡眠,它具有较为严格的时间限制。
一:中断相关函数
(1)Linux中断:每个中断都有一个中断号,通过中断号区分不同中断。
(2)申请中断函数:request_irq()函数是在Linux内核中想要使用某个中断时候需要申请的
(3)释放中断函数:free_irq(unsigned int irq, void *dev).
(4)中断处理函数:irqreturn_t(irq_handler_t)(int , void),使用request_irq函数申请中断的时候需要设置中断处理函数,
(5)中断使能与禁止中断:中断使能函数enable_irq(unsigned int irq),中断禁止函数disable_irq(unsigned int irq),enable_irq和disable_irq用于使能和禁止指定的中断,irq就是要禁止的中断号。
二:上半部和下半部
request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,中断服务函数就会执行,中断处理函数一定要快点执行完毕,越快越好,但事实上事与愿违,于是我们必须缩短中断服务函数的执行时间,这个时候中断处理就分为上半部和下半部了。
上半部:上半部就是中断处理函数,耗时较短。
下半部:如果中断处理过程比较耗时,就将这耗时的代码提出来,交给下半部去执行。
上下半部的没有严格区分,这里给出几个参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。
这里记录一下下半部机制
(1)软中断
Linux内核使用结构体softirq_action表示软中断,定义在include/linux/interrupt.h文件中
struct softirq_action
{
void (*action)(struct softirq_action *);
};
软中断描述符有10个,了解一下就好了,要使用软中断,要先注册,使用函数open_softir,注册以后还要使用raise_softirq触发
(2)tasklet
tasklet是利用软中断来实现的另外一种下半部机制,在软中断和tasklet之间,推荐使用tasklet。Linux内核使用结构体tasklet_struct来定义
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
tasklet也需要用到上半部,知识上半部的中断处理函数重点是调用tasklet_schedule,使用tasklet首先要定义一个tasklet函数,然后是初始化(重点是设置对应的处理函数)
(3)工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,工作队列在Linux内核中是一个结构体
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
三:设备树中断信息节点
在imx6ull.dtsi文件中,有中断信息节点,如下
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
**
**
一:修改设备树
在前几期的Linux驱动开发之按键实验已经编写好了key节点,本次中断实验只需要在原来的节点添加interrupt属性。如下,其中 IRQ_TYPE_EDGE_BOTH是在include/linux/irq.h中有定义。IRQ_TYPE_EDGE_BOTH意味着松开和释放按键都会触发中断
key{
compatible = "alientek,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
status = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
二:老规矩,摆上创建好的字符设备驱动框架,做驱动嘛,就应该保留好能够兼容其他驱动的框架,全部替换重命名就好了。有需要的也可以复制下面的驱动框架。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "imx6uirq"
/*imx6uirq设备结构体*/
struct imx6uirq_dev{
dev_t devid;
int major; //主设备号
int minor; //次设备号
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
};
struct imx6uirq_dev imx6uirq;/*imx6uirq*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq;
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t imx6uirq_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret;
return 0;
}
/*cdev操作集合*/
static const struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.write = imx6uirq_write,
.open = imx6uirq_open,
.release = imx6uirq_release,
};
/*驱动入口函数*/
static int __init imx6uirq_init(void)
{
int ret = 0;
/*1注册字符设备驱动*/
imx6uirq.major = 0;
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);
}
if(ret <0){
goto fail_devid;
}
printk("imx6uirq major = %d, imx6uirq = %d \r\n",imx6uirq.major,imx6uirq.minor);
/*2初始化cdev*/
imx6uirq.cdev.owner = THIS_MODULE;
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);//初始化cdev
/*3添加cdev*/
cdev_add(&imx6uirq.cdev, imx6uirq.<