指令 | 功能 | 备注 |
---|---|---|
insmod+xxx.ko | 向开发板内核添加驱动文件 | |
rmmod+xxx.ko | 从开发板内核中移除对应的驱动文件 | |
ls /sys/bus/xxxbus/devices/ | 查看xxx总线中的设备文件 | 需要添加总线和设备文件 |
ls /sys/bus/xxxbus/drivers/ | 查看xxx总线中的驱动文件 | 需要添加总线和驱动文件 |
ls /dev/xxx | 查看添加的设备文件 | 就是device_create()第5个参数 |
ls /proc | 查看设备号 | |
./xxx | 执行xxx可执行文件 | 需要添加可执行文件 |
ls /proc/device-tree/ | 查看添加到设备树上的节点 | 要在设备树文件中添加节点并重新编译,拷贝编译后的设备树文件到tftpdir目录,重启开发板,利用tftp方式下载到开发板中。 |
ls /proc/interrupts/ | 查看申请的中断 | 就是request_irq()的第4个参数 |
注:该路径为个人路径
路径 | 描述 | 备注 |
---|---|---|
/home/ubuntu/code/kernel/linux-3.14 | Linux-3.14内核路径 | 包含众多内核文件 |
/home/ubuntu/tftpdir | tftp服务路径 | 包含tftp服务上传下载文件 |
/home/ubuntu/nfsdir/rootfs | nfs服务路径 | 包含nfs服务共享的文件(根文件系统) |
/home/ubuntu/ARM_Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc | gcc交叉编译工具路径 | |
/home/ubuntu/code/kernel/linux-3.14/arch/arm/boot/dts | 设备树文件 | |
/* 头文件 */
#include
/* 函数入口实现 */
static int __init chr_dev_init(void)
{
printk("-----%s-----\n",__FUNCTION__);
return 0;
}
static void __exit chr_dev_init(void)
{
printk("-----%s-----\n",__FUNCTION__);
}
/* 模块入口声明 */
module_init(chr_dev_init);
module_exit(chr_dev_init);
MODULE_LICENSE("GPL");
KERNEL_DIR=/home/ubuntu/code/kernel/linux-3.14
CUR_DIR=$(shell pwd)
MODULE_NAME=module
APP_NAME=led_app
ROOTFS_DIR=/home/ubuntu/nfsdir/rootfs/drv_module
ifeq ($(KERNELRELEASE), )
all:
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
/home/ubuntu/ARM_Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc $(APP_NAME).c -o $(APP_NAME)
clean:
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
install:
cp *.ko $(ROOTFS_DIR)
cp $(APP_NAME).o $(ROOTFS_DIR)
else
obj-m += $(MODULE_NAME).o
endif
首先应用程序想要实现底层硬件的控制,必须要通过内核去调用设备驱动来控制底层硬件,所以说直接控制硬件的是内核中的设备驱动。那么应用程序又是如何让设备驱动去控制硬件的呢?首先需要在内核中添加应用程序想要控制的硬件的设备驱动,让内核知道有这个驱动设备,有了想要的设备驱动,然后让应用程序关联这个设备驱动,实现应用程序 调用文件IO,设备驱动执行对应的接口函数。我们可以在这个接口函数中,编写程序实现对硬件寄存器的控制(地址的控制),最终实现硬件的控制。
这里有几个知识点
答:通过创建字符设备文件(设备节点)来实现。这个字符设备文件其实就是一个设备信息结构体,
其中存放了我们想要的设备驱动信息,其中包含了,字符设备的信息内容、我们想要创建的设备驱动的设备号、以及给我们想要创建的设备驱动取得名字等信息。然而这些作为该结构体成员的设备驱动的具体参数值(内容),需要我们去创建出来,然后利用一个返回值为该结构体类型的指针函数struct device *device_create(),将这些参数值(内容)传到这个结构体中。这样才算创建完了一个字符设备文件,此时的字符设备文件中相当于被初始化了,将传入的参数值(内容)作为对应成员的值(内容)。
这里又有个问题:
在回答这个问题前,我们先要知道,这个结构体(设备文件)需要我们创建哪些具体的参数值(内容):创建的设备文件的信息内容、父类对象、设备号、私有数据、设备文件名。好,明白了要创建什么,现在就要知道,怎么创建
参数1的具体值(内容)-------设备文件的信息内容
通过 class_create()函数创建
struct class * class_create(owner,name);
例如:struct class * cls_led = class_create(THIS_MODULE,"led cls");
参数1:
owner:拥有者,一般THIS_MODULE
参数2:
name:字符串,描述信息
返回值:
struct class *
信息结构体
参数2的具体值(内容)-------创建父类对象
这个参数一般为NULL
参数3的具体值(内容)--------设备号
在创建设备号之前需要明白:
设备号:32bit = 主设备号(高12bit) + 次设备号(低20bit)
主设备号:表示同一类设备
次设备号:表示同一类设备中的不同设备
方式1:
其实以下两步操作可以通过一个封装好的函数来实现 -------MKDEV(主设备号,次设备号),返回值就是设备号
方式2:
我们只需要设置主设备号,次设备号自动分配
/* 向内核申请注册设备号 */
int register_chrdev( unsigned int major,
const char *name,
const struct file_operations *fops)
例如:ret = register_chrdev(250,"led",&fops);
进一步计算的到设备号:
dev_t devt = 250<<20 | 0;
参数1:
unsigned int major:主设备号
无符号整数
参数2:
const char *name:描述一个设备驱动信息,自定义,通俗来说就是设备名叫什么
字符串首地址
参数3:
const struct file_operations *fops:文件操作对象
结构体地址(指针)
解释:文件操作对象,就是一个结构体的实例化对象的地址,该结构体中的成员都是函数指针,这个结构体可以实现应用层程序和存储驱动间的函数关联,置于这个关联有什么用,下面会详细讲,这里大概就说一下,就是应用层调用什么文件IO接口,底层设备驱动就执行该结构体实例化的对象中赋值的函数接口。
返回值:
正确返回0,失败返回负数
参数4的具体值(内容)-------私有数据
私有数据,一般填NULL
参数5的具体值(内容)------设备文件名
一个字符串
当我们得到结构体(设备节点)所需要的成员的具体值(内容)后,就可以利用下面这个函数将值赋值给(设备节点)结构体中对应的成员。
struct device *device_create( struct class *class, //参数1
struct device *parent, //参数2
dev_t devt, //参数3
void *drvdata, //参数4
const char *fmt, //参数5
...)----创建设备节点
例如:struct device * led_dev = device_create(cls_led,NULL,devt,NULL,"led%d",3);
首先我们要知道,什么是关联,所谓关联就是:在驱动中实现文件io接口功能,当应用程序调用文件io时,驱动程序也调用对应的文件io接口函数
在内核中设计好的结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};//函数指针的集合,
每个函数指针赋值为函数地址,就代表当应用程序调用对应的文件io函数时,驱动就执行函数指针赋值的对应函数
例如:
//驱动需要对应执行的IO函数实现,注意,这些IO函数是根据应用程序的需要进行关联和编写的,如果应用程序中没有用到相关的IO函数,就不需要关联和编写
int led_open (struct inode * inode, struct file * file)
{
printk("led_open\n");
return 0;
}
int led_close (struct inode * inode, struct file * file)
{
printk("led_close\n");
return 0;
}
ssize_t led_read (struct file * file, char __user * data, size_t size, loff_t * ops)
{
printk("led_read");
copy_to_user(data,"nihao",6); //内核向应用程序中输出6字节大小的数据
return 0;
}
ssize_t led_write (struct file * file, const char __user * data, size_t size, loff_t * ops)
{
int num = 0;
copy_from_user(&num,data,size); //内核从应用程序中接受size大小的字节数据到num中
printk("num = %d\n",num);
if(num == 1)
{
*gpx1dat |= 1;
}
else
{
*gpx1dat &= ~1;
}
return 0;
}
/* 驱动和应用程序间实现关联 */
const struct file_operations fops = {
.open = led_open,//当应用程序调用open时,驱动则执行结构体成员open对应的赋值函数
.release = led_close,
.read = led_read,
.write = led_write,
};
//注意:这个结构体对象fops的地址,就是创建设备号时register_chrdev()的第三个参数,表示文件操作对象的地址。
这里是一个应用程序
#include
#include
#include
#include
#include
int main()
{
int a = 10;
char buf[] = "hello world";
int fd = open("/dev/led3",O_RDWR);
printf("%s\n",buf);
sleep(2);
read(fd,buf,10); //应用程序从内核驱动中读取10字节大小的内容到buf中
printf("%s\n",buf);
while(1) //实现led3每隔1s闪烁一次
{
a = 1;
write(fd,&a,4); //应用程序从a中向内核写入4字节大小的数据
sleep(1);
a = 0;
write(fd,&a,4);
sleep(1);
}
close(fd);
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgdD28hO-1666346371077)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221016025522916.png)]
中断简单来讲就是当产生中断信号并通知内核,让CPU停下当前的工作去完成中断处理,完成之后返回并继续执行之前的工作的过程。
从底层中断原理来讲来说,中断根据不同的异常工作模式,分为软中断SWI、快中断FIQ,普通中断IRQ。按照触发方式可分为内部中断和外部中断。按照是否带有操作系统,分为裸机中断和驱动中断。由于操作系统的内核已经添加了中断驱动,gpio控制器驱动、GIC中断控制器驱动,所以我们不需要像裸机开发那样,对gpio控制器引脚、GIC中断寄存器的众多寄存器进行配置,只需要将对应的引脚信息告诉内核,让内核知道我们需要实现哪个引脚的中断功能就可以了。所以相比较而言,裸机中断需要完成的任务更加底层,需要直接控制寄存器。这里说一下裸机开发中的中断控制原理,首先就是当中断源产生中断信号以后,判断是外部中断还是内部中断,如果是外部中断,那么需要通过gpio引脚控制器将中断信号传入到GIC中断控制器中,内部中断则不需要通过GPIO控制器就可以将中断信号传给GIC中断控制器。在GIC控制器中需要完成外部/内部中断使能、中断分发、分发使能、cpu接口选择,接口使能,中断优先级设置,这一系列配置完成之后,根据不同中断类型输入到内核处理器中完成处理,具体的处理过程就是,中断异常处理机制,要完成"四大步,三小步"操作。第一大步是将当前ARM内核工作模式cpsr寄存器的值保存到相应异常模式下的spsr寄存器中;第二大步是将PC寄存器的值保存到相应异常模式下的lr寄存器中。前两步主要完成中断异常现场保护。第三大步,就是将当前工作模式下的cpsr寄存器的值设置为相应的异常中断模式;第四大步就是将PC寄存器的值设置为相应异常中断处理函数的入口地址,也就是异常向量表中对应异常的入口地址。之后在第四大步中分为三小步操作,第一步就是将当前程序中寄存器的值入栈保存;第二步才是跳转到用户用C语言编写的中断异常处理函数的地址,执行具体的中断处理;第三步是当中断处理完毕之后需要恢复中断前的现场,所以需要将栈中的数据出栈,其中就包括了lr寄存器的值出栈赋值给PC寄存器,然后继续执行之前的程序,这样就完成了一次中断异常处理。当然有些简单的项目不涉及到异常向量表,而是使用的中断向量表,不过二者都是类似的,唯一的不同就是使用异常向量表可以处理更多的一些异常,中断就是异常的一种而已。
首先,外设触发中断,然后发送中断信号到驱动设备中,驱动设备检测到中断信号,得到对应的中断号,接着利用得到的中断号向CPU申请中断,CPU接受中断申请后就会调用对应的中断处理函数,实现中断处理,这样就完成了一次由外部硬件触发的中断。
这里有几个知识点;
答:不同于裸机开发,我们需要手动设置中断控制器,给对应中断配置设置中断号,开启外部中断,总中断使能,以及中断到来时需要编写异常向量表,实现对应中断的入口和跳转到对应的中断处理函数。我们在有操作系统的情况下,更多是将内核的寄存器配置都配置好了,例如这里的中断寄存器GIC。此外设备树中也有许多的硬件设备描述信息,其中就包含了每个可实现中断的硬件设备的中断号。像这样**在设备树中添加硬件设备信息描述(其中分为两部分添加,一个是在.dtsi文件中添加对应控制器的中断控制信息描述,第二部分就是在.dts中添加具体哪一个硬件的中断信息描述,包括了描述信息、父类、在父类中对应第几个中断号、触发方式),在驱动中通过接口函数1找到产生中断的硬件设备在设备树上的节点,利用节点再调用接口函数2实现中断号的获取。**实现接口函数是
具体在设备树中添加硬件设备信息流程,如下图
在
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e9WyBrj2-1666346371078)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017095705686.png)]
设备树文件也有头文件,而且也是一级一级嵌套的,例如
interrupt-parent = <&gpx1>
其中gpx1就存在于exynos4412.dtsi头文件中
注:
X: GIC_SPI(共享中断)或者GIC_PPI(私有中断)
Y: 物理中断号
Z:触发方式
1 = low-to-high edge triggered 上升沿触发
2 = high-to-low edge triggered 下降沿触发
4 = active high level-sensitive 高电平触发
8 = active low level-senstive 低电平触发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xZhot7rk-1666346371080)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221016184837470.png)]
struct device_node *of_find_node_by_path(const char *path);-------接口函数1,找到对应的设备节点
参数1:硬件设备节点路径,这里的路径是设备树中的节点位置,(因为设备树中有一个根节点,所有添加的设备信息都在该根节点中)
当内核启动后,扫描设备树,将其中的每个节点都转换为一个device_node结构体,节点中的信息都存放在这个结构体中。这样的话在访问的时候就不需要在去扫描设备树了,只需要访问对应的结构体变量就可完成信息的获取。
返回值;硬件设备节点的结构体地址
unsigned int irq_of_parse_and_map(struct device_node *dev,int index);------接口函数2,获取中断号
参数1:硬件设备节点结构体地址
参数2:在当前具体硬件设备的中断列表中的第几个中断编号
返回值;硬件设备的中断号
例如
//a、获取中断号
//获取设备树中的要使用的硬件节点
struct device_node * node = of_find_node_by_path("/key3_node"); //这个key3_node就是我们自己在设备树中添加的节点名字
//获取节点中的中断号
int irqno = irq_of_parse_and_map(node,0);
答:当驱动获得了对应硬件设备的中断号以后,利用这个中断号向CPU申请中断,由于在内核的设备树中已经添加了对应硬件设备的描述信息(中断号),所以当驱动调用带有中断号的接口函数request_irq()就可以让内核知道这是哪一个具体的硬件设备
/* 申请中断 */
request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
参数1:
unsigned int irq:申请中断的中断号
参数2:irq_handler_t handler
typedef irqreturn_t (*irq_handler_t)(int, void *); //typedef类型替换,用irq_handler_t表示一个函数指针
irqreturn_t (*)(int, void *) <---- irq_handler_t
irq_handler_t handler:是一个函数指针,进行注册中断,当产生中断时需要自动调用的中断处理函数
函数原型:irqreturn_t 函数名 (int irqno, void * dev)
当该函数同时绑定了多个中断设备,那么就可以通过中断号irqno来判断到底是哪一个中断设备触发的中断,避免了每一个中断 设备都需要写一个中断处理函数。
参数3:
unsigned long 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 ------低电平触发
参数4:
const char *name:字符串首地址,中断的描述信息,就是添加的中断的名字,利用下面这条指令在开发版中参看:
/proc/inruppter 开发板终端查看中断名字
参数5:
void *dev:(作为参数2这个函数的参数)
这个函数的使用方法:当我们创建的设备机构体实例化对象是一个局部的变量,那么就需要将这个结构体实例化对象的地址传入 到第二个参数,方便中断处理函数使用该结构体中的变量和函数。
返回值:
成功返回0,失败返回非0
例如:
request_irq(irqno,key_irq_handler,IRQF_TRIGGER_FALLING,"key interrupt",NULL);
释放中断:
void free_irq(unsigned int irq,void * dev_id)
参数1:
unsigned int irq
中断号
参数2:
void * dev_id:与申请中断第五个参数保持一致
两部分:驱动中获取到硬件的数据、驱动传递给用户(应用)
a、驱动中获取到硬件的数据
通过中断获取到数据(中断处理函数中)
具体硬件对应着一个数据值
key2------'a'
key3------'q'
从硬件的寄存器中获取数据值
key3--------按下和抬起-------1/0
读取key3的gpio寄存器---数据寄存器
b、驱动传递给用户(应用)
驱动在文件io接口实现中,完成传递给应用
驱动在xxxx_read函数中将数据传递给用户
ret = copy_to_user(buf,&data,1);
编写应用程序,利用IO接口函数实现数据获取。
阻塞io模型------休眠等待
阻塞:当进程读取外部资源(数据),但是外部资源没有准备好,进程就进行休眠等待资源可用
具体过程:该过程主要用到CPU进程调度机制(上下文切换,时间片轮转),首先,外部硬件产生中断后,CPU执行中断进程,对中断任务进行处理,此时中断进程处于运行态,当中断处理函数执行完毕后,继续检测是否有对应中断触发。如果有的话,继续执行中断任务;如果外部没有中断触发时,中断进程就进入中止态,具体的操作包括中断进程改变进程状态,让出调度(不占用cpu资源),进入到等待队列中。此后,如果外部中断触发了,那么中断进程被唤醒,从中止态切换到就绪态,占用CPU,然后执行中断任务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqgmOOwL-1666346371081)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221013020242939.png)]
在应用中:
read
wirte
accept
scanf
默认都是阻塞的
驱动中实现阻塞:
创建等待队列头:
wait_queue_head_t head; //在自定义结构体中添加,定义一个等待队列头变量head
init_waitqueue_head(&head); //在模块初始化函数中添加,表示创建一个等待队列头
1、在需要等待的位置(没有数据),就阻塞等待
在驱动IO函数中添加,用于判断是否阻塞读写数据
wait_event_interruptible(wq,condition)-----根据参数是否进行阻塞等待,完成阻塞等待,
参数1:
wq:等待队列头,把当前进程加入到哪个等待队列中
参数2:
condition:是否执行阻塞等待的条件
condition:真---不进行阻塞
condition:假---进行阻塞
2、中断处理函数中进行阻塞唤醒
wake_up_interruptible(&head);
注:如何让驱动知道打开的文件到底是阻塞打开还是非阻塞打开-----------利用file->f_flags参数查看
f_flags这个变量是一个32位的数,利用特定的位上的值来代表文件的一个属性,例如第几位表示读权限,第几位表示写权限,
这里的O_NONBLOCK就是利用f_flags的某一位来表示,如果应用程序文件打开方式是阻塞等待那么这一位上的值就是0,否者就是1,表示非阻塞等待
利用condition这个变量的值,来判断是否有中断产生,当有中断产生时,该值被设置为1,当没有中断产生时,该值就是0
理解:
这里无非就是有4种情况,
1. 阻塞没有中断产生------key_read()阻塞等待中断产生并读取数据,返回给应用程序,返回0
2. 阻塞有中断产生------key_read()直接读取中断产生的数据,返回给应用程序,返回0
3. 非阻塞没有中断-----key_read()直接返回-1,
4. 非阻塞有中断-----key_read()直接读取中断产生的数据,并返回给应用程序,返回0
在这四种情况中,只有第三种会立即返回,所以可以简写逻辑代码为以下形式:
表示的含义就是:判断应用程序是否是阻塞方式打开设备驱动,如果为非阻塞方式,而且此时还没有中断触发,产生数据,就会直接返回-1,表示没有读取到数据
如果为非阻塞,此时有中断触发产生数据,那么直接读取数据并返回1,表示读取到了数据,注意这里就也会执行wait_event_interruptable()函数,因为此时有数据产生(中断触发了),所以conditon变量是1,对于
wait_event_interruptable()函数内部实现原理而言,conditon变量为1,需要取反判断,如果为取反结果为0,那么就不进入等待队列中,直接读取数据,如果取反结果为1(中断没有触发),那么满足进入等待队列的条件,就需要
进入到等待队列中等待中断产生,然后唤被醒。
总之就是说:
当conditon=1(中断触发了)时,无论是阻塞还是非阻塞都直接读取数据并返回1,conditon=0(中断没触发),阻塞进入到等待队列中,等待中断产生数据,非阻塞直接返回-1
为什么要使用异步通知:
大多数情况下,我们更希望是当中断产生后应用程序才去调用IO函数,实现数据的收发。而不是应用程序一直循环调用IO函数,判 断驱动是否有中断产生。因为我们希望应用程序除了接受中断数据以外,还需要执行其他代码,完成其他的功能。
那么怎么实现异步通知呢?
两个方面的设置:一是应用程序中需要提前设置信号处理,当信号到达时执行对应操作。二是驱动程序中需要建立与应用程序进程之 间的关联,以及中断触发时发送信号给应用程序
应用程序设置;
1. 设置SIGIO信号的处理操作-----信号处理函数
2. 设置SIGIO属主进程
3. 设置为异步信号模式(fasync)
驱动程序设置:
1. 与进程进行关联
2. 发送SIGIO信号
具体的执行过程:
提前在应用程序中设置好信号处理(3点),在驱动程序中设置与进程进行关联(2点),然后当中断产生后在中断处理函数中发送 信号通知具体的一个应用进程,然后具体的应用程序接收到这个信号,执行信号处理函数,调用文件IO收发数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJVyQ8fr-1666346371082)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017193706249.png)]
涉及的函数;
应用程序:
signal(int signum,sighandler_t handler);
参数1:驱动向应用程序发送的信号类型,除了SIGKILL和SIGSTOP信号
参数2:描述与信号关联的动作,它可以去以下三种值:
SIG_IGN:表示忽略该信号
SIG_DFL:这个符号表示恢复对信号的系统默认处理,不写此处理函数默认也是执行系统默认操作
sighandler_t类型的指针函数,即用户自定义信号处理函数。
fcntl(fd,F_SETOWN,getpid());
描述;设置当前进程为SIGIO信号的属主进程
参数1:open()打开的驱动设备文件返回的文件描述符
参数2:属主参数
参数3:进程号,这里一般用getpid()函数来获取当前进程的进程号
int flags = fcntl(fd,F_GETFL);
flags |= FASYNC;//添加异步属性,这一步操作的时候就会调用底层的异步信号通知结构体初始化函数fasync_helper()
fcntl(fd,F_SETFL,flags);
描述;将io模式设置为异步模式
参数1:open()打开的驱动设备文件返回的文件描述符
参数2:文件操作参数,F_GETFL获取文件属性,F_SETFL设置文件属性
参数3:文件属性参数,FASYNC就是用32bit数据中的某几位表示异步属性
驱动程序:
1. 需要和进程进行关联--记录信号该发给谁
fasync_helper(fd,file,no,&(key.fasync))
添加实现,一个fasync的接口
struct fsdync_struct *fasyns;
fasync_struct结构体用来存放对应设备文件的信息(例如fd,filp)并交给内核来管理
例如:
/* 驱动中配置异步通知信号 */
细节:要将该函数写在close函数上方,因为在close函数中会用到该函数用来对添加的异步消息节点销毁
int key_fasync (int fd , struct file * filp, int on)
{
printk("key fasync \n");
//记录信号发送给哪个谁,同时下面这个函数的作用还是初始化key.fasync这个结构体(或者说就是在内核中将异步通知信号链表中添加新的节点),将fd、filp、on参数信息给到结构体,on为0表示将当前异步通知信号从内核中将异步通知信号链表删除,on为1表示添加一个新的节点
return fasync_helper(fd,filp,on,&(key.fasync)); //fd为应用程序打开的文件获得文件描述符,
}
const struct file_operations fops = {
.open = key_open,
.release = key_release,
.read = key_read,
.fasync = key_fasync,
};
2. 在某个特定的时间去发送信号,在有数据的时候
kill_fasync()
例如:
//中断触发,发送信号给到应用程序,让应用程序执行对应操作,例如读写数据等操作
kill_fasync(&(key.fasync),SIGIO,POLLIN);
表示向异步信号结构体中发送SIGIO信号,POLLIN表示一个事件
按照后创建的先卸载原则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SREwrG9I-1666346371083)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221016221954859.png)]
iounmap(映射的虚拟地址)
释放中断:
void free_irq(unsigned int irq,void * dev_id)
参数1:
unsigned int irq
中断号
参数2:
void * dev_id:与申请中断第五个参数保持一致
device_destroy(设备类,设备号)
class_destroy(设备类)
unregister_chrdev(主设备号,设备名字)
kfree(创建的设备结构体地址)
用kzalloc()开辟的堆区空间,该函数不同于malloc(),他可以在创建的时候清空创建的空间
在应用程序上的应用,其实也是需要在驱动程序中关联。该函数的原型在file_opration这个函数中有声明,只需要在我们自己的驱动程序中像read、open等文件IO操作一样,定义就行了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnoO6Az7-1666346371083)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017175855826.png)]
/* 该函数的作用就是用来监听应用程序有没有事件产生 */
unsigned int (*poll) (struct file *filp, struct poll_table_struct *pts)
{
//返回一个mask值
unsigned int mask;
//调用poll_wait,将当前等待队列注册系统中-----初始化监听队列节点
poll_wait(filp,&key_dev->wqhead,pts);
//1. 当没有数据到时返回一个0
if(!key_dev->key_state)
mask =0;
//2.有数据返回一个POLLIN,这个POLLIN就是我们要监听的事件
if(key_dev->key_state)
mask |=POLLIN;
return mask;
}
/*
参数1:struct file *filp
应用程序打开的设备文件结构体地址(指针)
参数2:struct poll_table_struct *pts
监听对列表
*/
/*
什么时候调用该函数?实现监听的原理是什么?
应用层调用poll()函数,内核底层就会调用do_sys_poll()函数,do_sys_poll()函数中会调用do_poll()函数,do_poll()函数中会调用do_pollfd(),这个函数就是在循环遍历监听队列池,就是在应用层编写的pfd结构体数组。第一次进入监听的时候由于没有事件,do_pollfd()返回值为0,在代用do_pollfd()的函数中,会判断,如果返回值为0,计数count不增加,任然为0,这个计数count的目的就是为了用来判断do_pollfd()函数是否退出的。如果计数count为1,则退出do_pollfd()函数,否则为0不退出do_pollfd()函数,程序就阻塞在该函数中,进程存放到等待队列中,应用程序表现为程序停在了poll函数位置。当pfd结构体数组中的事件产生,就会唤醒等待队列中的进程,退出do_pollfd()函数,再次执行刚刚进来的操作,这一次有事件产生了,
当我们应用程序系统调用open一个驱动文件时,驱动程序也会调用open函数,此时就会产生一个file结构体,该结构体中定义了一个f_op的结构体,f_op结构体中有一个poll()函数,这个poll()函数就是我们在驱动程序中定义的poll()函数。所以当我们在应用程序中系统调用poll函数时,就会通过这种方式调用驱动中的poll函数,同时poll函数的返回值mask就是事件数,如果有事件产生,那么返回值mask被赋值为POLLIN,然后将maks赋值给pollfd()结构体的revents变量,通过事件数是否为0来判断计算count是否自加1,从而来决定是否要退出do_pollfd()函数,count被返回到do_poll()函数,表示返回事件数。,do_poll()函数又返回该值到do_sys_poll()函数,do_sys_poll()函数有返回该值到我们在驱动文件中定义的poll函数,最终返回到应用程序中,进行main函数的判断
参考:blog.csdn.net/u0128301148/article/details/80465789
*/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnuR5q9M-1666346371084)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017180341304.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MgKUbJ1f-1666346371085)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017180608425.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H8jj6yqh-1666346371086)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017205340401.png)]
注意:
中断的上半部主要是要马上处理的代码,刻不容缓。例如数据接收
数据处理和解析等不是特别着急处理的代码,封装成单独的函数,这个函数就是中断的下半部。然后采用一种新的内核调度机制,当系统不忙的时候就去执行中断下半部分函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTc4zt4k-1666346371087)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017211035774.png)]
softirq:软件中断是一种软件成面上的中断,它的优先级比普通应用程序优先级高,比硬件中断要低,可以被硬件中断打断
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJvYPghn-1666346371087)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017214005202.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfoBvkJK-1666346371088)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017213712824.png)]
初始化小任务,就是将后面两个参数保存到小任务结构体中的过程,如下图;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A0HuAs7j-1666346371089)(C:\Users\hejunqi\AppData\Roaming\Typora\typora-user-images\image-20221017214312199.png)]
/* 创建小任务结构体 */
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); //中断下半部的实现逻辑
unsigned long data; //传递给到fun函数
};
/* 创建小任务变量 */
struct tasklet_struct mystasklet;
/* 初始化小任务 */
tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
参数;
t:struct tasklet_struct结构体的地址;
func:启动下半部分后调函数;
date:回调函数参数结构体变量地址
中断下半部分具体函数实现
该函数并不会在中断中的下半步启动后立即执行,而是等到cpu不忙的时候再去执行
/* 启动下中断半部分后的回调函数 */
static void irq_next_half(unsigned long date)
{
printk("irq_next_half run\n");
//该值为1,表示中断触发
condition = 1;
//将等待队列中的进程唤醒
wake_up_interruptible(&(key.head));
//异步发送信号给到应用程序,让应用程序执行对应操作,例如读写数据等操作
kill_fasync(&(key.fasync),SIGIO,POLLIN);
}
在驱动中断函数启动下半步
//启动中断下半部分
tasklet_shedule (&mystasklet);
通过创建线程来处理中断下半部分,线程的开销更大,但是限制最小,可以有delay()、sleep()等函数
中断是否和其他进程一样,都是利用进程调度机制(上下文切换,时间片轮转),还是说当中断产生后cpu当前只执行中断处理,一直执行到中断处理完毕才会执行其他的进程?
#include
#include
#include
#include
#include
#include
struct pwm_drv
{
unsigned int major;
dev_t devno;
struct class * pwm_cls;
struct device * pwm_dev;
unsigned int * pwmtcntb0;
unsigned int * pwmtcmpb0;
unsigned int * pwmtcfg0;
unsigned int * pwmtcfg1;
unsigned int * pwmtcon;
unsigned int * gpd0con;
};
struct pwm_drv beep;
ssize_t beep_write (struct file * file, const char __user * buf, size_t size, loff_t * ops)
{
}
int beep_open (struct inode * inode, struct file * file)
{
}
int beep_close (struct inode * inode, struct file * file)
{
}
const struct file_operations fops = {
.open = beep_open,
.release = beep_close,
.write = beep_write,
};
static int __init beep_init(void)
{
int ret;
beep.major = 240;
beep.devno = beep.major << 20 | 0;
ret = register_chrdev(beep.major,"beep pwm",&fops);
if(ret < 0)
{
printk("register chrdev error\n");
goto err_1;
}
beep.pwm_cls = class_create(THIS_MODULE,"beep cls");
if(IS_ERR(beep.pwm_cls))
{
printk("class create error\n");
goto err_2;
}
beep.pwm_dev = device_create(beep.pwm_cls,NULL,beep.devno,NULL,"beep");
if(IS_ERR(beep.pwm_dev))
{
printk("device create error\n");
goto err_3;
}
/* 寄存器虚拟内存地址映射与赋值 */
beep.gpd0con = ioremap(0x114000a0,4);
*(beep.gpd0con) = *(beep.gpd0con) & ~(0xf) | 0x2;
beep.pwmtcntb0 = ioremap(0x139d000c,4);
*(beep.pwmtcntb0) = 2000;
beep.pwmtcmpb0 = ioremap(0x139d0010,4);
*(beep.pwmtcmpb0) = 1000;
beep.pwmtcfg0 = ioremap(0x139d0000,4);
*(beep.pwmtcfg0) = *(beep.pwmtcfg0) & ~0xff | 249;
beep.pwmtcfg1 = ioremap(0x139d0004,4);
*(beep.pwmtcfg1) &= ~0xf;
beep.pwmtcon = ioremap(0x139d0008,4);
*(beep.pwmtcon) |= 1<<3;
*(beep.pwmtcon) |= 1<<2;
*(beep.pwmtcon) |= 1<<1;
*(beep.pwmtcon) &= ~(1<<1);
*(beep.pwmtcon) |= 1;
//*(beep.pwmtcon) &= ~1;
return 0;
err_3:
class_destroy(beep.pwm_cls);
err_2:
unregister_chrdev(beep.major,"beep pwm");
err_1:
return -1;
}
static void __exit beep_exit(void)
{
}
module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
/*
功能描述:
异步通知实现的可阻塞可非阻塞IO按键中断
关键字:异步通知、阻塞/非阻塞IO模型、中断
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct key_node
{
unsigned int major;
struct class * cls;
struct device * dev;
dev_t devno;
wait_queue_head_t head;
struct fasync_struct * fasync;
};
//获取硬件数据存储的变量
char data;
int condition = 0;
struct key_node key;
ssize_t key_read (struct file * file, char __user * buf, size_t size, loff_t * ops)
{
/*
if(file->f_flags & O_NONBLOCK == 0)
{
wait_event_interruptible(key.head,condition);
}
else
{
if(condition == 0)//O_NONBLOCK
{
return -1;
}
}
注解;
f_flags这个变量是一个32位的数,利用特定的位上的值来代表文件的一个属性,例如第几位表示读权限,第几位表示写权限,
这里的O_NONBLOCK就是利用f_flags的某一位来表示,如果应用程序文件打开方式是阻塞等待那么这一位上的值就是0,否者就是1,表示非阻塞等待
利用condition这个变量的值,来判断是否有中断产生,当有中断产生时,该值被设置为1,当没有中断产生时,该值就是0
理解:
这里无非就是有4种情况,
1. 阻塞没有中断产生------key_read()阻塞等待中断产生并读取数据,返回给应用程序,返回0
2. 阻塞有中断产生------key_read()直接读取中断产生的数据,返回给应用程序,返回0
3. 非阻塞没有中断-----key_read()直接返回-1,
4. 非阻塞有中断-----key_read()直接读取中断产生的数据,并返回给应用程序,返回0
在这四种情况中,只有第三种会立即返回,所以可以简写逻辑代码为以下形式:
表示的含义就是:判断应用程序是否是阻塞方式打开设备驱动,如果为非阻塞方式,而且此时还没有中断触发,产生数据,就会直接返回-1,表示没有读取到数据
如果为非阻塞,此时有中断触发产生数据,那么直接读取数据并返回1,表示读取到了数据,注意这里就也会执行wait_event_interruptable()函数,因为此时有数据产生(中断触发了),所以conditon变量是1,对于
wait_event_interruptable()函数内部实现原理而言,conditon变量为1,需要取反判断,如果为取反结果为0,那么就不进入等待队列中,直接读取数据,如果取反结果为1(中断没有触发),那么满足进入等待队列的条件,就需要
进入到等待队列中等待中断产生,然后唤被醒。
总之就是说:
当conditon=1(中断触发了)时,无论是阻塞还是非阻塞都直接读取数据并返回1,conditon=0(中断没触发),阻塞进入到等待队列中,等待中断产生数据,非阻塞直接返回-1
*/
//设置非阻塞
if(((file->f_flags & O_NONBLOCK) != 0) && (condition == 0))
//不管是阻塞还是非阻塞,只要有数据都要读取数据继续执行
{
return -1;
}
//阻塞等待
wait_event_interruptible(key.head,condition);
//拷贝数据
int ret;
printk("key read\n");
ret = copy_to_user(buf,&data,1);
//下一次能够阻塞
condition = 0;
//把存储数据的缓冲区清空
data = '\0';
return 1;
}
int key_open (struct inode * inode, struct file * file)
{
printk("key open\n");
return 0;
}
int key_release (struct inode * inode , struct file * file)
{
printk("key close\n");
return 0;
}
//实现中断处理函数
irqreturn_t key_irq_handler(int irqno, void * dev)
{
//获取到数据
data = 'q';
condition = 1; //该值为1,表示中断触发
printk("irqno is %d\n",irqno);
printk("input char '%c'\n",data);//可以认为是一个字符‘q’,也可以从数据寄存器中获取真实的值
//唤醒,把进程从等待队列中拿出来继续执行
wake_up_interruptible(&(key.head));
//中断触发,发送信号给到应用程序,让应用程序执行对应操作,例如读写数据等操作
kill_fasync(&(key.fasync),SIGIO,POLLIN);
return IRQ_HANDLED;
}
/* 驱动中配置异步通知信号 */
int key_fasync (int fd , struct file * file, int no)
{
printk("key fasync \n");
//记录信号发送给哪个应用
return fasync_helper(fd,file,no,&(key.fasync)); //fd为应用程序打开的文件获得文件描述符,
}
const struct file_operations fops = {
.open = key_open,
.release = key_release,
.read = key_read,
.fasync = key_fasync,
};
static int __init key_drv_init(void)
{
//1、申请设备号
key.major = 230;
register_chrdev(key.major,"key drv",&fops);
//2、创建设备节点
key.cls = class_create(THIS_MODULE,"cls");
key.devno = MKDEV(key.major,0);
key.dev = device_create(key.cls,NULL,key.devno,NULL,"key3");
//4、硬件初始化
//中断初始化
//a、获取中断号
//获取设备树中的要使用的硬件节点
struct device_node * node = of_find_node_by_path("/key3_node");
//获取节点中的中断号
int irqno = irq_of_parse_and_map(node,0);
//创建等待队列头
init_waitqueue_head(&(key.head)); //默认是将当前进程放在等待队列中的,当中断触发就会执行下面的中断申请,然后自动调用中断处理函数,在终端处理函数中唤醒中断进程
//b、申请中断
request_irq(irqno,key_irq_handler,IRQF_TRIGGER_FALLING,"key interrupt",NULL);
//irqreturn_t (*handler)(int, void *)函数指着
//handler = key_irq_handler
return 0;
}
static void __exit key_exit(void)
{
}
module_init(key_drv_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include
#include
/*
为什么要使用异步通知:
大多数情况下,我们更希望是当中断产生后应用程序才去调用IO函数,实现数据的收发。而不是应用程序一直循环调用IO函数,判断 驱动是否有中断产生。因为我们希望应用程序除了接受中断数据以外,还需要执行
其他代码,完成其他的功能。
那么怎么实现异步通知呢?
两个方面的设置:一是应用程序中需要提前设置信号处理,当信号到达时执行对应操作。二是驱动程序中需要建立与应用程序进程之 间的关联,以及中断触发时发送信号给应用程序
应用程序设置;
1. 设置SIGIO信号的处理操作-----信号处理函数
2. 设置SIGIO属主进程
3. 设置为异步信号模式(fasync)
驱动程序设置:
1. 与进程进行关联
2. 发送SIGIO信号
具体的执行过程;
提前在应用程序中设置好信号处理,在驱动程序中设置与进程进行关联,然后当中断产生后在终端处理函数中发送信号通知具体的一 个应用进程,然后具体的应用程序接收到这个信号,执行信号处理函数,调用
文件IO收发数据
*/
int fd;
/* 捕获信号并执行信号处理 */
void catch_signal(int signo)
{
char num;
if( read(fd,&num,1) < 0)
{
printf("no data;");
return ;
}
printf("data is %c\n",num);
}
int main()
{
// int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);非阻塞
fd = open("/dev/key3",O_RDONLY);//阻塞
if(fd < 0)
{
perror("open error");
return -1;
}
//1、设置信号处理方式
//SIGIO这是一个驱动向应用程序发送的信号,应用程序通过检测接收这个信号,控制 catch_signal()函数的调用
signal(SIGIO,catch_signal);
//2、设置当前进程为SIGIO信号的属主进程
fcntl(fd,F_SETOWN,getpid());
//3、将io模式设置为异步模式
int flags = fcntl(fd,F_GETFL);
flags |= FASYNC;//添加异步属性
fcntl(fd,F_SETFL,flags);
//完成其他功能操作
while(1)
{
printf("hello world\n");
sleep(1);
}
close(fd);
/*
sleep(1);
char data = 'a';
int num = 10;
while(1)
{
num = 10;
data = 'a';
num = read(fd,&data,1);
printf("%d %c\n",num,data);
sleep(1);
}
*/
return 0;
}
/*
描述:
完成了按键key3设备文件的添加、映射GPX1DAT寄存器的虚拟内存地址、绑定了按键key3中断,创建了等待队列,由于编写了存在了异步发送功能,所以等待队列实现阻塞等待的意义就失去了,等待队列真正用到的地方是,当应用程序需要不断地循环读取数据时,没有数据就需要阻塞等待,让读取进程进入到等待队列中,减少对cpu资源的浪费。考虑到进程不可能只用来是循环读取数据,所以有了异步信号发送,可以实现驱动端发送信号给应用程序,然应用程序执行信号处理函数去执行读取操作(其实就是一个异步中断)。
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPX1CON_KEY3 0x11000c20
#define IOREMAP_SIZE 4
#define KEY3 3
#define Hight 1
#define Low 0
struct key3_device {
unsigned int major;
struct class* cls;
dev_t devno;
struct device* key_dev;
unsigned int* gpx1con;
unsigned int* gpx1dat;
struct device_node* dev_node;
wait_queue_head_t head;
unsigned int irqno;
//struct key_event* key_infomation;
struct fasync_struct* fasync;
};
struct key_event {
unsigned int Which_key;
unsigned int Leveling;
};
struct key_event key_infomation;
struct key3_device key3_my;
int condition = 0;
/* IO函数实现 */
int key_open(struct inode* inode, struct file* file)
{
printk("chrdev --(/dev/key3_dev_node) open successfully\n");
return 0;
}
ssize_t key_read(struct file* file, char __user* buf, size_t size, loff_t* ops)
{
//设置非阻塞,非阻塞且中断没触发,直接返回-1
if (((file->f_flags & O_NONBLOCK) != 0) && (condition == 0))
{
return -1;
}
//不管是阻塞还是非阻塞,只要中断触发有数据都要读取数据继续执行
wait_event_interruptible(key3_my.head, condition);
//拷贝数据
int ret;
//读取按键的电平给到应用程序
ret = copy_to_user(buf,&key_infomation,sizeof(key_infomation));
//恢复0,保证下一次能够阻塞
condition = 0;
return 1;
}
ssize_t(*write) (struct file*, const char __user*, size_t, loff_t*);
/* 驱动中配置异步通知信号 */
int key_fasync (int fd , struct file * filp, int on)
{
printk("key3 fasync \n");
//记录信号发送给哪个应用
return fasync_helper(fd,filp,on,&(key3_my.fasync)); //fd为应用程序打开的文件获得文件描述符,
}
const struct file_operations fops = {
.open = key_open,
.read = key_read,
.fasync = key_fasync,
};
irqreturn_t key_irq_handler(int irqno, void* dev)
{
printk("key_irq run\n");
//该值为1,表示中断触发
condition = 1;
//将等待队列中的进程唤醒
wake_up_interruptible(&(key3_my.head));
/* 通过按键的数据寄存器的值来判断按键是按下还是抬起 */
if ((*key3_my.gpx1dat & (0x1 << 2)) == 0)
{
printk("dev:key3 dowm\n");
key_infomation.Which_key = KEY3;
key_infomation.Leveling = Low;
}
else
{
printk("dev:key3 up\n");
key_infomation.Which_key = KEY3;
key_infomation.Leveling = Hight;
}
//中断触发,发送信号给到应用程序,让应用程序执行对应操作,例如读写数据等操作
kill_fasync(&(key3_my.fasync), SIGIO, POLLIN);
return IRQ_HANDLED;
}
static int __init key_int_my_init(void)
{
key3_my.major = 255;
register_chrdev(key3_my.major, "key3_my_devno", &fops);
key3_my.cls = class_create(THIS_MODULE, "key3 cls");
key3_my.devno = MKDEV(key3_my.major, 0);
key3_my.key_dev = device_create(key3_my.cls, NULL, key3_my.devno, NULL, "key3_dev_node");
/* 初始化硬件 */
key3_my.gpx1con = ioremap(GPX1CON_KEY3, IOREMAP_SIZE);
*(key3_my.gpx1con) = ( * (key3_my.gpx1con) & ~(0xf << 8)) | 0xf;
key3_my.gpx1dat = ioremap(GPX1CON_KEY3 + 4, IOREMAP_SIZE);
/* 中断初始化 */
//通过设备节点获取中断号
key3_my.dev_node = of_find_node_by_path("/key3_node");
/*key3_node是下载到开发板上的设备树的根节点上的设备节点
返回值是设备节点的信息结构体地址*/
key3_my.irqno = irq_of_parse_and_map(key3_my.dev_node, 0);
/* 返回值是中断号,参数1是设备节点信息结构体的地址,参数2是
按键3在该设备节点中的中断编号为第零个中断号*/
//创建等待队列头
init_waitqueue_head(&(key3_my.head));
//默认是将当前进程放在等待队列中的,当中断触发就会执行下面的中断申请,然后自动调用中断处理函数,在终端处理函数中唤醒中断进程
//绑定中断
request_irq(key3_my.irqno, key_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "key3_irq", NULL);
return 0;
}
static void __exit key_int_my_exit(void)
{
}
module_init(key_int_my_init);
module_exit(key_int_my_exit);
MODULE_LICENSE("GPL");
/*
描述:
完成了信号的设置,用来接受异步信号。
现象:(我这里只设置了一个按键key3,所以只会显示的是key3的信息,可以自己添加其他按键,然后按键值Which_key 判断是哪个按 键按下,以及查看Leveling 查看当前按键的电平状态)
当按键key3按下,就会在终端打印出是哪一个按键按下、此时的按键数据寄存器的电平状态为低电平
当按键key3抬起,就会在终端打印出是哪一个按键抬起、此时的按键数据寄存器的电平状态为高电平
*/
#include
#include
#include
#include
#include
#include
#define KEY3 3
#define Hight 1
#define Low 0
int fd;
struct key_event {
unsigned int Which_key;
unsigned int Leveling;
};
struct key_event key_infomation;
/* 捕获信号并执行信号处理 */
void catch_signal(int signo)
{
int num;
if (read(fd, &key_infomation, sizeof(key_infomation)) < 0)
{
printf("no data;");
return;
}
if (key_infomation.Which_key == KEY3)
{
if (key_infomation.Leveling == Hight)
{
printf("app:key3 up\n");
printf("app:key3 leveling is %d\n", key_infomation.Leveling);
}
else
{
printf("app:key3 down\n");
printf("app:key3 leveling is %d\n", key_infomation.Leveling);
}
}
}
int main()
{
fd = open("/dev/key3_dev_node", O_RDONLY | O_NONBLOCK);
//1、设置信号处理方式
//SIGIO这是一个驱动向应用程序发送的信号,应用程序通过检测接收这个信号,控制 catch_signal()函数的调用
signal(SIGIO, catch_signal);
//2、设置当前进程为SIGIO信号的属主进程
fcntl(fd, F_SETOWN, getpid());
//3、将io模式设置为异步模式
int flags = fcntl(fd, F_GETFL);
flags |= FASYNC;//添加异步属性
fcntl(fd, F_SETFL, flags);
//进程执行其他任务---1s打印一次hello world
while (1)
{
printf("hello world\n");
sleep(1);
}
}
KERNEL_DIR=/home/ubuntu/code/kernel/linux-3.14
CUR_DIR=$(shell pwd)
MODULE_NAME=key_int
APP_NAME=key_int_app
ROOTFS_DIR=/home/ubuntu/nfsdir/rootfs/drv_module
ifeq ($(KERNELRELEASE), )
all:
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
/home/ubuntu/ARM_Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc $(APP_NAME).c -o $(APP_NAME)
clean:
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
install:
cp *.ko $(ROOTFS_DIR)
cp $(APP_NAME) $(ROOTFS_DIR)
else
obj-m += $(MODULE_NAME).o
endif
uct key_event {
unsigned int Which_key;
unsigned int Leveling;
};
struct key_event key_infomation;
/* 捕获信号并执行信号处理 */
void catch_signal(int signo)
{
int num;
if (read(fd, &key_infomation, sizeof(key_infomation)) < 0)
{
printf(“no data;”);
return;
}
if (key_infomation.Which_key == KEY3)
{
if (key_infomation.Leveling == Hight)
{
printf("app:key3 up\n");
printf("app:key3 leveling is %d\n", key_infomation.Leveling);
}
else
{
printf("app:key3 down\n");
printf("app:key3 leveling is %d\n", key_infomation.Leveling);
}
}
}
int main()
{
fd = open(“/dev/key3_dev_node”, O_RDONLY | O_NONBLOCK);
//1、设置信号处理方式
//SIGIO这是一个驱动向应用程序发送的信号,应用程序通过检测接收这个信号,控制 catch_signal()函数的调用
signal(SIGIO, catch_signal);
//2、设置当前进程为SIGIO信号的属主进程
fcntl(fd, F_SETOWN, getpid());
//3、将io模式设置为异步模式
int flags = fcntl(fd, F_GETFL);
flags |= FASYNC;//添加异步属性
fcntl(fd, F_SETFL, flags);
//进程执行其他任务---1s打印一次hello world
while (1)
{
printf("hello world\n");
sleep(1);
}
}
#### Makef
KERNEL_DIR=/home/ubuntu/code/kernel/linux-3.14
CUR_DIR=$(shell pwd)
MODULE_NAME=key_int
APP_NAME=key_int_app
ROOTFS_DIR=/home/ubuntu/nfsdir/rootfs/drv_module
ifeq ($(KERNELRELEASE), )
all:
make -C ( K E R N E L D I R ) M = (KERNEL_DIR) M= (KERNELDIR)M=(CUR_DIR) modules
/home/ubuntu/ARM_Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc $(APP_NAME).c -o $(APP_NAME)
clean:
make -C ( K E R N E L D I R ) M = (KERNEL_DIR) M= (KERNELDIR)M=(CUR_DIR) clean
make -C ( K E R N E L D I R ) M = (KERNEL_DIR) M= (KERNELDIR)M=(CUR_DIR) clean
install:
cp *.ko $(ROOTFS_DIR)
cp $(APP_NAME) $(ROOTFS_DIR)
else
obj-m += $(MODULE_NAME).o
endif