一、開場白:
大家好,今天文章的主題是對Linux kernel I2C子系統驅動的闡述。軟件平臺介紹:Linux版本3.0.36,Android版本4.2.2。如有錯誤的地方還請大家指出,互相學習。
二、驅動背景:
上篇文章《Linux基本設備驅動闡述》中已經介紹了一些Linux I2C驅動的基本知識,從中我們可以知道Linux I2C驅動的編寫由兩部分組成:I2C總線驅動編寫、I2C設備驅動編寫。I2C總線驅動(i2c-adapter)主要負責驅動具體CPU I2C通信模塊,完成基本的I2C數據通信功能;I2C設備驅動(i2c-driver)主要負責驅動具體的外設硬件,讓外設進入正常的工作狀態。完成兩部分驅動的編寫,最基本的內容就是完善Linux
kernel提供出來的相關接口,而調用和管理這些接口就是I2C核心框架(i2c-core)。綜上可知Linux I2C子系統由三部分組成:I2C核心框架(i2c-core)、I2C總線驅動(i2c-adapter)、I2C設備驅動(i2c-driver)。我們可以通過下圖來簡單表示下Linux I2C子系統的基本內容:I2C設備驅動(i2c-driver)和I2C總線驅動(i2c-adapter)通過填充和完善I2C核心框架提供的接口,來保證Linux I2C子系統的正常工作通信。
從上圖我們也可以知道要分析Linux I2C子系統的工作原理與流程,就需要從I2C核心框架對外提供的接口開始分析(i2c-driver with i2c-core & i2c-adapter with i2c-core)。而通過上篇文章《Linux基本設備驅動闡述》的介紹,相信大家都比較熟悉I2C設備驅動的開發。因此本篇通過使用上篇文章所編寫的TPS65185設備驅動,來完成I2C設備驅動與I2C核心框架之間的接口分析,進而完成I2C核心框架的分析和I2C總線驅動的編寫。(說明1:下面的分析中I2C核心框架用i2c-core代替、I2C設備驅動用i2c-driver代替、I2C總線驅動用i2c-adapter代替;說明2:上篇文章中I2C設備註冊必須得放在MECHINE_START中的.init_machine成員函數中,具體原因下面篇幅進行解釋)。
三、子系統分析:
TPS65185設備驅動與i2c-core的交互接口有三部分:I2C設備註冊接口i2c_register_board_info、I2C設備驅動註冊接口i2c_add_driver、I2C設備通信接口i2c_transfer,因此我們按照接口的調用先後來完成對這三個接口的分析。
3.1、首先我們從I2C設備註冊接口(i2c_register_board_info)開始分析,TPS65185設備驅動中與此接口相關的代碼如下所示:
/* 設備信息 */
static struct i2c_board_info __initdata i2c0_info[] = {
{
.type = "tps65185",
.addr = 0x1d,
.flags = 0,
.platform_data = NULL,
},
};
/* 板級初始化函數 */
void __init init_machine(void) {
.........
i2c_register_board_info(0, i2c0_info, ARRAY_SIZE(i2c0_info));
.........
return;
}
由上可知I2C設備的註冊,即是完成2c_board_info數據結構的註冊,具體的處理過程我們需要通過分析i2c_register_board_info函數來完成。此函數的代碼簡化後如下所示:// file : kernel/drivers/i2c/i2c-boardinfo.c
/* These symbols are exported ONLY FOR the i2c core.
* No other users will be supported.
*/
//......
LIST_HEAD(__i2c_board_list);
EXPORT_SYMBOL_GPL(__i2c_board_list);
int __i2c_first_dynamic_bus_num;
EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num);
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
.........
/* dynamic bus numbers will be assigned after the last static one
* 當註冊的I2C設備總線號大於Linux I2C子系統可用總線號時,
* 將Linux I2C可用總線號涵蓋註冊的I2C設備總線號
*/
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
// I2C設備的數據結構,其由兩個成員 busnum、board_info
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
// 將初始化後的I2C設備數據結構添加至__i2c_board_list鏈表中
list_add_tail(&devinfo->list, &__i2c_board_list);
}
.........
return status;
}
i2c_register_board_info函數比較簡單,其主要由三個動作組成:更新I2C可用總線號(__i2c_first_dynamic_bus_num)、I2C設備結構(i2c_devinfo)初始化、更新I2C設備列表(__i2c_board_list)。__i2c_first_dynamic_bus_num和__i2c_board_list兩個數據結的定義即初始化是由i2c_boardinfo完成的,而其使用都是i2c-core進行的,因此我們進入i2c-core來進行相關分析。i2c-core對這兩個數據結構的使用如下所示:
// file: kernel/drivers/i2c/i2c-core.c
static int i2c_register_adapter(struct i2c_adapter *adap)
{
.........
/* create pre-declared device nodes
* 設備的預先申明是由i2c_register_board_info完成,由於I2C設備的註冊早於i2c-adapter的註冊,
* 因此如果此時註冊的i2c-adapter->nr總線號在I2C可用總線號中,則會去掃描設備列表創建相關設備節點
*/
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
/* Notify drivers <完成i2c-adapter與i2c-driver的綁定> */
/* 這種綁定方式在早期內核(2.6之前)被使用,稱爲legacy方式.
* 2.6+內核引入new-style方式代替legacy方式,由於new-style方式更加符合標準設備驅動模塊框架,
* 此部分對new-style方式沒有影響.
*/
mutex_lock(&core_lock);
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
mutex_unlock(&core_lock);
.........
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
// 全局變量__i2c_board_list的訪問需要利用互斥鎖進行保護
down_read(&__i2c_board_lock);
/* 循環遍歷__i2c_board_list設備列表,如果devinfo總線號與adapter總線號相同
* 則調用i2c_new_device完成I2C設備創建註冊,i2c_new_device大致動作如下:
* i2c_client的創建及初始化、將此設備添加至Linux設備樹device_register
*/
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
i2c-core對__i2c_first_dynamic_bus_num和__i2c_board_list兩個數據成員的使用,由上述兩個函數構成:i2c_register_adapter、i2c_scan_static_board_list。通過函數的名稱我們就能大概猜出其實現的功能:i2c_register_adapter函數是i2c-core提供給i2c-adapter進行註冊的接口;i2c_scan_static_board_list是i2c-core內部用於掃描設備列表用的。i2c-adapter調用i2c_register_adapter進行註冊的時候,會通過將adapter本身的總線號nr和I2C可用總線號__i2c_first_dynamic_bus_num進行對比,如果adapter->nr存在於可用總線號中(adapter->nr
< __i2c_first_dynamic_bus_num),那麼則會調用i2c_scan_static_board_list來掃描I2C設備列表。i2c_scan_static_board_list函數中通過循環遍歷__i2c_board_list設備列表,如果__i2c_board_list設備列表中存在設備是掛載在本次註冊的adaptor上時,則會調用i2c_new_device來處理。i2c_new_device的代碼如下所示://file : kernel/drivers/i2c/i2c-core.c
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
// 每個I2C設備都對應一個唯一i2c_client,如TPS65185外設芯片就會對應一個i2c_client
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
/* 每個client都擁有一個adapter成員,其代表着I2C設備的通信方法.
* i2c_transfer通信函數就是通過間接調用adapter->master_xfer通信函數實現數據通信的.
*/
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr; // addr即代表着外設器件的唯一器件地址
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
.........
/* 每個client都擁有一個dev成員,dev初始化完成後,會將其註冊至Linux設備樹上
* (device_register),後面i2c_driver註冊的時候(driver_register),
* 會通過通過bus_for_each_dev函數去搜索Linux設備樹,每次從Linux設備樹上搜索
* 的結果就是之前註冊的dev成員,利用container_of即可獲取出i2c_client成員.
*/
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type; // 標記此設備是掛載的總線i2c_bus上
client->dev.type = &i2c_client_type; // 標記此設備類型是i2c_client_type
client->dev.of_node = info->of_node;
.........
status = device_register(&client->dev); // 將設備添加到Linux設備樹中
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
}
i2c_new_device函數的功能可以分解成三部分:申請創建i2c_client;爲i2c_client記錄下與其對應的adapter;爲i2c_client記錄相關設備信息(eg.
addr);初始化i2c_client->device成員,並將其掛載至Linux設備樹上;上述動作完成後,i2c_driver在註冊的時候就能搜索出此設備,從而進行device和driver的匹配。
從上面的討論中,我們可以總結出以下幾點:1、init_machine函數中會調用i2c_register_board_info函數;2、i2c_register_board_info函數會根據I2C設備的相關信息來初始化兩個非常重要的數據成員,I2C系統可用總線號__i2c_first_dynamic_bus_num和I2C設備列表__i2c_board_list;3、上述兩個數據成員被i2c_register_adapter函數用於匹配I2C設備和I2C總線,當匹配成功後創建並初始化i2c_client,並將配對成功的設備添加至Linux設備樹中。4、i2c_register_adapter接口是i2c-core提供給i2c-adapter註冊所用接口,此函數會在i2c-adapter模塊加載的時候被調用;到此爲止我們對i2c_register_board_info的設備註冊的接口分析到此結束,下面開始對I2C驅動註冊的接口展開分析。
3.2、通過分析TPS65185設備驅動的源碼可知,I2C驅動註冊的結構爲i2c_add_driver。而i2c_add_driver函數定義在kernel/include/linux/i2.h文件中,相關源碼定義如下:
// file : kernel/drivers/test/tps65185.c
static struct i2c_driver tps65185_driver = {
.probe = tps65185_probe,
.remove = tps65185_remove,
.suspend = tps65185_suspend,
.resume = tps65185_resume,
.id_table = tps65185_id_table,
.driver = {
.name = "tps65185 driver",
.owner= THIS_MODULE,
},
};
/* 驅動模塊加載函數 */
static int __init tps65185_init(void) {
i2c_add_driver(&tps65185_driver);
return 0;
}
// file : kernel/include/linux/i2c.h
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);
}
從上面源碼可以看出,i2c_add_driver函數是通過調用i2c_register_driver接口來完成I2C驅動的註冊。i2c_register_driver函數則是由i2c-core提供實現,此接口函數的代碼量非常小,僅僅是通過利用driver_register接口來完成驅動的註冊。其源碼如下所示:// file: kernel/drivers/i2c/i2c-core.c
/*
* An i2c_driver is used with one or more i2c_client (device) nodes to access
* i2c slave chips, on a bus instance associated with some i2c_adapter.
*/
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
.........
/* Walk the adapters that are already present */
/* 此函數用於綁定driver和client,
* I2C設備驅動爲legacy形式時纔有效,new-style形式是無效
*/
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
對於TPS65185設備驅動而言,其傳給i2c_register_driver的i2c_driver參數爲tps65185_driver。上篇文章《Linux基本設備驅動闡述》中,我們有對其probe、remove、suspend、resume、id_table五個成員進行分析,唯獨沒有進行詳細闡述的就是tps65185->driver成員。struct
device_driver和struct device作爲Linux設備驅動模型最重要的兩個數據成員,對這兩個數據成員最重要的接口分別爲driver_register和device_register。struct device_driver和struct device這兩個成員幫助了內核統一設備驅動操作接口,這樣一來Linux基本的設備驅動框架就不需要直接與各個子系統定義的數據結構進行交互。在i2c_register_driver接口中,我們初始化了tps65185_driver->driver的bus成員爲i2c_bus_type,這樣一來此驅動則屬於I2C總線上的設備驅動。之後調用bus_for_each_dev查找設備的時候,則會僅僅搜索I2C總線上註冊的設備。tps65185_driver->driver成員初始化完成之後則調用driver_register函數進行驅動的註冊動作。從上面代碼的註釋中我們可以瞭解到,driver_register在設備驅動匹配成功後會去調用設備驅動的probe函數。爲什麼會調用到設備驅動的probe呢,我相信大家都會對此感興趣的。下面我們就對driver_register進行一個簡單的分析,本次分析主要目的就是查出driver_register是怎麼樣調用到設備驅動的probe成員的。
分析設備驅動的probe函數函數調用過程,最直接的方式就是在設備驅動程序的probe函數中添加dump_stack()函數。dump_stack()函數會查找相關堆棧信息,最後以log的形式打印出內核調用的函數流程。現在我們就在tps65185_probe函數中添加dump_stack(),重新啓動機器通過串口輸出的相關log如下所示:
<4> [<c043dac4>] (unwind_backtrace+0x0/0xe0) from [<c05e47c4>] (tps65185_probe+0xc/0xb8)
<4> [<c05e47c4>] (tps65185_probe+0xc/0xb8) from [<c06a8b94>] (i2c_device_probe+0xa8/0xd4)
<4> [<c06a8b94>] (i2c_device_probe+0xa8/0xd4) from [<c061b9e8>] (driver_probe_device+0xc8/0x1a8)
<4> [<c061b9e8>] (driver_probe_device+0xc8/0x1a8) from [<c061bb28>] (__driver_attach+0x60/0x84)
<4> [<c061bb28>] (__driver_attach+0x60/0x84) from [<c061ac38>] (bus_for_each_dev+0x4c/0x84)
<4> [<c061ac38>] (bus_for_each_dev+0x4c/0x84) from [<c061b304>] (bus_add_driver+0xbc/0x248)
<4> [<c061b304>] (bus_add_driver+0xbc/0x248) from [<c061bffc>] (driver_register+0xa8/0x138)
<4> [<c061bffc>] (driver_register+0xa8/0x138) from [<c06aadb4>] (i2c_register_driver+0x40/0xac)
<4> [<c06aadb4>] (i2c_register_driver+0x40/0xac) from [<c0418fec>] (tps65185_init+0x10/0x1c)
<4> [<c0418fec>] (tps65185_init+0x10/0x1c) from [<c0433634>] (do_one_initcall+0x90/0x160)
<4> [<c0433634>] (do_one_initcall+0x90/0x160) from [<c0408920>] (kernel_init+0x94/0x13c)
<4> [<c0408920>] (kernel_init+0x94/0x13c) from [<c0439238>] (kernel_thread_exit+0x0/0x8)
從上面的內核log就很明顯的看出,內核調用到TPS65185驅動probe函數的流程了。在分析這段log之前,我先簡單介紹下內核的相關啓動流程。我們知道最初內核啓動的過程中,首先會運行一段彙編代碼head.S,然後就會調用到第一個C函數start_kernel()。start_kernel()完成部分初始化之後,則會啓動內核第一個線程kernel_init(),熟悉了這個之後,我們就可以開始分析上面的log了。首先我們來分析kernel_init()這個線程的具體代碼:// file: kernel/init/main.c
static int __init kernel_init(void * unused)
{
.........
do_basic_setup();
.........
}
static void __init do_basic_setup(void)
{
cpuset_init_smp();
usermodehelper_init();
init_tmpfs();
driver_init();
init_irq_proc();
do_ctors();
do_initcalls();
}
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
}
從上面代碼的註釋中,我們可以瞭解到kernel_init調用do_basic_setup函數來完成硬件平臺相關的初始化動作。do_basic_setup調用了driver_init函數和do_initcalls函數,driver_init函數完成了驅動框架的初始化,do_initcalls完成平臺硬件的初始化。爲了不偏離文章主線太遠,driver_init函數我們這裏不做分析,有興趣的讀者可以自行分析。do_initcalls完成具體硬件的初始化,主要是通過調用位於init代碼段的所有函數來完成的,簡單的說就是按照指定的優先級來調用被__init字段修飾的函數。TPS65185驅動加載函數就是位於__init代碼段的,它的優先級爲subsys_initcall_sync,因此do_initcalls函數是會間接調用到tps65185_init驅動模塊加載函數的。結合上面對i2c_register_driver的初步分析,我們已經理解清楚了dump_stack打印出的以下log部分的調用流程:<4> [<c061bffc>] (driver_register+0xa8/0x138) from [<c06aadb4>] (i2c_register_driver+0x40/0xac)
<4> [<c06aadb4>] (i2c_register_driver+0x40/0xac) from [<c0418fec>] (tps65185_init+0x10/0x1c)
<4> [<c0418fec>] (tps65185_init+0x10/0x1c) from [<c0433634>] (do_one_initcall+0x90/0x160)
<4> [<c0433634>] (do_one_initcall+0x90/0x160) from [<c0408920>] (kernel_init+0x94/0x13c)
<4> [<c0408920>] (kernel_init+0x94/0x13c) from [<c0439238>] (kernel_thread_exit+0x0/0x8)
driver_register部分涉及相關的知識點非常多,爲了不偏離主線,下面的分析我們則需要以dump_stack的log爲參照來進行分析。driver_register部分簡化後的代碼如下所示:// file: kernel/drivers/base/driver.c
int driver_register(struct device_driver *drv)
{
.........
// 判斷驅動是否已經註冊過
other = driver_find(drv->name, drv->bus);
.........
// 驅動具體註冊
ret = bus_add_driver(drv);
.........
}
// file: kernel/drivers/base/bus.c
int bus_add_driver(struct device_driver *drv)
{
.........
/* drivers_autoprob成員默認值爲 1,此條件一般成立。
* 調用driver_attach完成驅動與設備的匹配。
*/
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
{
printk(KERN_ERR "driver_attach failed\n");
goto out_unregister;
}
}
// 設備與驅動匹配成功,完成設備文件節點的創建等
.........
}
// file: kernel/drivers/base/dd.c
int driver_attach(struct device_driver *drv)
{
/* 循環遍歷對應總線的設備樹,獲取出device後調用
* __driver_attch測試設備與驅動是否匹配
*/
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
// 調用drv->bus->mach函數測試驅動與設備是否匹配
if (!driver_match_device(drv, dev))
return 0;
.........
/* 調用drv->bus->probe函數,間接完成設備驅動的掛載
* 也就是說設備驅動的每次掛載,都會引起相應總線的probe動作
*/
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
........
return 0;
}
device_register函數通過driver_find對傳入的設備驅動driver進行初步的查重後,通過調用bus_add_driver函數完成設備驅動的進一步註冊。bus_add_driver函數通過driver_attach函數對驅動和設備進行匹配,如果匹配成功則繼續完成設備節點創建等動作,如果匹配失敗則退出bus_add_driver。driver_attach函數通過bus_for_each_dev函數循環遍歷drv->bus總線設備樹,遍歷獲得的設備通過__driver_attach函數完成驅動進行具體的匹配動作。__driver_attach函數通過driver_match_device函數驗證設備與驅動是否匹配,如果匹配成功則調用driver_probe_device函數來完成設備驅動的掛載動作。這兩部分源碼如下所示:
// file: kernel/drivers/base/base.h
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
// 調用對應總線的match成員函數,驗證驅動與設備是否匹配
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
// file: kernel/drivers/base/dd.c
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
.........
ret = really_probe(dev, drv);
.........
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
// 設備dev記錄下,與其匹配的driver方法
dev->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_match_device函數是通過調用drv->bus總線的match成員函數來驗證配是否成功,driver_probe_device函數也是通過調用dev->bus總線的probe成員函數來完成具體驅動的probe動作。由上面對i2c_register_driver接口的分析可以知道drv->bus = &i2c_bus_type,因此driver_match_device函數則是調用i2c_bus_type.match函數完成。由上面對i2c_new_device接口的分析也可以知道dev->bus
= &i2c_bus_type,因此driver_probe_device函數則是調用i2c_bus_type.probe函數完成。下面給出i2c_bus_type的相關代碼:// file: kernel/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,
};
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
/* 每個i2c device都會對應唯一一個client,
* 因此可以採用container_of形式反向獲取出client成員
*/
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
// 如果驅動的id_table存在,則繼續進行匹配驗證
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
static 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;
}
static int i2c_device_probe(struct device *dev)
{
/* 每個i2c device都會對應唯一一個client,
* 因此可以採用container_of形式反向獲取出client成員
*/
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
if (!client)
return 0;
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
client->driver = driver;
........
// 調用真正設備驅動的probe成員函數
status = driver->probe(client, i2c_match_id(driver->id_table, client));
........
}
i2c_bus_type.match成員即爲i2c_device_match函數,i2c_bus_type.probe成員即爲i2c_device_probe函數。i2c_device_match函數較爲簡單,就是通過對比client->type名稱是否與driver->id_table中的name名稱是否一樣來完成,由於其代碼較爲簡單就不進行詳細分析。i2c_device_probe函數對driver成員進行簡單的防錯處理後,則調用driver->probe函數完成設備驅動的probe動作。對於TPS65185設備驅動程序而言,driver->probe函數即爲tps65185_probe函數。這樣一來內核是怎麼調用到設備驅動的probe函數就分析完畢了,具體的調用流程也和dump_stack打印出來的log一致。本次分析設備驅動的probe調用流程,可以用下圖來進行總結。內核在啓動(kernel/init)的時候會主動去調用驅動模塊的加載函數(tps65185_init),模塊驅動的加載函數(tps65185_init)會調用相關驅動子系統的驅動註冊函數(kernel/drivers/i2c),相關驅動子系統(kernel/drivers/i2c)通過linux的基本設備驅動框架(kernel/drivers/base)來完成驅動的完整註冊,基本的驅動框架(kernel/drivers/base)在驅動註冊過程中需要調用到具體子系統的匹配探測接口(kernel/drivers/i2c),才能正確的完成設備驅動的註冊。
理解清楚內核是如何調用到設備驅動的probe成員函數,對我們編寫和調試設備驅動的幫助其實不大,因爲這樣最多隻能夠讓我們對linux的設備驅動模型有個初步的理解。雖然理解整個調用流程僅僅是爲了學習,但是認識設備驅動的probe是由其對應總線的probe所調用是非常重要的。設備驅動的probe的傳入參數實際上是總線的probe所構造的,理解這一點對我們驅動的開發是非常有好處的。以i2c_bus_type.probe成員函數爲例,i2c_device_probe通過將client->driver = driver一句,就將client與設備驅動進行了關聯。而在之前的i2c_new_device接口中client->adapter = adapter一句,就將client與總線驅動進行了關聯。最後i2c_device_probe通過將client傳遞給設備驅動的probe接口,這樣一來driver和adapter就通過client進行的間接的關聯,也就是說設備驅動在調用i2c_transfer接口的時候,通過傳入client->adapter參數就選用出正確的總線驅動來進行i2c通信。理解清楚這個流程後,如果設備驅動在運行的過程中如果出現i2c不能通信,那麼我們就可以根據此思路加上添加相關log來進行調試。到此爲止我們對i2c_register_driver驅動註冊接口的分析就結束了,下面我們開始對i2c_transfer接口進行相關分析。
3.3、從上篇文章《Linux基本設備驅動闡述》中,我們可以瞭解到i2c-core提供了兩類通信接口供設備驅動使用:普通的I2C通信接口(i2c_master_send、i2c_master_recv、i2c_transfer),Smbus通信接口(i2c_smbus_xfer)。對於普通的I2C通信接口而言,通過接口參數完成對msg的封裝後,最後都是調用i2c_transfer函數來完成實際的物理通信。i2c_transfer接口則是通過調用具體的總線驅動adapter->algo->master_xfer接口來完成實際的物理通信。對於Smbus方式而言,通過接口參數完成對msg的封裝後,最後是調用i2c_smbus_xfer。當我們實際的硬件支持Smbus方式的時候,i2c_smbus_xfer則會調用總線驅動的adapter->algo->smbus_xfer接口來完成實際的物理通信;而若實際硬件不支持的話,則會調用i2c_smbus_xfer_emulator軟件仿真來完成實際的物理通信。而i2c_smbus_xfef_emulator接口其實就是通過調用i2c_transfer接口來實現軟件模擬對Smbus協議的支持。對於RK3026硬件平臺而言,是不能直接支持Smbus協議的,因此對於Smbus通信接口的實現則是通過軟件模擬來代替。具體相關代碼如下所示:
// file: kernel/drivers/i2c/i2c-core.c
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;
int ret, try;
/* 判定硬件驅動是否支持I2C協議通信 */
if (adap->algo->master_xfer) {
.........
/* 由於adapter爲具體的硬件,所以對其操作需要加鎖處理,
* 保證每次只有一個線程在操作adapter,防止硬件使用竟態
*/
if (in_atomic() || irqs_disabled()) {
ret = i2c_trylock_adapter(adap);
if (!ret)
return -EAGAIN;
} else {
i2c_lock_adapter(adap);
}
/* 調用總線驅動 adap->alog->master_xfer 完成真正的i2c數據通信 */
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
.........
}
i2c_unlock_adapter(adap);
return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
char read_write, u8 command, int protocol,
union i2c_smbus_data *data)
{
unsigned long orig_jiffies;
int try;
s32 res;
flags &= I2C_M_TEN | I2C_CLIENT_PEC;
/* 判斷硬件是否支持Smbus協議 */
if (adapter->algo->smbus_xfer) {
i2c_lock_adapter(adapter);
/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (res = 0, try = 0; try <= adapter->retries; try++) {
/* 調用總線驅動 adap->alog->smbus_xfer 完成真正的i2c數據通信 */
res = adapter->algo->smbus_xfer(adapter, addr, flags,
read_write, command, protocol, data);
.........
}
i2c_unlock_adapter(adapter);
} else
/* 調用軟件模擬Smbus協議 */
res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
command, protocol, data);
return res;
}
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data *data)
{
/* So we need to generate a series of msgs. In the case of writing, we
need to use only one message; when reading, we need two. We initialize
most things with sane defaults, to keep the code below somewhat
simpler. */
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
int num = read_write == I2C_SMBUS_READ ? 2 : 1;
struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0, 100000, 0, 0 },
{ addr, flags | I2C_M_RD, 0, msgbuf1, 100000, 0, 0 }
};
int i;
u8 partial_pec = 0;
int status;
/* Smbus協議發送數據 */
msgbuf0[0] = command;
switch (size) {
case I2C_SMBUS_QUICK:
.........
case I2C_SMBUS_BYTE:
.........
case I2C_SMBUS_BYTE_DATA:
.........
case I2C_SMBUS_WORD_DATA:
.........
case I2C_SMBUS_PROC_CALL:
.........
case I2C_SMBUS_BLOCK_DATA:
.........
case I2C_SMBUS_BLOCK_PROC_CALL:
.........
case I2C_SMBUS_I2C_BLOCK_DATA:
.........
default:
.........
}
.........
/* 調用普通I2C通信接口來模擬Smbus協議 */
status = i2c_transfer(adapter, msg, num);
if (status < 0)
return status;
.........
/* Smbus協議讀取數據 */
if (read_write == I2C_SMBUS_READ)
switch (size) {
case I2C_SMBUS_BYTE:
.........
case I2C_SMBUS_BYTE_DATA:
.........
case I2C_SMBUS_WORD_DATA:
case I2C_SMBUS_PROC_CALL:
.........
case I2C_SMBUS_I2C_BLOCK_DATA:
.........
case I2C_SMBUS_BLOCK_DATA:
case I2C_SMBUS_BLOCK_PROC_CALL:
.........
}
return 0;
}
通過上面對i2c_transfer和i2c_smbus_xfer接口的分析,我們就可以知道I2C總線驅動在數據通信上總共有兩個接口algo->master_xfer和algo->smbus_xfer,對於RK3026硬件平臺而言,由於不支持硬件Smbus協議,因此RK30 I2C總線驅動只需要完善algo->master_xfer接口即可。最後其實設備驅動在進行數據通信之前,一般會在設備驅動程序的probe函數中調用i2c_check_functionality接口來查詢總線驅動是否支持i2c-core所封裝的這兩套通信方式,i2c_check_functionality其實也是通過調用algo->functionality函數完成的,其相關代碼如下所示:
/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{
return (func & i2c_get_functionality(adap)) == func;
}
/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{
return adap->algo->functionality(adap);
}
到目前爲止,我們對I2C核心的相關接口已經分析完畢了。總結3.1內容我們可以知道,I2C設備client是怎麼和I2C總線驅動adapter關聯起來的;總結3.2內容我們可以知道,I2C驅動driver是怎麼和I2C設備client關聯起來的。總結3.3我們可以知道,I2C驅動driver是怎樣和I2C總線驅動adapter進行交互的。通過總結上面三點,我們就基本理清楚Linux I2C子系統的工作流程。相信現在大家對本篇開始部分的I2C工作流程圖會有更加深刻的了。
四、驅動編寫
總結3.1~3.3的知識可知,對於I2C總線驅動需要完善或調用i2c-core提供的接口主要由三個:i2c_register_adapter總線註冊接口、adapter->algo->master_sfer數據通信接口,adapter->algo->functionality功能查詢接口。下面我們就開始簡單實現Linux I2C總線的驅動程序。RK3026平臺擁有多個I2C模塊,因此我們採用PLATFORM形式來完成I2C總線驅動與CPU I2C模塊的匹配。首先給出基本的驅動框架:
// file: kernel/drivers/i2c/busses/i2c-test.c
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/cpufreq.h>
#include <linux/wakelock.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <mach/board.h>
#include <mach/iomux.h>
#include <mach/gpio.h>
#include <asm/irq.h>
/* platform_driver module */
static int rk30_i2c_adapter_probe(struct platform_device * pdev) {
return 0;
}
static int rk30_i2c_adapter_remove(struct platform_device * pdev) {
return 0;
}
static struct platform_driver i2c_adapter_driver = {
.probe = rk30_i2c_adapter_probe,
.remove = rk30_i2c_adapter_remove,
.driver = {
.owner = THIS_MODULE,
.name = "rk30-i2c",
},
};
static int __init i2c_adapter_init(void) {
return platform_driver_register(&i2c_adapter_driver);
}
static void __exit i2c_adapter_exit(void) {
platform_driver_unregister(&i2c_adapter_driver);
}
subsys_initcall(i2c_adapter_init);
module_exit(i2c_adapter_exit);
上面的驅動框架相信大家已經非常熟悉了,所以具體的代碼這裏就不再做解釋,如果有需要可以回顧下上篇文章《Linux基本設備驅動闡述》。下面我們開始probe函數的編寫,通過上面對Linux I2C子系統的分析,我們可以知道總線驅動必須有的一個動作就是向i2c-core註冊總線驅動i2c_register_adapter。因此probe就至少包含兩個動作:adapter成員的初始化,adapter成員的註冊。adapter成員的初始化主要是對:adapter->owner、總線驅動的名稱adapter->name、數據通信方法adapter->algo、總線號adapter->nr等。其中非常重要的一個成員初始化就是adapter-algo,因爲其代表着總線驅動核心-數據通信方法。adapter的註冊接口採用i2c_add_numbered_adapter,此接口適用於靜態註冊總線驅動所用,即總線驅動的總線號是由我們靜態分配的(這樣做主要是便於I2C總線號就能與CPU
I2Cx進行對應)。因此對於adapter數據的操作,代碼如下所示:
// file: kernel/drivers/i2c/busses/i2c-test.c
/************** data struct defien *************/
struct rk30_i2c_platformdata {
int sda_pin;
int scl_pin;
int bus_num;
};
struct rk30_i2c {
spinlock_t lock;
void __iomem * reg_base_addr;
struct i2c_adapter adapter;
struct rk30_i2c_platformdata * pdata;
};
static int rk30_i2c_xfer(struct i2c_adapter * adapter,
struct i2c_msg * msgs, int num) {
return 0;
}
static u32 rk30_i2c_func(struct i2c_adapter * adapter) {
/* 支持基本I2C通信、支持模擬Smbus通信 */
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL \
| I2C_FUNC_PROTOCOL_MANGLING;
}
static const struct i2c_algorithm rk30_i2c_algorithm = {
.master_xfer = rk30_i2c_xfer,
.functionality = rk30_i2c_func,
};
static int rk30_i2c_adapter_probe(struct platform_device * pdev) {
.........
/* init i2c_adapter */
pi2c->adapter.owner = THIS_MODULE;
pi2c->adapter.retries = 5;
pi2c->adapter.timeout = msecs_to_jiffies(100);
pi2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
strlcpy(pi2c->adapter.name, "rk30_i2c", sizeof(pi2c->adapter.name));
pi2c->adapter.algo_data = (void *)pi2c;
pi2c->adapter.nr = pi2c->pdata->bus_num;
pi2c->adapter.algo = &rk30_i2c_algorithm;
ret = i2c_add_numbered_adapter(&pi2c->adapter);
if(ret < 0) {
printk("err[%s]: add adapter failed.\n", __func__);
ret = -EINVAL;
goto ADD_ADAPTER_ERR;
}
.........
}
rk30_i2c_func接口代表著總線驅動所能支持的協議類型,I2C_FUNC_I2C代表著基本的I2C數據通信支持,I2C_FUNC_SMBUS_EMUL代表着模擬Smbus協議支持,I2C_FUNC_PROTOCOL_MANGLING代表着msgs中不發送start信號。由於本人能力有限,目前只能讀懂和調試現有的數據通信驅動,還不能很好的獨立完成數據通信的編寫,因此本文對於rk30_i2c_xfer接口不給予詳細的編寫(大致流程也是通過自旋鎖、I2C中斷和工作隊列三者配合來完成)。adapter成員的初始化以及相關注冊都已經完善了,剩下的部分則主要是具體硬件的初始化了:管教初始化、時鐘初始化、中斷初始化。因此probe接口的內容如下所示:
/************** data struct defien *************/
struct rk30_i2c_platformdata {
int sda_pin;
int scl_pin;
int bus_num;
};
struct rk30_i2c {
spinlock_t lock;
void __iomem * reg_base_addr;
struct i2c_adapter adapter;
struct rk30_i2c_platformdata * pdata;
};
/************** rk30 i2c hardware init ***************/
static int rk30_i2c_io_init(struct rk30_i2c * pi2c) {
// platform_get_resource_byname();
// request_mem_region();
// ioremap();
return 0;
}
static int rk30_i2c_clk_init(struct rk30_i2c * pi2c) {
return 0;
}
static int rk30_i2c_irq_init(struct rk30_i2c * pi2c) {
// free_irq();
return 0;
}
static int rk30_i2c_io_deinit(struct rk30_i2c * pi2c) {
// iounmap();
return 0;
}
static int rk30_i2c_clk_deinit(struct rk30_i2c * pi2c) {
return 0;
}
static int rk30_i2c_irq_deinit(struct rk30_i2c * pi2c) {
// platform_get_irq();
// request_irq();
return 0;
}
static void rk30_i2c_enable_clk(struct rk30_i2c * pi2c, int bus_num) {
return;
}
static void rk30_i2c_disable_clk(struct rk30_i2c * pi2c, int bus_num) {
return;
}
static void rk30_i2c_set_clk(struct rk30_i2c * pi2c,
int bus_num, unsigned long scl_rate) {
return;
}
static void rk30_i2c_do_xfer(struct rk30_i2c * pi2c,
struct i2c_msg * msgs, int num) {
return;
}
/*********************************************************/
static int rk30_i2c_xfer(struct i2c_adapter * adapter,
struct i2c_msg * msgs, int num) {
unsigned long scl_rate = 0;
struct rk30_i2c * pi2c = (struct rk30_i2c *)adapter->algo_data;
rk30_i2c_enable_clk(pi2c, num);
rk30_i2c_set_clk(pi2c, num, scl_rate);
rk30_i2c_do_xfer(pi2c, msgs, num);
rk30_i2c_disable_clk(pi2c, num);
return 0;
}
static u32 rk30_i2c_func(struct i2c_adapter * adapter) {
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL \
| I2C_FUNC_PROTOCOL_MANGLING;
}
static const struct i2c_algorithm rk30_i2c_algorithm = {
.master_xfer = rk30_i2c_xfer,
.functionality = rk30_i2c_func,
};
static int rk30_i2c_adapter_probe(struct platform_device * pdev) {
int ret = 0;
struct rk30_i2c * pi2c = NULL;
if(pdev->dev.platform_data == NULL) {
printk("err[%s]: unknow platform_data.\n", __func__);
ret = -EINVAL;
return ret;
}
pi2c = (struct rk30_i2c *)kmalloc(sizeof(struct rk30_i2c), GFP_KERNEL);
if(pi2c == NULL) {
printk("err[%s]: memory alloc failed.\n", __func__);
ret = -ENOMEM;
goto NO_MEM_ERR;
}
pi2c->pdata = (struct rk30_i2c_platformdata *)pdev->dev.platform_data;
spin_lock_init(&pi2c->lock);
/* init i2c_adapter */
pi2c->adapter.owner = THIS_MODULE;
pi2c->adapter.retries = 5;
pi2c->adapter.timeout = msecs_to_jiffies(100);
pi2c->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
strlcpy(pi2c->adapter.name, "rk30_i2c", sizeof(pi2c->adapter.name));
pi2c->adapter.algo_data = (void *)pi2c;
pi2c->adapter.nr = pi2c->pdata->bus_num;
pi2c->adapter.algo = &rk30_i2c_algorithm;
ret = i2c_add_numbered_adapter(&pi2c->adapter);
if(ret < 0) {
printk("err[%s]: add adapter failed.\n", __func__);
ret = -EINVAL;
goto ADD_ADAPTER_ERR;
}
/* init hw i2c */
ret = rk30_i2c_io_init(pi2c);
if(ret < 0) {
printk("err[%s]: rk30 i2c io init failed.\n", __func__);
ret = -EINVAL;
goto IO_INIT_ERR;
}
ret = rk30_i2c_clk_init(pi2c);
if(ret < 0) {
printk("err[%s]: rk30 i2c clk init failed.\n", __func__);
goto CLK_INIT_ERR;
}
ret = rk30_i2c_irq_init(pi2c);
if(ret < 0) {
printk("err[%s]: rk30 i2c irq init failed.\n", __func__);
goto IRQ_INIT_ERR;
}
/* storage pi2c to pdev */
platform_set_drvdata(pdev, pi2c);
return 0;
IRQ_INIT_ERR:
rk30_i2c_clk_deinit(pi2c);
CLK_INIT_ERR:
rk30_i2c_io_deinit(pi2c);
IO_INIT_ERR:
i2c_del_adapter(&pi2c->adapter);
ADD_ADAPTER_ERR:
kfree(pi2c);
NO_MEM_ERR:
return ret;
}
remove接口與probe接口恰好相反,因此remove接口也給出如下所示:
static int rk30_i2c_adapter_remove(struct platform_device * pdev) {
struct rk30_i2c * pi2c = (struct rk30_i2c *)platform_get_drvdata(pdev);
rk30_i2c_irq_deinit(pi2c);
rk30_i2c_io_deinit(pi2c);
rk30_i2c_clk_deinit(pi2c);
kfree(pi2c);
return 0;
}
到此爲止I2C總線驅動的框架結構就已經完成了,I2C總線驅動的編寫主要還是如對I2C子系統分析所說,主要是完善algo->master_xfer、algo->functionality兩個接口的編寫。而其中最爲核心重要的就是rk30_i2c_xfer接口完成數據通信,對於此接口本篇卻沒有具體的實現,希望讀者自己參閱相關代碼進行進一步的學習。
五、結尾
本篇文章對Linux I2C子系統分析和Linux I2C總線驅動編寫的分析就已經結束了,不知道讀者有沒有收穫到些許東西,對實際驅動編寫調試能不能起到幫助。常用的外設總線除了I2C外,還有SPI、SDIO、USB總線。Linux SPI子系統和Linux I2C子系統兩者比較相似,有興趣的讀者可以去分析學習下。至於SDIO、USB總線比較複雜,本人現在還沒有接觸過,希望在以後工作鍛鍊中能夠熟悉這兩大總線,然後再和大家進行交流學習。
謝謝!