Skip to content

本文档剖析 otxFlash 模块中基于 ASAM MCD3D 协议栈的 DoIP(Diagnostics over IP)文件下载子系统。该系统提供了三种下载策略——MCD FlashJob 模式VBF 手动模式BIN 手动模式——并通过 MCDCmdTrans 低层 UDS 命令引擎与 MCDCmdBoardcast 广播通信单例,构建了完整的 ECU 刷写通信链路。

Sources: MCDFileDownloader.hpp

架构总览:三种下载策略的协同体系

MCDFileDownloader 是整个子系统的命名核心,但它并非唯一的下载实现。系统实际包含 三个下载器类,它们并非通过继承关系关联,而是在 ECUFlasherImpl::downloadFile 中根据 ECU 类型进行策略选择。三者的共同点在于:都实现 asam::mcd::MCDEventHandler 接口以接收 MCD3D 运行时的事件回调,都持有 ECUFlasher*ECU*File* 三个核心依赖指针。

mermaid
graph TB
    subgraph "策略选择层"
        EFI["ECUFlasherImpl::downloadFile()"]
    end

    subgraph "下载器层"
        MCD["MCDFileDownloader<br/>MCD FlashJob 模式"]
        VBF["VbfFileDownloader<br/>VBF 手动 UDS 模式"]
        BIN["BinFileDownloader<br/>BIN 手动 UDS 模式"]
    end

    subgraph "通信层"
        TRANS["MCDCmdTrans<br/>点对点 UDS 命令引擎"]
        BOARD["MCDCmdBoardcast<br/>广播通信单例"]
    end

    subgraph "MCD3D 运行时"
        PROV["MCDProjectProvider<br/>共享 MCD 对象"]
        LL["MCDLogicalLink<br/>逻辑链路"]
        HEX["eMCDHEXSERVICE<br/>Hex 服务原语"]
        FJ["MCDFlashJob<br/>刷写作业"]
    end

    EFI -->|"域控 TCAM/BGM/CDC/ACU"| BIN
    EFI -->|"其他 ECU"| VBF
    EFI -.->|"FLASHER_USE_MCD_METHOD=0 时"| MCD

    BIN --> TRANS
    VBF --> TRANS
    BIN --> BOARD
    VBF --> BOARD
    MCD --> PROV
    MCD --> FJ

    TRANS --> LL
    TRANS --> HEX
    TRANS --> PROV
    BOARD --> LL
    BOARD --> PROV

    style MCD fill:#f9f,stroke:#333,stroke-dasharray: 5 5

关键设计决策MCDFileDownloader 通过 MCD3D 的 MCDFlashJob API 进行刷写,依赖 ODX/PDX 项目数据库中预定义的 Flash Session 和 DataBlock 配置,属于声明式刷写。而 VbfFileDownloaderBinFileDownloader 则绕过 FlashJob 抽象层,通过 MCDCmdTrans 直接构造 UDS 诊断帧(0x34 RequestDownload、0x36 TransferData、0x37 TransferExit 等),属于命令式刷写。当前编译配置 FLASHER_USE_MCD_METHOD=1,因此 实际使用的是 VbfFileDownloader 和 BinFileDownloaderMCDFileDownloader 作为备用路径存在。

Sources: ECUFlasherImpl.cpp

MCDCmdTrans:UDS 命令引擎的深度剖析

MCDCmdTrans 是手动刷写模式的核心通信引擎。它在构造时建立到指定 ECU 的 MCD 逻辑链路(LogicalLink),并创建一个 Hex 服务原语(eMCDHEXSERVICE) 用于收发原始 UDS 诊断帧。

ECU 变体名映射

构造函数中包含一个硬编码的 ECU 变体名重映射表,将简化的 ECU 名称映射到 ODX 数据库中定义的标准变体名和逻辑链路名:

匹配模式映射变体名映射逻辑链路名
含 "BGM"EV_BGM_SOCLL_BGM_SOC_DoIP
含 "ACU"EV_ACU_SOCLL_ACU_SOC_DoIP
含 "TCAM"EV_TCAMLL_TCAM_DoIP
含 "CDC"EV_CDC_SOCLL_CDC_SOC_DoIP

