标签: 安全边界

  • Chrysler 固件身份:0x22 F132 双偏移与转向能力前缀启发式

    结论:Chrysler 平台的身份链里,0x22 F132 这一个 DID 走的是两条响应路由:仪表/SRS/ABS/雷达/EPS 用 rx_offset=-0x280,ABS/混动/发动机/变速箱用默认 +0x8,发动机与变速箱还额外查 supplier 软件号。匹配分精确与通用 fuzzy 两级,fuzzy 明确排除 EPS/前视雷达并要求两个 ECU 地址上的独有版本指向同一车型,且 Chrysler 没有品牌自定义 fuzzy。转向能力不是规范化零件号解析,而是对 EPS 固件版本前 4 字节的前缀启发式:非 Ram 平台 >= b"6841" 抬高最低转向速度,Ram 1500 落在 b"68" < prefix <= b"6831" 时才可设为 0。识别、BSM、雷达解析与可发送控制是四种不同能力,必须分别声明与验证。

    十个平台是入口索引,不是适用性声明

    CAR 与 ChryslerCarSpecs 声明 10 个平台:Pacifica 4、Jeep 3、Ram 2、Durango 1,并给出 PT/雷达 DBC、质量、轴距与基础最低转向速度。Cherokee 使用 CUSW PT DBC,Ram 使用独立 DBC。FW_VERSIONS 对这 10 个平台逐一列出 ECU 地址与版本原始字节:发动机版本可含尾部空格,Ram HD 列了 engine 0x7e0 却没有 transmission 条目,该文件也没有旧式 FINGERPRINTS。

    这些声明不是物理线束、EPS 固件或执行权限的证明,也不代表已完成所有年款的报文与安全审计。版本字节必须按字节保存,不应剥除尾部空格或把车型名称当成版本匹配规则。

    FW_QUERY_CONFIG:一个 DID,两条响应路由

    查询配置在 bus 0 上组织了三组请求,最容易踩坑的是「同一个 ABS 类型出现在两组」——因此不能只按 ECU 类型推导响应地址,必须按请求组分别对应偏移。

    请求组 目标 ECU 偏移 / 结果
    0x22 F132 仪表 / SRS / ABS / 雷达 / EPS rx_offset=-0x280;如 EPS 0x75a 预期响应 0x4da
    0x22 F132 ABS / 混动 / 发动机 / 变速箱 默认 +0x8;如发动机 0x7e0 默认响应 0x7e8
    0x22 supplier 发动机 / 变速箱 额外查询 supplier ECU 软件号
    extra_ecus 混动 ABS 替代地址 0x7e4 加入查询集合,get_fw_versions() 标为 logging,build_fw_dict() 排除

    CHRYSLER_VERSION_REQUEST 为 0x22 F132,supplier 软件号也走 0x22,每组正响应以 0x62 加相同 DID 为前缀。这里的版本正则 [A-Z0-9_]{10} ? 只用于离线固件表的全匹配测试,不是运行时未知版本归一化器。混动 ABS 的替代地址应视为额外采集,不应成为自动选型证据。

    精确与模糊匹配:独有 ECU 数与冲突拒绝

    match_fw_to_car_exact() 对每个候选的已知 ECU/版本逐项检查:未见 essential ECU 或已见不匹配都可淘汰,非 essential 缺席可略过。要记住它返回的是集合,不保证唯一,没有实测固件时不能据此宣称车型已识别。

    通用 fuzzy(match_fw_to_car_fuzzy())排除 EPS、前视雷达等共享 ECU,要求两个不同 ECU 地址上的独有版本都指向同一车型。Chrysler 未配置品牌自定义 fuzzy,因此未知 EPS 版本不能单独作为 fuzzy 身份;两个 ECU 冲突时会拒绝候选,缺帧或共享件则可能导致无结果。

    能力 判定依据 边界
    精确匹配 候选的已知 ECU/版本逐项核对,essential 缺失或不匹配可淘汰 返回集合,不保证唯一
    通用 fuzzy 两个不同 ECU 地址上的独有版本指向同一车型 排除 EPS/前视雷达;Chrysler 无品牌自定义 fuzzy
    BSM 依据 bus 0 地址探测,而非固件型号 与车型识别是两件事
    雷达解析 统一 radarUnavailable=True 默认禁用,需另行启用

    转向能力:前缀启发式,不是零件号解析

    CarInterface._get_params() 用固件版本前 4 字节做前缀判断。非 Ram 平台:新平台或任意 EPS 版本前缀按字节序 >= b"6841" 时,置高最低转向速度标志;Ram 1500 的 EPS 前缀落在 b"68" < prefix <= b"6831" 时可设 minSteerSpeed=0;最后的高阈值标志把最低速度设为 17.5 m/s。

    这些判断是代码里的能力启发式,不是规范化零件号解析;CarParams 需要保留固件来源和未知状态。CarController.update() 用 minSteerSpeed 决定 LKAS 使能,平台分支决定关断迟滞并包含重新使能等待,扭矩在未激活或使能位关闭时归零。参数阈值会进入实时执行链,不能仅凭插件清单声明「支持低速转向」。

    四种能力必须分开声明

    Ram HD 与 CUSW 被标记 dashcamOnly;CUSW 选独立 safety model,Ram DT/HD 使用安全参数位;雷达统一 radarUnavailable=True;BSM 依据 bus 0 地址而非固件型号。源文档把这一点总结为一句要求:车型识别、BSM、雷达解析和可发送控制是四种不同能力,必须分别声明、验证和降级。对缺失或相互矛盾的证据应返回未知,高阈值与零速转向不应从车型说明或单一未验证响应推断。

    常见问题

    为什么 ABS 的响应地址不能只按 ECU 类型推导?

    因为同一个 DID 0x22 F132 在 bus 0 上有三组请求,同一个 ABS 类型会同时出现在 rx_offset=-0x280 组和默认 +0x8 组。源文档明确不能按 ECU 类型单独推导响应地址,必须按请求组对应偏移。

    EPS 版本能不能单独决定车型身份?

    不能。通用 fuzzy 明确排除 EPS、前视雷达等共享 ECU,要求两个不同 ECU 地址上的独有版本都指向同一车型,且 Chrysler 未配置品牌自定义 fuzzy。因此未知 EPS 版本不能单独作 fuzzy 身份。

    Ram 1500 的零速转向是怎么判定的?

    由 EPS 固件版本前 4 字节的前缀启发式判定:当 b"68" < prefix <= b"6831" 时可设 minSteerSpeed=0。这是代码中的能力启发式,不是规范化零件号解析,CarParams 需保留固件来源和未知状态。

    车型被识别出来,是否就能发送转向控制?

    不能等同。源文档强调车型识别、BSM、雷达解析和可发送控制是四种不同能力。Ram HD/CUSW 标记 dashcamOnly,雷达统一 radarUnavailable=True,而 minSteerSpeed 等阈值会进入实时执行链,不能仅凭插件清单声明支持低速转向。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CHRYSLER_FIRMWARE_QUERY_STEERING_CAPABILITY_IDENTITY_DEEP_DIVE.md(取用了 10 个平台固件库、0x22 F132 双偏移查询、精确与 fuzzy 匹配边界、转向能力前缀启发式及四类能力分层)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • tinygrad PCI 两条内存路径:alloc 与 map 的所有权不对称

    结论:tinygrad 的 PCI 接口把「本地发现与独占」「内存分配与映射」和「远程 RPC 调试」三件事放在同一模块里,普通插件很容易只看到最后一句 alloc(),却忽略它前面的宿主状态改动。两条内存路径的所有权并不对称:VRAM 路径走 MemoryManager.valloc(),释放时有 vfree();而 SYS 路径单独持有 VA、系统内存和 GPU 页表,free() 只做本地 munmap(),没有显式归还 VA 或拆页表。远程服务 serve.py 监听 0.0.0.0 且在协议处理前没有认证,属于开发工具。以下为固定归档的静态源码追踪,未解绑驱动、未映射 BAR、未启动远程服务、未访问真实 PCI 设备。

    本地调用链:构造对象即改变宿主 PCI 状态

    _System.pci_scan_bus() 按 vendor/device 掩码和可选 base class 筛选设备;list_devices() 在设置 REMOTE 时改走 TCP 远程扫描并缓存结果;pci_probe_device() 应用可见设备过滤后构造设备。

    真正需要警惕的是 PCIDevice.__init__():它取得文件锁、解绑现有内核驱动、移除同 slot 的 sibling functions、选择 VFIO 或 sysfs 启用,并打开 PCI config。也就是说,构造对象本身就会改变宿主机 PCI 状态,研究时不应把它当成只读操作。PCIIfaceBase.__init__() 随后探测设备、保留 VA 区间、尝试 resize BAR 并构造具体实现。

    步骤 负责函数 副作用 / 边界
    扫描与筛选 pci_scan_bus()、pci_probe_device() 按 vendor/device 掩码与可见设备过滤。
    远程分流 list_devices() 设置 REMOTE 时改走 TCP 扫描并缓存。
    独占与启用 PCIDevice.__init__() 取文件锁、解绑驱动、移除 sibling function、选 VFIO/sysfs。
    VA 与 BAR PCIIfaceBase.__init__() 保留 VA 区间、尝试 resize BAR;resize 异常被吞掉并继续。

    alloc 与 map:所有权不对称

    alloc() 依据 host/cpu_access/BAR 选择锁页系统内存或 VRAM。VRAM 路径调用 MemoryManager.valloc();SYS 路径则取得 VA、分配 sysmem 并写 GPU 页表。map() 允许同 host 的 CPU/PYTHON/NPY 缓冲或同 peer group 的 PCI GPU 缓冲进入 GPU VA;host 路径检查页对齐与 VA 范围并锁页,P2P 路径拒绝小 BAR 与跨 peer group 映射,p2p_paddrs() 用目标 BAR 物理基址形成系统地址空间映射。

    释放时的不对称是重点。PCIIfaceBase.free() 对 PHYS 路径调用 vfree()、对本地 CPU 映射调用 munmap(),但没有对 SYS 分配调用 unmap_range() 或 va_allocator.free()。同样,map() 对 host 缓冲调用 System.lock_memory(),对应的 unmap() 只调用页表 unmap_range(),未见 munlock()。

    入口 主要职责 需要核对的结果
    alloc_sysmem()、system_paddrs() MAP_LOCKED 匿名映射并从 pagemap 取物理页 依赖权限、页固定和完整物理页列表。
    map_bar()、bar_info() resource 文件长度/地址与 BAR mmap off/size、BAR 边界与 MADV_DONTFORK 是内核契约。
    alloc() 输出 BufferStorage 加 PCIAllocationMeta PHYS 有 valloc() 所有权;SYS 单独拥有 VA、映射和页表。
    free() PHYS 走 vfree();本地 CPU 映射 munmap() SYS 路径未显式归还 VA 或拆页表。
    map()、unmap() 外部缓冲映射与页表拆除 unmap() 仅拆 GPU PTE,host mlock() 解锁责任不明确。

    此外,USBPCIDevice 通过 ASM24 桥配置 PCI bus/BAR,并用 512 KiB SRAM Bump 分配器提供 DMA 暂存。这属于特定 USB 桥路径,不应混作通用主板 PCI 内存。另有几个静态可见的失败边界:PCIDevice.reset() 使用 shell 命令但不检查退出状态;BAR resize 异常在 __init__() 被吞掉并继续初始化——因此不能把构造成功等同于 reset/resize 已成功。

    远程 RPC:服务端没有认证

    RemotePCIDevice 经 TCP 的 REMOTE_REQ/REMOTE_RESP 与服务端通信。RemoteMMIOInterface 的读取使用往返 RPC,写入是 posted 命令,同一 TCP 流上的后续请求用于观察此前的写入。服务端 handle() 暴露设备探测、config/BAR 读写、reset、系统内存映射、直接内存读写,以及 LOAD_PROG/EXEC_PROG。

    信任边界很高:server.bind() 监听 0.0.0.0,在协议处理前没有认证或授权;SYSMEM_READ/WRITE 直接以请求提供的地址构造进程内存视图;LOAD_PROG/EXEC_PROG 可装载并执行客户端给出的程序。只要服务可被访问,客户端就获得远超「推理插件数据搬运」的能力。源码本身是开发工具,不应在车载生产网络上裸露运行,应物理或网络隔离,并增加认证、命令白名单与最小权限。这一判断是接口能力分析,不表示当前车辆上已启动该服务。

    静态可见的失败边界与验收

    除了 alloc/free 的 VA 与锁页不对称,还有两处条件性风险。其一,alloc() 的 SYS 路径先取 VA,再申请系统内存和建立页表,失败中途没有本层回滚,属条件性资源累积风险,需区分底层设备复位或进程退出清理与运行中的热重配。其二,远程 scan()/_connect() 缓存 socket,而 serve.py 的已发现设备、打开设备、BAR 映射和程序列表是模块全局状态,连接关闭不会在同一进程中显式清掉,热重连与多客户端所有权需要另测。

    面向自制硬件的验收应先在无设备 mock 中覆盖 VA/SYS 分配失败回滚、host 锁页与释放、BAR 边界和 P2P peer group;再在专用台架验证驱动解绑与恢复、VFIO/IOMMU、DMA 地址、重复启动和断电回收。远程调试服务只能放在隔离开发环境,绝不能成为「万物即插件」的通用车载执行接口;任何 PCI/计算插件都不得直接获得 sendcan 或 panda safety 配置权限。

    常见问题

    alloc 之后 free 就能把资源全部还回去吗?

    不一定对称。PHYS/VRAM 路径在 free() 里走 vfree(),本地 CPU 映射走 munmap();但 SYS 路径没有显式调用 unmap_range() 或 va_allocator.free(),host 锁页的解锁责任也未在这一对函数里明确。

    构造 PCIDevice 对象是只读操作吗?

    不是。PCIDevice.__init__() 会取得文件锁、解绑现有内核驱动、移除同 slot 的 sibling functions,并选择 VFIO 或 sysfs 启用,因而会改变宿主机 PCI 状态。

    远程 serve.py 暴露了什么能力?

    它暴露设备探测、config/BAR 读写、reset、系统内存映射、直接内存读写,以及 LOAD_PROG/EXEC_PROG;server.bind() 监听 0.0.0.0 且协议处理前没有认证,SYSMEM_READ/WRITE 直接按请求地址构造进程内存视图。

    BAR resize 失败会影响后续初始化吗?

    会存在被忽略的情况。BAR resize 的异常在 PCIIfaceBase.__init__() 被吞掉并继续初始化;PCIDevice.reset() 也使用 shell 命令但不检查退出状态,因此不能把构造成功等同于 reset/resize 已成功。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_PCI_LOCAL_REMOTE_MEMORY_TRUST_LIFECYCLE_DEEP_DIVE.md(取用了本地调用链、alloc/map/free 所有权不对称、USB 桥路径、远程 RPC 服务端无认证与静态失败边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • tinygrad BNXT 固件控制面:HWRM 长度信任与队列所有权缺口

    结论:tinygrad 的 BNXT 驱动控制面是一条「初始化—队列—故障」三段式链路:BNXTDev 负责 PCI/固件邮箱,BNXTQP 负责 QP 状态机与 doorbell。它的静态缺口集中在两处——hwrm() 未约束固件返回的 resp_len,fini() 仅注销功能驱动而无显式 QP/CQ 销毁与内存逐项释放。它是计算数据搬运组件,不是车辆控制或安全网关。

    分层初始化:从 PCI reset 到 QP 的 RTS

    BNXTDev.__init__() 复位 PCI 设备、开启 Bus Master、映射 BAR0/BAR2,再用 hwrm() 取得版本/能力、复位功能、注册驱动并获取 doorbell 偏移。setup_backing_store() 按固件报告的 entry size、子类型和最小容量分配系统内存并建立 PBL,随后 _open_rcfw() 建 CREQ/CMDQ/NQ 并初始化 RCFW。

    _open_l2() 建 L2 CQ、RX ring、VNIC 和 MAC filter,供 RoCE ingress 使用;源码注释称不向这个 L2 RX ring 投递接收缓冲。最后注册本地 GID。BNXTQP.__init__() 建两个 CQ 与 SQ/RQ,创建 QP 并置 INIT;connect() 通过两次 modify_qp 转 RTR/RTS。

    HWRM/RCFW 回包:长度信任与状态断言

    函数/组件 输入到输出 关键实现细节
    build_pbl() 物理页列表到 PBL 层级/地址 单页直指;多页分配页表;超过一张底层表时加顶层表,顶层最多 512 项;队列尾两页带终止标志。
    BNXTQueue.read()/write() 逻辑 index 到 ring 槽位 通过 index % entries 环绕;aux=True 写 SQ 后半的 MSN 表。调用方承担数据不跨槽与写入顺序。
    db_value() QP/CQ/NQ ID、index、epoch 到 64 位 doorbell 高位含 RoCE path/type/valid,低位含索引和 epoch;doorbell() 写前调用内存屏障。
    hwrm() 固件请求到 HWRM 回包 seq 递增、清空响应页、写邮箱、等待 resp_len/seq/valid,再解结构并断言 error_code == 0。
    rcfw() RCFW 请求到 CREQ 回包 16 字节槽位、首包标记、等待 toggle、推进 consumer 和 NQ_ARM doorbell,断言 status == 0。
    register_mem() 物理页/VA/长度到 MR key PBL 只取覆盖 size 的页;启用本地/远端写。unregister_mem() 按 key 注销。
    msn_entry() WQE 号、PSN、长度到 MSN 项和下一 PSN 每 4096 字节一包,最少耗一个 PSN;24 位回绕。
    cqe_ready() CQE 与 consumer 到可读布尔值 比较 toggle 与 CQ 环绕 epoch;poll() 另查状态字节,避免把错误完成视作成功。

    hwrm() 在读取响应页末项前未约束固件给出的 resp_len 是否超过响应页长度,也未先验证它足以覆盖期望结构。畸形大长度可产生 IndexError 而非设计的超时/协议错误;这是静态可见的输入校验缺口,真实固件是否会返回此值尚未验证。

    队列与 doorbell:环绕、epoch 与内存屏障

    队列通过 index % entries 环绕,调用方承担数据不跨槽与写入顺序的责任;db_value() 把 RoCE path/type/valid、索引与 epoch 拼成 64 位 doorbell,写前调用内存屏障。cqe_ready() 比较 toggle 与 CQ 环绕 epoch 判断可读,poll() 另查状态字节,以避免把错误完成当成成功。

    值得注意的是双端协议与完成语义的差别:connect() 设置 RoCEv2 IPv4、MTU 4096、目标 QPN/GID/MAC 与重试参数,这些是源码写入的配置,并未证明网络拓扑、交换机拥塞策略或异机互通可靠。CPU 示例的 poll() 检查 CQE status,而 GPU 提交路径只等待 toggle,两条路径的「成功」判据不能混同。

    环绕与终止标志同样需要调用方自己负责:BNXTQueue.read()/write() 通过 index % entries 环绕,aux=True 写 SQ 后半的 MSN 表,调用方需承担数据不跨槽与写入顺序;build_pbl() 在队列尾两页写入终止标志。这些约定在代码里由调用方保证,而不是由驱动在每次写入时校验。

    故障与资源所有权:fini 只调 unrgtr

    同步调用方面,wait_cond() 使用毫秒超时的轮询;HWRM/RCFW 各自有超时和非零状态断言,但没有在这里定义重试、故障分类或失联后的 QP 重建。硬件平台需要把超时、固件错误和传输错误分别上报。

    所有权方面,fini() 仅调用 func_drv_unrgtr。这份文件没有显式 destroy_qp/destroy_cq、backing store 逐项释放或初始化异常回滚;资源可能由功能复位/进程退出收走,但不能据此认定热重启无泄漏,需要目标驱动/固件证据和重启压力测试。

    阶段 当前证据 下一验收
    无设备封包 12 项单测通过,覆盖 PBL/backing store、邮箱请求、RCFW 环绕、MR/QP/WQE/CQ 格式。 增加畸形 HWRM resp_len、RCFW 超时、非零状态、重复初始化与清理注入。
    BNXT 台架 loopback.py 与 connect.py 存在,但本轮未运行。 本地 PHY loopback、异机 SEND/RECV、环绕、乱序启动、断线重连、长时间稳定性。
    GPU 数据面 仅静态追到 tinygrad rdma_copies()。 双节点 GPU 校验和、CQE 错误传播、缓冲视图/越界、缓存可见性、尾延迟。
    车端集成 未连接目标硬件、模型实体或车辆。 只把 BNXT 暴露为可选计算传输插件;网络或固件失效不得获得 CAN 发送与 panda safety 权限。

    验证矩阵与插件边界

    BNXT 插件的最小声明应包括 PCI ID、固件/芯片版本、拓扑 peer group、MR 对齐与生命周期、队列容量、端到端超时、CQE 状态映射和故障后的资源回收。该驱动是计算数据搬运组件,不是车辆控制或安全网关;把它当作推理加速之外的东西会越过它本不承担的安全边界。

    还要强调一点:HWRM 与 RCFW 的超时和状态断言都属于「失败即停」,而不是「失败即分类」。在自制硬件上,固件版本、邮箱响应页长度、doorbell 偏移都必须由设备身份绑定后写入契约;任何绕过 hwrm() 直接写邮箱的路径,都会跳过这里的版本与能力协商,也就失去了一次可核对的一致性检查。

    常见问题

    HWRM 回包的长度为什么是风险点?

    因为 hwrm() 在读取响应页末项前未约束固件给出的 resp_len 不超过响应页长度,也未先验证它足以覆盖期望结构。畸形大长度可产生 IndexError 而非设计的超时/协议错误,属静态可见的输入校验缺口。

    fini() 之后 QP 和内存会被释放吗?

    源文档未给出显式 destroy_qp/destroy_cq、backing store 逐项释放或初始化异常回滚。fini() 仅调用 func_drv_unrgtr,资源可能由功能复位/进程退出收走,但不能据此认定热重启无泄漏。

    CPU 示例与 GPU 路径的「完成」判据相同吗?

    不相同。CPU 示例的 BNXTQP.poll() 检查 CQE status,而 GPU 提交路径只等待 toggle。两条路径的成功判据不能混同。

    BNXT 驱动能承担车辆控制或安全网关吗?

    不能。源文档明确它是计算数据搬运组件,不是车辆控制或安全网关;车端集成时应只把它暴露为可选计算传输插件,网络或固件失效不得获得 CAN 发送与 panda safety 配置权限。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_BNXT_FIRMWARE_QUEUE_TRANSPORT_FAILURE_CONTRACT_DEEP_DIVE.md(取用了 BNXT 分层初始化、HWRM/RCFW 回包、队列与故障边界及验证矩阵)。
    • 项目 02 —— openpilot-research/TINYGRAD_RDMA_BNXT_QUEUE_MEMORY_COMPLETION_CONTRACT_DEEP_DIVE.md(仅用于确认 RDMA 提交与完成语义的边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Cabana 消息详情:标签同步、编辑提交与告警的三条边界

    结论:Cabana 的「消息详情」页是 DBC 分析/编辑界面,不提供车端 CAN 发送或安全模式授权。它把消息选择、位图/信号视图、历史页与告警收敛在一个 DetailWidget 里,通过 MessageId(source/address)建立标签并刷新状态。需要记住三条边界:告警不是协议证明(不查校验、计数器与新鲜度);编辑只是内存 DBC 变更(磁盘保存是另一条链);关闭最后一个标签后详情与序列化的活动消息 ID 可能仍指向旧项。

    入口与状态传播

    主窗口把消息列表选中事件接到 CenterWidget::setMessage(),CenterWidget 在首次选择时惰性创建详情页。DetailWidget::setMessage() 用 source/address 组成的 MessageId 查找或新建标签,选择标签后同步 SignalView、BinaryView 和 LogsWidget,最后刷新标题/告警。TabBar::setCurrentIndex() 会同步发 currentChanged,回调再次进入 setMessage() 时由相同 ID 的早退门避免重复更新。

    函数 主要职责 边界
    findOrAddTab() 以 MessageId::toString() 作为标签文本和查找键,新增时填充消息名 tooltip。 tooltip 只在新增时设置;DBC 改名后 refresh() 更新标题,不同步重写旧标签 tooltip。
    restoreTabs() 从会话字符串恢复当前 DBC 中存在的消息标签,再尝试恢复活动项。 fromString() 解析失败退回 0:0;只查 DBC 存在性,坏字符串可能别名到 0:0;未知 DBC 消息不会恢复。
    refresh() 重算标题、删除按钮可用性和告警。 它不是所有子组件状态的唯一刷新器;信号视图/历史页各自响应 DBC、撤销事件。
    updateState() 若到达集合包含当前 ID,更新当前显示的位图或历史页,再重算告警。 未显示页不在该调用中增量更新;切换到 Logs 页时另调用 onShown()。

    标签关闭请求由 DetailWidget 构造连接直接交给 TabBar::removeTab()。若关闭最后一个标签,TabBar 发 currentChanged(-1);详情页的回调忽略负索引,也未清空 msg_id_ 或销毁 CenterWidget。因此最后一个标签关闭后,详情内容及序列化的活动消息 ID 可能仍指向旧项——这是源码可见的状态边界,实际 GUI 表现待验证。初始 msg_id_ 为默认 0:0,setMessage() 的相同 ID 早退也使首次选择真实 0:0 时不能建立标签。

    告警不是协议证明

    updateWarnings() 仅在当前 ID 能找到 DBC 消息时检查三类问题:没有收到该 source/address 的帧、DBC 长度与最新帧长度不同、以及 BinaryView::getOverlappingSignals() 返回的位重叠。没有 DBC 消息时不生成这些告警。重叠集合受当前可见复用分支和 Normal 类型筛选影响,不能当作完整 DBC 冲突审计。

    告警会检查 告警不会检查
    该 source/address 是否收到帧 checksum 是否正确
    DBC 长度与最新帧长度是否一致 计数器连续性与回绕
    当前可见信号是否位重叠 时间戳新鲜度
    —— 车辆拓扑与 panda safety

    热力图菜单有 Hidden、Live 和当前范围/All 三态;timeRangeChanged 回调在共享范围变化时更新文字,并让无范围的直播流进入 live 统计模式。需注意 Hidden 是布局可见性,不等于清掉已有统计。热力图菜单也只影响显示与统计模式,不会修改原始 CAN 帧。

    消息编辑与撤销栈

    editMsg() 以已有 DBC 长度或最新帧长度初始化非阻塞 EditMessageDialog。弹窗的 Name、Size、Node、Comment 字段分别使用字符过滤/数值输入/多行文本;只有用户改动 Size 输入时才把数值夹到 1..CAN_MAX_DATA_BYTES。若初始长度为 0 而用户不碰该输入,静态代码未再次夹取;是否可经完整 GUI 流程提交 0 需验证。

    validateName() 大小写不敏感地禁止保留名 untitled,非空且修改过时检查当前 source 的 DBC 重名;空字符串没有被此函数判为无效,nameValidator 只过滤新输入字符、允许删空字段。result() 会 trim 名称/节点/注释,DetailWidget::draw() 在弹窗返回「已接受」后才压入 EditMsgCommand。

    更细的一条边界:EditMsgCommand::undo() 以 old_name.empty() 判断原消息是否不存在。若先把已有消息改为空名称,再编辑一次,后一条命令的撤销会按「新建消息」路径移除消息。删除按钮另走 RemoveMsgCommand。DBCFile::updateMsg() 直接保存传入名称和长度到内存对象,没有补充非空名称验证。这些都是内存 DBC 编辑,磁盘保存是另一条链。

    插件与硬件边界

    若把详情页拆成插件,消息选择、可见统计、DBC 编辑和安全诊断应分成不同能力。编辑插件输出版本化 DBC 草稿和校验结果,不能把「当前无告警」解释为硬件接线或发送规则安全——告警本就不检查 checksum、计数器、新鲜度与 panda safety。插件宿主应统一校验名称非空、CAN 长度范围、source/address 身份和提交事务,再让只读分析视图消费已确认的版本;硬件采集插件保留原始帧及时间戳,不依赖 UI 当前选项。

    常见问题

    Cabana 详情页没有告警,是否说明报文没问题?

    不是。updateWarnings() 只检查「是否收到帧」「DBC 长度与最新帧长度是否一致」「当前可见信号是否位重叠」三类;它不检验 checksum、计数器、时间戳新鲜度、车辆拓扑或 panda safety,且重叠集合受可见复用分支和 Normal 类型筛选影响,不能当作完整 DBC 冲突审计。

    为什么关闭最后一个标签后详情还可能指向旧消息?

    因为关闭请求直达 TabBar::removeTab(),关闭最后一个标签时 TabBar 发 currentChanged(-1),而详情页回调忽略负索引,也未清空 msg_id_ 或销毁 CenterWidget。于是详情内容及序列化的活动消息 ID 可能仍指向旧项。这是源码可见边界,实际 GUI 表现待验证。

    编辑消息时把名称删空会发生什么?

    validateName() 不把空字符串判为无效,nameValidator 也允许删空字段,DBCFile::updateMsg() 更不会补非空校验。更麻烦的是 EditMsgCommand::undo() 以 old_name.empty() 判断原消息不存在:先改空名再编辑一次,后一条命令的撤销会按「新建消息」路径移除消息。

    Cabana 能直接向车上发 CAN 或授权安全模式吗?

    不能。这页是 DBC 分析/编辑界面,源码明确它不提供车端 CAN 发送或安全模式授权。它做的是内存 DBC 编辑与显示统计,磁盘保存是另一条链;插件化时也应把编辑、统计与安全诊断拆成不同能力。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CABANA_DETAIL_MESSAGE_TABS_EDIT_WARNING_STATE_DEEP_DIVE.md(取用了详情页入口与标签同步、告警三类检查、编辑弹窗与 EditMsgCommand 撤销语义、插件与硬件边界)。
    • 项目 02 —— openpilot-research/CABANA_DBC_UNDO_MULTIFILE_DIRTY_CONTRACT_DEEP_DIVE.md(仅用于确认命令栈专题的接续关系,未取用其未公开内容)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • tinygrad DSP 后端:ION 视图偏移与 FastRPC 反向服务边界

    结论:tinygrad 的 Hexagon DSP 后端(ops_dsp.py)在固定归档里只是一条可选、且未被车端选用的推理路径:openpilot 的 load_oob() 当前只选 USB+AMD、QCOM、METAL 或 CPU:LLVM,不选择 DSP。它把主机缓冲以「size + offset + ION share fd」三元组编入 FastRPC 参数,并在 DSP 侧生成 entry 包装读取 pra 数组;同时用一条常驻 daemon 线程向 DSP 提供 open/close/seek/read/stat/mmap 反向文件服务。这两处——非零 offset 视图的解映射地址,以及反向 RPC 的请求字段——都是跨处理器契约,不能当作无权限的数据通道。

    DSP 不是当前车端推理硬件

    源文档开篇即划定边界:openpilot 的 load_oob() 只选择 USB+AMD、QCOM、METAL 或 CPU:LLVM,不选择 DSP。因此 ops_dsp.py 是 tinygrad 侧的 Hexagon 后端实现,不能被当作现有车端推理硬件。归档源码不修改,本篇为静态分析与无设备契约检查。

    真实路径的初始化是多阶段过程:DSPDevice.__init__() 打开 /dev/ion、读取 /dsp/cdsp/fastrpc_shell_3 并复制到 ION 缓冲、初始化 /dev/adsprpc-smd,然后启动反向服务线程;mock 路径则只注册模拟 renderer/program。渲染器禁用了 fp8/bfloat16 与 SQRT 原生映射,global_size、local_size 与 wait 未参与启动参数。

    阶段 静态行为 源文档标注未验证
    编译 目标 hexagonv65 / HVX v65 128b;真实路径生成 4 KiB 节对齐共享库链接脚本 Hexagon 工具链与编译产物
    主机内存 ION 分配并 share fd,再 mmap 为 CPU 可访问缓冲;视图继承同一 fd 并累加 offset /dev/ion 与驱动行为
    FastRPC 装载 shell、初始化 CDSP、动态装载 tinylib,失败重连一次 /dev/adsprpc-smd 与真实调用
    反向服务 daemon 线程响应 DSP 发起的文件操作 请求长度与权限边界

    主机缓冲与 DSP ABI 的三元组

    DSPAllocator 返回 DSPBuffer(va_addr,size,share_info,offset);DSPProgram 从缓冲提取 size、offset、shared fd,rpc_prep_args() 把 size/标量、offset 数组、8 字节计时输出和 fd/attrs 打包进 FastRPC 调用。DSP 侧生成的 entry 按固定下标读这些参数,这个数组顺序本身就是后端 ABI,不能只改 allocator 或 renderer 一侧。

    DSP 侧参数 含义
    pra[0] 读 buffer size 或标量值
    pra[1] 读偏移 offi
    pra[2] 写计时输出
    pra[i+3] 取得该输入的 DMA fd

    DSPProgram.__call__() 至多 15 个缓冲,按 8 字节槽写入 buffer size 与标量,另传 32 位偏移和 ION shared fd;调用 exec_lib() 后返回 DSP 侧微秒计时换算值。这意味着调用期必须持有 memoryview 引用以覆盖 ioctl,否则缓冲可能在传输中被回收。

    非零 offset 视图:映射与解映射地址不一致

    源文档把「非零 offset 的视图」列为需要单独验收项。DSPAllocator._offset() 给视图保留 size 并累计 offset;而 DSPRenderer._render_entry() 生成 HAP_mmap(..., sz_or_val_i, ..., fd, 0)+offi,最后却用已加偏移的 buf_i 调 HAP_munmap(buf_i, sz_or_val_i)。无设备调用 _render_entry() 的最小样例实际输出了这两行。

    后果是条件性的:当 offi 大于 0 时,映射基址、传给 kernel 的指针及解映射地址三者不一致;若长度只等于视图大小,指针后方可用范围也小于视图大小。源文档把它定性为「源码/生成文本可复核的条件性视图契约问题」,并明确尚未在目标 DSP 上确认驱动行为。对插件 ABI 的直接含义是:应传原始基址和映射总长,分别持有映射租约与视图偏移。

    反向 RPC 是一条跨处理器信任边界

    RPCListener.run() 是响应 DSP 发起请求的 daemon 线程,处理 open/close/seek/read/stat/mmap;tinylib 由当前 binded_lib 提供,其他路径经宿主 os.open/os.stat 等访问。源文档明确:请求字段是跨处理器信任边界,循环中对输入/输出对象个数、长度及指针推进没有完整容量验证。即使本代码只期望可信 DSP shell 与编译产物,硬件插件设计也应把路径白名单、请求大小上限、fd 生命周期和异常隔离前置。

    生命周期还有两处需要故障注入:exec_lib() 在 invoke 成功后关闭库,异常时先重连再重试;若 invoke 已打开句柄而后续失败,关闭/连接恢复的边界需验证。正常路径关闭库句柄,失败路径没有 finally 保证配对关闭。此外 RPCListener 是无限 daemon 循环,没有显式停止/join,同一设备上的 binded_lib/offset 为共享可变字段,并发装载需单独验证;未知 sc 抛异常会终止线程。

    自制硬件的验收顺序

    源文档给出的顺序是:先在没有执行器的隔离台架验证参数布局、视图偏移、fd/映射回收、RPC 文件权限、超时与并发,再对参考模型做逐节点数值和完整模型尾延迟比较。测试层面归档包含 DSP 专属量化用例,但需要对应设备与工具链;本轮只执行了 _render_entry() 的无设备生成探针,未运行 /dev/ion、FastRPC、Hexagon 编译、qemu 或真实模型。

    最后一条边界是权限:DSP 插件不应拥有 CAN 发送或修改 panda 安全策略的权限。真实初始化中的 ION 分配、fd share、CPU mmap、shell 装载和 FastRPC 连接是多阶段过程,源文档指出未见统一逐阶段回滚,这也是台架要覆盖的失败路径。

    常见问题

    DSP 后端现在被 openpilot 车端推理使用吗?

    没有。源文档明确 openpilot 的 load_oob() 当前只选择 USB+AMD、QCOM、METAL 或 CPU:LLVM,不选择 DSP。ops_dsp.py 是 tinygrad 侧的 Hexagon 后端实现,不能当作现有车端推理硬件。

    非零 offset 的缓冲视图有什么风险?

    源文档探针显示,_render_entry() 生成映射时基址加了偏移,但解映射却用了已加偏移的缓冲指针。当 offi 大于 0 时,映射基址、传给 kernel 的指针与解映射地址不一致;若长度只等于视图大小,可用范围也小于视图大小。插件 ABI 应分别持有映射租约与视图偏移。

    反向 RPC 文件服务需要做哪些限制?

    该 daemon 线程把 DSP 请求的路径、大小与 fd 交给宿主文件操作,源文档指出循环中没有完整的容量验证,非 tinylib 名称可传到 os.open。即使只期望可信 DSP,也应前置路径白名单、请求大小上限、fd 生命周期和异常隔离。

    qemu 模拟出来的执行时间能当作 DSP 实时性数据吗?

    不能。mock 路径用 qemu-hexagon-static 执行静态程序,返回值按 1 GHz 假设把指令数伪装成秒数,源文档明确它不能作为 DSP 或车端实时性证据。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_DSP_FASTRPC_ION_VIEW_RPC_SECURITY_CONTRACT_DEEP_DIVE.md(取用了 DSP 后端组件表、pra 参数 ABI、非零 offset 视图探针结果与反向 RPC 安全边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 系统时间自举:日期范围门、GNSS 校时与信任边界

    结论:openpilot 的系统时间自举建立在几个纯日期范围检查上:Python 侧的 system_time_valid() 用本地墙钟在固定下界 2025-02-21 与固定上界 2035-01-01 之间做严格开区间比较,若找到 systemd 可执行文件,还会把它的修改时间加一天并与下界取较大值。「有效」只表示当前日期落在区间内,不等于可信 UTC、证书有效或各传感器时钟已对齐:一台时钟在区间内但快慢数天的设备同样能通过。本文为固定源码快照 c8fb906 的静态阅读与有限隔离探针,未修改系统时钟、未连接 GNSS 或访问网络。

    公共日期门:范围检查而非可信时间

    MIN_DATE 与 MAX_DATE 分别为 2025-02-21 和 2035-01-01。min_date() 若发现 systemd 可执行文件,会把该文件的修改时间按本地时间加一天,再与固定下界取较大值;system_time_valid() 用本地 datetime.now() 做严格开区间比较。它没有读 NTP 同步状态、GNSS 精度、硬件 RTC 来源或 TLS 证书有效期。

    一个结构性边界是:若该文件的修改时间异常地进入未来,动态下界可能高于上界,导致整个有效区间为空。四项假路径探针确认了这一族行为:没有该文件或修改时间早于固定下界时仍用 2025-02-21;较新的修改时间会把下界抬到其本地日期加一天;恰好等于动态下界或 2035-01-01 上界均判无效;修改时间晚于上界可使整个有效区间为空。

    原生 C++ 侧还有一条不同的判断:它的固定下界是 2024-08-26,也考虑该文件修改时间加一天,但没有 2035 上界,安装器依赖这一路径。两端不能被当作同一个时间状态。

    项目 Python 侧 C++ 侧
    固定下界 2025-02-21 2024-08-26
    固定上界 2035-01-01 无
    动态下界 systemd 文件修改时间的本地时间加一天,与固定下界取大 同样考虑该文件修改时间加一天
    加一天的算术 先转本地无时区时间,再加一个日历日 直接对 epoch 秒加 86400,即固定 24 小时

    两端的「加一天」也不完全等价:在使用夏令时的本地时区且这一天跨越切换点时,两个下界可差一小时。离线以 America/New_York 的 2026-03-07 12:00 为修改时间,Python 得到次日 12:00,固定秒数得到次日 13:00;2026-10-31 12:00 则分别得到次日 12:00 与 11:00。这只验证时区算术差异,目标设备是否配置夏令时尚未验证。

    timed 的 GNSS 校时链

    定位服务按启动时的一个可用性参数在外部与内部定位主题之间选一个,timed.main() 创建一次订阅,不在主循环中动态切换。每轮先把当前墙钟写入时钟消息,并用上述范围判断设置外层有效位。只有所选主题本轮更新、消息单调时间距当前不超过两秒、定位有效且 GNSS 时间落在下界到上界之间,才调用校时函数;此路径没有检查所选定位消息的顶层有效位。偏差不足十秒直接跳过,更大的偏差通过系统命令设置,失败只记录异常。

    六项假消息与假命令探针补充了执行顺序证据:时钟发布发生在 GNSS 门控之前,即使本地墙钟无效也可用新鲜定位尝试校时;所选定位消息的外层无效不阻断调用。定位时间戳在检查「本轮更新、新鲜度、定位有效」之前即被转成日期时间,因此假消息即使标为未更新,超出可表示范围的时间戳仍会先抛异常,使本轮不进入其余门控。此外主循环在校时函数正常返回后无条件睡十秒,未根据实际校时成功与否区分退避。

    环节 实现事实 边界
    订阅选择 按启动时可用性参数在两条定位主题中选一条 主循环不动态切换
    新鲜度门 消息距当前单调时间不超过两秒 需定位有效且时间在范围内
    校时调用 偏差超过十秒才设置系统时间 不检查定位消息顶层有效位
    异常处理 设置失败只记录异常 主循环仍无条件睡十秒

    启动自举与下游行为

    时间门被多个网络与硬件路径复用:GNSS 相关服务仅在时间「有效」时给模组发送辅助时间,并在网络可用时才取辅助数据。另一方面 timed 又依赖 GNSS 首次有效定位来校准系统时间,所以自制硬件必须设计 RTC、NTP 或独立定位的冷启动路径,不能假设辅助时间与 GNSS 校时会自动互相解锁。

    下游同样依赖这个门:eSIM 的配置下载函数先检查系统时间范围,通知上传也有同一门;更新服务在时间无效时等待再重试;UI 配对与更新路径、以及取令牌的辅助函数也依赖它。部分初始设置在证书错误且本地时间无效时会尝试重启时间同步服务。这些调用说明「日期在范围内」是业务前置条件,却不能替代 HTTPS 自身的证书验证和服务端认证。

    还有两个容易混淆的语义:GNSS 时间戳被转为 UTC 后移除时区信息,再与本地时间下界比较,设备为 UTC 时一致、非 UTC 时边界会偏移;而外层有效位为真只表示当前墙钟通过了范围门,不是刚从 GNSS 成功校时。该进程也不会证明相机帧起始、IMU 中断与诊断日志时间戳已与墙钟完成共时钟标定。

    硬件与插件化要求

    时间服务应分别声明墙钟来源与可信级别、UTC 与闰秒、单调时钟域、观测时间戳、最后同步时间、估计误差、时钟跳变和上游服务状态。只有受信任的平台层可设置系统时间;GNSS 与网络插件应提交带来源与精度的候选时间,不能直接把范围检查当成「可信时间」,也不能自行运行系统校时命令。UI、更新与 eSIM 的网络门应区分「尚无可信时间、可信时间可用、时间失步」三种状态,避免把范围检查误认为完整安全检查。

    待验证边界

    尚未验证的场景包括:无 RTC 冷启动、真实镜像中该文件修改时间异常、时区非 UTC、GNSS 定位延迟或失效、NTP 与 GNSS 冲突,以及系统时间前后跳变对证书、令牌、日志与相机时序的影响。本轮未执行这些动态或实板测试。

    常见问题

    system_time_valid() 为真就代表时间可信吗?

    不代表。它只是一个纯日期范围检查,不读 NTP 同步状态、GNSS 精度、硬件 RTC 来源或 TLS 证书有效期。一台时钟在区间内但快慢数天的设备同样能通过,因此「日期合理」不等于可信 UTC。

    Python 与 C++ 的时间有效判断一致吗?

    不一致。Python 侧下界为 2025-02-21、上界为 2035-01-01;C++ 侧下界为 2024-08-26 且没有上界。两端的「加一天」算术也不同:Python 先转本地无时区时间再加一个日历日,C++ 直接对 epoch 秒加 86400,跨夏令时时可差一小时。

    只要 GNSS 有定位就会校准系统时间吗?

    不一定。只有所选主题本轮更新、消息距当前单调时间不超过两秒、定位有效且时间落在范围内才调用校时函数;偏差不足十秒会直接跳过。而且该路径不检查定位消息的顶层有效位。

    本地墙钟无效会不会阻止 GNSS 校时?

    不会。探针显示时钟发布发生在 GNSS 门控之前,即使本地墙钟范围检查不通过,也可用新鲜定位尝试校时;所选定位消息的外层无效同样不阻断调用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SYSTEM_TIME_BOOTSTRAP_TRUST_CONTRACT_DEEP_DIVE.md(取用了公共日期门、GNSS 校时链、下游消费者与硬件插件要求)。
    • 项目 02 —— openpilot-research/NATIVE_UTIL_IO_EXIT_CONTRACT_DEEP_DIVE.md(关联的原生工具专题,用于对照 C++ 与 Python 两套日期门差异,本文未展开其 I/O 实现)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Ford 与 GM:曲率、ASCM 网关与双车速检查的三道安全边界

    结论:Ford 与 GM 都以「软件控制器只生成候选 CAN 帧、panda 固件独立放行」为前提,但两者路径不同:Ford 走角度/曲率式横向控制并用 ABS 车速与 PCM 第二车速交叉核验;GM 分 ASCM/OBD-II 网关与相机 ACC 两条接法,部分车型用 sigmoid 加线性拟合做横向映射。无论哪种,alpha_long、safetyParam、ALLOW_DEBUG 与 dashcamOnly 必须同时核对,不能单看软件发送代码推断硬件可执行。本文为源码静态分析,不是接线图、适配声明或实车安全验证。

    Ford:曲率控制与双车速检查

    CarInterface._get_params() 使用角度/曲率式横向控制,按 CAN FD、雷达 DBC、EPS 固件和指纹设置 ford 安全模式与总线。非 CAN FD 通过 EPS 固件配置排除缺少 TJA/LCA API 的车辆;CAN FD 的 16 字节 SecOC 控制报文未支持时标记 dashcamOnly;手动车型设最低使能车速。get_pid_accel_limits() 在临近巡航目标速度时降低 PID 正加速度上限以防积分饱和——参数层在 alpha_long 或雷达可用路径写入纵向安全参数,但这不是固件已放行的充分证据,CAN FD 纵向白名单还受编译门限制。

    输出侧,CarController.update() 以 20 Hz 曲率命令工作,部分车型先用 anti_overshoot() 平滑,随后限测量曲率误差和变化率;CAN FD 与经典 CAN 分别调用不同 lateral 构帧函数,其它未验证的曲率率、路径角和偏移信号保持 inactive。纵向由 apply_creep_compensation() 及 CarController.update() 根据低速蠕行、目标加速度下降率和坡度影响生成 ACC 的油门、减速度及制动预充请求。

    层 关键入口 已核对行为
    参数 _get_params() 角度/曲率式横向;按 CAN FD、雷达 DBC、EPS 固件设置安全模式;SecOC 未支持标 dashcamOnly
    横向输出 CarController.update() 20 Hz 曲率;可先 anti_overshoot() 平滑;限曲率误差与变化率
    纵向输出 apply_creep_compensation() 按蠕行、目标加速度下降率与坡度生成油门、减速度及制动预充
    雷达 RadarInterface.update() 按 DBC 区分 Delphi ESR 与 MRR;MRR 检查扫描序列、覆盖范围及临时不可用
    固件 RX ford_rx_hook() ABS 车速与 PCM 第二车速交叉核验;用 yaw rate 估算曲率
    固件 TX ford_tx_hook() 不允许 Lane_Assist_Data1 承载执行转向;不允许设置阻止原车 AEB 的位
    模式选择 ford_init() CAN FD 自控纵向仅在 ALLOW_DEBUG 且安全参数启用时添加 ACCDATA

    两个细节需要单独记住:Ford 固件 TX 对两种横向报文都要求额外路径信号为 inactive;RX 对部分 PCM 第二车速帧忽略计数器/校验和,但仍检查质量位。因此把多条总线合并为无来源的 VehicleSpeed 会损失安全判据。

    GM:ASCM 网关与相机 ACC

    CarInterface._get_params() 在 ASCM/OBD-II 网关路径默认自控纵向,在相机 ACC 路径默认原车纵向,实验纵向由 alpha_long 和安全参数选择;EV、无雷达、替代 ACC、SDGM 分支另有限制,无雷达或缺少测试覆盖可标记 dashcamOnly。alphaLongitudinalAvailable 只是能力提示,不能代替检查 openpilotLongitudinalControl 与固件模式。

    横向映射上,部分车型用 get_lataccel_torque_siglin() 与 torque_from_lateral_accel() 以 sigmoid 加线性拟合横向加速度与扭矩并提供逆向插值,其余走基础线性映射——此调校是车型特定控制模型,不可作为新硬件通用执行器标定。输出侧 CarController.update() 按驾驶员扭矩限幅;相机接法需对齐相机计数器,回环确认距上次发送不足 15 ms 时跳过以避免 EPS 故障,活跃/非活跃横向采用不同节拍。

    层 关键入口 已核对行为
    参数 _get_params() ASCM/OBD-II 网关默认自控纵向;相机 ACC 默认原车纵向;EV/无雷达等另有限制
    横向映射 get_lataccel_torque_siglin() / torque_from_lateral_accel() 部分车型 sigmoid + 线性拟合与逆向插值;其余基础线性映射
    横向输出 CarController.update() 相机接法对齐相机计数器;回环确认不足 15 ms 时跳过
    纵向输出 同上 自控纵向按 25 Hz 生成油门/再生制动与摩擦制动;网关接法发 ADAS keepalive
    雷达 create_radar_can_parser() / RadarInterface.update() ASCM 路径解析 Continental LRR 头与目标槽,检查故障、CAN 有效性与目标生命周期
    固件 RX gm_rx_hook() 从驾驶员扭矩、后轮速度、巡航按钮或原车 ACC 决定 controls_allowed
    固件 TX gm_tx_hook() 检查扭矩、摩擦制动、油门/再生制动及 apply 位;原车纵向仅允许取消语义按钮
    模式选择 gm_init() 相机自控纵向仅在 ALLOW_DEBUG 中添加白名单;ASCM 模式关闭固件转发

    需要指出的是,GM 的公共 RX 检查仍有校验和/计数器 TODO,不能称为完整协议完整性验证;ASCM 模式则关闭固件转发,与相机接法的行为不同。原车纵向仅发送延迟后的取消按钮,以降低用户踩刹车时的 ECM 故障风险。

    对车载硬件和插件接口的结论

    1. 车型插件应声明精确平台、DBC、总线拓扑、雷达来源、纵向模式、编译能力和 safetyParam。Ford CAN FD SecOC 不支持、GM 相机自控纵向需调试固件的限制,必须在装车前阻止执行模式。
    2. Ford 的双车速与 yaw 质量位、GM 的回环计数器/时间间隔需要在硬件接口中保留来源与时间戳;把多条总线合并为无来源字段会损失安全判据。
    3. 软件控制器只生成候选 CAN 帧。panda 的 RX/TX/转发规则仍是独立硬件边界;未知平台、无法确认固件、错误总线或诊断失败时保持被动。
    4. 后续目标车型需完成 DBC 解析/打包、丢帧/错校验/双车速失配、回环时序、AEB 干预、断电与失联的 SIL/HIL,再做封闭场地验证。这里没有实车结论。

    常见问题

    alphaLongitudinalAvailable 为真,是否就能启用纵向控制?

    不能。GM 侧源文档明确 alphaLongitudinalAvailable 只是能力提示,不能代替检查 openpilotLongitudinalControl 与固件模式;Ford 侧参数层即使写入纵向安全参数,也不是固件已放行的充分证据,CAN FD 纵向白名单还受编译门限制。

    Ford 的转向命令能否走 Lane_Assist_Data1?

    不能。ford_tx_hook() 明确不允许 Lane_Assist_Data1 承载执行转向,也不允许设置阻止原车 AEB 的位;固件还会对曲率及测量误差、ACC 油门/制动、按钮权限分别检查,并要求两种横向报文的额外路径信号为 inactive。

    GM 的横向映射能不能直接照搬到新硬件?

    不能。部分车型使用的 sigmoid 加线性拟合与逆向插值属于车型特定控制模型,源文档明确它不可作为新硬件通用执行器标定;其余车型走基础线性映射,两者不能混用。

    为什么不能把多条总线合并成一个 VehicleSpeed?

    因为会损失安全判据。Ford 用 ABS 车速与 PCM 第二车速交叉核验、GM 依赖回环计数器/时间间隔,这些来源与时间戳本身就是判据;源文档要求硬件接口保留来源与时间戳,未知平台或无法确认固件时保持被动。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/FORD_GM_DEEP_DIVE.md(取用了 Ford 曲率控制与双车速检查、GM ASCM/相机两条接法、固件 RX/TX 边界与插件结论)。
    • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(仅用于确认 Ford/GM 品牌目录与跨品牌流程的总体位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • panda safety 测试链:libsafety、回放与变异的证据边界

    结论:panda safety 的测试链要分四层证据看——本机逻辑库、离线回放、板级验证、车辆验证,它们不能互相替代。源文档反复强调:默认启用的 ALLOW_DEBUG 测试库、test.sh 要求的 100% 行覆盖率、以及回放返回的布尔通过,都不等于目标固件、线束或实车安全认证。make_CANPacket() 得到的只是 DBC 构帧后的 CFFI CANPacket_t,不是 panda USB/SPI、FDCAN 或 ECU 真实发送回执。

    一、本机库与通用断言

    入口 作用与限制
    _build_libsafety()、__getattr__() 首次取 libsafety 时将测试 C 翻译单元编译为临时共享库;默认启用 ALLOW_DEBUG,release=True 才不加该宏。正常模式加 UBSan,覆盖率模式改用 gcov flags。
    make_CANPacket()、CANPackerSafety.make_can_msg_safety() DBC 构帧后转成 CFFI CANPacket_t,保留地址、bus、长度码与数据;不是 panda USB/SPI、FDCAN 或 ECU 真实发送回执。
    init_tests() 测试库可人工改时钟、使能和测量窗口,并设置 safety_mode_cnt=2 等测试专用状态,并不模拟真实 MCU 启动。
    test_spam_can_buses()、test_fwd_hook() 对已列扫描地址、总线及测试用长度检查 TX/转发;不是 29 位地址和 0~64 字节载荷的全穷举。
    test_safety_tick()、test_relay_malfunction() 分别验证缺失关键 RX 时退出与继电器故障否决;仍是本机状态模拟。

    库编译使用 -Wall -Wextra -Werror -nostdlib -fno-builtin;common.py 用继承式 mixin 组合地址扫描、踏板/巡航、转向扭矩、角度、曲率、纵向和转发断言。SCANNED_ADDRS 扫全 11 位地址,但仅取少量 29 位诊断区间,且基础 spam 测试固定构造 8 字节零载荷。test_tx_hook_on_wrong_safety_mode() 读取其他测试类声明的 TX 列表并有多组车型/地址重叠例外,它是跨模式回归检查,不是形式化证明。另一个关键点:独立 test_release_build.py 只确认两种测试库能编译,没有对 release 版本逐车型跑相同 TX/RX 行为矩阵。

    还要注意测试库与真实固件的差异:libsafety/safety.c 内嵌仓库的 can.h、safety.h、ignition.h,以全局 timer_cnt 替代 MCU 微秒计时器,并暴露测试专用 getter/setter。模式切换仍走真实 safety 头文件,但总线驱动、线束继电器、电源/心跳和芯片时钟不在测试库内;init_tests() 在 set_safety_hooks() 之后另改测试状态,例如跳过初期继电器忽略窗口,故它是逻辑钩子验证而不是板级启动验证。

    二、覆盖率与变异测试的解释边界

    机制 给出的结论 不能被读成什么
    test.sh + UBSan 先跑 UBSan 单测,再在 SAFETY_COVERAGE=1 下重跑。 行覆盖率(排除 libsafety 包装器后 100%)不等于分支、状态序列、真实总线或发布固件覆盖。
    MISRA(test_misra.sh) 用 cppcheck 执行静态检查。 不能与动态 safety 断言合并为同一种证据。
    enumerate_sites()、build_priority_tests()、eval_mutant() 对预处理后的 safety C 生成变异点,按车型文件或共享核心测试选用例,统计杀死、存活或基础设施错误。 modes/ 下默认只取同名 test_<mode>.py;共享模式头文件若无同名测试,所选列表可能为空,不能据「该文件变异全部存活」直接断言无测试能覆盖它。

    变异统计里还有两个陷阱:worker 异常或进程池崩溃在结果汇总中被记为 killed,而另一路 Python 异常记为 infra_error——查看变异得分时必须把崩溃原因从有效测试断言失败中分离;脚本还有三条已知存活变异例外,不能把返回码零解释为零存活点。

    三、回放的证据边界

    replay_drive() 从日志 carParams.safetyConfigs[-1] 默认取最后一个模式,可覆盖参数;package_can_msg() 将 src % 4 映射为本地 bus。它按 logMonoTime 排序,给安全库设置软件时钟;在离片段首尾各超过 1 秒的区间,对每条 CAN 消息调用 safety_tick(),而板端该函数按 1 Hz 调用。回放成功条件是「控制允许时无 TX 被拒、无 RX 无效、没有区间内的 RX 健康失败」;控制未允许时被拒的 TX 仍计数但不使返回值失败。没有 can/sendcan 消息时会在首尾索引处失败,不是空路段的安全通过证明。

    init_segment() 用首个识别到的转向 sendcan 帧人工设 controls_allowed、上轮目标及测量值,然后断言该帧 TX 可过。这样可降低片段开头状态缺失导致的误报,但也意味着回放不完整验证实际巡航使能沿和最初几帧的真实状态。测试前应记录路段是否从设备启动/控制开始,不能只看最终布尔结果。

    四、自制硬件的验收证据链

    1. 本机逻辑:按目标安全模式分别运行 development/release 构建和关键 RX/TX 单测,保存精确源提交、编译宏、测试选择及失败列表。
    2. 离线时序:用目标车型路段回放,记录首帧预置、源 bus 映射、RX 健康与「控制未允许时拒帧」;回放不能替代无车故障注入。
    3. 板级验证:确认 MCU 固件签名/模式参数读回、实际 FDCAN 发送回执、继电器方向、心跳失联和异常报文抑制,不用 CFFI 测试库结果替代。
    4. 车辆验证:在合法合规的受控条件下单独确认 ECU 接受、原车 AEB/制动保留、驾驶员覆盖与恢复;插件系统不得把测试报告布尔值当作实时控制许可。

    本轮未运行完整安全测试,也没有目标硬件/车辆证据;上述内容是测试框架能力与缺口的源码索引。

    常见问题

    默认测试通过能否当作发布固件权限证明?

    不能。测试库默认启用 ALLOW_DEBUG,日常单测可覆盖仅调试注册的模式和纵向参数;test_release_build.py 只确认两种测试库能编译,没有对 release 版本逐车型跑相同 TX/RX 行为矩阵。

    100% 行覆盖率说明什么?

    只说明在 SAFETY_COVERAGE=1、排除 libsafety 包装器后行覆盖达到 100%,不等于分支、状态序列、真实总线或发布固件覆盖。MISRA 的 cppcheck 检查是另一种证据,不能与之合并。

    回放成功是否等于通过了安全验证?

    不等于。回放要求「控制允许时无 TX 被拒、无 RX 无效、无区间 RX 健康失败」,但控制未允许时被拒的 TX 仍计数而不使返回值失败;init_segment() 还人工预置首个转向帧状态,故不完整验证实际使能沿与最初几帧。

    变异评分零存活就代表没有缺口吗?

    不代表。共享模式头文件若无同名测试文件,所选用例列表可能为空;worker 异常或进程池崩溃会被汇总记为 killed,脚本还有三条已知存活变异例外,因此不能把返回码零解释为零存活点。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SAFETY_TEST_HARNESS_REPLAY_EVIDENCE_DEEP_DIVE.md(取用了本机库与通用断言、覆盖率与变异边界、回放证据边界、硬件验收证据链)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • openpilot 发布构建链:文件选择、LFS、测试与推送

    结论:openpilot 的发布构建有两条 Git 主链:build_stripped.sh 生成精简源分支,build_release.sh 在固定设备工作树内编译并产出设备预编译分支。文件选择由 release_files.py 按 git ls-files 的受跟踪路径清单过滤,实际复制的是工作树文件,因此本地未提交修改可能进入产物。路径存在、blob 不超限、LFS 已安装,都不证明模型内容与哈希正确。本文为固定源码快照 c8fb906 的静态阅读,未执行任何发布命令。

    两条主链的入口与时序

    build_stripped.sh 的默认目标目录由 mktemp 生成后又被删除重建,显式指定的 TARGET_DIR 同样会被删除;它复制源 .git、新建 orphan tmp、移除旧文件及子模块目录,写入源提交元数据后提交,再检查子模块、LFS 与尺寸,只在有 BRANCH 时强推。它不运行 SCons,也不检查运行时模型是否可用。

    build_release.sh 则在固定的设备工作树目录中操作:先强制移除 worktree,失败则整体删除;随后添加共享引用的 worktree、删除本地 staging 引用、把当前 HEAD 指向它并清空 index。它运行 scons,再按 PANDA_DEBUG_BUILD 选择 release 证书构建或 debug panda;INCLUDE_BIG_MODEL 时仅用 test -f 检查三种模型路径。最后删除中间产物与发布工具、生成 prebuilt、提交、做尺寸检查、运行 test_onroad.py,成功后对一个或多个分支强推。

    两条链路共同的数据流是:git ls-files -z --recurse-submodules 取清单,正则过滤,xargs -0 cp -pR --parents 复制,再在目标目录提交。

    阶段 精简源分支 设备预编译分支
    调用 build_stripped.sh,可由 BRANCH 决定是否推送 build_release.sh,必须给 RELEASE_BRANCH
    目标目录 临时目录或 TARGET_DIR,都会被删除重建 固定的设备工作树目录,先移除 worktree
    构建与模型 不运行 SCons,不检查运行时模型 scons,INCLUDE_BIG_MODEL 时仅 test -f 检查模型路径
    提交前后 写源提交元数据、提交、检查子模块/LFS/尺寸 提交、尺寸检查、test_onroad.py、强推

    文件选择与共享辅助

    release_files.py 排除 .git/、.venv/、CI 工作流、.gitmodules、.gitattributes 与默认 .lfsconfig 等;未设置 INCLUDE_BIG_MODEL 时还按 big_ 前缀排除大模型。选择依据是跟踪路径清单,实际复制的是工作树文件,所以已跟踪但已从工作树删除的路径可能让复制失败。check-dirty.sh 是单独的辅助门,并不在两个构建脚本内被调用。

    共享辅助各管一段:setup_lfs.sh 在 INCLUDE_BIG_MODEL 时重建匹配大模型的 .gitattributes、设置目标 Git 的 hooksPath 并运行 git lfs install --local --force,但它不负责下载模型实体;identity.sh 固定发布提交的作者与提交者,覆盖调用者身份环境变量。

    组件/函数 作用与边界
    release_files.py 按受跟踪路径清单过滤;实际复制工作树文件,未在脚本内强制检查 clean
    setup_lfs.sh 重建大模型 .gitattributes 与 hooks;不下载模型实体
    check_file_sizes.sh 拒绝当前 HEAD 中超过 95 MiB 的 blob;在提交之后运行
    check-submodules.sh 检查子模块固定提交可达;未设 set -e,且不是两个构建脚本的内联门
    check-dirty.sh 检查工作树 git status --porcelain;Jenkins 常规 onroad 阶段调用,发布脚本不调用
    pack.py 按扩展名白名单 zipapp 打包;不覆盖发布主链的测试与尺寸门

    门禁层次与元数据

    check_file_sizes.sh 的尺寸检查发生在提交之后,不能证明外部 LFS 对象已就绪。SConstruct 以 .gitmodules 是否存在推断 release,这与文件选择器主动排除 .gitmodules 相配,但它不是签名或测试证明。version.py 把 prebuilt 文件当作预构建标记,而构建元数据另按 channel 判断 tested 与 release,两者是不同维度,不能因为有 prebuilt 就认定来自受测分支。

    在流水线侧,Jenkins 的设备阶段调用 build_release.sh,目标包括 staging 设备;nightly 分别构建普通与 PANDA_DEBUG_BUILD=1 版本;Chestnut nightly 另设 INCLUDE_BIG_MODEL=1 并以对应车端测试作为参数。车端测试位于提交和尺寸检查之后、强推之前,而精简脚本本身不运行该测试。

    可见风险与插件化约束

    第一类是破坏性路径与引用:目标目录、固定设备路径、本地 staging 引用和目标远端分支都可能被覆盖,这些入口只应由受控 CI 身份调用,并对目标路径与分支做独立白名单校验。第二类是来源一致性:目前按 Git 跟踪名单拷贝工作树,而非直接从已审计提交导出,插件构建应绑定主仓与子模块提交、干净状态、依赖锁和产物哈希,避免「提交 ID 一样、拷贝内容不同」。

    第三类是 LFS 与大模型:.lfsconfig、.gitattributes 和模型实体是不同对象,路径存在、blob 不超限、LFS 已安装都不证明内容正确。第四类是安全固件模式:PANDA_DEBUG_BUILD 使 panda 构建走非 release 证书分支,nightly-dev 与普通 nightly 不能互作安全权限等价物,插件框架不能允许普通软件插件选择或绕过固件安全模式。

    验证范围

    上述结论来自固定归档的脚本与直接调用点;未运行 Git worktree、LFS、SCons、panda 构建、设备测试、强推或安装。README 里的发布步骤是人工清单,不能当作流水线已执行证据。后续还需逐项审查 Jenkins 设备锁与凭据,以及构建产物的实际内容,并在隔离 Git 仓库中验证工作树复制与 LFS 规则。强推前应保留独立审核与可回滚来源,pack.py 只是 Python 分发辅助,不覆盖 CAN 安全、模型运行或车端依赖。

    常见问题

    发布脚本复制的文件来自提交还是工作树?

    来自工作树。选择依据是 git ls-files 的受跟踪路径清单,实际复制的是工作树文件,所以本地未提交修改可能进入产物,而已跟踪但已从工作树删除的路径可能让复制失败;check-dirty.sh 并不在两个构建脚本内被调用。

    95 MiB 尺寸门能证明 LFS 模型已就绪吗?

    不能。check_file_sizes.sh 只拒绝当前 HEAD 中超过 95 MiB 的 blob,且检查发生在提交之后;它不能证明外部 LFS 对象已就绪,路径存在、blob 不超限、LFS 已安装都不证明模型内容与哈希正确。

    PANDA_DEBUG_BUILD 构建能当发布固件用吗?

    不能。该开关使 panda 构建走非 release 证书分支,nightly-dev 与普通 nightly 不能互作安全权限等价物。插件框架也不应允许普通软件插件选择或绕过固件安全模式。

    pack.py 打出的产物是整车可部署包吗?

    不是。pack.py 只按扩展名白名单用 zipapp 打包,没有拷贝独立子模块,也没有运行发布主链的测试与尺寸门,因此只是 Python 分发辅助,不能视作整车可部署产物。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/RELEASE_BUILD_FILE_SELECTION_LFS_PUSH_CONTRACT_DEEP_DIVE.md(取用了两条主链时序、文件选择器、共享辅助门、可见风险与验证范围)。
    • 项目 02 —— openpilot-research/BUILD_METADATA_VERSION_PROVENANCE_DEEP_DIVE.md(关联的版本与构建元数据专题,用于区分 prebuilt 标记与分支分类,本文未展开其探针细节)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Volkswagen MEB panda safety:Gen2 CRC 边界

    结论:Volkswagen MEB 的 panda safety 不是「按品牌名放行」,而是一组可逐帧核对的约束:MEB 选择 volkswagenMeb 安全模式与曲率转向,MEB_GEN2 额外设置 MEB_ALT_CRC;所有必需 RX 在 bus 0,使用 CRC-8H2F、四位计数器与存活检查;发送侧只放行 HCA_03、ACC_18、LDW_02、KLR_01、ACC_19、TA_01 六类地址。其中最容易被误读的一点是 Gen2 的替代 CRC:它不是「Gen2 一律不校验尾部」,标准整帧 CRC 仍可能被接受。

    一、车型资格与 RX 格式

    CarInterface._get_params() 为 MEB 选择 volkswagenMeb 安全模式和曲率控制;MEB_GEN2 设置 MEB_ALT_CRC。网关接线还由指纹判断,不能从品牌名推断物理总线。volkswagen_meb_init() 初始化 Gen1/Gen2 的 RX 长度与统一 TX 白名单,其中 Gen2 的 Motor_51 由 32 字节变为 48 字节,ESC_51 由 48 字节变为 64 字节。

    RX 报文 频率 Gen1 / Gen2 长度 作用
    LH_EPS_03 100 Hz — 驾驶员扭矩。
    Motor_14 10 Hz — 制动状态,再由通用 RX 规则参与退出。
    GRA_ACC_01 33 Hz — Set/Resume 释放沿且主开关开时使能;Cancel 位使其退出。
    QFK_01 50 Hz 32 字节 测量曲率;本模式 max_curvature_error=0,不运行相对误差限幅。
    ESP_21 50 Hz — 独立速度源。
    Motor_51 50 Hz 32 / 48 字节 ACC 状态 2 表示主开关可用,3/4/5 表示巡航接合;否则撤销 controls_allowed。
    ESC_51 50 Hz 48 / 64 字节 四轮原始速度判断移动并形成主速度。

    频率是检查配置,并非对每帧的实时 deadline 保证;计数器从字节 1 低四位、报文 CRC 从字节 0 读取。双车速这条链尤其要谨慎:ESC_51 形成一个速度源,ESP_21 提供独立速度源,曲率安全 helper 调用 speed_mismatch_check(),但不能用任一消息单独证明双源一致。

    二、Gen2 替代 CRC 的真实边界

    Gen2 替代 CRC 只影响 QFK_01、ESC_51 和 Motor_51。其逻辑是:先按整帧算标准 CRC;若开启替代标志,再按 28/60/44 字节前缀和另一组 salt 计算,只有前缀计算值等于字节 0 时才返回该值。因此标准整帧 CRC 仍可能被接受,前缀算法的接受范围则要结合车辆实报文确认。

    源文档给出的三项离线探针把这条边界说得很具体:对 QFK_01 的 32 字节帧,写入前缀 CRC 后修改索引 30 仍被接受,修改索引 20 则被拒;写入整帧 CRC 的帧修改索引 30 被拒。原因是 QFK_01 前缀长度 28 含校验字节,因此前缀算法不保护尾部索引 28..31。该结论只针对本机构造的 QFK_01 报文,不能外推为 Gen2 所有消息都忽略尾部或真实 ECU 会产生这种变异。

    三、TX 白名单与安全门

    地址 bus/长度 专属载荷检查
    0x303 HCA_03 0/24 曲率、请求状态为 4 时的激活位、转向功率;共享曲率 helper。
    0x14D ACC_18 0/32 加速度、HMS 停车状态、起步/保持位与 ACC 活动状态。
    0x397 LDW_02 0/8 无本模式专属载荷分支。
    0x25D KLR_01 0/8 与 2/8 无本模式专属载荷分支;仅 bus 2 项带 check_relay。
    0x300 ACC_19 0/48 无本模式专属载荷分支。
    0x26B TA_01 0/8 无本模式专属载荷分支。

    通用 safety_tx_hook() 先按地址、bus、长度白名单过滤,再调用车型 TX 钩子,最终还受继电器故障阻断;静态转发只在 bus 0↔2 间,阻断目的总线中带 check_relay 的地址。TX 准入不是 ECU 已接受、仪表内容正确或原车 AEB 不受影响的证明。

    ACC_18 的加速度量纲为 0.001 m/s²,活动范围 -3.5 至 2.0,未激活专用码为 3.01;停车请求 HMS=1(保持)或 4(起步)仅在允许控制时可发,0(无请求)与 5(坡道释放)可在退出后发。HCA_03 采用有符号曲率、请求位和功率三类输入,最大绝对曲率 29105、功率 125、名义频率 50 Hz;未请求转向时曲率必须为 0、功率必须为 0。一个关键细节:控制许可撤销后,带请求位的严格递减功率可作为退出时的降功率路径,不能把它误写成「失能后所有请求位帧必拒」。

    四、自制硬件与插件边界

    车型插件须提交 MEB 代际、DBC、接线位置和 CRC 变体的证据;未经确认时保持只读,不能仅按 ID.4/Born 名称自动授予 TX。感知/定位插件应分别报告 ESC_51 与 ESP_21 的来源、时间戳及健康;控制插件不能自行合并双车速再宣称冗余检查已通过。纵向控制插件应输出带语义的「加速度 / 保持 / 起步 / 坡道释放 / ACC 状态」,由可信任网关负责编码与状态序列,MCU 保留独立的字段检查和失联门。

    HUD、TA 与 KLR 白名单帧缺专属载荷检查,插件网关需给这些帧最小模板、速率和所有权,不能开放任意原始 CAN 写入。验证顺序应包含 Gen1/Gen2 CRC 尾部变异、错长度/计数、双车速失配、按钮沿、失联时保持/释放与功率降阶。现有 test_volkswagen_meb.py 覆盖两代配置、计数/校验、双车速、曲率功率、加速度与停车状态;本机运行该模块 90 项,7 项跳过,其余通过,但它不代替上述 Gen2 前缀/整帧 CRC 尾部变异探针,也未验证目标硬件。

    常见问题

    Gen2 是不是「不校验尾部 CRC」?

    不是。Gen2 替代 CRC 只影响 QFK_01、ESC_51 和 Motor_51,且先按整帧算标准 CRC;只有前缀计算值等于字节 0 时才返回该值,所以标准整帧 CRC 仍可能被接受。QFK_01 前缀长度 28 含校验字节,故前缀算法不保护尾部索引 28..31,这只是一个具体探针结论,不能外推为所有消息都忽略尾部。

    两条车速报文能互相证明冗余吗?

    不能。ESC_51 从四轮原始速度形成主速度,ESP_21 提供独立速度源,曲率 helper 调用 speed_mismatch_check(),但源文档明确不能用任一消息单独证明双源一致。控制插件也不能自行合并双车速再宣称冗余检查已通过。

    失能后所有带请求位的转向帧都会被拒吗?

    不是。源文档指出控制许可撤销后,带请求位的严格递减功率可作为退出时的降功率路径,相邻测试 test_power_without_control() 为此设了专门断言,不能把它误写成「失能后所有请求位帧必拒」。

    按车型名 ID.4/Born 就能自动授予发送权限吗?

    不能。车型插件须提交 MEB 代际、DBC、接线位置和 CRC 变体的证据,未经确认时保持只读;源文档明确不能仅按 ID.4/Born 名称自动授予 TX。网关接线位置还由指纹判断,不能从品牌名推断物理总线。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/VOLKSWAGEN_MEB_PANDA_SAFETY_DEEP_DIVE.md(取用了 MEB 车型资格、RX 格式与使能链、Gen2 替代 CRC 边界、TX 白名单与安全门、硬件插件边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11