引言
写本文的念头起源于做 spi 实验时看到了一个目录
bash
/sys/devices/platform/soc@3000000/4026000.spi/spi_master/spi1/spi1.0/spidev/spidev1.0
这么长的结构实在想看一下怎么组织起来的,探究的过程中发现其中涉及许多 /sys 下的目录,故就着资料整理出了本文
食用指南
关于本文框架
本文不以传统的 bus device driver 铁三角为线索进行组织分析,而是逐个分析 /sys 下的各个目录,因此阅读时可以暂时忘记 bus device driver 的关系,如 probe 和总线的 match 等概念,可能会减少理解难度
本文将以 sys -> sys/bus -> sys/class -> sys/devices -> 具体 spi 例子为线索梳理
关于示意图
本文中的图,全都不是笔者自己画的(实是网络上各位大牛画的图太清晰了,如有侵权我光速处理),是搬运自参考文档里的,函数名字是按着较早内核版本来的,但意思是可以表述清楚的
关于 attribute 和 链接
在 /sys 下有许多 attribute 文件,不会做过多说明,重点聚焦在 bus device driver class 这四个重点概念和目录的创建,目录的创建时机和 attribute 的生成时机基本一致
在 /sys 下有许多链接结构,链接在 sys/bus sys/class sys/devices 中互相穿插,导致介绍前面的内容时会涉及到后面的内容,可以用以下两点帮助记忆
- 所有 driver 和 device(通常是 dev_name) 互相的链接都是在 probe 阶段创建的(本文关注较少)
- 所有 涉及到的其他链接 都是 device_add (drivers/base/core.c) 这个函数搞出来的,看到链接或者感觉有些复杂的地方基本都找它就行
关于 kobject
阅读需要 kobject kset 前置知识,可以看下面的图或关注文中的附图,也可以搞清楚其中关系
前置知识可以参考:Linux 内核:设备驱动模型(1)sysfs 与 kobject 基类
帮助理解但不完全准确的说法:sysfs 下 kobject 就对应着目录, kset 也是一个 kobject,故 kset 也对应一个目录,是通过他含有的 kobject 结构对应的
关于开头问题的答案
跟着阅读顺序读下来会更容易理解结论,结论可以直接跳转到 SPI 相关 章节
本文对总线的分析以 platform 为例,对驱动模型的分析以 spi 为例
/SYS
要想探究 /sys/devices/...... 那么一大串目录,先探究一下最上层的目录 /sys/devices 是怎么来的
start_kernel() -> rest_init() ->kernel_init() -> do_basic_setup() -> driver_init() 代码路径:linux-5.4\init\main.c
下面的函数 driver_init 涉及到许多目录如 /sys/bus /sys/class /sys/devices /sys/bus/platform 的创建
scss
// drivers/base/init.c
void __init driver_init(void) {
/* These are the core pieces */
devtmpfs_init();
/* 详见 sys/devices */
devices_init();
/* 详见 sys/bus */
buses_init();
/* 详见 sys/class */
classes_init();
firmware_init();
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
/* 详见 sys/bus/platform */
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
}
/SYS/BUS
第一部分先看一下 /sys/bus ,在设备模型接触最多的三个概念 bus devices driver 在这个目录下都有对应,bus 目录本身就可以描述大多数设备的拓扑结构。
buses_init 创建了 /sys/bus /sys/devices/system
arduino
// drivers/base/bus.c
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
return 0;
}
下面以具体 platform 总线为例介绍 bus driver device 三者的关系。先看一下 /sys/bus/platform 是怎么创建的吧
/sys/bus/platform
图为,/sys/bus/platform 文件夹下文件内容
platform 总线的注册是由 platform_bus_init 函数完成的。该函数在内核启动阶段被调用,我们来简单看下调用过程:
start_kernel() -> rest_init() ->kernel_init() -> do_basic_setup() -> driver_init() -> platform_bus_init()
scss
// drivers/base/platform.c
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
// 创建了 /sys/bus/platform 见后文 /sys/devices
error = device_register(&platform_bus);
if (error)
return error;
// bus 初始化 ------------------------------------------------------------------------------------------------------------
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
device_register 在 /sys/devices 章着重分析,目前只需关注 bus_register
ini
// drivers/base/bus.c
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;
// priv 和 bus 绑定
// bus 的 priv 属性代表了 bus 的 kobject,kset 等
// priv 的 subsys 结构即 kobject 相关
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
// bus->name = platform 以上图为例即修改文件夹名字为 platform
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
// 经上述绑定和此函数后 /sys/bus/platform 创建
retval = kset_register(&priv->subsys);
// 之后均在 /sys/bus/platform 下创建
// 创建 uevent 文件
retval = bus_create_file(bus, &bus_attr_uevent);
// 创建 devices 文件夹
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
// 创建 drivers文件夹
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj)
// 创建 drivers_autoprobe 和 drivers_probe
retval = add_probe_files(bus);
// platform 下没有 bus_groups
retval = bus_add_groups(bus, bus->bus_groups);
}
bus_register 小结
经过 bus_register() 函数注册之后,一种新类型 bus 在 sysfs 中的层次结构为:
图片来自:www.cnblogs.com/pwl999/p/15... 画的太好了
bus_register 调用后完成了图片中目录的创建,bus bus/devices bus/drivers 结构已然创建,但是目前后两个目录下都是空的,接下来关注 /sys/bus/xxx/drivers 和 /sys/bus/xxx/devices 目录下的文件是如何创建的
内核提供了 device_register 和 driver_register 两个接口,供各个 device 和 driver 使用,来在 sysfs 中创建目录结构。而这两个接口的逻辑之中包含,bus 模块的 bus_add_device 和 bus_add_driver ,让我们暂时摒弃 register 中其他的操作,重点关注 bus 模块下创建 device 和 driver 的部分。下面我们看看这两个接口的处理逻辑
bus 下的 drivers
/sys/bus/platform/drivers
device_driver 是依附于总线的,他并没有独立的根目录,与之对应的是 device ,每个 device 都会在 /sys/devices 下有特定的根目录空间。
因此 driver_register() 会注册驱动对象到 /sys/bus/bus_name/drivers/ 目录。而不像 device_register() 在 /sys/bus/bus_name/devices/ 目录下创建的都是符号链接。
进入 drivers 文件夹下,我们只关注 spi 相关的信息,发现有 drivers/spi 目录,进入 spi 目录,目录结构如图所示
/sys/drivers 目录下的 attribute 创建过程如下
rust
// drivers/base/bus.c
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
// 具体结构体见:d1-h\lichee\linux-5.4\drivers\base\base.h
struct driver_private *priv;
bus = bus_get(drv->bus);
klist_init(&priv->klist_devices, NULL, NULL);
// 将该指针保存在 device_driver 的p处
priv->driver = drv;
drv->p = priv;
// 将 driver 的 kset(priv->kobj.kset)
// 设置为 bus 的 drivers kset(bus->p->drivers_kset),
// 这就意味着所有 driver 的 kobject 都位于
// bus->p->drivers_kset 之下
//(即:/sys/bus/xxx/drivers目录下)
priv->kobj.kset = bus->p->drivers_kset;
// 调用 kobject_init_and_add 接口,在 sysfs 中注册 driver 的 kobject,
// 体现在 /sys/bus/xxx/drivers/ 目录下,如 /sys/bus/spi/drivers/spidev
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
// 将该 driver 保存在 bus 的 klist_drivers 链表中,
// 并根据 drivers_autoprobe 的值,选择是否调用 driver_attach 进行 probe
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
}
module_add_driver(drv->owner, drv);
// 调用 driver_create_file 接口,在 sysfs 的该 driver 的目录下,
// 创建 uevent attribute
error = driver_create_file(drv, &driver_attr_uevent);
// 调用 driver_add_attrs 接口,在 sysfs 的该 driver 的目录下,
// 创建由 bus->drv_attrs 指针定义的默认 attribute
error = driver_add_groups(drv, bus->drv_groups);
// 同时根据 suppress_bind_attrs 标志,决定是否在 sysfs 的该 driver 的目录下,
// 创建 bind 和 unbind attribute
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
}
return 0;
}
platform_driver 的注册过程小结
经过 driver_register()函数注册以后,一种新类型 driver_register 在 sysfs 中的层次结构为:
再回顾一下 drivers/spi 的目录结构
bus 下的 devices
/sys/bus 下的 devices 都是链接,入下图所示,bus_add_device 的过程就是在创建链接
/sys/bus/platform/devices
/sys/bus/platform/devices 目录下的文件链接创建过程如下
rust
// 调用过程 device_register -> device_add -> bus_add_device
// 具体见 /sys/devices
int bus_add_device(struct device *dev)
{
struct bus_type *bus = bus_get(dev->bus);
int error = 0;
if (bus) {
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
// 调用内部的device_add_attrs接口,将由bus->dev_attrs指针定义的默认attribute添加到内核中,
// 它们会体现在/sys/devices/xxx/xxx_device/目录中
error = device_add_groups(dev, bus->dev_groups);
// 在sys/bus/总线类型/devices/目录下,创建名字为dev_name的符号链接,指向sys/devices/相同设备名字
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
// 在sys/devices/分类/设备名字/目录下创建目录名字为subsystem的符号链接,
// 并且指向在sys/bus/总线类型&dev->bus->p->subsys.kobj, "subsystem"
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
// 设备指针保存在bus->priv->klist_devices中
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;
}
以 spi 为例,其使用 sysfs_create_link() 函数完成在 sys/bus/spi/devices/ 目录下,创建 spi0.0 的符号链接,指向 sys/devices 相同设备名字
bus_add_device 的处理逻辑:
- 调用 sysfs_create_link 接口,将该 device 在 sysfs 中的目录,链接到该 bus 的 devices 目录下,例如:
bash
xxx# ls /sys/bus/spi/devices/spi1.0 -l
lrwxrwxrwx root root 2014-04-11 10:46 spi0.0 ->
../../../devices/platform/s3c64xx-spi.1/spi_master/spi1/spi1.0
其中/sys/devices/.../sp01.0,为该 device 在 sysfs 中真正的位置,
而为了方便管理,内核在该设备所在的 bus 的 xxx_bus/devices 目录中,创建了一个符号链接。
调用 sysfs_create_link 接口,在该设备的 sysfs 目录中(如/sys/devices/platform/alarm/)中,创建一个指向该设备所在 bus 目录的链接,取名为 subsystem,例如:
bash
xxx # ls /sys/devices/platform/alarm/subsystem -l
lrwxrwxrwx root root 2014-04-11 10:28 subsystem -> ../../../bus/platform
device_register 调用的 device_add 调用的 bus_add_device ,目前先大概了解下目录结构,在 /sys/devices 章节中再看 device_register 的具体代码
经过 device_register() 函数注册以后,一种新类型 device 在 sysfs 中的层次结构为:
经过 device_register 后,创建的目录和链接关系如下:
probe 中的 sys 结构变化
rust
// drivers/base/dd.c
static int really_probe(struct device *dev, struct device_driver *drv)
{
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
...
}
// drivers/base/dd.c
static int driver_sysfs_add(struct device *dev)
{
/* 在/sys/bus/XXX/drivers/XXX目录下建立symlink
,链接名为kobj->name
链接指向/sys/devices/platform/XXX */
ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
kobject_name(&dev->kobj));
/* 在/sys/devices/platform/XXX/下建立symlink,链接名为driver,
指向/sys/bus/xxx/drivers目录下的某个目录*/
ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
"driver");
}
以 platform 总线下的 spi 为例,创建了如下两个链接,即在 device 的实际位置和 driver 的 sysfs 下位置创建了互相的链接,在文件目录下可以方便找到对应的 device/driver
/SYS/CLASS
class 抽象层面介绍
class 的概念搜了一些资料后还是没能弄的很清,归咎其原因应该在于,classs 有很多提供给用户侧的接口,但是目前没有很多对 class 的实践机会。延续本文主线,本章节不总结 class 承担的抽象概念,仅关注一个问题:这个 /sys/class 下的目录是怎么出现、什么时候出现的。
附上资料中抽象出的 class 概念
struct device 和 struct device_driver 这两个数据结构,其中 struct device 结构会包含一个 struct class 指针(这从侧面说明了 class 是 device 的集合,甚至,class 可以是 device 的 driver)。当某个 class driver 向内核注册了一个 class 后,需要使用该 class 的 device,通过把自身的 class 指针指向该 class 即可,剩下的事情,就由内核在注册 device 时处理了。
参考文章的表述非常抽象...,对目录的探究只需要知道 device 结构会有 class 指针,device 的 class 指针和 parent 指针会影响 device 的创建就行
/sys/class
bus 和 class 有相似的设计背景,他们都持有 subsys 结构,subsys 含有 kobject 和其下面的设备列表、驱动列表,说明其在抽象层面有类似的功能
貌似一个 device 不会同时具有 class 和 bus ,否则在 device_add 创建链接的阶段会报错,这个也说明了二者有很深的联系,下文的 /sys/devices 章节 对 device_add 的讲解会进一步说明
bus 没有内含 kobject 结构,其 kobject 用 bus->p->subsys.kobject 获取,(class 获取 kobject 的方式基本类似,也通过 struct subsys_private *p 数据结构)
/sys/class 目录的初始化如下
arduino
//drivers/base/class.c
int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL);
if (!class_kset)
return -ENOMEM;
return 0;
}
/sys/class/tty
可以以 uart 下的结构看一个例子理解 class 概念,/sys/class/tty 目录的初始化如下
ini
// drivers/tty/tty_io.c
static int __init tty_class_init(void)
{
tty_class = class_create(THIS_MODULE, "tty");
if (IS_ERR(tty_class))
return PTR_ERR(tty_class);
tty_class->devnode = tty_devnode;
return 0;
}
// drivers/base/class.c
// 调用关系 class_create -> class_register -> __class_register
int __class_register(struct class *cls, struct lock_class_key *key)
{
struct subsys_private *cp;
cp->subsys.kobj.kset = class_kset;
error = kset_register(&cp->subsys);
}
经过 class_create ()函数注册以后,一种新类型 class 在 sysfs 中的层次结构为:
uart 的 tty
bash
讲到 Class 的部分可以关注一下主线任务即 /sys/devices/platform/soc@3000000/4026000.spi/spi_master/spi1/spi1.0/spidev/spidev1.0
先给出该目录的名字含义在代码中的解释,之后再揭晓他们的本质
目录名:4026000.spi /spi_master /spi1 /spi1.0 /spidev /spidev1.0
代码中:platform_device /类名 /spi_master /spi_device /类名 /device
这个目录结构涉及 device 创建中的 subsystem class bus 概念,先以较简单的 uart 为例
以 uart 为例,/sys/class/tty 目录下,如果使能了 uart 串口 x 则会出现 ttySx 链接如以下形式:
ttyS0 -> ../../devices/platform/soc@3000000/2500000.uart/tty/ttyS0
回到 /sys/devices/platform/soc@3000000/2500000.uart 目录下观察 subsystem 可以看到存在两个 subsystem
本目录下 subsystem 指向 bus
subsystem -> ../../../../bus/platform
tty/ttyS1 目录下指向 class
subsystem -> ../../../../../../class/tty
前者的 subsystem 指向在 paltform, 通过该指向可以知道该设备是在 platform_device 相关操作即设备树解析时创建(调用 device_add )的,该目录是 devices 侧负责创建的。
后者 /tty/ttyS1/subsystem 指向 class/tty
tty_driver 和 device 在 uart 中的创建可以参考以下链接 www.cnblogs.com/easynote/p/...
/tty/ttyS1 目录创建流程,由 uart_register_driver 函数在 sunxi_uart_init 函数中执行,是驱动侧在 init 时进行的 device_add 操作
2500000.uart/tty/ttyS0 就是 device/class/device 的模式
两个 subsystem 指向不一样地方,这个模式的具体说明见后文 /sys/devices 章节
spi
/sys/class 下和 spi 有关的类目录如下,spi_master 和 spidev 文件夹的创建是 class_create 相关,与具体设备的创建无关
/sys/class/spi_master 和 /sys/class/spidev 下的目录创建与具体设备的创建有关,归根结底是 device_add 做的,涉及函数 device_add_class_symlinks ,前文说过会把 device_add_class_symlinks 和 bus_add_device 对比讲解,具体讲解放在 支线任务 -- bus 和 class
/sys/class/spi_master 目录结构如下
/sys/class/spidev 目录结构如下
device 的注册最终是由 device_add 接口(drivers/base/core.c)实现了,该接口中和 class 有关的动作包括:
- 调用 device_add_class_symlinks 接口,创建各种符号链接,即:在对应 class 的目录下,创建指向 device 的符号链接;在 device 的目录下,创建名称为 subsystem、指向对应 class 目录的符号链接
- 调用 device_add_attrs,添加由 class 指定的 attributes(class->dev_attrs)
rust
// drivers/base/core.c
static int device_add_class_symlinks(struct device *dev)
{
struct device_node *of_node = dev_of_node(dev);
int error;
if (of_node) {
error = sysfs_create_link(&dev->kobj, of_node_kobj(of_node), "of_node");
if (error)
dev_warn(dev, "Error %d creating of_node link\n",error);
/* An error here doesn't warrant bringing down the device */
}
error = sysfs_create_link(&dev->kobj,
&dev->class->p->subsys.kobj,
"subsystem");
if (dev->parent && device_is_not_partition(dev)) {
error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
}
/* class 中指向 device 位置的链接 */
error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev));
return 0;
}
/SYS/DEVICES
终于到了前面出现了很多次的 /sys/devices 章节,扯点题外话,笔者自己看网络上大多讲 bus driver device 的文章都会有点割裂感,bus -> driver 部分还好,driver 大多时候就在 bus 目录下,紧密围绕 bus 去创建和 probe 设备。bus -> device 就避免不了讲 device 的创建,就又要关注 /sys/devices 和 /sys/class 才能把 device 创建讲清楚。device 就是创建在 devices,/sys/bus 下全是链接,而且还不是所有设备都在 /sys/bus ,/sys/class 下也有设备的链接,可能这就是割裂感产生的原因
感觉用 /sys 目录做区分就可以单独把 device 的创建拎出来,层次分的清楚、也可以看的更深一点
按照惯例先看一下根目录的创建,为了探究 /sys/devices/platform 下的目录需要关注两个目录的创建 /sys/devices 和 /sys/devices/platform
/sys/devices 目录的创建见下面代码,kset_create_and_add 和 kobject_create_and_add 都创建了 kobj, 区别在前者对 kset 专属的数据结构进行了初始化。
根目录
ini
// drivers/base/core.c
int __init devices_init(void)
{
// 创建 sys/devices
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
// 创建 sys/dev
dev_kobj = kobject_create_and_add("dev", NULL);
// 创建 sys/dev/block
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
// 创建 sys/dev/char
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
}
小节 /sys/bus/platform 出现了这段代码,介绍了 bus_register, 在当前目录下需要关注 device_register ,第 8 行 device_register 创建了 /sys/bus/platform 目录
scss
// drivers/base/platform.c
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
// /sys/devices/platform
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
明确一点,刚才这段代码是 /sys/bus/platform 的创建,因为总线也属于一个设备,所以有 device_register 的部分,我们需要关注的是,/sys/devices/platform 下的 device_register,不过其实应该是 /sys/devices/platform/soc...../xxxxx 下的 device_register ,因为设备树解析阶段会把目录创建到这层了
设备树如何把节点解析成 platform_device 可以看下设备树相关博客
/sys/devices/platform
现在关注一下 device_register ,device_register 调用了 device_add 为什么只加粗了 device_add,因为 device_register 着实有点短
device_register 的代码只有两行,但是非常关键,之后所有持有 device 的结构的初始化,包括 spi_master, spi_device,都会用各种方式调用这两个函数 device_initialize device_add。有必要重点关注下这两个函数
scss
// drivers/base/core.c
int device_register(struct device *dev)
{
device_initialize(dev); /*初始化dev的某些字段*/
return device_add(dev); /*将设备添加到系统中*/
}
device_initialize
scss
// drivers/base/core.c
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; /*设置kobj属于哪个kset,/sys/devices/*/
kobject_init(&dev->kobj, &device_ktype);/*初始化dev->kobj*/
// 其他初始化
}
本文只关注目录结构,即 kobject 。代码中可以看到,所有 device 的 kset 都是 /sys/devices 这个目录持有的 kset,kset 是创建目录时找不到创建位置时的默认值,说明 device 于什么位置创建和其 kset 关系不大,重点要看其对 parent 的处理,在 device_add 中可以看到
终于可以看到 device_add 了,device_add 中和目录有关的代码段如下
ini
// drivers/base/core.c
int device_add(struct device *dev)
{
// 寻找父目录
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (kobj)
dev->kobj.parent = kobj;
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
/*在XXX下建立文件uevent*/
error = device_create_file(dev, &uevent_attr);
/* 在sys/dev/char/下建立symlink,名字为主设备号:次设备号,该链接指向XXX */
error = device_create_sys_dev_entry(dev);
error = device_create_file(dev, &dev_attr_uevent);
// 这里创建了 class 相关的链接
error = device_add_class_symlinks(dev);
error = device_add_attrs(dev);
// 这里创建了 bus 相关的链接,并且与之前 class 创建的有点关系
error = bus_add_device(dev);
error = dpm_sysfs_add(dev);
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);
}
}
}
主线任务 -- 父目录获取
紧跟主线,device_add 代码 5 行获取 dev->parent 第 6 行根据 dev->parent dev->parent->class dev->class 决定父目录并返回到 kobj 中
get_device_parent() 的逻辑:如果 dev->class 为空,表示随父设备,有 parent 则返回 parent->kobj,没有则如果 dev->bus->dev_root 不为空,即 bus 提供了存放位置,则放到这里,否则返回 NULL。如果有 dev->class 呢,情况就比较复杂了,也许 device 有着与 parent 不同的 class,也许 device 还没有一个 parent,等等。
如果 parent 不为空,而且存在 parent->class,则还放在 parent 目录下。不然,要么 parent 不存在,要么 parent 没有 class,很难直接将有 class 的 device 放在 parent 下面。目前的解决方法很简单,在 parent 与 device 之间,再加一层表示 class 的目录,class->p->glue_dirs 就是专门存放这种中间 kobject 的 kset,在第一次创建出 glue_dir 后,之后再有相同 class 的 device 创建的话就可以沿用之前的 glue_dirs 了。如果 parent 都没有,那就把 /sys/devices/virtual 当做 parent。
关于为什么要有中间的以 class_name 为 glue_dirs 来组织结构,以 spi 为例,platform_device/spi_master/spi1 和 platform_device/spi_slave/spi1 都有可能出现,此法可以避免命名空间冲突,同时可以在 sys 目录下展现得更加直观
红色字就是主线任务的关键线索,代码如下,这个函数比较关键就把所有代码都放上来了
rust
// drivers/base/core.c
static struct kobject *get_device_parent(struct device *dev,
struct device *parent)
{
if (dev->class) {
struct kobject *kobj = NULL;
struct kobject *parent_kobj;
struct kobject *k;
#ifdef CONFIG_BLOCK
/* block disks show up in /sys/block */
if (sysfs_deprecated && dev->class == &block_class) {
if (parent && parent->class == &block_class)
return &parent->kobj;
return &block_class.p->subsys.kobj;
}
#endif
/*
* If we have no parent, we live in "virtual".
* Class-devices with a non class-device as parent, live
* in a "glue" directory to prevent namespace collisions.
*/
if (parent == NULL)
parent_kobj = virtual_device_parent(dev);
else if (parent->class && !dev->class->ns_type)
return &parent->kobj;
else
parent_kobj = &parent->kobj;
mutex_lock(&gdp_mutex);
/* find our class-directory at the parent and reference it */
spin_lock(&dev->class->p->glue_dirs.list_lock);
list_for_each_entry(k, &dev->class->p->glue_dirs.list, entry)
if (k->parent == parent_kobj) {
kobj = kobject_get(k);
break;
}
spin_unlock(&dev->class->p->glue_dirs.list_lock);
if (kobj) {
mutex_unlock(&gdp_mutex);
return kobj;
}
/* or create a new class-directory at the parent device */
k = class_dir_create_and_add(dev->class, parent_kobj);
/* do not emit an uevent for this simple "glue" directory */
mutex_unlock(&gdp_mutex);
return k;
}
/* subsystems can specify a default root directory for their devices */
if (!parent && dev->bus && dev->bus->dev_root)
return &dev->bus->dev_root->kobj;
if (parent)
return &parent->kobj;
return NULL;
}
其实主线任务的答案已经可以给出,就是两个 /device/class/device 结构
bash
目录名:4026000.spi /spi_master /spi1 /spi1.0 /spidev /spidev1.0
本质: device /class /device /device /class /device
代码中:platform_device /类名 /spi_master /spi_device /类名 /device
对 spi 下目录的具体分析放在 SPI 相关 章节
支线任务 -- bus 和 class
device_add 调用了 bus_add_device 和 device_add_class_symlinks 之前有提及过,class 和 bus 有很深的关联,他们都有一样的 subsys_private 结构体,都可以作为 subsystem 被 device 指向,现在看在 device_add 里也是成对调用和链接有关的函数。目前笔者对,bus 和 class 分别承担了 device 的何种抽象,这个概念不甚了解,只能把他们的一些对应找出来先放着。感觉探索这个概念,是之后实践中学习的线索。
/sys/bus: bus_add_device 已经在 /sys/bus/devices 出现过了,就是在 /sys/devices 和 /sys/bus 相关目录下建立链接,
ini
// drivers/base/bus.c
int bus_add_device(struct device *dev)
{
struct bus_type *bus = bus_get(dev->bus);
int error = 0;
if (bus) {
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
error = device_add_attrs(bus, dev);
/*在 sys/bus/XXX/devices 下建立 symlink,
名字为设备名,该链接指向 /sys/devices/ 下的某个目录*/
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
/*在 sys/devices/ 的某个目录下建立 symlink,
名字为 subsystem,该链接指向 /sys/bus/ 下的某个目录*/
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
/*在 sys/devices/ 的某个目录下建立 symlink,
名字为 bus,该链接指向 /sys/bus/ 下的某个目录*/
error = make_deprecated_bus_links(dev);
}
/sys/class: device_add_class_symlinks 在之前也出现过,也把其代码放过来看一下
rust
// drivers/base/core.c
static int device_add_class_symlinks(struct device *dev)
{
struct device_node *of_node = dev_of_node(dev);
int error;
if (of_node) {
error = sysfs_create_link(&dev->kobj, of_node_kobj(of_node), "of_node");
if (error)
dev_warn(dev, "Error %d creating of_node link\n",error);
/* An error here doesn't warrant bringing down the device */
}
error = sysfs_create_link(&dev->kobj,
&dev->class->p->subsys.kobj,
"subsystem");
if (dev->parent && device_is_not_partition(dev)) {
error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
}
/* class 中指向 device 位置的链接 */
error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev));
return 0;
}
总结
device_register 小结
经过 device_register()函数注册以后,一种新类型 device 在 sysfs 中的层次结构为:
SPI 相关
引言
在本章开始前总结一下设备模型的层次
- 第一层类 kobject、kset、subsys
- 第二层类 bus_type、device、device_driver、class
- 第三层类 spi_bus_type、spi_device、spi_driver
这个部分开始的目录,位于第三层类,SPI 抽象了一层自己的总线,拥有自己的总线拓扑结构,但大体结构上与 platform 类似。SPI 总线的初始化如下
SPI 部分的主要结构初始化过程见下图
上图只需要知道,spi_controller spi_device 的信息源自设备树,spi protocol driver 的信息在 spidev.c 下,其余可以边往下看边理解
/sys/bus/spi
sys/bus/spi 目录下的组织方式和 sys/bus/platform 类似,我们重点关注 sys/devices 和 sys/class 下 spi 相关目录的创建
/sys/bus/spi 目录初始化如下
ini
// drivers/spi/spi.c
static int __init spi_init(void)
{
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!buf) {
status = -ENOMEM;
goto err0;
}
// 关注这里 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
status = bus_register(&spi_bus_type);
if (status < 0)
goto err1;
// 和这里 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
status = class_register(&spi_master_class);
}
刚才的代码段中 bus_register 初始化了 /sys/bus/spi 再往下就能看到 class_register 创建了 /sys/class/spi_master
/sys/devices 下不得不讲的 struct spi_master
struct spi_master 是一个持有了 device 的结构体,同时有个类名 class 也叫 spi_master (代码中即 spi_master_class) 这两个概念同时出现时需要予以分辨
往下探究 /sys/devices 下的 spi 目录必须先介绍 struct spi_master,可以说它负责了 spi 下目录结构的组织
struct spi_master 持有了 device 内存空间,故 struct spi_mater 是一个 device,与之前的 device 分析类似,struct spi_master 也需要 device_initialize 和 device_add 在此之外再添加 spi 相关的信息,其初始化在 spi_alloc_controller 注册在 spi_register_controller
arduino
// driers/spi/spi.c
// dev 是 spi 代表的 platform_device
// ctrl->dev 是 spi_controller 代表的 device
// 他本身也是一个device
struct spi_controller *__spi_alloc_controller(struct device *dev,
unsigned int size, bool slave)
if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave)
ctlr->dev.class = &spi_slave_class;
else
// 在 sys/class 下的拓扑结构
// 会在 /sys/class/spi_master 下生成链接指向该 dev
// 的实际位置
ctlr->dev.class = &spi_master_class;
// 在 /sys/devices 下的拓扑结构
// 在 /4026000.spi 目录下的结构
ctlr->dev.parent = dev;
}
// driers/spi/spi.c
int spi_register_controller(struct spi_controller *ctlr)
{
// dev 是 spi 代表的 platform_device
// ctrl->dev 是 spi_controller 代表的 device
struct device *dev = ctlr->dev.parent;
// 代表 spi_master 的设备名称是 spix
dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);
status = device_add(&ctlr->dev);
}
问题: ctrl->dev.parent 在文件目录中是 4026000.spi,ctrl->dev.name = spi1 ,为什么最后的文件目录结构是 4026000.spi/spi_master/spi1 ,而不是 4026000.spi/spi1
如果仔细读了 主线任务 -- 父目录获取 的话,心中应该已经有答案了
/sys/devices/platform/xx/4026000.spi
spi1 是 struct spi_master 的设备的名字,spi_master 是 struct spi_master 所属的 class 名, 在 device_add 中有如下逻辑
scss
// drivers/base/core.c
int device_add(struct device *dev)
{
kobj = get_device_parent(dev, parent);
}
static struct kobject *get_device_parent(struct device *dev,
struct device *parent)
{
// spi_master 的父设备是 platform_device
// spi_master 的 class 是 spi_master 父设备的 class 不存在
// 逻辑会走到这一个函数,创建 spi_master 文件夹作为 spi_master
// 的 device 的父文件夹。不直接放到 platform_device 下
k = class_dir_create_and_add(dev->class, parent_kobj);
}
以 spi 举例,spi 的 platform_device 的 subsystem 指向 bus/platform ,platform_device /spi_master/spi1 的 subsystem 指向 class/spi_master
这个模式在 uart 中也存在,uart 的 platform_device 的 subsystem 指向 bus/platform ,platform_device/tty/ttyS1 的 subsystem,指向 class/tty
/sys/devices/platform/xx/4026000.spi/spi_master/spi1/
前面说明了 spi1 也就是 spi_master 的设备的存放位置,还没有涉及到 spi_device, spi_driver 的概念,当前目录的内容如下
继续往下探究,可能的入口点是 spi1.0,spi1.0 的创建在 spi_register_controller -> of_register_spi_devices ,它是直接解析设备树创建的。 of_register_spi_devices -> of_register_spi_device -> spi_alloc_device 中绑定了与 master 的设备的关系
ini
// drivers/spi/spi.c
struct spi_device *spi_alloc_device(struct spi_controller *ctlr)
{
struct spi_device *spi;
spi->master = spi->controller = ctlr;
spi->dev.parent = &ctlr->dev;
spi->dev.bus = &spi_bus_type;
}
在 of_register_spi_devices -> of_register_spi_device -> spi_add_device -> spi_dev_set_name 中创建了该设备的名称 spi1.0
rust
static void spi_dev_set_name(struct spi_device *spi)
{
struct acpi_device *adev = ACPI_COMPANION(&spi->dev);
dev_set_name(&spi->dev, "%s.%u", dev_name(&spi->controller->dev),
spi->chip_select);
}
spi1.0 的目录结构如下图所示,可以看到 spi1.0 作为 spi_device 和 platform_device 的目录结构很像,通过 driver 可以找到其对应的驱动名字和层级
spidev1.0 目录的创建在 spi_device 与 spi_driver probe 时
在 spi1.0 目录下的 spidev/spidev1.0 出现了之前的 /device//sub_device 模式
之前是
4026000.spi/spi_master/spi1
对应
platform_device/class/spi_device
这里是
spi1.0/spidev/spidev1.0
对应
spi_device/class/device
总结
至此算是对主线任务告一段落了,目前是从 /sys 的角度,看了一下一开始提到的主线任务,只是很功利地找到了文件结构的对应对 /sys 目录有了一定的理解,对 bus driver device class 的组织有了一定理解
但是对具体 spi 驱动模块,或是具体 platform 总线的框架的理解可能还是要从实践中去加深
/SYS/DEV
sys/dev 貌似和用户侧做响应的设备才会注册到这里,在这个目录下我们关注的 spi 有关的文件只有
lrwxrwxrwx 1 root root 0 Jan 1 08:00 153:0 -> ../../devices/platform/soc@3000000/4026000.spi/spi_master/spi1/spi1.0/spidev/spidev1.0
这说明了和用户侧进行对接的是 spidev1.0 设备
总结
这篇文章比较功利,就是做了 /sys 目录和代码的对应,每涉及到一个目录都牵扯很多知识点,好在这个主线任务足够聚焦,可以在较小范围内写完。
参考文档
wowotech:Linux 设备模型 wowotech 很细,还有些别的概念的 gpio 设备树的,就着源码讲的
PCI 为例的设备模型讲解(超级详细)图很牛,用的全是这的图
Linux 设备驱动框架剖析 图很清楚
Linux 内核部件分析 设备驱动模型之 device 针对 /drivers/base/core.c 的分析