linux 中bus驅動解析
總線(bus)是linux發展過程中抽象出來的一種設備模型,為了統一管理所有的設備,內核中每個設備都會被掛載在總線上,這個bus可以是對應硬件的bus(i2c bus、spi bus)、可以是虛擬bus(platform bus)。
簡述bus的工作流程
bus將所有掛在上面的具體設備抽象成兩部分,driver和device。
driver實現了同類型設備的驅動程序實現,而device則向系統注冊具體的設備需要的資源,每當添加一個新的driver(device)到bus中時,都將調用bus的match函數,試圖尋找匹配的device(driver)。
總線大概是這樣的:
如果匹配成功,就調用probe函數,在probe函數中實現設備的初始化、各種配置以及生成用戶空間的文件接口。
舉個例子,針對AT24CXX(一種常用的存儲設備)這種同系列產品,他們的操作方式都是非常相似的,不同的無非是容量大小。
那么我們就沒有必要為AT24C01、AT24C02去分別寫一份驅動程序,而是統一為其寫一份兼容所有AT24CXX的驅動程序,然后再傳入不同的參數以對應具體的型號。
在linux驅動管理模型中的體現就是:驅動程序對應driver、需要的具體型號的硬件資源對應device,將其掛在bus上。
將driver注冊到bus上,當用戶需要使用AT24C01時,以AT24C01的參數構建一個對應device,注冊到bus中,bus的match函數匹配上之后,調用probe函數,即可完成AT24C01的初始化,完成在用戶空間的文件接口注冊。
以此類推,添加所有的AT24CXX設備都可以以這樣的形式實現,只需要構建一份device,而不用為每個設備重寫一份驅動,提高了復用性,節省了內存空間。
linux bus結構體
linux將設備掛在總線上,對應設備的注冊和匹配流程由總線進行管理,在linux內核中,每一個bus,都由struct bus_type來描述:
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
...
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);
struct subsys_private *p;
...
};
為了突出重點,省去了一些暫時不需要深入了解的成員,我們來看看其中最主要的幾個成員:
name : 該bus的名字,這個名字是這個bus在sysfs文件系統中的體現,對應/sys/bus/$name.
dev_name : 這個dev_name並不對應bus的名稱,而是對應bus所包含的struct device的名字,即對應dev_root。
dev_root:bus對應的device結構,每個設備都需要對應一個相應的struct device.
match:bus的device鏈表和driver鏈表進行匹配的實際執行回調函數,每當有device或者driver添加到bus中時,調用match函數,為device(driver)尋找匹配的driver(device)。
uevent:bus時間回調函數,當屬於這個bus的設備發生添加、刪除、修改等行為時,都將出發uvent事件。
probe:當device和driver經由match匹配成功時,將會調用總線的probe函數實現具體driver的初始化。事實上每個driver也會提供相應的probe函數,先調用總線的probe函數,在總線probe函數中調用driver的probe函數。
remove:移除掛載在設備上的driver,bus上的driver部分也會提供remove函數,在執行移除時,先調用driver的remove,然后再調用bus的remove以清除資源。
struct subsys_private *p:見下文
struct subsys_private *p
struct subsys_private *p主要實現了對bus中數據的管理:
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
...
};
其中struct kset subsys、struct kset *devices_kset、struct kset *drivers_kset都是在sysfs文件系統中創建對應的目錄。
struct klist klist_devices、struct klist klist_drivers是兩個主要的數據部分,klist_devices是存儲所有注冊到bus的device的鏈表,而klist_drivers是存儲所有注冊到bus的driver的鏈表。
bus的注冊
了解了bus的結構,那么,bus是怎么注冊的呢?
spi bus的注冊過程在KERNEL/drivers/spi/spi.c中:
static int __init spi_init(void)
{
...
status = bus_register(&spi_bus_type);
...
return 0;
}
postcore_initcall(spi_init);
i2c bus的注冊過程在KERNEL/drivers/i2c/i2c-core-base.c中:
static int __init i2c_init(void)
{
...
bus_register(&i2c_bus_type);
...
}
postcore_initcall(i2c_init);
而platform bus的注冊過程在KERNEL/drivers/base/platform.c中:
int __init platform_bus_init(void)
{
...
bus_register(&platform_bus_type);
...
}
i2c和spi為物理總線,這兩種總線通過postcore_initcall()將各自的init函數注冊到系統中,postcore_initcall的詳解可以參考另一篇博客:linux init機制。
而platform作為虛擬總線,platform_bus_init被系統初始化時直接調用,調用流程為:
start_kernel
-> rest_init();
-> kernel_thread(kernel_init, NULL, CLONE_FS);
-> kernel_init()
-> kernel_init_freeable();
-> do_basic_setup();
-> driver_init();
->platform_bus_init();
spi_bus_type、i2c_bus_type、platform_bus_type分別為對應的struct bus_type描述結構體。
對應的spi_bus_type、i2c_bus_type、platform_bus_type實現我將分別在spi、i2c、platform具體框架解析中介紹。
bus_register()
可以看到,這三種總線都是由bus_register()接口注冊的,那么這個接口到底做了什么呢?
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
retval = bus_add_groups(bus, bus->bus_groups);
if (retval)
goto bus_groups_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
}
在上面貼出的代碼中,可以看出,bus_register()其實也沒做什么特別的事,主要是兩個:
- 在sysfs系統中注冊各種用戶文件接口,將bus的信息和操作接口導出到用戶接口。
- 初始化device和driver鏈表。
向總線中添加driver/device
既然bus_register只是初始化了相應的資源,在/sys下導出接口文件,那整個bus是如何工作的呢?
以i2c為例,我們來看看這整個過程:
首先使用i2c_new_device接口來添加一個device:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL);
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;
client->irq = info->irq;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
...
device_register(&client->dev);
...
}
申請一個i2c_client並對其賦值,然后以這個為參數調用device_register(&client->dev),將dangqiandevice添加到bus中。
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
int device_add(struct device *dev)
{
...
bus_add_device(dev);
...
}
int bus_add_device(struct device *dev){
...
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
...
bus_probe_device(dev);
...
}
在device_register中,調用了device_add,緊接着調用bus_add_device,根據函數名稱可以看出是將這個device添加到對應的bus中。
果然,根據bus_add_device()的源代碼,可以看到,將當前device鏈接到其對應bus的devices鏈表,然后在下面調用bus_probe_device();這個函數的作用就是輪詢對應bus的drivers鏈接,查看新添加的device是否存在匹配的driver。
對應的,i2c_driver_register()將i2c driver部分添加到bus中,再輪詢檢查bus的devices鏈表是否有對應的device能匹配上,有興趣的可以從i2c_driver_register()開始研究源代碼。
device和driver的匹配
上文中提到當bus中有新的device和driver添加時,會調用bus的match函數進行匹配,那么到底是怎么匹配的呢?
簡單來說,在靜態定義的device中,一般會有.name屬性,與driver的.id_table屬性相匹配。
device部分還有可能從設備樹轉換而來,就有設備樹中相應的.compatible屬性和driver的of_match_table.compatible屬性相匹配。
事實上對於匹配這一部分,可以直接參考每個bus的match函數實現。
這一章節只是對linux中的總線做一個概念性的說明,在之后的博客中會詳細介紹到相應bus的框架,同時也會詳解對應的match()函數實現。
敬請期待!
好了,關於linux的bus討論就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言
原創博客,轉載請注明出處!
祝各位早日實現項目叢中過,bug不沾身.