Linux SPI总线设备驱动模型详解
隨著技術不斷進步,系統的拓撲結構越來越復雜,對熱插拔、跨平臺移植性的要求越來越高,早期的內核難以滿足這些要求,從linux2.6內核開始,引入了總線設備驅動模型。其實在linux2.4總線的概念就已經提出來了,直到2.6版本的內核才運用。
Linux系統中有很多條總線,如I2C、USB、platform、PCI等。
以spi為例,假如有M種不同類型CPU,N中不同SPI外設,在寫裸機驅動的時候,M種CPU驅動同一個外設需要M份代碼,而N種外設使用同一個cpu又需要N份代碼,所以需要M*N份代碼,這是典型的高內聚低耦合架構。
這種網狀的拓撲結構是不符合人的邏輯思維的,將M*N種耦合變成M+1+N中耦合,將大大減少linux移植工作。
在系統中抽象出一條SPI總線,然后總線中(總線注冊的那個文件?spi.c?和spi.h,I2C總線注冊是i2c-core.c和i2c-core.h)包含SPI控制器抽象結構體spi_master等,spi控制器和外設之間交互采用spi總線提供的標準api來進行,控制器設備和外設驅動填充相關結構體。
試想一下usb,當我們把鼠標或者鍵盤插入電腦時,是不是會有個驅動加載的過程?這就是在尋找總線上的驅動。總線有一種義務,就是感知設備在總線上的掛載和卸載,同時有義務去尋找與設備匹配的驅動。我們的spi也一樣,當有外設掛載到spi總線上的時候,就會尋找總線上所有的驅動與之匹配,匹配成功,則由該驅動服務這個設備。反過來,總線有義務感知驅動在總線上的掛載和卸載,當驅動掛載到總線時,會尋找與之匹配的設備,該驅動就服務于匹配的設備。
?
總線在內核中的抽象
在linux內核中,總線由bus_type結構描述,定義在linux/device.h中。
[cpp]?view plain?copy?總線的注冊與注銷
注冊:bus_register(struct bus_type *bus)若成功,新的總線將被添加進系統,并可在/sys/bus?下看到相應的目錄。
注銷:void bus_unregister(struct bus_type *bus)。
進入到板子的/sys/bus目錄,ls一下,可以看到系統所有的總線。
隨便進入一個目錄,如SPI目錄
Devices目錄表示這條總線上所有掛載的設備。Drivers目錄表示這條總線上所有的設備。
下面以一個示例來注冊一條總線到系統中,一般情況下,是不需要另外添加總線到設備中的。添加的總線名字叫my_bus,加載驅動之后,會在/sys/bus目錄下看到一個my_bus目錄。
新建bus.c:
[cpp]?view plain?copyEXPORT_SYMBOL(my_bus_type);將my_bus_type結構導出給外部文件用,因為設備和驅動都需要指明要掛載到哪條總線上。
編寫Makefile,使之生成.ko模塊,加載bus.ko,然后在/sys/bus目錄下會生成my_bus目錄
?
驅動描述結構
在?Linux內核中,?驅動由?device_driver結構表示。
[cpp]?view plain?copy?驅動的注冊與注銷:
驅動的注冊使用:int driver_register(struct device_driver *drv)
驅動的注銷使用:void driver_unregister(struct device_driver *drv)
接下來編寫driver.c文件,編譯成模塊,將驅動加載到內核并掛載到my_bus總線上。
[cpp]?view plain?copy驅動的名字叫“yty”,屬于bus.c中的my_bus_type這條總線,驅動和設備匹配成功之后,就會運行my_probe函數,也就是會打印出"driver found the devicre it can handle\n"信息。
編譯成.ko文件,然后insmod,在/sys/bus/my_bus/drivers目錄下就生成了yty目錄。
設備描述結構
在?Linux內核中,?設備由struct device結構表示。
[cpp]?view plain?copy設備的注冊與注銷
設備的注冊使用int device_register(struct device *dev)
設備的注銷使用:void device_unregister(struct device *dev)
?編寫device.c文件:
[cpp]?view plain?copy.init_name要和驅動的.那么一樣,要不然匹配不上,.bus仍然是要屬于my_bus總線。
編譯并加載.ko文件,然后會出現如下打印:
由圖可知,當掛載設備到my_bus總線上時,先調用總線上的my_match函數,然后驅動來處理這個設備,驅動中的my_probe就運行了。
總線的感性認識就到此結束了。
?
SPI總線設備驅動分析
在sourceInsight中打開內核代碼drivers/spi/spi.c文件,然后分析。
在spi_init函數中,調用了bus_register注冊一條總線,總線的名字叫做spi,spi_bus_type結構就是我們需要關注的,順便看看.match。
看內核代碼挑重要的看,不要每一行都看,直接跳到strcmp函數去,可以知道總線上驅動和設備的配備是通過比較驅動和設備的名字。如果有多個相同的設備,那么就應該定義.id了,靠id來區別我這個驅動到底是服務哪個設備。
??總線的注冊就講解完畢。在spi.c中,提供了注冊設備和注冊驅動的標準api、提供了spi收發函數、spi初始化函數等。可以理解為spi總線向我們提供了標準的API接口。
以系統提供的范例spidev.c為例:
我們知道,在注冊一個spi驅動是調用系統給我們提供的函數-spi_register_driver,這個標準的api也是由spi.c提供給我們的。通過sourceInsight跳轉到spi_register_driver函數,這個函數就在spi.c中。
由前面的范例代碼知道,注冊一個驅動使用driver_register。
sdrv->driver.bus = &spi_bus_type;表示這個驅動屬于spi這條總線。另外spidev中的probe,remove都通過指針傳到了spi_register_driver函數中。設備和驅動匹配成功,調用spi_drv_probe,它經過賦值之后,是指向spidev.c中的spidev_probe。在spi通用外設驅動spidev.c中,調用spi_async來實現發送和接收數據的,spi_async也是由spi.c提供的,即”總線提供標準API”。
?
Spi設備掛載分析:
添加外設之后,一般都是需要修改板級邏輯的,使用spi通用驅動也不例外。在borad-sam9x5ek.c中要添加。其它cpu類似。
在ek_board_init中調用了at91_add_device_spi函數,將設備注冊到系統。
用sourceInsight繼續追蹤該函數。at91_add_device_spi調用spi_register_board_info?調用spi_register_board_info。spi_register_board_info這個函數就是在spi.c中,也就是說,總線提供標準的API注冊設備到總線上。這個API其實最終還是調用device_register將設備注冊到總線上。
接下來看看spi_async是如何訪問到spi相關寄存器的。追蹤spi_async,spi_async調用__spi_async,然后調用return master->transfer(spi, message);也就是調用master的transfer指針函數,這個函數在哪里被賦值了呢?
找到atmel_spi.c文件。S3c6410板子是spi_s3c64xx.c。然后找到probe函數,atmel是atmel_spi_probe。就會看到如下代碼:
spi_alloc_master也是spi總線提供的標準API,用于申請一個spi_master結構,然后對這個結構初始化,所以spi_async將調用atmel_spi_transfer,然后我們進一步追蹤代碼,atmel_spi_transfer調用atmel_spi_next_message調用atmel_spi_next_xfer?調用atmel_spi_next_xfer_pio,atmel_spi_next_xfer_pio函數就是真正讀寫寄存器的操作了。訪問寄存器不能直接寫哦,需要iomap哦,而且要采用專門的讀寫函數,如readl、readb、writel、writeb、spi_wrtel等。
假如有多個控制器,那么外設怎么和某個控制器建立關系呢?這個任務是由板級邏輯來聯系的。就以剛剛的spidev板級代碼來說
max_speed_hz是說明我這個spidev外設,需要使控制器100萬Hz的時鐘頻率,bus_num說明說明spidev外設需要使用spi0控制器。
?總結:
SPI,I2C,USB等采用總線的方式,將主機驅動和外設驅動分離,這樣就涉及到四個軟件模塊:
1.主機端的驅動。根據具體的cpu芯片手冊操作IIC、SPI、USB等寄存器,產生各種波形。主機端驅動大部分由原廠實現好。
2.連接主機和外設的紐帶。外設驅動不直接調用主機端的驅動來產生波形,而是調用一個標準的API,由這個標準的API把這個波形的傳輸請求間接轉發給了具體的主機端驅動。
3.外設端驅動。外設掛載到IIC、SPI、USB等總線上,我們在probe()函數中去注冊它的具體類型(I2C,SPI,USB等類型),當要去訪問外設的時候,就調用標準的API。如SPI讀寫函數spi_async,I2C讀寫函數:i2c_smbus_read_byte??i2c_smbus_write_byte?等。
4.板級邏輯。板級邏輯用來描述主機和外設如何聯系在一起的,假如cpu有多個SPI控制器,cpu又接有多個SPI外設,那究竟用哪個SPI控制器去控制外設?這個管理屬于板級邏輯的責任。如board-sam9x5ek.c中:.bus_num= 0,表示用SPI0去控制spi通用外設驅動spidev。
總結
以上是生活随笔為你收集整理的Linux SPI总线设备驱动模型详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: register_chrdev_regi
- 下一篇: Linux杂项设备驱动