NandFlash ECC 校驗算法原理與實現

    ECC的全稱是Error Checking and Correction,是一種用於Nand的差錯檢測和修正算法。如果操作時序和電路穩定性不存在問題的話,NAND Flash出錯的時候一般不會造成整個Block或是Page不能讀取或是全部出錯,而是整個Page(例如512Bytes)中只有一個或幾個bit出錯。ECC能糾正1個比特錯誤和檢測2個比特錯誤,而且計算速度很快,但對1比特以上的錯誤無法糾正,對2比特以上的錯誤不保證能檢測。

校驗碼生成算法

ECC校驗每次對256字節的數據進行操作,包含列校驗和行校驗。對每個待校驗的Bit位求異或,若結果爲0,則表明含有偶數個1;若結果爲1,則表明含有奇數個1。列校驗規則如表1所示。256字節數據形成256行、8列的矩陣,矩陣每個元素表示一個Bit位。


    其中CP0 ~ CP5 爲六個Bit位,表示Column Parity(列極性),
    CP0爲第0、2、4、6列的極性,CP1爲第1、3、5、7列的極性,
    CP2爲第0、1、4、5列的極性,CP3爲第2、3、6、7列的極性,
    CP4爲第0、1、2、3列的極性,CP5爲第4、5、6、7列的極性。
    用公式表示就是:CP0=Bit0^Bit2^Bit4^Bit6, 表示第0列內部256個Bit位異或之後再跟第2列256個Bit位異或,再跟第4列、第6列的每個Bit位異或,這樣,CP0其實是256*4=1024個Bit位異或的結果。CP1 ~ CP5 依此類推。

    行校驗如下圖所示


    其中RP0 ~ RP15 爲十六個Bit位,表示Row Parity(行極性),

    RP0爲第0、2、4、6、….252、254 個字節的極性

    RP1-----1、3、5、7……253、255

    RP2----0、1、4、5、8、9…..252、253 (處理2個Byte,跳過2個Byte)

    RP3---- 2、3、6、7、10、11…..254、255 (跳過2個Byte,處理2個Byte)

    RP4---- 處理4個Byte,跳過4個Byte;

    RP5---- 跳過4個Byte,處理4個Byte;

    RP6---- 處理8個Byte,跳過8個Byte

    RP7---- 跳過8個Byte,處理8個Byte;

    RP8---- 處理16個Byte,跳過16個Byte;

    RP9---- 跳過16個Byte,處理16個Byte;

    RP10----處理32個Byte,跳過32個Byte;

    RP11----跳過32個Byte,處理32個Byte;

    RP12----處理64個Byte,跳過64個Byte;

    RP13----跳過64個Byte,處理64個Byte;

    RP14----處理128個Byte,跳過128個Byte;

    RP15----跳過128個Byte,處理128個Byte;

    可見,RP0 ~ RP15 每個Bit位都是128個字節(也就是128行)即128*8=1024個Bit位求異或的結果。

    綜上所述,對256字節的數據共生成了6個Bit的列校驗結果,16個Bit的行校驗結果,共22個Bit。在Nand中使用3個字節存放校驗結果,多餘的兩個Bit位置1。存放次序如下表所示:


    以K9F1208爲例,每個Page頁包含512字節的數據區和16字節的OOB區。前256字節數據生成3字節ECC校驗碼,後256字節數據生成3字節ECC校驗碼,共6字節ECC校驗碼存放在OOB區中,存放的位置爲OOB區的第0、1、2和3、6、7字節。

校驗碼生成算法的C語言實現

    在Linux內核中ECC校驗算法所在的文件爲drivers/mtd/nand/nand_ecc.c,其實現有新、舊兩種,在2.6.27及更早的內核中使用的程序,從2.6.28開始已經不再使用,而換成了效率更高的程序。可以在Documentation/mtd/nand_ecc.txt 文件中找到對新程序的詳細介紹。首先分析一下2.6.27內核中的ECC實現,源代碼見:http://lxr.linux.no/linux+v2.6.27/drivers/mtd/nand/nand_ecc.c

/*
* Pre-calculated 256-way 1 byte column parity
*/

