學習驅動程式不久,看LDD3大概有4章吧,最開始寫了個hello world驅動,後來是個面向記憶體的字元裝置驅動,後者讓我清楚了量子和量子集的使用,但是沒有個真正的,肉眼看得見的裝置真是有點不爽,查了些資料,參考了下其他書,打算自己寫個LED的linux驅動,學了字元裝置驅動的話寫這個其實挺簡單的。
帶系統的驅動跟裸機的驅動可不一樣啊,裸機的驅動像單片機那種,你只要把連接埠設0或者設1就可以控制那些裝置,多簡單,與帶作業系統的驅動程式不同。第一,單片機驅動控制的地址是物理地址,而帶作業系統的驅動控制的是虛擬位址,這個在linux上可以很方便的用ioremap函數把物理地址映射為虛擬位址,這個函數在<asm/io.h>聲明;第二,帶作業系統的驅動,是在核心空間工作的,你要提供API給使用者,使用者用這些介面來寫他們的應用程式,他們不會關心底層是怎麼啟動並執行,好比餐廳裡的,作業系統的驅動是廚師,應用程式是顧客,這樣說可能明白很多。
看,是linux的應用程式調用的基本架構,上部黃色的是使用者空間,下部藍色的是核心空間。從上往下,程式員在最上層寫程式,操作一些檔案,這些檔案可以是普通的檔案,也可能是裝置檔案等等,這些操作例如open、read、write、ioctl等,這些操作,到了C庫這一層,C庫會產生一個swi val中斷或者說異常,這個異常就會使應用程式從使用者空間進入到核心空間,下面一層叫System all interface,系統調用介面,這一層會調用下面的虛擬檔案系統VFS(Virtual File System)的sys_open、sys_write這些函數,這些函數就會根據不同的檔案不同的屬性,裝置檔案中還會根據裝置號,來尋找特定的驅動程式,這些驅動程式就是我們要寫的,裡面帶led_open、led_write這些函數。
這些虛擬檔案系統函數sys_open等是怎麼找到我們的驅動程式裡的led_open等的呢?其實在虛擬檔案系統中有我們的很多種類的檔案,普通檔案、目錄檔案、裝置檔案等,就裝置檔案中的字元裝置舉例,它會建立一個字元裝置的數組,這些數組以主裝置號為下標,貌似從0到255,這些數組裡面裝的是一些file_operations結構指標,這些file_operations是在驅動程式裡面定義的,而file_operations結構裡面就包含了我們的驅動的操作函數led_open、led_write等,數群組成員不同,file_operations結構就不同,驅動程式就不同,操作的裝置就不同......大概是這樣
上面是為了協助大家理清思路,大牛無視就行了,
下面正題了,簡單的在linux下的led驅動程式。
開發平台:
ubuntu10.04
測試平台:
TQ2440,作業系統為linux,核心版本linux-2.6.30.4
//leds.c#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/kdev_t.h>#include <linux/kernel.h>#include <asm/io.h>#define GPBOUT (1<<(5*2)) |(1<<(6*2)) | (1<<(7*2)) | (1<<(8*2)); //設定GPB5/6/7/8為輸出int major = 0;struct cdev* leds_cdev;volatile unsigned long *GPBCON = NULL;volatile unsigned long *GPBDAT = NULL;static int leds_open(struct inode *inode, struct file *filp){ *GPBCON = 0; //控制連接埠清零 *GPBCON = GPBOUT; return 0;}static int leds_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg){ if (arg > 4) { printk("deyond the led no\n"); } switch (cmd) { case 1: *GPBDAT |= (1<<(arg + 4)); //滅燈 break; case 0: *GPBDAT &= ~(1<<(arg + 4)); //亮燈 break; default: printk("cmd error\n"); break; } return 0;}struct file_operations leds_fops = { .owner = THIS_MODULE, .open = leds_open, .ioctl = leds_ioctl,};static int __init leds_init(void){ dev_t dev; leds_cdev = cdev_alloc(); alloc_chrdev_region(&dev, 0, 1, "leds"); major = MAJOR(dev); leds_cdev->ops = &leds_fops; cdev_init(&leds_cdev, &leds_fops); cdev_add(&leds_cdev, dev, 1); GPBCON = (volatile unsigned int *)ioremap(0x56000010, 16); //將物理地址映射到虛擬位址,GPBCON物理地址為0x56000010 GPBDAT = GPBCON + 1; //GPBDAT物理地址為0x56000014 return 0;}static void __exit leds_exit(void){ dev_t dev; dev = MKDEV(major, 0); cdev_del(&leds_cdev); unregister_chrdev_region(dev, 1);}module_init(leds_init);module_exit(leds_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("不做超哥已多年");MODULE_DESCRIPTION("leds");
測試程式:
//leds_test.c#include <stdio.h>#include <linux/types.h>#include <fcntl.h>#include <unistd.h>#include <errno.h>#define ON 0#define OFF 1int main(int argc, char* argv[]){ int led_no; if (argc != 3) { printf("Usage: %s <led_no> <ON/OFF>\n", argv[0]); exit(0); } int fd; fd = open("/dev/leds", O_RDWR); if (fd < 0) { printf("open error\n"); exit(0); } led_no = strtoul(argv[1], 0, 0); //將字串轉換成無符號長整型數 if (!strcmp(argv[2], "ON")) ioctl(fd, ON, led_no); else if (!strcmp(argv[2], "OFF")) ioctl(fd, OFF, led_no); else exit(0); return 0;}
編譯驅動模組,在arm板上載入,交叉編譯測試程式,在板子上運行成功
命令是./leds_test led_no ON/OFF 如 ./led_test 1 ON就是點亮led1燈
點亮四個燈後效果:
看到可以控制燈亮滅了,你是否想起了當初玩單片機時點led燈時的興奮呢......哈哈哈哈,好興奮啊