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_PORT

        handler:

                中断服务函数 

                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");

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值