跳转到内容
新建笔记

VibraSense AD7768 XDMA 采集 SDK 设计、部署与验收

本阶段完成了振动分析仪 FPGA 到 i.MX8MP 应用之间的本机采集 SDK,形成以下产品数据路径:

flowchart LR
ADC["AD7768-4<br/>4 通道 24-bit"]
FPGA["FPGA 采样与 64 KiB 组包"]
XDMA["PCIe XDMA<br/>Streaming C2H"]
DEV["/dev/xdma0_c2h_0<br/>/dev/xdma0_user"]
DAEMON["vib-acqd<br/>唯一硬件所有者"]
IPC["Unix Domain Socket<br/>IPC 2"]
LIB["libvib_acq_client.so.2<br/>C ABI 2"]
APP["Qt GUI / 算法 / 记录工具"]
ADC --> FPGA --> XDMA --> DEV --> DAEMON --> IPC --> LIB --> APP

候选版本信息:

项目值
SDK 版本0.2.0
SDK 分支codex/complete-sdk
已验收提交f2da2c364f9892387c28e3a59d996bc23030b2f3
公开 C ABI2
daemon IPC2
动态库 SONAMElibvib_acq_client.so.2
目标系统OK8MP,Linux 5.4.70-2.3.0,glibc 2.30
FPGA 协议0x00010002
FPGA build0x20260818
通道与采样率4 通道,256000 frame/s/channel

已经可以交付 Qt/算法团队的是:

  • vib-acqd 本机采集服务;
  • libvib_acq_client.so.2 公开 C ABI;
  • CMake package VibAcq::client 和 pkg-config 元数据;
  • 固定四通道交织 int32_t block 格式;
  • 数据不连续、FPGA 丢帧、坏包、断序和主机缓存溢出的显式错误语义;
  • systemd 服务、部署流程和实机验收工具。

必须准确保留两个独立结论:

  1. FPGA packetizer -> XDMA -> daemon -> C ABI 的 30 分钟传输通过。 坏包、断序、标签错误、FPGA dropped frame、FPGA ingress drop 和 host overrun 都为 0。
  2. 完整 AD7768 输入链路未达到零错误。 同一次长测中 fpga_frame_errors=19,表示 128-bit TDM 帧未接收完成时又检测到下一次 DRDY 下降沿。下一步应排查 DCLK、DRDY、DOUT0 的输入时序和信号完整性。

版本提醒:目标板已经部署并验证 ABI 2 产物,但截至本记录复核时,SDK 自身仓库的 codex/complete-sdk 尚未合入其远端 main。跨电脑继续开发前,应先按代码仓库流程 推送或合并该提交,不能只依赖某一台电脑上的工作区和目标板已安装二进制。

FPGA 侧当前产品基线和 bitstream 构建见 XC7A35T 无 DDR XDMA C2H 直通采集链路。本页只负责 i.MX8MP 本机 SDK、应用契约和验收,不重复 FPGA RTL 细节。

直接让 Qt 程序打开 /dev/xdma0_c2h_0 看起来简单,但会把以下问题全部推入 GUI:

  • FPGA 启停、清零和状态寄存器访问;
  • XDMA 阻塞读、超时和短传输恢复;
  • 64 KiB packet 校验和四通道标签校验;
  • 4092-frame packet 到 4096-frame 应用 block 的重组;
  • 多线程停止、客户端断开和 systemd 关闭;
  • 数据丢失统计、错误传播和版本兼容;
  • GUI、记录器和诊断程序竞争同一个 C2H 数据流。

因此最终采用“单一硬件所有者 + 本机 IPC + 稳定 C ABI”:

产品运行时:
vib-acqd 独占 /dev/xdma0_user 和 /dev/xdma0_c2h_0
普通应用:
Qt / C / C++ -> libvib_acq_client.so -> /run/vib-acqd.sock
底层诊断例外:
停止 vib-acqd -> ad7768_xdma_monitor 独占 XDMA -> 恢复 vib-acqd

这样 GUI 团队只处理稳定的 block、状态和错误码,不需要理解 XDMA 描述符、FPGA CSR 或 packet header。

本 SDK 负责:

  • FPGA packet 的读取、校验和重组;
  • FPGA CSR 的身份、配置、启停、清零和状态访问;
  • 有界内存缓存;
  • Unix Socket 服务;
  • C ABI、示例程序和原始数据记录;
  • 数据完整性统计、错误传播和恢复。

本 SDK 不负责:

  • AD7768 SPI 寄存器方案和模拟前端精度;
  • VREF、IEPE 前端增益、传感器灵敏度和零偏标定;
  • FFT、包络、阶次跟踪、动平衡等算法;
  • Qt 页面、绘图和交互;
  • 多设备硬件同步和绝对时间基准;
  • 多个独立客户端同时订阅全速数据。

SDK 保存的是 ADC integer code。工程量换算和标定属于上层:

volts = code * vref_volts / 8388608.0

默认 VREF=4.096 V 时约为 0.48828125 µV/LSB,但该数字不等于整机精度。

SDK 源码根目录记为 <sdk_src>,即产品仓库中的 interfaces/vib_acq_sdk。

<sdk_src>/
├── include/vib_acq_client.h # 公开 C ABI
├── src/common/ # FPGA 协议、解析、重组、Ring Buffer
├── src/xdma/ # XDMA 字符设备封装
├── src/fpga/ # FPGA CSR 控制
├── src/daemon/ # AcquisitionService、IPC server、vib-acqd
├── src/client/ # Unix Socket client 和 C ABI 实现
├── tools/ # 示例、raw 记录、独占诊断
├── tests/ # 无硬件单元和端到端测试
├── systemd/ # vib-acqd.service 模板
└── docs/ # 部署、集成和实机验收记录

