Linux 驱动实例 (输入子系统 按键中断)

中断按键驱动

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)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值