crypto: eliminate fallback proxy overhead while disabled

Crypto API 动态软件回退框架 · 代理注册/注销与 enabled 开关绑定 · 禁用态热路径零代理开销

💡 一句话总结

在 crypto 硬件加速器(如 EIP93)默认关闭动态软件回退的形态下,旧实现把回退代理算法常驻注册进 Crypto API,导致新分配 transform 即使禁用也会解析到代理层、每个请求多付一次阈值判断与间接派发开销。补丁把代理注册/注销与 per-device 的 enabled 开关绑定:禁用时注销代理使新 transform 直连硬件,启用时再注册并用 kref 保留退役代理直到旧 transform 释放。效果:默认关闭态硬件热路径代理开销归零(补丁未单独量化;系列数据指示代理在 4096B 大请求上较直连约 +4.4% 周期,作者自报,未独立验证),而小请求启用后经软件路径 16B 周期下降 94.4%(作者自报,未独立验证)。

📋 补丁基本信息

项目内容
补丁类型优化(性能 · 消除特性默认关闭态的热路径代理开销;属"动态软件回退"新特性系列的性能收尾补丁)
性能类别热路径(禁用态移除一层阈值判断 + 间接派发;免去无用的代理包装层)
状态In Review(v1 首版,截至 Linux 7.2-rc6 未合入本地内核 git)
当前版本v1(4/4)· 当前版链接
版本演进首版,暂无演进(2026-07-28 首次提交;截至 2026-08-03 lore 归档无后续版本/回复)
作者机构Jihong Min(hurryman2212@gmail.com,个人开发者,无机构标注)
提交日期2026-07-28
改动范围crypto/fallback.c,1 文件,+214/-70(commit message 标注 284 行变更)
核心函数fallback_enabled_store() / fallback_register_algs() / fallback_unregister_algs() / fallback_alg_destroy() / crypto_fallback_register() / crypto_fallback_unregister()
原始链接lore Message-ID

系列背景:本补丁是 PATCH 0/4「crypto: introduce generic dynamic software fallback and EIP93 support」 的第 4 篇。前 3 篇依次为:① 移出 tcrypt 的周期基准辅助函数、② 引入动态软件回退框架(crypto/fallback.c 本体,+1441 行)、③ EIP93 驱动接入。本补丁 4/4 是针对框架本身的"禁用态开销"优化。

📊 速览卡片

核心机制
按需注册代理
优化目标
禁用态零开销
适用场景
crypto 加速器
实测提升
未单独量化

🎯 解决什么问题

背景 / 原始动机
系列 cover letter 说明整套框架的动机:crypto 加速器通常要付"提交请求、DMA、中断、完成"的固定开销,老式设备把固定开销和高延迟叠在一起,小请求可能比 CPU 软件实现更慢。cover letter 明确引用近期 QCE 讨论(Eric Biggers 的 "crypto: qce - Remove driver",2026-07-24):"a crypto device cannot be evaluated solely by whether there is any performance improvement over the CPU software path"——不能只看"是否比 CPU 软件快"来评价一个设备。EIP93 被选为首个接入对象:大请求确有提升,但小请求即便对 AN7581 里较弱的 CPU 也明显更慢;社区已有针对 AES 的静态 size override(stevenj/ph-openwrt-mtk-eip93),但既不系统测交叉点、也不是可扩展的通用框架。
系统层面:【代理常驻注册,禁用态仍付派发开销】
本补丁针对框架自身的缺陷,commit message 原话:"Register fallback proxy providers only when the per-device control is enabled. Unregister them again on disable so newly allocated transforms select the hardware provider directly."(仅在 per-device 开关启用时注册回退代理 provider;禁用时注销,让新建 transform 直连硬件 provider。)在补丁 4/4 之前的框架里,crypto_fallback_register() 在驱动注册时就把所有代理算法注册进 Crypto API,与 enabled 状态无关;"禁用"只是把阈值清零(强制走硬件)。但代理(一个包装算法)仍留在 Crypto API 命名空间里——按名字解析的新 transform 可能命中代理层,每请求仍要多走一次"读阈值 → 比较 size → 间接派发"。
场景层面:【默认关闭态不该为不用的特性付代价】
回退特性被设计为默认关闭(disabled by default),这是它能在主线落地的安全前提:已有用户(IPsec、dm-crypt、AF_ALG/TLS offload 等走该设备硬件的负载)不应该因为"多注册了一个他们不用的代理算法"而变慢。场景特征:小报文、高频、每请求固定开销敏感的转发/加密路径(如 VPN 网关、路由器小包转发)。这类路径上,"代理层多一次间接派发"即便只有几十周期,乘上每秒百万级请求也是可见的。更关键的是框架语义:禁用=直连硬件是它对外承诺的形态,旧实现没兑现这个承诺。
受影响负载:使用硬件 crypto 设备且不启用动态回退的系统 · 为什么此特性解决此场景:把"禁用=直连"落实为默认态,代理开销只在用户明确启用时才出现。