static const u_char
nand_ecc_precalc_table[] = {
   0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
   0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
   0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
   0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
   0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
   0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
   0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
   0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
   0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
   0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
   0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
   0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
   0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
   0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
   0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
   0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00
};

    爲了加快計算速度,程序中使用了一個預先計算好的列極性表。這個表中每一個元素都是unsigned char類型,表示8位二進制數。
    表中8位二進制數每位的含義:


      這個表的意思是:對0~255這256個數,計算並存儲每個數的列校驗值和行校驗值,以數作數組下標。比如 nand_ecc_precalc_table[ 13 ]  存儲13的列校驗值和行校驗值,13的二進制表示爲 00001101, 其CP0 = Bit0^Bit2^Bit4^Bit6 = 0;

    CP1 = Bit1^Bit3^Bit5^Bit7 = 1;

    CP2 = Bit0^Bit1^Bit4^Bit5 = 1;

    CP3 = Bit2^Bit3^Bit6^Bit7 = 0;

    CP4 = Bit0^Bit1^Bit2^Bit3 = 1;

    CP5 = Bit4^Bit5^Bit6^Bit7 = 0;

    其行極性RP = Bit0^Bit1^Bit2^Bit3^Bit4^Bit5^Bit6^Bit7 = 1;

    則nand_ecc_precalc_table[ 13 ] 處存儲的值應該是 0101 0110,即0x56.

    注意,數組nand_ecc_precalc_table的下標其實是我們要校驗的一個字節數據。

    理解了這個表的含義,也就很容易寫個程序生成這個表了。程序見附件中的 MakeEccTable.c文件。

    有了這個表,對單字節數據dat,可以直接查表 nand_ecc_precalc_table[ dat ] 得到 dat的行校驗值和列校驗值。 但是ECC實際要校驗的是256字節的數據,需要進行256次查表,對得到的256個查表結果進行按位異或,最終結果的 Bit0 ~ Bit5 即是256字節數據的 CP0 ~ CP5.


    在這裏,計算列極性的過程其實是先在一個字節數據的內部計算CP0 ~ CP5, 每個字節都計算完後再與其它字節的計算結果求異或。而表1中是先對一列Bit0求異或,再去異或一列Bit2。 這兩種只是計算順序不同,結果是一致的。 因爲異或運算的順序是可交換的。

    行極性的計算要複雜一些。

    nand_ecc_precalc_table[] 表中的 Bit6 已經保存了每個單字節數的行極性值。對於待校驗的256字節數據,分別查表,如果其行極性爲1,則記錄該數據所在的行索引(也就是for循環的i值),這裏的行索引是很重要的,因爲RP0 ~ RP15 的計算都是跟行索引緊密相關的,如RP0只計算偶數行,RP1只計算奇數行,等等。

