STM32MP157驱动开发——阻塞与非阻塞IO(下)

STM32MP157驱动开发——阻塞与非阻塞IO(下)


0.前言

  上一节理解了阻塞与非阻塞IO的原理,以及两种模式下常用的事件处理方式:阻塞型IO使用等待队列处理事件、非阻塞IO使用select、poll方式处理。本小节就分别使用两种方式进行简单的启动开发和验证。

一、阻塞型IO驱动开发

  在使用中断方式进行按键驱动开发实验中,应用程序使用 read 不断读取按键状态,在中断服务函数中对按键进行延时消抖。这种方法有一个缺点:测试App在一个 while 死循环中不断的读取键值,会占用很高的CPU资源。
  这一节就使用阻塞型IO,让测试程序在没有按键触发时,进入休眠状态,就可以大大降低CPU使用率。
  相关的驱动程序在中断模式下的按键驱动基础上进行修改:
blockio.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/of_irq.h>

#define KEY_CNT         1
#define KEY_NAME        "key"

/*定义按键状态*/
enum key_status {
    KEY_PRESS = 0,  /*按键按下*/
    KEY_RELEASE,    /*按键释放*/
    KEY_KEEP,       /*按键保持*/
};

/*key设备结构体*/
struct key_dev{
    dev_t devid;                /*设备号*/
    struct cdev cdev;           /*cdev*/
    struct class *class;        /*类*/
    struct device *device;      /*设备*/
    struct device_node *nd;     /*设备节点*/
    int key_gpio;               /*key所使用的的gpio号*/
    struct timer_list timer;    /*定时器*/
    int irq_num;                /*中断号*/
    
    atomic_t status;            /*按键状态*/
    wait_queue_head_t   r_wait; /*等待队列头*/
};

struct key_dev keydev;          /*创建key设备*/

/*按键防抖处理,也就是开启定时器延时15ms,这里作为中断处理函数*/
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
    mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(15));
    return IRQ_HANDLED;
}

/*解析设备树中的按键属性*/
static int key_parse_dt(void){
    int ret;
    const char *str;

    /*设置Key所使用的GPIO*/
    /*获取设备节点*/
    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL){
        printk("keydev node not find!\r\n");
        return -EINVAL;
    }

    /*获取status值*/
    ret = of_property_read_string(keydev.nd, "status", &str);
    if(ret < 0){
        printk("read status failed!\r\n");
        return -EINVAL;
    }
    if(strcmp(str, "okay")){
        printk("key device not okay!\r\n");
        return -EINVAL;
    }

    /*获取compatible属性并匹配*/
    ret = of_property_read_string(keydev.nd, "compatible", &str);
    if(ret < 0){
        printk("read compatible failed!\r\n");
        return -EINVAL;
    }
    if(strcmp(str, "amonter,key")){
        printk("keydev: compatible match failed!\r\n");
        return -EINVAL;
    }

    /*获取设备树中的gpio属性,得到KEY0的设备编号*/
    keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
    if(keydev.key_gpio < 0){
        printk("can't get key-gpio");
        return -EINVAL;
    }
    printk("key-gpio num = %d\r\n", keydev.key_gpio);

    /*获取GPIO对应的中断号*/
    keydev.irq_num = irq_of_parse_and_map(keydev.nd, 0);
    if(!keydev.irq_num){
        return -EINVAL;
    }
    printk("irq-num = %d\r\n", keydev.irq_num);

    return 0;
}

static int key_gpio_init(void)
{
    int ret;
    unsigned long irq_flags;

    /*向GPIO子系统申请使用GPIO*/
    ret = gpio_request(keydev.key_gpio, "KEY0");
    if(ret){
        printk(KERN_ERR "keydev: failed to request key-gpio!\r\n");
        return ret;
    }

    /*设置PG3为GPIO输入模式*/
    ret = gpio_direction_input(keydev.key_gpio);
    if(ret < 0){
        printk("can't set gpio mode!\r\n");
        return ret;
    }

    /*获取设备树中指定的中断触发类型*/
    irq_flags = irq_get_trigger_type(keydev.irq_num);
    if(IRQF_TRIGGER_NONE == irq_flags){
        irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;;
    }
    /*申请中断*/
    ret = request_irq(keydev.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);
    if(ret){
        gpio_free(keydev.key_gpio);
        return ret;
    }

    return 0;
}

