總線(bus)是linux發展過程當中抽象出來的一種設備模型,爲了統一管理全部的設備,內核中每一個設備都會被掛載在總線上,這個bus能夠是對應硬件的bus(i2c bus、spi bus)、能夠是虛擬bus(platform bus)。html
bus將全部掛在上面的具體設備抽象成兩部分,driver和device。node
driver實現了同類型設備的驅動程序實現,而device則向系統註冊具體的設備須要的資源,每當添加一個新的driver(device)到bus中時,都將調用bus的match函數,試圖尋找匹配的device(driver)。linux
總線大概是這樣的:框架
若是匹配成功,就調用probe函數,在probe函數中實現設備的初始化、各類配置以及生成用戶空間的文件接口。函數
舉個例子,針對AT24CXX(一種經常使用的存儲設備)這種同系列產品,他們的操做方式都是很是類似的,不一樣的無非是容量大小。post
那麼咱們就沒有必要爲AT24C0一、AT24C02去分別寫一份驅動程序,而是統一爲其寫一份兼容全部AT24CXX的驅動程序,而後再傳入不一樣的參數以對應具體的型號。debug
在linux驅動管理模型中的體現就是:驅動程序對應driver、須要的具體型號的硬件資源對應device,將其掛在bus上。rest
將driver註冊到bus上,當用戶須要使用AT24C01時,以AT24C01的參數構建一個對應device,註冊到bus中,bus的match函數匹配上以後,調用probe函數,便可完成AT24C01的初始化,完成在用戶空間的文件接口註冊。code
以此類推,添加全部的AT24CXX設備均可以以這樣的形式實現,只須要構建一份device,而不用爲每一個設備重寫一份驅動,提升了複用性,節省了內存空間。orm
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 { 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是怎麼註冊的呢?
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()接口註冊的,那麼這個接口到底作了什麼呢?
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()其實也沒作什麼特別的事,主要是兩個:
既然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()開始研究源代碼。
上文中提到當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不沾身.