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;
}
}
}
}
本篇小结
- 0x10会话控制的核心复杂在于Jump-to-Boot状态机——编程会话可以是直接会话切换,也可以是跳转bootloader接管。
- 0x27安全访问通过奇偶子功能编码了完整的Seek/Key状态机——序列错误直接NRC 0x24。
- 防爆破机制通过延迟定时器在DspTimerMain中周期性递减——失败计数的阈值由配置表决定。
- 种子全零的特殊含义(已解锁)是一个极简洁的“不需要再走sendKey“的通信机制。
【下集预告】:安全访问完毕——你有了读受保护DID的权限。DID怎么读的?怎么查表的?SYNCH/ASYNCH/SR/NVM四种端口在代码里长什么样?递归DID引用链怎么处理?我们拆0x22在DSP中的完整实现。