/* Build up column parity */
    for(i = 0; i < 256; i++) {
/* Get CP0 - CP5 from table */
        idx = nand_ecc_precalc_table[*dat++];
        reg1 ^= (idx & 0x3f);
/* All bit XOR = 1 ? */
        if (idx & 0x40) {
            reg3 ^= (uint8_t) i;
            reg2 ^= ~((uint8_t) i);
        }
}

    這裏的關鍵是理解第88和89行。Reg3和reg2都是unsigned char 型的變量,並都初始化爲零。

    行索引(也就是for循環裏的i)的取值範圍爲0~255,根據表2可以得出以下規律:

    RP0只計算行索引的Bit0爲0的行,RP1只計算行索引的Bit0爲1的行;

    RP2只計算行索引的Bit1爲0的行,RP3只計算行索引的Bit1爲1的行;

    RP4只計算行索引的Bit2爲0的行,RP5只計算行索引的Bit2爲1的行;

    RP6只計算行索引的Bit3爲0的行,RP7只計算行索引的Bit3爲1的行;

    RP8只計算行索引的Bit4爲0的行,RP9只計算行索引的Bit4爲1的行;

    RP10只計算行索引的Bit5爲0的行,RP11只計算行索引的Bit5爲1的行;

    RP12只計算行索引的Bit6爲0的行,RP13只計算行索引的Bit6爲1的行;

    RP14只計算行索引的Bit7爲0的行,RP15只計算行索引的Bit7爲1的行;

    已經知道,異或運算的作用是判斷比特位爲1的個數,跟比特位爲0的個數沒有關係。如果有偶數個1則異或的結果爲0,如果有奇數個1則異或的結果爲1。

    那麼,程序第88行,對所有行校驗爲1的行索引按位異或運算,作用便是:

    判斷在所有行校驗爲1的行中,

    屬於RP1計算範圍內的行有多少個------由reg3的Bit 0指示,0表示有偶數個,1表示有奇數個;

    屬於RP3計算範圍內的行有多少個------由reg3的Bit 1指示,0表示有偶數個,1表示有奇數個;

    屬於RP5計算範圍內的行有多少個------由reg3的Bit 2指示,0表示有偶數個,1表示有奇數個;

    屬於RP7計算範圍內的行有多少個------由reg3的Bit 3指示,0表示有偶數個,1表示有奇數個;

    屬於RP9計算範圍內的行有多少個------由reg3的Bit 4指示,0表示有偶數個,1表示有奇數個;

    屬於RP11計算範圍內的行有多少個------由reg3的Bit 5指示,0表示有偶數個,1表示有奇數個;

    屬於RP13計算範圍內的行有多少個------由reg3的Bit 6指示,0表示有偶數個,1表示有奇數個

    屬於RP15計算範圍內的行有多少個------由reg3的Bit 7指示,0表示有偶數個,1表示有奇數個;

   所以,reg3每個Bit位的作用如下表所示:

Reg3


    第89行,對所有行校驗爲1的行索引按位取反之後,再按位異或,作用就是判斷比特位爲0的個數。比如reg2的Bit0爲0表示:所有行校驗爲1的行中,行索引的Bit0爲0的行有偶數個,也就是落在RP0計算範圍內的行有偶數個。所以得到結論:

    在所有行校驗爲1的行中,

    屬於RP0計算範圍內的行有多少個------由reg2的Bit 0指示,0表示有偶數個,1表示有奇數個;

    屬於RP2計算範圍內的行有多少個------由reg2的Bit 1指示,0表示有偶數個,1表示有奇數個;

    屬於RP4計算範圍內的行有多少個------由reg2的Bit 2指示,0表示有偶數個,1表示有奇數個;

    屬於RP6計算範圍內的行有多少個------由reg2的Bit 3指示,0表示有偶數個,1表示有奇數個;

    屬於RP8計算範圍內的行有多少個------由reg2的Bit 4指示,0表示有偶數個,1表示有奇數個;

    屬於RP10計算範圍內的行有多少個------由reg2的Bit 5指示,0表示有偶數個,1表示有奇數個;

    屬於RP12計算範圍內的行有多少個------由reg2的Bit 6指示,0表示有偶數個,1表示有奇數個;

    屬於RP14計算範圍內的行有多少個------由reg2的Bit 7指示,0表示有偶數個,1表示有奇數個;

    所以,reg2每個Bit位的作用如下表所示:

Reg2


    至此,只用了一個查找表和一個for循環,就把所有的校驗位CP0 ~ CP5 和RP0 ~ RP15全都計算出來了。下面的任務只是按照表3的格式,把這些比特位重新排列一下順序而已。

    從reg2和reg3中抽取出 RP8~RP15放在tmp1中,抽取出RP0~RP7放在tmp2中,Reg1左移兩位,低兩位置1,然後把tmp2, tmp1, reg1 放在 ECC碼的三個字節中。程序中還有CONFIG_MTD_NAND_ECC_SMC, 又進行了一次取反操作,暫時還不知爲何。

