日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux SPI总线设备驱动模型详解

發布時間:2025/3/21 linux 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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
  • struct?bus_type?{??
  • ????const?char?*name;?/*總線名稱*/??
  • ????int?(*match)?(struct?device?*dev,?struct??
  • device_driver?*drv);?/*驅動與設備的匹配函數*/??
  • ………??
  • }??
  • 主要關注match函數,當有一個設備掛載到一條總線上的時候,總線要把這個設備和掛載到這條總線上的驅動一一進行匹配,匹配的函數就是這個match指針。


    ?總線的注冊與注銷

    注冊: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?copy
  • #include?<linux/init.h>??
  • #include?<linux/module.h>??
  • #include?<linux/kernel.h>??
  • #include?<linux/device.h>??
  • ???
  • int?my_match(struct?device?*dev,?struct?device_driver?*drv)??
  • {??
  • ????printk("my_match?was?run\n");??
  • ????return?!strncmp(dev->kobj.name,drv->name,strlen(drv->name));??
  • }??
  • ???
  • struct?bus_type?my_bus_type?=?{??
  • ????.name?=?"my_bus",//總線名稱??
  • ????.match?=?my_match,//驅動與設備匹配函數??
  • };??
  • ???
  • EXPORT_SYMBOL(my_bus_type);??
  • ???
  • static?int?my_bus_init()??
  • {??
  • ????int?ret;??
  • ?????
  • ????ret?=?bus_register(&my_bus_type);??
  • ?????
  • ????return?ret;??
  • }??
  • ???
  • static?void?my_bus_exit()??
  • {??
  • ????bus_unregister(&my_bus_type);??
  • }??
  • ???
  • module_init(my_bus_init);??
  • module_exit(my_bus_exit);??
  • ???
  • MODULE_LICENSE("GPL");??
  • 首先,總線也是內核的一個模塊,我們把它編譯成.ko的方式加載到內核,總線的名字是”my_bus”,總線的匹配函數是my_match,當總線上的驅動和設備都掛載上去時,會調用my_match函數進行配對,配對也很簡單,就是對比驅動和設備名字是否相同。返回非0表示my_match匹配成功,返回0表示匹配失敗。

    EXPORT_SYMBOL(my_bus_type);將my_bus_type結構導出給外部文件用,因為設備和驅動都需要指明要掛載到哪條總線上。

    編寫Makefile,使之生成.ko模塊,加載bus.ko,然后在/sys/bus目錄下會生成my_bus目錄

    ?

    驅動描述結構

    在?Linux內核中,?驅動由?device_driver結構表示。

    [cpp]?view plain?copy
  • struct?device_driver?{??
  • {??
  • ??const?char?*name;?/*驅動名稱*/??
  • ??struct?bus_type?*bus;?/*驅動程序所在的總線*/??
  • ??int?(*probe)?(struct?device?*dev);??
  • ??………??
  • }??
  • Name表示驅動的名字;bus表示驅動要掛載到哪條總線上,待會兒將掛載到剛剛創建的my_bus總線上;probe表示驅動和設備匹配成功之后要運行的函數。

    ?驅動的注冊與注銷:

    驅動的注冊使用:int driver_register(struct device_driver *drv)

    驅動的注銷使用:void driver_unregister(struct device_driver *drv)

    接下來編寫driver.c文件,編譯成模塊,將驅動加載到內核并掛載到my_bus總線上。

    [cpp]?view plain?copy
  • #include?<linux/init.h>??
  • #include?<linux/module.h>??
  • #include?<linux/kernel.h>??
  • #include?<linux/device.h>??
  • ???
  • extern?struct?bus_type?my_bus_type;??
  • ???
  • int?my_probe(struct?device?*dev)??
  • {??
  • ????printk("driver?found?the?devicre?it?can?handle\n");??
  • ????return?0;??
  • }??
  • ???
  • struct?device_driver?my_driver?=??
  • {??
  • ????.name?=?"yty",//驅動名字??
  • ????.bus?=?&my_bus_type,//屬于哪條總線??
  • ????.probe?=?my_probe,??
  • };??
  • ???
  • static?int?my_driver_init()??
  • {??
  • ????return?driver_register(&my_driver);??
  • }??
  • ???
  • static?int?my_driver_exit()??
  • {??
  • ????driver_unregister(&my_driver);??
  • }??
  • ???
  • module_init(my_driver_init);??
  • module_exit(my_driver_exit);??
  • ???
  • MODULE_LICENSE("GPL");??
  • 驅動的名字叫“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
  • struct?device?{??
  • {??
  • const?char?*init_name;?/*設備的名字*/??
  • struct?bus_type?*bus;?/*設備所在的總線*/??
  • ………??
  • }??

  • 設備的注冊與注銷

    設備的注冊使用int device_register(struct device *dev)

    設備的注銷使用:void device_unregister(struct device *dev)

    ?編寫device.c文件:

    [cpp]?view plain?copy
  • #include?<linux/init.h>??
  • #include?<linux/module.h>??
  • #include?<linux/kernel.h>??
  • #include?<linux/device.h>??
  • ???
  • extern?struct?bus_type?my_bus_type;??
  • ???
  • struct?device?my_dev?=??
  • {??
  • ????.init_name?=?"yty",??
  • ????.bus?=?&my_bus_type??
  • };??
  • ???
  • static?int?my_device_init()??
  • {??
  • ????int?ret;??
  • ????ret?=?device_register(&my_dev);??
  • ????return?ret;??
  • }??
  • ???
  • static?void?my_device_exit()??
  • {??
  • ????device_unregister(&my_dev);??
  • }??
  • ???
  • module_init(my_device_init);??
  • module_exit(my_device_exit);??
  • ???
  • MODULE_LICENSE("GPL");??
  • .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总线设备驱动模型详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。