Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

中断基础

什么是中断?

在这里插入图片描述

中断上下文

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Linux中断现在不可以嵌套

设备书中的中断节点和相关函数

设备树中的中断节点

在这里插入图片描述
设备树中断的参考绑定文档:

/Documentation/devicetree/bindings/arm/gic.txt

在这里插入图片描述
在这里插入图片描述
比如,在imx6qdl.dtsi文件,可以看到中断控制器的信息
在这里插入图片描述
这个是不需要我们进行配置的,只需要会调用就可以了

继续往下看,就可以看到子中断控制器,这个是gpio1的中断控制器

在这里插入图片描述
这里是gpio1的中断属性信息,这里我们可以看到是#interrupt-cells = <2>;,有两个cell,所以,有两个中断,一组gpio有32个引脚,那么这里就会分成两组,比如0-15就是中断号66,剩下的16-31中断号就是67

interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
			<0 67 IRQ_TYPE_LEVEL_HIGH>;

下面通过实际的代码了解一下中断的使用。

首先,在我们的imx6qdl-sabresd.dtsi文件中, 看到了这么一段代码,显然使用了中断。
在这里插入图片描述

代码中,使用了interrupt-parent和interrupts属性来描述中断,interrupt-parent属性值是gpio1,也就是描述了使用了哪一个中断控制器。

interrupts属性设置的是中断源,这里有两个cells,其中18就是具体的中断号,也就是我们对应的引脚号,后面的8就是我们用到的中断的类型。

关于触发类型,有更加规范的写法,通过宏定义已经被定义好了。在如图所示的文件目录下
在这里插入图片描述

#define IRQ_TYPE_NONE		0
#define IRQ_TYPE_EDGE_RISING	1
#define IRQ_TYPE_EDGE_FALLING	2
#define IRQ_TYPE_EDGE_BOTH	(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH	4
#define IRQ_TYPE_LEVEL_LOW	8

在这里插入图片描述
但是我们后面有相关的函数,可以直接获取到gpio的中断号,所以,使用gpio的话不需要步骤二也是可以的,但一般都写上。

中断相关函数

获取中断号相关函数

在这里插入图片描述
在这里插入图片描述

申请中断函数

在这里插入图片描述
在这里插入图片描述

#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

在这里插入图片描述

中断处理函数

在这里插入图片描述
关于中断处理函数的返回值:中断程序的返回值是一个特殊类型—irqreturn_t。但是中断程序的返回值却只有两个—IRQ_NONE和IRQ_HANDLED。

IRQ_NONE means we didn’t handle it.中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号.此时返回次值
IRQ_HANDLED means that we did have a valid interrupt and handled it.接收到了准确的中断信号,并且作了相应正确的处理
IRQ_RETVAL(x) selects on the two depending on x being non-zero (for handled)

在实际中,如图所示操作后
在这里插入图片描述
在编译过程中会出现警告
在这里插入图片描述

free_irq函数

在这里插入图片描述

按键中断实验(gpio_to_irq函数获取中断号)

配置设备树

把原设备树文件中的按键相关的代码注释掉,然后写一个我们测试用的设备树节点
在这里插入图片描述
因为我们这个是gpio的中断,所以interrupt-parent和interrupts属性信息可以不写,直接在驱动函数中用相关的of函数获取中断编号就可以了。

	test_key {
		compatible = "keys";						// 设置compatible属性
		pinctrl-names = "default";					// 配置pinctrl子系统
		pinctrl-0 = <&pinctrl_gpio_keys>;
		gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;		// 描述管脚
	};

配置完之后,编译

make dtbs

启动并且烧录设备树到我们的开发板,可以看到我们自己定义的节点,信息都是匹配的。

在这里插入图片描述

驱动编写

这里继续复用之前实验的代码,在原有的代码基础上进行修改,整体的思路就是

  1. of_find_node_by_path查找指定路径的节点
  2. of_get_named_gpio获取设备树编号
  3. gpio_direction_input方向设置为输入模式
  4. gpio_to_irq获取中断号
  5. request_irq请求中断,带入中断的处理函数作为参数

