天天看點

Linux裝置模型剖析系列之四(BUS)

六、Bus

1. 概述

在Linux裝置模型中,Bus(總線)是一類特殊的裝置,它是連接配接處理器和其它裝置之間的通道(channel)。為了友善裝置模型的實作,核心規定,系統中的每個裝置都要連接配接在一個Bus上,這個Bus可以是一個内部Bus、虛拟Bus或者platform Bus。

核心通過​

​struct bus_type​

​結構抽象Bus,它是在include/linux/device.h中定義的。下文會圍繞該結構,描述Linux核心中Bus的功能,以及相關的實作邏輯。最後,會簡單的介紹一些标準的Bus(如platform),介紹它們的用途、它們的使用場景。

2. 功能說明

按照老傳統,描述功能前,先介紹一下該子產品的一些核心資料結構,對bus子產品而言,核心資料結構就是​

​struct bus_type​

​,另外,還有一個subsystem相關的結構,會一并說明。

2.1 struct bus_type

/* inlcude/linux/device.h, line 93 */
struct bus_type {
    const char              *name;
    const char              *dev_name;
    struct device           *dev_root;
    struct bus_attribute    *bus_attrs;
    struct device_attribute *dev_attrs;
    struct driver_attribute *drv_attrs;

    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 (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;

    struct iommu_ops *iommu_ops;

    struct subsys_private *p;
    struct lock_class_key lock_key;
};      
  • name:該bus的名稱,會在sysfs中以目錄的形式存在,如platform bus在sysfs中表現為"/sys/bus/platform”。
  • dev_name:該名稱和struct device結構中的init_name有關。對有些裝置而言(例如批量化的USB裝置),設計者根本就懶得為它起名字的,而核心也支援這種懶惰,允許将裝置的名字留白。這樣當裝置注冊到核心後,裝置模型的核心邏輯就會用"bus->dev_name+device ID”的形式,為這樣的裝置生成一個名稱。
  • dev_root:根據核心的注釋,dev_root裝置為bus的預設父裝置(Default device to use as the parent),但在核心實際實作中,隻和一個叫sub system的功能有關,随後會介紹。
  • bus_attrs、dev_attrs、drv_attrs:一些預設的attribute,可以在bus、device或者device_driver添加到核心時,自動為它們添加相應的attribute。
  • match:一個由具體的bus driver實作的回調函數。當任何屬于該Bus的device或者device_driver添加到核心時,核心都會調用該接口,如果新加的device或device_driver比對上了自己的另一半的話,該接口要傳回非零值,此時Bus子產品的核心邏輯就會執行後續的處理。
  • uevent:一個由具體的bus driver實作的回調函數。當任何屬于該Bus的device發生添加、移除或者其它動作時,Bus子產品的核心邏輯就會調用該接口,以便bus driver能夠修改環境變量。
  • probe、remove:這兩個回調函數,和device_driver中的非常類似,但它們的存在是非常有意義的。可以想象一下,如果Bus下的某裝置驅動需要調用probe(其實就是初始化)的話,需要保證該device所在的bus是被初始化過、且能正确工作的。這就要就在執行device_driver的probe前,先執行它的bus的probe。remove的過程相反。但也并不是所有的bus都需要probe和remove接口的,因為對有些bus來說(例如platform bus),它本身就是一個虛拟的總線,無所謂初始化,直接就能使用,是以這些bus的driver就可以将這兩個回調函數留白。
  • shutdown、suspend、resume:和probe、remove的原理類似,屬于電源管理相關的實作,後文再說。
  • pm:電源管理相關的邏輯,後文再說。
  • iommu_ops:後文再說。
  • p:後文再說。

2.2 struct subsys_private

該結構和device_driver中的struct driver_private類似。先看定義:

/* drivers/base/base.h, line 28 */
struct subsys_private {
    struct kset subsys;
    struct kset *devices_kset;
    struct list_head interfaces;
    struct mutex mutex;

    struct kset *drivers_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;