这一映射使得上层调用者可以使用简化的 ECU 名称,而不必关心 ODX 数据库中的精确命名约定。

Sources: MCDCmdTrans.cpp

逻辑链路建立流程

链路建立的完整流程为:从 MCDProjectProvider 获取共享的 MCDSystemMCDProjectMCDInterface 对象 → 调用 mcdProject->createLogicalLinkByVariantAndInterface() 创建逻辑链路 → 如果链路状态为 eCREATED,依次调用 open()gotoOnline() 将链路激活至在线状态。任何异常都会将 isValid 标记为 false,后续所有命令调用都会静默返回空数据。

Sources: MCDCmdTrans.cpp

sendCommand:同步 UDS 帧收发

sendCommand 是 MCDCmdTrans 的最底层通信原语。它将二进制数据封装为 asam::mcd::MCDValue(类型为 eA_BYTEFIELD),通过 hexCop->getRequest()->enterPDU(pdu) 注入请求 PDU,然后调用 hexCop->executeSync() 同步执行。执行结果通过 result->getResponses()->getItemByIndex(0) 获取响应,并提取响应的 bytefield 返回。发送和接收的数据都会被记录到 ECU 对象的命令日志中(通过 ecu->addCmd()),用于调试和追溯。

关于内存管理:每次 executeSync() 返回的 MCDResult* 对象都需要调用者手动 delete,源码注释明确指出这是 JVM 创建的克隆版本,必须显式释放以避免内存泄漏。

Sources: MCDCmdTrans.cpp

UDS 刷写命令集

MCDCmdTrans 封装了完整的 UDS 刷写命令序列,每个命令返回 bool 表示成功/失败:

方法UDS SID功能描述
EnterProgramMode()0x10 0x82进入编程会话(先发 0x10 0x82 再等待 1 秒)
EnterSystem()0x22 F1 86 / 0x10 0x02检查并进入默认会话,读取 DID F186 确认状态
getEcuSignatureMode()0x22 D0 1C读取 ECU 签名模式(Prod/Dev)
SecurityAccess(level)0x27 0x01/0x02安全访问 SeedToKey 认证,支持 6 个安全级别
SecurityAccess_ECOS(level)0x27 0x01/0x02ECOS 变体的安全访问(总是用 pinCode 作为 mask)
EraseMemory(addr, size)0x31 0x01 0xFF擦除指定地址范围的 Flash 内存
RequestDownload(...)0x34请求下载,支持 ≤4GB(0x00 0x44 格式)和 >4GB(0x00 0x88 格式)两种长度编码
RequestTransferExit()0x37结束数据传输
ActivateSbl(addr)0x31 0x01 0x03 0x01激活 Secondary Bootloader
VerifyAuthenticity(...)0x31 0x01 0x02 0x08/0x12验证刷写文件签名(domain 模式用 ASCII 字符串,hex 模式用二进制)
FinishProgram()0x31 0x01 0x02 0x05完成编程(检查 9 字节响应中的第 9 字节为 0)

Sources: MCDCmdTrans.cpp

RequestDownload 的帧长度自适应

RequestDownload 展现了精心的协议适配逻辑。当数据长度 nDataLength ≤ 0xFFFFFFFF(4GB)时,使用 11 字节的传统格式(0x34 0x00 0x44 后跟 4 字节地址和 4 字节长度)。当超过 4GB 时,自动切换到 19 字节的扩展格式(0x34 0x00 0x88,前 8 字节地址清零,后 8 字节承载 64 位长度)。ECU 响应中的 nFrameSize 由响应的第二个字节决定:0x20 表示 2 字节长度字段、0x30 表示 3 字节、0x40 表示 4 字节。最终 nFrameSize 会被钳制到 MAX_FLASH_FRAME_SIZE (0xFFFFFF00) 以下,并减去 2(因为 TransferData 帧需要 0x36 和计数器两个字节)。

Sources: MCDCmdTrans.cpp

SeedToKey 安全访问算法

