u-boot的任務是啓動內核,內核的任務是啓動應用程序 ,應用程序會涉及不少文件和硬件操做(固然不會直接操做硬件),好比讀寫文件,點燈、獲取按鍵值。 好比對於控制led燈的用戶程序與驅動程序,最簡單的實現方法是: 應用程序中須要打開led燈,就須要open函數,在內核中的驅動程序中也有對應的led_open函數,這個led_open函數就是用來負責初始化led的引腳功能,應用程序中要調用read函數讀取led燈的狀態,內核中的驅動程序也有led_read函數。這是應用程序與內核中驅動程序一種最簡單的對應方式. 那麼應用程序中的open、read函數最終怎樣調用到驅動程序中的led_open、led_read呢,中間有哪些東西? 在linux中共有4層軟件,以下圖: 如下名詞解釋:node
例如:linux
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 剩下的就是驅動框架了。數組
本節目的: <font color=#FF0000>先講解驅動框架,而後寫出first_drv驅動程序,來打印一些信息</font> 寫出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" )來聲明 接下來咱們編寫並調試驅動程序。網絡
代碼以下:框架
#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" );
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
1)make,編譯生成frist_drv.ko文件 2)開發板經過nfs網絡文件系統來加載frist_drv.ko 注:加載以前首先經過 cat /proc/devices來查看字符主設備號111是否被佔用,而後經過 insmod first_drv.ko來掛載, 經過 cat /proc/devices就能看到first_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 打印信息:spa
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.一、修改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.二、每次都要手工建立設備節點,你們確定也會以爲這樣作太麻煩了。 改進方法:可使用自動建立設備節點,Linux有udev、mdev的機制,而咱們的ARM開發板上移植的busybox有mdev機制,而後mdev機制會經過class類來找到相應類的驅動設備來自動建立設備節點 (前提須要有mdev) 問:在哪裏設置了mdev機制? 答:在製做根文件系統之使用裏有介紹 2.5.三、接下來使用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.
本節目的: <font color=#FF0000>在上一節搭建的驅動框架下添加硬件的操做</font> 硬件的操做(控制LED)主要分爲以下幾步: 1)看原理圖,肯定引腳 2)看2440手冊 3)寫代碼(須要使用ioremap()函數映射虛擬地址,在linux中只能使用虛擬地址) 4)修改上一節的測試程序 5)使用次設備號來控制設備下不一樣的燈
看原理圖能夠肯定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6
配置GPFCON15:0的位[8:9]、位[10:11]、位[12:13] 都等於0x01(輸出模式) 控制GPFDAT7:0中的位4~6來使燈亮滅(低電平亮)
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)); }
代碼以下:
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時,不能控制點燈 問:若是咱們想分別控制不一樣的燈,該怎麼作? 答:可使用次設備號,次設備號就是用來區分同一設備下不一樣子設備
咱們先來看下面兩個函數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"); //聲明許可證
本節目的: <font color=#FF0000>寫second程序,內容:經過查詢方式驅動按鍵</font>
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註銷設備節點
寫Makefile並編譯後,放在板子上insmod後,看看lsmod、cat /porc/devices、 ls -l /dev/second是否加載成功
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)) 上傳給用戶層
1)寫測試程序Secondtest.c 此測試程序使用read(fd,val,sizeof(val));函數讀取內核層的數據 使用此測試程序的用法就是./Secondtest 2)後臺運行測試程序 使用./ Secondtest & 後臺運行測試程序 後臺會一直運行這個程序,當咱們有按鍵按下時,就會打印數據出來,以下圖: 3)top指令觀察CPU佔有率 經過top命令能夠發現這個./ Secondtext佔了CPU的99%時間 緣由:咱們的Secondtext測試程序一直在while中經過查詢方式讀取按鍵狀態,這樣的效率是很是低的. 接下來開始使用中斷方式來改進按鍵驅動程序,提升效率。
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");