天天看點

Linux裝置模型(五) Bus

參考文章:http://www.wowotech.net/linux_kenrel/bus.html

目錄

        • 1. 前言
        • 2. Bus 基礎知識
          • 2.1. struct bus_type 介紹
          • 2.2. struct subsys_private 介紹
          • 2.3. Bus 功能總結
          • 2.4. struct bus_type 和 Kobject 的關系
        • 3. 内部執行邏輯分析
          • 3.1. bus的注冊
          • 3.2. device和device_driver的添加
        • 4. 裝置驅動 probe 的時機
          • 4.1. 先 match 後 probe
          • 4.2. Bus match 回調函數執行個體: i2c_device_match
        • 5. 說明
          • 5.1. system/virtual/platform bus
          • 5.2. subsys interface

1. 前言

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

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

2. Bus 基礎知識

按照老傳統,描述功能前,先介紹一下該子產品的一些核心資料結構,對bus子產品而言,核心資料結構就是struct bus_type,另外,還有一個sub system相關的結構,會一并說明。

源碼版本:Kernel 3.10

源碼路徑:

         include/linux/device.h

         drivers/base/base.h

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,該名稱和"Linux裝置模型(5)_device和device driver”所講述的struct device結構中的init_name有關。對有些裝置而言(例如批量化的USB裝置),設計者根本就懶得為它起名字的,而核心也支援這種懶惰,允許将裝置的名字留白。這樣當裝置注冊到核心後,裝置模型的核心邏輯就會用"bus->dev_name+device ID”的形式,為這樣的裝置生成一個名稱。

bus_attrs、dev_attrs、drv_attrs,一些預設的attribute,可以在bus、device或者device_driver添加到核心時,自動為它們添加相應的attribute。

dev_root,根據核心的注釋,dev_root裝置為bus的預設父裝置(Default device to use as the parent),但在核心實際實作中,隻和一個叫sub system的功能有關,随後會介紹。

match,一個由具體的bus driver實作的回調函數。match回調函數至關重要,後面會詳細介紹。

注1:當任何屬于該Bus的device或者device_driver添加到核心時,核心都會調用該接口,如果新加的device或device_driver比對上了自己的另一半的話,該接口要傳回非零值,此時Bus子產品的核心邏輯就會執行後續的處理。

uevent,一個由具體的bus driver實作的回調函數。當任何屬于該Bus的device,發生添加、移除或者其它動作時,Bus子產品的核心邏輯就會調用該接口,以便bus driver能夠修改環境變量。

probe、remove,這兩個回調函數,和device_driver中的非常類似,但它們的存在是非常有意義的。可以想象一下,如果需要probe(其實就是初始化)指定的device話,需要保證該device所在的bus是被初始化過、確定能正确工作的。這就要就在執行device_driver的probe前,先執行它的bus的probe。remove的過程相反。

注2:并不是所有的bus都需要probe和remove接口的,因為對有些bus來說(例如platform bus),它本身就是一個虛拟的總線,無所謂初始化,直接就能使用,是以這些bus的driver就可以将這兩個回調函數留白。

shutdown、suspend、resume,和probe、remove的原理類似,電源管理相關的實作,暫不說明。

pm,電源管理相關的邏輯,暫不說明。

iommu_ops,暫不說明。

p,一個struct subsys_private類型的指針,後面我們會用一個小節說明。

2.2. struct subsys_private 介紹

該結構和device_driver中的struct driver_private類似,在"Linux裝置模型(四)_device和device driver”章節中有提到它,但沒有詳細說明。

要說明subsys_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啊,看的暈暈的!不過還是試着先了解一下為什麼起名為subsys吧:

按理說,這個結構就是集合了一些bus子產品需要使用的私有資料,例如kset啦、klist啦等等,命名為bus_private會好點(就像device_driver子產品一樣)。不過為什麼核心沒這麼做呢?看看include/linux/device.h中的struct class結構(我們會在下一篇文章中介紹class)就知道了,因為class結構中也包含了一個一模一樣的struct subsys_private指針,看來class和bus很相似啊。

想到這裡,就好了解了,無論是bus,還是class,還是我們會在後面看到的一些虛拟的子系統,它都構成了一個“子系統(sub-system)”,該子系統會包含形形色色的device或device_driver,就像一個獨立的王國一樣,存在于核心中。而這些子系統的表現形式,就是/sys/bus(或/sys/class,或其它)目錄下面的子目錄,每一個子目錄,都是一個子系統(如/sys/bus/spi/)。

好了,我們回過頭來看一下struct subsys_private中各個字段的解釋:

subsys、devices_kset、drivers_kset,是三個kset,由"Linux裝置模型(2)_Kobject”中對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。有關interface的概念後面會詳細介紹。

