i2c的設備樹和驅動是如何匹配以及何時調用probe的?

2020-12-01 一口Linux

一、粉絲提問

i2c的設備樹和驅動是如何匹配以及何時調用probe的? 粉絲手裡的I2C外設是ov5640,一個攝像頭。 粉絲提問,一口君必須安排。

二、問題分析

設備樹信息如下:

 ov5640: ov5640@3c {  compatible = "ovti,ov5640";   reg = <0x3c>;  pinctrl-names = "default";  pinctrl-0 = <&pinctrl_csi1                             &csi_pwn_rst>;  clocks = <&clks IMX6UL_CLK_CSI>;  clock-names = "csi_mclk";  pwn-gpios = <&gpio1 4 1>;  rst-gpios = <&gpio1 2 0>;  csi_id = <0>;  mclk = <24000000>;  mclk_source = <0>;  status = "okay";  port {   ov5640_ep: endpoint {    remote-endpoint = <&csi1_ep>;   };  }; };

驅動最重要的結構體如下:

ov5640_i2c_driver

要搞懂這個問題,我們需要有一些基礎知識:

1.內核如何維護i2c總線

Linux內核維護很多總線,platform、usb、i2c、spi、pci等等,這個總線的架構在內核中都支持的很完善,內核通過以下結構體來維護總線:

struct bus_type { const char  *name; const char  *dev_name; struct device  *dev_root; struct device_attribute *dev_attrs; /* use dev_groups instead */ const struct attribute_group **bus_groups; const struct attribute_group **dev_groups; const struct attribute_group **drv_groups; int (*match)(struct device *dev, struct device_driver *drv); int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device *dev); int (*remove)(struct device *dev); void (*shutdown)(struct device *dev); int (*online)(struct device *dev); int (*offline)(struct device *dev); int (*suspend)(struct device *dev, pm_message_t state); int (*resume)(struct device *dev); const struct dev_pm_ops *pm; struct iommu_ops *iommu_ops; struct subsys_private *p; struct lock_class_key lock_key;};

i2c對應總線結構體變量為i2c_bus_type,定義如下:

drivers/i2c/I2c-core.c 

struct bus_type i2c_bus_type = { .name  = "i2c", .match  = i2c_device_match, .probe  = i2c_device_probe, .remove  = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm  = &i2c_device_pm_ops,};

其中:

  1. i2c_device_match(),匹配總線維護的驅動鍊表和設備信息鍊表,如果其中名字完全相同,則返回true,否則false;
  2. i2c_device_probe(),當我們註冊一個i2c_drive或者i2c_client結構體時,會從對應的鍊表中查找節點,並通過i2c_device_match函數比較,如果匹配成功,則調用i2c_drive中定義的probe函數,即ov5640的ov5640_probe()函數;
  3. remove:如果卸載i2c_drive或者i2c_client結構體,會調用該函數卸載對應的資源;
  4. shutdown、pm是電源管理的接口,在此不討論。

該結構體變量在函數i2c_init()中初始化:

static int __init i2c_init(void){ ………… retval = bus_register(&i2c_bus_type); …………}

i2c架構是通用架構,可支持多種不同的i2c控制器驅動。

2. i2c架構如何如何管理硬體信息和驅動?

不論哪一種總線,一定會維護兩個鍊表,一個是驅動鍊表,一個是硬體信息鍊表。 鍊表如下:

i2c總線的兩個節點信息如下:

「struct i2c_driver」

struct i2c_driver { unsigned int class; /* Notifies the driver that a new bus has appeared. You should avoid  * using this, it will be removed in a near future.  */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; /* Standard driver model interfaces */ int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); /* driver model interfaces that don't relate to enumeration  */ void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); /* Alert callback, for example for the SMBus alert protocol.  * The format and meaning of the data value depends on the protocol.  * For the SMBus alert protocol, there is a single bit of data passed  * as the alert response's low bit ("event flag").  */ void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions  * with the device.  */ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver; const struct i2c_device_id *id_table; /* Device detection callback for automatic device creation */ int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients;};

  1. 當總線匹配驅動和硬體信息成功後就會調用其中的probe()函數;
  2. struct device_driver driver,內核中註冊的驅動模塊,必須包含該類型的結構體成員。

「struct i2c_client」

