????????最近在研究ThreadX和FileX,发现官方STM32H7 x-cube-azrtos包(目前最新版本1.1.0)有一个BUG,本文记录一下,供大家参考。
问题描述:
????????移植官方例程Fx_uSD_File_Edit后程序正常运行,但使能FX_ENABLE_EXFAT宏启用exfat文件系统支持和D-Cache数据缓存后,无法挂载exfat文件系统,返回FX_IO_ERROR,关闭D-Cache可以挂载使用exfat文件系统。
原因分析:
????????最开始调试的时候思路错了,在FileX协议库里面找了很久没有头绪,但后来一想FileX库具备各种安全认证应该不会出错,于是就从STM32官方包的移植接口中寻找BUG?。
????????首先介绍一下Cache,如果CPU要读取的SRAM内的数据在Cache 中已经加载好,这就叫读命中(Cache hit),如果Cache 里面没有怎么办,这就是读Cache Miss。如果CPU要写的SRAM区数据在Cache 中已经开辟了对应的区域,这就叫写命中,如果Cache 里面没有开辟对应的区域,这就是所谓的写Cache Miss。
????????STM32H7的Cache可以通过MPU配置成四种策略,
本文没有使用MPU所以Cache默认的策略是01即write-back,write and read allocate。
????????介绍一下什么是write-back和write allocate。如果CPU要写的SRAM区数据在Cache 中已经开辟了对应的区域,那么会写到Cache 里面,而不会立即更新SRAM;如果没有,就用到配置write allocate,CPU写到往SRAM里面的数据,会同步在Cache 里面开辟一个空间将SRAM中写入的数据加载进来,如果此时立即读此SRAM区,那么就会有很大的速度优势。
????????但如果Cache命中的情况下,此时仅Cache更新了,而SRAM没有更新,那么DMA直接从 SRAM里面读出来的就是错误的。因此如果同时使用Cache的write-back和DMA功能,需要额外的措施保持内存一致性。
????????FileX对SD卡读写接口全部位于fx_stm32_sd_driver.c中,由CubeMX自动生成,查看该文件可以发现在读写函数中已经考虑到使用Cache与DMA的情况,如下代码所示。
SCB_InvalidateDCache_by_Addr((uint32_t*)scratch, DEFAULT_SECTOR_SIZE);
SCB_InvalidateDCache_by_Addr((uint32_t*)media_ptr->fx_media_driver_buffer, num_sectors * DEFAULT_SECTOR_SIZE);
????????SCB_InvalidateDCache_by_Addr()函数用于将该地址D-Cache无效化,无效化的意思是将Cache Line 标记为无效,等同于删除操作。这样下次读写该地址数据时,D-Cache中无此数据,CPU直接对SRAM读写数据,保证CPU读取到的数据是真实的。
源码分析:
以读取函数为例:
case FX_DRIVER_READ:
{
media_ptr->fx_media_driver_status = FX_IO_ERROR;
unaligned_buffer = (UINT)(media_ptr->fx_media_driver_buffer) & 0x3;
if (sd_read_data(media_ptr, media_ptr->fx_media_driver_logical_sector + media_ptr->fx_media_hidden_sectors,
media_ptr->fx_media_driver_sectors, unaligned_buffer) == FX_SUCCESS)
{
media_ptr->fx_media_driver_status = FX_SUCCESS;
}
break;
}
????????第4行判断读取地址是否四字节对齐,因为使用MDMA读写SD卡需要地址四字节对齐,并作为参数传入sd_read_data()函数中。
static UINT sd_read_data(FX_MEDIA *media_ptr, ULONG start_sector, UINT num_sectors, UINT use_scratch_buffer)
{
...
if (use_scratch_buffer)
{
read_addr = media_ptr->fx_media_driver_buffer;
for (i = 0; i < num_sectors; i++)
{
/* Start DMA read into the scratch buffer */
status = BSP_SD_ReadBlocks_DMA(SD_INSTANCE, (uint32_t*)scratch, start_sector++, 1);
if (status != BSP_ERROR_NONE)
{
/* DMA transfer failed, release semaphore and return immediately */
tx_semaphore_put(&transfer_semaphore);
return FX_IO_ERROR;
}
/* Block while trying to get the semaphore until DMA transfer is complete */
if(tx_semaphore_get(&transfer_semaphore, DEFAULT_TIMEOUT) != TX_SUCCESS)
{
return FX_ACCESS_ERROR;
}
#if (ENABLE_CACHE_MAINTENANCE == 1)
SCB_InvalidateDCache_by_Addr((uint32_t*)scratch, DEFAULT_SECTOR_SIZE);
#endif
_fx_utility_memory_copy(scratch, read_addr, DEFAULT_SECTOR_SIZE);
read_addr += DEFAULT_SECTOR_SIZE;
}
/* Check if all sectors were read */
if (i == num_sectors)
{
status = FX_SUCCESS;
}
else
{
status = FX_BUFFER_ERROR;
}
}
else
{
status = BSP_SD_ReadBlocks_DMA(SD_INSTANCE, (uint32_t*)media_ptr->fx_media_driver_buffer, start_sector, num_sectors);
if (status != BSP_ERROR_NONE)
{
/* DMA transfer failed, release semaphore and return immediately */
tx_semaphore_put(&transfer_semaphore);
return FX_IO_ERROR;
}
/* Block while trying to get the semaphore until DMA transfer is complete */
if(tx_semaphore_get(&transfer_semaphore, DEFAULT_TIMEOUT) != TX_SUCCESS)
{
return FX_ACCESS_ERROR;
}
#if (ENABLE_CACHE_MAINTENANCE == 1)
SCB_InvalidateDCache_by_Addr((uint32_t*)media_ptr->fx_media_driver_buffer, num_sectors * DEFAULT_SECTOR_SIZE);
#endif
status = FX_SUCCESS;
}
/* Operation finished, release semaphore */
tx_semaphore_put(&transfer_semaphore);
return status;
}
? ? ? ? 第4行,若传输地址不是四字节对齐,则使用内部定义的一块32字节对齐的地址作为中转站,先用MDMA读取到数据,再将数据拷贝到用户提供地址。else分支若地址已经是四字节对齐,则直接使用用户提供的地址进行MDMA传输。
? ? ? ? 代码中tx_semaphore_get(&transfer_semaphore, DEFAULT_TIMEOUT) 这个信号量操作用于在MDMA传输过程中使该线程进入休眠,节省CPU资源,在传输完成中断中释放信号量,使该线程恢复运行。
????????然而else分支的代码存在一个bug,当用户传入的地址为四字节对齐时,进入else分支,在MDMA传输完成后,进行SCB_InvalidateDCache_by_Addr()操作,但是该函数要求传入的地址为32字节对齐,因为Cortex-M7内核的Cache line大小为八个字。
????????所以当传入的地址为4字节对齐但非32字节对齐时,SCB_InvalidateDCache_by_Addr()调用传入的参数不符合要求,D-Cache缓存清除错误,使CPU读取到缓存中错误的值。这就是BUG产生的原因。
解决方案:
????????问题找到了,修改起来就很容易了,在判断地址是否4字节对齐的地方改成判断地址是否32字节对齐,BUG圆满解决。
unaligned_buffer = (UINT)(media_ptr->fx_media_driver_buffer) & 0x1f;
|