迅为嵌入式linux驱动开发笔记(六)— 中断与中断下文

设备树节点以及相关函数

一. 设备树中的中断节点。

如果一个设备需要用到中断功能, 开发人员就需要在设备树中配置好中断属性信息, 因为 设备树是用来描述硬件信息的, 然后 Linux 内核通过设备树配置的中断属性来配置中断功能。 设备树中断的参考绑定文档:

Documentation/devicetree/bindings/arm/gic.txt

中断实际上是非常复杂的, 但是作为开发人员, 我们只需要关系怎么在设备树中指定中断, 怎么在代码中获得中断就可以。 其他的事情, 比如设备树中的中断控制器, 这些都是由原厂的 BSP 工程师帮我们写好了, 我们不需要来修改他。 比如, 在 imx6ull. dtsi 文件, 其中的 inc 节点就是 imx6ull 的中断控制器节点, 如下图 所示:
在这里插入图片描述

比如, 对于 GPIO 来说, GPIO 的节点也可以作为中断控制器, 在 imx6ull.dtsi 文件中 GPIO1 的节点内容如下图所示:
在这里插入图片描述
在设备树里面描述一个外设的中断节点, 例:

key { 
		#address-cells = <1>; 
		#size-cells = <1>; 
		compatible = "key"; //匹配名称
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key>; 
		key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */ 
		interrupt-parent = <&gpio1>; 
		interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
		 status = "okay"
 }

在这个例子中, 我们先使用 pinctrl 和 gpio 子系统把这个引脚设置为了 gpio 功能, 因为在使用中断的时候需要把引脚设置成输入。然后使用 interrupt-parent 和 interrupts 属性来描述中断。 interrupt-parent 的属性值是 gpio1,也就是要使用 gpio1这个中断控制器, 为什么是 gpio1 呢,因为引脚使用的是 gpio1 里面的io18,所以使用的是gpio1这个中断控制器。interrupts 属性设置的是中断源,为什么里面是俩个cells 呢, 因为在 gpio1 这个中断控制器里面#interrupt-cells 的值为 2, 如下图所示:

在这里插入图片描述

例子中的第一个 cells 的 18 表示 GPIO1 组的 18 号 IO。 IRQ_TYPE_EDGE_BOTH 表示 上升沿和下降沿同时有效。 IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中, 定义如下:
在这里插入图片描述
所以在设备树里面配置中断的时候只需要俩个步骤即可, 第一个步骤是把管脚设置为 gpio 功能。 第二个步骤是使用 interrupt-parent 和 interrupts 属性来描述中断。

二. 中断相关函数

<1>获取中断号相关函数
编写驱动的时候需要用到中断号,每一个中断都有中断号, 用到中断号,中断信息已经写到了设备树里面, 因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

参数: dev 设备节点。
index: 索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值: 中断号。
如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:

 int gpio_to_irq(unsigned int gpio)

参数: gpio: 要获取的 GPIO 编号。
返回值: GPIO 对应的中断号。

<2>申请中断函数 同 GPIO 一样,在 Linux 内核里面,如果要使用某个中断也是需要申请的,申请中断使用的函数是 request_irq函数原型:

 int request_irq( unsigned int irq, i rq_handler_t handler, unsigned long flags, const char *name, void *dev) 

参数:
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志。
中断标识可以在文件 include/linux/interrupt.h 里面查看所有的中断标志, 介绍几个常用的中断标志,如下图所示:
在这里插入图片描述name: 中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断, 一 般 情 况 下将 dev 设置为设备 结构体,dev会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值:0 中断申请成功, 其他负值中断申请失败,如果返回-EBUSY的话表示中断已经被申请了。

< 3 > 中断处理函数
使用 request_irq 函数申请中断的时候需要设置中断处理函数, 中断处理函数 格式 如下所示:第一个参数是要中断处理函数要相应的中断号。 第二个参数是一个指 向 void 的指 针 ,也就是个通用 指 针 , 需要 与 request_irq 函数的 dev 参数 保持 一 致 。 用于区分 共享 中断的不同设备, dev 也可以指 向 设备数 据结构 。 中断处理函数的返回值为 i rqreturn_t 类 型, irqreturn_t 类 型定义如下所示:

enum irqreturn { 
		IRQ_NONE = (0 << 0), 
		IRQ_HANDLED = (1 << 0), 
		IRQ_WAKE_THREAD = (1 << 1), 
}; 
typedef enum irqreturn irqreturn_t;

可以看 出 irqreturn _t 是个枚举类 型, 一共 有三种返回值。一般中断服务函数返回值使用如下 形式 :

< 4 >free_irq 函数
中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享 的,那么 free_irq 会删除中断处理函数并且禁止中断。free_irq 函数原型如下所示:

void free_irq(unsigned int irq,void *dev)

参数:
irq:要释放的中断。
dev: 如果中断设置为 共享( IRQF_SHARED ) 的话, 此参数用来区分具体的中断。共享中断只有在 释放最 后中断处理函数的时候才会被禁止掉。
返回值: 无

按键中断实验

包含头文件

#include <linux/interrupt.h>

修改设备树信息

test_key{
	compatible = "keys";
	pinctrl-names = "default";
	pintrl-0=<&pinctrl_gpio_keys>;//通过宏 把管脚设置为gpio功能
	gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;

}

修改驱动匹配函数

const struct of_device_id of_match_table[] = 
{
    {.compatible = "keys"},
    {},
};

在beep_probe添加如下的代码:

