【Leo的手記】Linux設(shè)備驅(qū)動(dòng)程序手記1+2

注:筆記用
想要編寫Linux設(shè)備驅(qū)動(dòng)程序需要先準(zhǔn)備好對(duì)應(yīng)版本的Linux源碼。如果想要進(jìn)行交叉編譯還需要準(zhǔn)備對(duì)應(yīng)的交叉編譯工具鏈。注意的是,編譯驅(qū)動(dòng)的交叉編譯工具鏈應(yīng)該盡可能地和編譯內(nèi)核所使用的相同。
1.聲明驅(qū)動(dòng)程序入口與出口
每一個(gè)驅(qū)動(dòng)程序(內(nèi)核模塊)在加載和卸載時(shí)都需要做一些對(duì)應(yīng)的操作,這一系列操作被封裝為入口函數(shù)與出口函數(shù)。其中,對(duì)于Linux內(nèi)核模塊,其入口與出口函數(shù)通過module_init和module_exit宏所指定。
其中入口函數(shù)的函數(shù)原型為
出口函數(shù)原型為
2.GPL協(xié)議聲明
Linux內(nèi)核采用GPL協(xié)議,故要求所有的內(nèi)核模塊源碼都應(yīng)該使用GPL協(xié)議進(jìn)行開源。
因此必須要在Linux驅(qū)動(dòng)源碼中添加GPL協(xié)議聲明。如下
3.為字符設(shè)備提供文件IO方法
對(duì)于Linux字符設(shè)備而言,其最常見的抽象方法便是對(duì)外抽象為一個(gè)設(shè)備文件,通過對(duì)設(shè)備文件的open,close,read,write,ioctl等方法對(duì)設(shè)備進(jìn)行直接操作。因此我們需要分別實(shí)現(xiàn)其對(duì)應(yīng)的接口。
在Linux內(nèi)核源碼中對(duì)于文件操作的封裝是一個(gè)名為file_operations的結(jié)構(gòu)體。
因此需要聲明一個(gè)對(duì)應(yīng)的包含了文件操作方法的函數(shù)指針的結(jié)構(gòu)體。如下
在實(shí)現(xiàn)了對(duì)應(yīng)的讀寫接口之后,便可以在入口中向內(nèi)核注冊(cè)對(duì)應(yīng)的字符設(shè)備。
4.注冊(cè)字符設(shè)備
在Linux中,對(duì)于一個(gè)設(shè)備而言,其需要一個(gè)主設(shè)備號(hào)和從設(shè)備號(hào)。該設(shè)備號(hào)可以指定也可以傳入0通過系統(tǒng)分配。在此我們通過系統(tǒng)自動(dòng)分配。
5.注銷字符設(shè)備
在模塊卸載的時(shí)候需要釋放資源,因此最好在驅(qū)動(dòng)卸載時(shí)注銷字符設(shè)備。如下
6.Makefile
內(nèi)核模塊需要借助Linux內(nèi)核源碼進(jìn)行編譯。因此需要編寫Makefile去調(diào)用內(nèi)核源碼的Makefile,如下
對(duì)于交叉編譯,在make時(shí)需要指定ARCH和CROSS_COMPILE,同內(nèi)核編譯
7.用戶空間與內(nèi)核空間
對(duì)于Linux驅(qū)動(dòng)程序而言,其作為內(nèi)核模塊運(yùn)行于內(nèi)核空間中,因此不能與用戶空間地址進(jìn)行直接的操作。在進(jìn)行數(shù)據(jù)拷貝的操作中,需要使用copy_from_user和copy_to_user方法進(jìn)行操作。
另外在內(nèi)核模塊中進(jìn)行輸出,不能使用用戶態(tài)的printf等函數(shù),內(nèi)核提供了printk函數(shù)用于輸出系統(tǒng)日志,可以在系統(tǒng)中通過dmesg命令進(jìn)行查看。
8.映射物理地址
編寫一個(gè)驅(qū)動(dòng)程序,用于控制某個(gè)GPIO引腳。其他的部分和一般的字符設(shè)備驅(qū)動(dòng)相同,實(shí)現(xiàn)文件操作接口,注冊(cè)字符設(shè)備,注冊(cè)設(shè)備類以及設(shè)備文件。但是對(duì)于硬件外設(shè)而言,通常需要通過控制對(duì)應(yīng)的外設(shè)寄存器來實(shí)現(xiàn)外設(shè)的控制。外設(shè)寄存器以官方給定的物理地址存在于系統(tǒng)的物理內(nèi)存空間內(nèi)。因此想要操作對(duì)應(yīng)的寄存器需要映射對(duì)應(yīng)的物理內(nèi)存地址到虛擬內(nèi)存空間。
8.1 映射Allwinner F1c100s的GPIOA的寄存器
查看F1c100s的官方手冊(cè)可以得知,對(duì)于GPIOA的操作,不考慮中斷的前提下僅需要操作兩個(gè)寄存器,即PA Configure Register0和PA Data Register,分別對(duì)應(yīng)地址0x01c20800和0x01c20810。
在程序中聲明全局靜態(tài)變量PACFG0與PADAT,類型為32位無符號(hào)整型指針
在內(nèi)核模塊入口函數(shù)中,使用ioremap對(duì)地址進(jìn)行映射
ioremap函數(shù)原型為
其中offset為物理地址偏移,size為映射的大小。
映射F1c100s PIOA相關(guān)寄存器的代碼如下。
9.初始化GPIO
假定要實(shí)現(xiàn)GPIO輸出的控制,需要配置GPIO的模式為Out模式,見手冊(cè)

如要操作PA0,僅需將PACFG0的低三位配置為0b001即可。在入口函數(shù)中加入
同樣在出口函數(shù)中也要對(duì)其進(jìn)行對(duì)應(yīng)的配置,可以在入口中保存其初始值并在出口中恢復(fù),也可以直接關(guān)閉,在此選擇直接關(guān)閉。
10.GPIO的讀寫
GPIO的讀寫僅需要對(duì)其數(shù)據(jù)寄存器的對(duì)應(yīng)位進(jìn)行操作即可。
附錄A. 驅(qū)動(dòng)程序代碼
附錄B. 測(cè)試程序代碼