Linux驱动开发之中断编程
1,中断号--就是一个号码,需要通过一定的方式去获取到
在3.14.0内核中,从设备树中获取
获取中断号的方法:
1, 宏定义
IRQ_EINT(号码)
2,设备树文件中
arch/arm/boot/dts/exynos4412-fs4412.dts
硬件连接:
key ---- gpx1_2--- EINT10
打开设备树文件:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
找到gpx1:(该代码已经编译好,无需人工编写,此处作为分析源码)
gpx1: gpx1 {
gpio-controller;//描述信息:这是一个gpio口控制器
#gpio-cells = <2>;//
interrupt-controller;//描述信息:这是一个中断控制器
interrupt-parent = <&gic>;//中断继承于gic(gpio具有中断功能,继承了一些中断特性)
interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;//中断号的一些表述(见芯片手册中断部分的说明,
//上图右下角)
#interrupt-cells = <2>;
};
在编程过程中,需要定义自己的节点--描述当前设备用的中断号(此处需要人工编写完成)
arch/arm/boot/dts/exynos4412-fs4412.dts +51
key_int_node{
compatible = "test_key";//可以通过compatible 来搜索这个节点
interrupt-parent = <&gpx1>;//继承gpx1
interrupts = <2 4>;//这里设置gpx1中的<0 26 0>,该成员位于gpx1中interrupts 的第2个,所以第一个位置
//填写2第二个位置填写的是触发方式(0:上升沿 2:下降沿 4:高电平 8:低电平,
//具体待查,可以不用管)
};
编译设备树文件:
make dtbs
更新dtbs文件:
cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
2,在驱动中去通过代码获取到中断号,并且申请中断(实现中断处理方法)
a,获取到中断号码:
int get_irqno_from_node(void)
{
// 获取到设备树中的节点
struct device_node *np = of_find_node_by_path("/key_int_node");
if(np){
printk("find node ok\n");
}else{
printk("find node failed\n");
}
// 通过节点去获取到中断号码
int irqno = irq_of_parse_and_map(np, 0);
printk("irqno = %d\n", irqno);
return irqno;
}
b,申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
参数1: 设备对应的中断号
参数2: 中断的处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);
参数3:触发方式
#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 //低电平触发
参数4:中断的描述,自定义,主要是给用户查看的
/proc/interrupts
参数5:传递给参数2中函数指针的值
返回值: 正确为0,错误非0
参数2的赋值:
irqreturn_t key_irq_handler(int irqno, void *devid)
{
return IRQ_HANDLED;
}
释放中断:
void free_irq(unsigned int irq, void *dev_id)
参数1: 设备对应的中断号
参数2:与request_irq中第5个参数保持一致
3,实现字符设备驱动的框架
// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,MKDEV(key_dev->dev_major,0), NULL, "key0");
4,驱动中将硬件所产生的数据传递给用户
a,硬件如何获取数据
key: 按下和抬起: 1/0
读取key对应的gpio的状态,可以判断按下还是抬起
读取key对应gpio的寄存器--数据寄存器
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
b,驱动如何传递给用户
在中断处理中填充数据:
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;//按键的值(0/1)
在xxx_read中将数据传递给用户
ret = copy_to_user(buf, &key_dev->event, count);
c,用户如何拿到--编写应用程序
while(1)
{
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else{
printf("APP__ key enter up\n");
}
}
}
5,实现文件IO模型之一阻塞,等同于休眠
文件io模型:
1,非阻塞
2,阻塞
3,多路复用--select/poll
4, 异步信号通知faync
阻塞: 当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠
linux应用中,大部分的函数接口都是阻塞
scanf();
read();
write();
accept();
驱动中需要调用
1,将当前进程加入到等待队列头中
add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait)
2,将当前进程状态设置成TASK_INTERRUPTIBLE
set_current_state(TASK_INTERRUPTIBLE)
3,让出调度--休眠
schedule(void)
更加智能的接口,等同于上面的三个接口:
wait_event_interruptible(wq, condition)
驱动如何去写代码(参考:https://blog.csdn.net/yikai2009/article/details/8653578)
1,等待队列头
wait_queue_head_t wq_head;//定义等待队列
init_waitqueue_head(&key_dev->wq_head);//初始化等待队列。函数原型:init_waitqueue_head(wait_queue_head_t *q);
struct key_disc
{
unsigned int dev_major;
struct class * cls;
struct device * dev;
int irqno;
void * reg_base;
struct key_event event;
wait_queue_head_t wq_head;//定义一个等待队列变量
int key_state;//等待队列头的标志位
}* key_dev;
static int __init key_drv_init(void)
{
int ret;
key_dev = kzalloc(sizeof(struct key_disc), GFP_KERNEL);
key_dev->dev_major = register_chrdev(0, "key_dev_test", &my_fops);
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key%d",0);
key_dev->irqno = get_irqno_from_node();
key_dev->reg_base = ioremap(GPXCON_REG,8);
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return -EBUSY;
}
init_waitqueue_head(&key_dev->wq_head);//初始化等待队列
return 0;
}
2,在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(wait_queue_head_t wq, condition) // 内部会构建一个等待队列项/节点wait_queue_t
参数1: 等待队列头
参数2: 条件,如果是为假,就会等待(进程进入 TASK_INTERRUPTIBLE 模式睡眠,并挂在 queue 参数所指定的等待队列上),如果为真,就不会等待
可以用一标志位,来表示是否有数据
ssize_t key_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("********%s********\n",__FUNCTION__);
int ret;
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);/*等待队列头,当按键按下或抬起时会产生中断,
key_dev->key_state会被置1,不再等待,直接往下运行代码*/
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
flag = 1;
return count;
}
3,在一个合适的时候(有数据),会将进程唤醒
wake_up_interruptible(wait_queue_head_t *q)// 从等待队列 q 中唤醒状态为 TASK_INTERRUPTIBLE 的进程
用法:
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
irqreturn_t key_irq_handler(int irqno, void * devid)//按键中断处理函数
{
//printk("---------%s----------\n", __FUNCTION__);
//printk("***********************************************\n");
if(flag){
int value = readl(key_dev->reg_base + 4) & (0x1<<2);
if(value)
{
printk("k3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}
else
{
printk("k3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
wake_up_interruptible(&key_dev->wq_head);//唤醒等待
key_dev->key_state = 1;}
return IRQ_HANDLED;
}
6, 非阻塞: 在读写的时候,如果没有数据,立刻返回,并且返回一个出错码
用的会比较少,因为比较耗资源
open("/dev/key0", O_RDWR|O_NONBLOCK);
------------------------------------
驱动中需要去区分,当前模式是阻塞还是非阻塞
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
7,多路复用--select和poll
poll的应用:
1, 需要打开多个文件(多个设备)
2, 利用poll来实现监控fd的读,写,出错
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数1: 表示多个文件描述符集合
struct pollfd描述的是文件描述符的信息
struct pollfd {
int fd; //文件描述符
short events; //希望监控fd的什么事件:读,写,出错
POLLIN 读,
POLLOUT 写,
POLLERR出错
short revents; //结果描述,表示当前的fd是否有读,写,出错
//用于判断,是内核自动赋值
POLLIN 读,
POLLOUT 写,
POLLERR出错
};
参数2:被监控的fd的个数
参数3: 监控的时间:
正: 表示监控多少ms
负数: 无限的时间去监控
0: 等待0ms,类似于非阻赛
返回值: 负数:出错
大于0,表示fd中有数据
等于0: 时间到
8,如果应用中使用poll对设备文件进行了监控,那么设备驱动就必须实现poll接口
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一个mask值
unsigned int mask;
// 调用poll_wait,将当前的等待队列注册到系统中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,当没有数据的时候返回一个0
if(!key_dev->key_state)
mask = 0;
// 2,有数据返回一个POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
const struct file_operations key_fops = {
.poll = key_drv_poll,
};
//设计一个全局设备对象--描述按键信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有数据
};
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一个mask值
unsigned int mask;
// 调用poll_wait,将当前的等待队列注册系统中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,当没有数据到时候返回一个0
if(!key_dev->key_state)
mask = 0;
// 2,有数据返回一个POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
const struct file_operations key_fops = {
.open = key_drv_open,
.read = key_drv_read,
.write = key_drv_write,
.release = key_drv_close,
.poll = key_drv_poll,
};
static int __init key_drv_init(void)//入口函数
{
//演示如何获取到中断号
int ret;
// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中断申请
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何获取数据--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
return 0;
}
/*按键测试程序,配合驱动使用*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
struct key_event{
int code; // 表示按键的类型: home, esc, Q,W,E,R,T, ENTER
int value; // 表示按下还是抬起 1 / 0
};
#define KEY_ENTER 28
int main(int argc, char *argv[])
{
int ret;
struct key_event event;
char in_buf[128];
int fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
//监控多个文件fd
struct pollfd pfd[2];
pfd[0].fd = fd; //监控按键设备
pfd[0].events = POLLIN;
pfd[1].fd = 0; //标准输入
pfd[1].events = POLLIN;
while(1)
{
ret = poll(pfd, 2, -1); // 驱动需要去实现poll接口,类似于open, read,
printf("ret = %d\n", ret);
if(ret > 0)
{
if(pfd[0].revents & POLLIN)
{
read(pfd[0].fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
if(pfd[1].revents & POLLIN)
{
fgets(in_buf, 128, stdin);
printf("in_buf = %s\n", in_buf);
}
}else{
perror("poll");
exit(1);
}
}
close(pfd[0].fd);
return 0;
}
9,异步信号通知: 当有数据到时候,驱动会发送信号(SIGIO)给应用,就可以异步去读写数据,不用主动去读写
a,应用--处理信号,主要是读写数据
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO");
// 读取数据
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
// 1,设置信号处理方法
signal(SIGIO,catch_signale);
// 2,将当前进程设置成SIGIO的属主进程
fcntl(fd, F_SETOWN, getpid());
// 3,将io模式设置成异步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
b,驱动--发送信号
1,需要和进程进行关联--记录信号该发送给谁
实现一个fasync的接口
int key_drv_fasync(int fd, struct file *filp, int on)
{
//只需要调用一个函数记录信号该发送给谁
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
2,在某个特定的时候去发送信号,在有数据的时候
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
/*驱动部分代码*/
//设计一个全局设备对象--描述按键信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有数据
struct fasync_struct *faysnc;
};
struct key_desc *key_dev;
irqreturn_t key_irq_handler(int irqno, void *devid)
{
printk("-------%s-------------\n", __FUNCTION__);
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 抬起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
//2,在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
// 表示有数据
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 传递给用户数据之后,将数据清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
// 返回一个mask值
unsigned int mask;
// 调用poll_wait,将当前到等待队列注册系统中
poll_wait(filp, &key_dev->wq_head, pts);
// 1,当没有数据到时候返回一个0
if(!key_dev->key_state)
mask = 0;
// 2,有数据返回一个POLLIN
if(key_dev->key_state)
mask |= POLLIN;
return mask;
}
int key_drv_fasync(int fd, struct file *filp, int on)
{
//只需要调用一个函数记录信号该发送给谁
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
const struct file_operations key_fops = {
.open = key_drv_open,
.read = key_drv_read,
.write = key_drv_write,
.release = key_drv_close,
.poll = key_drv_poll,
.fasync = key_drv_fasync,
};
static int __init key_drv_init(void)
{
//演示如何获取到中断号
int ret;
// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,
MKDEV(key_dev->dev_major,0), NULL, "key0");
// 获取中断号
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno,key_irq_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何获取数据--gpx1(地址映射)
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
return 0;
}
/*测试文件*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
struct key_event{
int code; // 表示按键的类型: home, esc, Q,W,E,R,T, ENTER
int value; // 表示按下还是抬起 1 / 0
};
#define KEY_ENTER 28
static int fd;
static struct key_event event;
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO\n");
// 读取数据
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
int main(int argc, char *argv[])
{
int ret;
fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
// 1,设置信号处理方法
signal(SIGIO,catch_signale);
// 2,将当前进程设置成SIGIO的属主进程
fcntl(fd, F_SETOWN, getpid());
// 3,将io模式设置成异步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
while(1)
{
// 可以做其他的事情
printf("I am waiting......\n");
sleep(1);
}
close(fd);
return 0;
}
10-中断的下半部
1,softirq: 处理比较快,但是内核级别的机制,需要修改整个内核源码,不推荐也不常用
2,tasklet: 内部实现实际调用了softirq
3, workqueue: 工作队列
1,tasklet:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); // 下半部的实现逻辑
unsigned long data; // 传递给func的参数
};
a, 初始化
struct tasklet_struct mytasklet;
tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
例子:
void key_tasklet_half_irq(unsigned long data)
{
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
b,在上半部中放入到内核线程中--启动
// 启动下半步
tasklet_schedule(&key_dev->mytasklet);
c,模块卸载的时候:
tasklet_kill(&key_dev->mytasklet);
/*驱动部分代码*/
//设计一个全局设备对象--描述按键信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有数据
struct fasync_struct *faysnc;
struct tasklet_struct mytasklet;
};
struct key_desc *key_dev;
void key_tasklet_half_irq(unsigned long data)//中断的下半部
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
irqreturn_t key_irq_handler(int irqno, void *devid)//中断的上半部
{
printk("-------%s-------------\n", __FUNCTION__);
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 抬起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
// 启动下半步
tasklet_schedule(&key_dev->mytasklet);
return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
//2,在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
// 表示有数据
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 传递给用户数据之后,将数据清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
static int __init key_drv_init(void)
{
//演示如何获取到中断号
int ret;
// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL,
MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中断申请
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何获取数据--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
//初始化tasklet
tasklet_init(&key_dev->mytasklet, key_tasklet_half_irq, 45);
return 0;
}
2,工作队列和工作
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
a, 初始化
void work_irq_half(struct work_struct *work)
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
struct work_struct mywork;
INIT_WORK(struct work_struct *work, work_func_t func);
b, 在上半部中放入到内核线程中--启动
schedule_work(&key_dev->mywork);
/*驱动部分代码*/
//设计一个全局设备对象--描述按键信息
struct key_desc{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int irqno;
void *reg_base;
struct key_event event;
wait_queue_head_t wq_head;
int key_state; //表示是否有数据
struct fasync_struct *faysnc;
struct work_struct mywork;
};
struct key_desc *key_dev;
void work_irq_half(struct work_struct *work)//代码的下半部
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
irqreturn_t key_irq_handler(int irqno, void *devid)//代码的上半部
{
printk("-------%s-------------\n", __FUNCTION__);
//读取数据寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);
if(value){ // 抬起
printk("key3 up\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 0;
}else{//按下
printk("key3 pressed\n");
key_dev->event.code = KEY_ENTER;
key_dev->event.value = 1;
}
// 启动下半步
schedule_work(&key_dev->mywork);
return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
//printk("-------%s-------------\n", __FUNCTION__);
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
int ret;
//2,在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
// 表示有数据
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
// 传递给用户数据之后,将数据清除掉
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
static int __init key_drv_init(void)
{
//演示如何获取到中断号
int ret;
// 1,设定一个全局的设备对象
key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);
// 2,申请主设备号
key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
// 3,创建设备节点文件
key_dev->cls = class_create(THIS_MODULE, "key_cls");
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
// 4,硬件的初始化--地址映射或者中断申请
key_dev->irqno = get_irqno_from_node();
ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
// a,硬件如何获取数据--gpx1
key_dev->reg_base = ioremap(GPXCON_REG, 8);
// 初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
INIT_WORK(&key_dev->mywork, work_irq_half);//初始化工作队列
return 0;
}