Embedded Linux Development 28 - Asynchronous Notification Experiment

_Using blocked or non-blocked methods in previous blogs to read the key values in the driver is actively read by the application. For non-blocked methods, the application also needs to pollFunctions are constantly polling. The best way is for the driver to actively notify the application that it is accessible, and then the application reads or writes data from the driver, similar to an interrupt. Linux provides an asynchronous notification mechanism to accomplish this.

Asynchronous Notification

1. Introduction to Asynchronous Notification

_Let's start with a review of InterruptionsInterrupt is an asynchronous mechanism provided by the processor. Once an interrupt is configured, the processor can handle other things. When an interrupt occurs, it triggers the interrupt service function that we have set up beforehand to do specific processing in the interrupt service function. For example, GPIO we write while bare.Key interruption experiment, we switch buzzer by keys. After interruption, the processor does not need to check if the keys are pressed all the time because the interruption will be triggered automatically when the keys are pressed. Similarly, Linux The application can access the driver either blocked or non-blocked. If accessed blocked, the application will sleep, wait for the driver to be available, or poll if accessed non-blockedFunctions continuously poll to see if the driver device files are available. Both methods require the application to actively query the device usage. If a similar interrupt mechanism can be provided, it is best to actively tell the application when the driver is accessible.
Signals are created for this purpose, similar to interrupts used on our hardwareThe signal is only at the software level. It is a simulation of the interrupt at the software level. The driver can actively send a signal to the application to report that he can access it. Once the application gets the signal, it can read or write data from the driver. The whole process is equivalent to the application receiving one from the driver.Then the application responds to the interrupt, and the application does not query whether the driver device is accessible during the entire processing. All is told to the application by the driver device itself.
_Blocking, non-blocking and asynchronous notification are three different solutions proposed for different situations. There is no difference between good and bad. In actual work and learning, you can choose the appropriate treatment method according to your actual needs.
_We need to set up an interrupt handler when we use interrupts. Similarly, if you want to use signals in your application, you must set up the signal processing function used by the signals. In your application, you must use the signal function to set up the processing function for the specified signals. The prototype of the signal function is as follows:

sighandler_t signal(int signum, sighandler_t handler)

The_function parameters and return values mean the following:
_signum: The signal to set the processing function.
_handler: A signal processing function.
Return value: Set the previous processing function of the return signal if successful, and return SIG_ERR if failed.
The prototype signal processing function is as follows:

typedef void (*sighandler_t)(int)

_When the CTRL+C key combination on the keyboard is pressed, the SIGINT signal will be sent to the application which is currently occupying the terminal. The default action of SIGINT signal is to close the current application. Here we modify the default processing function of SIGINT signal. When the CTRL+C key combination is pressed, the SIGINT signal will be printed on the terminal first!This line of string before closing the current application.

#include "stdlib.h"
#include "stdio.h"
#include "signal.h"
void sigint_handler(int num)
{
printf("\r\nSIGINT signal!\r\n");
exit(0);
}

int main(void)
{
signal(SIGINT, sigint_handler);
while(1);
return 0;
}

_We set the SIGINT signal processing function to sigint_handler. When CTRL+C is pressed to send the SIGINT signal to the signaltest, the sigint_handler function executes, which first outputs a line of "SIGINT signal!" string, then calls the exit function to close the signaltest application.

2.Signal processing in drivers

2.1 fasync_struct structure

_First, we need to define a fasync_struct structure pointer variable in the driver. The fasync_struct structure content is 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.2 fasync function

_If you want to use asynchronous notification, you need to implement the fasync function in the file_operations operation set in the device driver, which is formatted as follows:

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

In the_fasync function, the fasync_helper function is usually called to initialize the fasync_struct structure pointer defined earlier. The fasync_helper function prototype is as follows:

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

_The first three parameters of the fasync_helper function are the three parameters of the fasync function, and the fourth parameter is the fasync_struct structure pointer variable to be initialized. When an application changes the fac tag through fcntl(fd, F_SETFL, flags | FASYNC), the fac function in the driver file_operations set executes.
The fasync function reference examples in the_driver are as follows:

struct xxx_dev {
......
struct fasync_struct *async_queue; /* Asynchronous Related Structures */
};

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,
......
};

_Closing the driver file requires releasing fasync_struct in the release function in the file_operations set. The release function of fasync_struct is also fasync_helper. The release function parameter reference example is as follows:

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

static struct file_operations xxx_ops = {
......
.release = xxx_release,
};

_Release of fasync_struct is accomplished by calling the xxx_fasync function, but it is ultimately accomplished by the fasync_helper function.

2.3 kill_fasync function

