经过前面的配置,S5PV210开发已经可以成功进入Linux控制台了,那么,有了这个环境就可以开始学习Linux驱动的编写和测试了。学习Linux设备驱动,通常是从字符设备驱动开始。由于linux驱动开发具有比较系统的体系结构,我很难在一篇文章中阐述其开发思路,为了简单起见,从本文开始,自行编写的驱动将直接附上代码,对开发过程中感触比较深的地方稍作陈述。
我写的第一个驱动程序是Led的,但是感觉没有必要发出来了,S5PV210(TQ210)的按键驱动程序源码,仅供参考:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct cdev cdev;
static struct class* buttons_class;
static struct device* buttons_device;static wait_queue_head_t button_waitq;static volatile int pressed = 0;
static unsigned char key_val;struct key_desc{unsigned int pin;unsigned char value;
};static struct key_desc key_descs[8] = {[0] = {.pin = S5PV210_GPH0(0),.value = 0x00,},[1] = {.pin = S5PV210_GPH0(1),.value = 0x01,},[2] = {.pin = S5PV210_GPH0(2),.value = 0x02,},[3] = {.pin = S5PV210_GPH0(3),.value = 0x03,},[4] = {.pin = S5PV210_GPH0(4),.value = 0x04,},[5] = {.pin = S5PV210_GPH0(5),.value = 0x05,},[6] = {.pin = S5PV210_GPH2(6),.value = 0x06,},[7] = {.pin = S5PV210_GPH2(7),.value = 0x07,},
};static irqreturn_t buttons_irq(int irq, void *dev_id){volatile struct key_desc *key = (volatile struct key_desc *)dev_id;if(gpio_get_value(key->pin)){key_val = key->value|0x80;}else{key_val = key->value;}pressed = 1;wake_up_interruptible(&button_waitq);return IRQ_RETVAL(IRQ_HANDLED);
}static int buttons_open(struct inode *inode, struct file *file){int ret;ret = request_irq(IRQ_EINT(0), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &key_descs[0]);if(ret)return ret;ret = request_irq(IRQ_EINT(1), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &key_descs[1]);if(ret)return ret;ret = request_irq(IRQ_EINT(2), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &key_descs[2]);if(ret)return ret;ret = request_irq(IRQ_EINT(3), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &key_descs[3]);if(ret)return ret;ret = request_irq(IRQ_EINT(4), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &key_descs[4]);if(ret)return ret;ret = request_irq(IRQ_EINT(5), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key6", &key_descs[5]);if(ret)return ret;ret = request_irq(IRQ_EINT(22), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key7", &key_descs[6]);if(ret)return ret;ret = request_irq(IRQ_EINT(23), buttons_irq, IRQ_TYPE_EDGE_BOTH, "key8", &key_descs[7]);if(ret)return ret;return 0;
}static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){if(count != 1){printk(KERN_ERR "The driver can only give one key value once!\n");return -ENOMEM;}wait_event_interruptible(button_waitq, pressed);pressed = 0;if(copy_to_user(data, &key_val, 1)){printk(KERN_ERR "The driver can not copy the data to user area!\n");return -ENOMEM;}return 0;
}static int buttons_close(struct inode *inode, struct file *file){free_irq(IRQ_EINT(0), &key_descs[0]);free_irq(IRQ_EINT(1), &key_descs[1]); free_irq(IRQ_EINT(2), &key_descs[2]);free_irq(IRQ_EINT(3), &key_descs[3]);free_irq(IRQ_EINT(4), &key_descs[4]);free_irq(IRQ_EINT(5), &key_descs[5]);free_irq(IRQ_EINT(22), &key_descs[6]);free_irq(IRQ_EINT(23), &key_descs[7]);return 0;
}struct file_operations buttons_ops = {.open = buttons_open,.read = buttons_read,.release = buttons_close,
};int buttons_init(void){int ret;cdev_init(&cdev, &buttons_ops);cdev.owner = THIS_MODULE;ret = alloc_chrdev_region(&devno, 0, 1, "buttons");if(ret){printk(KERN_ERR "alloc char device region faild!\n");return ret;}ret = cdev_add(&cdev, devno, 1);if(ret){printk(KERN_ERR "add char device faild!\n");goto add_error;}buttons_class = class_create(THIS_MODULE, "buttonsdrv");if(IS_ERR(buttons_class)){printk(KERN_ERR "create class error!\n");goto class_error;}buttons_device = device_create(buttons_class, NULL, devno, NULL, "buttons");if(IS_ERR(buttons_device)){printk(KERN_ERR "create buttons device error!\n");goto device_error;}init_waitqueue_head(&button_waitq);return 0;device_error:class_destroy(buttons_class);
class_error:cdev_del(&cdev);
add_error:unregister_chrdev_region(devno,1);return -ENODEV;
}void buttons_exit(void){device_destroy(buttons_class, devno);class_destroy(buttons_class);cdev_del(&cdev);unregister_chrdev_region(devno, 1);
}module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试程序代码:
#include
#include
}
相比轮询方式的按键驱动程序,中断方式编写的按键驱动程序可以很大程度上节省CPU资源,因此,推荐使用中断方式。
但是,这种方式有个弊端,如果一直接收不到按键,程序就会永远阻塞在这里,幸运的是,linux内核提供了poll机制,可以设置延迟时间,如果在这个时间内受到按键消息则取得键值,反之则超时退出。使内核支持poll非常简单,为file_operations的poll成员提供poll处理函数即可。
使内核支持poll还需要以下几步:
添加poll头文件
编写poll处理函数:
static unsigned buttons_poll(struct file *file, poll_table *wait){unsigned int mask = 0;poll_wait(file, &button_waitq, wait);if (pressed)mask |= POLLIN | POLLRDNORM;return mask;
}
将poll处理函数添加给file_operations:
.poll = buttons_poll,
这样,驱动程序就支持poll机制了。下面是poll方式的测试程序:
#include
#include
#include
#include
#include
}
这样按键驱动程序就完成了。如果您在编写测试阶段发现了其他问题,欢迎留言讨论。