对M4芯片的CRC模块改造来计算标准CRC32

对M4芯片的CRC模块改造来计算标准CRC32

【摘要】

  最近使用的M4系列芯片中,有用于计算CRC的硬件CRC模块,这个模块计算出的校验和与我们平时使用的CRC32并不相同。下文用CRC’32指代M4的硬件CRC模块计算出的校验和,以与标准CRC32区分。虽然我们可以使用纯软件的方法来计算CRC32,不过,既然有CRC硬件,我们不妨试试对其进行加工,软件硬件配合来进行计算。

一、 案例概述

  这个硬件CRC模块有两个问题:其一,是其计算的CRC’32,计算过程与标准的CRC32算法并不一致;其二,是传给CRC模块的数据,必须以32位进行分组。若分组后剩余1~3字节,不足32位的情况,是无法计算的,必须补齐32位才行。但补齐之后,CRC值自然也就改变了。

二、 问题原因分析 

  标准CRC32的模型如下:
 POLY = 0x04C11DB7, INIT_REM = 0xFFFFFFFF, FIN_XOR = 0xFFFFFFFF, REF_IN = TRUE, REF_REM = TRUE
  那么算法不一致,就一定是上述几项有不同。根据Data Sheet的描述:
    Uses CRC-32 (Ethernet) polynomial: 0x4C11DB7.
    The CRC calculator can be reset to 0xFFFF FFFF with the RESET control bit in the CRC_CR register.
  从这两点可以确定的是POLY和INIT_REM的值,剩下的三个虽然不确定,但是取值有限,可以轻易穷举得出。
  FIN_XOR的值虽然是32位的,有着丰富的可能性,但是常见取值只有两个,那就是0x000000000xFFFFFFFF。REF_IN和REF_REM都是布尔值,取值也只有TRUEFALSE。这样组合之后,只有8种可能。依次测试对照,便可以确定,其CRC’32模型如下:
 POLY = 0x04C11DB7, INIT_REM = 0xFFFFFFFF, FIN_XOR = 0x00000000, REF_IN = FALSE, REF_REM = FALSE
  是最糟糕的情况,也就是模型的后三个参数全部不同。

三、 解决方案

  对于CRC算法不一致的问题,既然已经找到了模型之间的差异,自然就可以进行改造,也就是,在输入时逆序,在输出时逆序,输出后与0xFFFFFFFF异或(也就是全取反)。
  对于剩余不足32位的情况,这是硬件设计上的问题,索性CRC算法是一个纯粹的线性算法,而且是可逆的(这里提到的可逆,不是指用校验和推算原文),因此想要破解是很容易的。破解的方式无外乎两种:其一,是在数组前补若干字节,以凑齐32位;其二,是在数组后补若干字节,以凑齐32位。
  以5字节举例说明,数组是D0 D1 D2 D3 D4,补齐的方式有如下几种:
    前补三字节:?? ?? ?? D0, D1 D2 D3 D4
    前补七字节:?? ?? ?? ??, ?? ?? ?? D0, D1 D2 D3 D4
    前补11字节或者更多:暂不考虑
    后补三字节:D0 D1 D2 D3, D4 ?? ?? ??
    后补七字节:D0 D1 D2 D3, D4 ?? ?? ??, ?? ?? ?? ??
    后补11字节或者更多:暂不考虑
  对于前补字节的方法,我们只要保证这几个字节对计算CRC没有影响便可。换句话说,就是要这几个字节的CRC’32结果是0xFFFFFFFF,这样一来,寄存器的初值是0xFFFFFFFF,处理这几个字节之后的值也是0xFFFFFFFF,可以达到伪造校验和的目的。
  对于后补字节的方法,我们只要简单的补零,并且在补零之后得到临时的CRC’32结果,然后利用CRC算法的可逆性,使用软件计算来逆向消除补零产生的影响,即可得到真正的CRC’32结果。
  对比前补字节和后补字节的方法,前者需要事先计算出要补充的字节,然后交给硬件计算就行了;后者则统一补充零字节,但需要额外编写CRC的逆运算函数。二者相比,我们选择前者,接下来计算那些“魔术字节”。

四、 实践情况

  根据CRC’32模型,编写M4特有的硬件CRC计算函数软件版如下:

<span style="font-size:14px;">unsigned long crc32_m4( const unsigned char *buf, unsigned long len )
{
    unsigned long rem = 0xFFFFFFFF;
    
    unsigned long byte_p = 0;
    int bit_p = 0;

    if (buf != 0)
    {
        for (byte_p = 0; byte_p < len; byte_p++)
        {
            for (bit_p = 7; bit_p >= 0; bit_p--)
            {
                if (((rem >> 31U) ^ (buf[byte_p] >> bit_p)) & 1U)
                {
                    rem = (rem << 1U) ^ 0x04C11DB7U;
                }
                else
                {
                    rem = rem << 1U;
                }
            }
        }
    }

    return rem;
}</span>

  CRC算法有一个很奇妙的性质,以32位的CRC来讲,那就是,当数据的位数小于等于32的时候,不同的数据得到的校验和一定不重复。1~4字节的数据,长度相同且内容不同的数据,得到的CRC32一定不同,当然CRC’32也一定不同。进一步说,对于32位的数据,其等效的32位整型值与32位CRC校验和是一一对应的,稍微花点时间就可以穷举出我们需要的“魔术字节”了。
  这样看来,前补三字节的方法很有可能不存在。实际上,对24位数据进行穷举,计算CRC’32的值,确实不存在校验和是0xFFFFFFFF的情况。

  接下来讨论前补七字节的方法。前四字节的32位进行穷举,后三字节固定为零,即要寻找

    CRC’32(?? ?? ?? ?? 00 00 00) == 0xFFFFFFFF 的情况。