依赖方向固定为:

flowchart TD
COMMON["common<br/>协议 / 重组 / Ring Buffer"]
XDMA["xdma<br/>设备访问"]
FPGA["fpga<br/>CSR 控制"]
DAEMON["daemon<br/>状态机 / DMA worker / IPC"]
CLIENT["client<br/>公开 C ABI"]
TOOLS["client tools"]
MONITOR["direct monitor"]
FPGA --> XDMA
DAEMON --> COMMON
DAEMON --> FPGA
DAEMON --> XDMA
CLIENT --> COMMON
TOOLS --> CLIENT
MONITOR --> COMMON
MONITOR --> FPGA
MONITOR --> XDMA

关键所有权规则:

  • DMA worker 是 C2H 唯一读取者;IPC thread 不直接读 DMA;
  • vib-acqd 是产品运行时唯一 XDMA 所有者;
  • FPGA CSR 顺序访问通过同一个 mutex 串行化;
  • daemon 当前只接受一个活动客户端;
  • vib_acq_block_t.samples 始终由调用者分配和释放;
  • vib_acq_close() 不能与同一 handle 的其他 API 并发。

5. FPGA packet 与应用 block 契约

跳转到“5. FPGA packet 与应用 block 契约”

每次 C2H 读取固定为 65536 字节:

区域大小内容
packet header64 Bmagic、协议、序号、计数和状态
payload65472 B4092 个四通道 frame
合计65536 B一次完整 XDMA transfer

每个 frame 是 16 字节:

CH0 word, CH1 word, CH2 word, CH3 word

每个 32-bit channel word:

[31:24] 通道标签:00 / 01 / 02 / 03
[23:0] signed 24-bit 二补码 ADC code

SDK 必须验证标签和 packet 连续性,再将 24-bit code 符号扩展为 int32_t。

5.2 为什么 packet 是 4092 frame,应用 block 是 4096 frame

跳转到“5.2 为什么 packet 是 4092 frame,应用 block 是 4096 frame”

64 KiB packet 需要保留 64-byte header,因此 payload 只能容纳:

(65536 - 64) / 16 = 4092 frame

GUI 和算法更适合使用 2 的幂长度,所以 SDK 通过跨 packet 重组输出 4096-frame block:

4096 frame * 4 channel * 4 byte = 65536 byte/application block

在 256000 frame/s 下:

一个 block = 4096 / 256000 = 16 ms
每秒 block = 62.5
原始吞吐 = 256000 * 4 * 4 = 4,096,000 byte/s

重组器必须保持连续 sample index,不能在 packet 边界伪造连续性。

默认 --ring-blocks 256:

256 * 65536 byte = 16 MiB
256 * 16 ms = 4.096 s

队列满时拒绝新 block 并增加 host_overruns,不覆盖尚未消费的旧数据。这样应用能明确 知道“发生了数据缺口”,而不是拿到看似连续但实际已覆盖的数据。

标准调用顺序:

vib_acq_t *acq = NULL;
vib_acq_open(&acq);
vib_acq_get_info(acq, &info);
vib_acq_configure(acq, &config);
vib_acq_start(acq);
vib_acq_read_block(acq, &block, 2000);
vib_acq_get_status(acq, &status);
vib_acq_stop(acq);
vib_acq_close(acq);

调用者至少准备:

4096 frame * 4 channel = 16384 个 int32_t 元素

sample_capacity 的单位是元素,不是字节。

6.2 DATA_LOSS 不是普通成功

跳转到“6.2 DATA_LOSS 不是普通成功”

VIB_ACQ_ERROR_DATA_LOSS 的语义:

  • read_block() 返回时可能仍带有一个完整 block,并设置“此前不连续”flag;
  • stop() 返回时表示停止和清理已完成,但最终排空或 FPGA 诊断发现本会话有数据丢失;
  • 应用必须保存诊断、标记记录不连续,并用新会话继续;
  • 不得把它等同于 VIB_ACQ_OK,也不应把它误解成 daemon 未能停止。

工具退出码约定:

退出码含义
0完整成功
1连接、I/O 或服务错误
2参数错误
3检测到数据不连续或非零完整性计数

Qt/C++ 项目:

find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(vib_acq_client CONFIG REQUIRED)
target_link_libraries(vibration_gui PRIVATE
Qt6::Core
VibAcq::client
)

普通 C 项目:

终端窗口
cc main.c -o acquisition_app $(pkg-config --cflags --libs vib_acq_client)

系统目录安装后执行 ldconfig。产品部署不要依赖临时 LD_LIBRARY_PATH。

vib_acq_read_block() 是阻塞调用,不能放在 GUI 线程:

flowchart LR
WORKER["采集 worker / QThread<br/>read_block 全速读取"]
DSP["算法线程<br/>FFT / 包络 / 统计"]
GUI["GUI 线程<br/>降采样波形 / 状态"]
WORKER --> DSP
WORKER --> GUI

建议:

  • 一个 worker 持有唯一 vib_acq_t;
  • worker 连续读取完整 block;
  • 算法线程消费全速 integer code;
  • GUI 只接收降采样波形、频谱结果和状态;
  • GUI、记录器和示例不能同时各自打开 handle;
  • 如果未来需要 GUI 与记录器并行,必须增加进程内分发或服务端发布/订阅,不能让多个 进程竞争 C2H/Ring Buffer。

