CP Autosar - 万字长文调试NvM

无聊逛51
发布于 2023-11-13 11:45
浏览
0收藏

先给读者们跳一段

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

前言

NvM模块我们知道,位于AutoSar的Memory Stack的服务层,Stack的结构图如下:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

那么,AutoSar(ETAS)的代码是如何实现EEPROM的读写呢?以如下代码为引,调试下NvM读取时关键变量、状态机的跳转过程。

NvM_ReadAll();
do
{
    NvM_MainFunction();
    MemIf_Rb_MainFunction();

    NvM_Rb_GetStatus(&NvM_Sts);
    MemIF_Sts = MemIf_Rb_GetStatus();
} while ((NVM_RB_STATUS_BUSY == NvM_Sts) || (MEMIF_BUSY == MemIF_Sts));

NvM_ReadAll()

根据isolar中配置的NvM block属性 NvM_Prv_BlockDescriptors_acst,

设置NvM block的请求状态 NvM_Prv_stRequests_rAMwAM_au16 为 

NvM_Prv_ServiceBit_ReadAll_e ,

设置请求结果状态 NvM_Prv_stRequestResult_rAwAM_au8 为

NVM_REQ_PENDING 。

(方便后面调试复制,蓝色:变量,绿色:枚举,咖色:函数)

如图所示:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

如开头代码的while循环,接下来是挨个block去读取内容的过程。

NvM_MainFunction()

还算是个比较清爽的、以  NvM_Prv_Main_st.Activity_rAMwM_en  变化做状态机跳转的函数。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

接下来一个个状态机分解调试一下看看。

首先可以看下Init之后,NvM_Prv_Main_st.Activity_rAMwM_en:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

NVM_PRV_ACTIVITY_ARBITRATE

NvM活动仲裁:

在执行完函数 NvM_Prv_MainFunctionArbitrate() 后,可以看到 

NvM_Prv_Main_St 结构体的变化如下:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

选取了第一个有效的地址进行Read操作。

NVM_PRV_ACTIVITY_JOB_START

  1. 如果我们配置了SingleBlockStartCallback,则会首先调用回调函数;

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

2.之后会根据当前NvM的状态,去调用 NvM_Prv_JobStart_Read 函数,而从这边,会去区分是使用FEE(flash模拟EEPROM)接口还是EA(eeprom抽象)接口。

目前项目使用的是FEE,自然就执行到了 FEE_Read 函数了。

这里面也不算复杂,是对 Fee_OrderFifo_st 结构体的赋值。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

3.最后,改变状态机进入下一个步骤。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

NVM_PRV_ACTIVITY_POLL_RESULT

调用  Fee_GetJobResult 获取下全局变量 Fee_GlobModuleState_st 的值,即Fee的当前状态。当状态不为 MEMIF_JOB_PENDING ,可以去下一个步骤 

NVM_PRV_ACTIVITY_JOB_COMPLETE 。

那么其实NvM就是通过 job_start 和 poll_result 这2个状态机,对Fee模块做请求和等待结果的处理。

NVM_PRV_ACTIVITY_JOB_COMPLETE

调用的 NvM_Prv_JobComplete_Read,其实是一个保护函数,如果NvM状态为未完成状态,重新回到 NvM_Prv_MainFunctionJobStart 。

之后 进入NVM_PRV_ACTIVITY_RESULT_EVAL 。


NVM_PRV_ACTIVITY_RESULT_EVAL

如果读的流程没有问题,修改 NvM_Prv_stRequestResult_rAwAM_au8 的状态为 NVM_REQ_OK ,则会调用

NvM_Prv_MainFunctionResultEval_ResetMainStates,这边又是个内联函数,需要打特殊的断点才能看到。

清除NvM_Prv_stRequests_rAMwAM_au16请求状态,初始化

NvM_Prv_Main_st.JobData_st。再回到 NVM_PRV_ACTIVITY_ARBITRATE, 准备执行下一个block的读任务。


MemIf_Rb_MainFunction()

其实就是封装了Fee的mainfuction。而   Fee_MainFunction() ,是以

Fee_Rb_WorkingState_en 变化做状态机跳转。暂时调试一下读。

FEE_RB_IDLE_E

承接NvM_Mainction()中的NVM_PRV_ACTIVITY_JOB_START,可以看到是在那边修改了Fee_OrderFifo_st 中 FEE_NVM_JOB 的 Mode_en 为 Fee_Read_Order。


