Asynchronous notification in Linux device driver
The signal is similar to the "interrupt" used in hardware, but the signal is at the software level
of 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.
Blocking, non blocking and asynchronous:
-block IO Yes, wait until the device is accessible -Non blocking IO Yes, query whether the device can be accessed -Asynchronous notifications are devices that notify themselves that they can access
The driver and application do the following work respectively to realize asynchronous notification:
Application layer receives SIGIO
Like other signals, the application layer needs to register a signal processing function,
The way to register is to use signal()
In addition, the application layer also needs to add itself to the driver's notification linked list. The added code is as follows
fcntl(dev_fd,F_SETOWN,getpid()); int oflags = fcntl(dev_fd,F_GETFL); fcntl(dev_fd,F_SETFL,oflags|FASYNC); ... while(1);
After completing the above work, the application layer program can wait for the arrival of SIGIO.
Drive send SIGIO
After the application layer is registered, the final sending depends on the processing method of the device driver. In order to make the device support asynchronous notification mechanism, refer to the interface of the application layer. The driver involves three tasks.
Support F_SETOWN command. FILP - > F can be set in this command_ Owner is the ID of the corresponding process, which has been done by the kernel
Support F_SETFL, whenever the FASYNC flag changes, fasync() in the driver will be executed. so, fasync() in the driver should be implemented.
When device resources are available, use kill_fasync() send SIGIO
The fasync() function is file_ Function pointer int (* fasync) in operations (int, struct file *, int);
When the application changes the fasync flag through "fcntl(fd, F_SETFL, flags | FASYNC)", the driver file_ The fasync function in the operations operation set is executed.
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*mremap)(struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); ## int (*fasync) (int, struct file *, int); ...... };
In order to implement the above three functions in the kernel, the driver needs to use 1 structure + 2 API s, and the structure is struct fasync_struct, the function is fasync_helper() and kill_fasync()
struct fasync_struct { spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; /* singly linked list */ struct file *fa_file; struct rcu_head fa_rcu; };
fasync_ The helper () function is to put a fasync_ The object of struct is registered into the kernel. When the application layer executes fcntl(dev_fd, F_SETFL, oflags|FASYNC), it will call back the driver's fops.fasync(), so fasync is usually used_ Helper () is placed in the implementation of fasync ().
/** *fasync_helper - Put a fasync_ Register struct objects into the kernel *@fd:File descriptor, passed in by fasync *@filp:file Pointer, passed in by fasync *@sig:SIGIO is usually used as the signal type *@dev_fasync:Pre prepared fasync_ Pointer to struct object */ int fasync_helper(int fd, struct file * filp, int sig, struct fasync_struct ** dev_fasync);
The following API is to release SIGIO to different locations according to different requirements.
/** *kill_fasync - Release a signal *@dev_fasync:Use fasync beforehand_ The helper is registered into the kernel's fasync_ Pointer to struct object *@filp:file Pointer, passed in by fasync *@sig:SIGIO is usually used as the signal type *@flag:Flag, usually POLLIN if the resource is readable and POLLOUT if the resource is writable */ void kill_fasync(struct fasync_struct **dev_fasync, int sig, int flag);
Routine:
//drive #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/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/of_irq.h> #include <linux/irq.h> #include <linux/fcntl.h> #include <linux/wait.h> #include <linux/poll.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define IMX6UIRQ_CNT one /* Number of equipment numbers */ #define IMX6UIRQ_NAME "asyncnoti" /* name */ #define KEY0VALUE 0X01 /* KEY0 key value */ #define INVAKEY 0XFF /* Invalid key value*/ #define KEY_NUM one /* Number of keys */ /* Interrupt IO description structure */ struct irq_keydesc { int gpio; /* gpio */ int irqnum; /* interrupt number */ unsigned char value; /* Key value corresponding to key */ char name[10]; /* name */ irqreturn_t (*handler)(int, void *); /* Interrupt service function */ }; /* imx6uirq Equipment structure */ struct imx6uirq_dev{ dev_t devid; /* Equipment number */ struct cdev cdev; /* cdev */ struct class *class; /* class */ struct device *device; /* equipment */ int major; /* Main equipment No */ int minor; /* Secondary equipment No */ struct device_node *nd; /* Device node */ atomic_t keyvalue; /* Valid key values */ atomic_t releasekey; /* A key that marks whether a single completion is completed, including press and release */ struct timer_list timer;/* Define a timer*/ struct irq_keydesc irqkeydesc[KEY_NUM]; /* Key init array */ unsigned char curkeynum; /* Current init key number */ wait_queue_head_t r_wait; /* Read wait queue header */ struct fasync_struct *async_queue; /* Asynchronous correlation structure */ }; struct imx6uirq_dev imx6uirq; /* irq equipment */ /* @description : Interrupt the service function and start the timer * The timer is used for key debounce. * @param - irq : interrupt number * @param - dev_id : Equipment structure. * @return : Interrupt execution result */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id; dev->curkeynum = 0; dev->timer.data = (volatile long)dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms timing */ return IRQ_RETVAL(IRQ_HANDLED); } /* @description : Timer service function, used to press the key to eliminate jitter. After the timer arrives * Read the key value again. If the key is still pressed, it means that the key is valid. * @param - arg : Equipment structure variable * @return : nothing */ void timer_function(unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; num = dev->curkeynum; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); /* Read IO value */ if(value == 0){ /* Press the key */ atomic_set(&dev->keyvalue, keydesc->value); } else{ /* Key release */ atomic_set(&dev->keyvalue, 0x80 | keydesc->value); atomic_set(&dev->releasekey, 1); /* Mark and release the key to complete a complete key pressing process */ } if(atomic_read(&dev->releasekey)) { /* A complete key pressing process */ if(dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); /* Release SIGIO signal */ } #if 0 /* Wake up process */ if(atomic_read(&dev->releasekey)) { /* Complete a key press process */ /* wake_up(&dev->r_wait); */ wake_up_interruptible(&dev->r_wait); } #endif } /* * @description : Key IO initialization * @param : nothing * @return : nothing */ static int keyio_init(void) { unsigned char i = 0; char name[10]; int ret = 0; imx6uirq.nd = of_find_node_by_path("/key"); if (imx6uirq.nd== NULL){ printk("key node not find!\r\n"); return -EINVAL; } /* Extract GPIO */ for (i = 0; i < KEY_NUM; i++) { imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i); if (imx6uirq.irqkeydesc[i].gpio < 0) { printk("can't get key%d\r\n", i); } } /* Initialize the IO used by the key and set it to interrupt mode */ for (i = 0; i < KEY_NUM; i++) { memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* Buffer reset */ sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* Combination name */ gpio_request(imx6uirq.irqkeydesc[i].gpio, name); gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); #if 0 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio); #endif } /* Application interruption */ imx6uirq.irqkeydesc[0].handler = key0_handler; imx6uirq.irqkeydesc[0].value = KEY0VALUE; for (i = 0; i < KEY_NUM; i++) { ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq); if(ret < 0){ printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } } /* Create timer */ init_timer(&imx6uirq.timer); imx6uirq.timer.function = timer_function; /* Initialize wait queue header */ init_waitqueue_head(&imx6uirq.r_wait); return 0; } /* * @description : open device * @param - inode : inode passed to driver * @param - filp : The device file has a file structure called private_ Member variable of data * Generally, private is used when open ing_ Data points to the device structure. * @return : 0 success; Other failures */ static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = &imx6uirq; /* Set private data */ return 0; } /* * @description : Read data from device * @param - filp : Device file to open (file descriptor) * @param - buf : Data buffer returned to user space * @param - cnt : Length of data to read * @param - offt : Offset relative to the first address of the file * @return : The number of bytes read. If it is negative, it indicates that the read failed */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; unsigned char releasekey = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; if (filp->f_flags & O_NONBLOCK) { /* Non blocking access */ if(atomic_read(&dev->releasekey) == 0) /* If no key is pressed, return to - EAGAIN */ return -EAGAIN; } else { /* Blocking access */ /* Join the waiting queue and wait to be awakened, that is, a key is pressed */ ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); if (ret) { goto wait_error; } } keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if (releasekey) { /* A key is pressed */ if (keyvalue & 0x80) { keyvalue &= ~0x80; ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(&dev->releasekey, 0);/* Press the flag to clear */ } else { goto data_error; } return 0; wait_error: return ret; data_error: return -EINVAL; } /* * @description : poll Function to handle non blocking access * @param - filp : Device file to open (file descriptor) * @param - wait : Wait list (poll_table) * @return : Device or resource status, */ unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; poll_wait(filp, &dev->r_wait, wait); /* Add wait queue header to poll_ In table */ if(atomic_read(&dev->releasekey)) { /* Press the key */ mask = POLLIN | POLLRDNORM; /* Return to PLLIN */ } return mask; } /* * @description : fasync Function to handle asynchronous notifications * @param - fd : File descriptor * @param - filp : Device file to open (file descriptor) * @param - on : pattern * @return : A negative number indicates that the function failed to execute */ static int imx6uirq_fasync(int fd, struct file *filp, int on) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; return fasync_helper(fd, filp, on, &dev->async_queue); } /* * @description : release Function, which will be executed when the application calls close to close the driver file * @param - inode : inode node * @param - filp : Device file to open (file descriptor) * @return : A negative number indicates that the function failed to execute */ static int imx6uirq_release(struct inode *inode, struct file *filp) { return imx6uirq_fasync(-1, filp, 0); } /* Device operation function */ static struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .read = imx6uirq_read, .poll = imx6uirq_poll, .fasync = imx6uirq_fasync, .release = imx6uirq_release, }; /* * @description : Drive entry function * @param : nothing * @return : nothing */ static int __init imx6uirq_init(void) { /* 1,Build equipment number */ if (imx6uirq.major) { imx6uirq.devid = MKDEV(imx6uirq.major, 0); register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME); } else { alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME); imx6uirq.major = MAJOR(imx6uirq.devid); imx6uirq.minor = MINOR(imx6uirq.devid); } /* 2,Register character device */ cdev_init(&imx6uirq.cdev, &imx6uirq_fops); cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT); /* 3,Create class */ imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.class)) { return PTR_ERR(imx6uirq.class); } /* 4,Create device */ imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.device)) { return PTR_ERR(imx6uirq.device); } /* 5,Start button */ atomic_set(&imx6uirq.keyvalue, INVAKEY); atomic_set(&imx6uirq.releasekey, 0); keyio_init(); return 0; } /* * @description : Drive exit function * @param : nothing * @return : nothing */ static void __exit imx6uirq_exit(void) { unsigned i = 0; /* Delete timer */ del_timer_sync(&imx6uirq.timer); /* Delete timer */ /* Release interrupt */ for (i = 0; i < KEY_NUM; i++) { free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq); } cdev_del(&imx6uirq.cdev); unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); device_destroy(imx6uirq.class, imx6uirq.devid); class_destroy(imx6uirq.class); } module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL");
//APP #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" #include "sys/select.h" #include "sys/time.h" #include "linux/ioctl.h" #include "signal.h" static int fd = 0; /* File descriptor */ /* * SIGIO Signal processing function * @param - signum : Signal value * @return : nothing */ 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); } } /* * @description : main main program * @param - argc : argv Number of array elements * @param - argv : Specific parameters * @return : 0 success; Other failures */ int main(int argc, char *argv[]) { int flags = 0; char *filename; if (argc != 2) { printf("Error Usage!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("Can't open file %s\r\n", filename); return -1; } /* Set the processing function of signal SIGIO */ signal(SIGIO, sigio_signal_func); fcntl(fd, F_SETOWN, getpid()); /* Set the current process to receive SIGIO signal */ 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; }