工作项目用有个需求是监测某个GPIO输入方波的频率!通俗的讲就是一个最最简单的测方波频率的示波器!不过只是测方波的频率!频率范围是0~200HZ,而且频率方波不是一直都是200HZ,大多数的时候可能一直是0或者一个更低频率的方波!同时要考虑到方波有可能一直维持在200HZ ,同时保持效率和性能的情况下,fasync驱动异步通知是个不错的选择,当初写demo的时候实测1K的方波完全没有问题!应用到项目中也是完全能满足需求!驱动很简单!业余时间把自己之前学到的知识总结一下!对自己也是个提高!
根据需求,驱动中实现比较简单!自己只实现open、close、fasync和read函数 ,这里只需要读取方波的频率即可!
驱动大概实现原理:方波每产生一个下降沿,产生一个中断,然后根据中断在通过异步通知应用程序,以此来测定输入方波的频率!
fansync机制的优势是能使驱动的读写和应用程序的读写分开,使得应用程序可以在驱动读写的时候去做别的事情!
下面是驱动的源码:
**-------File Info--------------------------------------------------------------------------------------- ** File Name: gpioInt.c ** Latest modified Data: 2015_11_16 ** Latest Version: v1.0 ** Description: NOME ** **-------------------------------------------------------------------------------------------------------- ** Create By: K ** Create date: 20015-11-16 ** Version: v1.0 ** Descriptions: 混杂设备驱动程序 GPIO中断驱动 下降沿触发GPIO 内核会向用户空间发送一个键值 ** 用户态的应用程序通过读取键值来判断GPIO中断状况 ** **-------------------------------------------------------------------------------------------------------- *********************************************************************************************************/ #include<linux/init.h> #include<linux/module.h> #include<mach/gpio.h> #include<asm/io.h> #include"mach/../../mx28_pins.h" #include <mach/pinctrl.h> #include "mach/mx28.h" #include<linux/fs.h> #include <linux/io.h> #include<asm/uaccess.h> #include<linux/miscdevice.h> #include<linux/irq.h> #include<linux/sched.h> #include<linux/interrupt.h> #include<linux/timer.h> #include <linux/kernel.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/io.h> /* *中断事件标志,中断服务程序将它置1,在gpio_drv_read将它清0 */ static volatile int ev_press = 0; /* *异步结构体指针 用于读 */ static struct fasync_struct *b_async; /* *中断引脚描述结构体 */ struct pin_desc_s{ unsigned int pin; unsigned int key_val; unsigned int irq; }; static unsigned char key_val; struct pin_desc_s pin_desc[5] = { {MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,}, /* IO1 rain GPIO1_31 */ {MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,}, /* IO2 windspeed GPIO1_29*/ {MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,}, /* 机箱门 */ {MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,}, /* key1 GPIO3_12 */ {MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,}, /* key2 GPIO3_13 */ }; static DECLARE_MUTEX(b_lock); static DECLARE_WAIT_QUEUE_HEAD(b_waitq); static irqreturn_t b_irq(int irq, void *dev_id) { struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id; unsigned int pinval; pinval = gpio_get_value(pindesc->pin); if (pinval) { key_val = 1; } else { key_val = pindesc->key_val; } ev_press = 1; wake_up_interruptible(&b_waitq); //唤醒等待队列里面的进程 kill_fasync(&b_async, SIGIO, POLL_IN); //异步通知 //printk("interrupt occur..........\n"); return IRQ_RETVAL(IRQ_HANDLED); } static int gpio_drv_open(struct inode *inode, struct file *file) { int iRet[5]={0}; int i = 0; if (file->f_flags & O_NONBLOCK) { if (down_trylock(&b_lock)) return -EBUSY; } else { down(&b_lock); } for(i = 0; i < 5; i++) { gpio_direction_input((pin_desc[i]).pin); (pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin); if ((pin_desc[i]).irq) disable_irq((pin_desc[i]).irq); set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING); //下降沿中断 iRet[i] = request_irq((pin_desc[i]).irq, buttons_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]); if (iRet[i] != 0){ printk("request irq failed!! ret: %d irq:%d \n", iRet[i],(pin_desc[i]).irq); return -EBUSY;} } return 0; } ssize_t gpio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { if (size != 1) return -EINVAL; if (file->f_flags & O_NONBLOCK) { if (!ev_press) return -EAGAIN; } else { wait_event_interruptible(b_waitq, ev_press); } copy_to_user(buf, &key_val, 1); ev_press = 0; return 1; } int gpio_drv_close(struct inode *inode, struct file *file) { int i = 0; for( i = 0; i < 5; i++) { free_irq((pin_desc[i]).irq, &pin_desc[i]); } up(&b_lock); return 0; } static int gpio_drv_fasync (int fd, struct file *filp, int on) { printk("driver: gpio_drv_successful\n"); return fasync_helper (fd, filp, on, &b_async); } static struct file_operations gpio_drv_fops = { .owner = THIS_MODULE, .open = gpio_drv_open, .read = gpio_drv_read, .release = gpio_drv_close, .fasync = gpio_drv_fasync, }; static struct miscdevice b_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "magic-gpio", .fops = &gpio_drv_fops, }; static int __init gpio_drv_init(void) { int iRet=0; printk("gpio_miscdev module init!\n"); iRet = misc_register(&b_miscdev); if (iRet) { printk("register failed!\n"); } return 0; } static void __exit gpio_drv_exit(void) { printk("gpio_miscdev module exit!\n"); misc_deregister(&b_miscdev); } module_init(gpio_drv_init); module_exit(gpio_drv_exit); MODULE_AUTHOR("HEHAI & RK"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("gpio interrupt module");
struct miscdevice { int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; const char *nodename; mode_t mode; };当然我上面的驱动代码只初始化了前面的关键三项:
static struct miscdevice b_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "magic-gpio", .fops = &gpio_drv_fops, };
http://blog.csdn.net/yongan1006/article/details/6778285 这个可以研究一下,还比较有意思!
剩下的两个name 和 fops成员对驱动开发来说就最熟悉不过了!驱动的名字和驱动的接口函数这里就不说了!
注册混杂设备驱动后就是接口函数的表演了!
这里和内核硬件相关的就是struct pin_desc_s 结构了,硬件的初始化工作比较简单,放在open函数里面了!
struct pin_desc_s pin_desc[5] = { {MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,}, /* IO1 rain GPIO1_31 */ {MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,}, /* IO2 windspeed GPIO1_29*/ {MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,}, /* 机箱门 */ {MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,}, /* key1 GPIO3_12 */ {MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,}, /* key2 GPIO3_13 */ };
static int gpio_drv_open(struct inode *inode, struct file *file) { int iRet[5]={0}; int i = 0; if (file->f_flags & O_NONBLOCK)//非阻塞 { if (down_trylock(&b_lock)) return -EBUSY; } else { down(&b_lock); } for(i = 0; i < 5; i++) { gpio_direction_input((pin_desc[i]).pin);//设置对应的GPIO输入 (pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin);//把GPIO对应的pin值转换为相应的IRQ值并返回 if ((pin_desc[i]).irq) disable_irq((pin_desc[i]).irq);//先关闭中断并等待中断处理完 set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING); //设置下降沿中断 iRet[i] = request_irq((pin_desc[i]).irq, b_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]); if (iRet[i] != 0){ printk("request irq failed!! ret: %d irq:%d \n", iRet[i],(pin_desc[i]).irq); return -EBUSY;} } return 0; }
request_irq函数:http://blog.csdn.net/wealoong/article/details/7566546
说说上面的request_irq函数了:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
irq是要申请的硬件中断号。
handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
irqflags是中断处理的属性,SA_SHARED表示多个设备共享中断,
devname设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称。
dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。
这里用到回调函数b_irq函数就是根据响应的GPIO中断返回设置好的相应的值,这样应用程序在得到这个值的时候就可以知道是哪个GPIO发送的中断!
b_irq函数:
static irqreturn_t b_irq(int irq, void *dev_id) { struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id; unsigned int pinval; pinval = gpio_get_value(pindesc->pin); if (pinval) { key_val = 1; } else { key_val = pindesc->key_val; } ev_press = 1; wake_up_interruptible(&b_waitq); //唤醒等待队列里面的进程 kill_fasync(&b_async, SIGIO, POLL_IN); //异步通知 //printk("interrupt occur..........\n"); return IRQ_RETVAL(IRQ_HANDLED); }其中上面的b_waitq是这样定义的:
static DECLARE_WAIT_QUEUE_HEAD(b_waitq);//生成一个等待队列的头 名字为b_waitq关于等待队列可以看下这篇文章: http://www.cnblogs.com/xmphoenix/archive/2011/11/20/2256419.html
其实这里有一个很关键的地方就是kill_fasync异步通知应用程序。这里有很关键的一步,可以说是整个驱动程序的核心:kill_fasync 及 fasync_helper用于异步通知中,其中 kill_fasync(&b_async,SIGIO,POLL_IN)函数的功能是向应用程序发送可读信号,还有那个进程调用fasync_helper函数就向谁发!这个可以结合应用程序是如何拿到信号的对比着看,关于应用程序这里就不说了!网上的资料也比较多讲解的也很详细!例程代码还有理论分析都有!
fansync_helpr函数内部实现:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { struct fasync_struct *fa, **fp; struct fasync_struct *new = NULL; int result = 0; if (on) { new = kmem_cache_alloc(fasync_cache, GFP_KERNEL); if (!new) return -ENOMEM; } write_lock_irq(&fasync_lock); for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { if (fa->fa_file == filp) { if(on) { fa->fa_fd = fd; //区分向谁发 kmem_cache_free(fasync_cache, new); } else { *fp = fa->fa_next; kmem_cache_free(fasync_cache, fa); result = 1; } goto out; } } if (on) { new->magic = FASYNC_MAGIC; new->fa_file = filp; new->fa_fd = fd; new->fa_next = *fapp; *fapp = new; result = 1; } out: write_unlock_irq(&fasync_lock); return result; }kill_fasync函数里面的b_async参数:struct fasync_struct类型定义:
struct fasync_struct { int magic; int fa_fd; struct fasync_struct *fa_next; struct file *fa_file; };这个参数在下面中也被调用:实现的fasync成员函数
static int gpio_drv_fasync (int fd, struct file *filp, int on) { printk("driver: gpio_drv_successful\n"); return fasync_helper (fd, filp, on, &b_async); }这也是应用程序和内核之间传参的一个关键:
要实现传参,我们需要把一个结构体struct fasync_struct添加到内核的异步队列中,这个结构体用来存放对应设备文件的信息(如fd, filp)并交给内核来管理。一但收到信号,内核就会在这个所谓的异步队列头找到相应的文件(fd),并在filp->owner中找到对应的进程PID,并且调用对应的sig_handler了。
关于剩下的程序中用到的down() 、up() 还有 DECILARE_MUTEX(b_lock)这里简单的用到了信号量的两个简单的操作,主要是用于保护临界资源,保证中断不被丢失!
剩下的read和close都比较简单,驱动里面的函数基本都是对应的,close里面一把是释放所有申请的资源!这也是模块化驱动的一个好处!虽然这个驱动很简单!但是要仔细深究起来,里面所涉及的知识量也不小!上面也只是简单的分析总结一下!做个笔记算是对自己的一个提高,也别人在参考的时候能有一点点的帮助!
最近住的地方没网!感觉好长时间没写博客了!现在业余时间看linux驱动设备详解,哈哈,比一年多前看的效果好多了,至少书上的好多知识多多少少都接触过!而且看起来还比较有收获,就是看了就忘!看来总结还是相当重要的!好记性不如烂笔头!