經過前面的配置,S5PV210開發已經可以成功進入Linux控制台了,那麼,有了這個環境就可以開始學習Linux驅動的編寫和測試了。學習Linux設備驅動,通常是從字符設備驅動開始。我寫的第一個驅動程序是Led的,其實也就是熟悉下字符設備驅動的基本結構,本文以中斷方式的按鍵驅動為例,簡單的介紹下字符設備驅動程序。
一 按鍵驅動程序的簡單實現
下面是基於中斷和消息的按鍵驅動程序,其工作原理是:當應用程序讀取鍵值時,會調用按鍵驅動程序的read函數,而我們實現的read函數檢測完讀取長度後沒有直接讀取鍵值而是等待按鍵消息,如果沒有按鍵,程序會進入休眠狀態,這樣可以節省大量的CPU,而當我們按鍵時硬件會產生中斷,程序自動進入中斷處理函數,在中斷處理函數中,驅動程序讀取鍵值存入全局變量並激活read函數中等待的消息,應用程序被迅速喚醒並通過read函數讀取鍵值,如此,完成了獲取鍵值的工作。下面是源碼,比較簡單,也就不多說了。
源碼:
[cpp]
#include <linux/types.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
static dev_t devno;
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 <linux/types.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
static dev_t devno;
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");
測試程序代碼:
[cpp]
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd = open("/dev/buttons", O_RDWR);
if(fd < 0){
printf("open error");;
return 0;
}
unsigned char key;
while(1){
read(fd, &key, 1);
printf("The key = %x\n", key);
}
close(fd);
}
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd = open("/dev/buttons", O_RDWR);
if(fd < 0){
printf("open error");;
return 0;
}
unsigned char key;
while(1){
read(fd, &key, 1);
printf("The key = %x\n", key);
}
close(fd);
}相比輪詢方式的按鍵驅動程序,中斷方式編寫的按鍵驅動程序可以很大程度上節省CPU資源,因此,推薦使用中斷方式。
二 支持POLL機制
上面這種方式實現的按鍵驅動程序有個弊端,如果我們不按鍵,應用程序將會永遠阻塞在這裡,幸運的是,linux內核提供了poll機制,可以設置超時等待時間,如果在這個時間內讀取到鍵值則正常返回,反之則超時退出。使內核支持poll非常簡單,為file_operations的poll成員提供poll處理函數即可。
使內核支持poll還需要以下幾步:
添加poll頭文件
[cpp]
#include <linux/poll.h>
#include <linux/poll.h>
編寫poll處理函數:
[cpp]
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;
}
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:
[cpp]
.poll = buttons_poll,
.poll = buttons_poll,這樣,驅動程序就支持poll機制了。下面是poll方式的測試程序:
[cpp]
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
int main(int argc, char **argv){
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];
fd = open("/dev/buttons", O_RDWR);
if (fd < 0){
printf("can't open!\n");
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1){
ret = poll(fds, 1, 5000);
if (ret == 0){
printf("time out\n");
}
else{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
int main(int argc, char **argv){
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];
fd = open("/dev/buttons", O_RDWR);
if (fd < 0){
printf("can't open!\n");
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1){
ret = poll(fds, 1, 5000);
if (ret == 0){
printf("time out\n");
}
else{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}這樣,應用程序可以限制時間,如果在一定時間內讀取不到鍵值就可以做特殊處理,這種思想在網絡通信中應用廣泛。
三 支持異步機制
很多情況下,我們的程序在等待按鍵期間需要處理其它任務而不是在這裡空等,這時,就需要采用異步模式了。所謂異步模式,實際上是采用消息機制(以本文的按鍵程序為例),即當驅動程序檢測到按鍵後發送消息給應用程序,應用程序接收到消息後再去讀取鍵值。與前面的兩種模式相比,最大的不同在於異步方式是驅動告訴應用程序來讀而不是應用程序主動去讀。添加異步支持更加簡單,首先是為file_operations注冊fasync函數,函數內容如下:
[cpp]
static int buttons_fasync(int fd, struct file * file, int on){
return fasync_helper(fd, file, on, &button_async);
}
static int buttons_fasync(int fd, struct file * file, int on){
return fasync_helper(fd, file, on, &button_async);
}然後再buttons_read函數中添加一行代碼,修改後的代碼如下:[cpp] view plaincopyprint?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 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;
}這樣,驅動程序就支持異步獲取鍵值了,為了測試效果,測試程序也需要修改,代碼如下:[cpp] view plaincopyprint?#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
/* sixthdrvtest
*/
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun);
fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
if (fd < 0){
printf("can't open!\n");
return -1;
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
int rest;
while (1){
printf("Hello\n");
while(rest = sleep(50)){
sleep(rest);
}
}
return 0;
}
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
/* sixthdrvtest
*/
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun);
fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
if (fd < 0){
printf("can't open!\n");
return -1;
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
int rest;
while (1){
printf("Hello\n");
while(rest = sleep(50)){
sleep(rest);
}
}
return 0;
}這裡需要注意的是,應用程序接收到消息會打斷sleep,比如執行sleep(5)之後程序接收到了一個消息,這時,應用程序就被喚醒了,雖然是去執行的消息處理函數。如果程序接收到消息時僅睡眠了2秒,那麼sleep被中斷時會返回5-2=3,所以代碼中采用while循環方式進行sleep,這樣,即使接收到了消息也能完整的休眠5秒,當然,sleep函數本身是不夠精確的,不過相差無幾。
到這裡,這個驅動程序基本上就算可以了,當然,還有對阻塞和非阻塞的支持,同步與互斥的支持,而阻塞與非阻塞無非是加上個邏輯判斷,同步與互斥根應用程序的同步控制也差不多,無非就是信號量或者原子操作,這裡就不多說了,如果有朋友需要這些內容可以留言討論。