按键驱动:异步通知
4种读按键方式对比:
查询:非常耗费CPU资源
中断:会休眠,若一直没有按键动作,将一直休眠
Poll:可以指定超时时间,有按键动作立即唤醒,或超时后唤醒
异步通知:驱动程序主动通知应用程序可以读数据了
前3种都是应用程序读的时候资源并不一定就绪,异步通知是驱动程序通知应用程序去读,此时资源已经就绪
思路指导:
1. 应用程序:注册信号处理函数,接收到信号时会调用此函数
2. 谁发?驱动程序
3. 发给谁?应用程序,因此app需要告诉驱动自己的pid
4. 怎么发?调用kill_fasync()
编程要点:
驱动程序:
1. 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,驱动程序无需处理。
2. 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的file_operations->fasync()得以执行。
3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号。
应用程序:
signal(SIGIO, my_signal_fun); // 设置接收到SIGIO信号后的回调函数 // SIGIO表示有数据读或有空间写了 // 在回调函数中去执行read, write fcntl(fd, F_SETOWN, getpid()); // 告诉内核发给谁 oflags = fcntl(fd, F_GETFL); // 若要修改标志,先读出原始的 fcntl(fd, F_SETFL, oflags | FASYNC); // 改变fasync标记,最终会调用到驱动的fops->fasync()
源码
驱动:
/* * 引脚:PI0,1,2 */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/leds.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/module.h> #include <linux/pinctrl/consumer.h> #include <linux/err.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <asm/io.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <asm/signal.h> struct pin_desc{ int pin; int val; }; /* * 按下时,返回:0x81, 0x82, 0x83 * */ static struct pin_desc pins_desc[3] = { {NUC970_PI0, 0x1}, {NUC970_PI1, 0x2}, {NUC970_PI2, 0x3}, }; static unsigned char val; static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq); int evpress = 0; struct fasync_struct *buttons_async; static irqreturn_t buttons_handler(int irq, void *dev_id) { struct pin_desc *pin = (struct pin_desc *)dev_id; //int res; //res = gpio_get_value(pin->pin); val = 0x80 | pin->val; evpress = 1; wake_up_interruptible(&buttons_waitq); kill_fasync(&buttons_async, SIGIO, POLL_IN); // 发送信号 return IRQ_HANDLED; } static int buttons_open(struct inode *inode, struct file *filp) { // request_irq会自动设置引脚,此处不再配置 request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]); request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]); request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]); return 0; } static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos) { if (count != 1) { return -EINVAL; } wait_event_interruptible(buttons_waitq, evpress); evpress = 0; copy_to_user(buf, &val, 1); return 1; } int buttons_release (struct inode *inode, struct file *filp) { free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]); free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]); free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]); return 0; } static unsigned int buttons_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; poll_wait(file, &buttons_waitq, wait); // 不会立即休眠,只是把进程挂到buttons_waitq队列 if (evpress) { mask = POLLIN | POLLRDNORM; } return mask; } // 每当应用程序修改FAYNC标记时,此程序就会被调用 static int buttons_fasync(int fd, struct file *filp, int on) { return fasync_helper(fd, filp, on, &buttons_async); // 此函数会初始化buttons_async } static struct file_operations buttons_fops = { .owner = THIS_MODULE, .open = buttons_open, .read = buttons_read, .release = buttons_release, .poll = buttons_poll, .fasync = buttons_fasync, }; static int major; static struct class *buttons_class; static struct device *button_device; static int buttons_init(void) { major = register_chrdev(0, "buttons", &buttons_fops); buttons_class = class_create(THIS_MODULE, "buttons"); button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons"); return 0; } static void buttons_exit(void) { device_destroy(buttons_class, MKDEV(major, 0)); class_destroy(buttons_class); unregister_chrdev(major, "buttons"); } module_init(buttons_init); module_exit(buttons_exit); MODULE_LICENSE("GPL");
测试程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <poll.h> #include <signal.h> #include <fcntl.h> int fd; static void my_signal_fun(int sig) { unsigned char keyval; read(fd, &keyval, 1); printf("keyval = 0x%x\n", keyval); } int main(void) { int oflags; fd = open("/dev/buttons", O_RDONLY); if (fd < 0) { printf("Can't open /dev/buttons\n"); return -1; } signal(SIGIO, my_signal_fun); fcntl(fd, F_SETOWN, getpid()); // 告诉驱动发给谁 oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, oflags | FASYNC); while (1) { sleep(1000); } close(fd); return 0; }