成員 含義 unsigned short flags 從設備地址長度 unsigned short addr 從設備地址 char name[I2C_NAME_SIZE] 從設備地址名稱 struct i2c_adapter *adapter 從設備地址對應的控制器驅動地址 struct device dev 註冊到內核的每一個設備模塊都需要先定義一個該結構體變量,對應struct device_driver driver int irq 從設備地址往往會有一根中斷線連接到SOC的中斷控制器 struct list_head detected 鍊表

3. i2c_driver和i2c_client

1) i2c_driver如何註冊

i2c_driver結構需要我們自己定義,然後通過函數i2c_register_driver()註冊,將該結構體變量註冊到i2c_driver鍊表,同時從i2c_client鍊表中查找是否有匹配的節點:

設備樹情況下,會比較i2c_drive->driver->of_match_table->compatible和i2c_client->name,對應例子中的of_ov5640_id:

非設備樹比較i2c_drive->id_table->name和i2c_client->name,對應例子中的ov5640_id:

代碼中並沒有直接調用函數i2c_register_driver()註冊,而是使用了下面的這個宏:

該宏定義如下:

include/linux/I2c.h

該宏其實自動幫我生成了insmod和rmmod會用到宏module_init和module_exit,以及註冊和註銷i2c_driver結構體的代碼。

如果看不明白宏,可以編寫測試文件: test.c

#define module_i2c_driver(__i2c_driver) \ module_driver(__i2c_driver, i2c_add_driver, \   i2c_del_driver)   #define module_driver(__driver, __register, __unregister, ...) \static int __init __driver##_init(void) \{ \ return __register(&(__driver) , ##__VA_ARGS__); \} \module_init(__driver##_init); \static void __exit __driver##_exit(void) \{ \ __unregister(&(__driver) , ##__VA_ARGS__); \} \module_exit(__driver##_exit);module_i2c_drive(ov5640_i2c_driver);

預編譯:

gcc -E test.c

得到宏替換後的結果:

static int __init ov5640_i2c_driver_init(void) {  return i2c_add_driver(&(ov5640_i2c_driver)); } module_init(ov5640_i2c_driver_init); static void __exit ov5640_i2c_driver_exit(void) { i2c_del_driver(&(ov5640_i2c_driver)); } module_exit(ov5640_i2c_driver_exit);;

內核中有大量的高效簡潔的宏定義,Linux內核就是個寶庫,裡面有大量的優秀的代碼,想提高自己的編程能力,就一定要多看代碼,代碼讀百遍,其義自見。

一口君認為,如果Linux代碼都看不太明白,就不要自稱精通C語言,充其量是會用C語言。

2)i2c_client如何生成(只討論有設備樹的情況)

在有設備樹的情況下,i2c_client的生成是要在控制器驅動adapter註冊情況下從設備樹中枚舉出來的。

i2c控制器有很多種,不同的廠家都會設計自己特有的i2c控制器,但是不論哪一個控制器,最終都會調用 i2c_register_adapter()註冊控制器驅動。

i2c_client生成流程如下:

i2c_client

三、 i2c的設備樹和驅動是如何匹配以及何時調用probe?

1. i2c的設備樹和驅動是如何match,何時調用probe?

從第二章第3節可知,驅動程序中 module_i2c_drive()這個宏其實最終是調用 i2c_add_driver(&(ov5640_i2c_driver));註冊ov5640_i2c_driver結構體;當我們insmod加載驅動模塊文件時,會調用i2c_add_driver()。

該函數定義如下:

#define i2c_add_driver(driver) \ i2c_register_driver(THIS_MODULE, driver)

下面我們來追蹤i2c_register_driver()這個函數:

其中drv->bus就是我們之前所說的i2c_bus_type,上圖中,分別調用了.match、.probe:

struct bus_type i2c_bus_type = { .name  = "i2c", .match  = i2c_device_match, .probe  = i2c_device_probe, .remove  = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm  = &i2c_device_pm_ops,};

下面我們來追一追這兩個函數

2. i2c_device_match()

i2c_device_match

3. i2c_device_probe

如下圖所示,通過driver->probe()調用到我們定義的struct i2c_driver ov5640_i2c_driver結構體變量中的ov5640_probe()函數:

i2c_device_probe

【注意】 內核代碼中大量使用到driver = to_i2c_driver(dev->driver);通過通用的結構體變量成員struct device_driver *driver來查找自己註冊的xx_driver地址。


