本篇分为设备树部分和API接口部分
设备树
想要使用中断,设备树中需要有两个属性:
interrupts // 表示要使用哪一个中断, 中断的触发类型等等。
interrupt-parent // 这个中断要接到哪一个设备去? 即父中断控制器是谁
父中断控制器有两种指定方法:
- 只有一个父中断设备
- 有多个父中断设备
1)、只有一个父中断设备
interrupt-parent = <&父设备标号>;
interrupts = <... ...>, <... ...>;
2)、有多个父设备节点
interrupts-extended = <&父设备标号 .....>, <... ... ...>;
通过不断往里跳可以发现 设备树查找有这两种方式
方法一
此方法中,由于查看父节点是靠 interrupt-parent实现的,所以此属性不可少.
方法二
不断跳就会发现这个循环
此方法中是通过属性中第一个元素找出的父节点,所以无需指定中断父节点
上述属性用多少个u32表示
由它的父中断控制器来描述,在父中断控制器中, 至少有2个属性:
interrupt-controller; // 表示自己是一个中断控制器
#interrupt-cells // 表示自己的子设备里应该用几个U32的数据来描述中断
如此图,此节点是父节点中断控制器,他的子节点用2个u32 描述中断。
如何找到一个节点的父中断控制器
一般在描述子中断节点中都会有一个属性interrupt-parent,由此属性描述。
如果子中断节点中没有此属性,需要查看此节点的父节点,一级一级往上直到父节点中出现interrupt-parent。
API函数
获取中断号
cpp
int platform_get_irq(struct platform_device *dev, unsigned int num)
参数一:平台设备,
参数二:设备树里第几个中断引脚,一般给0
返回值:中断号
ps:需要在设备树里指定。
cpp
int gpio_to_irq(unsigned gpio)
参数:io口号
返回值:中断号
ps:此函数无需在设备树里指定中断信息,直接调用可直接在根目录下的gpio中断控制器里申请返回一个中断号,一旦在设备树里指定,也可获得中断号,不过与不指定的中断号可能存在差异,不过不影响,都可以实现中断。
注册中断
cpp
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
参数一:中断号
参数二:中断回调函数
参数三:在什么边沿触发
参数四:标签,可随意
参数五:中断回调函数传入的参数
cpp
int
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
此函数也可以注册中断,建议使用这个,参数跟上面函数大差不差不在重复介绍。
devm_request_irq和request_irq区别在于前者是申请的内核"managed"资源,不需要自己手动释放,会自动回收资源,而后者需要手动调用free_irq来释放中断资源
中断回调函数类型
flag类型
注销函数
cpp
const void *free_irq(unsigned int irq, void *dev_id)
参数一:中断号
参数二:与注册函数传入参数一致。
cpp
void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
参数与上述函数一致,注意配套使用
使用中断后,我们就可以在按下按键后就可以收到一次消息,但是由于上层函数在while循环里一直执行,每调用一次read就会调用底层内核的read,不会阻塞,所以cpu占用率百分之百,此时这就是个病毒驱动。但是我们在学习系统编程的时候,read是一个阻塞函数,当然那时内核实现的他的阻塞,所以我们就需要在内核里把read阻塞,当按键按下进入中断再让read解除阻塞,减少cpu的占用率。
等待队列
等待队列是目前内核最常用的阻塞同步机制之一
可以在内核层产生一个阻塞( 等于放弃 CPU 的! )
while(1); ->这种叫死等 -> 效率最低 占用 CPU 最高一种等待机制
所有的系统的等待机制都绝非死等-> 都是放弃 CPU ,后续被换唤醒机制!
在 FreeRTOS 大家应该接触过类似的概念
等待队列非常类似之前大家在系统层面学习 线程的同步和互斥:条件变量!
等待队列除了创建之外就两个函数,也是两个功能:
一个功能叫做: 阻塞 -> 调用后立刻产生挂起
wait_event
唤醒->调用会唤醒之前挂起进程 / 程序
wake_up
内核层的阻塞会引起上层的阻塞!
举个例子:我在 open-> 调用 " 阻塞 " -> 上层 open 也会阻塞
我在 read->调用 " 阻塞 " --> 上层 read 也会阻塞
等待队列的创建:(宏定义函数->用空间换时间)
cpp
DECLARE_WAIT_QUEUE_HEAD(name);
cpp
#define DECLARE_WAIT_QUEUE_HEAD(name) \
struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
// Expands to
struct wait_queue_head queue = { . lock = ( spinlock_t ) { { . rlock = { . raw_lock = { { . val = { ( 0 ) } } } , } } } , . head = { & ( queue ) . head , & ( queue ) . head } }
此函数为宏函数, 声明的时候已经帮我们初始化好了,括号里面是我们创建的变量名字
阻塞函数
cpp
wait_event_interruptible(wq_head, condition)
此函数也也为宏函数,可以看出,当condition为1时,不阻塞,为0时阻塞,另外第二个参数必须为变量,如果填0,则当唤醒函数执行后不会解除阻塞。wait_event_interruptible(以及wait_event打头的其他变体)是Linux的wait queue机制提供的线程同步接口(应先cond=0,把cond传进去)另外也可以看出,参数一直接传入变量名即可
唤醒函数
cpp
wake_up_interruptible(x)
此函数也为宏函数, 跳进去可发现,参数传入变量名地址。调用完该函数cond设置为1.
整体代码
cpp
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/interrupt.h"
#include "linux/irq.h"
#include "linux/module.h"
#include "linux/of_device.h"
#include "linux/of_gpio.h"
#include "linux/platform_device.h"
#include "linux/printk.h"
#include "linux/types.h"
#include "linux/uaccess.h"
#include "linux/wait.h"
#include "linux/zconf.h"
uint32_t pin;
dev_t dev_num;
struct cdev *cdev;
struct class *cls;
struct device * dev;
uint8_t cond;
DECLARE_WAIT_QUEUE_HEAD(queue);
uint8_t val;
irqreturn_t fun(int i, void * a)
{
val=gpio_get_value(pin);
printk("%d\r\n",val);
wake_up_interruptible(&queue);
cond=1;
return 0;
}
static ssize_t read(struct file *f, char __user *b, size_t s, loff_t *offt)
{
cond=0;
wait_event_interruptible(queue,cond);
int a=copy_to_user(b,&val,1);
if(a)
{
}
return 0;
}
static int open(struct inode *i, struct file *f)
{
int ret=devm_request_irq(dev, gpio_to_irq(pin),fun,IRQ_TYPE_EDGE_FALLING,"key", NULL);
printk("%d\r\n",ret);
return ret;
}
static int close(struct inode *i, struct file *f)
{
devm_free_irq(dev,gpio_to_irq(pin),NULL);
return 0;
}
struct file_operations fops={
.owner=THIS_MODULE,
.read=read,
.open=open,
.release=close,
};
int probe(struct platform_device *d)
{
// DECLARE_WAIT_QUEUE_HEAD(name);
// wait_event_interruptible(,);
// wake_up_interruptible(x)
platform_get_irq();
//free_irq();
// devm_free_irq();
dev=&d->dev;
pin=of_get_named_gpio(d->dev.of_node,"key_pin",0);
printk("%d\r\n",pin);
printk("%d\r\n", platform_get_irq(d,0));
printk("irq_num=%d", gpio_to_irq(pin));
gpio_request(pin,"key");
gpio_direction_input(pin);
// devm_request_irq(&d->dev, gpio_to_irq(pin),fun,IRQ_TYPE_EDGE_FALLING,"key", NULL);
alloc_chrdev_region(&dev_num, 0, 1,"key");
cdev=cdev_alloc();
cdev->ops=&fops;
cdev_add(cdev,dev_num,1);
cls=class_create(THIS_MODULE, "key");
device_create(cls, NULL,dev_num,NULL,"key");
return 0;
}
int remove(struct platform_device *d)
{
return 0;
}
static struct of_device_id match={
.compatible="key",
};
static struct platform_driver driver={
.probe=probe,
.remove=remove,
.driver={
.name="key",
.of_match_table=&match,
},
};
static int __init start(void)
{
platform_driver_register(&driver);
printk(KERN_INFO "Hello, world!\n");
return 0;
}
static void __exit stop(void)
{
platform_driver_unregister(&driver);
printk(KERN_INFO "Goodbye, world!\n");
}
module_init(start);
module_exit(stop);
MODULE_LICENSE("GPL");