DeviceDriver (eight): asynchronous notification

One: Asynchronous notification

        The core of asynchronous notification is the signal, which is different from blocking and non-blocking methods. The blocking mode access will make the application dormant and waiting for the drive device to be available. The non-blocking mode will continuously poll through the application’s poll function. Check whether the drive device file can be used. Both of these methods require the application to actively query the usage of the device. Asynchronous notification means that the driver actively informs the application that it can be accessed. After the application receives the signal, it can read and write to the drive device, similar to the "interrupt" processing method used on hardware.

Signals supported by Linux: These signals are equivalent to interrupt numbers. Different interrupt numbers represent different interrupts. Different interrupts do different processing. Therefore, the driver can implement different functions by sending different signals to the application. .

#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

If the signal is used in the application, then the signal processing function used by the signal must be set, and the signal function is used in the application to set the processing function of the specified signal:

signum: The signal to set the processing function

handler: signal processing function

Return value: If the setting is successful, it returns the previous processing function of the signal, and if the setting fails, it returns SIG_ERR.

sighandler_t signal(int signum, sighandler_t handler)

Signal processing function:

typedef void (*sighandler_t)(int)

Two: signal processing in the drive

1, fasync_struct structure

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

2. fasync function

If you want to use asynchronous notification, you need to implement the fasync function in file_operations:

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

The fasync function generally initializes the previously defined fasync_strcut structure pointer by calling the fasync_helper function:

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

When the application program changes the fasync flag through the function "fcntl", the fasync function in the driver file_operations will be executed:

(1) Application:

Focus on the last three lines of code

fcntl(fd, F_SETOWN, getpid()); //Tell the kernel
 oflags = fcntl(fd, F_GETFL); // Get the current process status
fcntl(fd, F_SETFL, oflags | FASYNC);//Change fasync, it will call the driver The fasync function pointer corresponds to the function.

void signal_fun(int signum)
{
    unsigned char key_val;
    read(fd, &key_val, 1);
    printf("key_val: 0x%x\n", key_val);
}

int main(int argc, char **argv)
{
    unsigned char key_val;
    int ret;
    int oflags;
    signal(SIGIO, signal_fun);
    
    fd = open("/dev/signal", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }

    fcntl(fd, F_SETOWN, getpid()); //告诉内核
    oflags = fcntl(fd, F_GETFL);   // 获取当前进程状态
    fcntl(fd, F_SETFL, oflags | FASYNC);//改变fasync,会调用驱动中的fasync函数指针对应的函数。

}

(2) Driver:

static int signal_drv_fasync(int fd, struct file *filp, int on)
{
    printk("driver: signal_drv_fasync\n");
    return fasync_helper (fd, filp, on, &signal_fasync);
}

static int signal_drv_release(struct inode *inode, struct file *filp)
{
    return signal_drv_fasync(-1, filp, 0); /* 删除异步通知 */
}

static struct file_operations signal_drv_fops = 
{
    .fasync  = signal_drv_fasync,
    .release = signal_drv_release,
};

3. Kill_fasync function

When the device is accessible, the driver needs to send a signal to the application, which is equivalent to generating an "interrupt". The kill_fasync function is responsible for sending the specified signal:

fp: the fasync_struct to be operated

sig: the signal to be sent

band: set to POLL_IN when readable, set to POLL_OUT when writing

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

static irqreturn_t xxx_irq(int irq, void *dev_id)
{
    struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;

    pinval = xxx_gpio_getpin(pindesc->pin);

    kill_fasync(&signal_fasync, SIGIO, POLL_IN);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

Three: Example

1. Application

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

#define LED_ON  1
#define LED_OFF 0

int fd1, fd2;
char keyval, ledval;
char val = 0;

static void key_fasync(int signum)
{
    read(fd1, &keyval, sizeof(keyval));
    if(keyval == LED_ON)
    {
        val = !val;
    }
    if(val == LED_ON)
    {
        ledval = LED_ON;
        write(fd2, &ledval, sizeof(ledval));
    }
    else if(val == LED_OFF)
    {
        ledval = LED_OFF;
        write(fd2, &ledval, sizeof(ledval));
    }
}

int main(int argc, char const *argv[])
{
    int flags = 0;
    if(argc != 3)
    {
        printf("Error Usage : ./app /dev/key /dev/led\n");
    }

    fd1 = open(argv[1], O_RDWR | O_NONBLOCK);
    if(fd1 < 0)
    {
        printf("%s can't open!\n", argv[1]);
        return -1;
    }

    fd2 = open(argv[2], O_RDWR);
    if(fd2 < 0)
    {
        printf("%s can't open!\n", argv[2]);
        return -1;
    }

    signal(SIGIO, key_fasync);
    fcntl(fd1, F_SETOWN, getpid());
    flags = fcntl(fd1, F_GETFD);
    fcntl(fd1, F_SETFL, flags | FASYNC);

    while (1)
    {
        sleep(2);
    }

    close(fd2);
    close(fd1);

    return 0;
}

2. Button drive, (LED drive is the same as Chapter 7)

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/poll.h>
#include <linux/fcntl.h>

#define KEY_NUM     1

struct st_keyirq
{
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];
    irqreturn_t (*handler)(int, void *);
};

