Linux driver_ Asynchronous notification

Linux applications usually have three ways to query whether device drivers can be accessed: blocking, non blocking and asynchronous notification.

Asynchronous notification: "signal" came into being. The signal is similar to the "interrupt" used in our hardware, but the signal is at the software level. It can be regarded as a simulation of interrupt at the software level. The driver can report by actively sending signals to the application
Tell yourself that you can access it. After the application obtains the signal, it can read or write data from the driver device. whole
The process is equivalent to that the application receives an interrupt sent by the driver, and then the application responds to the interrupt
In the whole process, the application does not query whether the driver device can be accessed. Everything is told by the driver device itself
For the application.

In arch / xtensa / include / UAPI / ASM / signal H file defines the signals supported by Linux:

#define SIGHUP 1 / * the terminal hangs or the control process terminates*/
#define SIGINT 2 / * terminal interrupt (Ctrl+C)*/
#define SIGQUIT 3 / * terminal exit (Ctrl + \ key combination)*/
#define SIGILL 4 / * illegal instruction*/
#Define sigrap 5 / * debug is used, and breakpoint instructions are generated*/
#define SIGABRT 6 / * exit instruction issued by abort(3)*/
#define SIGIOT 6 /* IOT instruction*/
#define SIGBUS 7 / * bus error*/
#define SIGFPE 8 / * floating point operation error*/
#define SIGKILL 9 / * kill and terminate the process*/
#User defined sigr1 / * user defined signal 1*/
#define SIGSEGV 11 / * segment violation (invalid memory segment)*/
#define SIGUSR2 12 / * user defined signal 2*/
#define SIGPIPE 13 / * write data to non read pipeline*/
#define SIGALRM 14 / * alarm clock*/
#define SIGTERM 15 / * software termination*/
#define SIGSTKFLT 16 / * stack exception*/
#define SIGCHLD 17 / * end of child process*/
#define SIGCONT 18 / * process continues*/
#define SIGSTOP 19 / * stop the execution of the process, just pause*/
#define SIGTSTP 20 / * stop the process (Ctrl+Z key combination)*/
#define SIGTTIN 21 / * the background process needs to read data from the terminal*/
#define SIGTTOU 22 / * the background process needs to write data to the terminal*/
#define SIGURG 23 / * with "emergency" data*/
#define SIGXCPU 24 / * CPU resource limit exceeded*/
#define SIGXFSZ 25 / * excess file size*/
#define SIGVTALRM 26 / * virtual clock signal*/
#define SIGPROF 27 / * clock signal description*/
#define SIGWINCH 28 / * window size change*/
#define SIGIO 29 / * can perform input / output operations*/
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 / * breakpoint restart*/
#define SIGSYS 31 / * illegal system call*/
#define SIGUNUSED 31 / * Unused signal*/

Of all the signals, only 9 and 19 cannot be ignored!

Signal reception:

In the development of single chip microcomputer, the interrupt processing function needs to be executed when using interrupt. Similarly, in Linux system, when the application receives the corresponding signal, it also needs to execute the signal processing function. Use the signal function in the application to set the processing function of the specified signal. The prototype of the signal function is as follows:

sighandler_t signal(int signum, sighandler_t handler)

Function parameters and return values have the following meanings:
signum: the signal to set the processing function.
handler: signal processing function.
Return value: the previous processing function of the signal is returned if the setting is successful, and SIG is returned if the setting fails_ ERR.  

Signal processing and transmission in driving:

        1,fasync_struct structure

You need to define a fasync in the driver_ Struct structure, pointer variable, fasync_ The contents of struct structure are as follows:

struct fasync_struct {
    spinlock_t fa_lock;
    int magic;
    int fa_fd;
    struct fasync_struct *fa_next;
    struct file *fa_file;
    struct rcu_head fa_rcu;
};

2. fasync function

To use asynchronous notification, you need to implement file in the device driver_ The fasync function in the operations operation set
The function format is as follows:

int (*fasync) (int fd, struct file *filp, int on)

In the fasync function, fasync is called_ The helper function initializes the previously defined fasync_struct structure, where fasync_ The helper function has the following format:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

Code example:

struct xxx_dev {
  // ......
 struct fasync_struct *async_queue; /* Asynchronous correlation structure */
 };

static int xxx_fasync(int fd, struct file *filp, int on)
{
 struct xxx_dev *dev = (xxx_dev)filp->private_data;

 if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
 return -EIO;
 return 0;
 }

 static struct file_operations xxx_ops = {
 //......
 .fasync = xxx_fasync,
 //......
 };

When unloading the driver, you need to release the fasync defined above_ Struct structure, refer to the following:

static int xxx_release(struct inode *inode, struct file *filp)
{
    return xxx_fasync(-1, filp, 0); /* Delete asynchronous notification */
}

