咱們在學習I2C、USB、SD驅動時,你們有沒有發現一個共性,就是在驅動開發時,每一個驅動都分層三部分,由上到下分別是:框架
一、XXX 設備驅動函數
二、XXX 核心層學習
三、XXX 主機控制器驅動設計
而須要咱們編寫的主要是設備驅動部分,主機控制器驅動部分也有少許編寫,兩者進行交互主要時由核心層提供的接口來實現;這樣結構清晰,大大地有利於咱們的驅動開發,這其中就是利用了Linux設備驅動開發中兩個重要思想,今天咱們來仔細解析一下。對象
1、設備驅動的分層思想繼承
在面向對象的程序設計中,能夠爲某一類類似的事物定義一個基類,而具體的事物能夠繼承這個基類中的函數。若是對於繼承的這個事物而言,其某函數的實現與基類一致,那它就能夠直接繼承基類的函數;相反,它能夠重載之。這種面向對象的設計思想極大地提升了代碼的可重用能力,是對現實世界事物間關係的一種良好呈現。接口
Linux內核徹底由C語言和彙編語言寫成,可是卻頻繁用到了面向對象的設計思想。在設備驅動方面,每每爲同類的設備設計了一個框架,而框架中的核心層則實現了該設備通用的一些功能。一樣的,若是具體的設備不想使用核心層的函數,它能夠重載之。舉個例子:開發
[cpp] view plain copyinput
1. return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2) it
2. {
3. if (bottom_dev->funca)
4. return bottom_dev->funca(param1, param2);
5. /* 核心層通用的funca代碼 */
6. ...
7. }
上述core_funca的實現中,會檢查底層設備是否重載了funca(),若是重載了,就調用底層的代碼,不然,直接使用通用層的。這樣作的好處是,核心層的代碼能夠處理絕大多數該類設備的funca()對應的功能,只有少數特殊設備須要從新實現funca()。
再看一個例子:
[cpp] view plain copy
1. return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
2. {
3. /*通用的步驟代碼A */
4. ...
5. bottom_dev->funca_ops1();
6. /*通用的步驟代碼B */
7. ...
8. bottom_dev->funca_ops2();
9. /*通用的步驟代碼C */
10. ...
11. bottom_dev->funca_ops3();
12. }
上述代碼假定爲了實現funca(),對於同類設備而言,操做流程一致,都要通過「通用代碼A、底層ops一、通用代碼B、底層ops二、通用代碼C、底層ops3」這幾步,分層設計明顯帶來的好處是,對於通用代碼A、B、C,具體的底層驅動不須要再實現,而僅僅只關心其底層的操做ops一、ops二、ops3。圖1明確反映了設備驅動的核心層與具體設備驅動的關係,實際上,這種分層可能只有2層(圖1的a),也多是多層的(圖1的b)信盈達嵌入式要領吧五六領悟四五吧。
這樣的分層化設計在Linux的input、RTC、MTD、I2 C、SPI、TTY、USB等諸多設備驅動類型中家常便飯。
2、主機驅動和外設驅動分離思想
主機、外設驅動分離的意義
在Linux設備驅動框架的設計中,除了有分層設計實現之外,還有分隔的思想。舉一個簡單的例子,假設咱們要經過SPI總線訪問某外設,在這個訪問過程當中,要經過操做CPU XXX上的SPI控制器的寄存器來達到訪問SPI外設YYY的目的,最簡單的方法是:
[cpp] view plain copy
1. return_type xxx_write_spi_yyy(...)
2. {
3. xxx_write_spi_host_ctrl_reg(ctrl);
4. xxx_ write_spi_host_data_reg(buf);
5. while(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
6. ...
7. }
若是按照這種方式來設計驅動,結果是對於任何一個SPI外設來說,它的驅動代碼都是CPU相關的。也就是說,固然用在CPU XXX上的時候,它訪問XXX的SPI主機控制寄存器,當用在XXX1的時候,它訪問XXX1的SPI主機控制寄存器:
[cpp] view plain copy
1. return_type xxx1_write_spi_yyy(...)
2. {
3. xxx1_write_spi_host_ctrl_reg(ctrl);
4. xxx1_ write_spi_host_data_reg(buf);
5. while(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
6. ...
7. }
這顯然是不能接受的,由於這意味着外設YYY用在不一樣的CPU XXX和XXX1上的時候須要不一樣的驅動。那麼,咱們能夠用如圖的思想對主機控制器驅動和外設驅動進行分離。這樣的結構是,外設a、b、c的驅動與主機控制器A、B、C的驅動不相關,主機控制器驅動不關心外設,而外設驅動也不關心主機,外設只是訪問核心層的通用的API進行數據傳輸,主機和外設之間能夠進行任意的組合。
若是咱們不進行上圖的主機和外設分離,外設a、b、c和主機A、B、C進行組合的時候,須要9個不一樣的驅動。設想一共有m個主機控制器,n個外設,分離的結果是須要m+n個驅動,不分離則須要m*n個驅動。
Linux SPI、I2C、USB、ASoC(ALSA SoC)等子系統都典型地利用了這種分離的設計思想。