[TOC]

drivers/base/faux.c 虚拟设备总线(Faux Bus) 简化虚拟设备驱动的创建

历史与背景

这项技术是为了解决什么特定问题而诞生的?

drivers/base/faux.c 文件所实现的 “Faux Bus”(虚拟总线)是一项较新的内核技术,在 Linux 6.14 内核中被合并。它的诞生主要是为了解决一个长期存在的内核开发“滥用”问题:将平台总线(Platform Bus)用于不属于平台设备(Platform Device)的场景

内核维护者 Greg Kroah-Hartman 指出,多年来,许多开发者为了利用设备模型的便利性(如自动的驱动-设备绑定、probe/remove回调机制),会将一些纯粹的虚拟设备或简单的逻辑设备注册为“平台设备”。 这种做法在功能上可行,但从设计哲学上看是不正确的,因为平台设备本意是指那些在SoC(片上系统)上,无法通过正常总线(如PCI, USB)枚举发现的、有固定内存地址或硬件资源的设备。将纯虚拟设备(例如一个用于测试的dummy regulator)注册为平台设备,是一种概念上的滥用,并给代码带来了不必要的复杂性。

Faux Bus 的目的就是提供一个极其简单的、专为这类虚拟或“假”设备设计的总线,从而清理代码,让平台总线的用途回归其本意。

它的发展经历了哪些重要的里程碑或版本迭代?

  • 2025年2月:由内核核心维护者 Greg Kroah-Hartman 提出 “Faux Bus” 的概念和初始补丁集,并征求社区意见。
  • Linux 6.14内核:该技术被正式合并进入主线内核。一个显著的特点是,它在被引入的同时,就提供了C语言和Rust语言的API绑定,这在内核新接口的开发中较为少见,也体现了对新兴内核开发语言的支持。
  • Linux 6.15及以后:社区开始将内核中原先滥用平台总线的驱动,逐步转换到使用 Faux Bus。

目前该技术的社区活跃度和主流应用情况如何?

作为一个新兴的内核API,Faux Bus 正处于被逐步推广和应用的阶段。内核社区正在积极地识别那些适合转换的驱动程序,并提交补丁。它的主要应用场景是内核内部的虚拟设备、测试驱动和一些简单的逻辑抽象设备,例如:

  • Dummy regulator 驱动
  • USB PHY (physical layer) 的一些辅助代码

核心原理与设计

它的核心工作原理是什么?

drivers/base/faux.c 的核心原理是提供一个极简的API,将“创建设备”和“创建并绑定驱动”这两个动作合二为一。

  1. 定义一个新总线:代码内部定义了一个名为 “faux” 的 bus_type。这是一个非常简单的总线,没有复杂的匹配逻辑。
  2. 统一的创建函数:它对外暴露的核心API是 faux_device_create_with_groups()。当一个内核模块调用这个函数时,它会执行以下一系列操作:
    • 动态地创建一个 struct device_driver 实例。这个驱动被设置为属于 “faux” 总线。
    • 动态地创建一个 struct device 实例,并将其总线也设置为 “faux”。
    • 自动绑定:由于设备和驱动被创建时就明确了对应关系,内核会立即将它们绑定在一起。这个绑定操作会触发调用调用者提供的 .probe 回调函数。
  3. 生命周期管理faux_device_create_with_groups() 函数会返回一个指向新创建的 faux_device 的指针。这个设备的生命周期由调用者管理。当不再需要该设备时,调用者只需调用 faux_device_destroy(),该函数会自动处理驱动的 .remove 回调、设备和驱动的注销以及内存的释放。

它的主要优势体现在哪些方面?

  • API极其简单:相比平台设备的注册,Faux Bus 的API更简单,只需要两个核心函数(创建和销毁)。
  • 概念清晰:为虚拟设备和逻辑设备提供了一个专属的、名正言顺的“家”,避免了对平台总线的滥用,使内核代码的意图更清晰。
  • 代码更简洁:在一些转换案例中,使用 Faux Bus 相比原先使用平台总线的代码,行数有所减少,逻辑也更直接。
  • 自动绑定:创建设备的同时就隐式创建并绑定了驱动,开发者无需关心匹配逻辑。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 功能极简:Faux Bus 被设计为只处理最简单的情况。它没有设备树(Device Tree)或ACPI支持,不适用于需要从固件获取信息的设备。
  • 无匹配逻辑:它不支持一个驱动匹配多个设备,或者一个设备在多个驱动间选择。设备和驱动是一对一的、在创建时就锁定的关系。
  • 不适用于真实硬件:它的设计目标是纯软件的、虚拟的设备。任何与真实物理总线或硬件资源相关的设备都不应该使用 Faux Bus。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

Faux Bus 是为那些纯软件模拟的、生命周期由其他内核模块动态管理的逻辑设备提供的首选解决方案。

  • 场景一:Dummy Regulator
    内核的 regulator/dummy.c 驱动可以创建一个虚拟的稳压器(regulator),用于测试或满足那些在软件上需要一个regulator句柄但实际上没有硬件对应的场景。在过去,它通过注册一个 platform_deviceplatform_driver 来实现。现在,它可以直接使用 faux_device_create() 来创建这个虚拟稳压器,代码更简洁且概念上更正确。
  • 场景二:USB PHY 辅助逻辑
    某些USB控制器驱动需要在内部管理一个抽象的PHY层,这个PHY层可能没有独立的物理设备与之对应。使用 Faux Bus 可以为此创建一个逻辑上的设备和驱动,以便利用设备模型的probe/remove机制来管理其初始化和清理过程,而无需污染平台总线。

是否有不推荐使用该技术的场景?为什么?

  • 所有与物理硬件相关的设备:如果一个设备是通过PCIe、USB、I2C、SPI等物理总线连接的,或者是在设备树/ACPI中描述的SoC内部设备,绝对不能使用 Faux Bus。这些设备必须使用其对应的真实总线类型(pci_driver, usb_driver, platform_driver 等)。
  • 需要驱动与设备分离的场景:如果希望一个驱动能支持多种设备,或者一个设备能由多个驱动中的一个来处理(例如一个通用驱动和一个厂商特定驱动),则不应使用 Faux Bus,因为它不支持这种分离的匹配逻辑。

对比分析

请将其 与 其他相似技术 进行详细对比。

Faux Bus 的主要对比对象就是被它取代的“滥用”方案——平台总线(Platform Bus),以及另一个虚拟总线——虚拟总线(Virtual Bus)。

特性 Faux Bus (faux.c) Platform Bus (platform.c) Virtual Bus (subsys_virtual_register)
设计用途 用于纯粹的、简单的、与硬件无关的虚拟/逻辑设备,以取代对平台总线的滥用。 用于SoC上那些无法被硬件自动枚举的、通常在设备树或ACPI中描述的真实硬件设备。 用于表示一类虚拟设备,这些设备通常出现在 /sys/devices/virtual/ 下,如网络设备(net)或输入设备(input)。
API复杂度 极低。核心API只有创建和销毁,设备和驱动在创建时自动绑定。 中等。需要分别定义platform_deviceplatform_driver,并通过匹配机制(如设备树的compatible字符串或设备名)进行绑定。 。提供注册总线的API,但设备和驱动的注册仍需分开处理。
设备-驱动关系 一对一,创建时锁定。一个faux_device_create调用就唯一对应一个驱动实例。 多对多,运行时匹配。一个驱动可以支持多种设备,一个设备也可以匹配多个潜在的驱动。 灵活,由子系统自身决定。
硬件/固件支持 。完全不处理设备树或ACPI。 核心特性。平台总线的核心任务就是处理通过固件描述的硬件设备。
Sysfs路径 /sys/bus/faux/devices/ /sys/bus/platform/devices/ /sys/devices/virtual/<subsys_name>/
典型例子 Dummy regulator, USB PHY 逻辑。 I2C控制器、SPI控制器、SoC内部的DMA引擎。 网络接口(eth0)、TTY设备、输入设备(event0)。

faux_bus_init: 初始化并注册一个虚拟总线

此代码片段的作用是定义并注册一个完整的、自包含的 “faux” (虚拟) 总线系统。这个过程是 Linux 设备模型中建立新总线的基础, 它包括三个核心步骤:

  1. 注册一个根设备 (faux_bus_root): 这个设备在 sysfs 中作为虚拟总线层次结构的顶层目录, 通常位于 /sys/devices/faux
  2. 注册一个总线类型 (faux_bus_type): 这定义了总线的名称、行为以及最重要的——设备与驱动的匹配规则 (match 函数) 和探测/移除逻辑 (probe/remove 函数)。
  3. 注册一个驱动程序 (faux_driver): 这是挂载在此总线上的一个驱动实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
* 定义一个静态的 struct device 实例, 作为虚拟总线的根设备.
* 在 sysfs 中, 它通常会表现为 /sys/devices/faux 目录.
* 'static' 关键字使其在此文件外部不可见.
*/
static struct device faux_bus_root = {
/*
* .init_name: 初始化时使用的设备名称.
* device_register() 函数将使用这个名称来创建 kobject, 也就是 sysfs 中的目录名.
*/
.init_name = "faux",
};

/*
* 定义一个静态的、常量类型的 struct bus_type 实例.
* 它描述了 "faux" 总线的所有核心属性和行为.
* 'const' 表示这个结构体的内容在编译后是只读的.
*/
static const struct bus_type faux_bus_type = {
/*
* .name: 总线的名称. 这将会在 sysfs 中创建 /sys/bus/faux 目录.
*/
.name = "faux",
/*
* .match: 一个函数指针, 指向用于判断设备和驱动是否匹配的函数.
* 当有新设备或新驱动注册到这个总线时, 内核会调用此函数进行匹配.
* 此处指向未提供的 faux_match 函数.
*/
.match = faux_match,
/*
* .probe: 一个函数指针, 指向总线级别的探测函数.
* 在驱动的 probe 函数被调用之前, 总线核心可能会调用此函数进行一些通用的设置.
* 此处指向未提供的 faux_probe 函数.
*/
.probe = faux_probe,
/*
* .remove: 一个函数指针, 指向总线级别的移除函数.
* 当设备从总线上移除时调用.
* 此处指向未提供的 faux_remove 函数.
*/
.remove = faux_remove,
};

/*
* 定义一个静态的 struct device_driver 实例.
* 它代表一个将要注册到 "faux" 总线上的驱动程序.
*/
static struct device_driver faux_driver = {
/*
* .name: 驱动程序的名称.
* 这将会在 /sys/bus/faux/drivers/ 目录下创建名为 "faux_driver" 的条目.
*/
.name = "faux_driver",
/*
* .bus: 一个指针, 指向此驱动所属的总线类型.
* 这是将驱动程序与 faux_bus_type 关联起来的关键链接.
*/
.bus = &faux_bus_type,
/*
* .probe_type: 指定探测类型为强制同步.
* 这意味着当设备与此驱动匹配时, 驱动的 probe 函数会立即在当前上下文中被同步调用,
* 而不是被推迟到异步的工作队列中. 这可以简化初始化时的依赖关系.
*/
.probe_type = PROBE_FORCE_SYNCHRONOUS,
/*
* .suppress_bind_attrs: 设置为 true, 禁止在 sysfs 中为此驱动自动创建 "bind" 和 "unbind" 文件.
* 这通常用于那些不希望被用户从用户空间手动绑定或解绑的驱动.
*/
.suppress_bind_attrs = true,
};


/*
* faux_bus_init: 模块初始化函数.
* __init 宏告诉编译器将此函数放入 ".init.text" 段.
* 在内核启动过程完成后, 这个段的内存可以被释放以节省RAM.
* @return: 成功时返回 0, 失败时返回负的错误码.
*/
int __init faux_bus_init(void)
{
/*
* 定义一个整型变量 ret, 用于存储函数调用的返回值.
*/
int ret;

/*
* 调用 device_register() 函数注册 faux_bus_root 设备.
* 这会在 sysfs 中创建 /sys/devices/faux.
*/
ret = device_register(&faux_bus_root);
/*
* 检查注册是否失败 (返回值非零).
*/
if (ret) {
/*
* 如果注册失败, 调用 put_device() 来清理已部分初始化的 device 结构体, 释放其引用.
*/
put_device(&faux_bus_root);
/*
* 返回错误码.
*/
return ret;
}

/*
* 调用 bus_register() 函数注册 faux_bus_type 总线类型.
* 这会在 sysfs 中创建 /sys/bus/faux, 并使总线对内核的设备模型可见.
*/
ret = bus_register(&faux_bus_type);
/*
* 检查注册是否失败.
*/
if (ret)
/*
* 如果失败, 跳转到 error_bus 标签处进行清理.
*/
goto error_bus;

/*
* 调用 driver_register() 函数注册 faux_driver 驱动程序.
* 这会将驱动添加到 faux_bus_type 的驱动列表中, 并创建 /sys/bus/faux/drivers/faux_driver.
*/
ret = driver_register(&faux_driver);
/*
* 检查注册是否失败.
*/
if (ret)
/*
* 如果失败, 跳转到 error_driver 标签处进行清理.
*/
goto error_driver;

/*
* 所有步骤都成功, 返回 0.
*/
return ret;

/*
* 错误处理标签. 当 driver_register 失败时, 从这里开始清理.
*/
error_driver:
/*
* 调用 bus_unregister() 注销之前成功注册的总线类型.
*/
bus_unregister(&faux_bus_type);

/*
* 错误处理标签. 当 bus_register 失败时, 从这里开始清理.
*/
error_bus:
/*
* 调用 device_unregister() 注销之前成功注册的根设备.
*/
device_unregister(&faux_bus_root);
/*
* 返回导致失败的错误码.
*/
return ret;
}

faux_device_create_with_groups / faux_device_create: 创建并注册一个虚拟设备

这两个函数共同提供了一个接口, 用于在内核中创建和注册一个虚拟的 (faux) 设备。虚拟设备是纯粹由软件定义的实体, 它们不直接对应任何物理硬件, 但能被无缝地集成到Linux的设备驱动模型中。这使得它们可以拥有自己的驱动程序(通过回调函数实现)、出现在sysfs文件系统中, 并与其他真实设备进行交互。
faux_device_create_with_groups是核心实现, 它允许在创建设备的同时为其定义一组sysfs属性文件。faux_device_create则是一个简化的便利性封装, 用于创建一个不带任何额外sysfs属性的虚拟设备。

  • faux_device_create_with_groups: 创建带sysfs属性的虚拟设备 (核心实现)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
* faux_device_create_with_groups - 创建一个虚拟设备, 将其注册到驱动核心,
* 并为该设备填充一组初始的sysfs属性文件.
*
* @name: 我们正在添加的设备的名称, 对于所有虚拟设备必须是唯一的.
* @parent: 指向一个可能的父设备(struct device)的指针. 如果设置为NULL,
* 设备将被创建在sysfs中虚拟设备树的"根"目录下.
* @faux_ops: 一个新的设备将回调的 struct faux_device_ops. 可以是NULL.
* @groups: 当设备注册到驱动核心时, 将为此设备创建的一组sysfs属性.
*
* (函数说明省略...)
*
* 返回值:
* * 如果创建设备时发生错误, 返回 NULL.
* * 指向一个已在sysfs中注册的、有效的 struct faux_device 的指针.
*/
struct faux_device *faux_device_create_with_groups(const char *name,
struct device *parent,
const struct faux_device_ops *faux_ops,
const struct attribute_group **groups)
{
/*
* 定义一个指向 faux_object 结构体的指针. faux_object 是包含 faux_device 和其他元数据的容器.
*/
struct faux_object *faux_obj;
/*
* 定义一个指向 faux_device 结构体的指针. faux_device 是对标准 device 结构体的封装.
*/
struct faux_device *faux_dev;
/*
* 定义一个指向标准 device 结构体的指针. 这是Linux内核中所有设备的通用表示.
*/
struct device *dev;
/*
* 定义一个整型变量 ret, 用于存储函数调用的返回值(错误码).
*/
int ret;

/*
* 为 faux_object 容器分配内存. kzalloc 会分配内存并将其内容清零.
* GFP_KERNEL 表示这是一个常规的内核内存分配, 在需要时可以阻塞(睡眠).
*/
faux_obj = kzalloc(sizeof(*faux_obj), GFP_KERNEL);
/*
* 如果内存分配失败, kzalloc 返回 NULL.
*/
if (!faux_obj)
return NULL;

/* 保存回调函数和属性组, 以便将来使用 */
faux_obj->faux_ops = faux_ops;
faux_obj->groups = groups;

/* 初始化设备部分, 并将其注册到驱动核心 */
/*
* 获取 faux_obj 中内嵌的 faux_device 结构体的地址.
*/
faux_dev = &faux_obj->faux_dev;
/*
* 获取 faux_dev 中内嵌的标准 device 结构体的地址.
*/
dev = &faux_dev->dev;

/*
* 调用 device_initialize, 对标准 device 结构体的成员(如kobject, 锁等)进行初始化.
* 这会为设备增加一个引用计数.
*/
device_initialize(dev);
/*
* 设置设备的 .release 回调函数. 这是至关重要的一步.
* 当此设备的最后一个引用被释放时(通过 put_device), 这个函数将被调用以释放 faux_obj 容器的内存, 防止内存泄漏.
*/
dev->release = faux_device_release;
/*
* 如果调用者提供了父设备, 则设置它.
*/
if (parent)
dev->parent = parent;
else
/*
* 否则, 使用虚拟总线的根设备作为父设备.
*/
dev->parent = &faux_bus_root;
/*
* 将设备的总线类型设置为 faux_bus_type. 这会将设备与虚拟总线关联起来.
*/
dev->bus = &faux_bus_type;
/*
* 使用调用者提供的名称来设置设备名. 这个名字将显示在sysfs中.
*/
dev_set_name(dev, "%s", name);

/*
* 调用 device_add, 将设备正式添加到驱动核心.
* 这是一个关键步骤, 它会使设备在sysfs中可见, 并触发总线去为它寻找匹配的驱动程序,
* 从而导致 faux_ops 中的 .probe 回调函数(如果存在)被同步调用.
*/
ret = device_add(dev);
/*
* 如果 device_add 失败.
*/
if (ret) {
/*
* 打印一条错误日志.
*/
pr_err("%s: device_add for faux device '%s' failed with %d\n",
__func__, name, ret);
/*
* 调用 put_device 来减少因 device_initialize 增加的引用计数.
* 这将触发上面设置的 .release 回调, 从而释放 faux_obj 的内存.
*/
put_device(dev);
return NULL;
}

/*
* 验证我们是否已将驱动绑定到设备上 (即 probe 是否成功).
* 如果没有, 我们就让创建失败, 因为调用者几乎不可能判断 probe 是否成功.
*/
if (!dev->driver) {
/*
* 如果 probe 失败 (dev->driver 仍然是 NULL), 则认为创建失败.
*/
dev_dbg(dev, "probe did not succeed, tearing down the device\n");
/*
* 调用 faux_device_destroy 来清理并注销设备.
*/
faux_device_destroy(faux_dev);
/*
* 将返回指针设置为 NULL.
*/
faux_dev = NULL;
}

/*
* 返回创建的虚拟设备指针, 如果失败则为 NULL.
*/
return faux_dev;
}
/*
* 将 faux_device_create_with_groups 符号导出, 供GPL许可证的模块使用.
*/
EXPORT_SYMBOL_GPL(faux_device_create_with_groups);

/**
* faux_device_create - 创建一个虚拟设备并将其注册到驱动核心
*
* (函数说明省略...)
*
*/
struct faux_device *faux_device_create(const char *name,
struct device *parent,
const struct faux_device_ops *faux_ops)
{
/*
* 这是一个便利性封装函数. 它直接调用核心实现函数 faux_device_create_with_groups,
* 并将属性组参数(groups)传递为 NULL, 表示不创建任何额外的sysfs属性文件.
*/
return faux_device_create_with_groups(name, parent, faux_ops, NULL);
}
/*
* 将 faux_device_create 符号导出, 供GPL许可证的模块使用.
*/
EXPORT_SYMBOL_GPL(faux_device_create);