首先说一下,这个driver驱动代码是有问题的,但是正好做一个报错的方案解决

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>

#include <linux/irq.h>
#include <linux/irqreturn.h>

struct device_node  *test_device_node;

int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号

irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{
    printk("test_key_handle ok!!!\n");
    return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}


/*probe函数*/
int beep_probe(struct platform_device *pdev){
    
    int ret = 0;

    printk("beep_probe ok!!!\n");
    
    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_key
    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
    if(gpio_name < 0) {
        printk("of_get_named_gpio error!!!\n");
        return -1;
    }else{
        printk("of_get_named_gpio ok!!!\n");
    }
    
    gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式
   
    irq = gpio_to_irq(gpio_name);                                       // 获取中断号,参数是gpio编号
    printk("irq is %d\n", irq);
    /* request_irq
     * 第一个参数:中断号,
     * 第二个参数:中断处理函数,
     * 第三个参数:中断标志(边沿触发方式),
     * 第四个函数:中断名字,
     * 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
     */
    ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  
    if(ret < 0) {
        printk("request_irq failed!!!\n");
        return -1;
    }else{
        printk("request_irq successful!!!\n");
    }
    
    return 0;
}

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    free_irq(irq, NULL);

    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

错误排查

上面的driver.c代码编译之后,发送到开发板,然后运行,发现开发板给我们报错。

那么来查看一下这个错误
在这里插入图片描述
这么一大串的错误,应该怎么办呢?

oops信息

其实错误信息打印的还是比较明确的