static void key_timer_function(struct timer_list *arg)
{
    static int last_val = 1;
    int current_val;

    /*读取按键值并判断按键当前状态*/
    current_val = gpio_get_value(keydev.key_gpio);
    if(0 == current_val && last_val){               /*按键按下*/
        atomic_set(&keydev.status, KEY_PRESS);
        wake_up_interruptible(&keydev.r_wait);
    } else if(1 == current_val && !last_val) {      /*按键释放*/
        atomic_set(&keydev.status, KEY_RELEASE);
        wake_up_interruptible(&keydev.r_wait);
    } else{         /*保持*/
        atomic_set(&keydev.status, KEY_KEEP);
    }

    last_val = current_val;
}

/*设备open操作函数*/
static int key_open(struct inode *inode, struct file *filp)
{
    return 0;
}

/*设备read操作函数*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;

    /* 加入等待队列,当有按键按下或松开动作发生时,才会被唤醒 */
    ret = wait_event_interruptible(keydev.r_wait, KEY_KEEP != atomic_read(&keydev.status));
    if(ret){
        return ret;
    }

    /* 将按键状态信息发送给应用程序 */
    ret = copy_to_user(buf, &keydev.status, sizeof(int));
    /* 状态重置 */
    atomic_set(&keydev.status, KEY_KEEP);

    return ret;
}

/*设备write操作函数*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*设备release操作函数*/
static int key_release(struct inode *inode, struct file *filp)
{ 
    return 0;
}

/*设备操作函数*/
static struct file_operations key_fops = {
    .owner      = THIS_MODULE,
    .open       = key_open,
    .read       = key_read,
    .write      = key_write,
    .release    = key_release,
};

/*驱动入口函数*/
static int __init mykey_init(void){
    int ret;
    /*初始化队列头*/
    init_waitqueue_head(&keydev.r_wait);
    /*初始化按键状态*/
    atomic_set(&keydev.status, KEY_KEEP);

    /*设备树解析*/
    ret = key_parse_dt();
    if(ret){        /*出错*/
        return ret;
    }
    /*gpio中断初始化*/
    ret = key_gpio_init();
    if(ret){    /*出错*/
        return ret;
    }

    /*注册字符设备驱动*/
    /*创建设备号,直接使用系统分配的设备号*/
    ret = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);     //由系统分配设备号
    if(ret < 0){
        pr_err("%s couldn't alloc_chrdev_region, ret = %d\r\n", KEY_NAME, ret);
        goto free_gpio;
    }

    /*初始化cdev*/
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    /*添加一个cdev*/
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
    if(ret < 0){
        goto del_unregister;
    }

    /*创建类*/
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(keydev.class)){
        goto del_cdev;
    }

    /*创建设备*/
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL,KEY_NAME);
    if(IS_ERR(keydev.device)){
        goto destroy_class;
    }

    /*初始化timer,设置定时器处理函数*/
    timer_setup(&keydev.timer, key_timer_function, 0);  //未设置周期,所以不会激活定时期
    return 0;

destroy_class:
    device_destroy(keydev.class, keydev.devid);
del_cdev:
    cdev_del(&keydev.cdev);
del_unregister:
    unregister_chrdev_region(keydev.devid, KEY_CNT);
free_gpio:
    free_irq(keydev.irq_num, NULL);
    gpio_free(keydev.key_gpio);
    return -EIO;
}

/*驱动出口函数*/
static void __exit mykey_exit(void){
    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid, KEY_CNT);
    del_timer_sync(&keydev.timer);      /*删除timer*/
    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
    free_irq(keydev.irq_num, NULL);     /*释放中断*/
    gpio_free(keydev.key_gpio);         /*释放IO*/
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("amonter");
MODULE_INFO(intree, "Y");