阶段 1:建立可测试的数据层

跳转到“阶段 1:建立可测试的数据层”

先在不依赖硬件的层中完成:

  1. FPGA packet 常量和 header 定义;
  2. 64 KiB packet 解析;
  3. 通道标签和 signed 24-bit 转换;
  4. 4092 -> 4096 frame 重组;
  5. 固定容量 Ring Buffer;
  6. Windows/Linux 主机单元测试。

这样可以在接触真实 XDMA 前锁定数据格式和边界条件。

阶段 2:封装 XDMA 和 FPGA CSR

跳转到“阶段 2:封装 XDMA 和 FPGA CSR”

建立两层:

  • src/xdma 只负责字符设备、顺序读写和系统错误;
  • src/fpga 负责 identity、配置、STOP、CLEAR、START 和状态快照。

FPGA 协议版本不匹配、link down、clear timeout 都必须阻止启动,不能降级成警告。

阶段 3:实现 daemon 状态机

跳转到“阶段 3:实现 daemon 状态机”

AcquisitionService 负责:

  • 启动顺序;
  • DMA worker 生命周期;
  • packet 校验、block 重组和 Ring Buffer;
  • 完整性累计计数;
  • 停止、排空和恢复;
  • 单客户端资源释放。

阶段 4:实现 IPC 和公开 C ABI

跳转到“阶段 4:实现 IPC 和公开 C ABI”

IPC 固定小端编码,不发送带 padding 的 native struct。每个请求包含 magic、version、 opcode、request id 和 payload length,并对长度、版本和 request id 做严格校验。

公开头文件不暴露 STL、Qt、C++ class、异常、文件描述符、socket 或 FPGA packet 类型。

阶段 5:工具、打包与服务化

跳转到“阶段 5:工具、打包与服务化”

完成:

  • vib_acq_example:公开 API 冒烟和统计;
  • ad7768_record_raw:无 header 原始数据记录;
  • ad7768_xdma_monitor:停 daemon 后直接诊断 FPGA/XDMA;
  • CMake package 和 relocatable pkg-config;
  • systemd unit;
  • 外部纯 C consumer 构建测试;
  • OK8MP 交叉构建、安装包和目标板验收。
终端窗口
cd <sdk_src>
cmake --preset debug -DVIB_ACQ_WARNINGS_AS_ERRORS=ON
cmake --build --preset debug
ctest --preset debug
git diff --check

最终结果:Windows 12/12 测试通过。

Windows 构建用于 parser、重组、Ring Buffer、CSR mock、IPC、状态机和工具参数验证。 Windows 运行时不提供 Unix Domain Socket 硬件客户端,不能替代 Linux 端到端测试。

终端窗口
cd <sdk_src>
cmake -S . -B build/linux-host -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTING=ON \
-DVIB_ACQ_BUILD_TESTS=ON \
-DVIB_ACQ_WARNINGS_AS_ERRORS=ON
cmake --build build/linux-host -j"$(nproc)"
ctest --test-dir build/linux-host --output-on-failure

最终结果:Linux strict、ASan/UBSan 和 TSan 均为 16/16。Sanitizer 构建的原始命令没有 在项目文档中固化,本页不凭记忆伪造;下一次发布应把 sanitizer preset 或 CI 命令提交 到仓库,使结果可重复。

测试必须实际安装到隔离 staging,再从一个纯 C 工程执行:

find_package(vib_acq_client CONFIG REQUIRED)
target_link_libraries(package_consumer PRIVATE VibAcq::client)

另用 pkg-config 构建第二个 consumer。这样能发现以下问题:

  • 导出 target 缺依赖;
  • 安装 include 路径错误;
  • .pc 文件写死构建机路径;
  • SONAME 或符号链接错误;
  • 头文件 ABI 与动态库不一致。

9. 选择正确的 OK8MP 工具链

跳转到“9. 选择正确的 OK8MP 工具链”

目标板:

Linux 5.4.70-2.3.0
glibc 2.30
AArch64

最终使用:

/opt/fsl-imx-xwayland/5.4-zeus/environment-setup-aarch64-poky-linux
GCC 9.2
glibc 2.30 sysroot

不能使用:

/opt/fsl-imx-wayland/6.18-wrynose/environment-setup-armv8a-poky-linux
GCC 15.2
glibc 2.43 sysroot

原因不是“新编译器不好”,而是用 glibc 2.43 sysroot 链接的产物可能要求目标 rootfs 不存在的新符号,导致板上加载失败。最终 AArch64 产物检查到的最高 glibc 要求为 GLIBC_2.17,兼容当前目标板。

9.2 Zeus 环境中的 CMake 版本陷阱

跳转到“9.2 Zeus 环境中的 CMake 版本陷阱”

加载 Zeus 环境后,PATH 前面的 CMake 是 3.15.3,而项目要求至少 3.16。解决办法是:

  • 保留 Zeus 提供的编译器、sysroot 和 OE_CMAKE_TOOLCHAIN_FILE;
  • 显式使用构建主机 /usr/bin/cmake 3.28.3;
  • 不要直接调用环境中排在前面的旧 cmake。

先检查:

终端窗口
source /opt/fsl-imx-xwayland/5.4-zeus/environment-setup-aarch64-poky-linux
echo "$CC"
echo "$OE_CMAKE_TOOLCHAIN_FILE"
/usr/bin/cmake --version

在 Linux SDK 构建主机执行:

终端窗口
source /opt/fsl-imx-xwayland/5.4-zeus/environment-setup-aarch64-poky-linux
sdk_src=/path/to/vib_acq_sdk
build_dir="$sdk_src/build/ok8mp"
staging_dir="$(mktemp -d)"
host_cmake=/usr/bin/cmake
"$host_cmake" --version
"$host_cmake" -S "$sdk_src" -B "$build_dir" -G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_TESTING=OFF \
-DVIB_ACQ_BUILD_TESTS=OFF \
-DVIB_ACQ_BUILD_TOOLS=ON \
-DCMAKE_TOOLCHAIN_FILE="$OE_CMAKE_TOOLCHAIN_FILE" \
-DVIB_ACQ_WARNINGS_AS_ERRORS=ON
"$host_cmake" --build "$build_dir" -j"$(nproc)"
DESTDIR="$staging_dir" "$host_cmake" --install "$build_dir"

检查安装布局:

终端窗口
find "$staging_dir" -type f -o -type l
file -L \
"$staging_dir/usr/sbin/vib-acqd" \
"$staging_dir/usr/lib/libvib_acq_client.so" \
"$staging_dir/usr/bin/vib_acq_example" \
"$staging_dir/usr/bin/ad7768_record_raw" \
"$staging_dir/usr/bin/ad7768_xdma_monitor"
readelf -d "$staging_dir/usr/lib/libvib_acq_client.so.0.2.0" | grep SONAME

预期至少存在:

/usr/sbin/vib-acqd
/usr/lib/libvib_acq_client.so*
/usr/include/vib_acq_client.h
/usr/bin/vib_acq_example
/usr/bin/ad7768_record_raw
/usr/bin/ad7768_xdma_monitor
/usr/lib/pkgconfig/vib_acq_client.pc
/usr/lib/cmake/vib_acq_client/
/usr/lib/systemd/system/vib-acqd.service

预期 ELF 为 AArch64,SONAME 为 libvib_acq_client.so.2。

部署归档只允许包含 usr/...,并统一为 root:root 和规范权限:

终端窗口
release=/tmp/vib-acq-sdk-0.2.0-ok8mp-zeus.tar.gz
tar \
--owner=0 \
--group=0 \
--numeric-owner \
--mode='u+rwX,go+rX,go-w' \
-C "$staging_dir" \
-czf "$release" \
usr
tar -tzf "$release" | sed -n '1,40p'
sha256sum "$release"

最终发布包 SHA-256:

21e8ecc1371be9e7a82bb3c0e26f868559eeccb18a69d3bd8eb9371b2d9df165

目标板自包含测试包 SHA-256:

81686090f34cb55ad46576b2234950e96faf30cec0dc057c13d700e3437f1fa1

为什么必须归一 owner:如果 staging 归档保留构建用户 UID/GID,再由 root 从 / 解包, 可能把 /usr 或服务文件变成普通用户所有。普通用户随后可以替换 root daemon,形成提权 风险。不能只保证最终二进制是 root owner,父目录也必须检查。

先确认设备节点:

终端窗口
test -c /dev/xdma0_user
test -c /dev/xdma0_c2h_0
ls -l /dev/xdma0_*

停止旧服务,整体替换同一个 ABI 版本:

终端窗口
systemctl stop vib-acqd
tar --numeric-owner -C / -xzf /tmp/vib-acq-sdk-0.2.0-ok8mp-zeus.tar.gz
/sbin/ldconfig
systemctl daemon-reload
systemctl enable --now vib-acqd

检查:

终端窗口
systemctl is-active vib-acqd
systemctl is-enabled vib-acqd
systemctl status vib-acqd --no-pager
journalctl -u vib-acqd -b -n 100 --no-pager
ls -l /run/vib-acqd.sock
grep '^#define VIB_ACQ_ABI_VERSION' /usr/include/vib_acq_client.h
readelf -d /usr/lib/libvib_acq_client.so.0.2.0 | grep SONAME
stat -c '%a %u:%g %n' \
/ /usr /usr/bin /usr/sbin /usr/lib /usr/include \
/usr/sbin/vib-acqd \
/usr/lib/libvib_acq_client.so.0.2.0 \
/usr/lib/systemd/system/vib-acqd.service

要求:

  • ABI 宏为 2U;
  • SONAME 为 .so.2;
  • daemon active 且 enabled;
  • socket 存在;
  • 系统目录和安装对象为 root:root;
  • 不把 ABI 1 client 与 IPC 2 daemon 混装。

ABI 2 相比 ABI 1 在公开 status 中新增 fpga_dropped_frames,IPC STATUS payload 从 80 字节扩展为 88 字节。升级必须同时替换:

vib-acqd
libvib_acq_client.so.2
vib_acq_client.h
客户端工具
重新编译的 GUI/消费端

最终 unit 的关键行为:

[Unit]
After=systemd-udev-settle.service
Wants=systemd-udev-settle.service
StartLimitIntervalSec=0
[Service]
Restart=on-failure
RestartSec=2
TimeoutStopSec=40s

解释:

  • XDMA 节点可能晚于服务启动;daemon 找不到设备时明确失败,systemd 每 2 秒重试;
  • StartLimitIntervalSec=0 避免多次启动失败后永久停止重试;
  • daemon 排空期限约 12 秒;
  • XDMA C2H 驱动超时约 10 秒;
  • TimeoutStopSec=40s 给阻塞读、排空和 FPGA 清理留出上界;
  • client 的 START/STOP 事务超时为 30 秒,普通控制请求为 2 秒。

默认 socket 为 0660 root:root。普通 GUI 若不是 root,应建立专用服务组并同步修改 unit Group= 与 GUI 用户组,不能直接把 socket 改成全局可写。

