中断按键驱动
gpx1:gpx1{
gpio-controller;
#gpio-cells=<2>;
interrupt-controller;
interrupt-parent=<&gic>;
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=<2>;
};
这个设备树节点描述了一个名为`gpx1`的GPIO控制器,我们可以从这个节点获取以下信息:
1. `gpio-controller`: 表示这个节点表示一个GPIO控制器。
2. `#gpio-cells=<2>`: 表示每个GPIO在使用时需要两个值(通常为引脚号和标志),这在GPIO消费者设备中用于指定引脚和设置。
3. `interrupt-controller`: 表示这个节点还是一个中断控制器。
4. `interrupt-parent=<&gic>`: 指定这个中断控制器的父中断控制器是`gic`(通常为全局中断控制器)。
5. `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>`: 这些值表示`gpx1`中断控制器管理的中断号,它们对应于父中断控制器(`gic`)中的中断号。这里列出了8个中断,分别是24、25、26、27、28、29、30和31。
6. `#interrupt-cells=<2>`: 表示每个中断需要两个值(通常为中断号和触发类型),这在中断消费者设备中用于指定中断和触发方式。
这个节点描述了一个既充当GPIO控制器又充当中断控制器的设备,你可以从中获取有关GPIO和中断配置的相关信息。
实例
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
/*
keys{//按键的节点信息
compatible="fs4412,fskey";//与驱动匹配用
interrupt-parent=<&gpx1>;//父中断名
interrupts=<1,2>,<2,2>;//第一个表示选择父中断器 上面的哪个中断线,第二个表示下降沿出发
};
*/
struct resource *key2_res;//创建key2_res的资源
struct resource *key3_res;
struct irqreturn_t fskey_handler(int irq,void *dev_id)
{
if(irq==key2_res->start)
printk("K2 pressed\n");
else
printk("k3 pressed\n");
return IRQ_HANDLER;//返回IRQ_ZONE 不是驱动管理的中断 IRQ_HANDLER驱动正常处理 IRQ_WAKE_THREAD需要唤醒一个内核线程
}
static int fskey_probe(struct platform_device *pdev)
{
int ret;
key2_res=platform_get_resource(pdev,IORESOURCE_IRQ,0);//获取到了key2_res的资源
key3_res=platform_get_resource(pdev,IORESOURCE_IRQ,1);
if(!key2_res||!key3_res)
{
ret=-ENOENT;
goto res_err;
}
//start 资源的开始I/O内存就是起始的内存地址,中断资源就是起始的中断号
ret=request_irq(key2_res->start,fskey_handler,key2_res->flags&IRQF_TRIGGER_MASK,"key2",NULL);
if(ret)
goto key2_err;
ret=request_irq(key3_res->start,fskey_handler,key2_res->flags&IRQF_TRIGGER_MASK,"key3",NULL);
if(ret)
goto key3_err;
key3_err:
free_irq(key2_res->start, NULL);
key2_err:
res_err:
return ret;
}
static int fskey_remove(struct platform_device *pdev)
{
free_irq(key3_res->start, NULL);
free_irq(key2_res->start, NULL);
return 0;
}
//用于描述设备树中与驱动程序兼容的设备。这个数组主要用于在设备树解析过程中,匹配设备和驱动
static const struct of_device_id fskey_of_matches[]={
{.compatible="fs4412,fskey",},
//当解析设备树时,如果内核找到一个与该驱动程序兼容的设备,
//它将调用驱动程序的 probe 函数来初始化设备。
{/* sentinel */},
};
MODULE_DEVICE_TABLE(of, fskey_of_matches);
//它在模块的二进制文件中添加一个特定的数据结构。这个数据结构用于在模块加载时通知内核与驱动
//程序兼容的设备。这样,在驱动程序作为模块加载到内核时,内核就可以知道与这个驱动程序兼容的设备。
struct platform_driver fskey_drv={
.driver={
.name="fskey",//先用设备树匹配 后用id匹配 最后用这个name匹配
.owner=THIS_MODULE,
.of_match_table=of_match_ptr(fskey_of_matches),//用于和struct of_device_id绑定
},
.probe=fskey_probe,
.remove=fskey_remove,
};
module_platform_driver(fskey_drv);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple device driver for Keys on FS4412 board");
1.为什么没有设备号和cdev?
这个驱动代码是一个按键驱动,它是一个典型的输入设备驱动。按键驱动与字符设备驱动有所不同,它们的关注点和实现方式有所差异。
按键驱动的主要任务是响应按键的按下和释放事件。在这个例子中,按键驱动只关心按下事件,并在中断处理函数fskey_handler
中进行处理。按键驱动并不涉及到字符设备驱动中的文件操作(如打开、关闭、读取、写入等),因此不需要设备号和cdev结构体。
字符设备驱动,如串口驱动或者块设备驱动,需要实现文件操作,因此需要设备号和cdev结构体。这样,当用户空间应用程序打开、关闭、读取或写入设备文件时,内核可以根据设备号找到对应的设备驱动,并调用驱动程序中实现的文件操作函数。
总之,这个按键驱动示例没有设备号和cdev结构体,是因为它是一个输入设备驱动,只关心按键事件,并不涉及到字符设备驱动中的文件操作。
按照传统分类,驱动类型主要分为字符设备驱动、块设备驱动和网络设备驱动。但是,在实际的驱动开发过程中,还有很多其他类型的设备驱动,比如输入设备驱动、GPIO设备驱动等。这些驱动类型并不完全符合上述三类驱动的定义。
按键驱动在这里更类似于一个输入设备驱动。虽然按键驱动可以被实现为字符设备驱动,但在这个示例中,它只关注按键事件,而没有实现字符设备驱动中的文件操作。因此,按键驱动在这个例子中并不是一个典型的字符设备驱动。
虽然Linux遵循“一切皆文件”的哲学,但在内核级别,对设备的操作并不完全依赖于设备文件。在这个按键驱动示例中,当按键事件发生时,中断处理程序(fskey_handler
)会被自动调用。这个处理程序并不依赖于设备文件,而是直接处理按键事件并输出相应的消息。
2.驱动中的.name
.name = "fskey"
是为 platform_driver 结构体中的 driver 成员设置名称。这个名称可以帮助内核在查找、加载和卸载驱动程序时进行识别。尽管在这个例子中,设备与驱动程序的匹配是通过 .of_match_table
成员进行的,但是为 driver 成员设置一个名称仍然是一个好的实践。
在某些情况下,.name
成员在查找和匹配设备时起到关键作用。例如,如果设备是静态定义的,而不是通过设备树进行匹配,那么内核就会使用 .name
字段来匹配设备和驱动程序。此外,这个名称也可以用于在调试或分析内核日志时,更容易地识别与特定驱动程序相关的信息。
3.struct of_device_id和MODULE_DEVICE_TABLE
在这个驱动程序中,struct of_device_id
结构体用于存储与设备树中的设备节点匹配的 compatible
字符串。MODULE_DEVICE_TABLE
宏用于在编译驱动程序时,将这个匹配信息添加到内核模块的元数据中。
当内核启动并解析设备树时,它会根据设备节点的 compatible
字符串来查找相应的驱动程序。在这个过程中,struct of_device_id
结构体和 MODULE_DEVICE_TABLE
宏起到关键作用。如果内核找到了匹配的驱动程序,它就会调用驱动程序的 probe
函数,对设备进行初始化和设置。
在设备热插拔的情况下,类似的过程也会发生。当一个新的设备被插入并被内核检测到时,内核会再次根据设备的 compatible
字符串查找匹配的驱动程序,并调用其 probe
函数。这样,驱动程序就能够在设备连接到开发板时,正确地执行和初始化设备。
.of_match_table=of_match_ptr(fskey_of_matches),将 struct of_device_id
结构体与驱动程序相关联。of_match_ptr(fskey_of_matches)