1. 程式人生 > >NandFlash ECC 校驗演算法原理與實現

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位。

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

其中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 依此類推。

行校驗如下圖所示

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

其中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。存放次序如下表所示:

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

以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

43/*

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

45*/

46static constu_char

47   0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,

48   0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,

49   0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,

50   0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,

51   0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,

52   0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,

53   0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,

54   0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,

55   0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,

56   0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,

57   0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,

58   0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,

59   0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,

60   0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,

61   0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,

62

0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00

63};

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

表中8位二進位制數每位的含義:

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

這個表的意思是:對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的下標其實是我們要校驗的一個位元組資料。

(這句話最重要,立刻明白了怎麼回事,網上其它人寫的ECC演算法都是來回抄,好多都抄錯了,弄得我不知所云,暈頭轉向。)

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

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

/* Build up column parity */

  81        for(i= 0;i< 256;i++) {

  82

/* Get CP0 - CP5 from table */

  83

  84

reg1^= (idx& 0x3f);

  85

  86            //這裡省略了一些,後面會介紹

  91        }

Reg1

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

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

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

nand_ecc_precalc_table[] 表中的 Bit6 已經儲存了每個單位元組數的行極性值。對於待校驗的256位元組資料,分別查表,如果其行極性為1,則記錄該資料所在的行索引(也就是for迴圈的i值),這裡的行索引是很重要的,因為RP0 ~ RP15 的計算都是跟行索引緊密相關的,如RP0只計算偶數行,RP1只計算奇數行,等等。

/* Build up column parity */

  81        for(i= 0;i< 256;i++) {

  82

/* Get CP0 - CP5 from table */

  83

  84

reg1^= (idx& 0x3f);

  85

  86

/* All bit XOR = 1 ? */

  87                if (idx& 0x40) {

  88

  89

reg2^= ~((uint8_t)i);

  90                }

  91        }

這裡的關鍵是理解第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

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

第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

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

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

從reg2和reg3中抽取出 RP8~RP15放在tmp1中,抽取出RP0~RP7放在tmp2中,

Reg1左移兩位,低兩位置1,

然後把tmp2, tmp1, reg1 放在 ECC碼的三個位元組中。

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演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

對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),其行列校驗碼如下表所示:

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

從表中可以計算出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。

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

再來看怎麼定位出錯的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位。下面的樹形結構更清晰地展示了這個判決過程:

ECC演算法的詳細說明 - 還東國 - 還東國的部落格screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" border=0>

圖表 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出錯