Linux学习笔记(17.6)——基于异步通知的按键驱动

  1. 异步通知

    ​ 使用休眠-唤醒、POLL机制时,都需要休眠等待某个事件发生时,它们的差别在于后者可以指定休眠的时长。

    如果APP不想休眠怎么办?也有类似的方法:驱动程序有数据时主动通知APP,APP收到信号后执行信息处理函数。

    1.1 什么叫“异步通知”?

    举个例子:你去买奶茶,

    • 你在旁边等着,眼睛盯着店员,生怕别人插队,他一做好你就知道:你是主动等待他做好,这叫“同步”。

    • 你付钱后就去玩手机了,店员做好后他会打电话告诉你:你是被动获得结果,这叫“异步”。

    1.2 如何使用异步通知?

    驱动程序怎么通知APP:发信号,这只有3个字,却可以引发很多问题:

    ① 谁发?驱动程序发

    ② 发什么? 信号

    ③ 发什么信号? SIGIO

    ④ 怎么发? 内核里提供有函数

    ⑤ 发给谁? APP,APP要把自己告诉驱动

    ⑥ APP收到后做什么? 执行信号处理函数

    ⑦ 信号处理函数和信号,之间怎么挂钩? APP注册信号处理函数

    Linux系统中也有很多信号,在Linux内核源文件include\uapi\asm-generic\signal.h中,有很多信号的宏定义:

    #define SIGHUP		 1
    #define SIGINT		 2
    #define SIGQUIT		 3
    #define SIGILL		 4
    #define SIGTRAP		 5
    #define SIGABRT		 6
    #define SIGIOT		 6
    #define SIGBUS		 7
    #define SIGFPE		 8
    #define SIGKILL		 9
    #define SIGUSR1		10
    #define SIGSEGV		11
    #define SIGUSR2		12
    #define SIGPIPE		13
    #define SIGALRM		14
    #define SIGTERM		15
    #define SIGSTKFLT	16
    #define SIGCHLD		17
    #define SIGCONT		18
    #define SIGSTOP		19
    #define SIGTSTP		20
    #define SIGTTIN		21
    #define SIGTTOU		22
    #define SIGURG		23
    #define SIGXCPU		24
    #define SIGXFSZ		25
    #define SIGVTALRM	26
    #define SIGPROF		27
    #define SIGWINCH	28
    #define SIGIO		29
    #define SIGPOLL		SIGIO
    

    ​ 就APP而言,你想处理SIGIO信息,那么需要提供信号处理函数,并且要跟SIGIO挂钩。这可以通过一个signal函数来“给某个信号注册处理函数”,用法如下:

    #include <signal.h>
    
    typedef void (*sighandler_t)(int);
    
    sighandler_t signal(int signum, sighandler_t handler);
    

    ​ APP还要做什么事?想想这几个问题:

    ① 内核里有那么多驱动,你想让哪一个驱动给你发SIGIO信号?

    ​ APP要打开驱动程序的设备节点。

    ② 驱动程序怎么知道要发信号给你而不是别人?

    ​ APP要把自己的进程ID告诉驱动程序。

    ③ APP有时候想收到信号,有时候又不想收到信号:

    ​ 应该可以把APP的意愿告诉驱动。

    ​ 驱动程序要做什么?发信号。

    ① APP设置进程ID时,驱动程序要记录下进程ID;

    ② APP还要使能驱动程序的异步通知功能,驱动中有对应的函数:

    • APP打开驱动程序时,内核会创建对应的file结构体,file中有f_flags;

    • f_flags中有一个FASYNC位,它被设置为1时表示使能异步通知功能。

    • 当f_flags中的FASYNC位发生变化时,驱动程序的fasync函数被调用。

    ③ 发生中断时,有数据时,驱动程序调用内核辅助函数发信号。

    ​ 这个辅助函数名为kill_fasync。

    综上所述,使用异步通知,也就是使用信号的流程如下图所示: Linux学习笔记(17.6)——基于异步通知的按键驱动
    重点从②开始:

    ② APP给SIGIO这个信号注册信号处理函数button_proc,以后APP收到SIGIO信号时,这个函数会被自动调用;

    ③ 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID;

    ④ 读取驱动程序文件flags;

    ⑤ 设置flags里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用;

    ⑥⑦ 调用faync_helper,它会根据FAYSNC的值决定是否设置button_async->fa_file=驱动文件filp:

    ​ 驱动文件filp结构体里面含有之前设置的PID。

    ⑧ APP可以做其他事;

    ⑨⑩ 按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用kill_fasync发信号;

    ⑪⑫⑬ APP收到信号后,它的信号处理函数被自动调用,可以在里面调用read函数读取按键。

    1.3 以按键进行编程实践

    1.3.1 按键设备驱动文件
    button_drv.c文件中,

    • 提供了button_drv_fasync函数,,调用faync_helper,它会根据FAYSNC的值决定是否设置button_async->fa_file=filp,驱动文件filp结构体里面含有之前设置的PID;

    • 有按键时(上升沿、下降沿或双边沿触发),进入中断服务函数gpio_btn_isr,在此中断服务函数调用wake_up_interruptible函数唤醒等待队列,并且调用kill_fasync(&ops->fp, SIGIO, POLL_IN)向APP进程发送信号;

    • button_drv_read函数得以继续执行,返回按键数据给应用程序。

    /**
     * 文件    : button_drv.c
     * 作者    : glen  
     * 描述    : button driver文件
     */
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/ide.h>
    #include <linux/init.h>
    #include <linux/poll.h>
    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/gpio.h>
    #include <linux/cdev.h>
    #include <linux/of.h>
    #include <linux/of_gpio.h>
    #include <linux/platform_device.h>
    #include <linux/gpio/consumer.h>
    #include <asm/mach/map.h>
    #include <asm/uaccess.h>
    #include <asm/io.h>
    #include <linux/fs.h>
    
    struct gbtn_irq {
        int gpio;
        struct gpio_desc *gpiod;
        int flag;
        int irq;
        int idx;
        char kval;
        struct fasync_struct *fp;
    };
    
    struct button_drv {
        struct class *class;
        struct gbtn_irq *gbtn_irq;
        
        char  *name;
        int count;
        int major;
    };
    
    static struct button_drv *btn_drv;
    
    /* 等待队列头的静态初始化 */
    static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);
    
    /* 实现file_operations结构体成员 read 函数 */
    ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
    {
        int minor = iminor(filp->f_inode);
        struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
    
        size = (size >= 1) ? 1 : 0;
        if (ops == NULL) {
            printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }
    
        wait_event_interruptible(gpio_button_wait, ops->kval);
    
        if (copy_to_user(buf, &ops->kval, size))
            return -EFAULT;
    
        ops->kval = 0;
    
        printk("Read button%d value successfully:", minor);
        return size;
    }
    
    /* 实现file_operations结构体成员 open 函数 */
    int button_drv_open(struct inode *nd, struct file *filp)
    {
        int ret;
        int minor = iminor(nd);
        struct gbtn_irq *ops; 
        
        if (btn_drv == NULL) {
            printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }
    
        ops = &btn_drv->gbtn_irq[minor];
    
        ret = gpiod_direction_input(ops->gpiod);
        if (ret) 
            printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        else 
            printk("Set the button%d pin as input successfully!\n", minor);
    
        filp->private_data = ops;
    
        return 0;
    }
    
    /* 实现file_operations结构体成员 release 函数 */
    int button_drv_release (struct inode *nd, struct file *filp)
    {
        struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
    
        if (ops == NULL) {
            printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }
    
        filp->private_data = NULL;
    
        return 0;
    }
    
    /* 实现file_operations结构体成员 poll 函数 */
    unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
    {
        int minor = iminor(filp->f_inode);
    
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        poll_wait(filp, &gpio_button_wait, wait);
        return ((btn_drv->gbtn_irq[minor].kval == 0) ? 0 : POLLIN | POLLRDNORM);
    }
    
    static int button_drv_fasync(int fd, struct file *filp, int on)
    {
        struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
    
    	if (fasync_helper(fd, filp, on, &ops->fp) >= 0)
    		return 0;
    	else
    		return -EIO;
    }
    
    /**
     * 1. 构造file_operations结构体 
     */
    static struct file_operations button_drv_ops = {
        .owner   = THIS_MODULE,
        .read    = button_drv_read,
        .open    = button_drv_open,
        .release = button_drv_release,
        .poll    = button_drv_poll,
        .fasync  = button_drv_fasync,
    };
    
    /* 中断服务函数 */
    static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
    {
        int val;
        struct gbtn_irq *ops = dev_id;
    
        /* 读取按键的值 */
        val = gpiod_get_value(ops->gpiod);
    
        printk("button%d %d %d\n", ops->idx, ops->gpio, val);
        ops->kval = (ops->gpio << 4) | val;
    
        /* 唤醒等待队列 */
        wake_up_interruptible(&gpio_button_wait);
    
        kill_fasync(&ops->fp, SIGIO, POLL_IN);
    
        return IRQ_HANDLED;
    }
    
    /* platform_driver结构体的 probe成员函数实现 */
    int btn_hw_drv_probe (struct platform_device *pdev)
    {
        int i;
        int ret;
        int count;
        // enum of_gpio_flags flag;
        struct device_node *node = pdev->dev.of_node;
    
        /* 从设备节点获取gpio数量 */
        count = of_gpio_count(node);
        if (!count) {
            printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }
        
        btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
        if (btn_drv == NULL) 
            return -ENOMEM;
    
        btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
        if (btn_drv->gbtn_irq == NULL)
            return -ENOMEM;
    
        for (i = 0; i < count; i++) {
            btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
            if (btn_drv->gbtn_irq[i].gpiod == NULL) {
                printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
                return -EIO;
            }
    
            btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
            btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);
    
            btn_drv->gbtn_irq[i].idx = i;
        }
    
        for (i = 0; i < count; i++) 
            /* 申请irq中断, 将中断服务程序注册到上半部 */
            ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
                              "gpio_btn", &btn_drv->gbtn_irq[i]);
    
        /* 注册file_operationss结构体对象 -- button_drv_ops  */
        btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
        btn_drv->class = class_create(THIS_MODULE, "gbtn");
        if (IS_ERR(btn_drv->class)) {
            printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
            unregister_chrdev(btn_drv->major, "gbtn");
            return PTR_ERR(btn_drv->class);
        }
    
        for (i = 0; i < count; i++)
            device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);
    
        btn_drv->count = count;
            
        return 0;
    }
    
    /* platform_driver结构体的 remove成员函数实现 */
    int btn_hw_drv_remove(struct platform_device *pdev)
    {
        int i;
        struct device_node *node = pdev->dev.of_node;
        int count = of_gpio_count(node);
    
        for (i = 0; i < count; i++) {
            device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
            free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
        }
        class_destroy(btn_drv->class);
        unregister_chrdev(btn_drv->major, "gbtn");
    
        kfree(btn_drv);
        return 0;
    }
    
    /* 构造用于配置的设备属性 */
    static const struct of_device_id gbtns_id[] = {
        {.compatible = "glen,gbtn"},
        { },
    };
    
    /* 构造(初始化)file_operations结构体 */
    static struct platform_driver btn_hw_drv = {
        .driver = {
            .name = "gbtn",
            .of_match_table = gbtns_id,
        },
        .probe = btn_hw_drv_probe,
        .remove = btn_hw_drv_remove,
    };
    
    /* 初始化 */
    static int __init button_drv_init(void)
    {
        int ret;
        ret = platform_driver_register(&btn_hw_drv);
        if (ret)
            pr_err("Unable to initialize button driver\n");
        else
            pr_info("The button driver is registered.\n");
    
        
        return 0;
    }
    module_init(button_drv_init);
    
    static void __exit button_drv_exit(void)
    {
        platform_driver_unregister(&btn_hw_drv);
        printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    }
    module_exit(button_drv_exit);
    
    /* insert author information for module */
    MODULE_AUTHOR("glen");
    /* insert license for module */
    MODULE_LICENSE("GPL");
     
    

    probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。

    1.3.2 设备树文件(不作更改)

    		pinctrl_btn0:btn0 {
    			fsl,pins = <
    				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18	0xF080	/* KEY0 */ 
    			>;
    		};
    
    		pinctrl_btn1:btn1 {
    			fsl,pins = <
                    MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xF080	/* KEY1  此按键不存在 */
    			>;
    		};
        /* 在根节点下添加基于pinctrl的gbtns设备节点 */
        gbtns {
            compatible = "glen,gbtn";
            #address-cells = <1>;
    
            pinctrl-names = "default";
            pinctrl-0 = <&pinctrl_btn0 
    		             &pinctrl_btn1>;
    
            gpio-controller;
            #gpio-cells = <2>;
            gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
                     &gpio1 3 GPIO_ACTIVE_LOW>;   /* button1 */
    
        };
    
    
    • 取消了gpios前缀“xxx-",相应地,在驱动程序用gpiod_get_index_optional函数获取gpio描述符时,第2个形参 ”const char *con_id“ 传递NULL即可;

    • 将pinctrl-0、gpios属性值由 “<>,<>;” 改为 “<>;",效果是一样的

    1.3.3 应用程序

    应用程序文件button_drv_test.c提供:

    • 定义sig_fun信号处理函数并注册信号,以后APP收到SIGIO信号时,这个函数会被自动调用;
    • fcntl(fd, F_SETOWN, getpid()); 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID;
    • oflags = fcntl(fd, F_GETFL); 读取驱动程序文件oflags
    • fcntl(fd, F_SETFL, oflags | FASYNC); 设置oflags里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用
    /*
     * 文件名   :  button_drv_test.c
     * 作者     :  glen
     * 描述     :  button_drv应用程序
     */
    
    #include "stdio.h"
    #include "sys/types.h"
    #include "sys/stat.h"
    #include "stdlib.h"
    #include "string.h"
    #include "poll.h"
    #include <signal.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    int fd;
    
    static void sig_fun(int sig)
    {
        char kval;
        read(fd, &kval, 1);
        printf("The glen button value is: %d!\n", kval);
    }
    
    /**
     * @brief   : main函数
     * @par     : argc  argv数组元素的个数
     *            argv  参数数组
     * @retval  : 0 成功    其它 失败
     */
    int main(int argc, char *argv[])
    {
        int ret;
        int oflags;
        char *filename;
    
        if (argc != 2) {
            printf("Error Usage!\r\n");
            return -1;
        }
    
        signal(SIGIO, sig_fun);
    
        filename = argv[1];
    
        /* 打开驱动文件 */
        fd = open(filename, O_RDWR);
        if (fd < 0) {
            printf("Can't open file %s\r\n", filename);
            return -1;
        }
    
        fcntl(fd, F_SETOWN, getpid());
        oflags = fcntl(fd, F_GETFL);
        fcntl(fd, F_SETFL, oflags | FASYNC);
    
        while (1) {
            sleep(2);
            printf("Read the glen button in sleepping!\n");
        }
    
        /* 关闭文件 */
        ret = close(fd);
        if (ret < 0) {
            printf("file %s close failed!\r\n", argv[1]);
            return -1;
        }
        return 0;
    }
    
    

    1.4.4 在alientek_linux_alpha开发板实测验证如下

    /drv_module # insmod button_drv.ko
    The button driver is registered.
    /drv_module # ./btn_drv_test /dev/gbtn0
    Set the button0 pin as input successfully!
    Read the glen button in sleepping!
    random: nonblocking pool is initialized
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    button0 18 1
    button0 18 1
    Read button0 value successfully:The glen button value is: 33!
    Read the glen button in sleepping!
    
    button0 18 0
    Read button0 value successfully:The glen button value is: 32!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    
    button0 18 1
    Read button0 value successfully:The glen button value is: 33!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    
    button0 18 0
    Read button0 value successfully:The glen button value is: 32!
    Read the glen button in sleepping!
    
    button0 18 1
    Read button0 value successfully:The glen button value is: 33!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    
    button0 18 0
    Read button0 value successfully:The glen button value is: 32!
    Read the glen button in sleepping!
    Read the glen button in sleepping!
    
    
上一篇:P1495【模板】【中国剩余定理】【曹冲养猪】


下一篇:Vue中防止按钮重复点击提交的方法