驅動函數(1)

話說,從事嵌入式或者相關開發的,最難搞的就是驅動了,其實說實在的,做任何的事情都不是那麼的簡單,你得到的回報大都是小於等於你相應的能力的,個別幸運的就不說了,所以只有學會大都都不會的,你才能脫穎而出,獲得更大的價值。

仔細的想下,很多人很是迷茫的做着自己的事情,甚至幹了很長的時間,也沒有真正的認清楚自己到底爲什麼這樣做,以及這樣做的目的,何爲驅動,簡單的概括,就是爲了給上層提供一個統一的接口,我們都知道在上層有很多的函數,都是通用的,你可以隨便的使用,但是函數名字都是一樣的,這是因爲如果不這樣做的話,估計全世界都亂套了,這是這個行業的規範(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; }

函數功能:向一個地址中寫四個字節



未完待續... ...















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章