驅動開發之模塊與外部編譯


驅動開發一:

概要:

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燈點亮

 

 

 

--------------------------------->學習路漫漫<------------------------------

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM