设备树节点以及相关函数
一. 设备树中的中断节点。
如果一个设备需要用到中断功能, 开发人员就需要在设备树中配置好中断属性信息, 因为 设备树是用来描述硬件信息的, 然后 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;
}