那么FEE_Mainfunction(),在执行查找 Fee_SearchNextOrder() 之后,就执行 Fee_LoadNextOrder(),

去修改 Fee_Rb_WorkingState_en 为 Read(FEE_RB_READ_MODE_E)。

FEE_RB_READ_MODE_E

执行函数:


CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

备注有误哦。


这个函数里面每个调用的子函数都有自己单独的状态机,调试的相当头疼,不过结束再回来看,发现其实状态机的跳转过程全部是一样的,大致如下:

Init→Search→Read→Read_Wait→Finished→Init。

在读到数据之后,则调用 Fee_UpdateStatus() 修改状态机,结束当前block读取,进入下一个block或者全部读取完退出while循环。


这边还是把函数单列一个章节去讲吧,内容其实还是比较多的(调试了有点久)。

Fee_HLReadBlock()

基于Fee_RdWrOrder_st.Fee_HLRdBlock的状态机跳转。

FEE_HL_RDWR_BLK_INIT_E

获取Fee_OrderFifo_st中当前需要读的block的PersistentId、lenth和Flags,赋值给Fee_GlobInfoLastRdHeader_st。然后设置Fee_RdWrOrder_st.Fee_HLRdBlock状态为 

FEE_HL_SEARCH_BLK_HDR_E 去查找block的头。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

FEE_HL_SEARCH_BLK_HDR_E

循环执行 函数 Fee_LLSearchSpecifiedBlkHeader(),它也有自己的状态机跳转

Fee_RdWrOrder_st.Fee_LLSearchBlkHdr_en,断点打上调就完了!


1.Fee_RdWrOrder_st.Fee_LLSearchBlkHdr_en

执行Fee_GetMostCurrentSectorIdx()获取当前的使用物理块,

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

然后调用 Fee_LLGetAddressFromCache,获取block的地址给到结构体Fee_RdWrOrder_st。

其中,Fee_Cache_au32就是 fee block的存储位置,在Fee_Init()时就已经设置好了。

如果我们曾经写过某个block,那么cache中存储的即为具体位置,如果没有写过,那么会有一个无效的地址 

FEE_CACHE_TMP_INVALID_VALUE (0xCAFEAFFE)。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

2.FEE_LL_SEARCHBLK_BLK_HEADER_E

执行Fee_LLSearchNextBlkHeader(),它的状态机是

Fee_RdWrOrder_st.Fee_LLRdState_en。

简单总结就是确定读取位置,polling模式读取,读取完成后校验内容。

如果是无效地址,则返回Fee_ERROR_E,初始化

Fee_RdWrOrder_st.Fee_LLRdState_en;

如果是有效地址,则在 Fee_LLSearchNextBlkHeader函数中会进一步调用

Fls_Read()。

不过这个函数也不是读函数,仅是在dflash not busy时,将地址偏移0xAF00 0000(使用的tc3**)后给到Fls_CinfigPtr。

之后调用 Fls_17_Dmu_MainFunction,到最底层的Fls_lMainRead,才是读函数。

首先确认 寄存器状态 HF_ERRSR、HF_ECCC,然后将ReadAddress的内容读到ReadBufferPtr的位置。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

读完之后置位FlsJobResult为  MEMIF_JOB_OK,

那么Fee_CheckFlsJobResult 函数就会去设置

Fee_RdWrOrder_st.Fee_LLRdState_en 为 FEE_LL_READ_FINISHED_E。


3.FEE_LL_READ_FINISHED_E

然后校验数据的头(0xA53C96)和 CRC16,之后

函数Fee_LLSearchSpecifiedBlkHeader返回FEE_ORDER_FINISHED_E,结束读命令。

设置Fee_RdWrOrder_st.Fee_HLRdBlock = FEE_HL_CHECK_BLK_CS_E。

block header结构体如下:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

FEE_HL_CHECK_BLK_CS_E

执行 函数 Fee_LLCalcBlkCrcInFlash,它也有状态机

Fee_RdWrOrder_st.Fee_LLCalcCrcBlk_en,其中又是去Dflash中读取数据,不过这次读的是Crc32部分的内容并校验。

FEE_HL_RD_DATA_FROM_BLK_E

执行 Fee_LLCopyData2Buffer(),终于到了给咱们设置的Ram buffer赋值的地方了,最终将函数读取完毕。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

整个流程Fee的读取流程如下:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