    struct kset glue_dirs;
    struct class *class;
};      
  • 看到結構内部的字段,就清晰多了,先了解一下為什麼起名為subsys:
  • 按理說,這個結構就是集合了一些bus子產品需要使用的私有資料,例如kset啦、klist啦等等,命名為bus_private會好點(就像device_driver子產品一樣)。不過為什麼核心沒這麼做呢?看看include/linux/device.h中的struct class結構就知道了,因為class結構中也包含了一個一模一樣的struct subsys_private指針,看來class和bus很相似啊。
  • 想到這裡,就好了解了,無論是bus,還是class,還是我們會在後面看到的一些虛拟的子系統,它都構成了一個“子系統(sub-system)”,該子系統會包含形形色色的device或device_driver,就像一個獨立的王國一樣,存在于核心中。而這些子系統的表現形式,就是/sys/bus目錄下面的子目錄,每一個子目錄,都是一個子系統(如/sys/bus/spi/)。
  • 再看下struct subsys_private中各個字段的解釋:
  • subsys、devices_kset、drivers_kset:這是三個kset,它是一個特殊的kobject,用來集合相似的kobject,它在sysfs中也會以目錄的形式展現。其中subsys,代表了本bus(如/sys/bus/spi),它下面可以包含其它的kset或者其它的kobject;devices_kset和drivers_kset則是bus下面的兩個kset(如​

    ​/sys/bus/spi/devices​

    ​​和​

    ​/sys/bus/spi/drivers​

    ​),分别包括本bus下所有的device和device_driver。
  • interface:它是一個list head,用于儲存該bus下所有的interface。
  • klist_devices和klist_drivers:這是兩個連結清單,分别儲存了本bus下所有的device和device_driver的指針,以友善查找。
  • drivers_autoprobe:用于控制該bus下的drivers或者device是否自動probe。
  • bus和class:分别指向上層的bus或者class指針。

2.3 功能總結

根據上面的核心資料結構,可以總結出bus子產品的功能包括:

  • bus的注冊和登出;
  • 本bus下有device或者device_driver注冊到核心時的處理;
  • 本bus下有device或者device_driver從核心登出時的處理;
  • device_drivers的probe處理;
  • 管理bus下的所有device和device_driver。

3. 内部執行邏輯分析

3.1 bus的注冊

bus的注冊是由bus_register接口實作的,該接口的原型是在include/linux/device.h中聲明的,并在drivers/base/bus.c中實作,bus_register:工作就是完成bus_type_private的初始化,建立注冊的這條總線需要的目錄檔案(在這條總線目錄下建立/device/driver 目錄;初始化這條總線上的裝置連結清單:struct klist klist_devices;初始化這條總線上的驅動連結清單:struct klist klist_drivers;)其原型如下:

/* include/linux/device.h, line 118 */
extern int __must_check bus_register(struct bus_type *bus);

 
#define BUS_ATTR(_name, _mode, _show, _store) \
  struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
 
#define __ATTR(_name,_mode,_show,_store) { \
  .attr = {.name = __stringify(_name), .mode = _mode }, \
  .show = _show,          \
  .store  = _store,         \
}
 
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
 
struct klist {
  spinlock_t      k_lock;
  struct list_head  k_list;
  void      (*get)(struct klist_node *);
  void      (*put)(struct klist_node *);
} __attribute__ ((aligned (sizeof(void *))));      
  • 該功能的執行邏輯如下:
  • 為bus_type中struct subsys_private類型的指針配置設定空間,并更新priv->bus和bus->p兩個指針為正确的值;
  • 初始化priv->subsys.kobj的name、kset、ktype等字段,啟動name就是該bus的name(它會展現在sysfs中),kset和ktype由bus子產品實作,分别為bus_kset和bus_ktype;
  • 調用​

    ​kset_register​

    ​将priv->subsys注冊到核心中,該接口同時會向sysfs中添加對應的目錄(如/sys/bus/spi);
  • 調用​

    ​bus_create_file​

    ​向bus目錄下添加一個uevent attribute(如/sys/bus/spi/uevent);
  • 調用​

    ​kset_create_and_add​

    ​分别向核心添加devices和device_drivers kset,同時會展現在sysfs中;
  • 初始化priv指針中的mutex、klist_devices和klist_drivers等變量;
  • 調用​

    ​add_probe_files​

    ​​接口,在bus下添加drivers_probe和drivers_autoprobe兩個attribute (如​

    ​/sys/bus/spi/drivers_probe​

    ​​和​

    ​/sys/bus/spi/drivers_autoprobe​

    ​),其中drivers_probe允許使用者空間程式主動出發指定bus下的device_driver的probe動作,而drivers_autoprobe控制是否在device或device_driver添加到核心時,自動執行probe;
  • 調用​

    ​bus_add_attrs​

    ​,添加由bus_attrs指針定義的bus的預設attribute,這些attributes最終會展現在/sys/bus/xxx目錄下