往期問答匯總:

  1. 粉絲提問|c語言:如何定義一個和庫函數名一樣的函數,並在函數中調用該庫函數
  2. 一個埠號可以同時被兩個進程綁定嗎?
  3. 兩個線程,兩個互斥鎖,怎麼形成一個死循環?
  4. 一個例子讓你看清線程調度的隨機性


更多關於Linux arm的知識,請關注 一口Linux

相關焦點

  • i2c的設備樹是如何匹配以及何時調用probe的?
    一、粉絲提問i2c的設備樹和驅動是如何匹配以及何時調用probe的?粉絲手裡的I2C外設是ov5640,一個攝像頭。粉絲提問,一口君必須安排。2. i2c架構如何如何管理硬體信息和驅動?不論哪一種總線,一定會維護兩個鍊表,一個是驅動鍊表,一個是硬體信息鍊表。鍊表如下:
  • 「正點原子Linux連載」第六十一章Linux I2C驅動實驗
    ,無設備樹的時候匹配ID表。第22~25行,of_device_id,設備樹所使用的匹配表。第28~37行,i2c_driver,當I2C設備和I2C驅動匹配成功以後probe函數就會執行,這些和platform驅動一樣,probe函數裡面基本就是標準的字符設備驅動那一套了。
  • fireflyAIO-3288C主板I2C簡介
    定義 I2C 驅動 在定義 I2C 驅動之前,用戶首先要定義變量 of_device_id 和 i2c_device_id 。of_device_id 用於在驅動中調用dts文件中定義的設備信息,其定義如下所示: static const struct of_device_id of_rk_lt8641ex_match[]={ { .compatible = "firefly,lt8641ex" }, { /* Sentinel */ } };
  • 《rt-thread驅動框架分析》-i2c驅動
    ③RTT在核心層上,也像pin驅動那樣,封裝了一套API(虛線箭頭),供用戶直接使用。④dev是提供RTT設備驅動框架的統一的API(實現箭頭)。⑤注意的是:模擬I2C驅動到核心層,增加了一層中間層。設備層:設備就是雜七雜八的使用I2C的總線的設備。而這些設備可以選擇使用RTT驅動框架的API,也可以選擇RTT封裝好的API。
  • 驅動調試技巧點滴分享
    那就總結一下自己看的代碼的一些感悟和技巧。如何利用你看的這些代碼?如何體現在工作的調試中?作為驅動工程師,主要的工作就是移植各種驅動,接觸各種硬體。接觸最多的就是dts、中斷、gpio、sysfs、proc fs。
  • 「正點原子FPGA連載」第二十四章Linux設備樹(二)
    24.3.9常用節點本來這小節給大家講一些常用到的節點,例如中斷控制器、GPIO控制器以及在節點當中如何使用中斷、如何使用gpio等。當想了想還是放在後面我們用到的時候再給大家介紹。24.4驅動與設備節點的匹配這部分內容已經在前面跟大家講過了,具體請看35.3.4小節中的第一個小點compatible屬性介紹。
  • [ARM筆記]驅動對設備的識別過程及實例——NAND Flash
    驅動程序識別設備時,有以下兩種方法:本文引用地址:http://www.eepw.com.cn/article/201612/341049.htm  (1)驅動程序本身帶有設備信息,比如開始地址、中斷號等;加載驅動程序時,就可以根據這些信息來識別設備。
  • Linux設備樹的引入與體驗(Linux4.19內核版本)
    ; };設備和驅動如何進行通信呢*通過bus進行匹配 platform_match函數確定(dev,drv)若匹配則調用drv中的probe函數 struct bus_type platform_bus_type = { .name = &34;,
  • Linux regulator子系統分析之四 虛擬regulator device驅動實現
    一、預備知識針對本文章,讀者需要了解如下幾方面的內容:Regulator 子系統相關的接口;熟悉platform 驅動開發;熟悉iic驅動開發;熟悉device驅動子系統中屬性文件的創建等接口熟悉regmap相關的接口調用
  • I2C總線驅動程序
    >\n\r"); } switch (s3c2440_i2c_xfer_data.state) { case STATE_START : // 發出S和設備地址後,產生中斷 // // 發出START信號和發出設備地址 // s3c2440_i2c_start();
  • 「正點原子Linux連載」第六十二章Linux SPI驅動實驗
    、platform_driver基本一樣,當SPI設備和驅動匹配成功以後probe函數就會執行。函數的對於設備和驅動的匹配過程基本一樣。第105行,of_driver_match_device函數用於完成設備樹設備和驅動匹配。比較SPI設備節點的compatible屬性和of_device_id中的compatible屬性是否相等,如果相當的話就表示SPI設備和驅動匹配。
  • 「正點原子FPGA連載」第二十四章Linux設備樹(一)
    在舊版本(大概是3.x以前的版本)的linux內核當中,ARM架構的板級硬體設備信息被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx目錄下的文件當中,例如板子上的platform設備信息、設備I/O資源resource、板子上的i2c設備的描述信息信息i2c_board_info、板子上spi設備的描述信息spi_board_info以及各種硬體設備的platform_data
  • linux系統和驅動中按鍵驅動的編寫詳解
    設備樹修改完後就可以編譯設備樹文件,然後用fsbl,u-boot,設備樹來製作boot.bin了。放到SD卡,啟動linux系統。接下來進入關鍵環節,key驅動的編寫。 2. 按鍵驅動代碼剖析 對於一個剛剛入門的人來說,其實了解了驅動的基本框架就好了。
  • Linux內核網絡設備驅動
    每個 PCI 驅動註冊了一個 probe() 方法,內核會對每個設備依次調用其驅動的 probe 方法,一旦找到一個合適的驅動,就不會再為這個設備嘗試其他驅動。很多驅動都需要大量代碼來使得設備 ready,具體做的事情各有差異。
  • 在Linux設備樹(DTS)中指定中斷_在代碼中獲得中斷
    這些層級關係、中斷號(hwirq),都會在設備樹中有所體現。在設備樹中,中斷控制器節點中必須有一個屬性:interrupt-controller,表明它是「中斷控制器」。I2C設備節點,I2C總線驅動在處理設備樹裡的I2C子節點時,也會處理其中的中斷信息。
  • 大咖說|Linux ALSA 音頻設備驅動分析
    它的主要特點有:① 支持多種音效卡設備;② 模塊化的內核驅動程序;③ 支持 SMP 和多線程;④ 提供應用開發函數庫(alsa-lib)以簡化應用程式開發;⑤ 支持 OSS API,兼容 OSS 應用程式等。
  • 輕鬆掌握pinctrl子系統驅動開發——一個虛擬pinctrl dev驅動開發
    ,包含支持的引腳描述、支持的引腳復用接口的賦值、支持的引腳配置接口的賦值、支持的group操作接口以及dt2map接口的賦值等;調用pinctrl_register/devm_pinctrl_register完成pinctrl device的註冊定義該soc pin controller的group相關變量的添加(若使用自行定義的結構存儲就自行實現,也可調用pinctrl_generic_add_group
  • 「正點原子FPGA連載」第二十五章設備樹下的LED驅動實驗
    LED驅動實驗上一章我們詳細的講解了設備樹語法以及在驅動開發中常用的OF函數,本章我們就開始第一個基於設備樹的Linux驅動實驗。本章在第二十三章實驗的基礎上完成,只是將其驅動開發改為設備樹形式而已。25.1設備樹LED驅動原理在《第二十三章 新字符設備驅動實驗》中,我們直接在驅動文件newchrled.c中定義有關寄存器物理地址,然後使用io_remap函數進行內存映射,得到對應的虛擬地址,最後操作寄存器對應的虛擬地址完成對GPIO的初始化。
  • 宋寶華:Linux設備與驅動的手動解綁與手動綁定
    眾所周知,Linux靠設備與驅動之間的match,來完成設備與驅動的bind,從而觸發驅動的probe()成員函數被執行。這種自動匹配非常簡單,實施起來也非常容易。但是有時候,這種自動匹配並不一定是我們想要的。比如我們有時候就是希望XXX設備用YYY驅動,而不是用XXX驅動。
  • Linux pinctrl子系統分析之六 設備與pinctrl子系統的bind
    本章我們分析設備與pinctrl子系統的bind,在前面幾章我們介紹了soc pin 描述相關的數據結構與註冊接口、board pin 描述相關的數據結構與註冊接口,但是我們卻沒有看到是在何時由誰實現對設備相關的引腳進行引腳復用與引腳配置的,而這些就是本章的內容。