int beep_probe(struct platform_device *pdev){

    /*查找我们要查找的节点*/
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL)
    {
        printk("of_find_by_path is erron\n");
        return -1;
    }    //获取gpio的节点
    
    gpio_num = of_get_named_gpio(test_device_node,"gpios",0);//获取gpio
    if (gpio_num<0)
    {    
        printk("of_get_named_gpio is erron\n");
        return -1;
    }
    
    gpio_direction_input(gpio_num);//设置方向为输入

    irq = gpio_to_irq(gpio_num);//获取中断号

    printk("irq is %d\n", irq);

    ret = request_irq(irq,test_key,IRQF_TRIGGER_RISING,"test_key",NULL);
    if (ret<0)
    {
        printk("request_irq is erron\n");
        return -1;
    }
    return 0;
}

烧入驱动:cat /proc/interrupts 查看中断是否加载完成。
cat /proc/irq/48/spurious 查看中断次数。

方法2:修改设备树代码

驱动设备树代码:

test_key{
	compatible = "keys";
	pinctrl-names = "default";
	pintrl-0=<&pinctrl_gpio_keys>;//通过宏 把管脚设置为gpio功能
	gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
//方法2 :在设备树上修改代码
	interrupt-parent = <&gpio1>;
	interrupts =<18 IRQ_TYPE_EDGE_BOTH>;/*FALLING RISING*/	
}

包含头文件

#include<linux/of_irq.h>

驱动代码:

int beep_probe(struct platform_device *pdev){

    /*查找我们要查找的节点*/
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL)
    {
        printk("of_find_by_path is erron\n");
        return -1;
    }    //获取gpio的节点
    
     /*获取GPIO的编号*/
    gpio_num = of_get_named_gpio(test_device_node,"gpios",0);//获取gpio
    if (gpio_num<0)
    {    
        printk("of_get_named_gpio is erron\n");
        return -1;
    } 

    /*设置gpio的方向*/
    gpio_direction_input(gpio_num);//设置方向为输入

    //irq = gpio_to_irq(gpio_num);//获取中断号

    /*获取irq的编号*/
    irq = irq_of_parse_and_map(test_device_node,0);//第二种获取中断号的方式,索引号为0

    printk("irq is %d\n", irq);
    /*申请中断*/
    ret = request_irq(irq,test_key,IRQF_TRIGGER_RISING,"test_key",NULL);
    if (ret<0)
    {
        printk("request_irq is erron\n");
        return -1;
    }
    return 0;
}

中断下文之tasklet

一、tasklet 相关知识点

1、什么是 tasklet ?
tasklet 是中断处理中断下文常用的一种方法,tasklet 是一种特殊的软中断。 处理中断下文的机制还有工作队列和软中断。
2、怎么使用 tasklet 来设计中断下文?
框图:
在这里插入图片描述
Linux 把中断分成俩个部分,一个是上半部分,一个是下半部分,在上半部分我们只 处理紧急的事情,同时可以调用 tasklet 来启动中断下文,比较耗时间的就要放到下文来 处理,调用 tasklet 以后, tasklet 绑定的函数并不会立马执行,而是出中断以后,经过一 个很短的不确定时间在来执行。
3、tasklet 定义
tasklet 由 tasklet_struct 结构表示,每个结构体单独代表一个 tasklet , 在<linux/interrupt.h> 中定义为:

struct tasklet_struct { 
		struct tasklet_struct *next; 
		unsigned long state; 
		atomic_t count; 
		void (*func)(unsigned long);
		unsigned long data; 
}; 

1.next : 链表中的下一个 tasklet , 方便管理和设置 tasklet;
2.state: tasklet 的状态
3.count : 表示 tasklet 是否处在激活状态,如果是 0,就处在激活状态,如果非0 ,就处在非激活状态
4.void (*func)(unsigned long) :结构体中的 func 成员是 tasklet 的绑定函数,data 是它唯 一的参数。
5.date : 函数执行的时候传递的参数

二、tasklet 相关函数

<1>tasklet_schedule 函数
作用:调度 tasklet
函数原型:

void tasklet_schedule(struct tasklet_struct *t) 

参数:指向 tasklet_struct 结构的指针。

<2>tasklet_init 函数
作用:动态初始化 tasklet (推荐)
函数原型:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); 

参数:
*t : 指向 tasklet_struct 结构的指针。
func : tasklet 绑定的函数。
data : 函数执行的时候传递的参数。

<3>tasklet_kill 函数
功能:删除一个 tasklet
函数原型: tasklet_kill(struct tasklet_struct *t)
参数:指向 tasklet_struct 结构的指针
注意:这个函数会等待 tasklet 执行完毕,然后再将它移除。 该函数可能会引起休眠,所以要 禁止在中断上下文中使用。
三、使用 tasklet 设计中断下文步骤
步骤一:定义一个 tasklet 结构体
步骤二:动态初始化 tasklet
步骤三:编写 tasklet 绑定的函数
步骤四:在中断上文调用 tasklet
步骤五:卸载模块的时候删除 taskle

实践课

初始化结构体:

struct tasklet_struct key_test;

在probe函数中初始化:tasklet_init

   //初始化
    tasklet_init(&key_test,test,0);

中断下文工作内容:

void test(unsigned long data){
    int i = 100;
    while(i--)
    printk("test_key is %d\n",i);
}

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

    tasklet_schedule(&key_test); //启动中断下文

    printk("end\n");

    return IRQ_HANDLED;
}
  • 0
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梅山剑客

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值