struct st_keydev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    struct mutex lock;
    atomic_t keyvalue;
    atomic_t releasekey;
    struct timer_list timer;
    struct st_keyirq irqkeydesc[KEY_NUM];
    unsigned char curkeynum;
    struct fasync_struct *key_fasync;
};
struct st_keydev keydev;
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);


static int key_open(struct inode *inode, struct file *file)
{
    file->private_data = &keydev;

    if(mutex_trylock(&keydev.lock) == 0)
    {
        return -EBUSY;
    }
    return 0;
}

static ssize_t key_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    struct st_keydev *dev = (struct st_keydev *)file->private_data;
    unsigned char releasekey = 0;
    char val = 0;
    int retvalue = 0;


    releasekey = atomic_read(&dev->releasekey);

    if(releasekey)
    {
        val = 1;
        retvalue = copy_to_user(buf, &val, sizeof(val));
        atomic_set(&dev->releasekey, 0);
    }
    else
    {
        val = 0;
        retvalue = copy_to_user(buf, &val, sizeof(val));
    }
    
    return 0;
}

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

    return fasync_helper(fd, filp, on, &dev->key_fasync);
}

static int key_release(struct inode *inode, struct file *file)
{
    struct st_keydev *dev = (struct st_keydev *)file->private_data;

    mutex_unlock(&dev->lock);

    return key_fasync(-1, file, 0);
};


struct file_operations key_fops = {
    .owner      =   THIS_MODULE,
    .open       =   key_open,
    .read       =   key_read,
    .fasync     =   key_fasync,
    .release    =   key_release, 
};

static irqreturn_t key1_handler(int irq, void *keydev)
{
    struct st_keydev *dev = (struct st_keydev *)keydev;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)keydev;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(5));
    return IRQ_RETVAL(IRQ_HANDLED);
}

void timer_function(unsigned long data)
{
    unsigned char value;
    unsigned char num;
    struct st_keyirq *keydesc;
    struct st_keydev *dev = (struct st_keydev *)data;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);
    if(value == 0)
    {
        atomic_set(&dev->releasekey, 0);
    }
    else
    {
        atomic_set(&dev->releasekey, 1);
        if(dev->key_fasync)
        {
            kill_fasync(&dev->key_fasync, SIGIO, POLL_IN);
        }
    }
}

static int key1_init(void)
{
    int retval = 0;
    unsigned char i = 0;

    mutex_init(&keydev.lock);

    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL)
    {
        printk("Key node not find!\n");
        return -EINVAL;
    }
    else
    {
        printk("key node find!\n");
    }

    for (i = 0; i < KEY_NUM; i++)
    {
        keydev.irqkeydesc[i].gpio = of_get_named_gpio(keydev.nd, "key-gpio", i);
        if(keydev.irqkeydesc[i].gpio < 0)
        {
            printk("Can't get key%d!\n", i);
            return -EINVAL;
        }
    }

    for (i = 0; i < KEY_NUM; i++)
    {
        memset(keydev.irqkeydesc[i].name, 0, sizeof(keydev.irqkeydesc[i].name));
        sprintf(keydev.irqkeydesc[i].name, "KEY%d", i);
        retval = gpio_request(keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].name);
        if(retval < 0)
        {
            printk("request failed!\n");
            return -EINVAL;
        }
        retval = gpio_direction_input(keydev.irqkeydesc[i].gpio);
        if(retval < 0)
        {
            printk("set input failed!\n");
            return -EINVAL;
        }
        keydev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keydev.nd, i);

        printk("key%d:gpio=%d, irqnum=%d\n", i, keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].irqnum);
    }

    keydev.irqkeydesc[0].handler = key1_handler;
    for (i = 0; i < KEY_NUM; i++)
    {
        /* code */
        retval = request_irq(keydev.irqkeydesc[i].irqnum, keydev.irqkeydesc[i].handler,
                                IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, keydev.irqkeydesc[i].name, &keydev);
        if(retval < 0)
        {
            printk("irq %d request failed!\n", keydev.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    if(keydev.major)
    {
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, 1, "key");
    }
    else
    {
        alloc_chrdev_region(&keydev.devid, 0, 1, "key");
        keydev.major = MAJOR(keydev.devid);
        keydev.minor = MINOR(keydev.devid);
    }
    printk("keydev major : %d minor : %d\n", keydev.major, keydev.minor);

    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    cdev_add(&keydev.cdev, keydev.devid, 1);

    keydev.class = class_create(THIS_MODULE, "key");
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, "key");

    init_timer(&keydev.timer);
    keydev.timer.function = timer_function;

    return 0;
}

static void key1_exit(void)
{
    unsigned char i = 0;

    del_timer_sync(&keydev.timer);
    for (i = 0; i < KEY_NUM; i++)
    {
        /* code */
        free_irq(keydev.irqkeydesc[i].irqnum, &keydev);
    }
    
    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);

    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid, 1);
}

module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/103920886