修改部分:
①在设备结构体中去掉了自旋锁属性,改用原子变量来对状态变换进行保护。因为自旋锁在拿不到锁时会轮询等待,而阻塞型IO的等待队列会使线程进入休眠,机制冲突。
②在设备结构体中添加一个等待队列头属性
③在定时器处理函数中对按键状态进行判断,如果状态改变则唤醒等待队列,并修改status变量。这样就可以解除被key_read阻塞的线程。
④在key_read函数中,判断是否有按键状态改变的动作发生,如果没有就将它加入到等待队列中,进行阻塞。如果等待队列被唤醒并且条件condition不为按键在保持状态,则解除阻塞。然后将读取到的按键状态发送给用户程序。由于采用了wait_event_interruptible函数,所以进入休眠状态可以被信号打断。
⑤在驱动入口函数中初始化等待队列头,以及使用原子操作将按键初始状态设置为KEEP
注意:使用等待队列实现阻塞式访问需要注意两点:a. 将任务或进程加入等待队列头;b. 在合适的地方唤醒等待队列,一般是中断处理函数中

测试App直接使用上一节中断模式下的按键驱动的测试App即可。
在这里插入图片描述
可以看到本小节开发的驱动,CPU占用几乎可以忽略不计,而如果使用上一节的驱动程序,CPU占用则将近50%:
在这里插入图片描述
这里可以使用kill -9 PID来杀死某一进程,PID为该进程的句柄。

二、非阻塞IO驱动开发

  在阻塞型IO驱动基础上进行修改,使用非阻塞IO模式进行驱动开发。
noblock.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/poll.h>

#define KEY_CNT         1
#define KEY_NAME        "key"

/*定义按键状态*/
enum key_status {
    KEY_PRESS = 0,  /*按键按下*/
    KEY_RELEASE,    /*按键释放*/
    KEY_KEEP,       /*按键保持*/
};

/*key设备结构体*/
struct key_dev{
    dev_t devid;                /*设备号*/
    struct cdev cdev;           /*cdev*/
    struct class *class;        /*类*/
    struct device *device;      /*设备*/
    struct device_node *nd;     /*设备节点*/
    int key_gpio;               /*key所使用的的gpio号*/
    struct timer_list timer;    /*定时器*/
    int irq_num;                /*中断号*/
    
    atomic_t status;            /*按键状态*/
    wait_queue_head_t   r_wait; /*等待队列头*/
};

struct key_dev keydev;          /*创建key设备*/

/*按键防抖处理,也就是开启定时器延时15ms,这里作为中断处理函数*/
static irqreturn_t key_interrupt(int irq, void *dev_id)
{
    mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(15));
    return IRQ_HANDLED;
}

/*解析设备树中的按键属性*/
static int key_parse_dt(void){
    int ret;
    const char *str;

    /*设置Key所使用的GPIO*/
    /*获取设备节点*/
    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL){
        printk("keydev node not find!\r\n");
        return -EINVAL;
    }

    /*获取status值*/
    ret = of_property_read_string(keydev.nd, "status", &str);
    if(ret < 0){
        printk("read status failed!\r\n");
        return -EINVAL;
    }
    if(strcmp(str, "okay")){
        printk("key device not okay!\r\n");
        return -EINVAL;
    }

    /*获取compatible属性并匹配*/
    ret = of_property_read_string(keydev.nd, "compatible", &str);
    if(ret < 0){
        printk("read compatible failed!\r\n");
        return -EINVAL;
    }
    if(strcmp(str, "amonter,key")){
        printk("keydev: compatible match failed!\r\n");
        return -EINVAL;
    }

    /*获取设备树中的gpio属性,得到KEY0的设备编号*/
    keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
    if(keydev.key_gpio < 0){
        printk("can't get key-gpio");
        return -EINVAL;
    }
    printk("key-gpio num = %d\r\n", keydev.key_gpio);

    /*获取GPIO对应的中断号*/
    keydev.irq_num = irq_of_parse_and_map(keydev.nd, 0);
    if(!keydev.irq_num){
        return -EINVAL;
    }
    printk("irq-num = %d\r\n", keydev.irq_num);

    return 0;
}