NvM_Write概述

NvM的写入过程,即是上层通过调用NvM接口改变队列信息,之后

NvM_MainFunction()再去逐级往下调用Fee_MainFunction(),最终将源Ram数据拷贝到目标Dflash地址中去。

流程图如下:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

再附一个write过程的最底层接口调试图:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

接下来就是具体的写入过程。

关键变量

UDS中某个2E服务DID:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

NvM的请求队列:

NvM_Prv_Queue_ast[]

Fee的请求队列:

Fee_OrderFifo_st[]

Fee存储 读、写的状态机和其他信息:

Fee_RdWrOrder_st

Fee的工作状态机:

Fee_Rb_WorkingState_en

Fee上次读指令存储的信息:

Fee_GlobInfoLastRdHeader_st

写入的存储临时buffer:

Fee_llPageBuf_au32


写入过程

上层请求逻辑

其实应用层调用写入接口有2种,一种是仅请求状态,之后统一时间段存储(可以是休眠前):

NvM_SetRamBlockStatus(NvM_BlockIdType BlockId, boolean BlockChanged),

另一种则是目前项目使用的立即触发存储:

NvM_WriteBlock(NvM_BlockIdType BlockId, const void*NvM_SrcPtr)

从函数内容来看,基本上只是请求的类型不同。


函数会首先判断下请求的NvM block的lenth和buffer地址的有效性,之后调用NvM_Prv_Queue_EnqueueRequest(idQueue_uo,&BlockData_pcst>QueueEntry_st);

对队列进行赋值操作,如下图:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

这边还能看到一个细节信息,队列的最大值size_cu16为50,其实是与isolar中的配置是一致的。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

NvM_MainFunction()

和Read一样,以 NvM_Prv_Main_st.Activity_rAMwM_en为状态机跳转。


首先,NvM_Prv_MainFunctionArbitrate() 函数将 队列

NvM_Prv_Queues_ast的内容拷贝到NvM_Prv_Main_st 中,之后设置

JobData_st 。

经过函数 NvM_Prv_JobStart_Write()之后,再看下属性:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

这边会调用 Fee_Write()设置 Fee_OrderFifo_st[FEE_NVM_JOB],

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

那么之后的NvM_Prv_MainFunctionPollResult(), 只需要等待Fee的返回结果就可以了。

最后再执行 NvM_Prv_MainFunctionJobComplete() 、

NvM_Prv_MainFunctionResultEval()清空状态,完成本次写操作。

rba_FeeFs1_MainFunction()

NvM在改变Fee_orderFifo_st之后 ,FEE_mainfunction()就要出场工作了。

主函数以  为状态机工作。

Fee通过执行 Fee_SearchNextOrder ()和Fee_LoadNextOrder()来查找order和Fee block的属性。


之后,调用函数 Fee_HLWriteBlock(),

(状态机Fee_RdWrOrder_st.Fee_HLWrBlock_en)。


写之前先要读,把当前dflash中最新的一块数据读出来,通过

Fee_LLSearchSpecifiedBlkHeader() ,流程跟readall中一致。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

然后计算当前待存储数据的Crc,

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

如果crc和取读的内容一致,那么其实就不需要写入,直接返回上一级即可,如果需要写入那么就开始填充。这里可以看到除了CRC,数据段也填充了2位,这就是整个头了。

再附一下header的结构体:

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

再往底层去,调用

Fee_LLWriteBlock(&Fee_GlobInfoLastRdHeader_st,
Fee_OrderFifo_st[Fee_idxActQueue_u8].DataBufferPtr_pu8);

(状态机:Fee_RdWrOrder_st.Fee_LLWrBlock_en)

计算存储所用空间,空间不够会触发erase的逻辑,下一篇做压力测试的时候再来调试。

如果空间是够用的,则调用 做真正的写动作,这里是先写了8个字节(1个page)。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

整个Fls写过程的调试过程如下,

FEE_LL_WR_WRITEHEADER_E

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

FEE_LL_WR_WRITEDATA_SEC_A_E

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

FEE_LL_WR_WAIT_WRITEHDRPG2_E

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区

最后的最后,清空状态,结束“写”动作。

CP Autosar - 万字长文调试NvM-鸿蒙开发者社区




文章转载自公众号:汽车与基础软件

分类
标签
已于2023-11-13 11:45:41修改
收藏
回复
举报
回复
    相关推荐