一、什么是中断
一种硬件上的通知机制,用来通知CPU发生了某种需要立即处理的事件
分为:
-
内部中断 CPU执行程序的过程中,发生的一些硬件出错、运算出错事件(如分母为0、溢出等等),不可屏蔽
-
外部中断 外设发生某种情况,通过一个引脚的高、低电平变化来通知CPU (如外设产生了数据、某种处理完毕等等)
二、中断处理原理
任何一种中断产生,CPU都会暂停当前执行的程序,跳转到内存固定位置执行一段程序,该程序被称为总的中断服务程序,在该程序中区分中断源,然后进一步调用该中断源对应的处理函数。
中断源对应的处理函数被称为分中断处理程序,一般每一个分中断处理程序对应一个外设产生的中断
写驱动时,如果外设有中断,则需要编写一个函数(分中断处理程序)来处理这种中断
三、中断接口
3.1 中断申请
一般再init中申请
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
/*
参数:irq:所申请的中断号handler:该中断号对应的中断处理函数flags:中断触发方式或处理方式 触发方式:IRQF_TRIGGER_NONE //无触发IRQF_TRIGGER_RISING //上升沿触发IRQF_TRIGGER_FALLING //下降沿触发IRQF_TRIGGER_HIGH //高电平触发IRQF_TRIGGER_LOW //低电平触发处理方式:IRQF_DISABLED //用于快速中断,处理中屏蔽所有中断IRQF_SHARED //共享中断name:中断名 /proc/interruptsdev:传递给中断例程的参数,共享中断时用于区分那个设备,一般为对应设备的结构体地址,无共享中断时写NULL
返回值:成功:0 失败:错误码
*/
3.2 中断释放
一般再exit中释放
void free_irq(unsigned int irq, void *dev_id);
/*
功能:释放中断号
参数:irq:设备号dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL
*/
3.3 中断处理函数原型
typedef irqreturn_t (*irq_handler_t)(int, void *);
/*
参数:int:中断号void*:对应的申请中断时的dev_id
返回值:typedef enum irqreturn irqreturn_t; //中断返回值类型enum irqreturn {IRQ_NONE = (0 << 0), //什么都没处理IRQ_HANDLED = (1 << 0), //处理正常IRQ_WAKE_THREAD = (1 << 1),};返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
*/
四、按键驱动
重点:区分3种中断编号的含义,以区分中断申请中编号
- 专用中断线
- gpio复用的中断线,所有复用的有个编号(外部中断号,查芯片手册可知)
- GIC内部组内有个编号
- 内部中断、外部中断又有个统一的编号
我们通过外部中断号,找到分组编号,再申请对应的内核对应的中断号
内核软件又有个统一的编号,与上面都不一样,int irq
按键原理图:
总的编号 ID
外部编号 Interrupt Source
组内编号SPI Port No(一个分配器可以将其路由到任意组合的处理器的外围中断)
gpx1节点的定义在/linux-3.14/arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
我们通过找到外部中断引脚gpx1,对应芯片手册找到外部编号EINT[9],找到组内编号25
exynos4412-fs4412.dts中增加节点mykey2_node {compatible = "mykey2,key2";key2-gpio = <&gpx1 1 0>; // 0代表工作模式高电平有效interrupt-parent = <&gpx1>;interrupts = <1 3>; //1代表图中<0 25 0> 3代表11,上升沿和下降沿都触发
};
设计思路
struct keyvalue
{int code; //which KEYint status; //按下或抬起
};struct fs4412key2_dev
{struct cdev mydev;int gpio; int irqno; struct keyvalue data; int newflag; //1 代表又新按键产生,0代表没有产生spinlock_t lock; //对data 和flag两个共享资源加锁控制,因为即会在异常上下文中使用,也会在任务上下文中使用wait_queue_head_t rq; //为了实现读时候的阻塞
};
五、按键实现
修改设备树
定义
fs4412_key.h
#ifndef FS4412_KEY_H
#define FS4412_KEY_Henum KEYCODE
{KEY2 = 1002,KEY3,KEY4,
};enum KEY_STATUS
{KEY_DOWN = 0,KEY_UP,
};struct keyvalue
{unsigned int code; //which keyunsigned int status; //1 0
};#endif
创建
fs4412_key2.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm/uaccess.h>#include "fs4412_key.h"int major = 11;
int minor = 0;
int fs4412key2_num = 1;struct fs4412key2_dev
{struct cdev mydev;unsigned int gpio;unsigned int irqno;struct keyvalue data;unsigned int newflag;spinlock_t lock;wait_queue_head_t rq;
};struct fs4412key2_dev * pgmydev = NULL;int fs4412key2_open(struct inode *pnode,struct file *pfile)
{pfile->private_data =(void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));//暂不处理重复设备打开的情况return 0;
}ssize_t fs4412key2_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;int size = 0;int ret = 0;if(count < sizeof(struct keyvalue)){printk("expect read size is invalid\n");return -1;}spin_lock(&pmydev->lock);if(!pmydev->newflag){if(pfile->f_flags & O_NONBLOCK){//非阻塞spin_unlock(&pmydev->lock);printk("O_NONBLOCK No Data Read\n");return -1;}else{//阻塞spin_unlock(&pmydev->lock);ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1);if(ret){printk("Wake up by signal\n");return -ERESTARTSYS;}spin_lock(&pmydev->lock);}}if(count > sizeof(struct keyvalue)){size = sizeof(struct keyvalue);}else{size = count;}ret = copy_to_user(puser,&pmydev->data,size);if(ret){spin_unlock(&pmydev->lock);printk("copy_to_user failed\n");return -1;}pmydev->newflag = 0;spin_unlock(&pmydev->lock);return size;
}unsigned int fs4412key2_poll(struct file *pfile,poll_table *ptb)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;unsigned int mask = 0;poll_wait(pfile,&pmydev->rq,ptb);spin_lock(&pmydev->lock);if(pmydev->newflag){mask |= POLLIN | POLLRDNORM;}spin_unlock(&pmydev->lock);return mask;
}int fs4412key2_close(struct inode *pnode,struct file *pfile)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;spin_lock(&pmydev->lock);pmydev->newflag = 0;spin_unlock(&pmydev->lock);return 0;
}irqreturn_t key2_irq_handle(int no,void *arg)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;int status = 0;status = gpio_get_value(pmydev->gpio);mdelay(1); //消抖if(status != gpio_get_value(pmydev->gpio)){return IRQ_NONE;}spin_lock(&pmydev->lock);if(status == pmydev->data.status){spin_unlock(&pmydev->lock);return IRQ_NONE;}pmydev->data.code = KEY2;pmydev->data.status = status;pmydev->newflag = 1;spin_unlock(&pmydev->lock);wake_up(&pmydev->rq);return IRQ_HANDLED;
}struct file_operations myops = {.owner = THIS_MODULE,.open = fs4412key2_open,.release = fs4412key2_close,.read = fs4412key2_read,.poll = fs4412key2_poll,
};int __init fs4412key2_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);struct device_node *pnode = NULL;pnode = of_find_node_by_path("/fs4412-key2");if(NULL == pnode){printk("find node failed\n");return -1;}pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);if(NULL == pgmydev){printk("kmallc for struct fs4412key2_dev failed\n");return -1;}pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);pgmydev->irqno = irq_of_parse_and_map(pnode,0);/*申请设备号*/ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");if(ret){ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2");if(ret){kfree(pgmydev);pgmydev = NULL;printk("get devno failed\n");return -1;}major = MAJOR(devno);//容易遗漏,注意}/*给struct cdev对象指定操作函数集*/ cdev_init(&pgmydev->mydev,&myops);/*将struct cdev对象添加到内核对应的数据结构里*/pgmydev->mydev.owner = THIS_MODULE;cdev_add(&pgmydev->mydev,devno,fs4412key2_num);init_waitqueue_head(&pgmydev->rq);spin_lock_init(&pgmydev->lock);ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);if(ret){printk("request_irq failed\n");cdev_del(&pgmydev->mydev);kfree(pgmydev);pgmydev = NULL;unregister_chrdev_region(devno,fs4412key2_num);return -1;}return 0;
}void __exit fs4412key2_exit(void)
{dev_t devno = MKDEV(major,minor);free_irq(pgmydev->irqno,pgmydev);cdev_del(&pgmydev->mydev);unregister_chrdev_region(devno,fs4412key2_num);kfree(pgmydev);pgmydev = NULL;
}MODULE_LICENSE("GPL");module_init(fs4412key2_init);
module_exit(fs4412key2_exit);
修改Makefile
ifeq ($(KERNELRELEASE),)ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_installclean:rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versionselseCONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
obj-m += leddrv.o
obj-m += leddrv_dt.o
obj-m += fs4412_key2.oendif
测试应用程序编写
testkey2_app.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>#include <stdio.h>#include "fs4412_key.h"int main(int argc,char *argv[])
{int fd = -1;struct keyvalue keydata = {0};int ret = 0;if(argc < 2){printf("The argument is too few\n");return 1;}fd = open(argv[1],O_RDONLY);if(fd < 0){printf("open %s failed\n",argv[1]);return 3;}while((ret = read(fd,&keydata,sizeof(keydata))) == sizeof(keydata)){if(keydata.status == KEY_DOWN){printf("Key2 is down!\n");}else{printf("Key2 is up!\n");}}close(fd);fd = -1;return 0;
}
编译拷贝到rootfs
插入内核模块,添加按键设备测试