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
43/*
44 * Pre-calculated 256-way 1 byte column parity
45 */
46static const u_char
nand_ecc_precalc_table[] = {
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位二進制數每位的含義:

這個表的意思是:對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.
/* Build up column parity */
for(i = 0; i < 256; i++) {
/* Get CP0 - CP5 from table */
idx = nand_ecc_precalc_table[*dat++];
reg1 ^= (idx & 0x3f);
//這里省略了一些,后面會介紹
}
reg1

在這里,計算列極性的過程其實是先在一個字節數據的內部計算CP0 ~ CP5, 每個字節都計算完后再與其它字節的計算結果求異或。而表1中是先對一列Bit0求異或,再去異或一列Bit2。 這兩種只是計算順序不同,結果是一致的。 因為異或運算的順序是可交換的。
行極性的計算要復雜一些。
nand_ecc_precalc_table[] 表中的 Bit6 已經保存了每個單字節數的行極性值。對於待校驗的256字節數據,分別查表,如果其行極性為1,則記錄該數據所在的行索引(也就是for循環的i值),這里的行索引是很重要的,因為RP0 ~ RP15 的計算都是跟行索引緊密相關的,如RP0只計算偶數行,RP1只計算奇數行,等等。
當往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)。
參考博客的最后給舉出了一個示例,可以幫助理解。