安全访问流程为:发送 0x27 0x01(安全级别由参数 iLevel 决定具体 SID) → 从响应中提取 3 字节 Seed → 结合 Mask 通过 Calc_Key 算法计算 3 字节 Key → 通过 0x27 0x02 发送 Key。

Calc_Key 采用 24 位线性反馈移位寄存器(LFSR) 变体算法,对 8 个输入字节逐一处理,每个字节执行 8 轮位迭代。初始状态 v3 = 0xC541A9,反馈多项式基于 0x109028。最终输出是一个 24 位值,取高 4 位、中 8 位、低 8 位组成 3 字节 Key。代码受 #ifdef _VM_PROTECT_ 保护,在启用虚拟化保护时关键计算逻辑会被包裹在 VMStart()/VMEnd() 之间。

特殊安全级别 level=7 不走 SeedToKey 流程,而是直接使用固定的 16 字节 Key。

Sources: MCDCmdTrans.cpp

安全掩码(Security Mask)映射表

Get_SecurityMask 维护了一个庞大的 DoIP 地址到 5 字节安全掩码 的映射表,覆盖 50+ 个 ECU 地址(0x1001 至 0x1BB7)。每个 ECU 有其独有的掩码,用于 SeedToKey 计算。默认掩码为 \x41\xAA\x42\xBB\x43

Sources: MCDCmdTrans.cpp

MCDCmdBoardcast:功能组广播通信单例

MCDCmdBoardcast 采用 单例模式,通过 "LL_FG_UDSOnDoIP" 逻辑链路实现功能组寻址(Functional Addressing) 广播。构造时不仅打开链路并上线,还会发送 eMCDSTARTCOMMUNICATION 原语以初始化广播通信。其 sendCommand 方法与 MCDCmdTrans::sendCommand 结构相同,但创建的是 eMCDHEXSERVICE 类型的临时 Primitive(每次发送后销毁重建)。

关键用途:在 BinFileDownloader 处理 PBL(fileType=3)刷写完成后,通过广播发送 0x11 0x81(ECU Reset)命令重置所有 ECU,然后调用 otx_reconnect_vehicle_ex 重新建立车辆连接并等待 170 秒(约 3 分钟)让所有 ECU 完成重启。

Sources: MCDCmdBoardcast.cpp

VbfFileDownloader:VBF 格式的命令式刷写

VbfFileDownloader 实现了对 VBF(Versatile Binary Format) 文件的完整刷写流程。它通过 VBFParserSmall 解析 VBF 文件结构,提取擦除区域列表、数据块、签名信息和 SBL 激活地址。

完整刷写序列

mermaid
sequenceDiagram
    participant VBF as VbfFileDownloader
    participant CMD as MCDCmdTrans
    participant ECU as ECU (DoIP)

    VBF->>CMD: new MCDCmdTrans(ecu)
    Note over CMD: 建立逻辑链路

    loop 每个擦除区域
        VBF->>CMD: EraseMemory(addr, size)
        CMD->>ECU: 0x31 0x01 0xFF [addr][size]
        ECU-->>CMD: 0x71 0x01 0xFF
    end

    loop 每个 VBF Block
        VBF->>CMD: RequestDownload(formatId, addr, len)
        CMD->>ECU: 0x34 0x00 0x44 [addr][len]
        ECU-->>CMD: 0x74 0x20/0x30/0x40 [maxLen]

        loop TransferData 帧序列
            VBF->>CMD: SendFrame_Cmd36(0x36 [ctr] [data])
            CMD->>ECU: TransferData Frame
        end

        VBF->>CMD: RequestTransferExit()
        CMD->>ECU: 0x37
        ECU-->>CMD: 0x77
    end

    opt VBF v2.6 签名验证
        VBF->>CMD: VerifyAuthenticity(signature, hex)
        CMD->>ECU: 0x31 0x01 0x02 0x12 [sig]
    end

    opt SBL 文件 (fileType=1)
        VBF->>CMD: ActivateSbl(callAddr)
        CMD->>ECU: 0x31 0x01 0x03 0x01 [addr]
    end

