1. 程式人生 > >linux裝置模型八(bus)

linux裝置模型八(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,另外,還有一個sub system相關的結構,會一併說明。


 
/**
 * struct bus_type - The bus type of the device
 *
 * @name:	The name of the bus.
 * @dev_name:	Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:	Default device to use as the parent.
 * @dev_attrs:	Default attributes of the devices on the bus.
 * @bus_groups:	Default attributes of the bus.
 * @dev_groups:	Default attributes of the devices on the bus.
 * @drv_groups: Default attributes of the device drivers on the bus.
 * @match:	Called, perhaps multiple times, whenever a new device or driver
 *		is added for this bus. It should return a positive value if the
 *		given device can be handled by the given driver and zero
 *		otherwise. It may also return error code if determining that
 *		the driver supports the device is not possible. In case of
 *		-EPROBE_DEFER it will queue the device for deferred probing.
 * @uevent:	Called when a device is added, removed, or a few other things
 *		that generate uevents to add the environment variables.
 * @probe:	Called when a new device or driver add to this bus, and callback
 *		the specific driver's probe to initial the matched device.
 * @remove:	Called when a device removed from this bus.
 * @shutdown:	Called at shut-down time to quiesce the device.
 *
 * @online:	Called to put the device back online (after offlining it).
 * @offline:	Called to put the device offline for hot-removal. May fail.
 *
 * @suspend:	Called when a device on this bus wants to go to sleep mode.
 * @resume:	Called to bring a device on this bus out of sleep mode.
 * @pm:		Power management operations of this bus, callback the specific
 *		device driver's pm-ops.
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
 *              driver implementations to a bus and allow the driver to do
 *              bus-specific setup
 * @p:		The private data of the driver core, only the driver core can
 *		touch this.
 * @lock_key:	Lock class key for use by the lock validator
 *
 * A bus is a channel between the processor and one or more devices. For the
 * purposes of the device model, all devices are connected via a bus, even if
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.
 * A USB controller is usually a PCI device, for example. The device model
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the
 * default attributes, the bus' methods, PM operations, and the driver core's
 * private data.
 */
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;
 
	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 (*online)(struct device *dev);
	int (*offline)(struct device *dev);
 
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
 
	const struct dev_pm_ops *pm;
 
	const 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裝置模型五(device和device driver所講述的struct device結構中的init_name有關。對有些裝置而言(例如批量化的USB裝置),設計者根本就懶得為它起名字的,而核心也支援這種懶惰,允許將裝置的名字留空。這樣當設備註冊到核心後,裝置模型的核心邏輯就會用"bus->dev_name+device ID”的形式,為這樣的裝置生成一個名稱。用官方翻譯說:用於子系統列舉像(“foo%u”,dev-> id)這樣的裝置

dev_attr,總線上裝置的預設屬性

bus_groups、bus_groups、bus_groups,一些預設的attribute,可以在bus、device或者device_driver新增到核心時,自動為它們新增相應的attribute。(老一些版本的核心是叫device_attribute,driver_attribute,bus_attribute作用是一樣的)

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

match,一個由具體的bus driver實現的回撥函式。當任何屬於該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的過程相反。 
注1:並不是所有的bus都需要probe和remove介面的,因為對有些bus來說(例如platform bus),它本身就是一個虛擬的匯流排,無所謂初始化,直接就能使用,因此這些bus的driver就可以將這兩個回撥函式留空。

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

online:被呼叫以使裝置重新上線(在離線後)。
offline:被呼叫以使裝置離線以進行熱移除。可能會失敗。

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

iommu_ops,此匯流排的IOMMU特定操作,用於連線IOMMU驅動程式實現到匯流排並允許驅動程式執行匯流排專用設定。

p,一個struct subsys_private型別的指標,驅動核心的私有資料,只有驅動核心才可以觸控這個。後面我們會用一個小節說明。

2.2 struct subsys_private

該結構和device_driver中的struct driver_private類似,在前面的章節 "裝置模型驅動七(device_driver細節)"中有提到它,但沒有詳細說明。

要說明subsys_private的功能,讓我們先看一下該結構的定義:

/**
 * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
 *
 * @subsys - the struct kset that defines this subsystem
 * @devices_kset - the subsystem's 'devices' directory
 * @interfaces - list of subsystem interfaces associated
 * @mutex - protect the devices, and interfaces lists.
 *
 * @drivers_kset - the list of drivers associated
 * @klist_devices - the klist to iterate over the @devices_kset
 * @klist_drivers - the klist to iterate over the @drivers_kset
 * @bus_notifier - the bus notifier list for anything that cares about things
 *                 on this bus.
 * @bus - pointer back to the struct bus_type that this structure is associated
 *        with.
 *
 * @glue_dirs - "glue" directory to put in-between the parent device to
 *              avoid namespace conflicts
 * @class - pointer back to the struct class that this structure is associated
 *          with.
 *
 * This structure is the one that is the actual kobject allowing struct
 * bus_type/class to be statically allocated safely.  Nothing outside of the
 * driver core should ever touch these fields.
 */
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模組一樣)【事實上早期版本確實是命名為bus_type_private】。不過為什麼核心最終拋棄了呢呢?看看include/linux/device.h中的struct class結構(我們會在下一篇文章中介紹class)就知道了,因為class結構中也包含了一個一模一樣的struct subsys_private指標,看來class和bus很相似啊,所以在核心的subsys_private在現在最新版本就是這樣。

2.6.35.7   最開始bus和class的private是分開的,已經很相似了

struct class_private {
	struct kset class_subsys;
	struct klist class_devices;
	struct list_head class_interfaces;
	struct kset class_dirs;
	struct mutex class_mutex;
	struct class *class;
};
 
struct bus_type_private {
	struct kset subsys;
	struct kset *drivers_kset;
	struct kset *devices_kset;
	struct klist klist_devices;
	struct klist klist_drivers;
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;
};

3.xxx   bus有需求升級為subsys_private ,同時為後面去掉class_private 做基礎


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;
 };
 