When the device is accessible, the driver needs to send a signal to the application,. kill_ The fasync function is responsible for sending the specified signal, kill_ The prototype of fasync function is as follows:

void kill_fasync(struct fasync_struct **fp, int sig, int band)

Function parameters and return values have the following meanings:
fp: fasync to operate_ struct.
sig: the signal to be sent.
band: set to poll when readable_ In, set to poll when writable_ OUT.
Return value: none.

Application processing of asynchronous notification:

The processing of asynchronous notification by the application includes the following three steps:
1. Register signal processing function
The application program sets the signal processing function according to the signal used by the driver, and the application program uses the signal function to process the signal
Set the signal processing function. I have already talked about it in detail, so I won't talk about it in detail here.
2. Tell the kernel the process number of this application
Use fcntl(fd, F_SETOWN, getpid()) to tell the kernel the process number of this application.
3. Turn on asynchronous notification
Use the following two lines to start asynchronous notification:
        flags = fcntl(fd, F_GETFL); /* Get current process status*/
        fcntl(fd, F_SETFL, flags | FASYNC); /* Enable the asynchronous notification function of the current process*/
The key point is to set the process status to fasync through the fcntl function. After this step, the fasync function in the driver
The number will be executed.

Driver code:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>
#include <linux/fcntl.h>


#define IMX6ULIRQ_COUNT 1
#define IMX6ULIRQ_NAME "asyncnoti"

#define KEY_NUM 1 / * because there is not only one key on a board, create a structure array to describe the key*/
#define KEY0VALUE 0X01
#define INVALKEY 0XFF


/*Key structure*/
struct irq_keydesc{
    int gpio;  /*io number*/
    int irqnum; /*Interrupt number*/
    unsigned char value; /*Key value*/
    char name[10];  /*Interrupt name*/
    irqreturn_t (*handler) (int, void *);  /*Interrupt handling function*/
};

/*Equipment structure*/
struct imx6ulirq_dev{
    dev_t devid;    /*Equipment number*/
    int major;  /*Main equipment No*/
    int minor;  /*Secondary equipment No*/
    struct cdev cdev;   /*Character device*/
    struct class *class;    /*Create class*/
    struct device *device;  /*Create device*/
    struct device_node *node; /*Device node*/
    struct irq_keydesc irqkey[KEY_NUM]; 
    struct timer_list timer;    /*Add timer*/
    atomic_t keyval; /*Key value*/
    atomic_t releasekey;
    wait_queue_head_t r_wait;   /*Read wait queue header*/
    struct fasync_struct *async_queue;  /*Asynchronous communication structure*/

};


struct imx6ulirq_dev imx6ulirq;


static int imx6ulirq_open(struct inode *inode, struct file *filp){
    filp->private_data = &imx6ulirq; /* Set private data */
    return 0;
}
 

static ssize_t imx6ulirq_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt){
    int ret = 0;
    unsigned char keyval;
    unsigned char releasekey;
    struct imx6ulirq_dev *dev = filp->private_data;

    if(filp->f_flags & O_NONBLOCK){   /*Non blocking access*/
        if(atomic_read(&dev->releasekey) == 0){
            return -EAGAIN;
        }
    }
    else{ 
            /*Wait event*/
    wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /*Wait for the key to be valid*/    
        }


#if 0
  /*Blocking access*/
        DECLARE_WAITQUEUE(wait,current);    /*Create a waiting queue item*/
        add_wait_queue(&dev->r_wait, &wait);    /*Adds a queue item to the waiting queue header*/
        __set_current_state(TASK_INTERRUPTIBLE);    /*Set the current process to be interruptible*/
        schedule();     /*Process switching*/

        /*Run from here after the signal wakes up*/
        if(signal_pending(current)){
            ret = -ERESTARTSYS;
            goto failed_error;
#endif


    keyval = atomic_read(&dev->keyval);
    releasekey = atomic_read(&dev->releasekey);

    if(releasekey){ /*If releasekey is true, it indicates a complete key pressing process*/
        if(keyval & 0X80){  /*Previously, the real key value or 0X80 was. Now judge whether it is true. If it is true, the original key value will be returned*/
            keyval &= ~0X80;
            ret = copy_to_user(buf, &keyval, sizeof(keyval));
        }
        else{
            goto failed_error;
        }
        atomic_set(&dev->releasekey, 0);    /*Press the mark to clear 0*/
    }
    else{
        goto failed_error;
    }

failed_error:
#if 0
    __set_current_state(TASK_RUNNING);  /*Set the current task to running state*/
    remove_wait_queue(&dev->r_wait, &wait); /*Remove waiting queue items*/
#endif
    return ret;

}

static unsigned int imx6ulirq_poll(struct file *filp, poll_table *wait){
    int mask = 0;
    struct imx6ulirq_dev *dev = filp->private_data;

    poll_wait(filp, &dev->r_wait, wait);

    /*Is it readable*/
    if(atomic_read(&dev->releasekey)){  /*Press the key to read*/
        mask = POLLIN | POLLRDNORM; /*Return to POLLIN*/
    }

    return mask;
}

static int imx6uirq_fasync(int fd, struct file *filp, int on){
    struct imx6ulirq_dev *dev = filp->private_data;
    return fasync_helper(fd, filp, on, &dev->async_queue);  /*Using fasync_helper function, initializing structure member variables*/
}

static int imx6ulirq_release(struct inode *inode, struct file *filp){
    
    return imx6uirq_fasync(-1, filp, 0);    /*Release fasync_struct pointer variable*/
}

static const struct file_operations imx6ulirq_fops = { /*Character device operation function set*/
    .owner = THIS_MODULE,
    .open = imx6ulirq_open,
    .read = imx6ulirq_read,
    .release = imx6ulirq_release,
    .poll = imx6ulirq_poll,
    .fasync = imx6uirq_fasync,
    
};

/*Interrupt handling function*/
static irqreturn_t key0_handler(int irq, void *dev_id){
    struct imx6ulirq_dev *dev = dev_id;

    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(15)); /*15ms timing*/
    

    return IRQ_HANDLED;
}