static int key_gpio_init(void)
{
    int ret;
    unsigned long irq_flags;

    /*向GPIO子系统申请使用GPIO*/
    ret = gpio_request(keydev.key_gpio, "KEY0");
    if(ret){
        printk(KERN_ERR "keydev: failed to request key-gpio!\r\n");
        return ret;
    }

    /*设置PG3为GPIO输入模式*/
    ret = gpio_direction_input(keydev.key_gpio);
    if(ret < 0){
        printk("can't set gpio mode!\r\n");
        return ret;
    }

    /*获取设备树中指定的中断触发类型*/
    irq_flags = irq_get_trigger_type(keydev.irq_num);
    if(IRQF_TRIGGER_NONE == irq_flags){
        irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;;
    }
    /*申请中断*/
    ret = request_irq(keydev.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);
    if(ret){
        gpio_free(keydev.key_gpio);
        return ret;
    }

    return 0;
}

static void key_timer_function(struct timer_list *arg)
{
    static int last_val = 1;
    int current_val;

    /*读取按键值并判断按键当前状态*/
    current_val = gpio_get_value(keydev.key_gpio);
    if(0 == current_val && last_val){               /*按键按下*/
        atomic_set(&keydev.status, KEY_PRESS);
        wake_up_interruptible(&keydev.r_wait);
    } else if(1 == current_val && !last_val) {      /*按键释放*/
        atomic_set(&keydev.status, KEY_RELEASE);
        wake_up_interruptible(&keydev.r_wait);
    } else{         /*保持*/
        atomic_set(&keydev.status, KEY_KEEP);
    }

    last_val = current_val;
}

/*设备open操作函数*/
static int key_open(struct inode *inode, struct file *filp)
{
    return 0;
}

/*设备read操作函数*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;

    if(filp->f_flags & O_NONBLOCK){     /*非阻塞式访问*/
        if(KEY_KEEP == atomic_read(&keydev.status)){
            return -EAGAIN;
        } else {                        /*阻塞式访问*/
            /*加入等待队列,当按键状态改变时,才会被唤醒*/
            ret = wait_event_interruptible(keydev.r_wait, KEY_KEEP != atomic_read(&keydev.status));
            if(ret){
                return ret;
            }
        }
    }
    /* 将按键状态信息发送给应用程序 */
    ret = copy_to_user(buf, &keydev.status, sizeof(int));
    /* 状态重置 */
    atomic_set(&keydev.status, KEY_KEEP);

    return ret;
}

/*设备write操作函数*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*设备release操作函数*/
static int key_release(struct inode *inode, struct file *filp)
{ 
    return 0;
}

/*poll函数,用来处理非阻塞访问*/
static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    poll_wait(filp, &keydev.r_wait, wait);
    if(KEY_KEEP != atomic_read(&keydev.status)){
        mask = POLLIN | POLLRDNORM;     //返回POLLIN
    }

    return mask;
}

/*设备操作函数*/
static struct file_operations key_fops = {
    .owner      = THIS_MODULE,
    .open       = key_open,
    .read       = key_read,
    .write      = key_write,
    .release    = key_release,
    .poll       = key_poll,
};

