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.3 DSL——会话层的缓冲区管理与时序状态机

从CanTp的PCI字节到DCM的完整消息——这一步在DSL

当CanTp收到最后一帧CF并把26个字节粘合回一条完整UDS消息后,它通过PduR呼叫DslTpRxIndicationFromPduR——这时DSL的角色正式启动。

DSL的1828行代码做了三件事:

  1. 把来自PduR的SDU数据放进DCM内部的消息缓冲区
  2. 管理S3会话超时——在MainFunction里每个周期递减计数器
  3. 把DSP处理完的响应数据从内部缓冲区吐出给PduR→传输层

缓冲区分配——DslStartOfReception

当传输层开始接收新消息时,它必须先从DCM拿到一个写入地址——DslStartOfReception负责这件事:

BufReq_ReturnType DslStartOfReception(
    PduIdType dcmRxPduId, PduLengthType tpSduLength,
    PduLengthType *rxBufferSizePtr, boolean internalRequest)
{
    // 在协议行配置中查找这个Rx PDU ID对应的运行时数据
    runtime = findRxPduIdParentConfigurationLeafs(dcmRxPduId);

    // 情况1: 外部Rx缓冲区可用?
    if (runtime->externalRxBufferStatus == BUFFER_AVAILABLE) {
        runtime->externalRxBufferStatus = PROVIDED_TO_PDUR;
        *rxBufferSizePtr = runtime->diagnosticRequestFromTester.size;
        return BUFREQ_OK;
    }

    // 情况2: 外部忙——试试内部本地缓冲区
    // 仅当: 非内部请求 && 消息长度 <= 8字节 && 本地缓冲区为空
    if (!internalRequest && (tpSduLength <= DCM_DSL_LOCAL_BUFFER_LENGTH)) {
        *rxBufferSizePtr = DCM_DSL_LOCAL_BUFFER_LENGTH;
        return BUFREQ_OK;  // PduR 会直接写进 localRxBuffer
    }

    // 情况3: 全忙
    *rxBufferSizePtr = 0;
    return BUFREQ_OVFL;
}

双缓冲的设计精髓在于:外部缓冲区处理完整长消息,本地8字节缓冲区专门处理并发的TesterPresent(0x3E)保活。当一个51字节的0x19 DTC查询正在进行时,诊断仪同时发的0x3E心跳不能因为缓冲区满了而丢失——否则S3超时会误触发。


接收到完成——DslTpRxIndicationFromPduR

PduR传完最后一个字节后触发此函数。这里是DSL最密集的逻辑:

void DslTpRxIndicationFromPduR(..., boolean internalRequest, ...) {
    // 1. 校验接收长度
    if (runtime->nofBytesHandled != tpSduLength) {
        releaseExternalRxTxBuffers(runtime);
        return;  // 长度不匹配,丢弃
    }

    // 2. TesterPresent特殊处理
    if (isTesterPresentCommand(&runtime->diagnosticRequestFromTester)) {
        // 只重置S3定时器,不需要经过DSD→DSP
        runtime->S3ServerTimeoutCount = S3ServerValue;
        releaseExternalRxTxBuffers(runtime);
        return;
    }

    // 3. 如果协议尚未启动,先启动协议
    if (!runtime->protocolStarted) {
        StartProtocolHelper(protocol);
    }

    // 4. 协议抢占: 如果已有活动请求在处理中
    if (protocolPreempt) {
        DcmResetDiagnosticActivity();
        // 发送抢占NRC: 0x7F + 原SID + busyRepeatRequest(0x21)
    }

    // 5. 把缓冲区交给DSD处理
    runtime->externalRxBufferStatus = PROVIDED_TO_DSD;
    DsdDslDataIndication(/* pduRxData, SIDTable, addrType, ... */);
}

DSL在这里做了一次TesterPresent短路优化——如果收到的消息是0x3E且suppressPosRsp=1,直接重置S3定时器并释放buffer,完全不必让DSD/DSP参与。这个优化在总线上负载最高的刷写场景下尤其重要——每秒可能有几十次0x3E保活需要处理。


S3会话超时——在DslMain中

void DslMain(void) {
    // 遍历所有协议行的运行时状态
    for (each runtime) {
        // S3会话超时倒计
        if (runtime->S3ServerStarted) {
            runtime->S3ServerTimeoutCount--;
            if (runtime->S3ServerTimeoutCount == 0) {
                // 超时 → 切回默认会话
                changeDiagnosticSession(runtime, DCM_DEFAULT_SESSION);
                runtime->S3ServerStarted = FALSE;
                // 安全等级自动锁住
                DslInternal_SetSecurityLevel(DCM_SEC_LEV_LOCKED);
            }
        }
    }
}

S3在每个MainFunction周期递减1——如果你的MainFunction周期是5ms且S3配置为5000ms——计数器初始值=1000,每个周期减1直到0。


响应回传——DslCopyTxData

当DSP处理完请求、DSD构建好正/负响应、DSL的Tx缓冲区被标记为DCM_TRANSMIT_SIGNALED后,传输层调用DslCopyTxData取走数据:

BufReq_ReturnType DslCopyTxData(PduIdType dcmTxPduId, ...) {
    // 从 runtime->diagnosticResponseFromDsd 拷贝到 PduR 提供的缓冲区
    memcpy(pduInfoPtr->SduDataPtr,
           &runtime->diagnosticResponseFromDsd.data[bytesAlreadySent],
           remainingToSend);
    runtime->nofBytesHandled += remainingToSend;
    *txDataCntPtr = remainingToSend;
    return BUFREQ_OK;
}

Rx/Tx缓冲区的六态完整流转

外部Rx缓冲:
  BUFFER_AVAILABLE ── DslStartOfReception() ──→ PROVIDED_TO_PDUR
  PROVIDED_TO_PDUR  ── 传输结束 TpRxIndication ──→ PROVIDED_TO_DSD
  PROVIDED_TO_DSD    ── 处理完成 release ──→ BUFFER_AVAILABLE

外部Tx缓冲:
  NOT_IN_USE ── DsdDslDataIndication ──→ PROVIDED_TO_DSD
  PROVIDED_TO_DSD ── DsdDspProcessingDone(DONE) ──→ DSD_PENDING_RESPONSE_SIGNALED
  DSD_PENDING_RESPONSE_SIGNALED ── PduR_DcmTransmit ──→ DCM_TRANSMIT_SIGNALED
  DCM_TRANSMIT_SIGNALED ── DslCopyTxData ──→ PROVIDED_TO_PDUR
  PROVIDED_TO_PDUR ── TxConfirmation ──→ NOT_IN_USE

本篇小结

  1. DSL的核心是双缓冲机制——外部缓冲处理完整长消息,本地8字节缓冲处理并发的TesterPresent保活。
  2. TesterPresent在DSL层被短路优化——不经过DSD/DSP,直接重置S3定时器。
  3. S3会话超时在DslMain每个周期递减一次——归零后自动切回默认会话并锁住安全等级。
  4. Rx/Tx缓冲的六态流转保证了DCM内部的“一请求一响应“消息信箱模型——前一个请求没处理完,后一个不收。

【下集预告】:DSL把消息喂给了DSD——但DSD怎么知道0x27应该分发给哪个处理函数?怎么保证当前会话、安全等级都允许执行?怎么处理suppressPosRsp位?我们拆开DSD的SID表查找逻辑和鉴权决策树。