記得第一年工作時,leader丟給我的第一項工作就是撰寫新的路由protocol,這個protocol有點像ADHOC的DSR(dynamic source routing),會在封包頭加上路由的資訊,藉由這樣的資訊,讓封包到達目的地,但問題是,我根本連Linux是什麼東東都不太清楚,大學+研所6年讀下來,都活在微軟的世界,碰過的OS只有Windows,所以上班的第一年幾乎每天都在讀Linux的技術文件,和kernel coding的資料,當然,也享受了整整半年多開機當機的慘況,因為kernel module寫的不好,OS就當給你看,經歷過這些雜碎的失敗後,當我寫的module正常的運行在實驗平台時,那種興奮是後來的成功無法取代的
我覺的開始寫複雜的kernel code前,先把演算法相關的程式在user space測試過,再搬到kernel code裡面,在kernel code只要專注kernel API即可(光搞這個就可以耗很多天了),不過我之前碰過一位R公司的RD,他很驕傲的跟我說,他們寫kernel code都不需像我這樣在user space先測,都直接kernel debug,我真的很佩服他們,台灣雖小,神人很多
OK,廢話太多了,我這次要講的kernel module是運行在QT2410上的LED driver,這支driver可透過讀寫proc檔案系統的方式點亮或熄滅LED,如果要點亮LED,下達指令echo 1 > /proc/led,要熄滅LED則下echo 0 > /proc/led,交叉編譯2.6 kernel module的Makefile很簡單,KDIR就是QT2410 Linux核心所在的資料夾路徑,M是kernel module的資料夾所在位置,請參考如下Makefile,如果要縮小kernel module的size,請下指令arm-linux-strip -d led.ko(只去掉debug symbol),這樣子可以減少體積至原來1/10的大小(2007/9/17修改)
- MODULE_NAME = led
- KDIR = ../linux-2.6.26
-
- obj-m := $(MODULE_NAME).o
-
- all:
- $(MAKE) -C $(KDIR) M=$(PWD) modules
- clean:
- $(MAKE) -C $(KDIR) M=$(PWD) clean
LED driver的重點如下
(1)因為QT2410 Linux有MMU,所以對GPIO存取前必需先將physical address轉為virtual address,請參考requestLEDMemRegion這段函式
(2)註冊proc file system,請參考sdr_init的code中,我是如何create_proc_entry和註冊此entry的讀寫函式,雖然user space program跟kernel module溝通的方法還有ioctl,我想我就不多贅述,請多讀Linux Device Driver這本書
LED driver原始碼如下所示
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/proc_fs.h>
- #include <asm/uaccess.h>
- #include <linux/ioport.h>
- #include <asm/io.h>
-
- #define GPIOBAdr 0x56000014
-
-
- static struct proc_dir_entry *LEDProc;
- static int LEDswitch=0;
- static void __iomem *GPIOBBase;
- static int LEDRead(char *page, char **start, off_t off, int count, int *eof,void *data)
- {
- char *p = page;
- int len;
-
- p += sprintf(p, "%d\n", LEDswitch);
- len = (p - page) - off;
- if (len < 0)
- len = 0;
- *eof = (len <= count) ? 1 : 0;
- *start = page + off;
- return len;
- }
-
- static int LEDWrite(struct file *file, const char __user *buffer,unsigned long count, void *data)
- {
- char mode;
-
- if (count > 0)
- {
- if (get_user(mode, buffer))
- return -EFAULT;
- if (mode == '0')
- {
- LEDswitch=0;
- (*(volatile unsigned *)GPIOBBase)|=0x00000002;
- }
- if (mode == '1')
- {
- LEDswitch=1;
- (*(volatile unsigned *)GPIOBBase)&=~(0x00000002);
- }
- }
- return count;
- }
-
- static int requestLEDMemRegion(void)
- {
- if (!request_mem_region(GPIOBAdr, 4, "GPIOBLed"))
- return -EFAULT;
- GPIOBBase= ioremap(GPIOBAdr, 4);
- if (GPIOBBase==0) return -EFAULT;
- else printk("GPIOB virtual address:0x%p\n",GPIOBBase);
-
- return 0;
- }
-
- static int __init sdr_init(void)
- {
- printk("QT2410 LED driver demo\n");
- if (requestLEDMemRegion()==-EFAULT) return -EFAULT;
- LEDProc=create_proc_entry("led",0666,NULL);
- LEDProc->read_proc=LEDRead;
- LEDProc->write_proc=LEDWrite;
- LEDProc->owner=THIS_MODULE;
- return 0;
- }
-
- static void __exit sdr_exit(void)
- {
- remove_proc_entry("led", NULL);
- release_mem_region(GPIOBAdr, 4);
- printk("LED driver remove successfully\n");
- }
- module_init(sdr_init);
- module_exit(sdr_exit);
-
- MODULE_DESCRIPTION("led module");
- MODULE_AUTHOR("Joey Cheng<jemicheng@gmail.com>");
- MODULE_LICENSE("GPL");
- MODULE_ALIAS("QT2410:LED module");
編譯moduel的畫面如下

fw_printenv-control u-boot enviroment variables under linux (2009-05-12 16:35)
rpcapd與netcat-測試嵌入式系統的好工具 (2009-04-09 17:06)
udev-強大的device node管理系統 (2009-02-02 20:35)
Framebuffer兩三事-Test On QT2410 (2009-01-12 16:29)
UIO-Linux user space I/O driver (2008-12-15 14:50)
GDB-刺入bug心臟的寶劍 (2008-10-15 16:11)
Telnet daemon-RS232以外的選擇 (2008-10-03 18:57)
在嵌入式系統切換file system-以squashfs和jffs2為例 (2008-10-01 19:44)
SQLite-麻雀雖小五臟俱全 (2008-09-05 14:46)