计算机接口总线

总线的本质作用是完成数据交换。总线用于将两个或两个以上的部件连接起来,使得它们之间可以进行数据交换,或者说通信。总线含义很广,它不仅仅是指用于数据交换的通道,有时也包含了软件硬件架构。比如 PCI 总线、USB 总线,它们不仅仅是指主板上的某个接口,还包含了与之相对应的整套硬件模型、软件架构。

1. 总线分类

按照总线在计算机系统中所处的物理位置,总线可以分为片上总线、内存总线、系统总线和设备总线。

按照数据传递的方向,总线可以分为单向总线和双向总线。单向总线是指数据只能从一端传递到另一端,而不能反向传递。单向总线也称为单工总线。双向总线是指数据可以在两个方向上传递,既可以从 A 端传递到 B 端,也可以从 B 端传递到 A 端。双向总线也称为双工总线。双工总线又可分为半双工总线和全双工总线。半双工总线是指在一个时间段内,数据只能从一个方向传送到另一个方向,数据不能同时在两个方向传递。全双工总线是指数据可以同时在两个方向传递。全双工总线包含两组数据线,分别用于两个方向的数据传输。

按照总线使用的信号类型,总线可以分为并行总线和串行总线。并行总线包含多位传输线,在同一时刻可以传输多位数据,而串行总线只使用一位传输线,同一时刻只传输一位数据。并行总线的优点在于相同频率下总线的带宽更大,但正因为采用了同一时刻并行传输多位数据的方法,必须保证多位数据在同一时刻到达。这样就会对总线的宽度和频率产生限制,同时也对主板设计提出了更高的要求。与并行总线相反,一般串行总线只使用一位传输线,同一时刻只能传输一位数据,而且使用编码的方式将时钟频率信息编码在传输的数据之中。因此,串行总线的传输频率可以大大提升。PCI 总线、DDR 总线等都是传统的并行总线,而 USB、SATA、PCIE 等都是串行总线。以串行总线传输方式为基础,使用多条串行总线进行数据传输的方式正在被广泛采用。以 PCIE 协议为例,PCIE 的接口规范中,可以使用 x1、x4、x8、x16 等不同宽度的接口,其中,x16 就是采用 16 对串行总线进行数据传输。多位串行总线与并行总线的根本差别在于,多位串行总线的每一个数据通道都是相对独立传输的,它们独立进行编解码,在接收端恢复数据之后再进行并行数据之间的对齐。而并行总线使用同一个时钟对所有的数据线进行同时采样,因此对数据传输线之间的对齐有非常严格的要求。

2. 片上总线

片上总线是指芯片片内互连使用的总线。芯片在设计时,常常要分为多个功能模块,这些功能模块之间的连接即采用片上互连总线。例如,一个高性能通用处理器在设计时,常常会划分为处理器核、共享高速缓存、内存控制器等多个模块,而一个 SoC(System on Chip,片上系统)芯片所包含的模块就更多了。下图是一个嵌入式 SoC 芯片的内部结构,可以看到里面包含了很多功能模块,这些模块之间的连接就需要用到片上互连总线。这些模块形成了 IP(Intellectual Property),一家公司在设计芯片时常常需要集成其他公司的 IP。这些 IP 的接口使用大家共同遵守的标准时,才能方便使用。因此,芯片的片上互连总线也形成了一定的标准。目前业界公开的主流片上互连总线是 ARM 公司的 AMBA 系列总线。
在这里插入图片描述

3. 内存总线

内存总线用于连接处理器和主存储器。

内存总线规范是由JEDEC(JointElectron Device Engineering Council)组织制定的,它包含了一般总线的三个层级:机械层、电气层和协议层。

在这里插入图片描述
这里只是简单分类介绍,具体的内存总线知识点可以自行搜索。

4. 系统总线

系统总线通常用于处理器与桥片的连接,同时也作为多处理器间的连接以构成多路系统。

英特尔处理器所广泛采用的 QPI(Quick Path Interconnect)接口及在 QPI 之前的 FSB(Front Side Bus),还有 AMD 处理器所广泛采用的 HT(HyperTransport)接口都属于系统总线。系统总线是处理器与其他芯片进行数据交换的主要通道,系统总线的数据传输能力对计算机整体性能影响很大。如果没有足够带宽的系统总线,计算机系统的外设访问速度会明显受限,类似于显示、存储、网络等设备的交互都会受到影响。随着计算机系统技术的不断进步,微处理器与其他芯片间的数据传输性能成为制约系统性能进一步提升的一个重要因素。为了提升片间传输性能,系统总线渐渐由并行总线发展为高速串行总线。