_When a device is accessible, the driver needs to signal the application, which is equivalent to generating an interrupt. The kill_fasync function is responsible for sending the specified signal, and the kill_fasync function prototype is as follows:

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

The_function parameters and return values mean the following:
_fp: fasync_struct to operate on.
sig: The signal to be sent.
_band: set to POLL_IN when read and POLL_OUT when write.
_Return value: None.

3.Application's handling of asynchronous notifications

_The application's handling of asynchronous notifications consists of the following three steps:

3.1 Register signal processing functions

_The application sets the signal processing function based on the signal used by the driver, and the application uses the signal function to set the signal processing function.

3.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.3 Turn on asynchronous notifications

_Use the following two lines to turn on asynchronous notifications:

flags = fcntl(fd, F_GETFL); /* Get the current process state */
fcntl(fd, F_SETFL, flags | FASYNC); /* Turn on current process asynchronous notification */

_The focus is to set the process state to FASYNC through the fcntl function, after which the fasync function in the driver will execute.

Experimental Programming

Driver writing:

#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 1 /* Number of device numbers */
#define IMX6UIRQ_NAME "asyncnoti" /*Name */
#define KEY0VALUE * 0X01 /* KEY0 key value */
#define INVAKEY > 0XFF /* Invalid key value*/
#define KEY_NUM * 1 /* Number of keys */

/* Interrupt IO Description Structures */
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 Device Architecture */
struct imx6uirq_dev{
	dev_t devid;			/* Device Number 	 */	
	struct cdev cdev;		/* cdev 	*/                 
	struct class *class;	/* class 		*/
	struct device *device;	/* equipment 	 */
	int major;				/* Main device number	  */
	int minor;				/* Secondary device number   */
	struct device_node	*nd; /* Device Node */	
	atomic_t keyvalue;		/* Valid key values */
	atomic_t releasekey;	/* Marks whether a completed key is completed at one time, including press and release */
	struct timer_list timer;/* Define a timer*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* Key init predicate array */
	unsigned char curkeynum;				/* Current init key number */
	wait_queue_head_t r_wait;				/* Read Waiting Queue Header */
	struct fasync_struct *async_queue;		/* Asynchronous Related Structures */
};

struct imx6uirq_dev imx6uirq;	/* irq equipment */

/* @description		: Interrupt service function, turn on timer		
 *				  	  The timer is used for button shaking.
 * @param - irq 	: interrupt number 
 * @param - dev_id	: Device 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 for key-press jitter suppression, timer arrives later
 *				  Read the key value again, and if the key is still pressed, the key is valid.
 * @param - arg	: Device Structure Variables
 * @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 release key to complete a complete key press process */
	}               

	if(atomic_read(&dev->releasekey)) {		/* A complete key press 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 IO used by key and set to interrupt mode */
	for (i = 0; i < KEY_NUM; i++) {
		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* Buffer zeroing */
		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* Group 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 for 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, the file structure, has a member variable called private_data
 * 					  Typically, you point private_data at the device structure when open ing.
 * @return 			: 0 Success;Other Failures
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &imx6uirq;	/* Set up 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 from file header address
  * @return          : Number of bytes read, if negative, indicating read failure
  */
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)	/* Return to -EAGAIN without key press */
			return -EAGAIN;
	} else {							/* Blocking access */
		/* Join the waiting queue for wakeup, i.e. press a key */
 		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) { /* Pressed with keys */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* Press mark 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    : Waiting 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 the wait queue header to poll_table */
	
	if(atomic_read(&dev->releasekey)) {		/* Key press */
		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          : Negative numbers indicate 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 is executed when the application calls close to close the driver file
 * @param - inode	: inode node
 * @param - filp    : Device file to open (file descriptor)
 * @return          : Negative numbers indicate that the function failed to execute
 */
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
	return imx6uirq_fasync(-1, filp, 0);
}

/* Device Operation Functions */
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 device 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,Initialization key */
	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");
	
	

Application writing:

#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 Processing Function for Signal SIGIO */
	signal(SIGIO, sigio_signal_func);
	
	fcntl(fd, F_SETOWN, getpid());		/* Set the current process to receive SIGIO signals 	*/
	flags = fcntl(fd, F_GETFD);			/* Get the current process state 			*/
	fcntl(fd, F_SETFL, flags | FASYNC);	/* Set up process to enable asynchronous notification 	*/	

	while(1) {
		sleep(2);
	}

	close(fd);
	return 0;
}

Compile Test

After the driver has loaded successfully, test the interrupt with the following commands:

./asyncnotiApp /dev/asyncnoti

_Press the KEY0 key on the development board and the terminal will output the key value as shown in the figure:

Tags: C Linux Embedded system stm32 imx6ull

Posted on Sun, 26 Sep 2021 12:05:42 -0400 by Ausx