在linux设备驱动中,有一类设备被称为"平台设备",通常Soc系统中集成的独立外设单元都被当做平台设备处理。如I2C,SPI等都归纳为平台设备。
倒数第二行将父设备指向&platform_bus,表示SPI控制器是一个platform设备,可以在
/sys/devices/platform目录下找到带spi字样的文件夹。
最后一行将所属的总线指向&platform_bus_type;表示SPI控制器连接在platform总线上,
可以在/sys/bus/platform/devices目录下找到带spi字样的文件
、dev->class等,这些函数就会执行。
1.2.1 创建目录kobject_add()
上文提到kobject_add()会调用kobject_add_internal()创建目录,其实创建目录之前还会设置kobj->parent。
因为在1.1中初始化了kobj.kset,所以会执行下面的代码段
具体创建过程不再分析了,这里讲下uevent_attr这个变量。
可以按照下面的函数调用流程研究下(不一定正确,嘿嘿),
我正在用的开发板,就将SPI控制器作为平台设备进行注册,其注册时会调用platform_device_register()进行注册。
platform_device_register(struct platform_device *dev)
->platform_device_add(struct platform_device *pdev)
->pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
platform_bus和platform_bus_type是在platform初始化时建立的,分别表示platform设备和platform总线
倒数第二行将父设备指向&platform_bus,表示SPI控制器是一个platform设备,可以在
/sys/devices/platform目录下找到带spi字样的文件夹。
最后一行将所属的总线指向&platform_bus_type;表示SPI控制器连接在platform总线上,
可以在/sys/bus/platform/devices目录下找到带spi字样的文件
本文分析platform_bus和platform_bus_type的建立过程,也可以说成/sys/devices/platform和/sys/bus/platform目录的建立过程。
platform_bus和platform_bus_type是在platform初始化时建立的,platform初始化函数的调用流程如下:
start_kernel()
->rest_init()
->kernel_init()
->do_basic_setup()
->driver_init()
->platform_bus_init()
platform初始化函数包含两个方面:platform设备的初始化和platform总线的初始化
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
/*1.向系统注册platform设备--platform设备的初始化*/
error = device_register(&platform_bus);
if (error)
return error;
/*2.向系统注册platform总线--platform总线的初始化*/
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
1.注册platform设备
struct device platform_bus = {
.init_name = "platform",
};
device_register(&platform_bus);
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
1.1初始化platform设备
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
};
void device_initialize(struct device *dev)
{
/*父容器(父kset)指向devices_kset
*devices_kset在上篇文章中分析过,代表/sys/devices目录
*表示platform对象(kobj)属于/sys/devices这个对象集合(kset)
*/
dev->kobj.kset = devices_kset;
/*初始化本对象(kobj),并设置kobj->ktype*/
kobject_init(&dev->kobj, &device_ktype);
/*初始化其他元素*/
INIT_LIST_HEAD(&dev->dma_pools);
init_MUTEX(&dev->sem);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_init_wakeup(dev, 0);
device_pm_init(dev);
set_dev_node(dev, -1);
}
1.2向系统添加一个platform设备
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct class_interface *class_intf;
int error = -EINVAL;
/*增加kobj的引用计数*/
dev = get_device(dev);
if (!dev)
goto done;
/*初始化设备的私有数据,主要是klist_children的初始化,以后会用到*/
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()
*/
/*将dev->konj->name设置为dev->init_name
*将dev->init_name设置为NULL
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
if (!dev_name(dev))
goto name_error;
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
/*platform设备没有父设备,所以parent为NULL
*以后注册SPI主控制器设备时,其父设备就不为NULL,
*而是现在的platform设备
*/
parent = get_device(dev->parent);
setup_parent(dev, parent);
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
/*1.2.1该函数会调用kobject_add_internal()中的create_dir()函数,创建目录*/
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/*为空*/
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
/*1.2.2在目录下创建uevent文件*/
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
/*为空*/
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
/*为空*/
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
/*为空*/
error = device_add_attrs(dev);
if (error)
goto AttrsError;
/*为空*/
error = bus_add_device(dev);
if (error)
goto BusError;
/*和CONFIG_PM_XX有关,本例为空*/
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
/*和CONFIG_PM_XX有关,本例为空*/
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysf_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->class_mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->class_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->class_interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->class_mutex);
}
done:
/*减少引用计数*/
put_device(dev);
return error;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
该函数中,因为dev-bus、dev->class等都为空,所以很多子函数都不执行。当以后注册SPI设备时,会先设置dev->bus
、dev->class等,这些函数就会执行。
1.2.1 创建目录kobject_add()
上文提到kobject_add()会调用kobject_add_internal()创建目录,其实创建目录之前还会设置kobj->parent。
因为在1.1中初始化了kobj.kset,所以会执行下面的代码段
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
/*将kobj->entry添加都到父容器kset->list中*/
kobj_kset_join(kobj);
/*将父对象设置为父容器对应的对象*/
kobj->parent = parent;
}
这个过程会在本文最后的结构图中体现。
1.2.2创建文件device_create_file();
device_create_file(dev, &uevent_attr);
int device_create_file(struct device *dev, struct device_attribute *attr)
{
int error = 0;
if (dev)
error = sysfs_create_file(&dev->kobj, &attr->attr);
return error;
}
总线初始化那篇文章也遇到过sysfs_create_file()函数。该函数主要用来创建文件,
具体创建过程不再分析了,这里讲下uevent_attr这个变量。
static struct device_attribute uevent_attr =
__ATTR(uevent, S_IRUGO | S_IWUSR, show_uevent, store_uevent);
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
展开之后,static struct device_attribute uevent_attr =
{
.attr =
{
.name = "uevent",
.mode = S_IRUGO | S_IWUSR
},
.show = show_uevent,
.store = store_uevent,
}
其中name代表文件名,mode表示文件权限,show,store函数会在读写该文件时调用。
可以按照下面的函数调用流程研究下(不一定正确,嘿嘿),
inode->i_fop = &sysfs_file_operations;
-->sysfs_read_file()
-->fill_read_buffer()
-->ops->show()
inode->i_fop = &sysfs_file_operations;
-->sysfs_write_file()
-->flush_write_buffer()
-->ops->store()
至此,我们创建了platform_bus,并在/sys/devices下创建了platform目录
2.注册platform总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
bus_register(&platform_bus_type);
int bus_register(struct bus_type *bus)
{
int retval;
struct bus_type_private *priv;
/*分配总线私有数据*/
priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/*建立总线和私有数据之间的关联*/
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
/*设置kobj的name,subsys是kset类型*/
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
/*设置kobj的父容器,和1.1中paltform设备初始化时类似,
*bus_kset是在总线初始化时创建的,代表/sys/bus目录
*/
priv->subsys.kobj.kset = bus_kset;
/*设置kobj->ktype*/
priv->subsys.kobj.ktype = &bus_ktype;
/*drivers_autoprobe自动检测是否有匹配的驱动*/
priv->drivers_autoprobe = 1;
/*注册kset,会创建/sys/bus/platform目录
*因为设置了父容器kobj.kset,所以会将kobj->entry添加到
*kobj->kset->list链表中,并设置kobj->parent为父容器里的kobj
*/
retval = kset_register(&priv->subsys);
if (retval)
goto out;
/*创建uevent文件,可以在/sys/bus/platform中看到*/
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
/*创建devices目录,可以在/sys/bus/platform中看到,这个比较重要*/
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
/*创建drivers目录,可以在/sys/bus/platform中看到,这个比较重要*/
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
/*以下两个链表在以后匹配设备和匹配驱动时有重要作用*/
/*初始化设备链表,以后注册的SPI等platform设备都会被添加到该列表*/
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
/*初始化驱动链表,以后注册的SPI等platform驱动都会被添加到该列表*/
klist_init(&priv->klist_drivers, NULL, NULL);
/*创建几个和热拔插(CONFIG_HOTPLUG)有关的文件,本例中没用到*/
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
/*bus_attrs为NULL*/
retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_attrs_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);
kfree(bus->p);
out:
bus->p = NULL;
return retval;
}
至此,我们创建了platform_bus_type,并在/sys/bus下创建了platform目录。