對M4芯片的CRC模塊改造來計算標準CRC32

對M4芯片的CRC模塊改造來計算標準CRC32

【摘要】

  最近使用的M4系列芯片中,有用於計算CRC的硬件CRC模塊,這個模塊計算出的校驗和與咱們平時使用的CRC32並不相同。下文用CRC’32指代M4的硬件CRC模塊計算出的校驗和,以與標準CRC32區分。雖然咱們可使用純軟件的方法來計算CRC32,不過,既然有CRC硬件,咱們不妨試試對其進行加工,軟件硬件配合來進行計算。

1、 案例概述

  這個硬件CRC模塊有兩個問題:其一,是其計算的CRC’32,計算過程與標準的CRC32算法並不一致;其二,是傳給CRC模塊的數據,必須以32位進行分組。若分組後剩餘1~3字節,不足32位的狀況,是沒法計算的,必須補齊32位才行。但補齊以後,CRC值天然也就改變了。

2、 問題緣由分析 

  標準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
  是最糟糕的狀況,也就是模型的後三個參數所有不一樣。

3、 解決方案

  對於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的逆運算函數。兩者相比,咱們選擇前者,接下來計算那些「魔術字節」。

4、 實踐狀況

  根據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了。

5、效果評價

  在STM32F429上,使用該方法計算CRC32,與使用zlib庫中的CRC32函數相比,在小數據量時,該方法比不過zlib庫;在大數據量時,該方法快過zlib庫一點,不是快不少。
  該方法的性能問題主要集中在,逆序時使用的大量位交換操做,這一點比M4的CRYP和HASH差的地方,就在於後兩個模塊能夠定義輸入數據的數據類型,由硬件進行位的逆序。

6、推廣建議

  對M4中須要使用的圖片資源,事先計算其CRC32的值,同時用zlib庫進行壓縮,以減小程序的尺寸,下降燒寫程序時消耗的時間。程序跑起來,初始化的時候,用zlib庫解壓縮,並計算解壓後的CRC32值來比對,做爲校驗。

參考資料

  《DM00031020-Reference manual STM32F42xxx and STM32F43xxx advanced ARM-based 32-bit MCUs.pdf》
相關文章
相關標籤/搜索