/*Timer supermarket processing function*/
static void timer_func(unsigned long arg) {
    int value = 0;
    struct imx6ulirq_dev *dev = (struct imx6ulirq_dev *)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0 ){
        atomic_set(&dev->keyval, dev->irqkey[0].value); /*If the key is pressed, set the key to the set value*/
    }
    else{
        atomic_set(&dev->keyval, 0X80 | (dev->irqkey[0].value));/*When released, set the key to the high value or 1 to distinguish*/
        atomic_set(&dev->releasekey, 1);    /*releasekey A setting of 1 indicates a complete key pressing process*/
    }
#if 0
    /*Wake up process*/
    if(atomic_read(&dev->releasekey)){
        wake_up(&dev->r_wait);
    }
#endif
    if(atomic_read(&dev->releasekey)){   /*If it is a complete key press, send a signal to the application*/
        if(dev->async_queue){
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN); /* Release SIGIO signal */
         }
        }
    

}

/*Key initialization*/
static int keyio_init(struct imx6ulirq_dev *dev){
    int ret = 0;
    int i = 0;
    /*1,Key initialization*/
    dev->node = of_find_node_by_path("/key");
    if(dev->node == NULL){
        ret = -EINVAL;
        goto failed_findnode;
    }

    /*Loop to find node*/
    for (i = 0; i < KEY_NUM; i++){
        dev->irqkey[i].gpio = of_get_named_gpio(dev->node, "key-gpios", i); 
        if(dev->irqkey[i].gpio < 0){
            printk("can't get gpio %d\r\n",i);
            ret = -EINVAL;
            goto failed_findnode;
        }   
    }
    /*Circular application gpio*/
     for (i = 0; i < KEY_NUM; i++){ 
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        ret = gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        if(ret){
            printk("Failed to request gpio \r\n");
            ret = -EINVAL;
            goto failed_findnode;
    }
    ret = gpio_direction_input(dev->irqkey[i].gpio);
    if(ret < 0){
        goto failed_setinput;
     }
    dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);   /*Get interrupt number*/
     }
    /*2,Key interrupt initialization*/

    dev->irqkey[0].handler = key0_handler;
    dev->irqkey[0].value = KEY0VALUE;

    for(i = 0;i< KEY_NUM; i++){
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, 
                         IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, dev->irqkey[i].name, &imx6ulirq);
        if(ret){
            printk("irq %d request failed!\r\n",i);
            goto failed_irq;
        }
    }

    /*Initialization timer*/
    init_timer(&dev->timer);
    dev->timer.function = timer_func;    /*Timer timeout processing function*/

    return 0;

failed_irq:
failed_setinput:
    for(i = 0;i< KEY_NUM;i++) {
        gpio_free(dev->irqkey[i].gpio);
    }
failed_findnode:
    return ret;
}