4.1 HyperTransport 总线

HyperTransport总线(简称HT总线)是AMD公司提出的一种高速系统总线,用于连接微处理器
与配套桥片,以及多个处理器之间的互连。HT 总线提出后,先后发展了 HT1.0、HT2.0、HT3.0 、HT3.1 等几代标准。下图是采用 HT 总线连接处理器与桥片的结构示意图。
在这里插入图片描述
与并行总线不同的是,串行总线通常采用点对点传输形式,体现在计算机体系结构上,就是一组串行总线只能连接两个芯片。以龙芯mips为例,在四路互连系统中,一共采用了7组HT互连总线,其中6组用于四个处理器间的全相联连接,1组用于处理器与桥片的连接,如图所示。
在这里插入图片描述
作为对比,PCI 总线则可以在同一组信号上连接多个不同的设备,如图6.20所示。
在这里插入图片描述

值得注意的是:HT总线的软件架构与PCI总线协议基本相同,都采用配置空间、IO空间和Memory空间的划分,通过对配置寄存器的设置,采用正向译码的方向对设备空间进行访问。基于 PCI 总线设计的设备驱动程序能够直接使用在 HT 总线的设备上。

但在电气特性及信号定义上,HT 总线与 PCI 总线却大相径庭,HT 由两组定义类似但方向不同的信号组成。其主要信号定义如表所示。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 设备总线

4.2.1. 通用驱动程序模型

尽管扩展设备通过设备驱动程序处理,而驱动程序与内核其余的代码通过一组固定的接口通信,因而扩展设备/驱动程序对核心的内核源代码没什么影响,但内核需要解决一个更基本的问题:设备如何通过总线附接到系统的其余部分。

在这里插入图片描述

与具体设备的驱动程序相比,总线驱动程序与核心内核代码的工作要密切得多。另外,总线驱动程序向相关的设备驱动程序提供功能和选项的方式,也不存在标准的接口。这是因为,不同的总线系统之间,使用的硬件技术可能差异很大。但这并不意味着,负责管理不同总线的代码没有共同点。相似的总线采用相似的概念,还引入了通用驱动程序模型,在一个主要数据结构的集合中管理所有系统总线,采用最小公分母的方式,尽可能降低不同总线驱动程序之间的差异。
现代总线系统在布局和结构的细节上可能有所不同,但也有许多共同之处,内核的数据结构即反映了这个事实。结构中的许多成员用于所有的总线(以及相关设备的数据结构中)。在内核版本2.6开发期间,一个通用驱动程序模型(设备模型,device model)并入内核,以防止不必要的复制。所有总线共有的属性封装到特殊的、可以用通用方法处理的数据结构中,再关联到总线相关的成员。

4.2.2 设备的表示

驱动程序模型采用一种特殊数据结构来表示几乎所有总线类型通用的设备属性。该结构直接嵌入到特定于总线的数据结构中,而不是通过指针引用,这与kobject相似。其定义如下:

/**
 * struct device - The basic device structure
 * @parent:	The device's "parent" device, the device to which it is attached.
 * 		In most cases, a parent device is some sort of bus or host
 * 		controller. If parent is NULL, the device, is a top-level device,
 * 		which is not usually what you want.
 * @p:		Holds the private data of the driver core portions of the device.
 * 		See the comment of the struct device_private for detail.
 * @kobj:	A top-level, abstract class from which other classes are derived.
 * @init_name:	Initial name of the device.
 * @type:	The type of device.
 * 		This identifies the device type and carries type-specific
 * 		information.
 * @mutex:	Mutex to synchronize calls to its driver.
 * @bus:	Type of bus device is on.
 * @driver:	Which driver has allocated this
 * @platform_data: Platform data specific to the device.
 * 		Example: For devices on custom boards, as typical of embedded
 * 		and SOC based hardware, Linux often uses platform_data to point
 * 		to board-specific structures describing devices and how they
 * 		are wired.  That can include what ports are available, chip
 * 		variants, which GPIO pins act in what additional roles, and so
 * 		on.  This shrinks the "Board Support Packages" (BSPs) and
 * 		minimizes board-specific #ifdefs in drivers.
 * @driver_data: Private pointer for driver specific info.
 * @links:	Links to suppliers and consumers of this device.
 * @power:	For device power management.
 *		See Documentation/driver-api/pm/devices.rst for details.
 * @pm_domain:	Provide callbacks that are executed during system suspend,
 * 		hibernation, system resume and during runtime PM transitions
 * 		along with subsystem-level and driver-level callbacks.
 * @pins:	For device pin management.
 *		See Documentation/driver-api/pinctl.rst for details.
 * @msi_list:	Hosts MSI descriptors
 * @msi_domain: The generic MSI domain this device is using.
 * @numa_node:	NUMA node this device is close to.
 * @dma_ops:    DMA mapping operations for this device.
 * @dma_mask:	Dma mask (if dma'ble device).
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
 * 		hardware supports 64-bit addresses for consistent allocations
 * 		such descriptors.
 * @bus_dma_mask: Mask of an upstream bridge or bus which imposes a smaller DMA
 *		limit than the device itself supports.
 * @dma_pfn_offset: offset of DMA memory range relatively of RAM
 * @dma_parms:	A low level driver may set these to teach IOMMU code about
 * 		segment limitations.
 * @dma_pools:	Dma pools (if dma'ble device).
 * @dma_mem:	Internal for coherent mem override.
 * @cma_area:	Contiguous memory area for dma allocations
 * @archdata:	For arch-specific additions.
 * @of_node:	Associated device tree node.
 * @fwnode:	Associated device node supplied by platform firmware.
 * @devt:	For creating the sysfs "dev".
 * @id:		device instance
 * @devres_lock: Spinlock to protect the resource of the device.
 * @devres_head: The resources list of the device.
 * @knode_class: The node used to add the device to the class list.
 * @class:	The class of the device.
 * @groups:	Optional attribute groups.
 * @release:	Callback to free the device after all references have
 * 		gone away. This should be set by the allocator of the
 * 		device (i.e. the bus driver that discovered the device).
 * @iommu_group: IOMMU group the device belongs to.
 * @iommu_fwspec: IOMMU-specific properties supplied by firmware.
 *
 * @offline_disabled: If set, the device is permanently online.
 * @offline:	Set after successful invocation of bus type's .offline().
 * @of_node_reused: Set if the device-tree node is shared with an ancestor
 *              device.
 *
 * At the lowest level, every device in a Linux system is represented by an
 * instance of struct device. The device structure contains the information
 * that the device model core needs to model the system. Most subsystems,
 * however, track additional information about the devices they host. As a
 * result, it is rare for devices to be represented by bare device structures;
 * instead, that structure, like kobject structures, is usually embedded within
 * a higher-level representation of the device.
 */
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	u64		bus_dma_mask;	/* upstream dma_mask constraint */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct iommu_fwspec	*iommu_fwspec;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
};

  • bus是一个指针,指向该设备所在总线(更多信息见下文)的数据结构的实例。
  • driver_data是驱动程序的私有成员,不能由通用代码修改。它可用于指向与设备协作必需、但又无法融入到通用方案的特定数据。platform_datafirmware_data 也是私有成员,可用于将特定于体系结构的数据和固件信息关联到设备。通用驱动程序模型也不会访问这些数据。

内核提供了标准函数device_register,用于将一个新设备添加到内核的数据结构。device_getdevice_put一对函数用于引用计数。

通用驱动程序模型也为设备驱动程序单独设计了一种数据结构。

/**
 * struct device_driver - The basic device driver structure
 * @name:	Name of the device driver.
 * @bus:	The bus which the device of this driver belongs to.
 * @owner:	The module owner.
 * @mod_name:	Used for built-in modules.
 * @suppress_bind_attrs: Disables bind/unbind via sysfs.
 * @probe_type:	Type of the probe (synchronous or asynchronous) to use.
 * @of_match_table: The open firmware table.
 * @acpi_match_table: The ACPI match table.
 * @probe:	Called to query the existence of a specific device,
 *		whether this driver can work with it, and bind the driver
 *		to a specific device.
 * @remove:	Called when the device is removed from the system to
 *		unbind a device from this driver.
 * @shutdown:	Called at shut-down time to quiesce the device.
 * @suspend:	Called to put the device to sleep mode. Usually to a
 *		low power state.
 * @resume:	Called to bring a device from sleep mode.
 * @groups:	Default attributes that get created by the driver core
 *		automatically.
 * @pm:		Power management operations of the device which matched
 *		this driver.
 * @coredump:	Called when sysfs entry is written to. The device driver
 *		is expected to call the dev_coredump API resulting in a
 *		uevent.
 * @p:		Driver core's private data, no one other than the driver
 *		core can touch this.
 *
 * The device driver-model tracks all of the drivers known to the system.
 * The main reason for this tracking is to enable the driver core to match
 * up drivers with new devices. Once drivers are known objects within the
 * system, however, a number of other things become possible. Device drivers
 * can export information and configuration variables that are independent
 * of any specific device.
 */
struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
	enum probe_type probe_type;

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	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 attribute_group **groups;

	const struct dev_pm_ops *pm;
	void (*coredump) (struct device *dev);

	struct driver_private *p;
};
  • name 指向一个正文串,用于唯一标识该驱动程序。
  • bus指向一个表示总线的对象,并提供特定于总线的操作
  • probe是一个函数,用于检测系统中是否存在能够用该设备驱动程序处理的设备。
  • 删除系统中的设备时会调用remove
  • shutdownsuspendresume 用于电源管理。
4.2.3 设备总线的表示

通用驱动程序模型不仅表示了设备,还用另一个数据结构表示了总线,定义如下:

/**
 * 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.
 * @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.
 * @num_vf:	Called to find out how many virtual functions a device on this
 *		bus supports.
 * @dma_configure:	Called to setup DMA configuration on a device on
 *			this bus.
 * @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
 * @need_parent_lock:	When probing or removing a device on this bus, the
 *			device core should lock the device's parent.
 *
 * 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;
	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);

	int (*num_vf)(struct device *dev);

	int (*dma_configure)(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;

	bool need_parent_lock;
};
  • name是总线的文本名称。特别地,它用于在sysfs文件系统中标识该总线。
  • uevent用于通知总线新设备已经添加到系统。
  • match指向一个函数,试图查找与给定设备匹配的驱动程序,这个在设备驱动和设备匹配时候调用。
  • 在有必要将驱动程序关联到设备时,会调用probe。该函数检测设备在系统中是否真正存在。
  • remove删除驱动程序和设备之间的关联。例如,在将可热插拔的设备从系统中移除时,会调用该函数。
  • shutdownsuspendresume函数用于电源管理。
4.2.4. 设备总线注册过程

在这里插入图片描述
上图说明了总线通过两个数据结构:devices_ketdriver_kset来管理注册在此总线上的所有的设备和驱动,为了方便遍历,linux增加了klist_devicesklist_drivers用来实现设备和驱动的遍历

4.2.4.1 注册总线 bus_register

在可以注册设备及其驱动程序之前,需要有总线。因此我们从bus_register开始,该函数向系统添加一个新总线。首先,通过嵌入的kset类型成员subsys,将新总线添加到总线子系统:

/**
 * 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.
 */
 /**
    任何总线注册都会经过这个函数。
    调用成功后,会在/sys/bus/下面生成一个目录
    目录下: 
            一个设备的目录,包含挂接在该总线上的所有设备
            一个驱动目录,包含挂接再该总线上的所有驱动
            一些总线属性文件
*/
int bus_register(struct bus_type *bus)
{
	int retval;
	struct subsys_private *priv;
	struct lock_class_key *key = &bus->lock_key;

	/*申请一个bus_type_private结构体priv,priv指针和bus指针互指*/
	priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);//分配subsys_private结构体
	if (!priv)
		return -ENOMEM;

	priv->bus = bus;//设置bus指针
	bus->p = priv;//设置bus->p指针

	BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);//初始化bus通知链
	/*总线需要了解相关设备及其驱动程序的所有有关信息,因此总线对二者注册了kset。
	两个kset分别是drivers和devices,都将总线作为父结点*/
	/*priv里面有一个kset类型的subsys,给subsys赋值,然后注册这个kset*/
	retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
	if (retval)
		goto out;

	priv->subsys.kobj.kset = bus_kset;//设置该bus所属的kset, 以及ktype
	priv->subsys.kobj.ktype = &bus_ktype;
	priv->drivers_autoprobe = 1;/*设置driver和device自动匹配*/

	retval = kset_register(&priv->subsys);//注册该kset到系统中,表现在/sys/bus下
	if (retval)
		goto out;

	/*在/sys/bus/platform/目录下新建uenvet文件*/
	retval = bus_create_file(bus, &bus_attr_uevent);//创建该bus的uevent属性
	if (retval)
		goto bus_uevent_fail;

	/*创建一个devices_kset,新建/sys/bus/platform/devices目录*/
	priv->devices_kset = kset_create_and_add("devices", NULL,
						 &priv->subsys.kobj);//在该bus下创建devices目录
	if (!priv->devices_kset) {
		retval = -ENOMEM;
		goto bus_devices_fail;
	}

	/*创建一个drivers_kset,新建/sys/bus/platform/drivers目录*/
	priv->drivers_kset = kset_create_and_add("drivers", NULL,
						 &priv->subsys.kobj);/在该bus下创建drivers目录
	if (!priv->drivers_kset) {
		retval = -ENOMEM;
		goto bus_drivers_fail;
	}

	INIT_LIST_HEAD(&priv->interfaces); //初始化interface, mutex, klist_devices, klist_drivers
	__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);

	/*在/sys/bus/platform/目录下新建了drivers_probe和drivers_autoprobe文件*/
	retval = add_probe_files(bus);//创建bus的probe属性
	if (retval)
		goto bus_probe_files_fail;

	retval = bus_add_groups(bus, bus->bus_groups);//创建bus的属性
	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;
}
EXPORT_SYMBOL_GPL(bus_register);

bus_register的工作就是:

  • 完成bus_type_private的初始化.创建 注册的这条总线需要的目录文件.
  • 在这条总线目录下创建/device /driver 目录。
  • 初始化这条总线上的设备链表:struct klist klist_devices;
  • 初始化这条总线上的驱动链表:struct klist klist_drivers;

比如常见的通过bus_register创建一个i2c总线,就会在/sys/bus/下创建i2c目录, 以及devices目录,driver目录, 以及probe属性, uevent属性。

uos@uos-PC:/sys/bus/i2c$ ls -al
drwxr-xr-x  4 root root     0 619 19:42 .
drwxr-xr-x 29 root root     0 619 19:42 ..
drwxr-xr-x  2 root root     0 619 19:42 devices
drwxr-xr-x  4 root root     0 619 19:42 drivers
-rw-r--r--  1 root root 16384 625 19:16 drivers_autoprobe
--w-------  1 root root 16384 625 19:16 drivers_probe
--w-------  1 root root 16384 625 19:16 uevent

而在devices下就是该i2c-bus下所有的设备,drivers下就是i2c-bus下所有的驱动。

uos@uos-PC:/sys/bus/i2c$ ls -al devices
drwxr-xr-x 2 root root 0 625 19:17 .
drwxr-xr-x 4 root root 0 625 19:17 ..
lrwxrwxrwx 1 root root 0 619 12:07 i2c-0 -> ../../../devices/platform/LOON0004:00/i2c-0
lrwxrwxrwx 1 root root 0 619 12:07 i2c-1 -> ../../../devices/platform/LOON0004:01/i2c-1
lrwxrwxrwx 1 root root 0 619 11:42 i2c-10 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-10
lrwxrwxrwx 1 root root 0 619 11:42 i2c-11 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-11
lrwxrwxrwx 1 root root 0 619 11:42 i2c-12 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-12
lrwxrwxrwx 1 root root 0 619 11:42 i2c-13 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-13
lrwxrwxrwx 1 root root 0 619 12:07 i2c-2 -> ../../../devices/platform/LOON0004:02/i2c-2
lrwxrwxrwx 1 root root 0 619 12:07 i2c-3 -> ../../../devices/platform/LOON0004:03/i2c-3
lrwxrwxrwx 1 root root 0 619 12:07 i2c-4 -> ../../../devices/platform/LOON0004:04/i2c-4
lrwxrwxrwx 1 root root 0 619 12:07 i2c-5 -> ../../../devices/platform/LOON0004:05/i2c-5
lrwxrwxrwx 1 root root 0 619 11:42 i2c-6 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-6
lrwxrwxrwx 1 root root 0 619 11:42 i2c-7 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-7
lrwxrwxrwx 1 root root 0 619 11:42 i2c-8 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-8
lrwxrwxrwx 1 root root 0 619 11:42 i2c-9 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0/i2c-9
uos@uos-PC:/sys/bus/i2c$ ls -al drivers
drwxr-xr-x 4 root root 0 625 19:17 .
drwxr-xr-x 4 root root 0 625 19:17 ..
drwxr-xr-x 2 root root 0 619 19:42 dummy
drwxr-xr-x 2 root root 0 619 19:42 pca953x
4.2.4.2 注册设备 device_register

注册设备包括两个独立的步骤,如图所示。具体是:初始化设备的数据结构,并将其加入到数据结构的网络中。
在这里插入图片描述

/**
 * device_register - register a device with the system.
 * @dev: pointer to the device structure
 *
 * This happens in two clean steps - initialize the device
 * and add it to the system. The two steps can be called
 * separately, but this is the easiest and most common.
 * I.e. you should only call the two helpers separately if
 * have a clearly defined need to use and refcount the device
 * before it is added to the hierarchy.
 *
 * For more information, see the kerneldoc for device_initialize()
 * and device_add().
 *
 * NOTE: _Never_ directly free @dev after calling this function, even
 * if it returned an error! Always use put_device() to give up the
 * reference initialized in this function instead.
 */
int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);
/**
 * device_initialize - init device structure.
 * @dev: device.
 *
 * This prepares the device for use by other layers by initializing
 * its fields.
 * It is the first half of device_register(), if called by
 * that function, though it can also be called separately, so one
 * may use @dev's fields. In particular, get_device()/put_device()
 * may be used for reference counting of @dev after calling this
 * function.
 *
 * All fields in @dev must be initialized by the caller to 0, except
 * for those explicitly set to some other value.  The simplest
 * approach is to use kzalloc() to allocate the structure containing
 * @dev.
 *
 * NOTE: Use put_device() to give up your reference instead of freeing
 * @dev directly once you have called this function.
 */
void device_initialize(struct device *dev)
{
	dev->kobj.kset = devices_kset;
	kobject_init(&dev->kobj, &device_ktype);
	INIT_LIST_HEAD(&dev->dma_pools);
	mutex_init(&dev->mutex);
	lockdep_set_novalidate_class(&dev->mutex);
	spin_lock_init(&dev->devres_lock);
	INIT_LIST_HEAD(&dev->devres_head);
	device_pm_init(dev);
	set_dev_node(dev, -1);
#ifdef CONFIG_GENERIC_MSI_IRQ
	INIT_LIST_HEAD(&dev->msi_list);
#endif
	INIT_LIST_HEAD(&dev->links.consumers);
	INIT_LIST_HEAD(&dev->links.suppliers);
	dev->links.status = DL_DEV_NO_DRIVER;
}
EXPORT_SYMBOL_GPL(device_initialize);
/**
 * device_add - add device to device hierarchy.
 * @dev: device.
 *
 * This is part 2 of device_register(), though may be called
 * separately _iff_ device_initialize() has been called separately.
 *
 * This adds @dev to the kobject hierarchy via kobject_add(), adds it
 * to the global and sibling lists for the device, then
 * adds it to the other relevant subsystems of the driver model.
 *
 * Do not call this routine or device_register() more than once for
 * any device structure.  The driver model core is not designed to work
 * with devices that get unregistered and then spring back to life.
 * (Among other things, it's very hard to guarantee that all references
 * to the previous incarnation of @dev have been dropped.)  Allocate
 * and register a fresh new struct device instead.
 *
 * NOTE: _Never_ directly free @dev after calling this function, even
 * if it returned an error! Always use put_device() to give up your
 * reference instead.
 */
int device_add(struct device *dev)
{
	struct device *parent;
	struct kobject *kobj;
	struct class_interface *class_intf;
	int error = -EINVAL;
	struct kobject *glue_dir = NULL;

	dev = get_device(dev);
	if (!dev)
		goto done;

	if (!dev->p) {
		error = device_private_init(dev);
		if (error)
			goto done;
	}

	/*
	 * for statically allocated devices, which should all be converted
	 * some day, we need to initialize the name. We prevent reading back
	 * the name, and force the use of dev_name()
	 */
	if (dev->init_name) {
		dev_set_name(dev, "%s", dev->init_name);
		dev->init_name = NULL;
	}

	/* subsystems can specify simple device enumeration */
	if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
		dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

	if (!dev_name(dev)) {
		error = -EINVAL;
		goto name_error;
	}

	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

	/*通过device->parent指定的父子关系转变为一般的
	内核对象层次结构:*/
	parent = get_device(dev->parent);
	kobj = get_device_parent(dev, parent);
	if (IS_ERR(kobj)) {
		error = PTR_ERR(kobj);
		goto parent_error;
	}
	if (kobj)
		dev->kobj.parent = kobj;

	/* use parent numa_node */
	if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
		set_dev_node(dev, dev_to_node(parent));

	/*在设备子系统中注册该设备只需要调用一次kobject_add,因为在device_initialize
	中已经将该设备设置为子系统的成员了。*/
	/* first, register with generic layer. */
	/* we require the name to be set before, and pass NULL */
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error) {
		glue_dir = get_glue_dir(dev);
		goto Error;
	}

	/* notify platform of device entry */
	if (platform_notify)
		platform_notify(dev);

	error = device_create_file(dev, &dev_attr_uevent);
	if (error)
		goto attrError;

	error = device_add_class_symlinks(dev);
	if (error)
		goto SymlinkError;
	error = device_add_attrs(dev);
	if (error)
		goto AttrsError;
	/*调用bus_add_device在sysfs中添加两个链接:一个在总线目录下指向设备,另一个在设备
	的目录下指向总线子系统。 bus_attach_device试图自动探测设备。如果能够找到适当的驱动程序,
	则将设备添加到bus->klist_devices。设备还需要添加到父结点的子结点链表中(此前,设备知道
	其父结点,但父结点不知道该子结点的存在)。*/
	error = bus_add_device(dev);
	if (error)
		goto BusError;
	error = dpm_sysfs_add(dev);
	if (error)
		goto DPMError;
	device_pm_add(dev);

	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &dev_attr_dev);
		if (error)
			goto DevAttrError;

		error = device_create_sys_dev_entry(dev);
		if (error)
			goto SysEntryError;

		devtmpfs_create_node(dev);
	}

	/* Notify clients of device addition.  This call must come
	 * after dpm_sysfs_add() and before kobject_uevent().
	 */
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_ADD_DEVICE, dev);

	kobject_uevent(&dev->kobj, KOBJ_ADD);
	bus_probe_device(dev);
	if (parent)
		klist_add_tail(&dev->p->knode_parent,
			       &parent->p->klist_children);

	if (dev->class) {
		mutex_lock(&dev->class->p->mutex);
		/* tie the class to the device */
		klist_add_tail(&dev->knode_class,
			       &dev->class->p->klist_devices);

		/* notify any interfaces that the device is here */
		list_for_each_entry(class_intf,
				    &dev->class->p->interfaces, node)
			if (class_intf->add_dev)
				class_intf->add_dev(dev, class_intf);
		mutex_unlock(&dev->class->p->mutex);
	}
done:
	put_device(dev);
	return error;
 SysEntryError:
	if (MAJOR(dev->devt))
		device_remove_file(dev, &dev_attr_dev);
 DevAttrError:
	device_pm_remove(dev);
	dpm_sysfs_remove(dev);
 DPMError:
	bus_remove_device(dev);
 BusError:
	device_remove_attrs(dev);
 AttrsError:
	device_remove_class_symlinks(dev);
 SymlinkError:
	device_remove_file(dev, &dev_attr_uevent);
 attrError:
	kobject_uevent(&dev->kobj, KOBJ_REMOVE);
	glue_dir = get_glue_dir(dev);
	kobject_del(&dev->kobj);
 Error:
	cleanup_glue_dir(dev, glue_dir);
parent_error:
	put_device(parent);
name_error:
	kfree(dev->p);
	dev->p = NULL;
	goto done;
}
EXPORT_SYMBOL_GPL(device_add);
4.2.4.3 设备驱动注册:driver_register

驱动程序使用内核的标准函数driver_register注册到系统中

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	if (!drv->bus->p) {
		pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
			   drv->name, drv->bus->name);
		return -EINVAL;
	}
	
	//检测总线的操作函数和驱动的操作函数是否同时存在
	//三种可能都会被考虑进行注册的动作,
	 //drv的总线probe和drv的probe都已定义
	 //drv的总线remove和drv的remove都已经定义
	 //drv的总线shutdown和drv的shutdown都已经进行定义
	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);
			
	//去已经注册的driver链表上查看,该driver是否已经被注册,防止重复注册同一个driver
	other = driver_find(drv->name, drv->bus);//这里查找drv->name="hub"驱动是否已经在drv->bus总线上注册,并增加引用计数,若已经注册,则返回提示信息
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	/把这个driver添加到该总线维护的driver链表中
	ret = bus_add_driver(drv);//如果之前没注册,就在此注册
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}
EXPORT_SYMBOL_GPL(driver_register);
4.2.4.4 注册设备驱动程序: bus_add_driver

在进行一些检查和初始化工作之后,driver_register调用bus_add_driver将一个新驱动程序添加到一个总线。同样,驱动程序首先要有名字,然后注册到通用数据结构的框架中:

/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
	struct bus_type *bus;
	struct driver_private *priv;
	int error = 0;

	//用于增加该bus所属的顶层bus的kobject的引用计数,返回的是其所属的顶层bus的指针。
	bus = bus_get(drv->bus);
	if (!bus)
		return -EINVAL;

	pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);//分配一个驱动私有数据driver_private
	if (!priv) {
		error = -ENOMEM;
		goto out_put_bus;
	}
	klist_init(&priv->klist_devices, NULL, NULL);//初始化设备链表
	//以下相互绑定
	priv->driver = drv;//私有驱动数据绑定drv
	drv->p = priv;//drv->p驱动绑定priv

	//指向顶层的bus的p->drivers_kse
	//设置私有数据的父容器,在这一步中,设置了kset为platform下的drivers_kset结构,也就是drivers那个目录
	priv->kobj.kset = bus->p->drivers_kset;//将priv->kobj.kset当前内核集合指向bus->p->drivers_kset内核集合(该集合在usb总线注册bus_register()描述)
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	if (error)
		goto out_unregister;

	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);//将当前注册的priv->knode_bus节点加入到全局bus->p->klist_drivers链表中
	/*如果总线支持自动探测,则调用driver_attach。该函数迭代总线上的所有设备,使用驱动程序
	的match函数进行检测,确定是否有某些设备可使用该驱动程序管理。最后,将该驱动程序添加到总
	线上注册的所有驱动程序的链表中。*/
	if (drv->bus->p->drivers_autoprobe) {//在我们注册总线bus_register()中,将值置为1//系统则会调用下面的driver_attach函数进行驱动与设备的匹配
		if (driver_allows_async_probing(drv)) {//在usb总线注册时对该变量drivers_autoprobe初始化为1了
			pr_debug("bus: '%s': probing driver %s asynchronously\n",
				drv->bus->name, drv->name);
			async_schedule(driver_attach_async, drv);
		} else {
			//调用driver_attach匹配该总线上的device,看到这里肯定会有点熟悉,因为device去匹配driver 的时候也是调用了device_attach这个函数去匹配
			error = driver_attach(drv);//见下面,按照内核启动的流程分析,这个时候usb总线上只有hub这个驱动,没有设备,所以driver_attach函数将返回失败
			if (error)
				goto out_unregister;
		}
	}
	module_add_driver(drv->owner, drv);//见下面,从该函数这开始,本次hub驱动将不会执行下面的函数了,不过为了分析函数内部的功能,我们继续,因为在其他驱动模块注册时会用到

	error = driver_create_file(drv, &driver_attr_uevent);
	if (error) {
		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
			__func__, drv->name);
	}
	error = driver_add_groups(drv, bus->drv_groups);
	if (error) {
		/* How the hell do we get out of this pickle? Give up */
		printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
			__func__, drv->name);
	}

	if (!drv->suppress_bind_attrs) {
		error = add_bind_files(drv);
		if (error) {
			/* Ditto */
			printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
				__func__, drv->name);
		}
	}

	return 0;

out_unregister:
	kobject_put(&priv->kobj);
	/* drv->p is freed in driver_release()  */
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

refer to:

  1. linux内核源码分析之设备驱动(platform)
  2. Linux驱动之bus_register分析
  3. 《计算机体系结构基础》
  4. Linux设备驱动模型-Bus
  5. Linux 设备总线驱动模型
  6. linux设备驱动(4)bus详解
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值