<span style="font-size:14px;">#include <stdio.h>
#define LENGTH 7

int main()
{
    unsigned long i[2] = { 0, 0 };

    while (1)
    {
        if (crc32_m4 ((unsigned char *)i, LENGTH) == 0xFFFFFFFF)
        {
            printf ("ans: %08X \n", i[0]);
            break;
        }

        i[0]++;
    }

    return 0;
}</span>

  算出这七个字节是:6A A5 9E 9D 00 00 00
  将#define LENGTH 分别改成6和5,可以算出:
  前补六个字节为:97 46 CD 0A 00 00
  前补五个字节为:CC 60 21 D0 00

<span style="font-size:14px;">#include <stdint.h>
#include "stm32f4xx_crc.h"
#include "stm32f4xx_rcc.h"

uint32_t crc32_std( const uint8_t *buf, uint32_t len )
{
    uint32_t ans = 0;
    
    if (buf > 0 && buf + (len - 1) > buf)
    {
        uint32_t i = 0, v = 0;
        
        RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_CRC, ENABLE);
        CRC_ResetDR ();
        
        switch (len % 4)
        {
            while (1)
            {
                v = ((v >> 0x01) & 0x55555555) | ((v & 0x55555555) << 0x01);
                v = ((v >> 0x02) & 0x33333333) | ((v & 0x33333333) << 0x02);
                v = ((v >> 0x04) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 0x04);
                CRC->DR = v; // <-- CRC_CalcCRC v;
                
                if (i >= len)
                {
                    break;
                }
        
        case 0: // 所有的字节可以按照4字节一组构成32位的组, 故不必前补额外的字节
                v = buf[i] << 24 | buf[i + 1] << 16 | buf[i + 2] << 8 | buf[i + 3];
                i += 4;
                continue;
        
        case 1: // 因多出1个字节无法构成32位, 故前补7字节以将之补齐
                CRC->DR = 0x6AA59E9D; // <-- CRC_CalcCRC 0x6AA59E9D;
                v = buf[i];
                i += 1;
                continue;
        
        case 2: // 因多出2个字节无法构成32位, 故前补6字节以将之补齐
                CRC->DR = 0x9746CD0A; // <-- CRC_CalcCRC 0x9746CD0A;
                v = buf[i] << 8 | buf[i + 1];
                i += 2;
                continue;
        
        case 3: // 因多出3个字节无法构成32位, 故前补5字节以将之补齐
                CRC->DR = 0xCC6021D0; // <-- CRC_CalcCRC 0xCC6021D0;
                v = buf[i] << 16 | buf[i + 1] << 8 | buf[i + 2];
                i += 3;
                continue;
            }
        
        default:
            break;
        }
        
        v = CRC->DR; // <-- CRC_GetCRC;
        
        v = ((v >> 0x01) & 0x55555555) | ((v & 0x55555555) << 0x01);
        v = ((v >> 0x02) & 0x33333333) | ((v & 0x33333333) << 0x02);
        v = ((v >> 0x04) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 0x04);
        v = ((v >> 0x08) & 0x00FF00FF) | ((v & 0x00FF00FF) << 0x08);
        v = ((v >> 0x10) & 0x0000FFFF) | ((v & 0x0000FFFF) << 0x10);
        
        ans = ~v;
        
        RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_CRC, DISABLE);
    }
    
    return ans;
}</span>

  代码中的CRC->DR是硬件CRC的寄存器,向其写值便是输入数据,从之读数便是获取校验和。代码中对变量v进行的大量位运算,是为了进行高低位的逆序。虽然可以使用查找表法来进行逆序,但是,要使用查找表的话,倒不如直接用查找表来软件计算CRC了。

五、效果评价

  在STM32F429上,使用该方法计算CRC32,与使用zlib库中的CRC32函数相比,在小数据量时,该方法比不过zlib库;在大数据量时,该方法快过zlib库一点,不是快很多。
  该方法的性能问题主要集中在,逆序时使用的大量位交换操作,这一点比M4的CRYP和HASH差的地方,就在于后两个模块可以定义输入数据的数据类型,由硬件进行位的逆序。

六、推广建议

  对M4中需要使用的图片资源,事先计算其CRC32的值,同时用zlib库进行压缩,以减少程序的尺寸,降低烧写程序时消耗的时间。程序跑起来,初始化的时候,用zlib库解压缩,并计算解压后的CRC32值来比对,作为校验。

参考资料

  《DM00031020-Reference manual STM32F42xxx and STM32F43xxx advanced ARM-based 32-bit MCUs.pdf》

你可能感兴趣的:(CRC32,m4,STM32F4)