14.1 先验证底层独占路径

跳转到“14.1 先验证底层独占路径”
终端窗口
systemctl stop vib-acqd
fuser /dev/xdma0_user /dev/xdma0_c2h_0
ad7768_xdma_monitor --packets 4

fuser 不应显示其他读取者。监视器依次执行 identity、STOP、CLEAR、选择 AD7768、 打开 C2H、START、读取和协议校验。

监视器结束后恢复产品服务:

终端窗口
systemctl start vib-acqd
systemctl is-active vib-acqd
终端窗口
vib_acq_example --blocks 8 --timeout-ms 5000 --summary-every 1

本次结果:

delivered_frames = 32768
bad_packets = 0
sequence_gaps = 0
bad_tags = 0
fpga_dropped_frames = 0
fpga_frame_errors = 0
fpga_ingress_drops = 0
host_overruns = 0
终端窗口
ad7768_record_raw \
--output /tmp/ad7768-64.raw \
--blocks 64 \
--timeout-ms 5000
stat -c '%s bytes' /tmp/ad7768-64.raw
sha256sum /tmp/ad7768-64.raw

期望文件大小:

64 * 65536 = 4194304 byte

本次实测:

检查结果
文件大小4194304 B
int32_t 样本数1048576
四通道 frame 数262144
超出 signed 24-bit 范围0
SHA-2562a3555d2c492dd3c49f0a1528f0b3b38659f43e41318c3335aaaf5922456ae30
CH05178362 .. 5181550
CH14282 .. 16943
CH2-136 .. 71
CH3-119 .. 79

这些数据只证明格式、范围和数字传输一致性,不证明模拟精度、噪声或传感器标定。

离线检查示例:

from pathlib import Path
import numpy as np
raw = Path("/tmp/ad7768-64.raw")
assert raw.stat().st_size == 64 * 65536
samples = np.fromfile(raw, dtype="<i4")
assert samples.size == 64 * 4096 * 4
frames = samples.reshape(-1, 4)
bad = np.count_nonzero((frames < -8388608) | (frames > 8388607))
print("frames:", frames.shape[0])
print("out_of_range:", bad)
for channel in range(4):
print(channel, frames[:, channel].min(), frames[:, channel].max())

通过延迟消费故意压满 daemon Ring Buffer:

终端窗口
vib_acq_example \
--blocks 80 \
--timeout-ms 5000 \
--summary-every 80 \
--sleep-ms 100

本次得到:

host_overruns = 168
exit code = 3

这是预期行为:SDK 没有静默覆盖数据,而是显式报告 DATA_LOSS。随后立即验证恢复:

终端窗口
vib_acq_example --blocks 1 --timeout-ms 5000 --summary-every 1

下一会话返回 0,所有完整性计数恢复为 0。

本次实际使用的等价循环:

终端窗口
: > /tmp/vib-cycle-abi2.log
for cycle in $(seq 1 100); do
vib_acq_example \
--blocks 1 \
--timeout-ms 5000 \
--summary-every 1 \
>> /tmp/vib-cycle-abi2.log 2>&1 || {
echo "FAIL cycle=$cycle"
exit 1
}
done
grep -c '^status ' /tmp/vib-cycle-abi2.log

结果:100 次 configure/start/read/stop/status 全部通过,每轮完整性计数为 0,最终状态 为 idle,FIFO 已排空。

14.6 客户端异常退出恢复

跳转到“14.6 客户端异常退出恢复”

验证方法:启动长采客户端后强制终止,使其不能正常调用 STOP;等待 daemon 自动清理,再 启动新客户端。

本次结果:

强制退出状态 = 137
daemon = active
后续 1-block 采集 = 成功
后续完整性计数 = 全 0

这证明客户端消失不会永久占用会话。

14.7 活动采集时停止服务

跳转到“14.7 活动采集时停止服务”

启动长采后执行:

终端窗口
systemctl stop vib-acqd
systemctl start vib-acqd
vib_acq_example --blocks 1 --timeout-ms 5000 --summary-every 1

本次活动停止耗时约 54 ms,重启后 1-block 采集成功,服务恢复 active。

终端窗口
vib_acq_example \
--blocks 112500 \
--timeout-ms 5000 \
--summary-every 112500 \
> /tmp/vib-long-abi2.log 2>&1
long_rc=$?
echo "LONG_EXIT=$long_rc"
sha256sum /tmp/vib-long-abi2.log

112500 block * 4096 frame = 460800000 frame,对应约 30 分钟。

最终状态:

received_frames = 460804212
delivered_frames = 460800000
bad_packets = 0
sequence_gaps = 0
bad_tags = 0
fpga_dropped_frames = 0
fpga_frame_errors = 19
fpga_ingress_drops = 0
host_overruns = 0

received_frames 多出的 4212 frame 来自客户端收到目标 block 后,STOP 流程为安全排空 C2H 而继续读取的数据;应用交付量以 delivered_frames 为准。

因为 fpga_frame_errors=19,STOP 正确返回 DATA_LOSS,工具退出码为 3。不能把退出码 3 简单写成“PCIe 长测失败”;它准确揭示了 AD7768 输入侧帧错误。

日志 SHA-256:

9d9ee994cbc51c62cad68052e56d059dc5b8ed001a7f5055f9244b78f4b9e63e

长测后立即执行 1-block 采集成功,所有计数为 0,说明会话清理和恢复正常。

在压力/长测前后分别保存:

