linux驱动--中断等待队列
1:中断的定义
中断是计算机科学中的一个术语,指的是计算机在执行程序的过程中,由于某些紧急事件的发生,暂时中止当前程序的运行,转而去处理更为紧急的任务。处理完这些紧急任务后,计算机会返回到被中断的地方,继续执行原来的程序。
通俗一点的理解方式为:中断即为系统异常,异常发生就会导致其他事件发生(中断服务函数),异常事件处理完毕后,事件回到正轨。
中断会打断当前正在做的事情,转而执行中断服务函数事件(中断服务函数/中断回调函数)等待中断服务事件处理完毕后再执行之前的事件。
而在linux驱动中正常的时候->CPU处于SVC模式
发生中断的时候->CPU会从SVC模式转换成IRQ模式
SVC转IRQ需要改变 CPSR寄存器后 5bit
SVC转IRQ还需要保护现场(压栈->SP指针)
2:linux下的中断
linux的核心思想就是分层:
中断也是如此,不管底层的中断控制有多复制,都无所谓,linux接口把所有的硬件的复杂度,靠中间层的接口控制所有的中断系统。
我们也把中断相关的linux下控制接口叫做-linux下中断子系统。
我所使用的是A系列芯片其中的中断控制:
管理中断的控制器->GIC(通用的中断控制器)
在STM系列中的管理中断的控制器为NVIC
在硬件手册中:
中断分为三类:
SGI:软中断(0-15)
PPI:私有中断(16-31)
PPI的中断编号 +16 = 硬件中断编号
SPI:共享中断(从0到SPI_PORT)
实际上硬件中断编号 = SPI_PORT+32
3:linux中断控制系统的接口
3.1 中断注册函数
request_irq(
unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char * name,
void * dev
)
int devm_request_irq(
struct device * dev,
unsigned int irq,
irq_handler_t handler,
unsigned long irqflags,
const char * devname,
void * dev_id
)
函数功能:都是向内核注册一个中断
参数:
dev:
platform匹配成功后probe->新入口的传参
irq:
中断编号 填入硬件中断编号
SPI_PORT + 32
PPI_PORT + 16
SGI_PORThandler:
中断服务函数
irqreturn_t (*irq_handler_t)(int, void *);
irqflags:
中断触发类型:
IRQ_TYPE_NONE: 默认的触发类型
IRQ_TYPE_EDGE_RISING:上升沿触发
IRQ_TYPE_EDGE_FALLING:下降沿触发
IRQ_TYPE_EDGE_BOTH:上升沿下降沿都触发
IRQ_TYPE_LEVEL_HIGH:高电平触发
IRQ_TYPE_LEVEL_LOW:低电平触发dename:
中断注册标签名字
dev_id:
传递给中断服务函数的第二个参数
3.2 注销函数
void *free_irq(unsigned int irq, void *dev_id)
irq:
中断编号
dev_id:
怎么传递给中断服务函数
你就如何传递释放
3.3 使能/失能中断
enable_irq(unsigned int irq)
disable_irq(unsigned int irq);
3.4 获取中断编号函数
int platform_get_irq(struct platform_device *dev, unsigned int num)
函数的功能:获取中断编号
参数:
dev:
匹配成功后的传参 probe->参数
num:
你要获取哪个中断号
原则上就是 0
返回值:
返回中断的编号
* 前提是这个设备树里面有:
interrupts 属性!
int gpio_to_irq(unsigned gpio)
函数的功能:通过提供一个 GPIO 的编号
给你返回对应这个引脚的 GPIO 的中断编号
参数 :
gpio-> GPIO 的编号 原则自己算
尽量所有的信息来源于设备树
返回值:
返回的也是中断编号
4:linux设备树中断的属性添加
interrupt-parent:
可以被继承
interrupt-parent=<&gic>
interrupts
描述一个节点下的中断属性 现在有两种写法:
中断父设备为 interrupt-parent=<&gic>
直接编写 中断编号信息:
interrupts = <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>;
分组 编号 触发类型
你的中断属性的父属性:
interrupt-parent = <&gpio0>; 中断属性来源 gpio0 组
GPIO 的中断属性:
interrupts = <RK_PC4 IRQ_TYPE_LEVEL_LOW>;
GPIO 小组 C 第四个引脚 触发类型
示例按键设备树编写:
xyd_key {
compatible = "xyd_key","xydkey","XYDKEY";
xyd-gpios =<&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PA4 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
5:linux下按键中断代码的编写
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/of_gpio.h"
#include "linux/device/class.h"
#include "linux/device.h"
#include "linux/platform_device.h"
#include "linux/miscdevice.h"
#include "asm/uaccess.h"
#include "linux/irq.h"
#include "linux/interrupt.h"
struct device_node * node =NULL;
struct platform_device * xydkeydev = NULL;
int gpio_num;
struct miscdevice * keymisc;
struct file_operations * ops;
int keyirqnum;
uint8_t value = 0;irqreturn_t mykey_irqhandler(int irqnum, void * arg)
{
//一定按键按下了!
value = gpio_get_value(gpio_num);
return 0;
}
int xyd_key_open(struct inode * i , struct file * dev)
{
//1: 获取中断号
int ret = 0;
keyirqnum = platform_get_irq(xydkeydev,0);
printk("keyirqnum == %d\r\n",keyirqnum);
if(keyirqnum < 0)
{
printk("ERROR!: keyirq is Error!\r\n");
return -EIO;
}
//2:使能
//enable_irq(keyirqnum);//新开发
//3: 向内核注册一个中断
ret = devm_request_irq(&xydkeydev->dev,\
keyirqnum,\
mykey_irqhandler,\
IRQ_TYPE_EDGE_FALLING,\
"key_irq",NULL);
ret = ret;
return 0;
}
int xyd_key_close(struct inode * i , struct file * dev)
{
//1: 释放中断
free_irq(keyirqnum,NULL);
//2: 失能中断
//disable_irq(keyirqnum);
return 0;
}
ssize_t xyd_key_read(struct file * file, char __user * buf, size_t
size, loff_t * offt)
{
int ret = copy_to_user(buf,&value,1);value = 0xFF;
ret = ret ;
return 1;
}
int xyd_key_probe(struct platform_device * dev)
{
node = dev->dev.of_node;
xydkeydev = dev;
//1: 获取 GPIO 的信息
gpio_num = of_get_named_gpio(node,"xyd-gpios",0);
//2: 申请 + 设置 输入
gpio_request(gpio_num,"xyd_key");
gpio_direction_input(gpio_num);
//3:注册杂项设备
keymisc = kzalloc(sizeof(struct miscdevice), GFP_KERNEL);
ops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
ops->owner = THIS_MODULE;
ops->open = xyd_key_open;
ops->release= xyd_key_close;
ops->read = xyd_key_read;
keymisc->minor = 255;
keymisc->name = "xyd_key";
keymisc->fops = ops;
return misc_register(keymisc);
}
struct of_device_id xyd_id_table={
.compatible = "xydkey",//匹配名字
};
struct platform_driver myxyd_key_driver={
.driver={
.name = "xyd_key",
.of_match_table = &xyd_id_table,
},
.probe = xyd_key_probe,
};
static int __init mykey_init(void)
{
return platform_driver_register(&myxyd_key_driver);
}
static void __exit mykey_exit(void)
{ }
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
6:等待队列
6.1 等待队列的意义
等待队列是目前内核最常用的阻塞同步机制之一
其中的作用为在内核层产生一个阻塞
等待队列的功能:
阻塞->调用后立刻产生挂起
wait_event()
唤醒->调用会唤醒之前挂起进程/程序
wake_up
内核层的阻塞会引起上层的阻塞!
6.2 内核的阻塞机制
等待队列的创建:
DECLARE_WAIT_QUEUE_HEAD(name);
等待队列阻塞函数:
wait_event_interruptible(wq,cond)
wq:
等待队列变量 而非指针!
struct wait_queue_head *wq_head
cond:
如果是 非 0 ->无调用效果
如果 0 -> 产生阻塞
解除阻塞:
wake_up_interruptible(x)
x:等待队列的结构体指针 而非变量
struct wait_queue_head *wq_head
7:等待队列应用优化按键
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/of_gpio.h"
#include "linux/device/class.h"
#include "linux/device.h"
#include "linux/platform_device.h"
#include "linux/miscdevice.h"
#include "asm/uaccess.h"
#include "linux/irq.h"
#include "linux/interrupt.h"
#include "linux/sched.h"
#include "linux/wait.h"
struct device_node * node =NULL;
struct platform_device * xydkeydev = NULL;
int gpio_num;
struct miscdevice * keymisc;
struct file_operations * ops;
int keyirqnum;
uint8_t value = 0;
int cond = 0;
//加一个 等待队列的结构体变量
DECLARE_WAIT_QUEUE_HEAD(xyd_key_wait);
irqreturn_t mykey_irqhandler(int irqnum, void * arg)
{
//一定按键按下了!
value = gpio_get_value(gpio_num);
cond = 1;//原则上解除阻塞后再次判断
wake_up_interruptible(&xyd_key_wait);
return 0;
}
int xyd_key_open(struct inode * i , struct file * dev)
{
//1: 获取中断号
int ret = 0;
keyirqnum = platform_get_irq(xydkeydev,0);
printk("keyirqnum == %d\r\n",keyirqnum);
if(keyirqnum < 0)
{
printk("ERROR!: keyirq is Error!\r\n");
return -EIO;
}
//2:使能
//enable_irq(keyirqnum);//新开发
//3: 向内核注册一个中断
ret = devm_request_irq(&xydkeydev- >dev,keyirqnum,mykey_irqhandler,IRQ_TYPE_EDGE_FALLING,"key_irq",NULL);
ret = ret;
return 0;
}
int xyd_key_close(struct inode * i , struct file * dev)
{
//1: 释放中断
free_irq(keyirqnum,NULL);
//2: 失能中断
//disable_irq(keyirqnum);
return 0;
}
ssize_t xyd_key_read(struct file * file, char __user * buf, size_t
size, loff_t * offt)
{
cond = 0;
wait_event_interruptible(xyd_key_wait,cond);//不管你按键有没有按下现阻塞在说
int ret = copy_to_user(buf,&value,1);
value = 0xFF;
ret = ret ;
return 1;
}
int xyd_key_probe(struct platform_device * dev)
{
node = dev->dev.of_node;
xydkeydev = dev;
//1: 获取 GPIO 的信息
gpio_num = of_get_named_gpio(node,"xyd-gpios",0);
//2: 申请 + 设置 输入
gpio_request(gpio_num,"xyd_key");
gpio_direction_input(gpio_num);
//3:注册杂项设备
keymisc = kzalloc(sizeof(struct miscdevice), GFP_KERNEL);
ops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
ops->owner = THIS_MODULE;
ops->open = xyd_key_open;
ops->release= xyd_key_close;
ops->read = xyd_key_read;keymisc->minor = 255;
keymisc->name = "xyd_key";
keymisc->fops = ops;
return misc_register(keymisc);
}
struct of_device_id xyd_id_table={
.compatible = "xydkey",//匹配名字
};
struct platform_driver myxyd_key_driver={
.driver={
.name = "xyd_key",
.of_match_table = &xyd_id_table,
},
.probe = xyd_key_probe,
};
static int __init mykey_init(void)
{
return platform_driver_register(&myxyd_key_driver);//注册设备的驱动信息
}
static void __exit mykey_exit(void)
{ }
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");