klist_devices、klist_drivers,是兩個連結清單,分别儲存了本bus下所有的device和device_driver的指針,以友善查找。

drivers_autoprobe,用于控制該bus下的drivers或者device是否自動probe,"Linux裝置模型(5)_device和device driver”中有提到。

bus、class,分别儲存上層的bus或者class指針。

2.3. Bus 功能總結

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

  • bus的注冊和登出
  • 本bus下有device或者device_driver注冊到核心時的處理
  • 本bus下有device或者device_driver從核心登出時的處理 device_drivers的probe處理
  • 管理bus下的所有device和device_driver
2.4. struct bus_type 和 Kobject 的關系

按照慣例,我們再提一下Linux裝置模型的基本資料結構 Kobject。

在Linux中,Kobject幾乎不會單獨存在。它的主要功能,就是内嵌在一個大型的資料結構中,為這個資料結構提供一些底層的功能實作。

現在來看一下struct bus_type、struct device、struct device_driver和Kobject的關系。這三個資料結構均包含了Kobject,通過Kobject在sysfs檔案系統中組成樹狀層次結構,如下圖示例。

本圖是對Linux裝置模型(二) 基本資料結構 Kobject中4.2小節圖的擴充,但是省略了細節部分,讀者可結合該圖加深了解。

Linux裝置模型(五) Bus

注1:什麼是Platform Bus?

在計算機中有這樣一類裝置,它們通過各自的裝置控制器,直接和CPU連接配接,CPU可以通過正常的尋址操作通路它們(或者說通路它們的控制器)。這種連接配接方式,并不屬于傳統意義上的總線連接配接。但裝置模型應該具備普适性,是以Linux就虛構了一條Platform Bus,供這些裝置挂靠。

注2:區分I2C控制器、I2C裝置。(SPI也是類似的,要區分SPI控制器和SPI裝置)

對于Linux Kernel而言,I2C控制器、I2C裝置都是裝置,都連接配接到了bus上。I2C控制器是連接配接到Platform Bus的。而 I2C Bus Device是連接配接到I2C Bus的。而我們通常寫的裝置驅動,是連接配接到 I2C Bus 上的 I2C Bus Device 驅動。讀者可看"Linux裝置模型(一) 概覽"裡的圖3.1嵌入式硬體拓撲圖。其實,I2C控制器就是通常說的I2C master,而I2C裝置就是通常說的I2C slave。後面會有專門的文章介紹Linux I2C子系統。

3. 内部執行邏輯分析

3.1. bus的注冊

bus的注冊是由bus_register接口實作的,該接口的原型是在include/linux/device.h中聲明的,并在drivers/base/bus.c中實作,其原型如下:

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

該功能的執行邏輯如下:

  1. 為bus_type中struct subsys_private類型的指針配置設定空間,并更新priv->bus和bus->p兩個指針為正确的值;
  2. 初始化priv->subsys.kobj的name、kset、ktype等字段,啟動name就是該bus的name(它會展現在sysfs中),kset和ktype由bus子產品實作,分别為bus_kset和bus_ktype;
  3. 調用kset_register将priv->subsys注冊到核心中,該接口同時會向sysfs中添加對應的目錄(如/sys/bus/spi);
  4. 調用bus_create_file向bus目錄下添加一個uevent attribute(如/sys/bus/spi/uevent);
  5. 調用kset_create_and_add分别向核心添加devices和drivers kset,同時會展現在sysfs中;
  6. 初始化priv指針中的mutex、klist_devices和klist_drivers等變量;
  7. 調用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;
  8. 調用bus_add_attrs,添加由bus_attrs指針定義的bus的預設attribute,這些attributes最終會展現在/sys/bus/xxx目錄下。
3.2. device和device_driver的添加

我們有在"Linux裝置模型(四)_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的處理邏輯:

  1. 調用内部的device_add_attrs接口,将由bus->dev_attrs指針定義的預設attribute添加到核心中,它們會展現在/sys/devices/xxx/xxx_device/目錄中;
  2. 調用sysfs_create_link接口,在該device所在bus目錄下,建立一個指向該device所在sysfs目錄的連結。例如:
    Linux裝置模型(五) Bus
    其中/sys/devices/…/spi0.1,為該device在sysfs中真正的位置,而為了友善管理,核心在該裝置所在的bus的xxx_bus/devices目錄中,建立了一個符号連結。
  3. 調用sysfs_create_link接口,在該device的sysfs目錄中(如/sys/devices/platform/mt-battery/),建立一個指向該device所在bus目錄的連結,取名為subsystem,例如:
    Linux裝置模型(五) Bus
  4. 最後,毫無疑問,要把該device指針儲存在bus->priv->klist_devices中。