3.2 device和device_driver的添加

核心提供了device_register和driver_register兩個接口,供各個driver子產品使用。而這兩個接口的核心邏輯,是通過bus子產品的bus_add_device和bus_add_driver實作的,下面我們看看這兩個接口的處理邏輯。

這兩個接口都是在drivers/base/base.h中聲明,在drivers/base/bus.c中實作,其原型為:

/* drivers/base/base.h, line 106 */
extern int bus_add_device(struct device *dev);

/* drivers/base/base.h, line 110 */ 
extern int bus_add_driver(struct device_driver *drv);      
  • bus_add_device的處理邏輯:
  • 調用内部的device_add_attrs接口,将由bus->dev_attrs指針定義的預設attribute添加到核心中,它們會展現在/sys/devices/xxx/xxx_device/目錄中;
  • 調用sysfs_create_link接口,将該device在sysfs中的目錄,連結到該bus的devices目錄下,例如:
# ls /sys/bus/spi/devices/spi1.0 -l 
lrwxrwxrwx root     root              2014-04-11 10:46 spi1.0 -> ../../../devices/platform/s3c64xx-spi.1/spi\_master/spi1/spi1.0      
  • 其中/sys/devices/…/spi1.0,為該device在sysfs中真正的位置,而為了友善管理,核心在該裝置所在的bus的xxx_bus/devices目錄中,建立了一個符号連結
  • 調用sysfs_create_link接口,在該裝置的sysfs目錄中(如/sys/devices/platform/alarm/)中,建立一個指向該裝置所在bus目錄的連結,取名為subsystem,例如:
# ls /sys/devices/platform/alarm/subsystem -l                                                  
lrwxrwxrwx root     root              2014-04-11 10:28 subsystem -> ../../../bus/platform      
  • 最後,毫無疑問,要把該裝置指針儲存在bus->priv->klist_devices中
  • bus_add_driver的處理邏輯:
  • 為該driver的struct driver_private指針(priv)配置設定空間,并初始化其中的priv->klist_devices、priv->driver、priv->kobj.kset等變量,同時将該指針儲存在device_driver的p處;
  • 将driver的kset(priv->kobj.kset)設定為bus的drivers kset(bus->p->drivers_kset),這就意味着所有driver的kobject都位于bus->p->drivers_kset之下(寄/sys/bus/xxx/drivers目錄下);
  • 以driver的名字為參數,調用kobject_init_and_add接口,在sysfs中注冊driver的kobject,展現在/sys/bus/xxx/drivers/目錄下,如/sys/bus/spi/drivers/spidev;
  • 将該driver儲存在bus的klist_drivers連結清單中,并根據drivers_autoprobe的值,選擇是否調用driver_attach進行probe;
  • 調用driver_create_file接口,在sysfs的該driver的目錄下,建立uevent attribute;
  • 調用driver_add_attrs接口,在sysfs的該driver的目錄下,建立由bus->drv_attrs指針定義的預設attribute;
  • 同時根據suppress_bind_attrs标志,決定是否在sysfs的該driver的目錄下,建立bind和unbind attribute。

3.3 driver的probe

我們已經介紹過driver的probe時機及過程,其中大部分的邏輯會依賴bus子產品的實作,主要為​

​bus_probe_device​

​​和​

​driver_attach​

​接口。同樣,這兩個接口都是在drivers/base/base.h中聲明,在drivers/base/bus.c中實作。

這兩個結構的行為類似,即搜尋所在的bus,比對是否有同名的device_driver(或device),如果有并且該裝置沒有綁定driver(注:這一點很重要,通過它可以使同一個driver,驅動相同名稱的多個裝置,後續在platform裝置的描述中會提及)則調用device_driver的probe接口。

4. 雜項

4.1 再談Subsystem

在舊的Linux核心版本中(linux2.6.23版本),sysfs下所有的頂層目錄(包括一些二級目錄)都是以調用​