终端窗口
dmesg | grep -Ei 'xdma.*timeout|aer.*error|completion timeout' \
> /tmp/vib-dmesg-before.txt || true
# 执行测试
dmesg | grep -Ei 'xdma.*timeout|aer.*error|completion timeout' \
> /tmp/vib-dmesg-after.txt || true
diff -u /tmp/vib-dmesg-before.txt /tmp/vib-dmesg-after.txt

本轮没有新增 XDMA timeout、PCIe AER 或 completion timeout。日志中 XDMA 初始化行的 timeout: h2c 10 c2h 10 sec 是驱动配置说明,不是超时故障。

15. 遇到的问题与解决办法

跳转到“15. 遇到的问题与解决办法”

15.1 错用了 Wrynose 6.18 工具链

跳转到“15.1 错用了 Wrynose 6.18 工具链”

现象:构建机存在更新的 Wrynose SDK,看起来更适合新项目。

根因:目标板仍是 Linux 5.4/glibc 2.30,Wrynose sysroot 是 glibc 2.43。使用它会把 新 glibc 符号需求带入产物。

解决:固定 Zeus 5.4 xwayland 工具链,构建后检查 ELF、动态依赖和目标板加载。

15.2 加载 Zeus 后 CMake 版本反而太旧

跳转到“15.2 加载 Zeus 后 CMake 版本反而太旧”

现象:source 环境后默认 CMake 变成 3.15.3,低于项目要求的 3.16。

根因:Yocto SDK 把自身 host 工具放到 PATH 前面。

解决:仍使用 Zeus 的编译器、sysroot、toolchain file,但显式调用主机 /usr/bin/cmake 3.28.3。

15.3 停止后仍有 FPGA 尾部数据

跳转到“15.3 停止后仍有 FPGA 尾部数据”

现象:旧停止流程可能提前结束 worker 或关闭 C2H,FPGA accepted/transmitted 计数 不相等,下一会话收到残留数据。

根因:停止“数据源”和停止“DMA 读取者”不是同一动作。先杀 worker 会失去排空者。

解决:

  1. 先 STOP FPGA source;
  2. worker 继续读 C2H;
  3. 等待 source idle、FIFO empty、accepted==transmitted;
  4. 再 join worker 和关闭设备;
  5. 排空不完整时返回明确 drain error 或 DATA_LOSS。

15.4 前一会话留下 partial C2H transaction

跳转到“15.4 前一会话留下 partial C2H transaction”

现象:watchdog、超时或异常断开后,下一会话首个 64 KiB packet 可能错位或短读。

根因:XDMA streaming transaction 跨会话残留,简单 CLEAR 不能恢复主机侧正在进行的 读事务。

解决:START 前执行:

STOP -> 打开 C2H -> 有界 drain stale stream -> CLEAR -> 选择源/配置 -> START

DMA buffer 固定为完整 64 KiB 页面并保持对齐;短 packet 不能用下一次 read 拼接。

15.5 partial abort 的 CLEAR 丢失诊断

跳转到“15.5 partial abort 的 CLEAR 丢失诊断”

现象:异常恢复时直接 CLEAR 会把 FPGA 计数清零,使本次会话的根因消失;处理顺序不当 还可能污染下一会话 baseline。

解决:

  • 先保存 pre-clear snapshot;
  • 关闭 C2H 后再 CLEAR;
  • CLEAR 后重置 raw counter baseline;
  • 保留 64-bit session diagnostic totals;
  • 验证传输侧和 ADC 侧计数均已清零。

15.6 清理错误覆盖真正的 C2H 根因

跳转到“15.6 清理错误覆盖真正的 C2H 根因”

现象:C2H 已发生短读或 I/O 错误,但随后 STOP、snapshot 或 CLEAR 又失败,最终返回的 错误变成“清理失败”。

根因:多个错误简单覆盖同一个 result。

解决:保存 first/root error;只有没有更早根因时,清理错误才成为最终返回值。

15.7 CSR 并发访问发生交叉

跳转到“15.7 CSR 并发访问发生交叉”

现象:DMA worker 和 IPC 控制路径同时读取顺序 user BAR,文件位置和多步读写可能交叉。

根因:该目标的 CSR 访问依赖顺序字符设备语义,不能假定所有 pread/lseek 组合都与 普通文件相同。

解决:所有 FPGA control 操作通过统一 mutex 串行化;XDMA 层只提供受控的顺序访问, CSR offset 规则集中在 FPGA 层。

15.8 32-bit FPGA 计数器会回绕

跳转到“15.8 32-bit FPGA 计数器会回绕”

现象:长会话中直接把 FPGA 原始 32-bit counter 暴露给应用会回绕;只读有效 packet header 还可能漏掉无效 packet 期间增加的错误。

解决:

  • 约每 64 个完成 DMA read 轮询一次 CSR;
  • 无效 packet 也计入轮询节奏;
  • 用模 2^32 差值扩展成 64-bit session total;
  • CLEAR 后重新建立 raw baseline,但不丢失会话诊断累计。

15.9 ABI 1 无法公开 FPGA dropped frame

跳转到“15.9 ABI 1 无法公开 FPGA dropped frame”

现象:ABI 1 的 STATUS 没有 fpga_dropped_frames,GUI 无法稳定获取 FPGA 传输 FIFO 丢帧累计。

解决:SDK 升级到 0.2.0,并同步升级:

C ABI 1 -> 2
IPC 1 -> 2
STATUS payload 80 B -> 88 B
SONAME .so.0 -> .so.2

ABI 1 和 ABI 2 不能混装。

15.10 工具漏掉 STOP 阶段发现的数据丢失

