Skip to content

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

Sources: MCDFileDownloader.hpp (L1-99)

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

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 配置,属于声明式刷写。而 VbfFileDownloader 和 BinFileDownloader 则绕过 FlashJob 抽象层,通过 MCDCmdTrans 直接构造 UDS 诊断帧(0x34 RequestDownload、0x36 TransferData、0x37 TransferExit 等),属于命令式刷写。当前编译配置 FLASHER_USE_MCD_METHOD=1,因此 实际使用的是 VbfFileDownloader 和 BinFileDownloader,MCDFileDownloader 作为备用路径存在。

Sources: ECUFlasherImpl.cpp (L114-148)

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 (L26-48)

逻辑链路建立流程 ​

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

Sources: MCDCmdTrans.cpp (L49-83)

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 (L146-199)

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 (L318-888)

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 (L693-799)

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 (L895-958)

安全掩码(Security Mask)映射表 ​

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

Sources: MCDCmdTrans.cpp (L960-1139)

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 (L1-157)

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 (L213-500)

签名验证策略 ​

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

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

Sources: VbfFileDownloader.cpp (L256-288)

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 秒 → 发送 EnterProgramMode → EnterSystem → 安全访问

安全访问双通道回退 ​

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

Sources: BinFileDownloader.cpp (L213-462)

transferFile 分帧传输实现 ​

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

Sources: BinFileDownloader.cpp (L350-459)

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

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

核心流程 ​

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

onPrimitiveProgressInfo:进度回调的关键实现 ​

MCDFileDownloader 的 onPrimitiveProgressInfo 方法是进度上报的参考实现。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 (L131-147)

异常抛出的事件处理器 ​

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

Sources: MCDFileDownloader.cpp (L25-220)

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 (L114-148)

数据模型: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 (L1-80), File.hpp (L1-60), DownloadArgs.hpp (L1-34)

关键设计决策与模式总结 ​

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

核心模式:

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

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