linux設備驅動程序-i2c(1):i2c總線的添加與實現
(基於4.14內核版本)
在上一章節linux設備驅動程序-i2c(0)-i2c設備驅動源碼實現中,我們演示了i2c設備驅動程序的源碼實現,從這一章節開始,我們來剖析i2c設備驅動程序框架的實現原理。
前情回顧
在這之前,建議各位先閱讀博主之前的兩篇博客以建立基本linux內核串行通信框架的概念:
linux設備驅動程序--串行通信驅動框架分析
linux設備驅動程序--bus
i2c總線的初始化
分析i2c框架自然是從i2c總線的初始化開始,一切內核中i2c的相關操作都將建立在i2c總線的基礎上。
在實際驅動開發過程中,i2c總線也是集成在系統中的,驅動開發者不需要關心i2c總線的初始化,只需要將相應的i2c_client和i2c_driver掛載在總線上進行匹配即可。
將總線注冊到系統
那么,i2c總線在系統中是如何初始化得來的呢?
答案就在文件i2c-core-base.c中,它的過程是這樣的:
static int __init i2c_init(void)
{
...
bus_register(&i2c_bus_type);
...
}
postcore_initcall(i2c_init);
在i2c_init函數中,使用bus_register()將i2c總線注冊到系統中,那么這個i2c_init()函數是在哪里被調用的呢?
在內核啟動的過程中,進入C語言環境的第一個入口函數是start_kernel(),但是i2c_init()並非由start_kernel()間接調用,而是借助於linux內核中的initcall機制被調用,簡而言之,就是將這個函數地址在編譯階段放入特定的段中,在內核初始化時由某個啟動函數將這些段中的函數一一取出並執行。
i2c總線通過postcore_initcall()將init函數注冊到系統中,postcore_initcall的詳解可以參考另一篇博客:linux init機制。
總線對應的參數
在上一節中的bus_register()注冊函數中,從函數名可以看出,將i2c_bus_type注冊到bus系統中,我們再來看看i2c_bus_type是何方神聖:
struct bus_type {
...
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);
struct subsys_private *p;
...
};
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
當這個模塊被加載進系統時,就會執行i2c_init函數來進行初始化。
struct bus_type結構體描述了linux中的各種bus,比如spi bus,i2c bus,platform bus等等,可以看到,在i2c_bus_type中,定義了match(),probe(),remove()和shutdown()函數,match()函數就是當有新的i2c_client或者i2c_driver添加進來時,試圖尋找與新設備匹配的項,返回匹配的結果。
remove()和shutdown(),顧名思義,這兩個函數是管理的驅動移除行為的。
而對於probe函數,在之前的章節中提到:當相應的device和driver經由總線匹配上時,會調用driver部分提供的probe函數。
那么:
- 總線的probe函數和具體驅動driver部分的probe函數是怎么一個關系呢?
- match函數和probe函數到底是怎么被調用的,以及執行一些什么行為呢?
帶着這兩個疑問,我們接着往下看。
match()和probe()
在這里我們不免要回顧一下前面章節所說的,作為一個驅動開發者,如果我們要開發某些基於i2c的設備驅動,需要實現的框架流程是怎樣的:
- 填充一個i2c_driver結構體,如果以設備樹方式匹配,需要填充of_match_table部分,如果是其他總線方式匹配,需要填充.driver.name或者.id_table屬性,然后提供一個.probe函數以及一個.remove函數,probe中對設備進行操作,remove負責回收資源。 將i2c_driver注冊到i2c總線中。
- 如果是設備樹形式,提供相應的設備樹節點。 如果是總線匹配形式,提供一個i2c_client部分注冊到i2c總線中。
- 當雙方注冊到i2c總線時,device(i2c_client)和driver部分匹配,調用driver提供的probe函數。
linux下的標准總線方式的驅動框架,但是問題是,為什么device和driver都注冊進去之后,就會調用driver部分提供的probe函數呢?
走進源代碼
為了解決這些問題,最好的辦法就是看源代碼,假設我們是以設備樹的方式進行匹配,device(即i2c_client)部分已經被注冊到系統中,此時我們向系統中注冊(insmod由driver部分程序編譯的模塊)相應的driver部分,接下來我們跟蹤driver部分注冊到i2c總線的流程,看看它是怎么實現的:
...
static struct i2c_driver drv = {
...
.probe = drv_probe,
.remove = drv_remove,
.id_table = drv_id_table,
};
static __init xxx_init(){
i2c_add_driver(&drv);
}
...
跟蹤i2c_add_driver:
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
...
driver->driver.bus = &i2c_bus_type; //設置當前driver的bus類為i2c_bus_type
driver_register(&driver->driver);
...
}
int driver_register(struct device_driver *drv)
{
...
bus_add_driver(drv);
...
}
經過一系列的代碼跟蹤,找到了bus_add_driver(),根據名稱可以知道,這個函數就是將當前i2c_driver添加到i2c_bus_type(即i2c總線)中。
接着往下看:
int bus_add_driver(struct device_driver *drv)
{
...
struct bus_type *bus;
struct driver_private *priv;
bus = bus_get(drv->bus); //獲取的bus為上一節中介紹的i2c_bus_type
priv->driver = drv;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
...
...
driver_attach(drv);
...
}
從第一部分可以看到,將當前drv鏈入到bus->p->klist_drivers鏈表中,那么可以猜到,在注冊device部分的時候,就會將device鏈入到bus->p->klist_devices中。
然后,再看driver_attach(drv):
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{
...
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
...
}
static int __driver_attach(struct device *dev, void *data)
{
...
int ret;
struct device_driver *drv = data;
ret = driver_match_device(drv, dev);
check_ret();
driver_probe_device(drv, dev);
...
}
driver_attach調用bus_for_each_dev,傳入當前驅動bus(即i2c_bus_type),當前驅動drv,以及一個函數__driver_attach。
bus_for_each_dev對每個device部分調用__driver_attach。
在__driver_attach中,對每個drv和dev調用driver_match_device(),並根據函數返回值決定是否繼續執行調用driver_probe_device()。
從函數名來看,這兩個函數大概就是我們在前文中提到的match和probe函數了,我們不妨再跟蹤看看,先看看driver_match_device()函數:
static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
果然不出所料,這個函數十分簡單,如果當前驅動的所屬的bus有相應的match函數,就調用match函數,否則返回1.
當前driver所屬的總線為i2c_bus_type,根據上文i2c總線的初始化部分可以知道,i2c總線在初始化時提供了相應的match函數,所以,總線的match函數被調用,並返回匹配的結果。
接下來我們再看看driver_probe_device()函數:
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
...
really_probe(dev, drv);
...
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
...
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
}
else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
...
}
在driver_probe_device中又調用了really_probe,在really_probe()中,先判斷當前bus總線中是否注冊probe()函數如果有注冊,就調用總線probe函數,否則,就調用當前drv注冊的probe()函數。
到這里,我們解決了上一節中的一個疑問:總線和driver部分都有probe函數時,程序是怎么處理的?
答案已經顯而易見了:優先調用總線probe函數。
而且我們理清了總線的match()函數和probe()函數是如何被調用的。
那么,上一節中還有一個疑問沒有解決:總線的match和probe函數執行了一些什么行為?
總線probe函數
我們直接根據i2c總線初始化時設置的probe函數來查看:
struct bus_type i2c_bus_type = {
.match = i2c_device_match,
.probe = i2c_device_probe,
};
static int i2c_device_probe(struct device *dev)
{
...
struct i2c_driver *driver;
driver = to_i2c_driver(dev->driver);
if (driver->probe_new)
status = driver->probe_new(client);
else if (driver->probe)
status = driver->probe(client,
i2c_match_id(driver->id_table, client));
else
status = -EINVAL;
...
}
對於總線probe函數,獲取匹配成功的device,再由device獲取driver,優先調用driver->probe_new,因為driver中沒有設置,直接調用driver的probe函數。
總線probe和driver的probe函數的關系就是:當match返回成功時,優先調用總線probe,總線probe完成一系列的初始化,再調用driver的probe函數,如果總線probe函數不存在,就直接調用driver的probe函數,所以當我們在系統中添加了相應的device和driver部分之后,driver的probe函數就會被調用。
總線match函數
對於總線match函數,我們直接查看在i2c總線初始化時的函數定義:
struct bus_type i2c_bus_type = {
.match = i2c_device_match,
.probe = i2c_device_probe,
};
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
i2c_device_match就是i2c_driver與i2c_device匹配的部分,在i2c_device_match函數中,可以看到,match函數並不只是提供一種匹配方式:
- i2c_of_match_device,看到of我們就應該馬上意識到這是設備樹的匹配方式。
- acpi_driver_match_device則是acpi的匹配方式,acpi的全稱為the Advanced Configuration & Power Interface,高級設置與電源管理。
- i2c_match_id(),通過注冊i2c_driver時提供的id_table進行匹配,這是設備樹機制產生之前的主要配對方式。
接下來再深入函數內部,查看匹配的細節部分:
設備樹匹配方式
const struct of_device_id *i2c_of_match_device(const struct of_device_id *matches,
struct i2c_client *client)
{
const struct of_device_id *match;
match = of_match_device(matches, &client->dev);
if (match)
return match;
return i2c_of_match_device_sysfs(matches, client);
}
在i2c_of_match_device中,調用了of_match_device()和i2c_of_match_device_sysfs()兩個函數,這兩個函數代表了兩種匹配方式,先來看看of_match_device:
const struct of_device_id *of_match_device(const struct of_device_id *matches,const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)
{
const struct of_device_id *match;
match = __of_match_node(matches, node);
return match;
}
static const struct of_device_id *__of_match_node(const struct of_device_id *matches,const struct device_node *node)
{
...
const struct of_device_id *best_match = NULL;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
__of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
...
}
...
}
of_match_device調用of_match_node。
of_match_node調用__of_match_node函數。
如果你對設備樹有一定的了解,就知道系統在初始化時會將所有設備樹子節點轉換成由struct device_node描述的節點。
了解linux設備樹可以參考我的另一系列博客:linux設備驅動程序-設備樹(0)-dtb格式
在被調用的__of_match_node函數中,對device_node中的compatible屬性和driver部分的of_match_table中的compatible屬性進行匹配,由於compatible屬性可以設置多個,所以程序中會對其進行逐一匹配。
我們再回頭來看設備樹匹配方式中的i2c_of_match_device_sysfs()匹配方式:
static const struct of_device_id* i2c_of_match_device_sysfs(const struct of_device_id *matches,struct i2c_client *client)
{
const char *name;
for (; matches->compatible[0]; matches++) {
if (sysfs_streq(client->name, matches->compatible))
return matches;
name = strchr(matches->compatible, ',');
if (!name)
name = matches->compatible;
else
name++;
if (sysfs_streq(client->name, name))
return matches;
}
return NULL;
}
由i2c_of_match_device_sysfs()的實現可以看出:當設備樹中不存在設備節點時,driver部分的of_match_table中的compatible屬性試圖去匹配i2c_client(device部分)的.driver.name屬性.
因為設備樹的默認規則,compatible屬性一般形式為"vender_id,product_id",當compatible全字符串匹配不成功時,取product_id部分再進行匹配,這一部分主要是兼容之前的不支持設備樹的版本。
acpi匹配方式較為少用且較為復雜,這里暫且不做詳細討論
id_table匹配方式
同樣的,我們查看i2c_match_id(driver->id_table, client)源代碼:
const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
這種匹配方式一目了然,就是對id_table(driver部分)中的.name屬性和i2c_client(device部分)的.name屬性進行匹配。那么i2c_client的.name是怎么來的呢?
在非設備樹的匹配方式中,i2c_client的.name屬性由驅動開發者指定,而在設備樹中,i2c_client由系統對設備樹進行解析而來,i2c_client的name屬性為設備樹compatible屬性"vender_id,product_id"中的"product_id",所以,在進行匹配時,默認情況下並不會嚴格地要求
of_match_table中的compatible屬性和設備樹中compatible屬性完全匹配,driver中.drv.name屬性和.id.name屬性也可以與設備樹中的"product_id"進行匹配。
好了,關於linux i2c總線的初始化以及運行機制的討論就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言
原創博客,轉載請注明出處!
祝各位早日實現項目叢中過,bug不沾身.