bus_add_driver的處理邏輯:

  1. 為該driver的struct driver_private指針(priv)配置設定空間,并初始化其中的priv->klist_devices、priv->driver、priv->kobj.kset等變量,同時将該指針儲存在device_driver的p處。
  2. 将driver的kset(priv->kobj.kset)設定為bus的drivers kset(bus->p->drivers_kset),這就意味着所有driver的kobject都位于bus->p->drivers_kset之下(寄/sys/bus/xxx/drivers目錄下)
  3. 以driver的名字為參數,調用kobject_init_and_add接口,在sysfs中注冊driver的kobject,展現在/sys/bus/xxx/drivers/目錄下,如/sys/bus/spi/drivers/spidev。
  4. 将該driver儲存在bus的klist_drivers連結清單中,并根據drivers_autoprobe的值,選擇是否調用driver_attach進行probe。
  5. 調用driver_create_file接口,在sysfs的該driver的目錄下,建立uevent attribute。
  6. 調用driver_add_attrs接口,在sysfs的該driver的目錄下,建立由bus->drv_attrs指針定義的預設attribute。
  7. 同時根據suppress_bind_attrs标志,決定是否在sysfs的該driver的目錄下,建立bind和unbind attribute。

4. 裝置驅動 probe 的時機

我們在"Linux裝置模型(四)_device和device driver”中,我們已經介紹過driver的probe時機及過程,其中大部分的邏輯會依賴bus子產品的實作,主要為device_attach和driver_attach接口。同樣,這兩個接口都是在include/linux/device.h中聲明,在drivers/base/bb.c中實作。

Linux裝置模型(五) Bus

device_attach和driver_attach這兩個接口的行為類似,邏輯也很簡單,最終都是調用 driver_match_device() 和 driver_probe_device(), 既:搜尋所在的bus,比對是否有同名的device_driver(或device),如果有并且該裝置沒有綁定Driver(注:這一點很重要,通過它,可以使同一個Driver,驅動相同名稱的多個裝置,後續在Platform裝置的描述中會提及)則調用device_driver的probe接口。可以看出,無論先将device注冊到核心中,還是先将device_driver注冊到核心中,在match條件滿足時,最終都會觸發驅動的probe回調函數。

4.1. 先 match 後 probe
// drivers/base/dd.c, line 410
static int __device_attach(struct device_driver *drv, void *data)
{
	struct device *dev = data;
	if (!driver_match_device(drv, dev))
		return 0;
	return driver_probe_device(drv, dev);
}
           
// drivers/base/dd.c, line 461
static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;
	if (!driver_match_device(drv, dev))
		return 0;
    ......
	driver_probe_device(drv, dev);
    ......
	return 0;
}
           

先看一下driver_match_device()函數。該函數非常簡單,如果該device_driver所在的bus(struct device_driver 包含struct bus_type)有提供match回調函數,則調用該match回調函數進行device和device_driver的比對,否則傳回1。

// drivers/base/base.h, line 116
static inline int driver_match_device(struct device_driver *drv, struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
           

在driver_probe_device()中調用了really_probe,在really_probe()中,先判斷目前bus總線中是否注冊probe()函數如果有注冊,就調用總線probe函數,否則,就調用目前drv注冊的probe()函數。

// drivers/base/dd.c, line 393
/**
 * driver_probe_device - attempt to bind device & driver together
 * @drv: driver to bind a device to
 * @dev: device to try to bind to the driver
 */
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    ......
	if (!device_is_registered(dev))
		return -ENODEV;
    ......
	ret = really_probe(dev, drv);
    ......
}
// drivers/base/dd.c, line 279
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;
	}
    ......
}
           
4.2. Bus match 回調函數執行個體: i2c_device_match
// drivers/i2c/i2c-core.c, line 442
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,
};
           
// drivers/i2c/i2c-core.c, line 72
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 (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_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)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}
           

i2c_device_match就是i2c device_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進行比對,這是裝置樹機制産生之前的主要配對方式。

其他bus的match回調函數基本和i2c match類似,這裡不再贅述。關于上述幾種match機制,後面有專門的文章介紹。

5. 說明

5.1. system/virtual/platform bus

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

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

virtaul bus是一個比較新的bus,主要用來抽象那些虛拟裝置,所謂的虛拟裝置,是指不是真實的硬體裝置,而是用軟體模拟出來的裝置,例如虛拟機中使用的虛拟的網絡裝置。

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

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

5.2. subsys interface

subsys interface是一個很奇怪的東西,我們再後面的文章再詳細讨論。

繼續閱讀