一、驅動程序概念介紹
u-boot的任務是啟動內核,內核的任務是啟動應用程序 ,應用程序會涉及很多文件和硬件操作(當然不會直接操作硬件),比如讀寫文件,點燈、獲取按鍵值。
比如對於控制led燈的用戶程序與驅動程序,最簡單的實現方法是:
應用程序中需要打開led燈,就需要open函數,在內核中的驅動程序中也有對應的led_open函數,這個led_open函數就是用來負責初始化led的引腳功能,應用程序中要調用read函數讀取led燈的狀態,內核中的驅動程序也有led_read函數。這是應用程序與內核中驅動程序一種最簡單的對應方式.
那么應用程序中的open、read函數最終怎樣調用到驅動程序中的led_open、led_read呢,中間有哪些東西?
在linux中共有4層軟件,如下圖:
以下名詞解釋:
- 應用程序:就是被調用的那些庫函數,例如open、read、write... ...
- C庫(系統調用):其中的其實就是實現open、read這些函數來調用swi val 指令進入內核(函數不同val值都會不同)
- 內核: 內核根據swi后面不同的值去調用VFS中的system_open/system_read/ system_write等異常處理函數,找到相應的驅動程序(VFS:virtual file system 虛擬文件系統)
例如:
int main()
{
int fd1 fd2;
int val=1;
fd1 = open(“/dev/led”,O_RDWR); //打開led
write(fd1, &val, 4);
fd2 = open(“hello.txt”,O_RDWR); //打開文本
write(fd2, &val, 4);
}
問:上面的應用程序主要實現點燈與打開文本文件,都是用的同樣的函數。但是點燈與打開文本文件的行為顯然不一樣。那么誰來實現這些不一樣的行為呢?
答:對於LED燈,有led_open驅動程序。對於文本文件存在於flash設備上,也有對於的驅動程序。system_open、system_read最終會根據打開的不同文件,找到底層的不同驅動程序,然后調用驅動程序中的硬件操作函數,比如led_open來實現對具體硬件設備的操作。這就是整個的字符設備驅動程序框架。
例如LED,如下圖:
在應用層應用程序中有open、read、write
同樣,在驅動程序中也對應有led_open、led_read、led_write
剩下的就是驅動框架了。
二、制作第一個驅動程序
本節目的:
先講解驅動框架,然后寫出first_drv驅動程序,來打印一些信息
寫出first_drv驅動程序需要以下幾步:
1)寫出驅動程序first_drv_open first_drv_write
2)需要定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write
對於字符設備來說,常用file_operations以下幾個成員:
3) 模塊加載函數,通過函數 register_chrdev(major, “first_drv”, &first_drv_fops) 來注冊字符設備
4)寫驅動的first_drv_init 入口函數來調用這個register_chrdev()注冊函數,
5)通過module_init()來修飾入口函數,使內核知道有這個函數
6)寫驅動的first_drv_exit出口函數,調用這個unregister_chrdev()函數卸載,
7) 通過module_exit()來修飾出口函數
8) 模塊許可證聲明, 最常見的是以MODULE_LICENSE( "GPL v2" )來聲明
接下來我們編寫並調試驅動程序。
2.1、創建first_drv.c文件
代碼如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*1寫出驅動程序first_drv_open first_drv_write */
/*inode結構表示具體的文件,file結構體用來追蹤文件在運行時的狀態信息。*/
static int first_drv_open(struct inode *inode, struct file *file)
{
printk(“first_drv_open\n”); //打印,在內核中打印只能用printk()
return 0;
}
/*參數filp為目標文件結構體指針,buffer為要寫入文件的信息緩沖區,count為要寫入信息的長度,ppos為當前的偏移位置,這個值通常是用來判斷寫文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk(“first_drv_write\n”); //打印,在內核中打印只能用printk()
return 0;
}
/*2定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write */
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, //被使用時阻止模塊被卸載
.open = first_drv_open,
.write = first_drv_write,
};
/*4寫first_drv_init入口函數來調用這個register_chrdev()注冊函數*/
int first_drv_init(void)
{
/*3 register_chrdev注冊字符設備,並設置major=111*/
/*如果設置major為0,表示由內核動態分配主設備號,函數的返回值是主設備號*/
register_chrdev (111, “first_drv”, &first_drv_fops); //111:主設備號,”first_drv”:設備名
/*register_chrdev作用:在VFS虛擬文件系統中找到字符設備,然后通過主設備號找到內核數組里對應的位置,最后將設備名字和fops結構體填進去*/
return 0;
}
/*5 module_init修飾入口函數*/
module_init(first_drv_init);
/*6 寫first_drv_exit出口函數*/
void first_drv_exit(void)
{
unregister_chrdev (111, “first_drv”); //卸載驅動,只需要主設備號和設備名就行
}
/*7 module_exit修飾出口函數*/
module_exit(first_drv_exit);
/*8許可證聲明, 描述內核模塊的許可權限,如果不聲明LICENSE,模塊被加載時,將收到內核被污染 (kernel tainted)的警告。*/
MODULE_LICENSE( "GPL v2" );
2.2、寫Makefile編譯腳本:
KERN_DIR = /work/system/linux-2.6.22.6 //依賴的內核目錄,前提內核是編譯好的
all:
make -C $(KERN_DIR) M=`pwd` modules
// M=`pwd`:指定當前目錄
//make -C $(KERN_DIR) 表示將進入(KERN_DIR)目錄,執行該目錄下的Makefile
//等價於在linux-2.6.22.6目錄下執行: make M=(當前目錄) modules
// modules:要編譯的目標文件
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += frist_drv.o //obj-m:內核模塊文件,指將myleds.o編譯成myleds.ko
2.3、編譯、加載
1)make,編譯生成frist_drv.ko文件
2)開發板通過nfs網絡文件系統來加載frist_drv.ko
注:加載之前首先通過 cat /proc/devices來查看字符主設備號111是否被占用,然后通過 insmod first_drv.ko來掛載, 通過 cat /proc/devices就能看到first_drv已掛載好
2.4、通過測試程序測試frist_drv模塊
測試程序first_driver_test.c代碼如下
#include <sys/types.h> //調用sys目錄下types.h文件
#include <sys/stat.h> //stat.h獲取文件屬性
#include <fcntl.h>
#include <stdio.h>
/*輸入”./first_driver_test”, agc就等於1, argv[0]= first_driver_test */
/*輸入”./first_driver_test on”, agc就等於2, argv[0]= first_driver_test,argv[1]=on; */
int main(int argc,char **argv)
{
int fd1, fd2;
int val=1;
fd1 = open("/dev/xxx",O_RDWR); //打開/dev/xxx設備節點
if(fd1<0) //無法打開,返回-1
printf("can't open%d!\n", fd1);
else
printf("can open%d!\n", fd1); //打開,返回文件描述符
write(fd1, &val, 4); //寫入數據1
return 0;
}
1)通過“arm-linux-gcc -o first_driver_text first_driver_test.c”指令生成執行文件
2)回到板子串口上使用./first_driver_test來運行,發現如果open()打不開,會返回-1
打印信息:
can't open-1!
原因:這是因為我們沒有創建dev/xxx這個設備節點,然后我們來創建,使它等於剛剛掛載好的first_drv模塊。
3)運行指令:
mknod -m 660 /dev/xxx c 111 0 // first_drv模塊的主設備號=111
./first_driver_test
打印信息:
first_drv_open
can open3!
first_drv_write
通過打印信息發現測試程序里的open()函數調用了驅動中的first_drv_open(),write()函數調用了驅動中的first_drv_write(),
其中open()函數返回值為3,是因為描述符0,1,2都已經被控制台占用了,所以從3開始
2.5、改進底層驅動,使用動態裝載:
除了靜態裝載驅動外,還可以動態裝載,讓系統自動為我們驅動設備自動分配設備號
2.5.1、修改first_drv_init入口函數和first_drv_exit 出口函數:
代碼如下:
int major; //定義一個全局變量,用來保存主設備號
int first_drv_init(void)
{
/*設置major為0,由內核動態分配主設備號,函數的返回值是主設備號*/
major =register_chrdev (0, “first_drv”, &first_drv_fops);
return 0;
}
void first_drv_exit(void)
{
unregister_chrdev (major, “first_drv”); //卸載驅動, 將major填入即可
}
通過動態分配得出它的主設備號是252(此數字隨機分配),然后重創252的測試程序
運行指令:
rm dev/xxx
mknod -m 660 /dev/xxx c 252 0
./first_driver_test
打印信息:
first_drv_open
can open3!
first_drv_write
2.5.2、每次都要手工創建設備節點,大家肯定也會覺得這樣做太麻煩了。
改進方法:可以使用自動創建設備節點,Linux有udev、mdev的機制,而我們的ARM開發板上移植的busybox有mdev機制,然后mdev機制會通過class類來找到相應類的驅動設備來自動創建設備節點 (前提需要有mdev)
問:在哪里設置了mdev機制?
答:在制作根文件系統之使用里有介紹
2.5.3、接下來使用insmod自動創建設備節點, rmmod自動注銷設備節點
1)首先創建一個class設備類,class是一個設備的高級視圖,它抽象出低級的實現細節,然后在class類下,創建一個class_device,即類下面創建類的設備:(在C語言中class就是個結構體)
static struct class *firstdrv_class; //創建一個class類
static struct class_device *firstdrv_class_devs; //創建類的設備
2)在first_drv_init入口函數中添加:
firstdrv_class= class_create(THIS_MODULE,"firstdrv");
//創建類,它會在sys/class目錄下創建firstdrv_class這個類
firstdrv_class_devs=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");
//創建類設備,會在sys/class/firstdrv_class類下創建xyz設備,然后mdev通過這個自動創建/dev/xyz這個設備節點,
3)在first_drv_exit出口函數中添加:
class_device_unregister(firstdrv_class_devs); //注銷類設備,與class_device_create對應
class_destroy(firstdrv_class); //注銷類,與class_create對應
重新編譯insmod后,會發現在/dev下自動的創建了xyz設備節點
其中在sys/class里有各種類的設備, 比如sys/class/fristdev下就有xyz
然后mdev通過insmod xxx 就去class找到相應類的驅動設備來自動創建設備節點
問:為什么內容一更改,mdv就能自動運行創建設備節點呢?
答:是因為以前創建根文件系統時候,在etc/init.d/rcS里添加了這么一段:
echo /sbin/mdev > /proc/sys/kernel/hotplug //支持熱拔插
然后kernel每當設備出現變動時,調用/sbin/mdev來處理對應的信息,使mdev應用程序操作/dev目錄下的設備,進行添加或刪除
4)再修改測試程序里open函數,將/dev/xxx改為/dev/xyz,這樣就測試模塊,就不需要再mknod了.
驅動程序first_drv_open first_drv_write中只是打印數據,接下便開始來點亮LED.
三、修改第一個程序來點亮LED
本節目的:
在上一節搭建的驅動框架下添加硬件的操作
硬件的操作(控制LED)主要分為如下幾步:
1)看原理圖,確定引腳
2)看2440手冊
3)寫代碼(需要使用ioremap()函數映射虛擬地址,在linux中只能使用虛擬地址)
4)修改上一節的測試程序
5)使用次設備號來控制設備下不同的燈
3.1、看led引腳
看原理圖可以確定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6
3.2、看2440手冊
配置GPFCON15:0的位[8:9]、位[10:11]、位[12:13] 都等於0x01(輸出模式)
控制GPFDAT7:0中的位4~6來使燈亮滅(低電平亮)
3.3、寫代碼
1)添加全局變量:
volatile unsigned long *GPFcon=NULL;
volatile unsigned long *GPFdat=NULL;
2)first_drv_init入口函數中使用ioremap()映射虛擬地址:
GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虛擬地址
GPFdat=GPFcon+1; //long:32位,所以GPFdat=0x56000050+(32/8)
3)first_drv_exit出口函數中注銷虛擬地址:
iounmap(GPFcon); //注銷虛擬地址
4)first_drv_open函數中添加配置GPFCON:
*GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12));
*GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12));
5)first_drv_write函數中添加拷貝應用層數據,然后來控制GPFDAT:
/*copy_to_user():將數據上給用戶*/
copy_from_user(&val,buf,count); //從用戶(應用層)拷貝數據
if(val==1) //點燈(低電平亮)
{
*GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6));
}
else //滅燈
{
*GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6));
}
3.4、修改測試程序main()
代碼如下:
int main(int argc,char **argv) //argc:參數個數,argv數組
{
int fd1, fd2;
int val=1;
fd1 = open("/dev/xyz",O_RDWR); //打開/dev/xxx設備節點
if(fd1<0) //無法打開,返回-1
printf("can't open%d!\n", fd1);
if(argc!=2)
{
printf("Usage:\n");
printf("%s <on|off>",argv[0]);
return 0;
}
if(strcmp(argv[1],"on")==0) //開燈
{
printf("led on...\n");
val=1;
}
else //關燈
{
printf("led off...\n");
val=0;
}
write(fd1, &val, 4);
return 0;
}
當輸入first_driver_text on點3個燈, 否則關3個燈
若參數不等於2時,不能控制點燈
問:如果我們想分別控制不同的燈,該怎么做?
答:可以使用次設備號,次設備號就是用來區分同一設備下不同子設備
3.5、使用次設備號來控制設備下不同的燈
我們先來看下面兩個函數MAJOR和MINOR,分別是提取主次設備號
minor=MINOR(inode->i_rdev); //open函數中提取次設備號
major=MAJOR(inode->i_rdev); //open函數中提取主設備號
minor=MINOR (file->f_dentry->d_inode->i_rdev); //write/read函數中提取次設備號
major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函數中提取主設備號
思路如下:
在測試程序中:
通過dev[1]來open打開不同的子設備節點,然后通過dev[2]來write寫入數據
實例: first_driver_text led1 on //點亮led1
在first_dev.c驅動文件中:
first_drv_init函數中創建不同的子設備節點
first_drv_exti函數中注銷不同的子設備節點
first_drv_open函數中通過MINOR(inode->i_rdev)來初始化不同的燈
first_drv_write函數中通過MINOR(file->f_dentry->d_inode->i_rdev)來控制不同的燈
如下圖,insmod后自動注冊3個設備節點
測試程序如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/*
* ledtest <dev> <on|off>
*/
void print_usage(char *file) //報錯打印幫助
{
printf("Usage:\n");
printf("%s <dev> <on|off>\n",file);
printf("eg. \n");
printf("%s /dev/leds on\n", file);
printf("%s /dev/leds off\n", file);
printf("%s /dev/led1 on\n", file);
printf("%s /dev/led1 off\n", file);
}
int main(int argc, char **argv)
{
int fd;
char* filename;
char val;
if (argc != 3)
{
print_usage(argv[0]);
return 0;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("error, can't open %s\n", filename);
return 0;
}
if (!strcmp("on", argv[2]))
{
// 亮燈
val = 0;
write(fd, &val, 1);
}
else if (!strcmp("off", argv[2]))
{
// 滅燈
val = 1;
write(fd, &val, 1);
}
else //數據輸入錯誤,打印幫助提示
{
print_usage(argv[0]);
return 0;
}
return 0;
}
驅動程序如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static struct class *firstdrv_class; //創建一個class類
static struct class_device *firstdrv_class_devs[4]; //創建類的設備,led,led1,led2,led3
volatile unsigned long *GPFcon=NULL;
volatile unsigned long *GPFdat=NULL;
/*1寫出驅動程序first_drv_open first_drv_write */
static int first_drv_open(struct inode *inode, struct file *file)
{
int minor=MINOR(inode->i_rdev);
printk("first_drv_open\n"); //打印,在內核中打印只能用printk()
GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虛擬地址
GPFdat=GPFcon+1; //long:32位,所以GPFdat=0x56000050+(32/8)
switch(minor)
{
case 0: //進入led設備,控制所有led
*GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12));
*GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12));
break;
case 1: //進入led1設備,控制 led1
*GPFcon&=~ ((0X3<<8) );
*GPFcon|= (0X1<<8) ;
break;
case 2: //進入led2設備,控制 led2
*GPFcon&=~ ((0X3<<10) );
*GPFcon|= (0X1<<10) ;
break;
case 3: //進入led3設備,控制 led3
*GPFcon&=~ ((0X3<<12) );
*GPFcon|= ((0X1<<12) );
break;
}
return 0;
}
/*參數filp為目標文件結構體指針,buffer為要寫入文件的信息緩沖區,count為要寫入信息的長度,ppos為當前的偏移位置,這個值通常是用來判斷寫文件是否越界*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
int minor=MINOR(file->f_dentry->d_inode->i_rdev);
copy_from_user(&val,buf,count); //通過用戶(應用層)拷貝數據
switch(minor)
{
case 0: //進入led設備,控制所有led
printk("led0,%d\n",val);
if(val) //開燈
{
*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6));
*GPFdat|= ((0X0<<4)| (0X0<<5)| (0X0<<6));
}
else //關燈
{
*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6));
*GPFdat|= ((0X1<<4)| (0X1<<5)| (0X1<<6));
}
break;
case 1: //進入led1設備,控制 led1
printk("led1,%d\n",val);
if(val) //開燈
{
*GPFdat&=~ (0X1<<4);
*GPFdat|= (0X0<<4);
}
else //關燈
{
*GPFdat&=~ (0X1<<4);
*GPFdat|= (0X1<<4);
}
break;
case 2: //進入led2設備,控制 led2
printk("led2,%d\n",val);
if(val) //開燈
{
*GPFdat&=~ (0X1<<5);
*GPFdat|= (0X0<<5);
}
else //關燈
{
*GPFdat&=~ (0X1<<5);
*GPFdat|= (0X1<<5);
}
break;
case 3: //進入led3設備,控制 led3
printk("led3,%d\n",val);
if(val) //開燈
{
*GPFdat&=~ (0X1<<6);
*GPFdat|= ( 0X0<<6);
}
else //關燈
{
*GPFdat&=~ (0X1<<6);
*GPFdat|= (0X1<<6);
}
break;
}
return 0;
}
/*2定義file_operations結構體來封裝驅動函數first_drv_open first_drv_write */
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, //被使用時阻止模塊被卸載
.open = first_drv_open,
.write = first_drv_write,
};
int major; //定義一個全局變量,用來保存主設備號
int first_drv_init(void)
{
int i;
/*3 register_chrdev注冊字符設備*/
/*如果設置major為0,表示由內核動態分配主設備號,函數的返回值是主設備號*/
major=register_chrdev (0, "first_drv", &first_drv_fops);
firstdrv_class= class_create(THIS_MODULE,"firstdrv");
//創建類,它會在sys目錄下創建firstdrv這個類
firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led");
//創建類設備,它會在firstdrv_class類下創建led設備,然后mdev通過這個自動創建/dev/xyz這個設備節點
for(i=1;i<4;i++) //創建led1 led2 led3 設備節點,控制led1 led2 led3
{
firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i);
}
return 0;
}
/*6 寫first_drv_exit出口函數*/
void first_drv_exit(void)
{
int i;
unregister_chrdev (major, "first_drv"); //卸載驅動,只需要主設備號和設備名就行
class_destroy(firstdrv_class); //注銷類,與class_create對應
for(i=0;i<4;i++) //注銷類設備led,led1,led2,led3
class_device_unregister(firstdrv_class_devs[i]);
iounmap(GPFcon); //注銷虛擬地址
}
/*5 module_init修飾入口函數*/
module_init(first_drv_init);
/*7 module_exit修飾出口函數*/
module_exit(first_drv_exit);
MODULE_LICENSE("GPL v2"); //聲明許可證
四、查詢方式來寫按鍵驅動程序
本節目的:
寫second程序,內容:通過查詢方式驅動按鍵
4.1、寫出框架
1)寫file_oprations結構體,second_drv_open函數,second_drv_read函數
2)寫入口函數,並自動創建設備節點,修飾入口函數
3)寫出口函數,並自動注銷設備節點,修飾出口函數
4)寫MODULE_LICENSE(“GPL v2”)聲明函數許可證
5)在入口函數中,利用class_create和class_device_create自動創建設備節點
6)在出口函數中,利用class_destroy和class_device_unregister注銷設備節點
4.2、編譯並加載
寫Makefile並編譯后,放在板子上insmod后,看看lsmod、cat /porc/devices、 ls -l /dev/second是否加載成功
4.3、在框架中實現硬件操作
1)看原理圖和2440手冊,確定用什么寄存器控制按鍵引腳
確定按鍵0~3分別是GPF0,GPF2,GPG3,GPG11
由於是使用查詢模式,並不是外部中斷模式
所以配置 GPFCON(0x56000050)的位[0:1]、位[4:5]等於0x00(輸入模式)
GPGCON(0x56000060)的位[6:7]、位[22:23]等於0x00(輸入模式)
通過GPGDAT (0x56000054) 和GPGDAT(0x56000064)來查詢按鍵狀態
2)寫代碼
init入口函數中使用ioremap()函數映射寄存器虛擬地址
exit出口函數中使用iounmap()函數注銷虛擬地址
open函數中配置GPxCON初始化按鍵
read函數中先檢查讀出的字符是否是4個,然后獲取GPxDAT狀態,用key_vals[4]數組保存4個按鍵值,最后使用 copy_to_user(buf, key_vals,sizeof(key_vals)) 上傳給用戶層
4.4、寫測試程序並測試
1)寫測試程序Secondtest.c
此測試程序使用read(fd,val,sizeof(val));函數讀取內核層的數據
使用此測試程序的用法就是./Secondtest
2)后台運行測試程序
使用./ Secondtest & 后台運行測試程序
后台會一直運行這個程序,當我們有按鍵按下時,就會打印數據出來,如下圖:
3)top指令觀察CPU占有率
通過top命令可以發現這個./ Secondtext占了CPU的99%時間
原因:我們的Secondtext測試程序一直在while中通過查詢方式讀取按鍵狀態,這樣的效率是非常低的.
接下來開始使用中斷方式來改進按鍵驅動程序,提高效率。
4.5、本節代碼
Secondtest測試程序代碼如下:
#include <sys/types.h> //調用sys目錄下types.h文件
#include <sys/stat.h> //stat.h獲取文件屬性
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
/*secondtext while一直獲取按鍵信息 */
int main(int argc,char **argv)
{
int fd,ret;
unsigned char val[4];
fd=open("/dev/buttons",O_RDWR);
if(fd<0)
{
printf("can't open!!!\n");
return -1;
}
while(1)
{
ret=read(fd,val,sizeof(val));
if(ret<0)
{
printf("read err!\n");
continue;
}
if((val[0]&val[1]&val[2]&val[3])==0)
printf("key0=%d,key1=%d,key2=%d,key3=%d\n",val[0],val[1],val[2],val[3]);
}
return 0;
}
second.c按鍵驅動代碼如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static struct class *seconddrv_class; //創建一個class類
static struct class_device *seconddrv_class_devs; //創建類的設備
volatile unsigned long *GPFcon;
volatile unsigned long *GPFdat;
volatile unsigned long *GPGcon;
volatile unsigned long *GPGdat;
static int second_drv_open(struct inode *inode, struct file *file)
{
/*初始化按鍵*/
/* 配置 GPFCON(0x56000050)的位[0:1]、位[4:5]等於0x00(輸入模式)
GPGCON(0x56000060)的位[6:7]、位[22:23]等於0x00*/
*GPFcon&=~((0x3<<0)|(0x3<<4));
*GPGcon&=~((0x3<<6)|(0x3<<22));
return 0;
}
static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
unsigned char key_vals[4];
/*按鍵0~3分別是GPF0,GPF2,GPG3,GPG11*/
if(count!=sizeof(key_vals))
return EINVAL;
key_vals[0]=(*GPFdat>>0)&0X01;
key_vals[1]=(*GPFdat>>2)&0X01;
key_vals[2]=(*GPGdat>>3)&0X01;
key_vals[3]=(*GPGdat>>11)&0X01;
/*上傳給用戶層*/
if(copy_to_user(buf,key_vals,sizeof(key_vals)))
return EFAULT;
return 0;
}
static struct file_operations second_drv_fops={
.owner = THIS_MODULE,
.open = second_drv_open,
.read = second_drv_read,};
volatile int second_major; //保存主設備號
static int second_drv_init(void)
{
second_major=register_chrdev(0,"second_drv",&second_drv_fops); //創建驅動
seconddrv_class=class_create(THIS_MODULE,"second_dev"); //創建類名
seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons");
/*申請虛擬地址,然后配置寄存器*/
/* GPFCON(0x56000050)
GPGCON(0x56000060) */
GPFcon=ioremap(0x56000050,16);
GPFdat=GPFcon+1;
GPGcon=ioremap(0x56000060,16);
GPGdat=GPGcon+1;
return 0;
}
static int second_drv_exit(void)
{
unregister_chrdev(second_major,"second_drv"); //卸載驅動
class_device_unregister(seconddrv_class_devs); //卸載類設備
class_destroy(seconddrv_class); //卸載類
/*注銷虛擬地址*/
iounmap(GPFcon);
iounmap(GPGcon);
return 0;
}
module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE("GPL v2");