//无法处理内核页面请求的虚拟地址
[   66.286951] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[   66.295109] pgd = a966c000
[   66.297849] [00000000] *pgd=38b1d831, *pte=00000000, *ppte=00000000
//内部错误oops
[   66.304246] Internal error: Oops: 17 [#1] PREEMPT SMP ARM
//表示内部错误发生在driver.ko驱动模块里
[   66.309659] Modules linked in: driver(O+)
[   66.313736] CPU: 2 PID: 848 Comm: insmod Tainted: G           O    4.1.15 #1
[   66.320799] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[   66.327343] task: a8e604c0 ti: a924c000 task.ti: a924c000
//PC值:程序运行成功的最后一次地址,位于first_drv_open()函数里,偏移值0x4,该函数总大小0x34
[   66.332765] PC is at strcmp+0x4/0x34
//LR值
[   66.336371] LR is at kset_find_obj+0x3c/0xa4

/*发生错误时的各个寄存器值*/
[   66.340662] pc : [<802b6184>]    lr : [<802b0c30>]    psr: a0070013
[   66.340662] sp : a924ddf8  ip : 00000000  fp : 00000124
[   66.352156] r10: 5640699c  r9 : 00000001  r8 : 00000000
[   66.357396] r7 : a803b688  r6 : 00000000  r5 : a803b680  r4 : a8148000
[   66.363939] r3 : 00000043  r2 : 0000013f  r1 : 00000000  r0 : a8144dc1
[   66.370484] Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
[   66.377633] Control: 10c53c7d  Table: 3966c04a  DAC: 00000015
//发生错误时,进程名称为insmod
[   66.383394] Process insmod (pid: 848, stack limit = 0xa924c210)
//栈信息
[   66.389329] Stack: (0xa924ddf8 to 0xa924e000)
[   66.393703] dde0:                                                       7f000564 80be2860
[   66.401904] de00: a9bf9440 7f000158 00000000 8038a570 7f000564 8038a5f4 80be2860 80be2860
[   66.410105] de20: a9bf9440 7f00017c 80be2860 80009718 abc7cac0 800e4224 00000002 a92d6500
[   66.418305] de40: 8040003e 00000001 00010000 800b1eb8 00000002 8040003e abc6d300 abc7cac0
[   66.426505] de60: 80bdbafc 80bd8430 a8001f00 80bdbafc 00000124 800e59fc 7f0005a8 00000001
[   66.434706] de80: 7f0005a8 00000001 a9bf9c00 7f0005a8 a9bf9680 808229e0 7f0005a8 a9bf9680
[   66.442894] dea0: a924df58 00000001 a9bf9688 80095154 7f0005b4 00007fff 80092194 00000000
[   66.451075] dec0: 00000000 7f0005f0 00000000 7f0006f0 c0e648a4 7f0005b4 00000000 80830a34
[   66.459257] dee0: c0e63000 000018f4 00001924 00000000 00000010 00000000 00000000 00000000
[   66.467438] df00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[   66.475619] df20: 00000000 00000000 00000000 00000000 00000010 00000000 00000003 00027bf4
[   66.483800] df40: 0000017b 8000f604 a924c000 00000000 00000000 800955b8 c0e63000 000018f4
[   66.491981] df60: c0e64584 c0e644b0 c0e63fb0 0000070c 0000098c 00000000 00000000 00000000
[   66.500163] df80: 00000014 00000015 0000000d 0000000a 00000008 00000000 0028c008 00000000
[   66.508345] dfa0: 00000000 8000f480 0028c008 00000000 00000003 00027bf4 00000000 00000000
[   66.516527] dfc0: 0028c008 00000000 00000000 0000017b 00000000 00000002 76fca000 00000000
[   66.524708] dfe0: 7eb00c18 7eb00c08 0001f2e0 76f08e90 60070010 00000003 00000000 00000000
//回溯信息
[   66.532898] [<802b6184>] (strcmp) from [<802b0c30>] (kset_find_obj+0x3c/0xa4)
[   66.540045] [<802b0c30>] (kset_find_obj) from [<8038a570>] (driver_find+0x14/0x30)
[   66.547620] [<8038a570>] (driver_find) from [<8038a5f4>] (driver_register+0x68/0xf8)
[   66.555373] [<8038a5f4>] (driver_register) from [<7f00017c>] (beep_driver_init+0x24/0x54 [driver])
[   66.564344] [<7f00017c>] (beep_driver_init [driver]) from [<80009718>] (do_one_initcall+0x8c/0x1d4)
[   66.573405] [<80009718>] (do_one_initcall) from [<808229e0>] (do_init_module+0x5c/0x1a8)
[   66.581510] [<808229e0>] (do_init_module) from [<80095154>] (load_module+0x1ba8/0x1e50)
[   66.589523] [<80095154>] (load_module) from [<800955b8>] (SyS_finit_module+0x80/0x90)
[   66.597364] [<800955b8>] (SyS_finit_module) from [<8000f480>] (ret_fast_syscall+0x0/0x3c)
[   66.605549] Code: e5e32001 1afffffb e12fff1e e4d03001 (e4d12001)
[   66.611683] ---[ end trace 44e08424ce7c760b ]---

Message from syslogd@imx6qsab[   66.616394] note: insmod[848] exited with preempt_count 1
resd at Wed Feb 24 11:05:14 2021 ...

Segmentation fault

通过回溯信息,我们得到以下的信息:

SyS_finit_module -> load_module -> do_init_module -> do_one_initcall -> beep_driver_init -> driver_register -> driver_find -> kset_find_obj -> strcmp

所以我们的错误是出现在了驱动这块

回到我们的代码中,通过对驱动设置相关的模块检查,发现是代码的编写过程中,少了两部分的代码

结果验证

补齐两行相关的代码之后,我们在进行模块的安装
在这里插入图片描述
正常加载模块后,操作我们的按键,就可以发现,我们预设的信息正常打印,试验成功。
在这里插入图片描述

查看中断信息

最后再验证一下中断的信息,是否正确,前面我们已经打印出来了irq is 132

对应到

cat /proc/interrupts

在这里插入图片描述
看到了test_key,这个是因为这段代码中的命名
在这里插入图片描述
继续查看中断信息

cd /proc/irq

可以看到很多的中断号
在这里插入图片描述
我们查找这次测试的中断号132,打开
在这里插入图片描述

cat spurious

在这里插入图片描述
可以看到一个count 11这个值,这个值对应的是我们触发了多少次的中断,我们刚才正好是按键了11次

按键中断实验(interrupt-parent和interrupts属性信息获取中断号)

配置设备树

跟上面的一样,需要在设备树里添加信息,这里主要就是两行代码的添加。
在这里插入图片描述

	test_key {
		compatible = "keys";						// 设置compatible属性
		pinctrl-names = "default";					// 配置pinctrl子系统
		pinctrl-0 = <&pinctrl_gpio_keys>;
		gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;		// 描述管脚

		interrupt-parent = <&gpio3>;
		interrupts = <29 IRQ_TYPE_EDGE_BOTH>;
	};

烧录到开发板,可以看到有了中断信息。
在这里插入图片描述

驱动编写

这里只是修改了中断号的获取方式,所以步骤与之前基本一致

  1. of_find_node_by_path查找指定路径的节点
  2. of_get_named_gpio获取设备树编号
  3. gpio_direction_input方向设置为输入模式
  4. irq_of_parse_and_map获取中断号
  5. request_irq请求中断,带入中断的处理函数作为参数
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>

struct device_node  *test_device_node;

int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号

irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{
    printk("test_key_handle ok!!!\n");
    return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}


/*probe函数*/
int beep_probe(struct platform_device *pdev){
    
    int ret = 0;

    printk("beep_probe ok!!!\n");
    
    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_key
    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
    if(gpio_name < 0) {
        printk("of_get_named_gpio error!!!\n");
        return -1;
    }else{
        printk("of_get_named_gpio ok!!!\n");
    }
    
    gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式
   
    // irq = gpio_to_irq(gpio_name);                                       // 通过gpio函数获取中断号,参数是gpio编号
   
    /* irq_of_parse_and_map,通过设备树中interrupts获取中断号
     * 第一个参数:设备树节点
     * 第二个参数:索引值,这里只有一个,所以是0
     * */
    irq = irq_of_parse_and_map(test_device_node, 0);
   
    printk("irq is %d\n", irq);
    /* request_irq
     * 第一个参数:中断号,
     * 第二个参数:中断处理函数,
     * 第三个参数:中断标志(边沿触发方式),
     * 第四个函数:中断名字,
     * 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
     */
    ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  
    if(ret < 0) {
        printk("request_irq failed!!!\n");
        return -1;
    }else{
        printk("request_irq successful!!!\n");
    }
    
    return 0;
}

const struct platform_device_id beep_id_table = {
    .name = "keys",
};

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "keys",
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
    .id_table = &beep_id_table,
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    free_irq(irq, NULL);

    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

结果验证

在这里插入图片描述
按键之后也可以顺利进入中断。
在这里插入图片描述

tasklet相关知识

什么是tasklet

在这里插入图片描述

怎么使用tasklet来设计中断下文

在这里插入图片描述
在这里插入图片描述

tasklet定义

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

tasklet相关函数

tasklet_schedule

在这里插入图片描述

tasklet_init函数

在这里插入图片描述

tasklet_kill函数

在这里插入图片描述

使用tasklet设计中断步骤

在这里插入图片描述

tasklet驱动程序设计

driver.c源码

遵循上面的步骤,添加相应的代码就可以了

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>

struct device_node  *test_device_node;

struct tasklet_struct test_key_tasklet_struct;

int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号

/*打印一百次,模拟一个较长实践的任务*/
void test(unsigned long data)
{
    int i = 100;
    while(i--){
        printk("test_key is %d\n",i);
    }
}

/*中断上文*/
irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{
    printk("start\n");

    /*启动中断下文*/
    tasklet_schedule(&test_key_tasklet_struct);
    printk("tasklet_schedule end!!!\n");

    return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}


/*probe函数*/
int beep_probe(struct platform_device *pdev){
    
    int ret = 0;

    printk("beep_probe ok!!!\n");
    
    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_key
    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
    if(gpio_name < 0) {
        printk("of_get_named_gpio error!!!\n");
        return -1;
    }else{
        printk("of_get_named_gpio ok!!!\n");
    }
    
    gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式
   
    // irq = gpio_to_irq(gpio_name);                                       // 通过gpio函数获取中断号,参数是gpio编号
   
    /* irq_of_parse_and_map,通过设备树中interrupts获取中断号
     * 第一个参数:设备树节点
     * 第二个参数:索引值,这里只有一个,所以是0
     * */
    irq = irq_of_parse_and_map(test_device_node, 0);
   
    printk("irq is %d\n", irq);

    /* request_irq申请中断
     * 第一个参数:中断号,
     * 第二个参数:中断处理函数,
     * 第三个参数:中断标志(边沿触发方式),
     * 第四个函数:中断名字,
     * 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
     */
    ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  
    if(ret < 0) {
        printk("request_irq failed!!!\n");
        return -1;
    }else{
        printk("request_irq successful!!!\n");
    }

    /* tasklet_init初始化
     * 第一个参数:tasklet_struct结构体
     * 第二个参数:中断下文执行的函数
     * 第三个参数:传递给中断下文的参数
     */
    tasklet_init(&test_key_tasklet_struct, test, 0);
    
    return 0;
}

const struct platform_device_id beep_id_table = {
    .name = "keys",
};

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "keys",
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
    .id_table = &beep_id_table,
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    free_irq(irq, NULL);
    tasklet_kill(&test_key_tasklet_struct);

    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

实验结果

在这里插入图片描述
我们也可以传入参数,只需要把我们之前设置的i=100,修改成如下所示代码
在这里插入图片描述
在这里插入图片描述
可以得到正确的实验结果,循环计数了一百次

在这里插入图片描述
到这里可能会有一个问题,就是为什么我们在test_key_handle里面打印的开始和结束的信息,会一起显示
在这里插入图片描述
这是因为tasklet_schedule绑定的函数不会立即执行,而是等我们的中断结束后的不确定时间,再开始执行我们的中断下文函数。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的Linux SPI中断处理驱动代码示例: ```c #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/spi/spi.h> #include <linux/interrupt.h> static struct spi_device *spi_device; static struct tasklet_struct spi_tasklet; // SPI中断处理函数 static irqreturn_t spi_irq_handler(int irq, void *dev_id) { // 禁用SPI中断 spi_disable(spi_device); // 启动任务队列,以异步方式处理数据 tasklet_schedule(&spi_tasklet); return IRQ_HANDLED; } // SPI中断处理任务函数 static void spi_tasklet_handler(unsigned long data) { // 处理SPI数据 // 重新启用SPI中断 spi_enable(spi_device); } static int __init spi_interrupt_init(void) { int err; // 分配并初始化SPI设备 spi_device = spi_alloc_device(...); // 注册SPI设备 err = spi_add_device(spi_device); if (err < 0) { printk(KERN_ERR "Failed to register SPI device\n"); return err; } // 初始化SPI中断处理任务队列 tasklet_init(&spi_tasklet, spi_tasklet_handler, 0); // 注册中断处理函数 err = request_irq(spi_device->irq, spi_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "spi", NULL); if (err < 0) { printk(KERN_ERR "Failed to request SPI IRQ\n"); spi_unregister_device(spi_device); return err; } return 0; } static void __exit spi_interrupt_exit(void) { // 释放中断 free_irq(spi_device->irq, NULL); // 停止任务队列 tasklet_kill(&spi_tasklet); // 注销SPI设备 spi_unregister_device(spi_device); } module_init(spi_interrupt_init); module_exit(spi_interrupt_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Linux SPI Interrupt Handling Driver"); ``` 需要根据实际情况修改其中的参数和逻辑。可以参考Linux内核源码中的spi-bcm2835aux.c等SPI驱动代码进行学习和调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值