Linux I2C 子系統全解:結構、機制與工程實戰
前言
I2C(Inter-Integrated Circuit)作為嵌入式系統和各種電子產品中最常用的串行通信總線之一,在 Linux 內核中的地位極其重要。然而,Linux I2C 子系統的分層結構、對象模型、驅動編寫與平臺適配,常常讓初學者和有經驗的驅動工程師都感覺“玄而又玄”:
- i2c-core 到底是什么?它是結構體還是框架代碼?
- i2c_adapter/i2c_client/i2c_driver 這三者的真正職責和關系是什么?
- 設備樹、ACPI 如何描述和管理 I2C 設備?
- 用戶空間如何安全、優雅地操作 I2C 設備?
- 為什么有些 I2C 設備驅動放在 drivers/i2c/chips,有些則在各自子系統?
- 適配器和外設驅動之間的數據流和調用棧到底長什么樣?
本文將用“站在一線工程師視角”的方式,結合內核主線代碼,系統剖析 Linux I2C 子系統的設計精髓,厘清常見痛點,助你真正吃透 I2C 驅動開發的門道。
一、I2C 子系統的整體分層架構
1.1 為什么要分層?
在內核設計里,“分層”幾乎是所有復雜子系統必須采取的手段。一方面解耦硬件和上層協議,另一方面便于移植和擴展。I2C 子系統的分層非常經典,既和 PCI、USB 等總線有相通之處,也有其獨特的地方。
1.2 四層結構一覽
層級 | 主要職責 | 典型源碼文件 | 關鍵結構體(接口) |
---|---|---|---|
用戶空間接口層 | 向用戶空間提供 /dev/i2c-x 訪問接口 | drivers/i2c/i2c-dev.c | struct file_operations |
設備驅動層 | 各類 I2C 外設(如 EEPROM、Sensor)協議驅動 | drivers/misc/eeprom/at24.c drivers/i2c/chips/*.c | struct i2c_driver struct i2c_client |
適配器驅動層 | 適配各類 I2C 控制器(主控/硬件) | drivers/i2c/busses/i2c-xxx.c | struct i2c_adapter struct i2c_algorithm |
I2C 核心層 | 管理適配器、設備、驅動的注冊、匹配與調度 | drivers/i2c/i2c-core-base.c drivers/i2c/i2c-core-of.c | struct i2c_adapter struct i2c_client struct i2c_driver struct bus_type(i2c_bus_type) |
分層的本質是:每一層只關心本層的職責,通過清晰的接口與上下層交互,彼此低耦合、高內聚。
二、I2C 子系統核心對象和關系
2.1 三大核心結構體
-
struct i2c_adapter
代表 I2C 控制器(主機端),由適配器驅動(adapter driver)實現。例如 i.MX6/8、Designware、Synopsys 等硬件廠商的控制器驅動都會定義并注冊自己的 adapter。struct i2c_adapter {struct module *owner;unsigned int class;const struct i2c_algorithm *algo;void *algo_data;struct device dev;int nr; // 適配器編號// ... 省略其余成員 };
-
struct i2c_client
代表 I2C 總線上的從設備(外設)。每個外設在系統里注冊為一個 i2c_client。struct i2c_client {unsigned short addr;struct i2c_adapter *adapter;struct device dev;// ... 省略其余成員 };
-
struct i2c_driver
代表外設驅動程序,由驅動開發者實現。會指定可以支持哪些 i2c_client。struct i2c_driver {int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);const struct i2c_device_id *id_table;struct device_driver driver;// ... 省略其余成員 };
2.2 i2c-core 的角色
i2c-core 是整個 I2C 框架的“中樞神經”:
它管理著 adapter、client、driver 的注冊與注銷;在總線匹配到合適的 client-driver 對時,自動調用 probe/remove 等回調;它還定義和注冊了 i2c_bus_type
,將 I2C 適配器和設備納入內核統一的設備模型(/sys/bus/i2c)。
易混淆點:
- i2c-core 并不是結構體對象,而是一組實現適配器/設備/驅動管理與調度的代碼集合。
- i2c-core 定義的結構體(adapter、client、driver)都是面向外部的對象,i2c-core 本身則是幕后操盤手。
2.3 各對象的注冊和生命周期
- 適配器注冊:
i2c_add_adapter()
/ 注銷:i2c_del_adapter()
- 驅動注冊:
i2c_add_driver()
/ 注銷:i2c_del_driver()
- 設備注冊:通常由核心層自動創建設備(client),也可用
i2c_new_client_device()
手動注冊
典型適配器注冊片段(i2c-imx.c)
static int imx_i2c_probe(struct platform_device *pdev)
{// ... 硬件初始化、資源分配struct i2c_adapter *adap = &i2c_imx->adapter;adap->owner = THIS_MODULE;adap->algo = &imx_i2c_algo;// ...i2c_add_adapter(adap); // 注冊到 i2c-core// ...
}
典型設備驅動注冊片段(at24.c)
static struct i2c_driver at24_driver = {.driver = {.name = "at24",.of_match_table = at24_of_match,},.probe_new = at24_probe,.remove = at24_remove,.id_table = at24_ids,
};static int __init at24_init(void)
{return i2c_add_driver(&at24_driver); // 注冊到 i2c-core
}
module_init(at24_init);
三、I2C 設備樹/ACPI與自動識別
3.1 設備樹下的 I2C 設備描述
現代 SoC 平臺上,I2C 設備和控制器大多通過 device tree 描述。例如:
&i2c1 {status = "okay";clock-frequency = <100000>;sensor@48 {compatible = "ti,tmp102";reg = <0x48>;};
};
&i2c1
:引用已聲明的 i2c1 適配器sensor@48
:代表掛在 i2c1 上、地址為 0x48 的設備compatible
:決定使用哪個驅動reg
:I2C 地址
i2c-core 會在啟動時掃描各 bus 下的節點,根據 compatible 字符串自動和 i2c_driver 的 of_match_table 匹配,自動創建設備并調用 probe。
3.2 ACPI 下的 I2C 設備識別
X86/服務器平臺常用 ACPI 描述 I2C 設備,匹配原理與 DT 類似,匹配 driver 的 acpi_match_table。
四、數據流與調用流程解析
4.1 從用戶空間到硬件的數據流
- 用戶空間調用 open/read/write/ioctl 操作
/dev/i2c-x
- 內核 i2c-dev.c 通過 file_operations 調用 i2c-core 的消息傳輸接口
- i2c-core 調用 i2c_adapter 提供的 master_xfer/algo 方法
- 適配器驅動操作硬件控制器,完成 I2C 物理通信
真實代碼流程片段(i2c-dev.c):
static ssize_t i2cdev_write(struct file *file, const char __user *buf,size_t count, loff_t *offset)
{struct i2c_client *client = file->private_data;struct i2c_adapter *adap = client->adapter;// ...ret = i2c_transfer(adap, msgs, num);// ...
}
i2c_transfer 又會調用適配器驅動里定義的 algorithm(如 master_xfer):
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{return adap->algo->master_xfer(adap, msgs, num);
}
五、工程痛點與常見疑問
5.1 i2c-core 到底是結構體還是代碼?
答:i2c-core 不是結構體,是整個 I2C 子系統的核心實現代碼模塊,提供對象注冊/調度/統一接口。它通過 struct bus_type 將 I2C 融入內核設備模型。
5.2 適配器驅動和設備驅動的關系到底怎么理解?
- 適配器驅動負責讓“硬件能動起來”,對上暴露 i2c_adapter 對象,提供收發能力。
- 設備驅動(如 at24、tmp102)不直接操作硬件,而是通過 i2c-core 提供的抽象接口,調用 i2c_transfer 等 API 間接與硬件通信。
- 兩者通過 i2c-core 橋接,對彼此無感知、相互獨立,極大增強了代碼復用和平臺兼容性。
5.3 “I2C 總線”在內核里到底有沒有獨立的 struct?
I2C 總線沒有像 PCI/USB 那樣單獨的 struct i2c_bus。Linux 用 adapter(控制器)+ client(設備)+ bus_type(總線類型)三者結合,實現了虛擬總線模型。所有掛在同一個 adapter 下的設備,即被認為掛在同一條邏輯 I2C 總線。
5.4 為什么 I2C 設備驅動分散在 chips、misc、media 等目錄?
這是歷史和分層帶來的必然產物。芯片類通用驅動(如 EEPROM、RTC、溫度傳感器)有的放在 i2c/chips,有的歸屬于自己子系統(如攝像頭 sensor、音頻 codec 等),但它們的入口都是統一的 i2c_driver/i2c_client,只是組織目錄不同。
5.5 設備樹下 I2C 設備無法自動 probe 的常見坑
- compatible 屬性寫錯或驅動里未配置 of_match_table
- i2c_adapter 未正常注冊(如 status = “disabled”)
- I2C 地址(reg)沖突或寫錯
- 驅動的 id_table 未包含正確的名稱
六、典型實戰代碼分析:at24 EEPROM 驅動
以主線 drivers/misc/eeprom/at24.c
為例,展示 i2c_driver 的專業實現范式。
核心結構注冊:
static struct i2c_driver at24_driver = {.driver = {.name = "at24",.of_match_table = at24_of_match,},.probe_new = at24_probe,.remove = at24_remove,.id_table = at24_ids,
};module_i2c_driver(at24_driver);
probe 實現要點:
- 獲取設備樹/ACPI/ID Table 數據,解析頁大小、容量等屬性
- 初始化 nvmem 子系統,導出用戶空間訪問接口
- 管理電源、runtime PM、regulator
- 多地址芯片通過 devm_i2c_new_dummy_device 注冊所有地址
與 i2c-core 交互流程
- probe 時,i2c-core 根據設備樹/of_match 匹配,自動調用 at24_probe
- 數據讀寫最終下沉到 i2c_transfer,自動適配底層控制器
七、調試與開發建議
7.1 如何排查 I2C 驅動無法 probe?
- 檢查 dmesg 是否有 adapter 注冊日志
- 檢查設備樹 compatible 是否和驅動匹配
- 用
i2cdetect
等工具確認設備地址能掃描到 - 用
i2cdump
驗證數據可讀寫 - 查看 /sys/bus/i2c/devices/ 是否有設備節點
7.2 推薦閱讀代碼入口
drivers/i2c/i2c-core-base.c
:框架主干、對象注冊、調度實現drivers/i2c/busses/
:主流適配器驅動實現- 具體外設驅動(如 at24.c、tmp102.c、lm75.c)
八、架構精髓與面試高頻考點
8.1 核心理念
- 統一分層、模塊解耦:適配器與設備驅動完全分離
- 設備模型集成:總線類型、自動綁定、生命周期管理
- 跨平臺支持:一套驅動代碼,適配 N 多硬件平臺
- 面向對象抽象:每類對象自管理,只暴露接口而不泄露實現細節
8.2 高頻面試問題
- 解釋 i2c_adapter、i2c_client、i2c_driver 的職責和關系
- 描述 i2c-core 如何實現驅動和設備的自動綁定
- 如何用設備樹描述 I2C 設備?驅動如何匹配?
- 用戶空間如何訪問 I2C 設備?其背后工作原理是什么?
- 如何調試 I2C 驅動 probe 不上的問題?
九、總結與延伸閱讀
Linux I2C 子系統雖然代碼量不大,但蘊含了大量內核設計哲學和工程經驗。理解其分層架構和對象模型,不僅能提升驅動開發能力,還能加深對 Linux 設備模型、總線機制的認知。希望本文的梳理和實例,能幫你建立起系統性思維框架,寫出健壯、可維護的 I2C 驅動。
附錄:常用內核 API/結構體速查
i2c_add_adapter()
/i2c_del_adapter()
i2c_add_driver()
/i2c_del_driver()
i2c_new_client_device()
/i2c_unregister_device()
i2c_transfer()
/i2c_smbus_xfer()
struct i2c_adapter
struct i2c_algorithm
struct i2c_client
struct i2c_driver
struct bus_type