TransferData 计数器与进度上报

每个 TransferData 帧以 0x36 开头、后跟 1 字节计数器(nCTR,从 1 开始,溢出后归零)。每发送一帧后,更新 ECU 级别的累计下载量(mECU->setDownloadedSize)和文件级别的增量(mEcuFile->increaseDownloadedSize),然后向 Flasher 发布 ECUFlasherFileDownloadProgressEventArg 进度事件。当 ECU 累计下载量达到总量时,发布 progress=100 的最终进度事件。

Sources: VbfFileDownloader.cpp

签名验证策略

对于 VBF v2.6 格式,根据 ECU 的 signatureMode 配置采用不同的验证策略:

  • 0(自动):优先用 isProd 标志选择 Prod/Dev 签名,失败后回退到另一个
  • 1(仅 Dev):仅验证 Dev 签名
  • 2(仅 Prod):仅验证 Prod 签名

Sources: VbfFileDownloader.cpp

BinFileDownloader:BIN 格式与域控刷写

BinFileDownloader 专用于 域控制器(TCAM、BGM、CDC、ACU) 的 BIN 格式文件刷写。它直接从磁盘流式读取文件,无需 VBF 解析器。

刷写流程差异

与 VbfFileDownloader 的关键差异在于:

  1. 擦除策略:调用 EraseMemory() 无参数重载(实际使用默认参数 uMem=0, uSize=0,表示整片擦除),而非按 VBF 擦除列表逐区域擦除
  2. 下载寻址RequestDownload(0, 0, fileSize) 使用格式标识符 0 和内存地址 0(由 ECU 自行决定写入位置)
  3. 退出传输:显式调用 RequestTransferExit()
  4. 域名验证VerifyAuthenticity(keyInfo, true) 使用 isDomain=true 模式(ASCII 字符串而非 hex)
  5. PBL 特殊处理:当 fileType=3 时,刷写完成后发送 ECU Reset → 重建连接 → 等待 170 秒 → 发送 EnterProgramModeEnterSystem → 安全访问

安全访问双通道回退

PBL 刷写后的安全访问采用 双通道回退策略:根据 securityAccessMode,优先尝试 SecurityAccess_ECOS(pinCode)SecurityAccess("FFFFFFFF"),失败后自动切换到另一通道。这覆盖了普通刷写(优先 pinCode)和换件刷写(优先 FFFFFFFF)两种场景。

Sources: BinFileDownloader.cpp

transferFile 分帧传输实现

与 VBF 的 Block 结构不同,BIN 的 transferFile 直接从文件流读取。帧大小被限制到 MIN(1MB, nFrameSize),使用 fread 逐帧读取文件内容,通过 SendFrame_Cmd36 发送。进度更新采用 oldProgress 去重机制——仅当百分比变化时才发布进度事件,避免重复通知。

Sources: BinFileDownloader.cpp

MCDFileDownloader:MCD FlashJob 声明式刷写(备用路径)

MCDFileDownloader 是三种策略中唯一使用 MCD3D FlashJob API 的实现。它通过 ODX 数据库中的 FlashJob 定义来驱动刷写流程,而非手动构造 UDS 命令。

