标签: 安全边界

  • tinygrad AMD PSP:固件加载成功不等于 GPU 能跑模型

    结论:AMD GPU 的固件链里,「固件加载成功」「GPU 队列可工作」「模型输出有效」是三个不同状态:PSP 把经过选择的镜像交给 sOS 后,后续 SMU/GFX/SDMA 才依次使用。固定 SHA-256 只保证字节与清单一致,不等于签名、可信发布者或运行时兼容性证明;而 _ring_submit() 的写指针未见软件取模,属待验证项而非已定性缺陷。

    本文依据固定归档的 AMFirmware 与 AM_PSP 做静态源码与纯内存探针分析。这是 AMD GPU 计算设备的启动链,不是车辆控制或 panda 安全固件链;没有下载固件、访问 GPU/车辆、发送 CAN 或执行刷写。系统总图与 AM IP 启动顺序属于相邻专题。

    主调用与关键函数

    AMDev.init_sw() 先构造 AMFirmware,再创建七个 IP 对象并依次调用 init_sw();GPU 硬件启动期间,AMDev.init_hw() 按传入 IP 顺序调用 init_hw()。数据流依次是:IP 版本 → load_fw() → 按头部拆 SOS 与 IP 描述符 → PSP msg1 缓冲 → bootloader 装 sOS → 创建 ring → TOC 与 TMR → SMU 与其他 IP 固件命令 → 后续 GPU IP 初始化。

    入口 具体职责 需要验证的边界
    AMFirmware.__init__() 按 MP0/MP1/GC/SDMA IP 版本选文件;拆 SOS;形成 SMU、SDMA、PFP/ME/MEC、IMU、RLC 等描述符 固件集合必须与芯片/IP 版本、头部版本和硬件启动模式一致
    load_fw() / fetch_fw() 从本地 amdgpu 固件目录解压并比对固定 SHA-256;本地无匹配文件时取固定修订再校验哈希 哈希保证字节与清单一致,不等于签名或运行时兼容性证明
    desc() 以头部给出的 offset/size 切 memoryview,挂接 PSP 固件类型 普通切片会截短越界长度,未显式确认 len(slice)==size
    AM_PSP.init_sw() USB 路径用 SYS 映射 msg1;非 USB 路径用 VRAM;分配命令、fence、64 KiB ring,并设置 boot-time/autoload TMR 标志 物理/MC/虚拟地址单位、映射寿命和 DMA 可见性不能交给通用算法插件
    init_hw() 缺 sOS 时逐项经 bootloader 加载;重建 ring;可选 TOC/TMR;先 SMU,再设 TMR,后载各 IP 固件;最后处理 RLC 启动依赖顺序是状态机,不宜平铺为可任意重排的插件钩子
    _prep_msg1() / _bootloader_load_component() 复制固件到 msg1,补 4 字节并按 16 字节填充,flush HDP;bootloader 经消息寄存器读取地址及组件 ID 长度断言只检查原始数据,未覆盖追加和对齐后的长度
    _ring_create() / _ring_submit() 销毁旧 ring 后创建;写命令与 frame,推进写指针,等 fence,再查响应 status ring 写指针没有显式按 ring_size 取模,硬件语义尚未确定

    固件来源与信任边界

    load_fw() 用 ctypes.from_address() 读取版本头和具体头部,desc() 与 SOS 拆分依赖固件给出的数量、偏移、长度。固定哈希减小意外字节变更风险,但不足以替代解析边界检查。缓存文件、镜像哈希、芯片版本、驱动版本应一起进入部署清单与启动审计。

    信任环节 当前做法 它能证明什么 / 不能证明什么
    镜像完整性 解压后比对固定 SHA-256;必要时取固定修订再校验 证明字节与清单一致;不证明签名或可信发布者
    头部解析 按头部 offset/size 切 memoryview 依赖固件自报数量/偏移/长度;需补结构/长度校验
    离线部署 车端需预置并锁定镜像 无网络时无法即时取固定修订,必须提前锁定
    启动结果 命令返回 status==0 不能扩大解释成整个 GPU 或模型健康

    已确认边界与未验证问题

    纯内存探针直接调用 _prep_msg1(),提供 32 字节 memoryview 与假的 flush_hdp(),未触碰设备:输入 16、28 字节成功;29、32 字节通过现有断言,但因追加 4 字节后填充到 16 字节导致结构不匹配的 ValueError;33 字节由断言拒绝。因此检查对象应是填充后的长度。该结果只证明函数缓冲边界,不代表现实固件恰好接近 msg1 容量。

    问题 证据 当前定性
    msg1 长度检查对象 探针:28 字节通过、29 与 32 字节在填充后报结构不匹配、33 字节被拒 应检查填充后的长度,而非仅原始长度
    ring 写指针回卷 用 ring_paddr + prev_wptr * 4 定位 frame,未见软件取模;模拟实现也线性取 frame 待验证项,不将其定性为实机越界缺陷
    desc 切片越界 普通切片会截短越界长度,未显式确认长度相等 后续加载前需做结构/长度校验

    _ring_submit() 用 ring_paddr + prev_wptr * 4 定位 frame,未见软件取模;MockPSP._ring_submit() 也按旧写指针线性取 frame,不会证明真实硬件自动回卷。需要先获得 PSP 对写指针单位、回卷规则和 ring 最大积压的文档或设备跟踪,再用小 ring 故障注入验证。

    TMR 与命令编码各有边界:_tmr_init() 由 TOC 响应给出 TMR 大小,部分启动从 scratch5 恢复大小,再按平台模式申请并提交 TMR——scratch 恢复值及内存保留关系须经冷/暖启动台架确认。_load_ip_fw_cmd()、_load_toc_cmd() 与 _rlc_autoload_cmd() 把镜像、TOC 或自动装载请求编码为 PSP 命令,但不能把命令返回 status==0 扩大解释成整个 GPU 或模型健康。

    启动顺序本身也是契约的一部分:init_hw() 先经 bootloader 逐项加载 sOS,重建 ring,可选 TOC/TMR,然后先 SMU、再设置 TMR、后载入各 IP 固件,最后处理 RLC。把这条依赖顺序平铺成可任意重排的插件钩子,会丢掉「前一步未就绪则后一步无意义」的状态约束。

    对自制硬件与插件化的结论

    将 PSP/MMIO/DMA/固件装载封在受信任设备驱动,只向推理插件暴露带版本的「设备发现、固件校验、内存就绪、队列就绪、任务完成」能力及健康状态。

    1. 插件不得提供任意固件路径、物理地址、PSP 命令或重置入口。
    2. 新车载算力平台可替换计算驱动,但必须独立通过固件/ABI、内存一致性、冷暖启动、异常清理和模型数值/时限验证。
    3. 最终车辆输出仍受车型适配和 panda 板端 safety 约束,不能因「固件加载成功」就授权执行。
    4. 启动依赖顺序是状态机,不应把步骤平铺为可任意重排的插件钩子。

    边界说明:本专题没有 GPU、实车或 LFS 模型验证证据;探针只证明函数缓冲边界。

    常见问题

    固件加载成功就等于 GPU 能跑模型吗?

    不等于。PSP 把经过选择的镜像交给 sOS 后,后续 SMU/GFX/SDMA 才依次使用;「固件加载成功」「GPU 队列可工作」「模型输出有效」是三个不同状态,命令返回 status==0 不能扩大解释成整个 GPU 或模型健康。

    固定 SHA-256 能证明固件可信吗?

    不能。哈希只保证字节与清单一致,不等于签名、可信发布者或运行时兼容性证明。离线车端还需要预置并锁定镜像,并把缓存文件、镜像哈希、芯片版本、驱动版本一起纳入部署清单与启动审计。

    _ring_submit 的写指针没有取模一定是缺陷吗?

    不能下这个结论。源码用 ring_paddr + prev_wptr * 4 定位 frame,未见软件取模,模拟实现也按旧写指针线性取 frame,不会证明真实硬件自动回卷。这是待验证项,需要文档或设备跟踪,再用小 ring 故障注入验证。

    为什么消息缓冲的长度断言要按填充后的长度?

    因为探针显示:输入 29 或 32 字节虽通过现有断言,但因追加 4 字节后再填充到 16 字节而对齐结构不匹配并报错;28 字节可以成功,33 字节被断言拒绝。所以检查对象应是填充后的长度,而不是仅原始长度。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_AM_PSP_FIRMWARE_BOOT_RING_TRUST_CONTRACT_DEEP_DIVE.md(取用了 AMFirmware/AM_PSP 函数索引、固件信任边界、msg1 与 ring 的探针结论)。
    • 项目 02 —— openpilot-research/TINYGRAD_AM_DEVICE_IP_BOOT_PARTIAL_RESET_LIFECYCLE_DEEP_DIVE.md(仅用于确认 AM IP 启动顺序,未取用其未公开内容)。

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

    更新记录

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

  • Nissan panda safety:总线变体锁定、转角与取消载荷

    结论:Nissan safety 的核心是「变体先见即锁定」:get_addr_check_index() 在健康检查组内第一次看到某地址/总线/长度变体后就锁定其下标,之后只匹配该变体。对 Altima,如果错误接线或额外转发让 0x285 总线 0 先于所需的总线 1 到达,该组会锁到总线 0,而车型 RX 钩子只处理总线 1,后续车速不再被识别。另一条关键边界是:不在当前锁定变体内的帧,rx_msg_safety_check() 返回 true,但 safety_rx_hook() 的 whitelisted 为假,所以「返回有效不等于已消费」。TX 侧 0x169 受共享角度限制,而 0x280 Leaf 取消帧与 HUD 帧没有逐位载荷门。

    初始化与总线变体锁定

    nissan_init() 的 ALT_EPS_BUS 参数位只切换 RX 钩子预期的车速、踏板和巡航总线,构建同一 TX 白名单。get_addr_check_index() 在组内首次匹配时设置 msg_seen 与 status.index,之后只匹配该变体;safety_rx_hook() 要求变体有效且当前仍匹配才调用车型 RX 钩子。

    nissan_rx_hook() 采样 0x185 转角、0x285 后轮速度、0x15C/0x239 油门、0x454/0x239 制动和 0x30F 原车巡航。在 ALT_EPS_BUS=1 时它从总线 1 读取 0x285、0x15C、0x454、0x30F;常规模式从总线 0 读取前三类、总线 2 读取巡航;0x185 转角始终要求总线 0。

    RX:五组健康检查与「有效不等于被消费」

    nissan_rx_checks 有五个健康检查组,各项均显式忽略校验和、计数器、质量位,仅以地址、长度、总线与名义频率/存活约束健康。

    检查组 地址 允许总线
    转角 0x185 0
    后轮速度 0x285 0/1
    巡航 0x30F 2/1
    油门 0x15C 或 Leaf 0x239 0/1 或 0
    制动/状态 0x454 或 0x1CC 0/1 或 0

    源文档强调,这不是对每个 CAN 字段做完整性认证。Leaf 0x239 总线 0 的同一帧可更新油门与制动;它被放在油门健康检查组,制动组另有 0x1CC 变体,后者未被车型 RX 钩子直接解析为 brake_pressed。因此「健康表监视的消息」与「车型钩子消费的信号」不是完全一对一。

    转角以 -0.01 度/位及偏置 1310 度解码到每度 100 单位;后轮双速取平均更新速度,任一原始后轮速度非零便视为在移动。原车巡航接合由 0x30F 输入巡航检查,其上升沿可使能、断开时退出。源文档提醒,新硬件不能将 Altima 总线、Leaf 踏板和 X-Trail 踏板归一成没有来源的旧值。

    TX:转角、按钮与 Leaf 取消载荷

    nissan_tx_hook() 对 0x169 从 CAN 位域解码目标角与 LKA_ACTIVE,用共享的转角命令检查:600 度绝对界限、随车速变化的起/退转角速率、测量角度及未使能中性状态。源文档强调这里是每度 100 原始单位,不是通用扭矩接口。对 0x20B 的附加判据只有 (data[1] & 0x3D) == 0:它禁止掩码内非取消按钮位,但不会逐位检查整帧或要求取消位必须为 1。

    TX 帧 检查强度 备注
    0x169 解码目标角与 LKA_ACTIVE,运行共享角度限制 每度 100 原始单位,不是通用扭矩接口
    0x20B 仅检查 (data[1] & 0x3D) == 0 可在总线 2 或 Altima 总线 1 发送;check_relay=false
    0x2B1、0x4CC HUD 无逐字段检查 总线 0
    0x280 Leaf 取消 无逐字段检查 总线 2;探针中非零任意测试载荷仍通过 safety TX

    软件 create_acc_cancel_cmd() 会复制原车字段并覆盖按钮;Leaf create_cancel_msg() 会把 CANCEL_SEATBELT 置位,利用原车安全带告警退出巡航。源文档指出后者是驾驶员可感知的安全/HMI 行为,不能包装成普通、静默的取消插件。软件构帧约定也不能替代 MCU 逐位门:隔离探针中 0x280 的非零任意测试载荷仍通过 safety TX。

    验证证据与插件边界

    已有测试覆盖常规与 Altima 的转角/按钮基础行为,但源文档指出 Leaf 的按钮测试被明确跳过,不能证明 0x280 的 CANCEL_SEATBELT 载荷被硬件安全规则约束。本机新增探针以严格编译和 UBSan 通过按钮掩码、0x280 无载荷门及 Altima 错误总线先到锁定序列;未运行完整车型测试、目标编译器、实板或车辆 ECU 行为。

    插件化时必须把 Nissan 平台、ALT_EPS_BUS、DBC、总线拓扑与取消方式绑定;0x280 及 HUD 原始帧应列为高权限输出,不能由任意插件直接构造。Nissan 未定义专属转发钩子,共享转发按当前 TX 表中 check_relay=true 的地址/目标总线阻断主机自身控制帧;0x20B 设置 check_relay=false,不应把它的白名单资格混同于静态阻断或原车报文真实性。

    常见问题

    为什么 Altima 的车速有时候识别不到?

    因为健康检查组先见即锁定。源文档指出,若错误接线或额外转发让 0x285 总线 0 先于 Altima 所需的总线 1 到达,该组会锁到总线 0,而车型 RX 钩子仅处理总线 1,后续总线 1 车速不会被该组识别;本机隔离探针复现了这一序列。

    健康检查通过是否说明车型已经消费了该信号?

    不说明。源文档明确:不在当前锁定变体内的帧会让 rx_msg_safety_check() 返回 true,但 safety_rx_hook() 的 whitelisted 为假,车型状态不更新,因此「返回有效不等于已消费」。

    0x280 Leaf 取消帧有逐位载荷校验吗?

    没有。源码的 TX 白名单允许总线 2 的 0x280,但 nissan_tx_hook() 对它没有逐字段检查;源文档的隔离探针显示非零任意测试载荷仍通过 safety TX,因此它应列为高权限输出。

    0x20B 的 check_relay=false 是否等于原车报文可信?

    不等于。0x20B 设置 check_relay=false,源文档提示不应把它的白名单资格混同于静态转发阻断或原车报文真实性;共享转发只按 check_relay=true 的地址与目标总线阻断主机自身控制帧。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/NISSAN_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了 RX 五组健康检查表、变体锁定语义、TX 帧检查强度表、Leaf 取消载荷与探针结果)。

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

    更新记录

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

  • joystick 输入生产者:100 Hz 发布与 0.2 秒失联门的缺口

    结论:joystick_control 是一个输入生产者:它要么读终端键盘、要么读游戏手柄,然后由一个独立的守护线程以 100 Hz 目标节拍发布 testJoystick。三个事实决定它的边界:轴值是运行期估计(极值动态扩展,未标定);发布线程不检查输入年龄与 cancel,采集阻塞时旧轴值仍能被封装成新的 valid=True 消息;下游 joystickd 的约 0.2 秒帧差失联门只感知消息停发,识别不了「重复旧输入的新消息」。要注意这描述的是软件路径,不是实车会保持执行器动作的结论——selfdriveState、card 和 panda 安全策略仍独立约束输出。

    两个实现:终端键盘与游戏手柄

    入口带一个容易被误读的开关:直接执行脚本时会检查 IsOffroad,但设置 ZMQ 环境变量会跳过该入口检查,而 main() 本身直接构造游戏手柄。这是开发工具的启动约束,不是强制的进程级安全策略,ZMQ 的存在也不能视为网络身份认证。

    组件 / 函数 具体行为 边界与含义
    Keyboard.update() 阻塞读取一个字符;W/S 与 A/D 分别按 0.05 累积,R 归零,C 将本地 cancel 置真,下一次输入又先置假。 轴值不自动归中;输入停止不触发归零,长期保持的值由发布线程继续发送。
    KBHit.__init__()、set_kbhit_terminal()、set_normal_term() 从 sys.stdin 取 fd,保存 termios,清除 ICANON/ECHO 并以 TCSAFLUSH 应用;注册进程正常退出时的恢复回调。 非 TTY 的 tcgetattr() 会失败;异常/信号强杀不保证 atexit 执行,终端状态不是按对象生命周期恢复。
    KBHit.getch()、getarrow()、kbhit() 单字符阻塞读、三字符方向键读取、select(..., 0) 非阻塞就绪查询分别作为静态方法暴露。 Keyboard.update() 只调用 getch(),未先查询 kbhit();getarrow() 假设读满三字符,畸形序列可抛异常。
    Joystick.update() get_gamepad()[0] 处理首个事件,按 PC/车载不同映射转换纵横轴;采集到的极值动态扩展区间,再插值、死区与三次曲线。 极值是运行期估计,不是经标定的设备物理量;只按首个事件更新。已处理的拔出异常会归零,其他异常或采集线程停止不进入该分支。
    send_thread() 独立守护线程以 100 Hz 目标节拍发布 testJoystick,把外层 valid 置真,只复制 axes_order 对应的两轴值。 不检查采集线程是否阻塞、上次真实输入时间或 cancel。
    Joystick schema 结构有 axes 和 buttons;服务表频率为 0。 当前生产者只填 axes;cancel 不是该生产者消息的一部分,下游也只取轴值。

    发布线程不看输入:100 Hz 与约 0.2 秒失联门

    send_thread() 周期性复制 axes_values。采集线程没有为每次输入维护时戳,发布线程也不检查 joystick.cancel。因此在主采集循环阻塞、设备静默但不抛已处理异常时,生产端仍会产生新的 valid=True 两轴消息。joystick_control_thread() 先同步写入 JoystickDebugMode=True,启动发布线程,主线程无限调用 update(),没有显式 stop/join/模式撤销协议。

    下游 joystickd 的约 0.2 秒帧差失联门只感知消息停发,不会识别这类「重复旧输入的新消息」。探针也印证了这一点:send_thread() 在轴值不变且 cancel=True 时仍连续发送相同的两轴,每条 valid=True 且不填 buttons。这里要重复一次边界——这描述的是软件路径,selfdriveState、card 和 panda 安全策略仍独立约束输出,不能据此断言实车会保持执行器动作。

    生命周期缺口 源码事实 后果
    输入年龄门 采集线程无每次输入时戳,发布线程不查上次输入时间 采集阻塞仍发布旧轴为「新消息」
    取消传播 C 只改本地 cancel,且下次按键即清除;消息不含取消字段 取消不会作为消息传给下游
    发布时钟 发布按 100 Hz 目标节拍,与采样时钟无关 发布时钟不能代表采样新鲜度
    关闭握手 无显式 stop/join/模式撤销协议 进程退出才停;不能推断采集卡住时会停止发布

    模式参数与下游 joystickd:两种进程切换方式

    JoystickDebugMode 在 manager 启动/下线时切换清理:车载调试模式用它把控制权在 controlsd 与 joystickd 之间切换。这里要区分两件事——参数清理不等于 testJoystick 发布生命周期监测,模式选择与输入有效性需要分开验证。下游 joystickd 只取轴值并设置帧数超时门,而 card 与 panda 各自还有独立的安全门。

    游戏手柄一侧的映射也不是只有一套:Joystick.update() 按 PC 与车载两套映射转换纵横轴,采集到的极值动态扩展区间后,再经插值、3 百分比死区与三次曲线。探针确认 PC 扳机翻转后会再归一化,已处理的拔出异常会清零轴值。源文档还列出若干待验证项:不同游戏手柄 inputs 事件顺序与极值、真实拔出/静默行为、GIL 调度下两个线程的读写时序、消息停发后的实际时间,以及车辆安全钩子的响应;当前归档目录没有针对这些边界的专用原仓库单元测试,本篇的隔离探针不能替代真实设备验证。

    输入契约:采样时钟不能由发布时钟代替

    源文档对插件化的要求很明确:输入插件输出应包含设备身份、采样单调时戳、序号、校准版本、轴语义/单位以及明确的取消事件,发布时钟不能代替采样时钟;宿主按真实输入年龄与设备健康授予短期租约,过期、拔出、采集线程失活或异常时撤销租约,并且发布无效/零值不能伪装成新鲜的真实操纵;测试控制与生产控制分属隔离命名空间,即使测试输入有效,也只形成受监督的候选指令,必须继续通过唯一控制仲裁和独立硬件安全门。

    还要留意终端资源这块的实测边界:Keyboard() 构造时才创建 KBHit,但 joystick_control_thread() 没有 finally 或显式调用 set_normal_term();正常 Python 退出时 atexit 尝试恢复,被强制终止则不能保证。此外该类注释里「Windows no-op」的说法与模块顶层 import termios 不一致,不能把它当跨平台终端插件接口。

    常见问题

    为什么「消息还在发」不代表输入还新鲜?

    因为 send_thread() 按 100 Hz 目标节拍复制 axes_values,而采集线程没有为每次输入维护时戳,发布线程也不检查上次真实输入时间或 cancel。采集阻塞但进程仍在时,旧轴值仍会被封装成新的 valid=True 消息。

    终端里按 C「取消」会传给下游吗?

    不会。C 只是把本地 cancel 置真,且下一次输入又会先置假;cancel 并不是该生产者消息的一部分,下游 joystickd 也只取轴值。因此不能把终端说明中的取消键理解为已传播的取消命令。

    游戏手柄拔出后轴值会归零吗?

    对已处理的 OSError/UnpluggedError 会归零并返回假;但源文档指出其他异常或采集线程停止并不进入该归零分支,而且极值是运行期估计,不是经标定的设备物理量。

    设置 ZMQ 环境变量是否等于更安全?

    不是。设置 ZMQ 只是跳过 CLI 入口的 IsOffroad 检查,改变的是本地检查,不能被当作网络身份认证或进程级安全策略。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/JOYSTICK_INPUT_PRODUCER_DEEP_DIVE.md(取用了键盘与游戏手柄两种输入实现、100 Hz 发布与约 0.2 秒失联门缺口、六项隔离探针结果与插件化输入契约)。

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

    更新记录

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

  • VehicleModel 的 0.1 与 1e-6:低速回退与横滚补偿奇异点

    结论:VehicleModel 是主机上的纯计算模型,不是独立的安全许可,它的两个数字最容易被误用。第一是速度下限 0.1:steady_state_sol() 在 u > 0.1 时选动态解,否则回退到运动学解,而动力学矩阵 A 含 1/u,不能把零速送入动态分支。第二是 1e-6:roll_compensation() 在 abs(sf) < 1e-6 时直接给零,否则按 g*roll/(1/sf - u²) 计算。此外 panda 固件的曲率因子不包含 Python 侧的 chi 或横滚补偿,两端不能宣称逐点数学等价。以下基于固定快照 c8fb906 的静态源码分析,未做实车动力学辨识或 CAN 发送验证。

    输入、状态与公式

    VehicleModel.__init__() 从 CarParams 保存质量 m、转动惯量 j、轴距 l、质心到前后轴距离 aF/aR、后轮转向比 chi 以及前后原始侧偏刚度。update_params() 仅把原始刚度乘以在线 stiffness_factor,并替换方向盘转向比 sR,不会累乘上次结果。

    三条核心公式:calc_slip_factor() 算 sf = m(cF*aF − cR*aR) / (l²*cF*cR);curvature_factor() 给出 k = (1 − chi) / (l * (1 − sf*u²));calc_curvature() 返回 k*sa/sR + roll_compensation,其中 sa 为方向盘弧度、u 为 m/s,输出单位为 1/m。get_steer_from_curvature() 先扣除横滚补偿再除以 k,不是简单以轴距乘曲率。

    函数 作用 输入与边界
    steady_state_sol() 速度 u>0.1 时选动态解,否则选运动学解 返回 [侧向速度, 横摆角速度];低速分支不使用 roll。
    kin_ss_sol() 低速按轴距、后轴到质心距离、转向比得线性解 即使 u=0 也返回零;不显式使用 chi。
    create_dyn_state_matrices() 构造状态 [侧向速度, 横摆角速度]、输入 [方向盘角, 横滚角] 的 2×2 A/B A 含 1/u,不能把零速送入动态分支。
    dyn_ss_sol() 用 −solve(A, B) @ [sa, roll] 求稳态 solve 可能因参数退化或矩阵奇异失败,函数本身不做回退。
    get_steer_from_yaw_rate() 先以 yaw_rate/u 求目标曲率,再反算方向盘角 u=0 无保护;与低速运动学回退是两条不同 API。

    低速回退与奇异点约束

    模型里有两个需要调用方自己守住的数值边界。其一是低速回退:u=0 时动态分支不可用,必须走运动学近似,且该近似即使 u=0 也返回零。其二是横滚补偿的奇异点:roll_compensation() 在 abs(sf) < 1e-6 时直接给零,避开分母 1/sf − u² 的退化。

    边界量 取值 作用
    动态/运动学切换 u>0.1 决定用动态解还是低速运动学解。
    侧偏因子近零门 abs(sf)<1e-6 横滚补偿直接给零,避开奇异分母。
    在线更新下限 0.1 controlsd 更新模型时取的下限,仅防非正值。
    角度限幅最小速度 1 m/s apply_steer_angle_limits_vm() 里原始速度至少取 1 m/s。

    需要注意的是,controlsd 从 vehicleParameters 读取估计值并各自以 0.1 为下限更新模型,这个下限仅防止非正值,不能证明估计值物理可信。上游 paramsd 发布时另带有效性字段,但该调用点没有以这些字段阻止更新。

    运行调用链与安全边界

    paramsd 发布估计的转向比、刚度、横滚和转角偏移,controlsd 每周期更新模型,再用转角减偏移估算实际曲率(此处曲率加负号以匹配控制层符号约定)。LatControlCurvature.update() 用模型曲率构成跟踪误差;LatControlTorque.update() 将其乘 vEgo² 作为横向加速度测量,并用模型把方向盘死区换算为加速度死区。模型误差会传播到控制反馈,但模型本身不决定 latActive。

    apply_steer_angle_limits_vm() 用反算方向盘角把横向 jerk/加速度上限转换为主机角度限幅;Tesla 与 BYD 的控制器各用固定安全车型参数实例化独立模型,避免把 controlsd 在线估计参数直接当作固件许可条件。

    主机与固件的差异

    panda 的 steer_angle_cmd_checks_vm() 在固件内独立计算角度/加速度/jerk 门;其 get_curvature_factor() 与 Python 基式相近,但采用固件给定 slip_factor/轴距/转向比,不包含 Python 的 chi 或 roll_compensation,还使用固件车速与额外容差。因此不能声称两端逐点数学等价。

    项 Python 主机模型 panda 固件
    后轮转向比 chi 参与 curvature_factor() 不包含
    横滚补偿 roll_compensation() 参与 不包含
    侧偏因子/轴距/转向比 来自 CarParams 或在线估计 固件给定值
    车速来源与容差 主机估计 固件车速加额外容差

    joystickd_thread() 也会用模型估算测试转角幅度,这不代表绕过车端许可。车辆模型可作为纯计算插件提供曲率、横摆角速度及反算接口,但应在边界明确参数来源、单位、版本、速度有效域和非有限值处理;在线估计值应视为观测数据而非授权数据,主机控制器与 panda 固件必须绑定同一车辆身份和经校准的安全参数集,版本失配时禁用执行。

    现有验证与缺口

    现有 test_vehicle_model.py 以 Honda Civic 参数验证 1–30 m/s 下的横摆角速度往返、动态稳态解与直接公式一致,以及一秒离散仿真逼近稳态;它没有覆盖 u=0 反算、参数退化、在线更新突变、固件与 Python 跨实现一致性或实车传感器误差。本专题只做静态源码核查,未运行测试或实车验证。下一阶段宜先在隔离仿真中加入零速/奇异点、参数边界、符号一致性和主机-固件限幅差异矩阵,再讨论车端实验。

    常见问题

    为什么 0.1 这个速度下限会出现在模型里?

    因为动态状态矩阵 A 含 1/u,steady_state_sol() 在 u > 0.1 时才选动态解、否则回退运动学解;controlsd 更新模型时也取 0.1 为下限,但那只是防止非正值,不能证明估计值物理可信。

    roll_compensation() 在什么情况下直接返回零?

    当 abs(sf) < 1e-6 时直接给零,否则按 g*roll/(1/sf − u²) 计算。这个门槛是为了避开分母退化,属于奇异点约束。

    主机模型和 panda 固件算的曲率一样吗?

    不一样,也不能宣称逐点等价。固件的 get_curvature_factor() 与 Python 基式相近,但采用固件给定的 slip_factor/轴距/转向比,不包含 Python 的 chi 或 roll_compensation,还使用固件车速与额外容差。

    VehicleModel 是一个转向安全许可吗?

    不是。它是主机上的纯计算模型,不决定 latActive;候选转角经宿主限幅后仍须由不可被普通插件替换的独立固件安全门检查。在线估计值应视为观测数据而非授权数据。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/VEHICLE_MODEL_DYNAMICS_CONTROL_SAFETY_CONTRACT_DEEP_DIVE.md(取用了输入与公式、关键函数索引、低速回退与 1e-6 奇异点、运行调用链、主机与固件差异以及现有测试缺口)。

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

    更新记录

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

  • Chrysler panda safety:CUSW 双位按钮与两套模式矩阵

    结论:Chrysler panda safety 有两套互不通用的安全模式:常规模式按参数位选 Pacifica/Jeep、Ram DT 或调试构建中的 Ram HD,CUSW 是独立且仅调试注册的另一套钩子。最需要记住的边界是按钮载荷:常规模式以 data[0]==1 判取消、data[0]==0x10 判恢复,两个值不能同时匹配;而 CUSW 用两个独立位,表达式 is_cancel || (is_resume && controls_allowed) 使 0x11 同时置位在控制未许可时仍会通过。

    本文基于固定源码快照,分析常规 Chrysler 与独立 CUSW safety 模式,并用本机隔离探针核对若干分支。ALLOW_DEBUG 是编译条件,不等于目标 panda 固件已经启用;探针不代表刷写、台架或实车验证。

    模式入口与函数索引

    chrysler_init() 按参数位选择 RX/TX 表,Ram DT 分支优先于 Ram HD;非调试构建忽略 Ram HD 位,落入 Pacifica/Jeep 配置。CUSW 的安全模式注册受 ALLOW_DEBUG 限制,属于独立钩子。

    函数 作用与关键差异
    chrysler_init() 按参数位选 Pacifica/Jeep、Ram DT 或调试构建中的 Ram HD;Ram DT 优先于 Ram HD,非调试构建忽略 Ram HD 位
    chrysler_rx_hook() 按平台地址与总线更新 EPS 电机扭矩、ACC 接合、停车状态、油门及制动
    chrysler_tx_hook() 解码目标转向扭矩及请求位,调用共享限幅;按钮仅允许取消,或在 controls_allowed 时恢复
    chrysler_cusw_init() 建立独立的五组 RX 与三组 TX 白名单;安全模式注册受 ALLOW_DEBUG 限制
    chrysler_cusw_tx_hook() 扭矩限制为 250 原始单位;取消与恢复按钮分别取 bit 0、bit 4
    chrysler_compute_checksum() 对除末字节外的载荷按多项式 0x1D 做 CRC8,初始值 0xFF,末尾按位取反;末字节为接收校验值

    平台与信号矩阵

    四种模式的电机扭矩误差项均为 80。表内上限是 safety 内部原始单位,不是物理方向盘扭矩,也不是可直接移植的标定值。常规模式的 EPS 测量来自载荷字节 4/5,CUSW 来自字节 3/4;常规模式以 0x1F4/0x99 的 bit 21 判断 ACC,CUSW 以 0x2EC 的 bit 7。

    模式 关键 RX 来源 TX 地址、总线、长度 扭矩上限 / 每帧升降 / 实时变化
    Pacifica/Jeep EPS 0x220/bus 0;ACC 0x1F4/bus 0;车速 0x202/bus 0;油门 0x22F、制动 0x140/bus 0 按钮 0x23B/0/3;转向 0x292/0/6;HUD 0x2A6/0/8 261 / 3、3 / 112
    Ram DT EPS 0x31/bus 0;ACC 0x99/bus 2;车速 0x79/bus 0;油门 0x9D、制动 0x83/bus 0 按钮 0xB1/2/3;转向 0xA6/0/8;HUD 0xFA/0/8 350 / 6、6 / 112
    Ram HD,仅 ALLOW_DEBUG EPS 0x220/bus 0;ACC 0x1F4/bus 2;车速 0x11C/bus 0;油门 0x22F、制动 0x140/bus 0 按钮 0x23A/2/3;转向 0x276/0/8;HUD 0x275/0/8 361 / 14、14 / 182
    CUSW,仅 ALLOW_DEBUG 电机扭矩 0x1EC;ACC 0x2EC;车速 0x1E4;油门 0x1FE;制动 0x1E8,均 bus 0 按钮 0x2FA/0/3;转向 0x1F6/0/4;HUD 0x5DC/0/4 250 / 4、4 / 150

    移动判断也按平台分:Pacifica/Jeep 用左右轮速 0x202,Ram 用 ESP_8 双字节非零,CUSW 用 0x1E4 车速字段非零。健康检查方面,常规 RX 表监视五组;Pacifica/Jeep 的 0x202 特别忽略校验和与计数器,其余四组检查 CRC/计数器但忽略质量位;常规计数器为字节 6 高半字节,CUSW 计数器为倒数第二字节低半字节。CRC/计数健康不等于报文来源认证,总线身份、线束和原车 ECU 时序仍需单独验证。

    按钮载荷与转发边界

    常规按钮帧以 data[0] == 1 判断取消,以 data[0] == 0x10 判断恢复,因此两值不能同时匹配,但其它字节没有车型专属检查。CUSW 以两个独立位判断,表达式 is_cancel || (is_resume && controls_allowed) 使 0x11 同时置位在控制未许可时仍通过。

    项目 常规模式 CUSW
    取消判定 data[0] == 1 bit 0
    恢复判定 data[0] == 0x10 bit 4
    双位组合 两值互斥,组合无法同时匹配 0x11 在控制未许可时仍通过
    测试覆盖 明确测试组合被拒 测了中性、单独取消/恢复,未断言双位组合

    各模式的 HUD 地址在 TX 白名单,但两套车型 TX 钩子都没有 HUD 载荷检查。转向与 HUD 的 check_relay=true 会参与共享继电器故障检查和来自 bus 2 的静态转发阻断;按钮的 check_relay=false 不具备这两项效果。

    转发边界的判定要分三步:safety_tx_hook() 先做通用地址/总线/长度白名单与继电器故障门,再由车型钩子做载荷判定,而来自另一总线的静态转发阻断由 safety_fwd_hook() 处理。因此「能不能发」与「会不会阻断原车转发」是两个独立问题,不能只看白名单。

    两套测试的口径也不同:现有 test_chrysler_cusw.py 测了中性、单独取消或恢复,但没有断言双位组合;常规 test_chrysler.py 则明确测试组合被拒。这一差异是现有钩子行为,不应当视作车辆接受或设计允许组合键。

    软件层与固件层不是同一件事

    软件 CarInterface._get_params() 把 Ram HD 与 CUSW 标为 dashcamOnly,CUSW 另选 chryslerCusw 模式;其 CarControllerParams 还设置软件侧限幅与周期。尤其 Ram DT 软件上限为 261,而板端限值为 350,说明软件策略和固件许可不是同一层。

    自制硬件插件不能把 dashcamOnly、软件限幅或模式参数当作板端已经正确配置的回执。插件化时应把平台指纹、DBC、总线映射、safety 模式/参数、编译能力与线束版本作为不可分割的准入清单。HUD 与按钮原始帧均须列为受控输出,普通 UI/诊断插件不能绕过 card 和板端安全层直接发送。

    本机隔离探针在 release/debug 两种 ALLOW_DEBUG 编译条件下均以 -Wall -Wextra -Werror 与 UBSan 通过,覆盖平台按钮总线、Ram HD 非调试回退、CUSW 非调试不可注册、CUSW 双位组合、HUD 无逐字段门及静态转发阻断。它没有输入有效 RX 序列、验证 CRC 错误/计数回绕或执行完整车型测试;更不代表目标固件、台架或实车。

    常见问题

    CUSW 模式和常规 Chrysler 模式能混用吗?

    不能。CUSW 是独立钩子,拥有自己的五组 RX 与三组 TX 白名单、独立的扭矩编码与限值,且安全模式注册受 ALLOW_DEBUG 限制;常规模式的地址、总线、长度与按钮位都不能按品牌名复用。

    Ram HD 在普通构建里会怎样?

    非调试构建会忽略 Ram HD 位,落入 Pacifica/Jeep 配置;而且 Ram DT 分支优先于 Ram HD。所以不是「选了 Ram HD 位就一定按 Ram HD 配置」,还要看构建条件与优先级。

    为什么 CUSW 的 0x11 组合键仍能通过?

    因为 CUSW 用两个独立位判断,表达式为 is_cancel || (is_resume && controls_allowed),只要有取消位就通过,所以 0x11 在控制未许可时仍会通过。常规模式则因两值互斥无法同时匹配,且测试明确覆盖组合被拒。这是现有钩子行为,不应视作车辆接受或设计允许组合键。

    HUD 地址在白名单里,是不是就有逐字段校验?

    不是。各模式的 HUD 地址在 TX 白名单,但两套车型 TX 钩子都没有 HUD 载荷检查。白名单控制的是地址、总线、长度,并受继电器故障门约束,不等于载荷逐字段校验。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CHRYSLER_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了常规与 CUSW 两套模式矩阵、按钮载荷差异、平台信号表与隔离探针结论)。
    • 项目 02 —— openpilot-research/ACTUATION_GATE_CHAIN_DEEP_DIVE.md(仅用于确认执行链分层语义,未取用其未公开内容)。

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

    更新记录

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

  • 车型协议接入实践:从指纹识别到安全固件的最低契约

    结论:接入一个新车型,最低工作集是走完一条固定骨架:安全板接收 CAN → 主机车况网关 → 指纹与 ECU 固件匹配 → 生成车型参数与安全模式 → DBC 解析出车况 → 控制器生成候选发送帧 → 回包编码 → 主机到板传输 → 板端安全钩子放行。公开资料在固定快照中核对了 17 个车型实现目录,但「实现入口存在」并不等于已完成某品牌全部平台与年款的报文与安全审计。接入的关键判断有三条:未识别 / 模糊匹配默认被动;品牌目录不能代替平台差异验证;含 ALLOW_DEBUG 条件的模式不等于发布固件可启用执行。

    车型实现的共同骨架

    公开资料显示,车型实现的加载与运行是一条固定链路,可按下面顺序理解:

    1. 安全板接收 CAN 帧,交给主机车况网关;
    2. 网关做指纹识别与 ECU 固件匹配,形成车型参数与安全配置;
    3. DBC 解析器把原始帧解析为车型车况;
    4. 控制器消费车况,生成控制意图;
    5. 车型控制器把控制意图编码为候选发送帧;
    6. 候选帧经主机发送线程下发,最终由安全固件的发送钩子决定是否放行。

    两个结构性事实需要注意:其一,车型接口由一份品牌清单静态加载,不是运行时扫描任意插件目录;其二,每个品牌常规包含车型枚举与物理配置、车型参数与安全模式、接收帧到车况、控制意图到候选帧这几组模块,部分品牌另有雷达接口。这些模块的职责边界,就是后续插件化时「能力契约」的划分依据。

    十七个品牌目录与平台差异

    固定快照中核对到的车型实现入口如下。每类的接口、状态、控制器与配置模块均已在目录中确认存在,「雷达接口」表示另有雷达相关模块。此表是开发入口索引,不能替代平台差异验证:

    品牌目录 额外雷达接口 平台/安全差异 当前深度
    body 无 非汽车双轮平台 链路已追踪
    byd 无 仅调试安全模式 单车型转角链路已追踪
    chrysler 有 多车型链 主要链路已追踪
    ford 有 经典 CAN 与 CAN FD 主要链路已追踪
    gm 有 ASC M / 相机若干链路 主要链路已追踪
    honda 有 两套转向实现 主要链路已追踪
    hyundai 有 经典 CAN 与 CAN FD 主要链路已追踪
    mazda 无 扭矩/速度迟滞 主要链路已追踪
    mg 无 仅调试安全模式 扭矩链路已追踪
    mock 无 空发送回退 非实车回退已追踪
    nissan 无 转角 / 车型取消 主要链路已追踪
    psa 无 三总线,仅调试安全模式 转角链路已追踪
    rivian 有 两代硬件 主要链路已追踪
    subaru 无 含 preglobal 分支 主要链路已追踪
    tesla 有 转角 / 原车转发 主要链路已追踪
    toyota 有 逐层样本 已逐层追踪
    volkswagen 有 多平台安全模式 主要链路已追踪

    特别要注意:安全钩子注册表把若干模式(第三方品牌、部分平台)置于 ALLOW_DEBUG 条件下;实现目录与测试存在,并不表示发布固件可启用执行。目标车型还须以具体平台与固件版本逐一核对。

    启动阶段的五道控制门

    车型识别与启动阶段依次经过多道门,每道门都可能让系统停留在「被动」状态:

    入口 行为
    CAN 指纹 在总线上按 CAN 地址与数据长度逐步排除候选;超过限定帧数后停止,不保证未知车型能识别
    指纹确认 优先查询 VIN、ECU 存在性及固件版本,再收集 CAN 指纹;单一固件候选可覆盖结果,环境变量可强制指纹(仅用于受控开发)
    固件匹配 先精确固件匹配,失败再尝试模糊 / 品牌自定义匹配,返回是否精确及候选集
    车型获取 无候选时回退空模型;用候选接口生成车型参数并保留 VIN、固件、指纹来源及模糊匹配标记
    控制就绪 仅在进程初始化、非被动、控制请求存活的路径调用车型编码形成候选发送帧

    这里要分清几种状态:空模型、被动、仅仪表记录互不相等,但都不能作为未知车型开始执行的理由;用户配置或环境变量强制指纹也不构成车辆协议兼容性验证。

    DBC 编解码的函数级语义

    解析与编码是两个方向,公开资料给出的关键语义如下:

    入口 关键含义
    DBC 加载 加载消息、信号、枚举;按 DBC 系列选择校验算法
    取值 按大小端与位提取原始整数;符号、倍率和偏移另行处理
    解析 解码信号、计算校验和与计数器;失败返回失败,不把这一帧的部分值提交
    计数器 错误计数达阈值则拒绝,有效后逐步恢复
    整帧解析 仅接收指定总线与已注册地址,更新值与时间戳,返回成功解析的地址集
    健康位 检查最近成功时间、计数器与缺失次数;总线空窗单独观察
    编码 反向应用倍率/偏移,自动补计数器与校验和;未知信号仅记录错误并跳过
    组帧 返回地址、编码后数据与总线号;未命中消息名时返回空数据

    最常见误用是只看数值而忽略健康位:解析器保存的是最近一次成功值,若忽略有效位与总线健康,就可能把旧值当成当前观测;编码器只是协议编码,不是转向扭矩或加速度安全门。

    样本三层与插件化最低契约

    以样本车型看,三层结构最清楚:

    层 已核对决策
    车型能力 设定安全模式与助力比例;按平台/固件选择扭矩或角度控制、制动地址、加密、原车/自控纵向能力
    ECU 变更 特定实验纵向模式会执行 ECU 通信控制,退出时尝试恢复;属车型专属且需单独台架验证
    输入状态 区分动力、相机总线与平台标志,解析车门、安全带、制动/油门、轮速故障、转角与驾驶员扭矩
    控制器 按驾驶员扭矩与助力限制转向请求;按提醒、纵向能力、车速与加密分支生成候选帧
    固件接收 从指定报文维护扭矩、角度、巡航、油门、刹车及车速样本
    固件发送 检查转向扭矩/角度速率、纵向加速度、AEB 与诊断报文

    从这条样本链可以抽出面向插件化的五条最低契约:

    1. 车型插件声明平台、ECU 固件范围、DBC 版本、总线拓扑、能力位、车型参数与安全模式;未识别 / 模糊匹配默认被动;
    2. 解码插件输出值、时间戳、来源、校验 / 计数器 / 超时状态,不能把上次成功值伪装成当前有效观测;
    3. 控制插件只产生结构化控制请求或候选帧,不能自行改固件白名单、安全模式或绕过车况网关;
    4. 硬件安全控制器独立于应用 CPU 实现收发与转发限制、心跳失效回退与板级故障处理;
    5. 每个目标车型逐一建立解析、编码、固件安全和回放基线;品牌目录索引不是一套通用线束或固件配置规格。

    常见问题

    有了某个品牌的车型目录,就能接这个品牌的全部车型吗?

    不能。品牌目录只证明实现入口存在,不能代替平台差异验证。目标车型须以具体平台与固件版本逐一核对;同一品牌可能同时存在经典 CAN 与 CAN FD 等不同分支。

    未知车型会怎样处理?

    无候选时会回退到空模型;未识别或模糊匹配默认被动。被动、仅仪表记录与空模型互不相等,但都不能作为未知车型开始执行的理由。

    CAN 解析出的值可以直接用吗?

    不能只看数值。解析器保存的是最近一次成功值,必须同时检查有效位、时间戳与总线健康,否则会把旧值误当成当前观测。

    安全模式在调试条件下可用,是否代表能上车执行?

    不代表。若干模式被置于 ALLOW_DEBUG 条件下,实现目录与测试存在并不表示发布固件可启用执行;实际执行还需台架与受控场地验证。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(取用车型实现骨架、17 个品牌目录索引、启动控制门、DBC 编解码语义、样本三层结构与插件化最低契约)。

    说明:本文为对固定源码快照(主仓库提交 c8fb906 及固定的车型协议与安全固件归档)的静态阅读整理,未接入目标车型、线束或安全板,未运行完整测试套件。文中所有入口、状态与目录均出自源文档,样本车型仅为实现链路示例,不是对任何年款或配置的实车适用性声明。

    更新记录

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

  • 大型 ADAS 代码库怎么读:四步阅读顺序与三类边界

    结论:读一个大型 ADAS 代码库,正确顺序不是从目录名或进程启动顺序开始,而是按职责与信任边界组织:先分清运行底座、输入感知、状态估计、车辆接入、决策执行、旁路验证六大块,再沿「车况从 CAN 进来到控制从 CAN 出去」这条链逐层追。本文给出一条可复用的四步阅读顺序——从进程与消息契约出发,追车况解析、追使能状态机、追候选发送与板端安全钩子——并强调三类不能混同的边界。源代码根提交为 c8fb906,归档缺少模型权重实体,以下为静态分析,不代表构建通过或实车验证。

    按职责与信任边界看,而不是按目录名

    这个代码库的关键结构不是「哪个目录属于哪个功能」,而是各部分所处的信任层级。可以用一张源码目录地图定位:

    目录 在系统中的位置 主要产物或边界
    系统与平台目录 设备与运行平台 进程管理、相机与传感器采集、记录、设备状态与远程服务
    消息契约与传输 数据契约与传输 日志结构定义、服务频率定义、跨进程消息队列与图像缓冲
    共享运行时 共享基础 参数持久化、时间与调度、日志、设备抽象与版本信息
    视觉推理目录 视觉推理 道路与驾驶员模型输入处理与推理输出;模型权重属于缺失的实体
    定位与监控目录 状态估计 定位、标定、车辆参数学习与驾驶员注意力策略
    车辆 I/O 目录 主机侧车辆 I/O 车况标准化、候选发送帧生成、与安全板通信
    决策与控制目录 决策与执行请求 雷达融合、纵向规划、控制器与使能告警状态机
    车型协议与安全目录 车型协议与独立安全边界 DBC、车型状态与控制、safety 规则;安全 MCU 执行收发与发送限制
    开发验证目录 开发验证 回放、仿真、CAN 分析与诊断,不属于车上实时控制路径

    要特别记住一点:安全板固件、车型协议、跨进程通信与推理运行时分别位于不同的源码归档,不要把它们误认为同一个可热插拔模块。这条边界决定了后面所有替换与移植工作的可行性。

    对照这张地图时,一个实用判断是看某模块是否参与了实时控制路径:属于开发验证的部分不应成为车上的必要依赖,而安全相关部分则必须与车型、总线与固件联合版本化。

    四步阅读顺序

    公开资料给出了一条从函数追到消息的阅读顺序,可以照此执行:

    1. 从进程配置与进程管理函数找到运行进程,再对照服务频率定义与日志结构定义的主题字段;
    2. 从车况更新函数追车型接口更新、车型状态与雷达轨迹,再追模型输出、雷达状态、纵向计划的生产者;
    3. 从事件汇总与状态机更新的函数追使能 / 退出条件,再看控制器如何生成执行请求;
    4. 从车型编码函数追车型控制器的更新、候选发送帧、主机到板传输与板端安全钩子;任何可替换模块都不能跨越最后一层安全门。

    顺序的关键在最后一步:候选发送帧在到达总线之前,必须经过安全 MCU 的独立检查。也就是说,主机侧插件、车型控制器和配置项都不能替代板端规则。

    这四步的意义在于把「消息契约」放在第一位:先知道有哪些主题、频率与字段,再去看谁生产它们。否则很容易在某个子目录里困住,却不知道它在闭环中的位置。追控制链时,建议始终回到「车况进来、控制出去」这两条主线上做交叉验证。

    运行阶段与消息速查

    下表按理解闭环的顺序排列,不是全局同步调用栈。进程各自运行,消息的发布、接收与板端执行发生在不同时间,表内的「输出」也不等于车辆已经执行:

    阶段 主要输入与产出 硬件或插件边界
    设备与进程 板 / 电源 / 温度等状态形成设备启动状态,进程管理据此增减受管进程 新平台必须先可靠报告点火、热状态和电源;进程启动不代表传感器健康
    物理采集 相机图像进入模型;CAN 经车型解析形成车况与雷达轨迹 帧时间、坐标系、CAN 来源总线及采样年龄必须随适配器一起验收
    观测发布 分别发布模型输出、相机里程计、车况与雷达轨迹 同一发布周期内的多个主题也可能有不同有效条件
    融合与规划 雷达轨迹与模型输出形成雷达状态;模型更新触发纵向计划 规划插件须声明输入时间轴、有效性和输出期限;计划存在不等于控制获准
    使能与控制 事件产生使能状态;控制器综合车况、模型和计划发布控制请求 使能、横向、纵向是不同权限;控制请求的 valid 不是最终硬阻断
    车型编码 控制请求经车型转换成为候选发送帧 主分支检查控制请求存活,不统一检查其有效位
    主机到 MCU 主机按消息年龄发送候选帧,另维护板状态与心跳 消息年龄门和心跳不是逐帧安全授权
    板端安全 按安全模式、发送白名单、车型钩子及故障决定是否接受候选帧 车型 / DBC / 安全参数 / 固件必须联合版本化

    建议先用只读 CAN 与图像采集验证前四阶段,再以离线回放验证控制请求,最后独立验证网关与 MCU 的拒帧、超时及故障退化。

    三类边界:输入、能力与验证

    从源码职责推导,面向自制硬件与插件重构有三条边界,对应四类插件:

    插件类型 可定义的契约 不可交给插件自我授权的部分
    传感器 / 感知 时间戳、坐标系、健康状态和标准消息输出 无效或过期输入继续参与控制的许可
    车型适配 指纹、DBC、车况与控制帧、受控总线清单 安全模式、发送白名单、驾驶员覆盖规则
    规划 / 控制 标准模型 / 雷达 / 控制契约及回放等价性 无条件接管发送或绕过使能状态机
    记录 / UI / 远程 只读订阅、存储和远程诊断能力 将远程命令直接转为车辆控制权限

    第一条边界是物理输入与安全发送:车型、总线、线束、摄像头时间戳以及安全板配置必须版本绑定。第二条是算法与辅助能力:感知、定位、日志和 UI 可以探索可替换接口,但控制相关组件不能仅凭「插件已安装」取得发送权。第三条是验证:先只读采集、进程回放和故障注入,再做台架与受控场地测试;这类文档不是接线图或上车许可。

    常见问题

    能不能只看目录名理解这套代码?

    不建议。应优先按职责与信任边界组织:安全板固件、车型协议、跨进程通信和推理运行时分别位于不同源码归档,按目录名会被误导。可用文中的目录地图定位各部分所处层级。

    控制请求发出去了就等于车辆执行了吗?

    不等于。候选发送帧在到达总线前还要经过安全 MCU 的独立检查;主机侧的消息年龄门和心跳也不是逐帧安全授权。文中所有表中的「输出」都不等于车辆已经执行。

    使能、横向、纵向是同一个开关吗?

    不是。公开资料特别指出它们是不同权限,且控制请求的 valid 不是最终硬阻断。判断能否执行应回到使能状态机与板端安全规则,而不是单一布尔值。

    这份分析能作为上车依据吗?

    不能。归档缺少模型权重实体,本文为静态源码分析,不代表构建通过或实车验证;它不是接线图或上车许可,任何执行能力都需要台架与受控场地另行验证。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/ARCHITECTURE_GUIDE_CN.md(取用源码目录地图、四步阅读顺序、运行阶段与消息速查、三类边界与插件类型表)。

    说明:本文为对固定源码快照(根提交 c8fb906)的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有入口、阶段与边界均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

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

  • 设备 CI 的四道门:锁、凭据、检出与测试跳过判定

    结论:设备 CI 的保护不是一道门,而是四道:设备锁、凭据注入、检出隔离与测试跳过判定。真正的风险不在「CI 通过」,而在两处静态缺口——差异列表用子串匹配判断是否跳过步骤,且检出快路径会保留被忽略文件与旧构建产物。UNSAFE=1 只是检出快路径的环境变量,不是车辆 safety 许可。

    阶段骨架:node 到 deviceStage

    流水线从 node 开始:setupCredentials() 注入三类 token,checkout(scm) 取分支/提交,再按分支选择 staging/nightly 或常规矩阵。特殊 chestnut-stresstest 最多循环 100 次并归档报告;其余按分支触发 staging/nightly,未排除分支运行常规并行矩阵。整个流水线的控制权都集中在 Jenkinsfile 的 node 段,因此插件化改造的关键不是换脚本,而是把设备锁、凭据与步骤过滤各自契约化。

    函数/阶段 主要实现契约
    setupCredentials() 从 Jenkins 凭据读三类 token 并放入 env;后续 device() 把它们扩展到远端 shell 环境。SSH 私钥另在 withCredentials 中取得。
    device() 用 alpine-ssh 包装的远端 SSH;连接禁用严格主机密钥检查。AGNOS 上清临时目录/共享内存、必要时启动 DNS 与重绑 USB hub;链接 Python 路径,执行步骤并等待子进程退出。
    deviceStage() 禁止 Jenkins Replay;按设备标签 lock(quantity: 1) 独占,在 Docker 中给整个阶段 35 分钟;设备时间同步与检出一起最多重试三次,测试步骤本身不自动重试。
    hasPathChanged() 对 GitHub compare 的返回字符串做子串匹配,不是解析已验证的文件列表;非 master/非 Jenkins loop 分支可据 diffPaths 跳过部分 build/test。
    chestnutStage() build、Chestnut 模型回放和指定 TestChestnutOnroad;设备标签为 mici-chestnut-ci。

    devel-staging 调用设备上的 build_release.sh 推 tizi/mici staging;__nightly 同时提交普通与 PANDA_DEBUG_BUILD=1 变体;__nightly-chestnut 加入 INCLUDE_BIG_MODEL=1。普通矩阵并行覆盖 onroad、功耗/编码器/manager、两种 camerad、sensord、模型回放、panda loopback/SPI/功放及 Chestnut。

    凭据与 SSH 信任边界

    setupCredentials() 从 Jenkins 凭据读 Azure、CI 工件、GitHub 评论 token 并放入 env,随后 device() 把它们扩展到远端 shell 环境;SSH 私钥另在 withCredentials 中取得。远端连接禁用严格主机密钥检查,因此 CI 网络、节点与凭据范围应被视为独立信任边界。

    需要分清的是:lock(label=...) 是 Jenkins 设备池的并发协调,不是车端进程互斥。在自建硬件平台上应收紧主机验证并落实凭据最小权限,而不是把 CI 的协调机制误当作运行时的排他保证。

    从信任边界看,凭据实际有三层:Jenkins 环境中的凭据存储、经 device() 扩展到远端 shell 的环境变量,以及独立取得的 SSH 私钥。三层的作用域应各自最小化,远端主机验证应收紧;本地 ZIP 归档无法代表真实 Jenkins 插件及凭据配置,因此本文的结论只到脚本与流水线文本层。

    检出两条路径:unsafe 与 safe

    setup_device_ci.sh 先要求 SOURCE_DIR、GIT_COMMIT、TEST_DIR,清数据与 staging 目录,再写 /data/continue.sh。该脚本中的 continue.sh 内容含 abctl --set_success、sshd/NetworkManager 修改与长循环;此处只写文件并设可执行位,后续由什么启动机制执行应另行验证,不能把写入等同于本轮已执行。

    路径 实现 结果/代价
    快路径 unsafe_checkout() 直接在 TEST_DIR fetch/强制 checkout/reset,清理,对子模块做 reset/clean/update。 不使用忽略文件清理标志,忽略文件和旧构建产物可留存;失败后回退 safe。
    全清路径 safe_checkout() 在 SOURCE_DIR fetch 固定提交,清顶层内容、全量清理与子模块,拉 LFS,再 rsync -a --delete 到 TEST_DIR。 删未跟踪/忽略文件和目标旧内容,隔离更强但仍依赖 Git 远端、LFS 与 rsync 成功。
    LFS pull_lfs() CHESTNUT 时拉全部;普通设备排除 big driving 模型,必要时恢复指针并删对应本地 LFS 对象。 防止普通设备携带大模型实体;只代表该脚本处理的路径,不证明所有模型/构建输入完整。
    包导入路径 在 TEST_DIR 创建 msgq/opendbc/rednose/teleoprtc/tinygrad 包链接,与运行时 Python 导入结构对齐。 链接结构需与运行时一致,否则导入行为分叉。

    safe_checkout() 在脚本中会删除工作目录内容、源仓锁文件与设备真实路段缓存;这只能在专用 CI 设备上使用,不能作为用户车辆的插件更新机制。

    跳过与漏测风险:hasPathChanged 的子串匹配

    deviceStage() 用 curl -s ... | jq ... || echo "/" 取得差异;未使用失败即错选项、管道错误传播或结构化校验。API/解析失败可能返回仅 /,后续 hasPathChanged() 找不到目标路径,导致带 diffPaths 的步骤被跳过。这是代码路径风险,不表示当前 CI 已发生漏测。

    跳过的范围也需准确理解:diff 是与 master 比较后的文本,跳过仅应用于带 diffPaths 的步骤且非 master/非 Jenkins loop,不是整个矩阵的开关。模型回放路径的 build 和 replay 都带同一过滤条件,其他硬件测试大多总跑。

    CI 绿灯不等于实车授权

    Chestnut 压测仅对特定中断异常记 FAIL 并继续,其它异常会使循环中断,但 finally 仍归档包含 INCOMPLETE 的报告;常规分支的测试集合还受设备资源、差异跳过和环境变量影响。因此 CI 绿灯不能替代完整覆盖,也不能作为实车安全性的证明。本专题未运行 Jenkins Groovy、compare API、SSH、设备锁、模型回放或硬件测试。

    换句话说,四道门保护的是「谁来跑、在哪跑、跑什么、跑哪些」这四件事,它们各自都有可被绕过的边界;把它们理解为一条不可绕过的单一闸门,正是设备 CI 最常见的误读。

    常见问题

    为什么差异接口失败可能导致步骤被跳过?

    因为 deviceStage() 用 curl -s ... | jq ... || echo "/" 取得差异,未使用失败即错、管道错误传播或结构化校验。API/解析失败可能返回仅 /,hasPathChanged() 找不到目标路径,带 diffPaths 的步骤就会被跳过。

    省略的检出快路径有什么代价?

    unsafe_checkout() 不使用忽略文件清理标志,忽略文件和旧构建产物可留存,因此隔离更弱;失败后才回退到 safe_checkout()。这条快路径仅适用于专用 CI 设备。

    lock 是否等同于车端进程互斥?

    不等同。lock(label=...) 是 Jenkins 设备池的并发协调,不是车端进程互斥。CI 网络、节点与凭据范围应被视为独立信任边界,并在自建硬件平台收紧主机验证与凭据最小权限。

    CI 全绿是否代表实车安全可用?

    不代表。Chestnut 压测仅对特定中断异常记 FAIL 并继续,其它异常会中断循环但仍归档 INCOMPLETE 报告;常规测试集合还受设备资源、差异跳过和环境变量影响,CI 绿灯不能替代完整覆盖。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/JENKINS_DEVICE_CI_CHECKOUT_LOCK_CREDENTIAL_TEST_GATE_DEEP_DIVE.md(取用了阶段骨架、凭据与 SSH 边界、检出两路径及跳过与信任边界)。
    • 项目 02 —— openpilot-research/RELEASE_BUILD_FILE_SELECTION_LFS_PUSH_CONTRACT_DEEP_DIVE.md(仅用于确认发布构建与强推语义的边界)。

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

    更新记录

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

  • Subaru Global safety:0x221 只允许取消,纵向不放行

    结论:Subaru Global safety 的 TX 权限极窄:转向帧 0x122 受扭矩与请求位门约束,而 0x221 ES_Distance 只允许取消——任何非 inactive_gas=1818 的 Cruise_Throttle 或未设置 Cruise_Cancel 都被拒,主动油门、制动或巡航 RPM 命令一律不放行。该快照下 alphaLongitudinalAvailable=False,不能因限值结构存在就向插件授予纵向执行权限。

    本文依据固定源码快照的 subaru.h、车型接口和相邻安全测试做静态分析,不证明目标固件、线束、ECU 或实车安全。本文覆盖 Global 模式;它与 Preglobal 是不同安全钩子,能力不能互相继承。

    模式与总线矩阵:Gen2 只搬了三组 RX

    Global 选择 subaru 安全模式,Gen2 额外设置 GEN2 参数。subaru_init() 按 Gen2 标志选择两组 RX/TX 总线表,没有纵向 TX 表分支。一个常见误解是「Gen2 把全部 RX 移到 bus 1」——实际只有轮速、制动、巡航三组从 bus 0 改到 bus 1,油门与驾驶员扭矩仍留在 bus 0。

    报文职责 Gen1 Gen2 钩子使用
    0x40 油门、0x119 驾驶员扭矩 RX bus 0 bus 0 两代相同
    0x13A 轮速、0x13C 制动、0x240 巡航 RX bus 0 bus 1 Gen2 的 alt bus 影响这三组,不能把全部 RX 一起移到 bus 1
    0x122 ES_LKAS TX bus 0 / 8 字节 bus 0 / 8 字节 两代均检查扭矩与请求位
    0x221 ES_Distance TX bus 0 / 8 字节 bus 1 / 8 字节 均只允许取消,Cruise_Throttle=1818
    0x321/0x322/0x323 HUD TX bus 0 / 8 字节 bus 0 / 8 字节 白名单与继电器检查;无本模式逐字段校验

    五组 RX 均要求 8 字节、8 位累加校验、四位计数器和存活检查;声明频率依次为油门 100 Hz、扭矩 50 Hz、轮速 50 Hz、制动 50 Hz、巡航 20 Hz。这些频率用于健康检查,不等于每帧独立实时截止时间。subaru_get_checksum()、subaru_get_counter()、subaru_compute_checksum() 定下字节 0 存校验和、字节 1 低四位为计数器;校验值为地址两字节加字节 1 起的数据之和并截断为 8 位。

    校验与计数器:一个已知的盲点

    一个值得写进契约盲点的现象来自离线探针:把已打包 0x221 帧的字节 4 异或 0xFF、保持原校验字节不变,两代仍能通过 TX 钩子。这说明该数据字节及由此产生的校验和失配不在本模式的 TX 专属检查内。需要强调边界:这不意味着 ECU 会接受畸形帧,只是固件这一层没有拦。

    项目 规则 边界
    校验和 字节 0 存校验和;地址两字节加字节 1 起数据求和截断为 8 位 8 位累加,非通用 CRC
    计数器 字节 1 低四位 四位回绕,配合存活检查
    TX 校验盲点 改字节 4 保持原校验字节仍可通过 TX 钩子 不在本模式 TX 专属检查内,不代表 ECU 接受

    转向、纵向与转发边界

    subaru_tx_hook() 对 0x122 解码 13 位有符号转向扭矩并反转符号,检查请求位。Gen1 最大扭矩 2047、上升/下降分别 50/70;Gen2 为 1000、40/40。两代共用实时差 940、驾驶员余量 60、倍数 50,以及至少 7 帧有效请求、最多 1 帧无效请求和 144 ms 最小间隔的容忍配置。这些是固件限值,不等于主机控制器一定按相同步长构帧。

    纵向一侧,源码定义了较多 SUBARU_LONG_LIMITS 字段,但当前 TX 钩子只在 0x221 读取其 inactive_gas=1818,没有放行主动油门、制动或巡航 RPM 命令。与之对应,接口显式关闭自控纵向能力。

    同时,两代的 0x122 与 HUD TX 均标 check_relay=true,若本机发送总线出现同地址原车帧,可触发共享继电器故障;0x221 为 check_relay=false。该模式无专属 fwd 钩子,通用转发只在 bus 0 与 bus 2 之间;Gen2 的 bus 1 不是该转发对。不能只看上层相机/网关接线位置就假定 0x221 已被正确转发或替代。

    状态侧,subaru_rx_hook() 对 0x119 读取 11 位驾驶员扭矩并反转符号;轮速解析四轮 13 位原始值,只要任一非零即认为移动,并以均值形成速度。通用 RX 门继续处理踏板退出。

    验证与插件准入

    三项原钩子离线探针用真实 DBC 打包 0x221 取消帧并调用编译版 safety_tx_hook():Gen1 仅 bus 0、Gen2 仅 bus 1 可通过;取消位缺失或油门从 1818 变为 1819 都被拒。相邻 test_subaru.py 覆盖 Gen1/Gen2 扭矩、请求位及取消帧;本机运行该模块 64 项,12 项跳过,其余通过。

    • 身份插件需证明 Global Gen1/Gen2、DBC 和物理总线布局;Preglobal、Hybrid、角度 LKAS 的能力不能继承本模式推断。
    • 车辆输入插件保留五组 RX 的来源与时间戳;Gen2 特别区分 bus 0 的油门/扭矩与 bus 1 的轮速/制动/巡航。
    • 控制插件只提交转向意图和受限取消请求;安全模式读回、CRC/计数、继电器、失联降级仍由独立 MCU/网关裁决。
    • 验证需覆盖错总线、校验/计数故障、巡航边沿、驾驶员反扭矩、请求位切断、0x221 非活动油门和 HUD 载荷,再进入台架与受控场地。

    HUD 三帧仅做通用地址/总线/长度白名单及继电器检查,未在本模式限制载荷字段。自制硬件应把这些帧的构造权留给可信任网关,限制模板与发送节拍,避免普通插件直接写原始 CAN。当前无目标板或实车验证。

    常见问题

    Subaru Global 支持自控纵向吗?

    不支持。该快照下 alphaLongitudinalAvailable=False,普通参数路径不会启用自控纵向;subaru_init() 也没有纵向 TX 表分支。当前 TX 钩子只在 0x221 读取 inactive_gas=1818,任何非该值的 Cruise_Throttle 或未设置 Cruise_Cancel 都被拒。

    Gen2 是把所有 RX 都移到 bus 1 吗?

    不是。Gen2 只把轮速、制动、巡航三组从 bus 0 改到 bus 1,油门与驾驶员扭矩仍留在 bus 0。不能把全部 RX 一起移到 bus 1,否则会读错来源。

    改了 0x221 的数据字节还能通过 TX 钩子,是不是说明固件有漏洞?

    该数据字节及由此产生的校验和失配不在本模式的 TX 专属检查内,所以按探针做法仍能通过 TX 钩子;但这不等于 ECU 会接受畸形帧。这里应把它写成契约盲点,而不是定性为整车级缺陷。

    为什么不能只按接线位置判断 0x221 是否被转发?

    因为该模式没有专属 fwd 钩子,通用转发只在 bus 0 与 bus 2 之间,Gen2 的 bus 1 不是该转发对;而 0x221 的 check_relay=false。只看上层相机/网关接线位置,无法推断 0x221 已被正确转发或替代。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SUBARU_GLOBAL_PANDA_SAFETY_DEEP_DIVE.md(取用了 Global Gen1/Gen2 总线矩阵、TX 取消帧约束、转向限值与三项离线探针结论)。
    • 项目 02 —— openpilot-research/SUBARU_PREGLOBAL_PANDA_SAFETY_DEEP_DIVE.md(仅用于确认 Preglobal 是不同安全钩子、能力不可继承)。

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

    更新记录

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

  • Tesla panda 安全模式:Autopark 优先与选择性转发矩阵

    结论:Tesla panda 安全模式的优先级不是「谁能发」而是「谁优先」:启动时先假定 Autopark/Summon 活跃并拒绝所有主机 Tesla 候选帧;原车 LKAS 锁存时把 0x488 从主机 TX 切回原车透传;原车 AEB 活跃时恢复 0x2B9 透传并拒绝主机取消帧。因此「白名单里有某地址」不等于该地址的每个字段都经过逐信号安全校验。

    RX 完整性:9 类监视帧与校验

    tesla_model3_y_rx_checks 含 9 类帧:相机总线 2 的 0x2B9/0x488,主总线 0 的 DI 速度、ESP 第二车速、EPAS 状态、油门、刹车、DI 巡航/Autopark 和 UI 警告。各自配置地址、长度、名义频率及计数上限;仅 ESP 轮速和刹车未忽略质量位。UI 警告进入通用 RX 健康监控,但 tesla_rx_hook() 并无对应的状态提取分支。

    共享 safety_rx_hook() 先验证校验/计数/质量位,再对命中且有效的帧运行车型钩子;坏 RX 帧不可当作有效的原车 AEB/LKAS 状态更新。校验按地址选择校验字节,计算地址两字节与其它载荷字节的 8 位和。

    函数 责任
    tesla_get_counter() 按地址从不同字节/位域取得 3 或 4 位计数器。
    tesla_get_checksum_byte()、tesla_get_checksum()、tesla_compute_checksum() 按地址选择校验字节,计算地址两字节与其它载荷字节的 8 位和。
    tesla_get_quality_flag_valid() 0x155 ESP 轮速质量位必须为 1;0x145 制动状态不能为未初始化或故障编码。
    tesla_rx_hook() 更新角度/接管、DI/ESP 双车速、踏板、巡航、Autopark、原车 AEB/LKAS 锁存。
    tesla_init() 选择默认或调试纵向 TX 表,重置原车辅助状态,并在启动时先假定 Autopark/Summon 活跃。

    原车状态锁存:Autopark、LKAS 与 AEB 的上升沿

    tesla_rx_hook() 的 DI 主车速与 0x155 ESP 第二车速按不同原始编码解码;后者经 speed_mismatch_check() 比较,差超过 2 m/s 时撤销控制许可。0x370 EPAS 角度进入采样窗口;高等级手握或特定高角速度故障置 steering_disengage,由共享接管逻辑消费。两速度帧及 EPAS 帧异步到达,静态代码不保证共时。

    0x286 的 Autopark 活跃编码包括 3、4、9;只有在原车巡航未接合的上升沿才锁存 tesla_autopark,状态退出时清除。随后的原车巡航状态须再与 !tesla_autopark 合取后送 pcm_cruise_check()。0x488 相机报文仅在自身 LKAS 状态上升沿且 controls_allowed=false 时锁存原车 LKAS;状态消失时清除。0x2B9 相机报文的 AEB 活动位则直接更新 tesla_stock_aeb。插件必须保留三种原车状态的来源、有效性和时间信息,不可只发布合并后的「辅助驾驶占用」布尔量。

    TX 与转发优先级矩阵

    条件 主机 TX 相机总线 2 到主总线 0
    初始化后尚未收到解除 Autopark 的 0x286,或锁存的 Autopark 活跃 tesla_tx_hook() 拒绝所有 Tesla 候选帧 车型动态钩子不阻断 0x488/0x27D/0x2B9,允许原车路径继续。
    普通非 Autopark、原车 LKAS 未锁存 0x488 可在角度/控制类型许可下发送 0x488 与 0x27D 被 tesla_fwd_hook() 阻断。
    原车 LKAS 已锁存 0x488 被车型 TX 钩子拒绝 0x488 透传给主总线。
    默认原车纵向 0x2B9 仅允许无执行量的 ACC_CANCEL_GENERIC_SILENT,且不得宣称 AEB 事件 0x2B9 一直透传;原车 AEB 活跃时主机取消帧也被拒绝。
    ALLOW_DEBUG 且纵向参数位置位 0x2B9 可按加速度限值发送,仍禁止主机 AEB 事件 无原车 AEB 时阻断原车 0x2B9;原车 AEB 活跃时恢复透传并拒绝主机 0x2B9。

    tesla_tx_hook() 对 0x488 只接受控制类型 NONE 或 ANGLE_CONTROL,并用车辆模型参数限制 0.1 度转角及横向动力学;原车 LKAS 锁存时即使数值合规仍拒绝。0x2B9 禁止主机设置任何 AEB 事件;默认模式仅可送加速度上下限均为中性 375 的取消编码。调试纵向模式的原始加速度范围为 288–425,中性为 375;两端都低于中性时额外拒绝,以防静止后倒车风险。

    白名单不等于逐信号校验:0x27D 的边界

    tesla_init() 的两个 TX 表都含 0x488、0x2B9、0x27D/总线 0。0x488 与 0x27D 保留 check_relay=true,但设 disable_static_blocking=true,因此共享 safety_fwd_hook() 不会先把它们恒定阻断,留给 Tesla 的动态钩子选择透传。默认模式的 0x2B9 check_relay=false;纵向模式则为 true 但仍关闭静态阻断。

    关键在于 0x27D 没有 Tesla TX 载荷分支:非 Autopark 时,它只经过通用地址/总线/长度、继电器状态和车型总阻断门,不能据此认定各字段经过逐信号安全校验。

    证据与新硬件边界

    test_tesla.py 包含校验/计数、速度失配、制动质量位、驾驶员接管、Autopark、原车 LKAS/AEB 透传、转角和倒车限制用例;其测试库默认加 ALLOW_DEBUG。新增的本机转发矩阵探针在严格编译和 UBSan 下分别运行 release/debug 变体,两者均通过:核对启动 Autopark 阻断、普通/原车 LKAS 转向透传,以及调试纵向和原车 AEB 的 0x2B9 转发切换。但它只设置隔离状态并检查通用 safety 函数,没有覆盖真实 RX 报文时序、目标编译器、板级继电器或 ECU 接受。

    自制硬件与插件化重构应把固件构建类型、Tesla 平台/转向控制字段代际、CAN 来源、车辆模型参数和安全参数版本绑定。感知/控制插件只能提供候选角度或加速度;Autopark、原车 LKAS/AEB 优先权、双车速、质量位与故障降级仍须由独立受信任安全层执行。

    常见问题

    为什么启动时会拒绝所有主机 Tesla 候选帧?

    因为 tesla_init() 在启动时先假定 Autopark/Summon 活跃,在收到解除 Autopark 的 0x286 之前,tesla_tx_hook() 会拒绝所有 Tesla 候选帧;Autopark 锁存活跃时同样如此。

    原车 LKAS 锁存后,0x488 还能由主机发送吗?

    不能。原车 LKAS 已锁存时 0x488 被车型 TX 钩子拒绝,主总线方向则改为把 0x488 透传给主总线。普通非 Autopark 且原车 LKAS 未锁存时,0x488 才可在角度/控制类型许可下发送。

    0x27D 在白名单里是否代表它经过逐信号校验?

    不代表。0x27D 没有 Tesla TX 载荷分支;非 Autopark 时它只经过通用地址/总线/长度、继电器状态和车型总阻断门,不能据此认定各字段经过逐信号安全校验。

    原车 AEB 活跃时 0x2B9 会怎样处理?

    原车 AEB 活跃时恢复 0x2B9 透传并拒绝主机 0x2B9;默认原车纵向模式下,主机侧 0x2B9 仅允许无执行量的 ACC_CANCEL_GENERIC_SILENT,且不得宣称 AEB 事件。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TESLA_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了 RX 监视帧、原车状态锁存、TX 与转发优先级矩阵及证据边界)。
    • 项目 02 —— openpilot-research/FORD_PANDA_SAFETY_MODE_DEEP_DIVE.md(仅用于对照品牌安全模式的通用分发结构)。

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

    更新记录

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