Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.6 DSP——会话与安全(0x10与0x27的完整实现)

诊室里最复杂的两个服务

0x10 DiagnosticSessionControl和0x27 SecurityAccess是DSP里状态机最复杂的两个服务——不是因为它们的业务逻辑最重,而是因为它们各自管理了一套全栈状态——会话切换牵扯到通讯模式、DTC设置、IO控制停止、安全等级锁住;安全访问牵扯到请求序列校验、延迟定时器、爆破尝试计数。


0x10 DspUdsDiagnosticSessionControl —— 解剖

void DspUdsDiagnosticSessionControl(
    Dcm_MsgContextType *pMsgContext,
    Dcm_NegativeResponseCodeType *dataNegRespCode)
{
    // 1. 长度校验: 必须 == 2
    if (pMsgContext->reqDataLen != 2) {
        *dataNegRespCode = DCM_E_INCORRECT_MESSAGE_LENGTH;
        return;
    }

    // 2. 子功能提取 (bit6-0)
    uint8 subFunc = pMsgContext->reqData[1] & 0x7F;
    uint8 suppressPosRsp = (pMsgContext->reqData[1] & 0x80) >> 7;

    // 3. 在会话配置表中查找
    const Dcm_DspSessionType *session = NULL;
    for (int i = 0; i < DCM_CFG_NUM_OF_SESSIONS; i++) {
        if (Dcm_ConfigPtr->Dsp->DspSession[i].DspSessionLevel == subFunc) {
            session = &Dcm_ConfigPtr->Dsp->DspSession[i];
            break;
        }
    }
    if (session == NULL) {
        *dataNegRespCode = DCM_E_SUBFUNCTION_NOT_SUPPORTED;
        return;
    }

    // 4. 检查Jump-to-Bootloader配置
    if (session->DspSessionForBoot == DCM_OEM_BOOT) {
        // 编程会话请求触发跳转到OEM bootloader
        dspUdsDiagnosticSessionControl.jumpToBoot = DCM_JTB_OEM_BOOT;
        // RTE模式切换
        Rte_Switch_DcmEcuReset_DcmEcuReset(
            RTE_MODE_DcmEcuReset_JUMPTOBOOTLOADER);
        // 设置pending状态——在所有Tx确认完成后才真正跳转
        dspUdsDiagnosticSessionControl.status = DCM_JTB_WAIT_RESPONSE_PENDING_TX_CONFIRM;
        *dataNegRespCode = DCM_E_FORCE_RCRRP; // 触发responsePending
        return;
    }

    // 5. 会话切换
    DslSetSesCtrlType(subFunc); // → DSL改变当前会话
    DspResetDiagnosticActivityOnSessionChange(subFunc); // 重置副作用

    // 6. 正响应构建
    if (activeProtocol == DCM_UDS_ON_CAN) {
        // CAN模式: 附带P2/P2*定时参数
        pMsgContext->resData[0] = subFunc;           // 回声子功能
        pMsgContext->resData[1] = (session->DspP2Server >> 8) & 0xFF;
        pMsgContext->resData[2] = session->DspP2Server & 0xFF;
        pMsgContext->resData[3] = (session->DspP2StarServer >> 8) & 0xFF;
        pMsgContext->resData[4] = session->DspP2StarServer & 0xFF;
        pMsgContext->resDataLen = 5;
    } else {
        // FlexRay/DoIP: 不附带定时参数
        pMsgContext->resData[0] = subFunc;
        pMsgContext->resDataLen = 1;
    }

    *dataNegRespCode = DCM_E_POSITIVERESPONSE;
}

Jump-to-Boot的状态机

如果在配置中编程会话(0x02)绑定了DspSessionForBoot = DCM_OEM_BOOT,那么这条0x10命令不再是简单的会话切换——而是跳转ECU的bootloader来接管刷写

JTB_IDLE ─── 收到编程会话+BOOT配置 ──→ JTB_WAIT_RESPONSE_PENDING_TX_CONFIRM
                                         (发NRC 0x78让诊断仪等)

JTB_WAIT_RESPONSE_PENDING_TX_CONFIRM
   │
   ├─ responsePending发送确认后:
   │     DspJumpToBootMainFunction() → JTB_EXECUTE
   │
   └─ JTB_EXECUTE:
         Rte_Switch → 实际执行复位跳转
         → JTB_IDLE (下一轮ECU启动后)

0x27 DspUdsSecurityAccess —— 解剖

这是DSP中逻辑最密的函数之一。它分为两个主要流程:

分支A: requestSeed (奇子功能)