struct class_private {
	struct kset class_subsys;
	struct klist class_devices;
	struct list_head class_interfaces;
	struct kset class_dirs;
	struct mutex class_mutex;
	struct class *class;
};

3.x後期,兩者完全統一用這個,class_private 在這個版本已經完全看不到了

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;         //
};

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


/**
 * bus_register - register a driver-core subsystem
 * @bus: bus to register
 *
 * Once we have that, we register the bus with the kobject
 * infrastructure, then register the children subsystems it has:
 * the devices and drivers that belong to the subsystem.
 */
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;    /* 繫結核心再初始化時就位所有的bus統一申請的kset */
	priv->subsys.kobj.ktype = &bus_ktype;   /* 繫結ktype */
	priv->drivers_autoprobe = 1;

	retval = kset_register(&priv->subsys);    /* 註冊kset,即在bus目錄下就出現了對應目錄 */
	if (retval)
		goto out;

	retval = bus_create_file(bus, &bus_attr_uevent);    /**  /
	if (retval)
		goto bus_uevent_fail;

    /* bus目錄下建立devices和drivers目錄,用來將來存放繫結該匯流排的驅動 */
	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);

    /* 匯流排的最重要的目的是匹配device和driver,用的就是probe機制 */
	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_groups_fail:
	remove_probe_files(bus);
bus_probe_files_fail:
	kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
	kset_unregister(bus->p->devices_kset);
bus_devices_fail:
	bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
	kset_unregister(&bus->p->subsys);
out:
	kfree(bus->p);
	bus->p = NULL;
	return retval;
}

匯流排最主要的功能是匹配繫結到它下面的device和driver,上面的其它都是建立sys檔案系統的關聯性的東西。

最後以一個小例子,來實現一條簡單匯流排。(該例子來自國嵌的老視訊,新核心有部分改動)

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");

static char *Version = "$Revision: 1.0 $";

static int my_match(struct device *dev, struct device_driver *driver)
{
    /* 比較裝置和驅動的名字 */
    return !strncmp(dev_name(dev), driver->name, strlen(driver->name));
}

struct bus_type my_bus_type = { 
    .name = "my_bus",
    .match = my_match,
};


EXPORT_SYMBOL(my_bus_type);

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}


static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);


static int __init my_bus_init(void)
{
    int ret;

        /*註冊匯流排*/
    ret = bus_register(&my_bus_type);
    if (ret)
        return ret;

    /*建立屬性檔案*/
    if (bus_create_file(&my_bus_type, &bus_attr_version))
        printk(KERN_NOTICE "Fail to create version attribute!\n");

    return ret;
}

static void my_bus_exit(void)
{
    bus_unregister(&my_bus_type);
}

module_init(my_bus_init);
module_exit(my_bus_exit);
                                      

上面繼續複習了前面的attribute屬性,以及基本的驅動模型的使用。匯流排的match函式,即比較裝置和驅動的函式名。

參考部落格