总线注册(platform,PCI)

在内核中可能存在不同种类的总线,需要将这些不同的内存存入内核中进行管理。这里,以platform总线和pci总线为例来进行说明。

platform总线是一种虚拟总线,当某一设备与处理器直接连接时,可以利用该虚拟总线来模拟设备通过platform总线连接到处理器这一结构。

内核中,关于platform总线的注册位于drivers/base/platform.c文件中。

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();
	//该函数的实现与CPU架构相关,当前分析的架构中其实现为空
	
	error = device_register(&platform_bus);
	//注册总线设备,关于总线设备的定义如下:
	//struct device platform_bus = {
	//	.init_name = "platform",
	//};
	
	if (error) {
		put_device(&platform_bus);
		return error;
	}
	error = bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	of_platform_register_reconfig_notifier();
	return error;
}

关于设备的注册,如下:

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
//可见,设备注册主要完成两个任务,分别是:初始化设备,添加设备

void device_initialize(struct device *dev)
{
	dev->kobj.kset = device_kset;	//设置该设备所属的集合
	kobject_init(&dev->kobj, &device_ktype);	//设置该设备所属类型
	//上边的操作与内核所提出的设备模型机制相关,利用该机制,内核可对所有的设备进行管理
	INIT_LIST_HEAD(&dev->dma_pools);
	mutex_init(&dev->mutex);
#ifdef CONFIG_PROVE_LOCKING
	mutex_init(&dev->lockdep_mutex);
#endif
	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);	//设置该设备关联的NUMA节点,此时设置为-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);
	INIT_LIST_HEAD(&dev->links.needs_suppliers);
	INIT_LIST_HEAD(&dev->links.defer_sync);
	dev->links.status = DL_DEV_NO_DRIVER;
}

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);
	//该函数的主要目的为对dev->kref->refcount变量进行自增
	if (!dev)
		goto done;

	if (!dev->p) {
		error = device_private_init(dev);
		//该函数主要用来初始设备的私有数据
		if (error)
			goto done;
	}
	
	if (dev->init_name) {
		dev_set_name(dev, "%s", dev->init_name);
		//设置dev->kobj->name为dev->init_name
		dev->init_name = NULL;
	}

	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__);

	parent = get_device(dev->parent);	//获取当前设备的父节点设备
	kobj = get_device_parent(dev, parent);	//获取父节点设备的kobject对象
	if (IS_ERR(kobj)) {
		error = PTR_ERR(kobj);
		goto parent_error;
	}
	if (kobj)
		dev->kobj.parent = kobj;	//如果父节点设备的kobject对象存在,则设置当前设备的kobject对象的父节点
	
	if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
		set_dev_node(dev, dev_to_node(parent));	//如果设备的父节点存在,且当前设备未设置NUMA节点,则将当前设备的NUMA节点设置父节点的NUMA节点。

	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error) {
		glue_dir = get_glue_dir(dev);
		goto Error;
	}

	error = device_platform_notify(dev, KOBJ_ADD);
	if (error)
		goto platform_error;
	
	error = device_create_file(dev, &dev_attr_uevent);
	//根据属性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;
		
	error = bus_add_device(dev);
	//向总线中添加设备,如果当前设备为总线,则该函数不执行,直接返回0
	if (error)
		goto BusError;
	...
	bus_probe_device(dev);
	//如果当前设备是实际存在的设备时,会检测该设备所挂载的总线上是否使能了设备驱动自动注册,如果已经使能,则调用device_initial_probe函数来间接的调用__device_attach函数来为该设备关联已存在的设备驱动
	...
}	

当总线按照上述过程,以设备的形式注册完成之后,接下来则注册实际的总线对象。

int bus_register(struct bus_type *bus)
{
	//这里以platform总线为例,因此传参对象为platform_bus_type,定义如下:
	//struct bus_type platform_bus_type = {
	//	.name = "platform",
	//	.dev_groups = platform_dev_groups,
	//	.match = platform_match,
	//	.uevent = plaform_uevent,
	//	.dma_configure = platform_dma_configure,
	//	.pm	 = &platform_dev_pm_ops,
	//};

	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);
	...
	retval = bus_create_file(bus, &bus_attr_uevent);
	...
	priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
	...
	priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
	...
	klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
	klist_init(&priv->klist_drivers, NULL, NULL);
	...
	retval = add_probe_files(bus);
	...
	retval = bus_add_groups(bus, bus->bus_groups);
}

通过对上边代码的分析,可知,总线注册的过程中并没有实质性的与设备相关的操作,而主要是以内核所提出的设备模型概念为主来创建相关的总线文件。

关于PCI总线,其实现形式与platform总线不同:
第一,platform总线的初始化是在内核初始化的前期,即start_kernel->arch_call_rest_init->rest_init->kernel_init->kernel_init_freeable->driver_init->platform_bus_init。而PCI总线的初始化则是通过驱动形式来完成初始化。
第二,在初始化platform总线时,需要特别将总线抽象成设备,并注册该设备,随后注册总线。而PCI总线则不需要,只需要和前者一样,注册总线即可。

PCI总线注册原型如下:

static int __init pci_driver_init(void)
{
	int ret;

	ret = bus_register(&pci_bus_type);
	//关于pci_bus_type,其原型定义如下:
	//struct bus_type pci_bus_type {
	//	.name = "pci",
	//	.match = pci_bus_match,
	//	.uevent = pci_uevent,
	//	.probe = pci_device_probe,
	//	...
	//};
	if (ret)
		return ret;

#ifdef CONFIG_PCIEPORTBUS
	ret = bus_register(&pcie_port_bus_type);
	if (ret)
		return ret;
#endif 
	dma_debug_add_bus(&pci_bus_type);
	return 0;
}

从上边的代码可以看到,在PCI总线的注册过程中确实没有注册设备的相关操作。而总线注册的过程,两者都调用同一个注册函数,因此两者的执行过程大致相同。

关于两者之间的不同,可能与以下两点因素相关:

  1. platform是虚拟总线,pci并不是虚拟总线;
  2. platform没有与之对应的实际设备,pci存在与之对应的设备(即PCI控制器)。

综上,便是对总线的注册过程中的理解。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值