在内核中可能存在不同种类的总线,需要将这些不同的内存存入内核中进行管理。这里,以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总线的注册过程中确实没有注册设备的相关操作。而总线注册的过程,两者都调用同一个注册函数,因此两者的执行过程大致相同。
关于两者之间的不同,可能与以下两点因素相关:
- platform是虚拟总线,pci并不是虚拟总线;
- platform没有与之对应的实际设备,pci存在与之对应的设备(即PCI控制器)。
综上,便是对总线的注册过程中的理解。