/*驱动入口函数*/
static int __init mykey_init(void){
    int ret;
    /*初始化队列头*/
    init_waitqueue_head(&keydev.r_wait);
    /*初始化按键状态*/
    atomic_set(&keydev.status, KEY_KEEP);

    /*设备树解析*/
    ret = key_parse_dt();
    if(ret){        /*出错*/
        return ret;
    }
    /*gpio中断初始化*/
    ret = key_gpio_init();
    if(ret){    /*出错*/
        return ret;
    }

    /*注册字符设备驱动*/
    /*创建设备号,直接使用系统分配的设备号*/
    ret = alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);     //由系统分配设备号
    if(ret < 0){
        pr_err("%s couldn't alloc_chrdev_region, ret = %d\r\n", KEY_NAME, ret);
        goto free_gpio;
    }

    /*初始化cdev*/
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    /*添加一个cdev*/
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
    if(ret < 0){
        goto del_unregister;
    }

    /*创建类*/
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(keydev.class)){
        goto del_cdev;
    }

    /*创建设备*/
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL,KEY_NAME);
    if(IS_ERR(keydev.device)){
        goto destroy_class;
    }

    /*初始化timer,设置定时器处理函数*/
    timer_setup(&keydev.timer, key_timer_function, 0);  //未设置周期,所以不会激活定时期
    return 0;

destroy_class:
    device_destroy(keydev.class, keydev.devid);
del_cdev:
    cdev_del(&keydev.cdev);
del_unregister:
    unregister_chrdev_region(keydev.devid, KEY_CNT);
free_gpio:
    free_irq(keydev.irq_num, NULL);
    gpio_free(keydev.key_gpio);
    return -EIO;
}

/*驱动出口函数*/
static void __exit mykey_exit(void){
    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid, KEY_CNT);
    del_timer_sync(&keydev.timer);      /*删除timer*/
    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
    free_irq(keydev.irq_num, NULL);     /*释放中断*/
    gpio_free(keydev.key_gpio);         /*释放IO*/
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("amonter");
MODULE_INFO(intree, "Y");

修改部分:
①使用poll函数作为非阻塞处理函数,所以加入头文件"/linux/poll.h"
②在 key_read 函数中判断是否为非阻塞式读取访问,如果是则判断按键状态是否改变,如果没有状态改变就返回-EAGAIN。
③此驱动保留了阻塞型访问方式,所以设备结构体中仍然保留相关属性,并且在read函数中也保留了相应的判断和处理方式。
④设计key_poll函数,当应用程序调用 select 或者 poll 函数时 key_poll 就会执行。并在 key_poll 中调用 poll_wait 函数将等待队列头添加到 poll_table,如果按键状态改变就返回 POLLIN,表示有数据可以读取。同时,在设备驱动操作集 file_operations 中添加poll方法,并指定为key_poll。

测试App:
由于使用非阻塞式访问,所以测试App需要做相应修改,添加使用 select 或 poll的访问操作。
noblockio_App.c:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>

int main(int argc, char *argv[])
{
    fd_set readfds;
    int key_val;
    int fd;
    int ret;

    /* 判断传参个数是否正确 */
    if (2 != argc)
    {
        printf("Usage:\n" "\t./keyApp /dev/key\n");
        return -1;
    }

    /* 打开设备 */
    fd = open(argv[1], O_RDONLY | O_NONBLOCK);
    if (0 > fd){
        printf("ERROR: %s file open failed!\n", argv[1]);
        return -1;
    }

    FD_ZERO(&readfds);
    FD_SET(fd, &readfds);

    /* 循环轮训读取按键数据 */
    while(1){
        ret = select(fd + 1, &readfds, NULL, NULL, NULL);
        switch (ret){
            case 0: /* 超时 */
                /* 用户自定义超时处理 */
                break;

            case -1: /* 错误 */
                /* 用户自定义错误处理 */
                break;

            default:
                if (FD_ISSET(fd, &readfds))
                {
                    read(fd, &key_val, sizeof(int));
                    if (0 == key_val)
                        printf("Key Press\n");
                    else if (1 == key_val)
                        printf("Key Release\n");
                }
                break;
        }
    }
    /* 关闭设备 */
    close(fd);
    return 0;
}

使用select进行非阻塞访问,在一个while死循环中使用select不断轮询,检查是否有数据可以读取,如果有则调用read函数读取按键数据。可以看到CPU占用也很小:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值