🧩 核心机制

补丁把代理 provider 的生命周期从"注册时全量常驻"改为"跟随 enabled 开关按需注册/注销",并用 kref + 每启用周期独立分配来保证重复开关与旧 transform 的安全。

简化理解
代理就像硬件加密引擎前面的一座"调度收费站"。旧设计里,即使把调度功能关停,收费站仍立在路上——每辆车(crypto 请求)都得减速过闸;新设计在关停时把收费站拆掉,车直达硬件。已经"上了这条路"的车(已绑定代理的 transform)允许走完全程——内核不能中途把它们移到另一条路(Crypto API 无法迁移已分配的 transform),于是用 kref 记着"还有几辆车没走完",等它们全部释放后再拆站。
从系统层面看
① 注册不再与启用解耦。旧的 crypto_fallback_register() 既分配结构体又注册全部代理;新实现只校验参数 + 分配 struct crypto_fallback(把 struct fallback_alg *algs 静态数组改成 list_head algs + spinlock_t alg_lock 动态链表),代理注册推迟到启用时。这直接兑现"禁用时命名空间里没有代理"。

② 开关点联动注册/注销。fallback_enabled_store()(sysfs enabled 属性写回调)在启用且当前未启用时调 fallback_register_algs(),为每个 group 分配并注册代理;禁用时置 enabled=false、阈值清零、调 fallback_unregister_algs() 注销全部已注册代理。每个启用周期分配全新的 fallback_alg 对象("generation"),与旧代互不干扰。

③ 退役代用 cra_destroy 延迟释放。Crypto API 对每个 crypto_alg 有引用计数,绑定到 transform 的算法不能随手释放。补丁把每个代理的 cra_destroy 指向 fallback_*_destroy:当最后一个使用该代理的 transform 释放、算法引用计数归零时,内核回调 crypto_destroy_alg() → cra_destroy → fallback_alg_destroy(),把代理标记 dead、从链表移除、kref_put 释放,同时 drop 掉 fallback 结构体的引用。于是"从命名空间注销(新查找跳过)"与"对象存活到旧 transform 释放"可以安全并存。
crypto fallback 代理禁用态开销消除前后对比
图 1:补丁 4/4 前后对比——禁用态(默认)下代理注册与否对请求路径的影响,以及启用态的派发行为。
来源:基于 lore 真实补丁 diff + 系列 cover letter 的 benchmark 数据绘制
步骤操作目的
注册时crypto_fallback_register() 只分配结构体,不再注册代理禁用态命名空间里无代理,新 transform 直连硬件
启用时fallback_enabled_store() → fallback_register_algs() 为每组分配 + 注册代理代理只在用户明确启用时出现
禁用时置 enabled=false + 阈值清零 + fallback_unregister_algs()新查找跳过代理;旧 transform 的代理被强制走硬件
释放时代理 cra_destroy → fallback_alg_destroy() → 标记 dead + kref 释放退役代保留到最后一个使用它的 transform 释放
框架释放fallback 本身 kref 计数,全部代理释放后才 crypto_fallback_free()避免结构体先于代理销毁的 UAF

🔬 关键代码

diff 逐字来自 lore 归档的 [PATCH 4/4] 原文。按补丁的三个核心逻辑点组织,每段讲"为什么这么写"。

