今天心情不错,听着歌,喝着铁观音,就把这两天做的事情写一下下。
平台:davinci dm6441,linux2.6.18
我们在板子上焊了一个按键,是和gpio41连接的,当然需要驱动咯,于是我就做了一个,但是没有去抖动,因为我这个应用不需要去抖动,公司催得紧,所以尽快完成,功能是
按下按键4s后重置系统的ip。
/* drivers/char/davinci_dm644x_button.c*/
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/arch/gpio.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/irq.h> //define for irq number
#include <asm-arm/arch-davinci/irqs.h>
#include <asm/arch-davinci/gpio.h>
//#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/arch/hardware.h>
#include <asm/arch/gpio.h>
//#include <asm/arch/regs-gpio.h>
#define DEVICE_NAME "dm6441_button" /*定义设备驱动的名字,或设备节点名称*/
#define BUTTON_MAJOR 198 /*使用 cat /proc/devices查看不要和存在的char节点重复*/
/*my app gpio define*/
#define ZX_GPIO41 41 /*GPIO41*/
/* 等待队列:
* 当没有按键被按下时,如果有进程调用buttons_read函数,
* 它将休眠
*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,buttons_read将它清0 */
static volatile int ev_press = 0;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
printk("key is pressed!/n");
ev_press = 1;
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
static int buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;
int gpio41_value;
ev_press = 0; //put it before function wait_event_interruptible(button_waitq, ev_press)
/* 如果ev_press等于0,休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 将按键状态复制给用户 */
err = copy_to_user(buff, (void *)&ev_press, sizeof(ev_press));
printk("this is at kernel, ev_press = %d/n", ev_press);
return err ? -EFAULT : 0;
}
static int buttons_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int gpio41_value1 = gpio_get_value(ZX_GPIO41);
printk("this is kernel, gpio41_value1 = %d/n", gpio41_value1);
return gpio41_value1;
}
static int buttons_open(struct inode *inode, struct file *file)
{
printk("open gpio,here is driver/n");
int result;
int irq_number;
int gpio41_value;
gpio_request(ZX_GPIO41, "buttons_interrupt");
irq_number = gpio_to_irq(ZX_GPIO41);
printk("irq_number = %d/n", irq_number);
gpio_direction_input(ZX_GPIO41);
set_irq_type((gpio_to_irq(ZX_GPIO41)), IRQ_TYPE_EDGE_FALLING); //RISING or Edge Falling type
enable_irq((gpio_to_irq(ZX_GPIO41)));
result = request_irq((gpio_to_irq(ZX_GPIO41)), buttons_interrupt, 0, "button", "key");
if (result < 0)
{
printk("Cannot initialize IRQ /n");
return result;
}
printk("initialize IRQ successful, result = %d/n", result);
gpio41_value = gpio_get_value(ZX_GPIO41);
printk("gpio41_value = %d/n", gpio41_value);
return 0;/*该函数可以什么都不做,也可以加入类似初始化的设置*/
}
static int buttons_close(struct inode *inode,struct file *filp)
{
free_irq(gpio_to_irq(ZX_GPIO41), "key"); //it will emit error if do not add "key"
return 0;
}
/*定义驱动设备文件API,在linux系统当中,任何设备都可以当做文件的方式操作,这一点和单片机和MCU有很大差别*/
static const struct file_operations davinci_dm644x_gpio_fileops = {
.owner = THIS_MODULE,
.open = buttons_open,
.release = buttons_close,
.read = buttons_read,
.ioctl = buttons_ioctl,
};
static int __init davinci_dm644x_button_open(void) /*内核初始化会调用该函数*/
{
int ret;
ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &davinci_dm644x_gpio_fileops);
if(ret < 0)
{
printk(DEVICE_NAME " register falid!/n");
return ret;
}
printk (DEVICE_NAME" initialized/n");
return ret;
}
static void __exit davinci_dm644x_button_exit(void)
{
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}
module_init(davinci_dm644x_button_open);
module_exit(davinci_dm644x_button_exit);
MODULE_AUTHOR("zhongxian <fenglailin>");
MODULE_DESCRIPTION("Davinci DM644x gpio driver");
MODULE_LICENSE("GPL");
一、 等待队列:
这个驱动中最值得一提的是wait_queue,读书时都是只闻其身见其面而已,重来都没好好地抚摸一下它,记得以前在大学写网络程序实验时说read()默认是阻塞的,我在做这个驱动时也想当然的这样认为咯(请不要见外,鄙人刚刚进入这个行业,还是可以原谅哈)。后来想起曾几何时听到过等待队列这个词语,于是查了一下资料,原来在linux驱动程序中,可以用等待队列来实现阻塞进程的唤醒。
所谓阻塞,就是当应用程序进行read(),write()等系统调用时,若设备的资源不能或得,而用户又希望以阻塞的方式访问设备,驱动程序应在设备的xxx_read(), xxx_write()等操作中将进程阻塞之到资源可以获取。
好了,现在回到等待队列的问题上来,我这个驱动程序中的实现是(红色部分)当button 没有被按下时,中断标志ev_press=0,wait_event_interruptible(button_waitq, ev_press)起作用,
应用程序调用read()时会阻塞,但当button被按下后,中断标志ev_press=1,
wait_event_interruptible(button_waitq, ev_press)就不起作用咯,因为这个函数就是这样规定的,当第二个参数必须满足时,才管用,我这里把它设置成0,所以当为1时就不管用啦,wake_up_interruptible(&button_waitq)函数唤醒了这个等待队列。此时应用程序调用read就不会休眠啦。关于等待队列的操作网上和一些驱动书上都讲得比较多,我这里只是说了一个简单的操作方式。比如http://zhuwenlong.blog.51cto.com/209020/40021这里。
一般步骤如下:
1、 定义等待队列头,这通常是全局的,wait_queue_head_t my_queue,
2、 初始化wait_queue_head,init_waitqueue_head(&my_queue),也可以把这两歩合成一步用DECLARE_WAIT_QUEUE_HEAD (name)“定义并初始化等待队列头”。像我这里就是这样做的。
3、 定义等待队列
DECLEAR_WAITQUEUE(name, tsk);
4、 添加/移除等待队列 这两歩通常在你的xxx_read(),XXX_write()中;由我这里看出这两歩不是必要的,我想可能是我只用一个等待队列吧。
5、 等待事件,这些函数就不写啦,因为想出去走走啦。网上都有介绍的。
只说我这里用到的一个wait_event_interruptible(button_waitq, ev_press)只有当第二个参数条件满足时才会管用即阻塞。
二、 copy_to_user(buff, (void *)&ev_press, sizeof(ev_press))
这个函数一看就知道是干什么的,我以前没有用过,在刚刚接触到它时参数还是没有搞对,呵呵,其实现在很多地方都是第一次实践,以前只是看书学习,不过我相信慢慢会积累的。我只说一点,就是第二个参数的类型,以后就这样用咯。
三、result = request_irq((gpio_to_irq(ZX_GPIO41)), buttons_interrupt, 0, "button", "key");
“button” 是注册中断的名字。用cat /proc/interrupt 可以看到它。
“key”在后面的free_irq(gpio_to_irq(ZX_GPIO41), "key")中要用到。
buttons_interrupt当然就是中断处理程序咯