話說,從事嵌入式或者相關開發的,最難搞的就是驅動了,其實說實在的,做任何的事情都不是那麼的簡單,你得到的回報大都是小於等於你相應的能力的,個別幸運的就不說了,所以只有學會大都都不會的,你才能脫穎而出,獲得更大的價值。
仔細的想下,很多人很是迷茫的做着自己的事情,甚至幹了很長的時間,也沒有真正的認清楚自己到底爲什麼這樣做,以及這樣做的目的,何爲驅動,簡單的概括,就是爲了給上層提供一個統一的接口,我們都知道在上層有很多的函數,都是通用的,你可以隨便的使用,但是函數名字都是一樣的,這是因爲如果不這樣做的話,估計全世界都亂套了,這是這個行業的規範(GPL),那麼針對不同的開發平臺,雖然上面是一樣的,但是下面肯定是不一樣的,這就是我們驅動工程師的事情了,我們要爲上層服務。
所以對驅動工程師的要求一般很多,既要了解整個平臺的架構,又要理解底層的相關硬件的原理,上層的話,你要知道整個應用層的具體實現,這樣拿到高的回報也就理所當然啦。
最近一直涉及驅動方面的學習還談不上研究,在我看來,相對於應用層的千變萬化來說,驅動層很少變化,很多都是相似的開發套路,下面結合最近所學,介紹幾個經常使用到的函數,以及關於驅動的相關知識,希望對正在從事本行業的友人,提供點滴參考,當然,如果哪裏寫的不是很恰當,歡迎批評指出。
1. 初始化設備cdev結構體函數
void cdev_init(struct cdev * cdev,const struct file_operations * fops)
函數功能: 初始化cdev 結構體
參數:
cdev : cdev 結構體
fops : 操作函數的結構體
2. 申請設備號的函數
Int register_chrdev_region(dev_t from ,unsigned count ,const char * name)
功能: 申請設備的設備號
參數:
from : 包含主設備號的數字
count : 設備號的個數
name : 設備號的名字,注意,我們可以在/proc/devices中看到我們所設置的名字
成功返回 0 ,失敗返回負的錯誤碼
3. 添加字符設備函數
int cdev_add(struct cdev * p,dev_t dev,unsigned count);
參數:
p : cdev結構體
dev : 第一個設備號
count : 次設備號的個數
成功返回0 ,失敗返回負的錯誤碼
4. 動態註冊設備號的函數
int alloc_chrdev_region(dev_t * dev , unsigned baseminor ,unsigned count,const char * name)
參數:
dev : 獲得的設備號
baseminor : 第一個次設備號
count : 次設備號的個數
name : 設備號的名字
成功返回0 ,失敗返回負的錯誤碼
5. 通過主 次設備生成設備號的宏
MKDEV( major, minor)
參數:
major:主設備號
minor:次設備號
6. 通過設備號,獲得主設備號,或次設備號
獲得主設備號的宏:MAJOR(dev_num) 獲得設備號的前12位
獲得此設備號的宏:MINOR (dev_num) 獲得設備號的後20位
7.如何在添加驅動的時候自動生成設備節點
1》創建類 在/sys/class目錄下創建一個子目錄
struct class * class_create(struct module *owner ,char *name)
參數:
owner : 這個是固定死了的,THIS_MODULE
name: 子目錄的名字
成功返回有效的指針,失敗返回負的錯誤碼
2》 在sysfs文件中註冊設備(導出主設備號和次設備號)
struct device * device_create(struct class *class ,struct device *parent, dev_t devt,void *drvdata, const char *fmt,...)
參數:
class: 類結構體的首地址
parent: NULL
devt : 設備號
drvdata:NULL
fmt: 格式化字符串 "mycdev" or "mycdev_%d",i
成功返回有效指針,失敗返回負的錯誤碼
8.那麼,我們如何判斷是有效的指針還是負的錯誤碼呢?
提供了一個宏:
IS_ERR(指針) PRT_ERR(指針)
返回值:若是負的錯誤碼返回真 返回負的錯誤碼
9. 用戶空間和驅動程序數據之間的交互
long copy_to_user(void __user *to,const void *from,unsigned long n)
函數功能: 將內和空間的數據拷貝到用戶空間,這裏邊需要注意的是,__user只是一個空的宏定義,爲的就是標記用戶空間
參數:
to :用戶空間的地址
from: 內核空間的地址
n : 大小
成功返回0 , 失敗返回未拷貝的數據大小
10. long copy_from_user(void *to ,const void __user from,unsigned long n)
參數 :
to : 內核空間的地址
from :用戶空間的地址
n: 大小
成功返回0 ,失敗返回未拷貝的數據的大小
11 .單個數據拷貝
宏: put_user(value , ptr)
功能:將value寫到ptr指向的地址(用戶)
宏: get_user(value,ptr)
功能: 將ptr指向的地址(用戶)內容讀到value
12.可以通過一些命令來控制的函數ioctl
這裏面需要注意的,就是內核版本的不同,所使用的函數的名字是不同的
在2.6內核以及之前使用的是:
int (*ioctl )(struct inode *inode ,struct file* file,unsigned int cmd,unsigned long arg);
而在2.6版本之後使用的是:
long (*unlocked_ioctl)(struct file * file,unsigned int cmd,unsigned long arg);
參數:
cmd:傳遞的命令,通常就是一些宏定義之類的
arg :傳遞的參數(包括數值或地址)如果是地址值的話,這個地址是用戶空間的,可以用put_user,or get_user來操作
13.另外,我們如果要對哪個GPIO口來進行操作的話,我們還要去查我們的用戶手冊,但是從手冊上面我們得到的地址是實際的物理地址,而我們的操作系統使用的是虛擬的地址,那麼這個時候,我們就需要將實際的物理地址做一個映射,映射爲虛擬地址。
void *ioremap(unsigned long phy_addr,int size)
功能:將物理地址與虛擬地址進行映射
參數:
phy_addr: 物理地址
size : 映射的大小
14. 有些時候我們需要從一個很長的地址中取出那些我們需要的位,這個時候,就需要使用下面的這兩個函數了
1》static inline u32 readl(const volatile void _iomem * addr)
{ return *(const valatile u32 __force *) addr; }
函數功能:從一個地址中讀取四個字節
2》static inline void writel(u32 b, volatile void _iomem * addr)
{ (volatile u32 _force *)addr = b; }
函數功能:向一個地址中寫四個字節
未完待續... ...