逻辑点①:启用/禁用时按需注册/注销代理(本补丁的核心)
@@ -476,28 +482,39 @@ static ssize_t fallback_enabled_store(struct device *dev,
 		for (i = 0; i < fallback->num_groups; i++)
 			WRITE_ONCE(fallback->groups[i].threshold.value,
 				   fallback->groups[i].value);
+
+		if (!fallback->enabled) {
+			err = fallback_register_algs(fallback);
+			if (err) {
+				for (i = 0; i < fallback->num_groups; i++)
+					WRITE_ONCE(fallback->groups[i].threshold.value,
+						   0);
+				goto out_unlock;
+			}
+		}
 	} else {
+		WRITE_ONCE(fallback->enabled, false);
 		for (i = 0; i < fallback->num_groups; i++)
 			WRITE_ONCE(fallback->groups[i].threshold.value, 0);
+		fallback_unregister_algs(fallback);
 	}
 	WRITE_ONCE(fallback->enabled, enabled);
 
 out_unlock:
 	mutex_unlock(&fallback->lock);
 
-	return count;
+	return err ?: count;
 }

▲ 为什么这么写:启用分支里 if (!fallback->enabled) 是关键——防止重复 enable 时重复注册同代代理;注册失败则把阈值清零并走 out_unlock(不置 enabled,保持禁用态)。禁用分支先置 enabled=false 再注销,配合 fallback_register_algs() 里对已注册状态的判断,让"禁用与注册/注销"在并发视角下自洽。返回值从恒 count 改为 err ?: count,使注册失败能如实反映到 sysfs 写回。

逻辑点②:退役代的延迟释放(cra_destroy 挂钩 + kref)
+static void fallback_alg_destroy(struct fallback_alg *alg)
+{
+	struct crypto_fallback *fallback = alg->fallback;
+	unsigned long flags;
+	bool release = false;
+
+	spin_lock_irqsave(&fallback->alg_lock, flags);
+	alg->dead = true;
+	alg->registered = false;
+	if (alg->registration_complete) {
+		list_del_init(&alg->list);
+		release = true;
+	}
+	spin_unlock_irqrestore(&fallback->alg_lock, flags);
+
+	if (release)
+		kref_put(&alg->refcount, fallback_alg_release);
+}
+
+static void fallback_skcipher_destroy(struct crypto_alg *base)
+{
+	struct skcipher_alg *alg;
+
+	alg = container_of(base, struct skcipher_alg, base);
+	fallback_alg_destroy(container_of(alg, struct fallback_alg,
+					  alg.skcipher));
+}

▲ 为什么这么写:这个回调由 Crypto API 在算法引用计数归零时调用(crypto_destroy_alg() → cra_destroy)。三个 type(skcipher/aead/ahash)各有一个 fallback_*_destroy 转发到 fallback_alg_destroy()。关键在 registration_complete:若注册尚未完成(disable 与注册竞态)就收到 destroy,只标记 dead、不释放(等注册完成回调 fallback_alg_complete_registration() 处理);只有注册完成后才把对象从链表摘下并 drop 引用。锁内改状态、锁外做 kref_put,避免持自旋锁调用可能睡眠的释放路径。

逻辑点③:按启用周期分配 + 注销时只摘已注册项
+			alg = kzalloc_obj(*alg, GFP_KERNEL);
+			if (!alg) {
+				err = -ENOMEM;
+				goto err_unregister_algs;
+			}
+
+			INIT_LIST_HEAD(&alg->list);
+			kref_init(&alg->refcount);
+			alg->fallback = fallback;
+			alg->threshold = &fallback->groups[i].threshold;
+			alg->type = group->benchmark.type;
+			if (strscpy(alg->driver_name, group->algs[j],
+				    sizeof(alg->driver_name)) < 0) {
+				kfree(alg);
+				err = -ENAMETOOLONG;
+				goto err_unregister_algs;
+			}
+
+			kref_get(&fallback->refcount);
+			spin_lock_irqsave(&fallback->alg_lock, flags);
+			list_add_tail(&alg->list, &fallback->algs);
+			spin_unlock_irqrestore(&fallback->alg_lock, flags);
+
+			err = fallback_register_alg(alg, fallback->owner);
+			err = fallback_alg_complete_registration(alg, err);

