驅動開發一:
概要:
1、模塊、外部編譯
2、字符設備框架(函數接口和結構體的關系)
3、字符設備框架、platform框架
4、設備樹、led驅動、蜂鳴器驅動
5、內核中斷子系統,按鍵驅動,中斷上下半部。
6、adc驅動,內核的IO模型(阻塞、非阻塞、異步通知、多路復用)
7、I2C總線驅動、I2C設備驅動
8、輸入子系統
知識補充:追內核:
make tags
vi -t xxx
一、什么是驅動?driver老司機
可以操作硬件,同時還會給應用程序提供交互的接口。
二、上層的程序如何操作硬件
1.系統調用:本質上是一個函數接口
2.函數的聲明:存放在指定頭文件中
3.函數的定義:在內核空間中
4.函數的調用:應用程序中使用
三、模塊基本特性
1、什么是模塊?
運行在內核空間中的一段代碼
應用程序 模塊
運行空間 用戶空間 內核空間
入口函數 main 加載函數
調用接口 庫函數或者系統調用 內核函數(主要是fs提供)
空間釋放 自動釋放 手動釋放
模塊不是驅動,它是實現驅動的一種方法。
在內核中驅動、文件系統、網絡協議棧都可以用模塊來實現。
2、模塊的三要素:模塊的聲明、加載函數、卸載函數
模塊的聲明:MODULE_LICENSE("協議名稱");
常見的協議:GPL BSD
加載函數:
-------->默認加載函數
1 int init_module(void);//源碼是由驅動的開發者定義的。
-------->自定義加載函數
297 #define module_init(initfn) \ 298 static inline initcall_t __inittest(void) \ 299 { return initfn; } \ 300 int init_module(void) __attribute__((alias(#initfn))); /*給默認加載函數去別名,叫做initfn*/
1 分析: 2 int a[2][3]; 3 int (*p)[3]; <==> int (*)[3] p; 4
5 int (*p)(void) <==> int (*)(void) p; 6
7 int (*initcall_t)(void);<==> typedef int (*)(void) initcall_t 函數指針類型 8 initfn是一個函數名,類型和initcall_t類型一致。 9 initfn的返回值為int,形參為void
卸載函數:
--------->默認卸載函數
void cleanup_module(void);
--------->自定義卸載函數
1 303 #define module_exit(exitfn)
2 304 static inline exitcall_t __exittest(void) //inline 內聯函數,不進行入棧出棧操作,操作幾次占幾份內存(避免循環使用) 3 305 { return exitfn; } 4 306 void cleanup_module(void) __attribute__((alias(#exitfn))); 5
6 typedef void (*exitcall_t)(void);
3、模塊的編譯方法
內部編譯:驅動源碼存放在內核的指定目錄下進行編譯
1 a.cp demo.c drivers/char 2 b.vi drivers/char/Kconfig 3 ----->添加選項 4 config DEMO 5 tristate "選項名稱" 6 c.vi drivers/char/Makefile 7 添加:obj-$(CONFIG_DEMO) += demo.o 8 d.進入menuconfig中,找到選項選為M 9 e.make modules 10 f.cp drivers/char/demo.ko /rootfs 11 g.開發板啟動后在開發板上執行insmod demo.ko
外部編譯:驅動源碼不在內核指定目錄下(比內部編譯方法方便)
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("GPL"); 4
5 //如果不使用__init加載函數直接被編譯到.text分段中 6 //如果使用__init加載函數會被編譯到.text.init分段中
7 int __init demo_init(void)//自定義加載函數
8 { 9
10 printk("demo_init\n"); 11 return 0; 12 } 13 module_init(demo_init);//給默認加載函數取別名 14
15 //如果不使用__exit,當將驅動代碼直接編譯到內核中時,卸載函數也會參與編譯 16 //如果使用__exit,當驅動代碼直接編譯到內核中,卸載函數不會參與編譯
17 void __exit demo_exit(void) 18 { 19 printk("demo_exit\n"); 20 } 21 module_exit(demo_exit);
1、自己寫Makefile
2、調用到內核提供的模塊編譯方法
3、通知內核模塊編譯方法哪些源文件參與編譯
內核頂層目錄Makefile中:
1248 # The following are the only valid targets when building external 1249 # modules. 1250 # make M=dir clean Delete all automatically generated files 1251 # make M=dir modules Make all modules in specified dir 1252 # make M=dir Same as 'make M=dir modules'
自己的Makefile:
1 ifeq ($(KERNELRELEASE),) /*避免死循環執行makefile*/ 2 PWD = $(shell pwd) /*當前路徑模塊*/ 3 #KERNEL_DIR = /home/linux/linux-3.14/ /*用這個目錄,需要將模塊文件拷貝到開發板上執行*/ 4 KERNEL_DIR = /lib/modules/$(shell uname -r)/build/ /*需要在ubuntu中使用模塊文件*/ 5 6 7 modules: 8 make -C $(KERNEL_DIR) M=$(PWD) modules /*-C進入路徑KERNEL_DIR,找到尋找modules目標,最后回來*/
9
11 clean:
12 make -C $(KERNEL_DIR) M=$(PWD) clean
13 else
14
obj-m += demo1.o //(-m模塊 -y -n)
//xxx-objs := ogj1.o,obj2.o
//obj-m += xxx.o (打包生成xxx.ko文件)
15 endif
模塊符號表導出:
符號本質就是一個函數名或者變量名。
1 EXPORT_SYMBOL_GPL(); 2 EXPORT_SYMBOL(); 3 功能:將符號信息存放到Module.symvers文件中
假設B模塊要使用A模塊中的一個函數。
1、模塊A的函數下調用EXPORT_SYMBOL_GPL(函數名);
2、編譯模塊A
3、加載模塊A
4、拷貝模塊A的Module.symvers文件給模塊B
5、編譯模塊B
6、加載模塊B
運行過程:
1、執行自己的Makefile
2、進入內核頂層目錄的Makefile,尋找modules目標
3、進入scripts/Makefile.modpost
4、回到自己的Makefile
4、模塊的命令
1 加載模塊:insmod xxx.ko 2 卸載模塊:rmmod xxx 3 dmesg 顯示內核打印信息到終端上 4 dmesg -c清空內核打印信息
printk(printf)的級別問題:
消息級別: 0 1 2 3 4 5 6 7
控制台級別: 1 2 3 4 5 6 7 8
----------》數字越小級別越高《-------------------
如果需要直接打印數據到終端上,那么必須保證消息級別大於控制台級別。
ubuntu內核: 4(當前控制台級別) 4(當前消息級別) 1(控制台級別的最小值) 7(默認的控制台級別)
Linux—3.14內核: 7 4 1 7
地址映射函數:
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
功能:地址映射
參數1:物理地址
參數2:映射的字節數
返回值:虛擬地址
/----------------------------------------作業部分-------------------------------------/
實驗點亮exynos4412-fs4412的led2
1.vi dome.c
1 #include <linux/module.h>
2 #include <linux/init.h>
3 #include <asm/io.h> /* vi -t 追到兩個相關頭文件(/include/asm-generic/io.h)如果使用此頭文件,會報錯*/
4 MODULE_LICENSE("GPL"); 5 int __init demo_init(void) 6 { 7 void __iomem *CON = (void __iomem*)ioremap(0x11000c20,4); 8 void __iomem *DAT = (void __iomem*)ioremap(0x11000c24,4); //也可通過地址偏移來實現(void *DAT = CON + 4) 9 //*CON = (*CON & (~(0xf << 0))) | (1 << 0);
writel(readl(CON &(~(0xf << 0))) | (1 << 0)),CON);//驅動程序的寫法,(從內存映射的i/o空間讀/寫數據) 10 //*DAT = *DAT | (1 << 0);
writel(readl(DAT | (1 << 0)),DAT); //readl中--->l:4byte w:2byte b:1byte 11 printk("This is sb!\n"); 12 return 0; 13 } 14 module_init(demo_init); 15
16 void __exit demo_exit(void) 17 { 18
19 } 20 module_exit(demo_exit);
2.vi makefile
1 ifeq ($(KERNELRELEASE),) 2 PWD = $(shell pwd) 3 KERNEL_DIR = /home/linux/linux-3.14/
4 modules: 5 make -C $(KERNEL_DIR) M=$(PWD) modules 6 clean: 7 make -C $(KERNEL_DIR) M=$(PWD) clean 8 else
9 obj-m += demo.o 10 endif
3.make
linux@ubuntu:~/lxq/class/drivers/1day$ make make -C /home/linux/linux-3.14/ M=/home/linux/lxq/class/drivers/1day modules make[1]: Entering directory `/home/linux/linux-3.14' CC [M] /home/linux/lxq/class/drivers/1day/demo.o Building modules, stage 2. MODPOST 1 modules CC /home/linux/lxq/class/drivers/1day/demo.mod.o LD [M] /home/linux/lxq/class/drivers/1day/demo.ko /*生成成功*/
4. cp ~/lxq/class/drivers/1day/demo.ko /rootfs/
5.make clean
6.開發板啟動后在開發板上執行insmod demo.ko -------------->led燈點亮
--------------------------------->學習路漫漫<------------------------------