diff --git a/linux-6.6/.vendor-keep.rsync b/linux-6.6/.vendor-keep.rsync new file mode 100644 index 0000000000000000000000000000000000000000..4f4fd876ee254048d86b6a6972219e8515c90f37 --- /dev/null +++ b/linux-6.6/.vendor-keep.rsync @@ -0,0 +1,73 @@ +# ========================================================================== +# rsync filter / keep-list for vendor upgrade of linux-6.6/ +# ========================================================================== +# +# This file is the SOURCE OF TRUTH for "what to import from a fresh upstream +# linux-6.6.x tarball into linux-6.6/". It is used as an rsync filter file +# during vendor upgrades (see UPGRADE.md). +# +# Filter syntax (see `man 5 rsync` PER-DIRECTORY RULES): +# + pattern include path (and necessary parent dirs must also be +) +# - pattern exclude path +# P pattern protect (don't delete from dst even with --delete) +# *** recursive match including slashes +# / anchored at the transfer root +# +# Recommended invocation: +# rsync -av --delete --filter='. linux-6.6/.vendor-keep.rsync' \ +# linux-6.6./ linux-6.6/ +# +# Default policy: anything not explicitly + is dropped (see final '- *'). +# This is intentional: new upstream subsystems are NOT silently pulled in; +# you must consciously add them here. +# +# ========================================================================== + +# ----- Protect vendor-private files from rsync --delete ----- +# These exist only in our vendor branch (not in the upstream tarball). +# Without P-rules, rsync --delete would wipe them on each upgrade. +P /.vendor-keep.rsync +P /.vendor-strip.list +P /tst-build.sh + +# ----- Top-level dotfiles to keep as upstream ----- ++ /.clang-format ++ /.cocciconfig ++ /.get_maintainer.ignore ++ /.gitattributes ++ /.gitignore ++ /.mailmap ++ /.rustfmt.toml + +# ----- arch/ -- only x86/include is retained ----- ++ /arch/ ++ /arch/.gitignore ++ /arch/x86/ ++ /arch/x86/.gitignore ++ /arch/x86/include/*** + +# ----- drivers/ -- only iommu is retained ----- ++ /drivers/ ++ /drivers/iommu/*** + +# ----- kernel/ -- only bpf is retained ----- ++ /kernel/ ++ /kernel/bpf/*** + +# ----- scripts/ -- fully retained ----- ++ /scripts/*** + +# ----- tools/ -- selftest-related subdirs ----- ++ /tools/ ++ /tools/arch/*** ++ /tools/bpf/*** ++ /tools/build/*** ++ /tools/gpio/*** ++ /tools/include/*** ++ /tools/lib/*** ++ /tools/scripts/*** ++ /tools/testing/ ++ /tools/testing/selftests/*** + +# ----- Drop everything else (default policy) ----- +- * diff --git a/linux-6.6/.vendor-strip.list b/linux-6.6/.vendor-strip.list new file mode 100644 index 0000000000000000000000000000000000000000..f0c27ce5e14750caf3c3f1b51f7e0bd8adbb66a2 --- /dev/null +++ b/linux-6.6/.vendor-strip.list @@ -0,0 +1,166 @@ +# ========================================================================== +# Vendor strip-list for linux-6.6/ (kselftest baseline) +# ========================================================================== +# +# This file is the HUMAN-READABLE reference of "what to delete from a fresh +# upstream linux-6.6.x kernel tarball before importing into linux-6.6/". +# +# IMPORTANT: For the actual upgrade, prefer .vendor-keep.rsync (whitelist +# via rsync filter rules), which is more compact and safer (anything new +# from upstream is dropped by default unless explicitly whitelisted). +# This file is mainly for human inspection / sanity-check. +# +# Path convention: relative to the kernel source root (where COPYING lives). +# Lines starting with '#' and empty lines are ignored. +# +# Usage example (after `tar xf linux-6.6..tar.xz`): +# cd linux-6.6. +# while read -r p; do +# [ -z "$p" ] && continue +# case "$p" in \#*) continue ;; esac +# rm -rf "./$p" +# done < ../linux-6.6/.vendor-strip.list +# +# How to detect new upstream top-level dirs that need adding here: +# diff <(cd linux-6.6. && ls -1) <(cd linux-6.6 && ls -1) +# +# ========================================================================== + +# ----- Top-level subsystem directories not used by kselftest ----- +block +certs +crypto +Documentation +fs +init +io_uring +ipc +LICENSES +lib +mm +net +rust +samples +security +sound +usr +virt + +# ----- Top-level non-kselftest files ----- +COPYING +CREDITS +Kbuild +Kconfig +MAINTAINERS +Makefile +README + +# ----- arch/ : only x86/include is retained ----- +# (Strip every non-x86 architecture) +arch/Kconfig +arch/alpha +arch/arc +arch/arm +arch/arm64 +arch/csky +arch/hexagon +arch/loongarch +arch/m68k +arch/microblaze +arch/mips +arch/nios2 +arch/openrisc +arch/parisc +arch/powerpc +arch/riscv +arch/s390 +arch/sh +arch/sparc +arch/um +arch/x86/Kbuild +arch/x86/Kconfig +arch/x86/Kconfig.assembler +arch/x86/Kconfig.cpu +arch/x86/Kconfig.debug +arch/x86/Makefile +arch/x86/Makefile.postlink +arch/x86/Makefile.um +arch/x86/Makefile_32.cpu +arch/x86/boot +arch/x86/coco +arch/x86/configs +arch/x86/crypto +arch/x86/entry +arch/x86/events +arch/x86/hyperv +arch/x86/ia32 +arch/x86/kernel +arch/x86/kvm +arch/x86/lib +arch/x86/math-emu +arch/x86/mm +arch/x86/net +arch/x86/pci +arch/x86/platform +arch/x86/power +arch/x86/purgatory +arch/x86/ras +arch/x86/realmode +arch/x86/tools +arch/x86/um +arch/x86/video +arch/x86/virt +arch/x86/xen +arch/xtensa + +# ----- drivers/ : only iommu retained ----- +# (Manually maintain: any new drivers/* the upgrade brings in needs review) +# This list is intentionally LEFT BLANK in this file because it would be +# enormous; the .vendor-keep.rsync whitelist is the source of truth. +# Tip to enumerate stripped paths: in upstream tarball: +# ls drivers/ | grep -v '^iommu$' + +# ----- kernel/ : only bpf retained ----- +# Same note as drivers/. Use .vendor-keep.rsync. + +# ----- tools/ : selftest-related subdirs only ----- +# Retained: arch, bpf, build, gpio, include, lib, scripts, testing/selftests +# Stripped (sample, not exhaustive): +tools/accounting +tools/bootconfig +tools/cgroup +tools/counter +tools/firewire +tools/firmware +tools/hv +tools/iio +tools/kvm +tools/laptop +tools/leds +tools/memory-model +tools/mm +tools/net +tools/objtool +tools/pci +tools/pcmcia +tools/perf +tools/power +tools/rcu +tools/spi +tools/testing/cxl +tools/testing/fault-injection +tools/testing/kunit +tools/testing/memblock +tools/testing/nvdimm +tools/testing/radix-tree +tools/testing/scatterlist +tools/testing/vsock +tools/thermal +tools/time +tools/tracing +tools/usb +tools/verification +tools/virtio +tools/vm +tools/wmi +tools/workqueue diff --git a/linux-6.6/UPGRADE.md b/linux-6.6/UPGRADE.md new file mode 100644 index 0000000000000000000000000000000000000000..c6d1144c01245b3bd56fc8ce25f6679aaa7b440c --- /dev/null +++ b/linux-6.6/UPGRADE.md @@ -0,0 +1,318 @@ +# linux-6.6 内核源码升级手册 + +本文档说明如何把 `linux-6.6/` 目录里的精简内核源码升级到一个新的 6.6.x stable 小版本(例如 6.6.99 → 6.6.110),同时不丢失任何私有化改动。 + +> 跨大版本升级(如 linux-6.12)请参考末尾的 [§ 跨大版本升级](#跨大版本升级)。 + +--- + +## 1. 设计概览:Vendor 分支 + 3-way merge + +仓库使用两条核心分支: + +``` +vendor/linux-6.6 ──● (6.6.99 stripped, +tools/bpf) ● ● + │ tag vendor/linux-6.6.99 │ │ + ↓ ↓ tag ↓ tag + │ 6.6.110 6.6.120 + │ +master ─────────────●──●──●──●──● (你的所有私有化 commits) + │ + ↓ git merge vendor/linux-6.6 + ● (合并 6.6.110 升级到 master) +``` + +- `vendor/linux-6.6`:只承载「上游精简后的纯净内核源码」+ vendor 元数据(`.vendor-keep.rsync` / `.vendor-strip.list`)。每次升级只在这条分支上更新,并打 tag `vendor/linux-6.6.`。 +- `master`:承载所有私有化修改(`tst-*.sh` 系列脚本、`runner.sh` 重写、`.code.yml` 等)。通过 `git merge vendor/linux-6.6` 把上游升级合并进来。 + +git 用「3-way merge」识别上游 diff 与私有 diff,只在两者改了同一行时才报冲突。 + +> **共同祖先验证**:`git merge-base master vendor/linux-6.6` 应输出 vendor 分支上"上次升级"对应的 commit。 + +--- + +## 2. 配套工具与文件 + +| 路径 | 分支 | 作用 | +|---|---|---| +| `linux-6.6/.vendor-keep.rsync` | vendor + master(同步) | rsync filter(白名单),是真正干活的工具 | +| `linux-6.6/.vendor-strip.list` | vendor + master(同步) | 人类可读的"应删上游路径"参考清单 | +| `linux-6.6/UPGRADE.md` | master | 本文档 | + +> `.vendor-*` 文件被上游 `linux-6.6/.gitignore` 的 `.*` 规则忽略;首次 `git add` 必须用 `git add -f`,之后已 tracked 不再受影响。 + +--- + +## 3. 升级流程(6.6.99 → 6.6.) + +下面以「升级到 6.6.110」为例。所有命令在仓库根 `tst-open-kselftests/` 下执行。 + +### 3.1 准备工作(与分支无关,建议从 `master` 起步) + +这一步只检查工作区状态、拉远程、下载/解压上游 tarball,**跟在哪条分支上无关**。日常都在 master 工作,所以建议就从 master 开始: + +```bash +# 0. 确认当前在 master(仅为了"日常都从 master 起步"这个习惯) +git checkout master + +# 1. 确保工作区干净(任何分支都得满足) +git status # 应该 nothing to commit, working tree clean + +# 2. 拉取最新远程 +git fetch origin + +# 3. 下载上游 6.6.110 tarball(在 /tmp 处理,跟仓库无关) +NEW=6.6.110 +cd /tmp +wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${NEW}.tar.xz +tar xf linux-${NEW}.tar.xz +# 得到 /tmp/linux-${NEW}/ +``` + +> 真正要切到 vendor 分支的步骤是 3.2 的第一行 `git checkout vendor/linux-6.6`。 + +### 3.2 在 vendor 分支上更新源码 + +```bash +cd +git checkout vendor/linux-6.6 + +# rsync 把上游内容同步进 linux-6.6/,使用 .vendor-keep.rsync 作为 filter +# - 白名单模式:只 + 列表里的路径,其它一律丢弃 +# - --delete:删掉 dst 里 src 没有的文件(旧版本残留) +# - P 规则保护 .vendor-* 和 tst-build.sh 不被 --delete 误删 +rsync -av --delete \ + --filter='. linux-6.6/.vendor-keep.rsync' \ + /tmp/linux-${NEW}/ linux-6.6/ + +# 检查结果(应该没有意外新增的子目录) +git status +``` + +> **如果上游引入了新顶级目录**(如新增了 `linux-6.6/tools/some-new-tool/`),rsync 会因为白名单不匹配而**默认丢弃**它。这是有意设计:不会被静默引入新功能。如果你确实需要这个新目录,请先编辑 `.vendor-keep.rsync` 加入对应的 `+ /path/***` 规则,再重跑 rsync。 + +### 3.3 在 vendor 分支提交并打 tag + +```bash +git add -A linux-6.6/ +git commit -m "vendor: import linux ${NEW} (stripped for kselftest)" +git tag vendor/linux-${NEW} +``` + +### 3.4 切回 master 并合并 + +```bash +git checkout master +git merge vendor/linux-6.6 -m "Merge linux ${NEW} from vendor" +``` + +可能的冲突场景与对策见 [§ 冲突处理](#冲突处理)。 + +### 3.5 push 到远程 + +```bash +git push origin vendor/linux-6.6 vendor/linux-${NEW} master +``` + +--- + +## 4. 冲突处理 + +### 4.1 哪些文件可能冲突 + +只有「**vendor 端 6.6.99 → 6.6.110 改了某行**」**且**「**master 端的私有化也改了同一行**」时才会冲突。当前已知的私有化改动只有: + +- `linux-6.6/tools/testing/selftests/kselftest/runner.sh`(PRIVATE-MOD 标记) + +所以日常升级,最可能的冲突文件就是这一个。 + +### 4.2 解决步骤 + +```bash +# git 报冲突后 +git status # 看哪些文件冲突 +git diff --diff-filter=U # 看冲突内容 + +# 在冲突文件里搜索 # PRIVATE-MOD 标记,确认哪些 hunk 是你的私有修改 +rg "PRIVATE-MOD" linux-6.6/ + +# 手工合并。推荐策略: +# - 上游对 PRIVATE-MOD 区域之外的修改:照单接受 +# - 上游对 PRIVATE-MOD 区域内的修改:核对意图,决定是否吸收 +# - 你的私有修改:保留(带 PRIVATE-MOD 标记) + +# 解决后 +git add +git commit # git 会带上预生成的 merge message +``` + +### 4.3 万一搞砸了 + +```bash +git merge --abort # 中止当前 merge,回到 merge 前状态 +``` + +--- + +## 5. 跨大版本升级(新建 linux-6.12 等) + +复用现有的"另起目录"模式,给每个大版本一条独立的 vendor 分支: + +```bash +git checkout master +mkdir linux-6.12 + +# 1. 完整下载上游 +cd /tmp +wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.x.tar.xz +tar xf linux-6.12.x.tar.xz + +# 2. 把当前 linux-6.6 的精简清单拷贝过去作为起点 +cp linux-6.6/.vendor-keep.rsync linux-6.12/.vendor-keep.rsync +cp linux-6.6/.vendor-strip.list linux-6.12/.vendor-strip.list +cp linux-6.6/tst-build.sh linux-6.12/tst-build.sh # 需要改 cd 路径 + +# 3. 用 keep-list 把上游同步进 linux-6.12/ +rsync -av --filter='. linux-6.12/.vendor-keep.rsync' \ + /tmp/linux-6.12.x/ linux-6.12/ + +# 4. 提交 + 建 vendor 分支 +git add -f linux-6.12/.vendor-* +git add linux-6.12/ +git commit -m "add linux-6.12.x" + +git tag vendor/linux-6.12.x HEAD +git branch vendor/linux-6.12 HEAD +``` + +后续 6.12 系列升级流程与本文档第 3 节相同,只是路径换成 `linux-6.12/` 与 `vendor/linux-6.12`。 + +--- + +## 6. 维护:新增(或删除)上游源码路径 + +`.vendor-keep.rsync` 是**白名单**,任何上游新增的内核子系统/子目录都会被默认丢弃。**这是有意的**:避免静默引入不需要的代码。 + +如果你发现 kselftest 编译/运行时缺了某个上游路径,或者升级新版本时上游引入了新的依赖目录,需要把对应路径加入白名单并补源码进来。下面是完整流程。 + +### 6.1 黄金法则:先改 keep-list,后补源码 + +> **核心顺序**:`.vendor-keep.rsync` 必须先加 `+` 规则,再补对应源码。否则下次升级 rsync `--delete` 会把刚加进来的源码删掉。 + +### 6.2 标准 4 步流程 + +```bash +git checkout vendor/linux-6.6 + +# 第 1 步:更新白名单 +vim linux-6.6/.vendor-keep.rsync +# - 子目录递归保留:+ /tools/testing/some-new-test/*** +# - 单文件保留: + /lib/some_helper.h +# - 中间目录散文件:+ /path/.gitignore(不能只 + /path/,参考 §6.4 易错点) + +# 第 2 步:把对应源码补进来 +# 方式 A:从已经解压好的上游 tarball 复制 +cp -r /tmp/linux-6.6.99/lib/some_helper.h linux-6.6/lib/ +# +# 方式 B(推荐):用 rsync 同步,会按更新后的 keep-list 自动拉取 +rsync -av --filter='. linux-6.6/.vendor-keep.rsync' \ + /tmp/linux-6.6.99/ linux-6.6/ + +# 第 3 步:commit +git add -A linux-6.6/ +git commit -m "vendor: include (needed by kselftest )" +# 可选:如果想标记基线变动,打个 fix tag +# git tag vendor/linux-6.6.99-fix1 + +# 第 4 步:merge 到 master +git checkout master +git merge vendor/linux-6.6 -m "Merge vendor: include " +``` + +### 6.3 两种典型场景 + +#### 场景 A:当前版本就缺源码 + +最常见情况。比如运行 kselftest 编译失败提示 `lib/some_helper.h: No such file`,按上面 4 步走。 + +#### 场景 B:升级时发现上游新增了路径 + +更优雅做法:把"加 keep-list 规则"和"升级"合并成一次 commit。先做评估,再走标准升级流程: + +```bash +git checkout vendor/linux-6.6 + +# 0a. 看上游新 tarball 比当前版本多了哪些顶级目录 +diff <(ls /tmp/linux-6.6.110/) <(ls linux-6.6/) | grep '^<' | head + +# 0b. 看 selftests/ 目录新增了哪些子测试 +diff <(ls linux-6.6/tools/testing/selftests/) \ + <(ls /tmp/linux-6.6.110/tools/testing/selftests/) | grep '^>' + +# 1. 决定保留哪些 → 编辑 keep-list +vim linux-6.6/.vendor-keep.rsync + +# 2. 走 §3 标准升级流程,rsync 会自动按新 keep-list 拉取 +``` + +### 6.4 易错点 + +| 易错点 | 后果 | 正确做法 | +|---|---|---| +| 只补源码、忘改 keep-list | 下次升级 rsync `--delete` 会把新加的源码删掉 | **先**改 keep-list,**再**补源码 | +| 中间目录散文件只写 `+ /path/` | 目录里的散文件(如 `.gitignore`)被默默丢弃 | 子目录用 `+ /path/***` 递归;散文件单独 `+ /path/file` 列出 | +| 直接在 master 上加上游路径 | vendor 不知道这事,下次升级要么误删要么 add/add 冲突 | 在 vendor 分支加,再 merge 到 master | +| 在 vendor 上加的内容 master 已经有了 | merge 时 add/add 冲突 | 让 vendor 加的内容跟 master 完全一致(来源同上游、内容 hash 相同),git 会自动识别为同一份新增、无冲突。这正是初始化时 `tools/bpf` 修复采用的套路 | +| 忘了同步更新 `.vendor-strip.list` | 文档与真实白名单不一致(仅文档对照不准,不影响功能) | 顺手把对应条目从 strip-list 删掉 | + +### 6.5 提交前的安全检查 + +正式 commit 前可以 dry-run 一次,确认 keep-list 改动符合预期: + +```bash +# 假设 /tmp/linux-6.6.99/ 是上游 tarball 解压目录 +rsync -avn --filter='. linux-6.6/.vendor-keep.rsync' \ + /tmp/linux-6.6.99/ /tmp/preview/ 2>/dev/null \ + | grep "your-new-path" +# 期望:能看到目标路径出现在 transfer list 里 +``` + +更彻底的「无遗漏」回放检查(用 vendor 分支自身当伪上游,对比 git tree vs rsync 实际同步集): + +```bash +TMP=$(mktemp -d) +git archive vendor/linux-6.6 linux-6.6/ | tar -x -C "$TMP" + +git ls-tree -r --name-only vendor/linux-6.6 -- linux-6.6/ \ + | sed 's|^linux-6.6/||' \ + | grep -v -E '^(\.vendor-|tst-build\.sh)' \ + | sort > /tmp/expected.txt + +rsync -avn --filter=". $TMP/linux-6.6/.vendor-keep.rsync" \ + "$TMP/linux-6.6/" /tmp/dst/ 2>/dev/null \ + | grep -v -E '^(sending|sent |total |\(DRY|^$|\./$)' \ + | grep -v '/$' | sort > /tmp/actual.txt + +# 真正遗漏(剔除 symlink 输出格式造成的误报) +comm -23 /tmp/expected.txt /tmp/actual.txt \ + | while read -r f; do test -L "$TMP/linux-6.6/$f" || echo "$f"; done +# 期望:无输出 +``` + +--- + +## 7. 调试小贴士 + +```bash +# 看当前 master 相对最近 vendor 升级有哪些私有 diff +git diff vendor/linux-6.6.99..master -- linux-6.6/ + +# 看上游 6.6.99 → 6.6.110 改了哪些路径 +git diff vendor/linux-6.6.99..vendor/linux-6.6.110 --stat + +# 找所有 PRIVATE-MOD 标记 +rg "PRIVATE-MOD" linux-6.6/ + +# 验证 vendor 是 master 的祖先 +git merge-base --is-ancestor vendor/linux-6.6.99 master && echo "OK" +``` diff --git a/linux-6.6/tools/testing/selftests/kselftest/runner.sh b/linux-6.6/tools/testing/selftests/kselftest/runner.sh index ea76d4a8f14f0c70799028a4d3be62bb42c2302c..7e3d0a32bac8caff4487602b104d055e9a51a356 100644 --- a/linux-6.6/tools/testing/selftests/kselftest/runner.sh +++ b/linux-6.6/tools/testing/selftests/kselftest/runner.sh @@ -10,6 +10,7 @@ export per_test_logging= # Defaults for "settings" file fields: # "timeout" how many seconds to let each test run before running # over our soft timeout limit. +# PRIVATE-MOD: bumped from upstream 45s to 300s for slower test environments. export kselftest_default_timeout=300 # There isn't a shell-agnostic way to find the path of a sourced file, @@ -43,6 +44,25 @@ tap_timeout() fi } +# ===================================================================== +# PRIVATE-MOD START: rewrite of run_one() for the tst framework +# --------------------------------------------------------------------- +# Differences vs upstream run_one(): +# 1. Result is persisted to "$RC_FILE" (0=PASS, 1=FAIL, 2=TIMEOUT, +# 4=SKIP), so an outer harness can collect per-case status across +# multiple invocations -- upstream relies only on `return $rc`. +# 2. TAP-13 compliant output ("ok / not ok $test_num ") instead +# of the upstream custom "[PASS]/[FAIL]/[SKIP]/[TIMEOUT]" format. +# 3. Accepts a third positional arg ($NUM = test_num) used in the +# TAP output above. +# 4. Drops the upstream `cd $CUR_DIR; return $rc` epilogue and uses +# `cd - >/dev/null` instead. +# +# When merging upstream changes to runner.sh, KEEP these private +# semantics intact -- the wrapping framework (tst-list-cases.sh / +# tst-install-cases.sh) depends on them. +# PRIVATE-MOD END +# ===================================================================== run_one() { DIR="$1" diff --git a/linux-6.6/tst-list-cases.sh b/linux-6.6/tst-list-cases.sh new file mode 100755 index 0000000000000000000000000000000000000000..a4fc8a2cd4862454323720ee09419a0c8dcaeaf2 --- /dev/null +++ b/linux-6.6/tst-list-cases.sh @@ -0,0 +1,339 @@ +#!/bin/bash + +# Desc: 列出 kselftest 全量测试程序清单(程序粒度,与编译环境无关) +# +# 默认(未显式设置 ARCH 环境变量):多架构汇总模式 +# 依次以 ARCH=arm64 / ARCH=x86 / ARCH=loongarch 各生成一份清单, +# 再求三者并集,输出到 kselftest-cases.all.txt。 +# 过程中会同时保留三份单架构清单作为副产物(与并集同目录): +# kselftest-cases.aarch64.txt +# kselftest-cases.amd64.txt +# kselftest-cases.loongarch64.txt +# +# 显式只生成单一架构的清单(向后兼容老用法): +# ARCH=arm64 ./tst-list-cases.sh kselftest-cases.aarch64.txt +# ARCH=x86 ./tst-list-cases.sh kselftest-cases.amd64.txt +# ARCH=loongarch ./tst-list-cases.sh kselftest-cases.loongarch64.txt +# +# Args: +# $1 -- 输出文件路径,可选 +# - 默认模式(多架构汇总)默认值: ./kselftest-cases.all.txt +# - 单架构模式(显式设置 ARCH)默认值: ./kselftest-cases.txt +# +# 实现说明: +# 通过对每个 TARGETS 目录调用 lib.mk 的 emit_tests target 求值, +# 借助 make 自动展开 wildcard / shell / 条件分支 等动态生成, +# 得到与编译产物无关、与上游 install 阶段一致的"理论完整"清单。 +# +# 对于 arm64/powerpc/riscv 这类"递归型 Makefile"(自身没用 lib.mk, +# 而是在 emit_tests 中递归调子目录),上游实现丢失了子目录信息 +# (输出 arm64:hwcap 而非 arm64/abi:hwcap)。本脚本会下钻到二级目录 +# 直接调用其 lib.mk emit_tests,保留完整的子目录路径。 +# +# 输出格式: : +# 例如: +# bpf:test_progs +# net:reuseport_addr_any.sh +# kvm:hyperv_clock +# arm64/abi:hwcap ← 下钻后的格式,保留 abi 这层 +# arm64/signal:sve_regs +# kvm/aarch64:arch_timer ← kvm 的子架构目录格式,见下文 +# kvm/x86_64:amx_test +# +# 注:上游 lib.mk 的 emit_tests 对每个 case 调用 basename,会把 +# kvm/Makefile 里 "aarch64/arch_timer" 这种带子目录前缀的 case +# 退化成 "kvm:arch_timer"。本脚本通过 -f Makefile -f 注入 +# 一个不抹掉子目录的 emit_tests_full 目标,把目录部分拼回 collection。 +# +# 注:6.6 上游 selftests 没有 loongarch 顶层目录,kvm 也没有 +# TEST_GEN_PROGS_loongarch,因此 loongarch 下输出比 aarch64/amd64 少 +# 一些架构相关 case,属预期行为;这些差异在三架构并集里会自动被纳入。 + +set -u + +g_tool_dir="$(dirname "$(realpath "$0")")" +g_selftests_dir="$g_tool_dir/tools/testing/selftests" + +# 模式判断:未显式设置 ARCH 时进入多架构汇总模式(默认) +g_multi_arch=0 +[ -z "${ARCH:-}" ] && g_multi_arch=1 + +# 输出路径在 cd 之前先 resolve 为绝对路径,避免脚本内 cd 后相对路径失效 +if [ "$g_multi_arch" = "1" ]; then + g_out_raw="${1:-kselftest-cases.all.txt}" +else + g_out_raw="${1:-kselftest-cases.txt}" +fi +case "$g_out_raw" in + /*) g_out="$g_out_raw" ;; + *) g_out="$(pwd)/$g_out_raw" ;; +esac + +# 已知的"host 环境陷阱"——某些 Makefile 用 host 工具/编译器检测控制 case 列表, +# 不受命令行 ARCH= 影响。这里强制传入"乐观值"绕过,确保任意 host 上都能列全。 +# +# 注:drivers/s390x/uvdevice/Makefile 用 UNAME_M 直接判 host 是否 s390x, +# 由于当前不再支持 s390x,这里不再强制打开它;非 s390x host 上自然不会 +# 出现在输出中,避免 aarch64/amd64/loongarch64 清单里夹带不可用的 s390x case。 +get_force_vars_for() { + local dir="$1" + # 规范化当前 ARCH(与 selftests 各 Makefile 中 ARCH 归一化保持一致) + local arch="${ARCH:-$(uname -m 2>/dev/null)}" + case "$arch" in + x86_64|i386|i686) arch=x86 ;; + aarch64) arch=arm64 ;; + ppc*) arch=powerpc ;; + esac + + case "$dir" in + # x86/Makefile 完全不看 $(ARCH),而是用 ./check_cc.sh 探测 host + # 能不能编 32/64 位 x86。这里按当前 ARCH 显式短路 host 探测: + # ARCH=x86 时强制开 64 位、关 32 位(防止装了 gcc-multilib 的 + # x86_64 host 上 check_cc.sh 把 32 位也认了进来) + # 其他 ARCH 全部关掉(避免 loongarch64/aarch64 等清单里混进 x86 case, + # 因为 host 多半是 x86_64,check_cc.sh 不被短路就会自然 pass) + x86) + if [ "$arch" = "x86" ]; then + echo "CAN_BUILD_I386=0 CAN_BUILD_X86_64=1 CAN_BUILD_WITH_NOPIE=1" + else + echo "CAN_BUILD_I386=0 CAN_BUILD_X86_64=0 CAN_BUILD_WITH_NOPIE=0" + fi + ;; + # mm/Makefile 在 ARCH=x86_64 时也会检测 32 位编译能力 + # (注:mm 加的是 TEST_GEN_FILES,emit_tests 本就不输出,此处仅语义对齐) + mm) + echo "CAN_BUILD_I386=0 CAN_BUILD_X86_64=1" ;; + # bpf/Makefile 检测 host 是否装了 bpf-gcc 和 clang 18+ (cpuv4) + bpf) + echo "BPF_GCC=/bin/true CLANG_CPUV4=1" ;; + esac +} + +# Wrapper Makefile:注入一个不抹掉子目录的 emit_tests_full 目标。 +# +# 背景:lib.mk 默认的 emit_tests 对每项 case 调用 basename,会把 +# kvm/Makefile 里 "aarch64/arch_timer" 这种带子目录前缀的 case 退化成 +# "kvm:arch_timer"。这里改用相对路径,把目录部分拼到 COLLECTION 后面, +# 保留 "kvm/aarch64:arch_timer" 这种格式。 +# +# 仅当目录依赖 lib.mk 提供 emit_tests 时使用本 wrapper;目录自身重写了 +# emit_tests 的(如 powerpc/pmu/Makefile 会递归到 ebb/sampling_tests/ +# event_code_tests 等孙目录拉 case),必须仍然调用其原生 emit_tests, +# 否则会丢失这些孙目录的 case(且这类目录的 TEST_GEN_PROGS 都是扁平的, +# basename 也不会带来路径丢失问题)。 +g_emit_wrapper=' +emit_tests_full: + @for TEST in $(TEST_GEN_PROGS) $(TEST_CUSTOM_PROGS) $(TEST_PROGS); do \ + REL=$${TEST#$(OUTPUT)/}; \ + DIR=$$(dirname "$$REL"); \ + BASE=$$(basename "$$REL"); \ + if [ "$$DIR" = "." ]; then \ + echo "$(COLLECTION):$$BASE"; \ + else \ + echo "$(COLLECTION)/$$DIR:$$BASE"; \ + fi; \ + done +' + +# 直接调用某个目录的 emit_tests +emit_one() { + local dir="$1" + local mk="$dir/Makefile" + [ -f "$mk" ] || return 0 + + local force_vars="" + + # 1) 自动扫描 `xxx_cc_support := $(shell $(CC) -march=... )` 类的编译器检测 + # (arm64/mte/Makefile 检 ARMv8.5 MTE,arm64/pauth/Makefile 检 ARMv8.3 PAuth 等) + local v + while IFS= read -r v; do + [ -n "$v" ] && force_vars+=" $v=1" + done < <(grep -oE '^[[:space:]]*[a-zA-Z_]+_cc_support[[:space:]]*:=' "$mk" 2>/dev/null \ + | sed -E 's/[[:space:]]*:=$//; s/^[[:space:]]*//') + + # 2) 已知陷阱目录的 host 检测变量 + local known_force + known_force="$(get_force_vars_for "$dir")" + [ -n "$known_force" ] && force_vars+=" $known_force" + + # 3) 选择 emit 实现:自定义 emit_tests 用原生(保留递归等副作用), + # 否则走 wrapper(修复 kvm 的子目录前缀丢失问题) + # shellcheck disable=SC2086 # force_vars 需要拆词 + if grep -qE '^emit_tests:' "$mk"; then + make -s -C "$dir" emit_tests COLLECTION="$dir" $force_vars 2>/dev/null + else + make -s -C "$dir" -f Makefile -f <(printf '%s' "$g_emit_wrapper") \ + emit_tests_full COLLECTION="$dir" $force_vars 2>/dev/null + fi +} + +# 处理一个 TARGET:自动判断是 "标准型 / 混合型 / 递归型",分别处理 +process_target() { + local t="$1" + local mk="$t/Makefile" + [ -f "$mk" ] || return 0 + + # 标准型 / 混合型:顶层未重写 emit_tests,沿用 lib.mk 默认实现 + if ! grep -qE '^emit_tests:' "$mk"; then + # 1) 先取顶层 case(如 futex:run.sh) + local top_out + top_out="$(emit_one "$t")" + [ -n "$top_out" ] && printf '%s\n' "$top_out" + + # 2) 混合型补丁:顶层借助 lib.mk 输出自己的 TEST_PROGS, + # 同时通过自定义 all / INSTALL_RULE 递归构建 SUBDIRS 下的子目录 + # (如 futex: SUBDIRS := functional,真实 case 都在 functional/ 里)。 + # lib.mk 的默认 emit_tests 不会跟进 SUBDIRS,必须这里手动下钻。 + # + # 门控:仅当顶层 emit_one 有产出才下钻,避免 sparc64 这类 + # `ifneq ($(ARCH),sparc64)` 早退、lib.mk 根本未被 include 的目录 + # 在非 sparc64 host 上被误纳入清单。 + if [ -n "$top_out" ]; then + local subdirs + subdirs=$(grep -E '^SUBDIRS[[:space:]]*:=' "$mk" \ + | sed -E 's/^SUBDIRS[[:space:]]*:=[[:space:]]*//') + local sub + for sub in $subdirs; do + local sub_mk="$t/$sub/Makefile" + [ -f "$sub_mk" ] || continue + grep -qE 'include.*lib\.mk' "$sub_mk" || continue + emit_one "$t/$sub" + done + fi + return 0 + fi + + # 递归型(如 arm64/powerpc/riscv):下钻到二级目录 + # 上游实现会丢失子目录信息(COLLECTION 不重置),这里绕过去 + # + # ARCH 守卫:arm64/riscv/powerpc 的顶层 Makefile 都用 + # ifeq/ifneq ARCH ... 把 SUB_DIRS / SUBTARGETS 在非目标架构上设为空 + # (arm64/riscv 注释里明写 "Avoid any output on non arm64 on emit_tests", + # powerpc 则是 SUB_DIRS 整个被 ifeq 守卫,emit_tests 在空集上循环)。 + # 我们下钻是为了修 lib.mk 默认 emit_tests 丢子目录前缀的问题,并不想 + # 绕开 ARCH 守卫——否则在非目标架构清单里会混进一堆编不出/跑不了的 case + # (如 loongarch64 清单里出现 arm64/abi:hwcap、powerpc/pmu:l3_bank_test 等)。 + # 所以先调一次上游原生 emit_tests 做探测:输出为空就尊重上游、整个 target 跳过。 + local probe + probe="$(make -s -C "$t" emit_tests COLLECTION="$t" 2>/dev/null)" + [ -n "$probe" ] || return 0 + + local found=0 + for sub_mk in "$t"/*/Makefile; do + [ -f "$sub_mk" ] || continue + # 只对真正使用 lib.mk 的子目录调用 + if grep -qE 'include.*lib\.mk' "$sub_mk"; then + emit_one "$(dirname "$sub_mk")" + found=1 + fi + done + + # 如果没找到任何二级目录,回退到原始 emit_tests + [ "$found" = "1" ] || emit_one "$t" +} + +# 子进程入口:处理单个 target,把结果写到 $g_tmp_dir/.out +# 由 xargs -P 在子 shell 中调用,需要重新进入 selftests 目录 +_worker_one_target() { + local t="$1" + cd "$g_selftests_dir" || return 1 + local safe="${t//\//_}" + process_target "$t" > "$g_tmp_dir/$safe.out" +} + +# 列出单架构清单(输出到 $1 指定路径)。 +# 用 subshell () 隔离 trap / cd / 局部变量,便于多架构模式下连续调用。 +_list_single_arch() ( + local out="$1" + + if [ ! -f "$g_selftests_dir/Makefile" ]; then + echo "Error: $g_selftests_dir/Makefile not found" >&2 + return 1 + fi + + cd "$g_selftests_dir" || return 1 + + local arch_label="${ARCH:-host}" + + # 提取顶层 TARGETS(不受 SKIP_TARGETS 影响,拿全集) + local targets + targets=$(grep -E '^TARGETS \+= ' Makefile | awk '{print $NF}') + + # 并发度:默认 nproc,可通过环境变量 JOBS 覆盖 + local jobs="${JOBS:-$(nproc 2>/dev/null || echo 4)}" + + # 临时目录存放每个 target 的输出,最后按 TARGETS 原顺序合并 + g_tmp_dir="$(mktemp -d -t kselftest-list.XXXXXX)" + trap 'rm -rf "$g_tmp_dir"' EXIT + + # 把待并行执行的函数和必要变量导出到子 shell + export g_selftests_dir g_tmp_dir g_emit_wrapper + export -f _worker_one_target process_target emit_one get_force_vars_for + + echo "==> [ARCH=$arch_label] listing kselftest cases (jobs=$jobs)..." + # xargs -P 多核并行:每个 target 一个子 bash,独立写自己的临时文件 + printf '%s\n' $targets | xargs -n1 -P "$jobs" -I{} \ + bash -c '_worker_one_target "$@"' _ {} + + # 按 TARGETS 原顺序合并,输出稳定(与串行版本一致) + : > "$out" + for t in $targets; do + local safe="${t//\//_}" + local f="$g_tmp_dir/$safe.out" + [ -s "$f" ] && cat "$f" >> "$out" + done + + echo " [ARCH=$arch_label] -> $out ($(wc -l < "$out") cases)" +) + +# 多架构汇总:依次以 ARCH=arm64 / x86 / loongarch 跑 _list_single_arch, +# 再求三者并集(保持稳定顺序:每行首次出现的位置不变,便于 diff 复查)。 +# 三份单架构清单同时保留为副产物,与并集同目录。 +_list_all_arches() { + local all_out="$1" + local out_dir + out_dir="$(dirname "$all_out")" + mkdir -p "$out_dir" + + # pair 形式:上游 ARCH 名:输出文件名后缀 + local pairs=( + "arm64:aarch64" + "x86:amd64" + "loongarch:loongarch64" + ) + local pair + local per_files=() + for pair in "${pairs[@]}"; do + local a="${pair%%:*}" + local s="${pair##*:}" + local per_out="$out_dir/kselftest-cases.$s.txt" + export ARCH="$a" + _list_single_arch "$per_out" || return 1 + per_files+=("$per_out") + done + unset ARCH + + # 求并集,保持稳定顺序:先 arm64,再补 x86 / loongarch 新增的 + awk '!seen[$0]++' "${per_files[@]}" > "$all_out" + + echo "==> Union of three arches -> $all_out" + local f + for f in "${per_files[@]}"; do + printf ' %-60s : %s cases\n' "$f" "$(wc -l < "$f")" + done + printf ' %-60s : %s cases\n' "$all_out (union)" "$(wc -l < "$all_out")" +} + +main() { + if [ "$g_multi_arch" = "1" ]; then + _list_all_arches "$g_out" + else + _list_single_arch "$g_out" + fi + + echo "By collection (top 20):" + awk -F: '{print $1}' "$g_out" | sort | uniq -c | sort -rn | head -20 + echo "Output: $g_out" +} + +main "$@"