ECC糾錯算法

    當往NAND Flash的page中寫入數據的時候,每256字節我們生成一個ECC校驗和,稱之爲原ECC校驗和,保存到PAGE的OOB(out-of-band)數據區中。當從NAND Flash中讀取數據的時候,每256字節我們生成一個ECC校驗和,稱之爲新ECC校驗和。

    將從OOB區中讀出的原ECC校驗和新ECC校驗和按位異或,若結果爲0,則表示不存在錯(或是出現了 ECC無法檢測的錯誤);若3個字節異或結果中存在11個比特位爲1,表示存在一個比特錯誤,且可糾正;若3個字節異或結果中只存在1個比特位爲1,表示 OOB區出錯;其他情況均表示出現了無法糾正的錯誤。

    假設ecc_code_raw[3] 保存原始的ECC校驗碼,ecc_code_new[3] 保存新計算出的ECC校驗碼,其格式如下表所示:


    對ecc_code_raw[3] 和 ecc_code_new[3] 按位異或,得到的結果三個字節分別保存在s0,s1,s2中,如果s0s1s2中共有11個Bit位爲1,則表示出現了一個比特位錯誤,可以修正。定位出錯的比特位的方法是,先確定行地址(即哪個字節出錯),再確定列地址(即該字節中的哪一個Bit位出錯)。

    確定行地址的方法是,設行地址爲unsigned char byteoffs,抽取s1中的Bit7,Bit5,Bit3,Bit1,作爲 byteoffs的高四位, 抽取s0中的Bit7,Bit5,Bit3,Bit1 作爲byteoffs的低四位, 則byteoffs的值就表示出錯字節的行地址(範圍爲0 ~ 255)。

    確定列地址的方法是:抽取s2中的Bit7,Bit5,Bit3 作爲 bitnum 的低三位,bitnum其餘位置0,則bitnum的表示出錯Bit位的列地址 (範圍爲0 ~ 7)。

    下面以一個簡單的例子探索一下這其中的奧妙。

    假設待校驗的數據爲兩個字節,0x45(二進制爲0100 0101)和0x38(二進制爲0011 1000),其行列校驗碼如下表所示:


    從表中可以計算出CP5 ~ CP0的值,列在下表的第一行(原始數據)。假設現在有一個數據位發生變化,0x38變爲0x3A,也就是Byte 1的Bit 1由0變成了1,計算得到新的CP5 ~ CP0值放在下表第2行(變化後數據)。新舊校驗碼求異或的結果放在下表第三行。可見,當 Bit 1發生變化時,列校驗值中只有CP1,CP2,CP4發生了變化,而CP0,CP3,CP5沒變化,也就是說6個Bit校驗碼有一半發生變化,則求異或的結果中有一半爲1。同理,行校驗求異或的結果也有一半爲1。這就是爲什麼前面說256字節數據中的一個Bit位發生變化時,新舊22Bit校驗碼求異或的結果中會有11個Bit 位爲1。


    再來看怎麼定位出錯的Bit位。以列地址爲例,若CP5發生變化(異或後的CP5=1),則出錯處肯定在 Bit 4 ~ Bit 7中;若CP5無變化(異或後的CP5=0),則出錯處在 Bit 0 ~ Bit 3 中,這樣就篩選掉了一半的Bit位。剩下的4個Bit位中,再看CP3是否發生變化,又選出2個Bit位。剩下的2Bit位中再看CP1是否發生變化,則最終可定位1個出錯的Bit位。下面的樹形結構更清晰地展示了這個判決過程:


圖表 1  出錯Bit列地址定位的判決樹

注意:圖中的CP指的是求異或之後的結果中的CP

    爲什麼只用CP4,CP2,CP0呢?其實這裏麪包含冗餘信息,因爲CP5=1則必有CP4=0,CP5=0則必有CP4=1,也就是CP5跟CP4一定相反,同理,CP3跟CP2一定相反,CP1跟CP0一定相反。所以只需要用一半就行了。

    這樣,我們從異或結果中抽取出CP5,CP3,CP1位,便可定位出錯Bit位的列地址。比如上面的例子中CP5/CP3/CP1 = 001,表示Bit 1出錯。

    同理,行校驗RP1發生變化,抽取RP1,可知Byte 1發生變化。這樣定位出Byte 1的Bit 0出錯。

    當數據位256字節時,行校驗使用RP0 ~ RP15,抽取異或結果的RP15,RP13,RP11,RP9,RP7,RP5,RP3,RP1位便可定位出哪個Byte出錯,再用CP5,CP3,CP1定位哪個Bit出錯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章