​subsystem_register​

​接口,以sub-system的形式注冊到核心的,如:

# ls /sys
/sys/bus/ /sys/devices/ /sys/devices/system/
/sys/block  /sys/kernel/  /sys/slab/
…      

那時的subsystem_register的實作很簡單,就是調用kset_register,建立一個kset。我們知道,kset就是一堆kobject的集合,并會在sysfs中以目錄的形式呈現出來。

在新版本的核心中(linux3.10.29),subsystem的實作有了很大變化,例如:去掉了subsystem_register接口(但為了相容/sys/device/system子系統,在drivers/base/bus.c中,增加了一個subsys_register的内部接口,用于實作相應的功能)。根據這些變化,現在注冊subsystem有兩種方式:

方式一:在各自的初始化函數中,調用kset_create_and_add接口,建立對應的子系統,包括:

  • bus子系統,/sys/bus/,buses_init(drivers/base/bus.c)
  • class子系統,/sys/class/
  • kernel子系統,/sys/kernel/
  • firmware子系統,/sys/firmware/
  • 等等

其中bus子系統就是本文所講的Bus子產品,而其它的,我們會在後續的文章中陸續講述。這種方式和舊版本核心使用kset_register接口的方式基本一樣。

方式二:在bus子產品中,利用subsys_register接口,封裝出兩個API:​

​subsys_system_register​

​​和​

​subsys_virtual_register​

​,分别用于注冊system裝置(/sys/devices/system/*)和virtual裝置(/sys/devices/virtual/*)。 而該方式和方式一的差別是:它不僅僅建立了sysfs中的目錄,同時會注冊同名的bus和device。

4.2 虛拟總線——system bus、virtual bus、platform bus

在Linux核心中,有三種比較特殊的bus(或者是子系統),分别是system bus、virtual bus和platform bus。它們并不是一個實際存在的bus(像USB、I2C等),而是為了友善裝置模型的抽象,而虛構的。

system bus是舊版核心提出的概念,用于抽象系統裝置(如CPU、Timer等等)。而新版核心認為它是個壞點子,因為任何裝置都應歸屬于一個普通的子系統,是以就把它抛棄了(不建議再使用,它的存在隻為相容舊有的實作)。

virtual bus是一個比較新的bus,主要用來抽象那些虛拟裝置。所謂的虛拟裝置,是指不是真實的硬體裝置,而是用軟體模拟出來的裝置,例如虛拟機中使用的虛拟的網絡裝置(有關virtula bus的描述,請參考​​LWN.net]​​)。

platform bus就比較普通,它主要抽象內建在CPU(SOC)中的各種裝置。這些裝置直接和CPU連接配接,通過總線尋址和中斷的方式,和CPU互動資訊。

我們會在後續的文章中,進一步分析這些特殊的bus,這裡就暫時不較長的描述了。

4.3 subsys interface

subsys interface抽象了bus下所有裝置的一些特定功能。代碼中是這樣注釋的:

/**
 * struct subsys_interface - interfaces to device functions
 * @name:       name of the device function
 * @subsys:     subsytem of the devices to attach to
 * @node:       the list of functions registered at the subsystem
 * @add_dev:    device hookup to device function handler
 * @remove_dev: device hookup to device function handler
 *
 * Simple interfaces attached to a subsystem. Multiple interfaces can
 * attach to a subsystem and its devices. Unlike drivers, they do not
 * exclusively claim or control devices. Interfaces usually represent
 * a specific functionality of a subsystem/class of devices.
 */     
struct subsys_interface {
        const char *name;
        struct bus_type *subsys;
        struct list_head node;
        int (*add_dev)(struct device *dev, struct subsys_interface *sif);
        int (*remove_dev)(struct device *dev, struct subsys_interface *sif);
};      
  • name:interface的名稱。
  • subsys:interface所屬的bus。
  • node:用于将interface挂到bus中。
  • add_dev/remove_dev:兩個回調函數,subsys interface的核心功能。當bus下有裝置增加或者删除的時候,bus core會調用它下面所有subsys interface的add_dev或者remove_dev回調。設計者可以在這兩個回調函數中實作所需功能,例如綁定該“specific functionality”所對應的driver,等等。

繼續閱讀