/*Entry function*/
static int __init imx6ulirq_init(void){
    int ret = 0;


    /*Register character device*/
    imx6ulirq.major = 0;     /*Kernel automatic application device number*/
    if(imx6ulirq.major){     /*If the equipment number is defined*/
        imx6ulirq.devid = MKDEV(imx6ulirq.major, 0);
        ret = register_chrdev_region(imx6ulirq.devid, IMX6ULIRQ_COUNT, IMX6ULIRQ_NAME); 
    }
    else{   /*Otherwise, the equipment number will be applied automatically*/
        ret = alloc_chrdev_region(&imx6ulirq.devid, 0, IMX6ULIRQ_COUNT, IMX6ULIRQ_NAME);
        imx6ulirq.major = MAJOR(imx6ulirq.devid); /*Save master equipment number*/
        imx6ulirq.minor = MINOR(imx6ulirq.devid); /*Save secondary equipment number*/
    }
    if(ret < 0){
        goto failed_devid;
    }
    printk("imx6ulirq_dev major = %d minor = %d \r\n",imx6ulirq.major,imx6ulirq.minor);  /*Print primary and secondary equipment numbers*/

    /*Add character device*/
    imx6ulirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6ulirq.cdev, &imx6ulirq_fops);
    ret = cdev_add(&imx6ulirq.cdev, imx6ulirq.devid, IMX6ULIRQ_COUNT);
    if(ret < 0){    /*Failed to add character device*/
        goto failed_cdev;
    }

    /*Automatically add device nodes*/
    /*Create class*/
    imx6ulirq.class = class_create(THIS_MODULE, IMX6ULIRQ_NAME); /*class_creat(owner,name);*/
    if(IS_ERR(imx6ulirq.class)){   /*Judge whether the class is created successfully*/
        ret = PTR_ERR(imx6ulirq.class);
        goto failed_class;
    }

    /*Create device*/

    imx6ulirq.device = device_create(imx6ulirq.class, NULL, imx6ulirq.devid, NULL, IMX6ULIRQ_NAME);
     if(IS_ERR(imx6ulirq.device)){   /*Judge whether the class is created successfully*/
        ret = PTR_ERR(imx6ulirq.device);
        goto failed_device;
    }

    /*Initialize IO*/
    ret = keyio_init(&imx6ulirq);
    if(ret < 0) {
        goto failed_keyinit;
    }

    /*Initializing atomic variables*/
    atomic_set(&imx6ulirq.keyval, INVALKEY);
    atomic_set(&imx6ulirq.releasekey, INVALKEY);

    /*Initialize wait queue header*/
    init_waitqueue_head(&imx6ulirq.r_wait);

    return 0;

failed_keyinit:
failed_device:
    class_destroy(imx6ulirq.class);
failed_class:
    cdev_del(&imx6ulirq.cdev);
failed_cdev:
    unregister_chrdev_region(imx6ulirq.devid, IMX6ULIRQ_COUNT);
failed_devid:
    return ret;
}

/*Exit function*/
static void __exit imx6ulirq_exit(void){
    int i = 0;

    /*Release interrupt*/
    for(i = 0;i< KEY_NUM;i++) {
        free_irq(imx6ulirq.irqkey[i].irqnum, &imx6ulirq); 
    }
    /*Release io*/
     for(i = 0;i< KEY_NUM;i++) {
        gpio_free(imx6ulirq.irqkey[i].gpio);
    }
    
    /*Delete timer*/
    del_timer_sync(&imx6ulirq.timer);

    /*Unregister character device*/
    cdev_del(&imx6ulirq.cdev);
    /*Uninstall device*/
    unregister_chrdev_region(imx6ulirq.devid, IMX6ULIRQ_COUNT);

    device_destroy(imx6ulirq.class, imx6ulirq.devid);
    class_destroy(imx6ulirq.class);

}


/*Module inlet and outlet*/
module_init(imx6ulirq_init);
module_exit(imx6ulirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZYC");

Application code:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>


static int fd = 0; /* File descriptor */

/*
28 * SIGIO Signal processing function
29 * @param - signum : Signal value
30 * @return : nothing
31 */
static void sigio_signal_func(int signum)
{
    int err = 0;
    unsigned int keyvalue = 0;

    err = read(fd, &keyvalue, sizeof(keyvalue));
    if(err < 0) {
    /* Read error */
    } else {
    printf("sigio signal! key value=%d\r\n", keyvalue);
    }
}



int main(int argc, char *argv[]) {
    int flags = 0;
    char *filename;


    if(argc != 2){
        printf("Error used!\r\n");
        return -1;
    }
    
    filename = argv[1];        /*Save file name*/
    fd = open(filename, O_RDWR);
    if(fd < 0 ){
        printf("Can't open file %s\r\n",filename);
    }

    /* Set the processing function of signal SIGIO */
    signal(SIGIO, sigio_signal_func);

    fcntl(fd, F_SETOWN, getpid()); /* Tell the kernel the process number of the current process */
    flags = fcntl(fd, F_GETFD); /* Get current process status */
    fcntl(fd, F_SETFL, flags | FASYNC);/* Set the process to enable asynchronous notification */

    while(1) {
    sleep(2);
    }

    close(fd);
    return 0;
    }

Keywords: C Linux Embedded system Linux Driver

Added by RCB on Wed, 16 Feb 2022 05:47:00 +0200