核心流程

  1. 通过 MCDProjectProvider 获取共享的 MCDSystemMCDProjectMCDInterface
  2. 创建 MCDLogicalLink 并上线
  3. 通过 getDbDiagComPrimitivesByType(eMCDDBFLASHJOB) 从数据库加载刷写作业定义
  4. 设置 FlashDataBlock 的 activeFileName 为待刷写文件路径
  5. 配置作业参数(PA_PINCodePA_FlashFileFlag
  6. 注册 MCDEventHandler 回调并调用 job->executeSync()
  7. 从响应中提取 PA_JobStatuHEXPA_AdditErrorInfor

onPrimitiveProgressInfo:进度回调的关键实现

MCDFileDownloaderonPrimitiveProgressInfo 方法是进度上报的参考实现。MCD3D 运行时以 0-100 的整数值回调进度,下载器将其转换为实际下载字节数:downloadedSize = fileSize × (progress / 100)。通过与 mEcuFile->getLastDownloadedSize() 比较计算增量 increase,仅当 increase > 0 时才更新 flasher->mDownloadedSize ——这是一个关键的防倒退保护:防止 Java 层计算的进度从 99% 回退到 98% 导致的统计偏差。最终重新计算精确百分比(基于实际累计字节数而非 MCD 的整数值),发布 ECUFlasherFileDownloadProgressEventArg 事件。

所有更新操作都在 std::lock_guard<std::mutex> lock(flasher->mFieldLock) 保护下进行,确保多 ECU 并行刷写场景下的线程安全。

Sources: MCDFileDownloader.cpp

异常抛出的事件处理器

MCDFileDownloader 中绝大多数 MCDEventHandler 虚函数(约 40 个)的实现体都是 throw -1,并注释"不可能发生的事件我们抛出异常,提醒开发者注意"。这是防御性编程的极端实践:将 FlashJob 执行期间不应触发的事件(如 onLinkStateOnlineonSystemLocked 等)显式标记为异常,一旦触发即崩溃以暴露问题。唯一正常实现的回调是 onFirstEvent()onLastEvent()(空操作)、onPrimitiveJobInfo()(空操作)和 onPrimitiveProgressInfo()(进度上报)。

Sources: MCDFileDownloader.cpp

ECUFlasherImpl::downloadFile:策略选择枢纽

ECUFlasherImpl::downloadFile 是三种下载策略的统一入口和路由枢纽

cpp
if (FLASHER_USE_MCD_METHOD > 0) {
    if (ecuName 包含 "TCAM"/"BGM"/"CDC"/"ACU") {
        使用 BinFileDownloader
    } else {
        使用 VbfFileDownloader
    }
} else {
    使用 MCDFileDownloader  // 模拟进度
}

每个下载器在调用前后都会发布 ECUFlasherFileEventArg(开始事件)和 ECUFlasherFileDownloadEndEventArg(结束事件,携带错误码和消息),形成完整的文件下载事件对。

Sources: ECUFlasherImpl.cpp

数据模型:ECU、File 与 DownloadArgs

ECU 类封装了单个 ECU 的完整刷写上下文:名称、ID、逻辑链路名、优先级、安全访问配置、签名模式、SBL 激活地址、文件列表以及动态下载统计(总大小、已下载大小)。其 m_vecCmd 成员记录所有发送/接收的 UDS 命令日志。

File 类描述待刷写文件:路径、大小、文件类型(0=Data、1=SBL、2=App、3=PBL)、PIN 码、密钥信息、以及运行时下载进度(累计下载量、最近一次增量)。内嵌 VBFParserSmall parser 用于 VBF 格式解析。

DownloadArgs 类是一个简单的参数聚合体,将 File、ECUFlasher 和下载大小打包传递,用于 TaskPool 的任务参数封装。

Sources: ECU.hpp, File.hpp, DownloadArgs.hpp

关键设计决策与模式总结

维度MCDFileDownloaderVbfFileDownloaderBinFileDownloader
通信方式MCD FlashJob APIMCDCmdTrans 手动 UDSMCDCmdTrans 手动 UDS
文件格式ODX DataBlock 配置VBF 解析器驱动裸 BIN 文件流
擦除策略FlashJob 内置VBF 擦除列表逐区整片擦除
签名验证FlashJob 内置MCDCmdTrans 二进制MCDCmdTrans 域名
进度上报MCD 事件驱动手动逐帧计算手动去重计算
适用场景通用(备用)普通 ECU域控制器
线程安全锁保护进度区无显式锁无显式锁

核心模式

  • 策略模式ECUFlasherImpl::downloadFile 根据 ECU 类型和编译宏选择下载器
  • 模板方法:三个下载器共享相同的生命周期(开始事件 → 下载 → 结束事件)
  • 单例模式MCDCmdBoardcast 全局唯一广播通信实例
  • 代理模式MCDLogicalLink 是底层 DoIP 通信的代理对象
  • 观察者模式MCDEventHandler 接口驱动进度回调

关于后续阅读,推荐按以下顺序深入: