Linux learning notes (17.6) -- key driver based on asynchronous notification

  1. Asynchronous notification

    When using sleep wake and POLL mechanisms, sleep is required. When waiting for an event to occur, the difference between them is that the latter can specify the duration of sleep.

    What if APP doesn't want to sleep? There are similar methods: when the driver has data, it actively notifies the APP, and the APP executes the information processing function after receiving the signal.

    1.1 what is asynchronous notification?

    For example: you buy milk tea,

    • You wait by and stare at the clerk for fear that others will jump the queue. As soon as he does a good job, you will know that you are actively waiting for him to do a good job. This is called "synchronization".

    • After you pay, you go to play with your mobile phone. After the clerk completes it, he will call and tell you: you get the result passively, which is called "asynchronous".

    1.2 how to use asynchronous notification?

    How does the driver tell the APP to send a signal? This is only three words, but it can cause many problems:

    ① Who sent it? Driver send

    ② What? signal

    ③ What signal? SIGIO

    ④ How? Functions are provided in the kernel

    ⑤ To whom? APP, APP should tell itself to the driver

    ⑥ What will the APP do after receiving it? Execute signal processing function

    ⑦ How to link between signal processing function and signal? APP registered signal processing function

    There are also many signals in the Linux system. In the Linux kernel source file include \ UAPI \ ASM generic \ signal In H, there are many macro definitions of signals:

    #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
    

    In terms of APP, if you want to process SIGIO information, you need to provide signal processing function and link it with SIGIO. You can "register a processing function for a signal" through a signal function. The usage is as follows:

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

    What else does APP have to do? Think about these questions:

    ① There are so many drivers in the kernel. Which driver do you want to send you SIGIO signal?

    APP opens the device node of the driver.

    ② How does the driver know to signal you instead of others?

    APP should tell its process ID to the driver.

    ③ APP sometimes wants to receive signals, and sometimes doesn't want to receive signals:

    It should be possible to tell the driver the intention of the APP.

    What does the driver do? to signal to.

    ① When APP sets the process ID, the driver shall record the process ID;

    ② APP also enables the asynchronous notification function of the driver. There are corresponding functions in the driver:

    • When APP opens the driver, the kernel will create the corresponding file structure with F in the file_ flags;

    • f_ There is a FASYNC bit in flags. When it is set to 1, it indicates that the asynchronous notification function is enabled.

    • When f_ When the fasync bit in flags changes, the driver's fasync function is called.

    ③ When an interrupt occurs and there is data, the driver calls the kernel auxiliary function to send a signal.

    This helper function is called kill_fasync.

    To sum up, the process of using asynchronous notification, that is, using signals, is shown in the following figure:
    The key points start from ②:

    ② APP registers the signal processing function button for SIGIO_ Proc, this function will be called automatically when APP receives SIGIO signal in the future;

    ③ Tell the PID (process ID) of the APP to the driver. This call does not involve the driver and records the PID at the file system level of the kernel;

    ④ Read the driver file flags;

    ⑤ Set the fasync bit in flags to 1: when the fasync bit changes, the driver's fasync will be called;

    ⑥ ⑦ call faync_helper, which will decide whether to set button according to the value of FAYSNC_ async->fa_ File = drive file filp:

    The filp structure of the driver file contains the previously set PID.

    ⑧ APP can do other things;

    ⑨ ⑩ press the key, an interrupt occurs, the interrupt service program of the driver is called, and kill is called inside_ Fasync sends signal;

    ⑪ ⑫ after the APP receives the signal, its signal processing function is automatically called, and the read function can be called inside to read the key.

    1.3 programming practice with keys

    1.3.1 key device driver file
    button_drv.c in the document,

    • Button provided_ drv_ Fasync function, call faync_helper, which will decide whether to set button according to the value of FAYSNC_ async->fa_ File = filp, the structure of the drive file filp contains the previously set PID;

    • When there is a key (triggered by rising edge, falling edge or bilateral edge), enter the interrupt service function gpio_btn_isr, interrupt the service function here and call wake_ up_ The interruptible function wakes up the waiting queue and calls kill_ Fasync (& Ops - > FP, sigio, poll_in) sends a signal to the APP process;

    • button_ drv_ The read function continues to execute and returns key data to the application.

    /**
     * Files: button_drv.c
     * Author: glen  
     * Description: button driver file
     */
    #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;
    
    /* Waiting for static initialization of queue header */
    static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);
    
    /* Implementation file_operations struct member read function */
    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;
    }
    
    /* Implementation file_operations struct member open function */
    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;
    }
    
    /* Implementation file_operations struct member release function */
    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;
    }
    
    /* Implementation file_operations struct member poll function */
    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. Construct file_operations structure 
     */
    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,
    };
    
    /* Interrupt service function */
    static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
    {
        int val;
        struct gbtn_irq *ops = dev_id;
    
        /* Read the value of the key */
        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 waiting queue */
        wake_up_interruptible(&gpio_button_wait);
    
        kill_fasync(&ops->fp, SIGIO, POLL_IN);
    
        return IRQ_HANDLED;
    }
    
    /* platform_driver Implementation of probe member function of structure */
    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;
    
        /* Get gpio quantity from device node */
        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++) 
            /* Apply for irq interrupt and register the interrupt service program in the upper half */
            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]);
    
        /* Register file_operationss structure object -- 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 Implementation of remove member function of struct */
    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;
    }
    
    /* Construct device properties for configuration */
    static const struct of_device_id gbtns_id[] = {
        {.compatible = "glen,gbtn"},
        { },
    };
    
    /* Construct (initialize) file_operations structure */
    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,
    };
    
    /* initialization */
    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");
     
    

    The probe function first obtains the number of key nodes, and then reads them respectively gpio Descriptor and obtain gpio and irq numbers through it, and apply for registration of interrupt service program.

    1.3.2 equipment tree file (no change)

    		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  This key does not exist */
    			>;
    		};
        /* Add a pinctrl based gbtns device node under the root node */
        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 */
    
        };
    
    
    • The gpios prefix "xxx -" is canceled. Accordingly, when the driver uses the gpiod_get_index_optional function to obtain the gpio descriptor, the second formal parameter "const char *con_id" can be passed NULL;

    • Change the property values of pinctrl-0 and gpios from "< >, < >;" Change to "< >;", the effect is the same

    1.3.3 application

    Application file button_drv_test.c provide:

    • Define sig_fun signal processing function and register the signal. In the future, when APP receives SIGIO signal, this function will be called automatically;
    • fcntl(fd, F_SETOWN, getpid()); Tell the PID (process ID) of the APP to the driver. This call does not involve the driver and records the PID at the file system level of the kernel;
    • oflags = fcntl(fd, F_GETFL); Read driver file oflags
    • fcntl(fd, F_SETFL, oflags | FASYNC); Set the fasync bit in oflags to 1: when the fasync bit changes, the driver's fasync will be called
    /*
     * File name: button_drv_test.c
     * Author: glen
     * Description: button_drv application
     */
    
    #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 function
     * @par     : argc  argv Number of array elements
     *            argv  Parameter array
     * @retval  : 0 Success other failures
     */
    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];
    
        /* Open driver file */
        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");
        }
    
        /* Close file */
        ret = close(fd);
        if (ret < 0) {
            printf("file %s close failed!\r\n", argv[1]);
            return -1;
        }
        return 0;
    }
    
    

    1.4.4 in alientek_ linux_ The measured verification of alpha development board is as follows:

    /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!
    
    

Keywords: Linux Operation & Maintenance server

Added by fxb9500 on Wed, 26 Jan 2022 22:52:41 +0200