跳转到“15.10 工具漏掉 STOP 阶段发现的数据丢失”

现象:工具完成目标 block 后先读 status,再 STOP;如果错误只在 STOP 排空阶段被发现, 工具可能错误返回 0。

解决:先 STOP,再读取最终 status;STOP 返回 DATA_LOSS 或任一完整性计数非零时, 工具退出 3。新增 stop-time-data-loss 端到端测试。

15.11 systemd 的 ConditionPathExists 阻止自动恢复

跳转到“15.11 systemd 的 ConditionPathExists 阻止自动恢复”

现象:开机时 XDMA 节点尚未出现,unit 被 condition 跳过,节点稍后出现也不会重试。

根因:ConditionPathExists 导致 systemd 认为服务无需启动,不属于失败重启。

解决:移除 condition,让 daemon 明确失败,配合:

Restart=on-failure
RestartSec=2
StartLimitIntervalSec=0

15.12 部署包污染 /usr 所有权

跳转到“15.12 部署包污染 /usr 所有权”

现象:从 staging 打包时保留构建用户 UID/GID,root 解包后系统目录或服务文件变成普通 用户所有。

风险:普通用户可以替换 root daemon,属于安全问题,不只是“文件看起来不整齐”。

解决:只打包 usr,归一 owner/group、numeric owner 和权限;部署后对 /、/usr 及安装对象执行 stat 检查。本次已把 /、/usr、/usr/bin、/usr/sbin、/usr/lib 和 /usr/include 恢复为 0755 root:root。

15.13 C2H poll 和跨线程 close 不能作为可靠超时

跳转到“15.13 C2H poll 和跨线程 close 不能作为可靠超时”

现象:希望用 .poll 或另一个线程 close(fd) 立即打断 streaming C2H 阻塞读。

根因:当前 XDMA streaming C2H .poll 不能表达需要的完成超时;一个线程关闭 fd 也 不能保证立即中断另一个线程正在进行的 read。

解决:

  • 驱动配置有限 c2h_timeout=10s;
  • daemon 使用有界排空;
  • client START/STOP 期限 30 秒;
  • systemd stop 期限 40 秒;
  • 所有权和停止顺序由 daemon 状态机统一管理。

15.14 测试期望与新累计语义冲突

跳转到“15.14 测试期望与新累计语义冲突”

现象:端到端测试先注入 FPGA drop,再把 mock 原始 counter 设回 0,随后仍期待 STOP 成功;实现返回 DATA_LOSS。

根因:ABI 2 保存的是会话累计 64-bit 诊断,不应因为原始 counter 回到 0 就忘记本 会话曾发生 drop。

解决:修正测试期望,STOP 返回 DATA_LOSS 才是正确行为。Windows、Linux、ASan、 UBSan 和 TSan 全套门禁重新通过。

15.15 目标板时间错误导致解包“未来时间”警告

跳转到“15.15 目标板时间错误导致解包“未来时间”警告”

现象:目标板系统时间仍显示 2021,构建产物时间是 2026,tar 报 timestamp in the future。

判断:这不等同于文件损坏;应继续校验归档返回码、文件大小和 SHA-256。

解决:验收仍以哈希和 ELF 检查为准;产品系统应配置 RTC/NTP,使日志和产物时间可追溯。

15.16 ldconfig 报 Vulkan 库截断

跳转到“15.16 ldconfig 报 Vulkan 库截断”

现象:ldconfig 同时报告目标 rootfs 中已有 Vulkan 库截断。

判断:这些库不属于本 SDK,不能把已有 rootfs 告警误判成 ABI 2 安装失败。

处理:确认 libvib_acq_client.so.2 链接和加载成功,单独记录 Vulkan rootfs 问题, 不在 SDK 任务中擅自删除系统库。

15.17 自包含工具测试缺少必需参数

跳转到“15.17 自包含工具测试缺少必需参数”

现象:批量执行目标板测试二进制时,把 vib_acq_tool_stop_data_loss_test 当作无参数 测试运行,得到“expected example and recorder paths”。

根因:该测试需要传入 vib_acq_example 和 ad7768_record_raw 路径;这是测试调用 错误,不是产品缺陷。

解决:按 CTest 定义单独执行并传入两个工具路径,测试通过。自动验收应优先使用 CTest 元数据,避免用通配符盲目执行所有二进制。

层级结果能证明什么不能证明什么
Windows strict12/12可移植协议、状态机、工具参数Unix Socket 和硬件
Linux strict16/16daemon/client/Socket 端到端FPGA、XDMA、AD7768
ASan/UBSan16/16已覆盖路径无已检出内存/未定义行为未执行硬件路径
TSan16/16已覆盖路径无已检出数据竞争所有设备驱动并发行为
Zeus AArch64 build通过工具链、架构、SONAME、staging板上设备可用
外部 C consumer通过CMake package 与 pkg-config 可消费实时采集稳定性
OK8MP 自包含测试11 个通过AArch64 测试运行和核心逻辑模拟精度
8-block 短采全部完整性计数 0公开 API 和真实数据短链路长稳
64-block raw4 MiB,范围/格式通过原始格式和四通道数字传输VREF/增益/噪声精度
慢消费者明确 DATA_LOSS,随后恢复有界缓存和错误可见性永不丢数据
100 次生命周期全通过重复启停和清理断电恢复
客户端异常退出后续立即恢复daemon 断连清理PCIe 物理断线
systemd 活动停止约 54 ms,重启恢复服务关闭路径所有 c2h_timeout 组合
30 分钟传输460800000 frame,传输计数全 0packet/XDMA/daemon/C ABI 长稳AD7768 输入零错误
AD7768 输入长稳fpga_frame_errors=19输入侧错误可见产品级零错误

