一、異步通知簡介
異步通知:驅動程式可以主動發送信号給應用程式,通知應用程式自己可以通路了,應用程式擷取到信号以後就可以從驅動裝置中讀取或者寫入資料。
異步通知的核心是信号,在 arch/arm/include/uapi/asm/signal.h 檔案中定義了 linux 所支援的所有信号:
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
這些信号除了 SIGKILL(9) 和 SIGSTOP(19) 這兩個信号不能被忽略,其他信号都可以忽略。
二、驅動中的信号處理
1、fasync_struct結構體
首先需要在驅動程式中定義一個 fasync_struct 結構體指針變量,fasync_struct 結構體定義如下:
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函數
使用異步通知,需要在 file_operations 操作集中添加 fasync 函數,當應用程式調用 fcntl 函數時,fasync 函數就會執行,函數格式如下:
fasync 函數内一般調用 fasync_helper 函數來初始化前面定義的 fasync_struct 結構體指針,fasync_helper 函數定義如下:
前三個參數是 fasync 函數的三個參數,第四個參數是需要初始化的 fasync_struct 結構體指針。
3、kill_fasync函數
當裝置可以通路的時候,驅動程式需要向應用程式發出信号, kill_fasync 函數負責發送指定的信号, kill_fasync 函數原型如下所示:
p:要操作的 fasync_struct。
sig:要發送的信号。
band:可讀時設定為 POLL_IN,可寫時設定為 POLL_OUT。
4、驅動程式參考示例
struct xxx_dev {
......
struct fasync_struct *async_queue; /* 異步相關結構體 */
};
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 删除異步通知 */
}
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 = {
......
.release = xxx_release,
.fasync = xxx_fasync,
......
};
在關閉應用程式前,要在 release 函數釋放 fasync_struct,使用 fasync 函數釋放。
三、應用程式對異步通知的處理
1、使用signal函數指定信号處理函數
signal 函數原型如下:
signum:要設定處理函數的信号。
handler:信号的處理函數。
傳回值:設定成功的話傳回信号的前一個處理函數,設定失敗的話傳回 SIG_ERR。
信号處理函數原型如下:
2、将本應用程式的程序号告訴核心
使用 fcntl(fd, F_SETOWN, getpid()) 将本應用程式的程序号告訴給核心。
3、開啟異步通知
使用如下兩行程式開啟異步通知:
flags = fcntl(fd, F_GETFL); /* 擷取目前的程序狀态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啟目前程序異步通知功能 */
重點就是通過 fcntl 函數設定程序狀态為 FASYNC,經過這一步,驅動程式中的 fasync 函數就會執行。
四、異步通知實驗
按鍵中斷後啟動定時器,在定時中斷中再次讀取按鍵狀态,以此實作消抖。
1、添加裝置樹節點
1)添加裝置節點
key_input {
compatible = "key_test";
status = "okay";
pinctrl-names = "default";
pinctrl = <&keyinput>;
gpio_key = <&gpio1 18 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupt = <18 IRQ_TYPE_EDGE_BOTH>;
};
第 7 行,設定 interrupt-parent 屬性值為“gpio1”,因為 KEY0 所使用的 GPIO 為 GPIO1_IO18,也就是設定 KEY0 的 GPIO 中斷控制器為 gpio1。
第 8 行,設定 interrupts 屬性,也就是設定中斷源,第一個 cells 的 18 表示 GPIO1 組的 18 号 IO。 IRQ_TYPE_EDGE_BOTH 定義在檔案 include/linux/irq.h 中。
2)添加pinctrl節點
在 iomuxc 節點下的子節點 imx6ul-evk 中添加 pinctrl 節點:
keyinput: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xf080
>;
};
2、添加裝置結構體
/* 裝置結構體 */
struct fasync_dev{
dev_t devid; //裝置号
int major; //主裝置号
int minor; //次裝置号
struct cdev cdev; //字元裝置
struct class *class; //類
struct device *device; //裝置
int key_gpio; //按鍵gpio編号
struct device_node *key_nd; //按鍵節點
int irq_num; //中斷号
struct timer_list timer; //定時器
struct fasync_struct *key_fasync; //異步通知結構體指針
atomic_t key_state; //按鍵狀态
};
struct fasync_dev fasync;
3、編寫加載和解除安裝注冊函數
加載和解除安裝注冊函數如下:
module_init(fasync_init);
module_exit(fasync_exit);
入口函數:
/* 入口函數 */
static int __init fasync_init(void)
{
int ret = 0;
atomic_set(&fasync.key_state, KEY_LOOSE);
/* 處理裝置号 */
fasync.major = 0;
if(fasync.major){ //設定了主裝置号
fasync.minor = 0;
fasync.devid = MKDEV(fasync.major, fasync.minor);
ret = register_chrdev_region(fasync.devid, DEVICE_CNT, DEVICE_NAME);
if(ret < 0){ //注冊失敗
printk("fail to register devid\r\n");
return -EINVAL;
}
}else{ //沒有設定主裝置号
ret = alloc_chrdev_region(&fasync.devid, 0, DEVICE_CNT, DEVICE_NAME);
if(ret < 0){ //申請裝置号失敗
printk("fail to register devid\r\n");
return -EINVAL;
}
fasync.major = MAJOR(fasync.devid);
fasync.minor = MINOR(fasync.devid);
}
printk("major = %d\r\nminor = %d\r\n", fasync.major, fasync.minor);
/* 注冊字元裝置 */
fasync.cdev.owner = THIS_MODULE;
cdev_init(&fasync.cdev, &fasync_fops);
ret = cdev_add(&fasync.cdev, fasync.devid, DEVICE_CNT);
if(ret < 0){
ret = -EINVAL;
printk("fail too add cdev\r\n");
goto fail_add_cdev;
}
/* 自動注冊裝置節點 */
fasync.class = NULL;
fasync.device = NULL;
fasync.class = class_create(THIS_MODULE, DEVICE_NAME);
if(fasync.class == NULL){
ret = -EINVAL;
printk("fail to create class\r\n");
goto fail_create_class;
}
fasync.device = device_create(fasync.class, NULL, fasync.devid, NULL, DEVICE_NAME);
if(fasync.device == NULL){
ret = -EINVAL;
printk("fail to create device\r\n");
goto fail_create_device;
}
printk("driver init\r\n");
return 0;
fail_add_cdev:
unregister_chrdev_region(fasync.devid, DEVICE_CNT);
fail_create_class:
cdev_del(&fasync.cdev);
unregister_chrdev_region(fasync.devid, DEVICE_CNT);
fail_create_device:
class_destroy(fasync.class);
cdev_del(&fasync.cdev);
unregister_chrdev_region(fasync.devid, DEVICE_CNT);
return ret;
}
出口函數:
如果激活了定時器,在解除安裝子產品時,一定要先删除定時器,否則将無法解除安裝子產品。
/* 出口函數 */
static void __exit timer_exit(void)
{
gpio_free(fasync.key_gpio); //登出gpio
del_timer_sync(&fasync.timer); //删除定時器
free_irq(fasync.irq_num, &fasync); //登出中斷
device_destroy(fasync.class, fasync.devid); //删除裝置
class_destroy(fasync.class); //删除類
cdev_del(&fasync.cdev); //删除字元裝置
unregister_chrdev_region(fasync.devid, DEVICE_CNT); //登出裝置号
}
4、編寫按鍵中斷初始化函數
/* 按鍵初始化函數 */
static int Key_init(void)
{
int ret = 0;
/* 擷取裝置節點 */
fasync.key_nd = of_find_node_by_name(NULL, "key_input");
if(fasync.key_nd == NULL){
printk("fail to get node\e\n");
return -EINVAL;
}
/* 擷取gpio編号 */
fasync.key_gpio = of_get_named_gpio(fasync.key_nd, "gpio_key", 0);
if(fasync.key_gpio < 0){
printk("fail to get gpio num\r\n");
return -EINVAL;
}
/* 設定gpio屬性 */
gpio_request(fasync.key_gpio, "key");
gpio_direction_input(fasync.key_gpio);
/* 擷取中斷号 */
fasync.irq_num = gpio_to_irq(fasync.key_gpio);
/* 申請中斷 */
ret = request_irq(fasync.irq_num, key_handler, IRQF_TRIGGER_FALLING, "key_irq", &fasync);
return 0;
}
5、編寫中斷回調函數
/* 中斷處理函數 */
static irqreturn_t key_handler(int irq_num, void *dev_id)
{
struct fasync_dev *dev = (struct fasync_dev *)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); //開啟定時器
return IRQ_RETVAL(IRQ_HANDLED);
}
6、編寫定時器初始化函數
/* 定時器初始化函數 */
void timer_init(void)
{
init_timer(&fasync.timer); //初始化定時器
fasync.timer.function = timer_function; //注冊定時回調函數
fasync.timer.data = (unsigned long)&fasync; //設定回調函數參數
}
7、編寫定時回調函數
linux 核心的定時器啟動後隻會運作一次,如果要連續定時,需要在回調函數中重新啟動定時器。
/* 定時回調函數 */
void timer_function(unsigned long arg)
{
struct fasync_dev *dev = (struct fasync_dev *)arg;
if(gpio_get_value(dev->key_gpio) == 0){
atomic_set(&dev->key_state, KEY_PRESS);
kill_fasync(&dev->key_fasync, SIGIO, POLL_IN); //發送信号給應用程式資料可讀
}
}
8、編寫裝置的具體操作函數
/* open函數 */
static int fasync_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &fasync; //設定私有資料
timer_init();
ret = Key_init();
return 0;
}
/* read函數 */
static ssize_t fasync_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int key_state,ret;
struct fasync_dev *dev = filp->private_data;
key_state = atomic_read(&dev->key_state);
if(key_state == KEY_PRESS){
ret = copy_to_user(buf, &key_state, cnt);
}
atomic_set(&dev->key_state, KEY_LOOSE);
return 0;
}
/* fasync函數 */
int fasync_key(int fd, struct file *filp, int on)
{
struct fasync_dev *dev = filp->private_data;
if(fasync_helper(fd, filp, on, &dev->key_fasync) < 0)
return -EIO;
return 0;
}
/* release函數 */
static int fasync_release(struct inode *inode, struct file *filp)
{
fasync_key(-1, filp, 0); //删除異步通知
return 0;
}
/* 操作函數集合 */
static const struct file_operations fasync_fops = {
.owner = THIS_MODULE,
.open = fasync_open,
.read = fasync_read,
.fasync = fasync_key,
.release = fasync_release,
};
4、添加頭檔案
參考 linux 核心的驅動代碼時,找到可能用到的頭檔案,添加進工程。在調用系統調用函數或庫函數時,在終端使用 man 指令可檢視調用的函數需要包含哪些頭檔案。
man 指令數字含義:1:标準指令 2:系統調用 3:庫函數
添加以下頭檔案:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/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_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#define DEVICE_NAME "fasync"
#define DEVICE_CNT 1
#define KEY_PRESS 0
#define KEY_LOOSE 1
5、添加 License 和作者資訊
驅動的 License 是必須的,缺少的話會報錯,在檔案最末端添加以下代碼:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");
6、編寫測試應用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/select.h"
#include "signal.h"
#include "fcntl.h"
static int fd = 0;
static void signal_func(int arg)
{
static key_state = 1;
read(fd, &key_state, sizeof(key_state));
if(key_state == 0)
printf("key press\r\n");
key_state = 1;
}
int main(int argc, char *argv[])
{
int flags = 0;
char *filename;
int key_state = 1;
if(argc != 2){
printf("missing parameter!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR | O_NONBLOCK); //非阻塞方式打開驅動
if(fd < 0){
printf("open file %s failed\r\n", filename);
return -1;
}
signal(SIGIO, signal_func); //指定信号處理函數
fcntl(fd, F_SETOWN, getpid()); //将應用程式的程序号告訴核心
flags = fcntl(fd, F_GETFL); /* 擷取目前的程序狀态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啟目前程序異步通知功能 */
while(1){
sleep(2);
}
close(fd);
return 0;
}