作為開頭篇,我不想寫HELLLOWORLD驅動,甚至字符設備驅動的開發,這樣文章充斥在各大網站上的博客上,隨便搜搜,就可以找到幾百篇。這是最基本的東西,通過這些內容的學習,我們要掌握LINUX驅動的基本要素,比如初始化函數,退初函數,以及去理解簡單的驅動的MAKEFILE的編寫,推薦去看LDD,這方面有比較詳細的敘述。
但是我的理解,即使我們會寫這些東西,對我們的工作也沒有太大的用處,如果你深入去讀LINUX的驅動代碼,就會發現,你還是雲裡霧裡。比如觸摸屏是字符設備,可是怎麼我看到的觸摸屏驅動裡根本看不到一點字符設備的影子。OHOH...,因此我覺得學習LINUX驅動,要跳出一個圈子,不能把自己局限在某個驅動的編寫上,而應該把重心放在模型或者架構的層次上去掌握它,只有這樣才能更快地深入地理解LINUX驅動的精髓。
因此在看過HELLWORLD或者寫過一個簡單的字符設備驅動之後,應該迅速地去學習LINUX的設備模型,這是我們必須第一個要學習的模型,也是最重要一個模型。如果你不能理解它,你將掉進驅動的泥潭裡。
學習之前,不要忘記,源碼,源碼!!!在看任何關於LINUX的文章的時候,都要把SOURCEINSIGHT打開,隨時要做好准備去查源碼,源碼是理解任何LINUX驅動的捷徑。
不要忘記這個目錄Documentation,你會有意想不到的發現,這是LINUX開發者留給我們的精華。
LINUX設備模型四要素
linux設備模型的抽象是總線、設備、驅動,類。按照這個順序來分析就可以勾勒出linux設備模型。
很多人看設備模型,會選擇直接去學習Kobject、Kset 和Subsystem(在2.6其實也是KSET),進入了個大坑,讓人直接對LINUX驅動模型的理解望而生畏。這麼復雜的東西,實在太難以理解了。我們是不是可以試著先忽略掉它們,你們是誰呀,我不想知道你們,滾一邊去。
我們先簡單的講一下總線、設備、驅動的關系(類到下一節再來說)。在LINUX驅動的世界裡,所有的設備和驅動都是掛在總線上的,也就是總線來管理設備和驅動的,總線知道掛在它上邊的所有驅動和設備的情況,由總線完成驅動和設備的匹配和探測。至於怎麼實現,以後再講,現在只要姐的這一點就可以了。讓我們想象以前我們工作碰到的各種總線,I2C,SPI,PCI等等,看來總線也不神秘呀。如果你夠聰明的話,估計你要問,有些設備不是直接連在總線上的呀,LINUX如何管理的呢?比如RTC,那我先告訴你,針對SOC(什麼是SOC,自己去BAIDU去)上一些外設,系統已經虛擬了一個PLATFORM總線,更准確的說,是一套PLATFORM總線,設備,驅動, 甚至I2C這些總線也是掛在這個PLARFORM上的。是不是迷糊了? 那就暫時忘記最後半句話。
在我們的腦海中,應該有這個概念,總線上掛著驅動和設備,一個驅動可以管理多個設備,一個設備保存一個對應驅動的信息,一般在初始化的時候,總線先初始化,然後設備先注冊,最後驅動去找設備,完成他們之間的銜接。
我們要不要去寫一個總線驅動呢?可以說99.999999%的人都不需要,系統已經給我們准備好了我們所學要的總線。對於我們來說,就是去學好怎麼在系統中添加設備以及相關的驅動就行了。我是沒寫過任何總線的驅動,所以我們看看設備和驅動,回頭再看總線。
LINUX設備
在底層,LINUX設備都可以用DEVICE結構的一個實例來表示:
struct device {
在這個節都中還包含著許多其他的結構成員,只是我們現在暫時不考慮,否則又要出現很多個為什麼了。我們現在只關心這幾個成員:
1)設備類型
2)設備所掛的總線
3)設備的驅動
4)設備的私有數據
可以看出,描述設備的結構已經把自身和總線以及設備關聯起來,一般情況下,我們也不會這麼定義一個設備。為了描述一個設備,常常把設備其他信息和這個結構定義在一起來描述特定的設備。以我們之前提到的虛擬的PLARFORMDEVICE為例:
struct platform_device {
};
在我們寫的驅動裡,我們常常這麼定義一個設備:platform_device
在S3C系列中,它所支持的大部分設備都是在common-sdk.c和MACH-SMDK***.C中定義好,在板級初始化的時候通過smdkXXXX_init(void)把設備添加到系統中->調用platform_add_devices。而這個函數platform_add_devices的參數smdk_devs則包含系統了S3C上支持的設備。
//共用的
static struct platform_device __initdata *smdk_devs[] = {
};
//具體芯片的
static struct platform_device *smdk2410_devices[] __initdata ={
};
在MACH-SMDK***.C文件(比如MACH-SMDK2410.C)的最後幾行看看MACHINE_START->smdk2410_init->smdk_machine_init()->platform_add_devices,就這樣完成了板子上主要設備的注冊,掛在PLATFORM總線上了。
關於換個PLATFORMDEVICE,還有一個很關鍵的地方就是該結構一個重要的元素是resource,該元素存入了最為重要的設備資源信息,定義在kernel\include\linux\ioport.h中,
struct resource {
};
具體可以這麼定義:
static struct resource s3c_lcd_resource[] = {
};
這裡定義了兩組resource,它描述了一個LCD設備的資源,第1組描述了這個LCD設備所占用的
總線地址范圍,也就是DATASHEET上LCD控制器的寄存器地址的范圍IORESOURCE_MEM表示第1組描述的是內存類型的資源信息,第2組描述了這個LCD設備的中斷號,IORESOURCE_IRQ表示第2組描述的是中斷資源信息。設備驅動會根據flags來獲取相應的資源信息。
設備驅動
在LINUX中,一個設備驅動是以device_driver這個結構描述的(還包含著許多其他的結構成員,只是我們現在暫時不考慮)。
struct device_driver {
};
driver_register用來把去總掛接到總線上。
和DEVICE類似,在寫驅動的時候,我們也會把device_driver 包裝一下,比如platform_driver。
struct platform_driver {
};
我們定義一個LCD驅動:
static struct platform_driver s3c_fb_driver = {
};
在模塊初始化的時候,調用platform_driver_register調用driver_register把注冊platform_driver到PLATFORM總線上,去看看platform_driver_register的實現就什麼都清楚了。
int platform_driver_register(struct platform_driver *drv)
{
}
在一個驅動中,最最重要的一個接口就是probe函數,它負責去獲取對應DEVICE的數據信息,然後初始化這個設備。如果完成這個函數,我們就完成了驅動的大部分工作了。暫時先停住,在具體的驅動分析中,我們再來考慮這個問題。
到目前為止,我們還沒有說清楚這個DEVICE和DRIVER是怎麼聯系起來的。正如你想到的,我們要去總線那裡去看看,它是設備模型中的管理者嘛,因此把驅動和設備聯系起來,就是它主要的工作了。
總線
我們直接看代碼,看看總線在系統中是如何表述的:
struct bus_type {
};
name 就是總線的名字,比如PLATFORM,PCI,I2C等等。
對於總線,PLATFORM總線倒是沒有封裝,直接采用下邊方法定義:
struct bus_type platform_bus_type = {
這條總線在系統初始化的時候通過platform_bus_init來初始化,顯然這個動作應該驅動注冊之前完成。
總線是用MATCH方法來完成驅動和設備的聯系的。當總線上的新設備或者新的驅動程序被添加的時候,會來調用這個函數。如果指定的驅動程序能夠處理指定的設備,干函數就返回非0,去執行驅動的PROBE函數.
我們結合驅動的注冊來看看這個過程是什麼樣子的?
在驅動初始化函數中調用函數platform_driver_register()注冊platform_driver,需要注意的是
platform_device結構中name元素和platform_driver結構中driver.name必須是相同的,這樣
在platform_driver_register()注冊時會對所有已注冊的所有platform_device中的name和當前注
冊的platform_driver的driver.name進行比較,只有找到相同的名稱的platfomr_device才能注冊
成功,當注冊成功時會調用platform_driver結構元素probe函數指針。
platform_driver_register
platform_driver->probe
這樣就直接走到我們驅動PROBE函數中,這個過程很負責,但是對於我們來說只要記住亮點:
1)MATCH的標准: NAME 要相同,或者有的驅動和設備支持ID
2)MATCH成功,我們就轉向驅動的PROBE函數。
我們來看一個驅動的PROBE函數:
static int
{
//這個數據是在設備定義的時候定義的,就是我們前面看到的內存,IRQ等資源
//獲取時鐘,並且ENABLE它
//處理來自驅動的資源,記著去ioremap,為什麼呢?見下邊。
//初始化硬件
//出錯處理
}
這裡說明一下如何獲取資源的:
當進入probe函數後,需要獲取設備的資源信息,獲取資源的函數有:
struct resource * platform_get_resource(struct platform_device*dev, unsigned int type, unsigned int num);
根據參數type所指定類型,例如IORESOURCE_MEM,來獲取指定的資源。
struct int platform_get_irq(struct platform_device *dev, unsignedint num);
獲取資源中的中斷號。
struct resource * platform_get_resource_byname(structplatform_device *dev, unsigned int type, char *name);
根據參數name所指定的名稱,來獲取指定的資源。
int platform_get_irq_byname(struct platform_device *dev, char*name);
根據參數name所指定的名稱,來獲取資源中的中斷號。
ioremap是用來把資源中定義的物理地址轉換成內核虛擬地址的,我們的代碼中用到的地址是虛擬地址,必須做這樣的轉換喲。還有一種靜態的轉換方法,我們以後再來看。
在ioremap之後,我們就可以讀寫外設的寄存器了,比如控制寄存器,狀態寄存器,數據寄存器等等,這樣就可以操作外圍設備了。
當然,還有一些其他東西沒有提,對應各種注冊,添加函數,同樣存在注銷,移除等函數,基本就是做相反的操作,只要稍微看看就行了。
上邊的例子是以PLATFORM 的總線,設備和驅動來講的,其實I2C等等總線以及設備也采取的是大致的流程。