static void DspUdsSecurityAccessGetSeedSubFnc(
    uint8 securityAccessType, Dcm_MsgContextType *pMsgContext, ...)
{
    // 计算安全等级: level = (subFunction + 1) / 2
    uint8 requestedSecurityLevel = (securityAccessType + 1) / 2;

    // 在安全配置表查找
    const Dcm_DspSecurityRowType *secRow = NULL;
    for (int i = 0; i < DCM_CFG_NUM_OF_SECURITY_LEVELS; i++) {
        if (Dcm_ConfigPtr->Dsp->DspSecurity[i].DspSecurityLevel
            == requestedSecurityLevel) {
            secRow = &Dcm_ConfigPtr->Dsp->DspSecurity[i];
            break;
        }
    }
    if (secRow == NULL) {
        *dataNegRespCode = DCM_E_REQUEST_OUT_OF_RANGE;
        return;
    }

    // 检查延迟定时器
    SecFalseAttemptChkType *chk =
        &dspUdsSecurityAccesData.secFalseAttemptChk[requestedSecurityLevel];
    if (chk->startDelayTimer == DELAY_TIMER_ON_EXCEEDING_LIMIT_ACTIVE) {
        *dataNegRespCode = DCM_E_REQUIRED_TIME_DELAY_NOT_EXPIRED;
        return;
    }

    // 如果已解锁同等级——返回种子=0 (标志已解锁)
    if (DslGetSecurityLevel() >= secRow->DspSecurityLevel) {
        pMsgContext->resData[0] = securityAccessType;
        memset(&pMsgContext->resData[1], 0x00, secRow->DspSecuritySeedSize);
        pMsgContext->resDataLen = 1 + secRow->DspSecuritySeedSize;
        *dataNegRespCode = DCM_E_POSITIVERESPONSE;
        return;
    }

    // 调用GetSeed回调
    uint8 seed[DCM_MAX_SEED_LENGTH];
    Std_ReturnType ret = secRow->GetSeed(seed, ...);
    if (ret == E_NOT_OK) {
        *dataNegRespCode = DCM_E_CONDITIONS_NOT_CORRECT;
        return;
    }

    // 存储当前安全上下文的请求状态
    dspUdsSecurityAccesData.reqInProgress = TRUE;
    dspUdsSecurityAccesData.reqSecLevel = secRow;

    // 构建正响应种子
    pMsgContext->resData[0] = securityAccessType;
    memcpy(&pMsgContext->resData[1], seed, secRow->DspSecuritySeedSize);
    pMsgContext->resDataLen = 1 + secRow->DspSecuritySeedSize;
    *dataNegRespCode = DCM_E_POSITIVERESPONSE;
}

分支B: sendKey (偶子功能)

static void DspUdsSecurityAccessCompareKeySubFnc(
    uint8 securityAccessType, Dcm_MsgContextType *pMsgContext, ...)
{
    // 1. 序列检查: 子功能必须是 (上一次requestSeed + 1)
    if (!dspUdsSecurityAccesData.reqInProgress ||
        securityAccessType != (dspUdsSecurityAccesData.lastReqSeedType + 1)) {
        *dataNegRespCode = DCM_E_REQUEST_SEQUENCE_ERROR;
        return;
    }

    // 2. 调用CompareKey
    Dcm_OpStatusType opStatus;
    Std_ReturnType ret = dspUdsSecurityAccesData.reqSecLevel->CompareKey(
        &pMsgContext->reqData[2], &opStatus);

    if (ret == E_PENDING) {
        *dataNegRespCode = DCM_E_FORCE_RCRRP;  // 加密引擎还在算
        return;
    }

    // 3. 比较结果
    if (ret == E_OK) {
        // 解锁
        DslInternal_SetSecurityLevel(
            dspUdsSecurityAccesData.reqSecLevel->DspSecurityLevel);
        pMsgContext->resData[0] = securityAccessType;
        pMsgContext->resDataLen = 1;
        *dataNegRespCode = DCM_E_POSITIVERESPONSE;
    } else {
        // 密钥无效
        SecFalseAttemptChkType *chk =
            &dspUdsSecurityAccesData.secFalseAttemptChk[...];
        chk->secAcssAttempts++;
        if (chk->secAcssAttempts >= secRow->DspSecurityNumAttDelay) {
            chk->startDelayTimer = DELAY_TIMER_ON_EXCEEDING_LIMIT_ACTIVE;
            chk->timerSecAcssAttempt = secRow->DspSecurityDelayTime;
        }
        *dataNegRespCode = DCM_E_INVALID_KEY;
    }

    dspUdsSecurityAccesData.reqInProgress = FALSE;
}

延迟定时器——在DspTimerMain中递减

void DspTimerMain(void) {
    for (int i = 0; i < DCM_CFG_NUM_OF_SECURITY_LEVELS; i++) {
        SecFalseAttemptChkType *chk =
            &dspUdsSecurityAccesData.secFalseAttemptChk[i];
        if (chk->startDelayTimer == DELAY_TIMER_ON_EXCEEDING_LIMIT_ACTIVE) {
            if (chk->timerSecAcssAttempt > 0) {
                chk->timerSecAcssAttempt--;
            } else {
                // 延迟结束——清除计数器
                chk->startDelayTimer = DELAY_TIMER_DEACTIVE;
                chk->secAcssAttempts = 0;
            }
        }
    }
}

本篇小结

  1. 0x10会话控制的核心复杂在于Jump-to-Boot状态机——编程会话可以是直接会话切换,也可以是跳转bootloader接管。
  2. 0x27安全访问通过奇偶子功能编码了完整的Seek/Key状态机——序列错误直接NRC 0x24。
  3. 防爆破机制通过延迟定时器在DspTimerMain中周期性递减——失败计数的阈值由配置表决定。
  4. 种子全零的特殊含义(已解锁)是一个极简洁的“不需要再走sendKey“的通信机制。

【下集预告】:安全访问完毕——你有了读受保护DID的权限。DID怎么读的?怎么查表的?SYNCH/ASYNCH/SR/NVM四种端口在代码里长什么样?递归DID引用链怎么处理?我们拆0x22在DSP中的完整实现。