▲ 为什么这么写:每次启用周期每个代理都是新分配的(diff 里新注释:"Allocate proxy algorithms per enable cycle because their destruction can be deferred until transforms allocated before disable release them")。kref_get(&fallback->refcount) 让代理持有框架结构体引用,保证框架不会在代理还活着时被释放;list_add_tail 进链表后由 fallback_register_alg() 真正注册,再用 fallback_alg_complete_registration() 标记注册完成。注销方 fallback_unregister_algs() 只挑 registered 的项注销并 drop 引用,未完成注册的项留给 destroy/complete 回调处理。

📈 性能影响

补丁 4/4 本身未单独提供基准数据(commit message 无数字)。下表是系列 cover letter 报告的框架级数据,作者自报,未独立验证。它同时展示了本补丁所针对的"代理开销"的量级:同一台机器上,禁用态直连硬件(Disabled 列,本补丁生效后的默认形态)与启用态走代理(Enabled 列)之差,就是代理层在请求路径上的代价。

场景/用例运行环境直连硬件 (Disabled)走代理 (Enabled)差异
tcrypt AES-128-CBC,16BOpenWrt 社区镜像 / Linux 6.18 / W1700K2 HW2.1 板 / Airoha AN7581 E2 SoC;中位数周期,5 次运行322 周期18 周期-94.4%
tcrypt AES-128-CBC,64B27443-84.3%
tcrypt AES-128-CBC,128B29274-74.7%
tcrypt AES-128-CBC,256B312138-55.8%
tcrypt AES-128-CBC,1024B434439+1.2%
tcrypt AES-128-CBC,1424B492488-0.8%
tcrypt AES-128-CBC,4096B9801023+4.4%
场景/用例运行环境直连硬件 (Disabled)走代理 (Enabled)吞吐增益
OpenSSL 3.5.7 AF_ALG AES-128-CBC,16B同上环境;两次各 2 秒运行取均值0.560 MB/s1.257 MB/s+124.5%
OpenSSL,64B2.4304.639+90.9%
OpenSSL,256B9.10614.168+55.6%
OpenSSL,1024B30.27930.332+0.2%
OpenSSL,8192B88.80789.383+0.6%
OpenSSL,16384B101.220102.068+0.8%

说明:所有数字来自系列 cover letter(lore 原文),标注"作者自报,未独立验证"。补丁 4/4 的收益是逻辑推导:它把"禁用=直连硬件"落实为默认态,因此上表 Disabled 列即本补丁生效后的默认形态;Enabled 列(走代理)中 1024B 以上大请求多出的 3~4% 周期,正是本补丁在禁用态消除的那层开销。阈值基准:启用耗时 0.82s,为每组选一个代表硬件/软件对,在 16/64/128/256/512/1024/1420/2048/4096/8192/16384B 共 11 档实测选交叉点;选定阈值如 raw_aes=512B、hash_sha2=-1(软件全程更快)。

on-CPU vs off-CPU 视角(Brendan Gregg)
本补丁消除的代理开销属于 on-CPU:代理派发是请求热路径上的一个分支 + 间接调用(读阈值、比较 size、再调到真实 provider),不涉及阻塞等待。而框架在小请求上的收益也主要是 on-CPU:小请求改走软件路径,省去了硬件提交/DMA/中断/完成这条同时含 on-CPU 与 off-CPU 等待的固定开销链路(16B 时 Disabled 322 周期里很大一部分是硬件往返固定成本)。补丁 4/4 未提供 off-CPU 数据,此项为逻辑分析(解读 AI 分析)。

🔄 方案演进

本补丁为系列首版(v1),截至 2026-08-03 的 lore 归档,系列 4 篇均无公开回复。演进以下列真实信息为准:

v1(首版,2026-07-28)
PATCH 4/4 首次提交,作为 0/4 系列 的收尾。核心方案一次性成型:按需注册/注销 + kref 退役代保留 + cra_destroy 延迟释放。commit message 带 Assisted-by: Codex:gpt-5.6(作者在 cover letter 的 LLM disclosure 中声明:patch 2/4 与 4/4 的开发获得 LLM 辅助,全部测试由作者本人执行)。
设计权衡 / 讨论推进
系列在提交时引用了近期 QCE 讨论作为动机(cover letter 脚注 [1][2][3]),其中 [3] 是 Eric Biggers 提议直接移除 QCE 驱动(-3534 行)。本系列给出的替代立场是"不是移除设备,而是让设备只在有优势时被使用"。截至归档日,Crypto 维护者 Herbert Xu / Christian Marangi 及社区对该系列均无公开回复(不编造)。后续是否有 v2 依 review 而定,尚不可知(推测)。

⚠️ 风险与局限

潜在回归 / 并发 / 边界
① 退役代累积(MAJOR):每个启用周期分配全新一代代理,且旧代要等"最后一个使用它的 transform 释放"才回收。若管理员在存在长寿命 transform(如常驻的 IPsec SA)时频繁 toggle enabled,退役代会堆积,占用内存直到那些 transform 释放。代码用 kref 保证正确性,但"多久才回收"取决于 transform 生命周期。(解读 AI 分析)

② 引用计数竞态(CRITICAL 潜在):新增的 dead / registration_complete / registered 三态 + alg_lock 自旋锁,处理"禁用与注册完成并发"这类窗口。若 fallback_alg_complete_registration() 与 fallback_alg_destroy() 的引用平衡任一分支出错,要么泄漏(退役代永不释放),要么 UAF(transform 仍引用已释放算法)。这是本补丁最需要维护者逐行 review 的点。(解读 AI 分析)

③ 全局注册锁的 toggle 成本(MINOR):注册/注销走 Crypto API 的 crypto_alg_sem 写锁,toggle enabled 期间会影响并发算法查找/分配。该操作低频(仅管理员开关时),一般可接受,但高并发 crypto 负载下开关瞬间可能有短暂分配延迟。(解读 AI 分析)

④ 行为变化:fallback_enabled_store() 返回值从恒 count 改为 err ?: count;benchmark 失败时每组 err 显式归零(此前错误被隐式吞掉)。sysfs 写回在注册失败时会报告错误,语义更清晰,但对依赖旧行为的脚本可能产生新的失败可见性。(解读 AI 分析)
严重度:潜在 CRITICAL(引用计数竞态)· MAJOR(退役代累积)· MINOR(toggle 全局锁 / 返回语义)。review 质疑:截至归档无公开回复,如实呈现,无已发表质疑可引。

🔗 交叉引用

📌 关联工作
PATCH 0/4 cover letter:crypto: introduce generic dynamic software fallback and EIP93 support — 系列总纲,含完整 benchmark 与阈值基准方法
PATCH 2/4:crypto: introduce dynamic software fallback — 引入 crypto/fallback.c 框架本体(本补丁优化的对象)
crypto: qce - Remove driver(Eric Biggers) — 系列动机引用的 QCE 讨论,提议移除低效加速器
QCE 讨论 v1(2026-07-12) — cover letter 脚注 [1]
QCE 讨论 v2(2026-07-12) — cover letter 脚注 [2]
stevenj/ph-openwrt-mtk-eip93 — 社区对 EIP93 的静态 AES size override(本框架的替代方案)

✅ 关键洞察

  • 发现:补丁解决"特性默认关闭却仍常驻代理、热路径白付一层派发开销"的框架缺陷——把代理注册/注销绑定到 per-device 的 enabled 开关,禁用态新 transform 直连硬件、启用态才注册代理,并用 kref + cra_destroy 保留退役代直到旧 transform 释放。
  • 证据:补丁未单独量化;系列数据(作者自报,未独立验证)显示代理层在 4096B 大请求上较直连约 +4.4% 周期(980→1023),这正是本补丁在禁用态消除的量级;启用后小请求经软件路径 16B 周期 -94.4%、OpenSSL 吞吐 +124.5%。
  • 边界:收益仅作用于"禁用态"(默认形态)——启用态代理刻意存在(那是框架的目的);新建 transform 受益,已绑定代理的旧 transform 保留到释放;本补丁为框架系列一部分,单独合入无意义。
  • 风险 / 建议:最需 review 的是引用计数竞态(dead/registration_complete 三态与 alg_lock);建议维护者补充"长寿命 transform 存在时反复 toggle enabled"的压力测试,并确认退役代内存回收时序;系列尚未获 Crypto 维护者公开回复,等待 v2 讨论。
⚠️ 免责声明

本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。