发布归档:

vib-acq-sdk-0.2.0-ok8mp-zeus.tar.gz
SHA-256 21e8ecc1371be9e7a82bb3c0e26f868559eeccb18a69d3bd8eb9371b2d9df165

测试归档:

SHA-256 81686090f34cb55ad46576b2234950e96faf30cec0dc057c13d700e3437f1fa1

目标板关键文件:

文件SHA-256
/usr/sbin/vib-acqd473ba01a55589c4e1830e98e3ab9f2dd096b8386211b5d72c09bd9be090139a6
/usr/lib/libvib_acq_client.so.0.2.013410e7de177f027ba5a2a33cb958afea28cc8438bcd2c3a780b75b8be515b2d
/usr/bin/vib_acq_examplea7433e29549512daa7733f51bdfa76f0ff46e531155991dc0a0e0979fad92272
/usr/bin/ad7768_record_rawa04e7520898e1c909857fedc80f9d901c5962d23575918e2c5cc6447a8e242cd
/usr/bin/ad7768_xdma_monitor30a355727d47e21d384bd0545d97597c3415ecfa7188db0129678b12d713098d

最终阶段的关键提交按依赖顺序为:

14f029a define and test acquisition IPC protocol
b358d15 implement acquisition service and DMA worker
e1708b9 complete acquisition daemon and client API
21bcd97 drain FPGA stream before stopping DMA
3c60518 add acquisition tools and package validation
566f59b recover and drain FPGA sessions reliably
e1573f2 serialize FPGA control operations
af9a9ce publish durable integrity status through ABI 2
2c63968 harden tools and package consumption
de42b35 retry vib-acqd when XDMA devices appear late
260176b publish ABI 2 integration and OK8MP acceptance
f2da2c3 update repository verification baseline

这组提交不是“只写几个 API”,而是逐步关闭停止、排空、错误保留、计数回绕、ABI、打包、 systemd 和实机恢复等产品问题。

19. 下次从零复现的最短清单

跳转到“19. 下次从零复现的最短清单”
  • 确认 SDK 源码提交为 f2da2c3 或其已审核后代
  • Windows strict 12/12
  • Linux strict 16/16
  • sanitizer 门禁通过
  • 加载 Zeus 5.4,而不是 Wrynose 6.18
  • 显式使用 CMake >= 3.16
  • AArch64 Release 严格构建
  • staging 布局、ELF、SONAME、glibc 需求检查
  • CMake 和 pkg-config 外部 C consumer 通过
  • 归档只含 usr、owner/group 0:0、权限规范
  • 记录发布包 SHA-256
  • /dev/xdma0_user 和 /dev/xdma0_c2h_0 存在
  • 停止旧 daemon
  • 同步安装 daemon、.so.2、头文件和工具
  • ldconfig、daemon-reload、enable/start
  • ABI 2、IPC 2、SONAME .so.2 一致
  • 安装对象和父目录为 root:root
  • socket 权限符合 GUI 运行用户设计
  • 独占 monitor 读取 4 个 packet
  • 公开 API 8-block 短采
  • 64-block raw 大小、哈希、四通道和 24-bit 范围检查
  • 慢消费者必须显式 DATA_LOSS
  • DATA_LOSS 后下一会话恢复
  • 100 次启停
  • 客户端异常退出恢复
  • 活动 systemd stop/start 恢复
  • 30 分钟 112500-block 长测
  • 测试前后内核 XDMA/AER 日志差异
  • 单独记录传输层计数和 AD7768 输入侧计数

20. 尚未完成的产品工作

跳转到“20. 尚未完成的产品工作”
  1. 使用示波器或 ILA 检查 DCLK、DRDY、DOUT0,消除 30 分钟内 19 次帧错误;
  2. 修复后重新完成 AD7768 输入侧零错误长测;
  3. 设计并执行受控 PCIe 物理断开/重训练;
  4. 验证 Linux 运行期间 FPGA 重烧或复位的错误可见性和恢复;
  5. 验证采集中移除 DCLK、DRDY 或 DOUT0;
  6. 把 SDK 正式集成到产品 rootfs/package recipe,而不是长期使用手工 tar 部署;
  7. 为普通 Qt 用户建立专用服务组;
  8. 若需要 GUI、记录器和远程服务并行消费,设计发布/订阅和每客户端有界队列;
  9. 引入硬件时间戳或共享时间基准,支持键相、多设备和绝对相位;
  10. 完成模拟精度、VREF、增益、零偏、噪声和传感器标定,这些不属于本次 SDK 验收。

本阶段最重要的经验不是“XDMA 能传 4 MB/s”。相对于 PCIe 带宽,这个吞吐很小。真正 决定产品可靠性的因素是:

  • 数据由谁拥有;
  • 停止时谁负责排空;
  • 错误是否会被后续清理覆盖;
  • 计数器回绕后是否仍可追踪;
  • 慢消费者是否得到明确的数据断点;
  • daemon、动态库、IPC 和头文件是否一起版本化;
  • 构建工具链是否与目标 rootfs 真正兼容;
  • 部署包是否保持系统目录安全;
  • 测试结论是否把传输层和 ADC 输入侧分开。

只有把这些问题形成代码、测试、部署和验收闭环,Qt GUI 才能在稳定接口上开发,而不是 把硬件竞态和数据完整性问题留到界面联调阶段。