轉:http://www.cnblogs.com/elect-fans/archive/2012/05/14/2500643.htmlhtml
0.NAND的操做管理方式linux
NAND FLASH的管理方式:以三星FLASH爲例,一片Nand flash爲一個設備(device),1 (Device) = xxxx (Blocks),1 (Block) = xxxx (Pages),1(Page) =528 (Bytes) = 數據塊大小(512Bytes) + OOB 塊大小(16Bytes,除OOB第六字節外,一般至少把OOB的前3個字節存放Nand Flash硬件ECC碼)。編程
關於OOB區,是每一個Page都有的。Page大小是512字節的NAND每頁分配16字節的OOB;若是NAND物理上是2K的Page,則每一個Page分配64字節的OOB。以下圖:數據結構
以HYNIX爲例,圖中黑體的是實際探測到的NAND,是個2G bit(256M)的NAND。PgSize是2K字節,PgsPBlk表示每一個BLOCK包含64頁,那麼每一個BLOCK佔用的字節數是 64X2K=128K字節;該NAND包好2048個BLOCK,那麼能夠算出NAND佔用的字節數是2048X128K=256M,與實際相符。須要注 意的是SprSize就是OOB大小,也剛好是2K頁所用的64字節。app
1.爲何會出現壞塊
因爲NAND Flash的工藝不能保證NAND的Memory Array在其生命週期中保持性能的可靠,所以,在NAND的生產中及使用過程當中會產生壞塊。壞塊的特性是:當編程/擦除這個塊時,會形成Page Program和Block Erase操做時的錯誤,相應地反映到Status Register的相應位。less
2.壞塊的分類
整體上,壞塊能夠分爲兩大類:(1)固有壞塊:這是生產過程當中產生的壞塊,通常芯片原廠都會在出廠時都會將每一個壞塊第一個page的spare area的第6個byte標記爲不等於0xff的 值。(2)使用壞塊:這是在NAND Flash使用過程當中,若是Block Erase或者Page Program錯誤,就能夠簡單地將這個塊做爲壞塊來處理,這個時候須要把壞塊標記起來。爲了和固有壞塊信息保持一致,將新發現的壞塊的第一個page的 spare area的第6個Byte標記爲非0xff的值。electron
3.壞塊管理
根據上面的這些敘述,能夠了解NAND Flash出廠時在spare area中已經反映出了壞塊信息,所以, 若是在擦除一個塊以前,必定要先check一下第一頁的spare area的第6個byte是不是0xff,若是是就證實這是一個好塊,能夠擦除;若是是非0xff,那麼就不能擦除,以避免將壞塊標記擦掉。 固然,這樣處理可能會犯一個錯誤―――「錯殺僞壞塊」,由於在芯片操做過程當中可能因爲 電壓不穩定等偶然因素會形成NAND操做的錯誤。可是,爲了數據的可靠性及軟件設計的簡單化,仍是須要遵守這個標準。ide
能夠用BBT:bad block table,即壞塊表來進行管理。各家對nand的壞塊管理方法都有差別。好比專門用nand作存儲的,會把bbt放到block0,由於第0塊必定是好 的塊。可是若是nand自己被用來boot,那麼第0塊就要存放程序,不能放bbt了。 有的把bbt放到最後一塊,固然,這一塊堅定不能爲壞塊。 bbt的大小跟nand大小有關,nand越大,須要的bbt也就越大。函數
須要注意的是:OOB是每一個頁都有的數據,裏面存的有ECC(固然不只僅);而BBT是一個FLASH纔有一個;針對每一個BLOCK的壞塊識別則是該塊第一頁spare area的第六個字節。
4.壞塊糾正佈局
ECC: NAND Flash出錯的時候通常不會形成整個Block或是Page不能讀取或是所有出錯,而是整個Page(例如512Bytes)中只有一個或幾個bit出 錯。通常使用一種比較專用的校驗——ECC。ECC能糾正單比特錯誤和檢測雙比特錯誤,並且計算速度很快,但對1比特以上的錯誤沒法糾正,對2比特以上的 錯誤不保證能檢測。
ECC通常每256字節原始數據生成3字節ECC校驗數據,這三字節共24比特分紅兩部分:6比特的列校驗和16比特的行校驗,多餘的兩個比特置1。(512生成兩組ECC,共6字節)
當往NAND Flash的page中寫入數據的時候,每256字節咱們生成一個ECC校驗和,稱之爲原ECC校驗和,保存到PAGE的OOB (out- of-band)數據區中。其位置就是eccpos[]。校驗的時候,根據上述ECC生成原理不難推斷:將從OOB區中讀出的原ECC校驗和新ECC校驗 和按位異或,若結果爲0,則表示不存在錯(或是出現了ECC沒法檢測的錯誤);若3個字節異或結果中存在11個比特位爲1,表示存在一個比特錯誤,且可糾 正;若3個字節異或結果中只存在1個比特位爲1,表示OOB區出錯;其餘狀況均表示出現了沒法糾正的錯誤。
5.補充
(1)須要對前面因爲Page Program錯誤發現的壞塊進行一下特別說明。若是在對一個塊的某個page進行編程的時候發生了錯誤就要把這個塊標記爲壞塊,首先就要把塊裏其餘好的 面的內容備份到另一個空的好塊裏面,而後,把這個塊標記爲壞塊。固然,這可能會犯「錯殺」之誤,一個補救的辦法,就是在進行完塊備份以後,再將這個壞塊 擦除一遍,若是Block Erase發生錯誤,那就證實這個塊是個真正的壞塊,那就絕不猶豫地將它打個「戳」吧!
(2)可能有人會問,爲何要使用每一個塊第一頁的spare area的第六個byte做爲壞塊標記。這是NAND Flash生產商的默認約定,你能夠看到Samsung,Toshiba,STMicroelectronics都是使用這個Byte做爲壞塊標記的。
(3)爲何好塊用0xff來標記?由於Nand Flash的擦除便是將相應塊的位所有變爲1,寫操做時只能把芯片每一位(bit)只能從1變爲0,而不能從0變爲1。0XFF這個值就是標識擦除成功,是好塊。
bbt壞塊管理 日月 發表於 - 2010-3-2 9:59:00 2 推薦 前面看到在nand_scan()函數的最後將會跳至scan_bbt()函數,這個函數在nand_scan裏面有定義: 2415 if (!this->scan_bbt) 2416 this->scan_bbt = nand_default_bbt; nand_default_bbt()位於Nand_bbt.c文件中。 1047 /** * nand_default_bbt - [NAND Interface] Select a default bad block table for the device * @mtd: MTD device structure * * This selects the default bad block table * support for the device and calls the nand_scan_bbt **/ int nand_default_bbt (struct mtd_info *mtd) { struct nand_chip *this = mtd->priv; 這個函數的做用是創建默認的壞塊表。 1059 /* Default for AG-AND. We must use a flash based * bad block table as the devices have factory marked * _good_ blocks. Erasing those blocks leads to loss * of the good / bad information, so we _must_ store * this information in a good / bad table during * startup */ if (this->options & NAND_IS_AND) { /* Use the default pattern deors */ if (!this->bbt_td) { this->bbt_td = &bbt_main_descr; this->bbt_md = &bbt_mirror_descr; } this->options |= NAND_USE_FLASH_BBT; return nand_scan_bbt (mtd, &agand_flashbased); } 若是Flash的類型是AG-AND(這種Flash類型比較特殊,既不是MLC又不是SLC,所以不去深究了,並且好像瑞薩要把它淘汰掉),須要使用默認的模式描述符,最後再進入nand_scan_bbt()函數。 1078 /* Is a flash based bad block table requested ? */ if (this->options & NAND_USE_FLASH_BBT) { /* Use the default pattern deors */ if (!this->bbt_td) { this->bbt_td = &bbt_main_descr; this->bbt_md = &bbt_mirror_descr; } if (!this->badblock_pattern) { this->badblock_pattern = (mtd->oobblock > 512) ? &largepage_flashbased : &smallpage_flashbased; } } else { this->bbt_td = NULL; this->bbt_md = NULL; if (!this->badblock_pattern) { this->badblock_pattern = (mtd->oobblock > 512) ? &largepage_memorybased : &smallpage_memorybased; } } return nand_scan_bbt (mtd, this->badblock_pattern); 若是Flash芯片須要使用壞塊表,對於1208芯片來講是使用smallpage_memorybased。 985 static struct nand_bbt_descr smallpage_memorybased = { .options = NAND_BBT_SCAN2NDPAGE, .offs = 5, .len = 1, .pattern = scan_ff_pattern }; 暫時沒看到如何使用這些賦值,先放着。後面檢測壞塊時用得着。 1099 return nand_scan_bbt (mtd, this->badblock_pattern); 最後將badblock_pattern做爲參數,調用nand_can_bbt函數。 844 /** * nand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s) * @mtd: MTD device structure * @bd: deor for the good/bad block search pattern * * The checks, if a bad block table(s) is/are already * available. If not it scans the device for manufacturer * marked good / bad blocks and writes the bad block table(s) to * the selected place. * * The bad block table memory is allocated here. It must be freed * by calling the nand_free_bbt . * */ int nand_scan_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd) { 檢測、尋找、讀取甚至創建壞塊表。函數檢測是否已經存在一張壞塊表,不然創建一張。壞塊表的內存分配也在這個函數中。 860 struct nand_chip *this = mtd->priv; int len, res = 0; uint8_t *buf; struct nand_bbt_descr *td = this->bbt_td; struct nand_bbt_descr *md = this->bbt_md; len = mtd->size >> (this->bbt_erase_shift + 2); /* Allocate memory (2bit per block) */ this->bbt = kmalloc (len, GFP_KERNEL); if (!this->bbt) { printk (KERN_ERR "nand_scan_bbt: Out of memory/n"); return -ENOMEM; } /* Clear the memory bad block table */ memset (this->bbt, 0x00, len); 一些賦值、變量聲明、內存分配,每一個block分配2bit的空間。1208有4096個block,應該分配4096*2bit的空間。 877 /* If no primary table decriptor is given, scan the device * to build a memory based bad block table */ if (!td) { if ((res = nand_memory_bbt(mtd, bd))) { printk (KERN_ERR "nand_bbt: Can't scan flash and build the RAM-based BBT/n"); kfree (this->bbt); this->bbt = NULL; } return res; } 若是沒有提供ptd,就掃描設備並創建一張。這裏調用了nand_memory_bbt()這個內聯函數。 653 /** * nand_memory_bbt - [GENERIC] create a memory based bad block table * @mtd: MTD device structure * @bd: deor for the good/bad block search pattern * * The creates a memory based bbt by scanning the device * for manufacturer / software marked good / bad blocks */ static inline int nand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd) { struct nand_chip *this = mtd->priv; bd->options &= ~NAND_BBT_SCANEMPTY; return create_bbt (mtd, this->data_buf, bd, -1); } 函數的做用是創建一張基於memory的壞塊表。 將操做符的NAND_BBT_SCANEMPTY清除,並繼續調用creat_bbt()函數。 271 /** * create_bbt - [GENERIC] Create a bad block table by scanning the device * @mtd: MTD device structure * @buf: temporary buffer * @bd: deor for the good/bad block search pattern * @chip: create the table for a specific chip, -1 read all chips. * Applies only if NAND_BBT_PERCHIP option is set * * Create a bad block table by scanning the device * for the given good/bad block identify pattern */ static int create_bbt (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip) { 真正的創建壞塊表函數。chip參數是-1表示讀取全部的芯片。 284 struct nand_chip *this = mtd->priv; int i, j, numblocks, len, scanlen; int startblock; loff_t from; size_t readlen, ooblen; printk (KERN_INFO "Scanning device for bad blocks/n"); 一些變量聲明,開機時那句話就是在這兒打印出來的。 292 if (bd->options & NAND_BBT_SCANALLPAGES) len = 1 << (this->bbt_erase_shift - this->page_shift); else { if (bd->options & NAND_BBT_SCAN2NDPAGE) len = 2; else len = 1; } 在前面咱們定義了smallpage_memorybased這個結構體,如今裏面NAND_BBT_SCANALLPAGES的終於用上了,對於1208芯片來講,len=2。 304 if (!(bd->options & NAND_BBT_SCANEMPTY)) { /* We need only read few bytes from the OOB area */ scanlen = ooblen = 0; readlen = bd->len; } else { /* Full page content should be read */ scanlen = mtd->oobblock + mtd->oobsize; readlen = len * mtd->oobblock; ooblen = len * mtd->oobsize; } 前面已經將NAND_BBT_SCANEMPTY清除了,這裏確定執行else的內容。須要將一頁內容都讀取出來。 316 if (chip == -1) { /* Note that numblocks is 2 * (real numblocks) here, see i+=2 below as it * makes shifting and masking less painful */ numblocks = mtd->size >> (this->bbt_erase_shift - 1); startblock = 0; from = 0; } else { if (chip >= this->numchips) { printk (KERN_WARNING "create_bbt(): chipnr (%d) > available chips (%d)/n", chip + 1, this->numchips); return -EINVAL; } numblocks = this->chipsize >> (this->bbt_erase_shift - 1); startblock = chip * numblocks; numblocks += startblock; from = startblock << (this->bbt_erase_shift - 1); } 前面提到chip爲-1,實際上咱們只有一顆芯片,numblocks這兒是4096*2。 335 for (i = startblock; i < numblocks;) { int ret; if (bd->options & NAND_BBT_SCANEMPTY) if ((ret = nand_read_raw (mtd, buf, from, readlen, ooblen))) return ret; for (j = 0; j < len; j++) { if (!(bd->options & NAND_BBT_SCANEMPTY)) { size_t retlen; /* Read the full oob until read_oob is fixed to * handle single byte reads for 16 bit buswidth */ ret = mtd->read_oob(mtd, from + j * mtd->oobblock, mtd->oobsize, &retlen, buf); if (ret) return ret; if (check_short_pattern (buf, bd)) { this->bbt[i >> 3] |= 0x03 << (i & 0x6); printk (KERN_WARNING "Bad eraseblock %d at 0x%08x/n", i >> 1, (unsigned int) from); break; } } else { if (check_pattern (&buf[j * scanlen], scanlen, mtd->oobblock, bd)) { this->bbt[i >> 3] |= 0x03 << (i & 0x6); printk (KERN_WARNING "Bad eraseblock %d at 0x%08x/n", i >> 1, (unsigned int) from); break; } } } i += 2; from += (1 << this->bbt_erase_shift); } return 0; 檢測這4096個block,剛開始的nand_read_raw確定不會執行。len是2,在j循環要循環2次。 每次循環真正要作的事情是下面的內容: ret = mtd->read_oob(mtd, from + j * mtd->oobblock, mtd->oobsize, &retlen, buf); read_oob()函數在nand_scan()裏被指向nand_read_oob(),這個函數在Nand_base.c文件中,看來得回Nand_base.c看看了。 1397 /** * nand_read_oob - [MTD Interface] NAND read out-of-band * @mtd: MTD device structure * @from: offset to read from * @len: number of bytes to read * @retlen: pointer to variable to store the number of read bytes * @buf: the databuffer to put data * * NAND read out-of-band data from the spare area */ static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf) { 才發現oob全稱是out-of-band, from是偏移量,len是讀取的長度,retlen是存儲指針。 1409 int i, col, page, chipnr; struct nand_chip *this = mtd->priv; int blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1; DEBUG (MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08x, len = %i/n", (unsigned int) from, (int) len); /* Shift to get page */ page = (int)(from >> this->page_shift); chipnr = (int)(from >> this->chip_shift); /* Mask to get column */ col = from & (mtd->oobsize - 1); /* Initialize return length value */ *retlen = 0; 一些初始化,blockcheck對於1208應該是(1<<(0xe-0x9)-1)=31。而後經過偏移量計算出要讀取oob區的page,chipnr和col。 1425 /* Do not allow reads past end of device */ if ((from + len) > mtd->size) { DEBUG (MTD_DEBUG_LEVEL0, "nand_read_oob: Attempt read beyond end of device/n"); *retlen = 0; return -EINVAL; } /* Grab the lock and see if the device is available */ nand_get_device (this, mtd , FL_READING); /* Select the NAND device */ this->select_chip(mtd, chipnr); /* Send the read command */ this->cmdfunc (mtd, NAND_CMD_READOOB, col, page & this->pagemask); 不容許非法的讀取,獲取芯片控制權,發送讀取OOB命令,這兒會調用具體硬件驅動中相關的Nand控制函數。 1442 /* * Read the data, if we read more than one page * oob data, let the device transfer the data ! */ i = 0; while (i < len) { int thislen = mtd->oobsize - col; thislen = min_t(int, thislen, len); this->read_buf(mtd, &buf[i], thislen); i += thislen; /* Read more ? */ if (i < len) { page++; col = 0; /* Check, if we cross a chip boundary */ if (!(page & this->pagemask)) { chipnr++; this->select_chip(mtd, -1); this->select_chip(mtd, chipnr); } /* Apply delay or wait for ready/busy pin * Do this before the AUTOINCR check, so no problems * arise if a chip which does auto increment * is marked as NOAUTOINCR by the board driver. */ if (!this->dev_ready) udelay (this->chip_delay); else nand_wait_ready(mtd); /* Check, if the chip supports auto page increment * or if we have hit a block boundary. */ if (!NAND_CANAUTOINCR(this) || !(page & blockcheck)) { /* For subsequent page reads set offset to 0 */ this->cmdfunc (mtd, NAND_CMD_READOOB, 0x0, page & this->pagemask); } } } /* Deselect and wake up anyone waiting on the device */ nand_release_device(mtd); /* Return happy */ *retlen = len; return 0; 開始讀取數據,while循環只要獲取到oob區大小的數據便可。注意,read_buf纔是最底層的讀寫Nand的函數,在咱們的驅動中根據參數能夠實現讀取528byte所有內容,或者16byte的oob區。 若是一次沒讀完,就要繼續再讀,根據咱們實際使用經驗好像沒出現過這種問題。 最後Return Happy~回到Nand_bbt.c的creat_bbt()函數,348行,好像都快忘記咱們還沒出creat_bbt()函數呢,我再把他貼一遍吧: 346 /* Read the full oob until read_oob is fixed to * handle single byte reads for 16 bit buswidth */ ret = mtd->read_oob(mtd, from + j * mtd->oobblock, mtd->oobsize, &retlen, buf); if (ret) return ret; if (check_short_pattern (buf, bd)) { this->bbt[i >> 3] |= 0x03 << (i & 0x6); printk (KERN_WARNING "Bad eraseblock %d at 0x%08x/n", i >> 1, (unsigned int) from); break; } } else { if (check_pattern (&buf[j * scanlen], scanlen, mtd->oobblock, bd)) { this->bbt[i >> 3] |= 0x03 << (i & 0x6); printk (KERN_WARNING "Bad eraseblock %d at 0x%08x/n", i >> 1, (unsigned int) from); break; } } } i += 2; from += (1 << this->bbt_erase_shift); } return 0; } 剛剛若是不是Ruturn Happy,下面的352行就會返回錯誤了。接着會調用check_short_pattern()這個函數。 113 /** * check_short_pattern - [GENERIC] check if a pattern is in the buffer * @buf: the buffer to search * @td: search pattern deor * * Check for a pattern at the given place. Used to search bad block * tables and good / bad block identifiers. Same as check_pattern, but * no optional empty check * */ static int check_short_pattern (uint8_t *buf, struct nand_bbt_descr *td) { int i; uint8_t *p = buf; /* Compare the pattern */ for (i = 0; i < td->len; i++) { if (p[td->offs + i] != td->pattern[i]) return -1; } return 0; } 檢查讀到的oob區是否是壞塊就靠這個函數了。前面放了很久的struct nand_bbt_descr smallpage_memorybased終於用上了,挨個對比,有一個不同直接返回-1,壞塊就這樣產生了。下面會將壞塊的位置打印出來,而且將壞塊記錄在bbt表裏面,在nand_scan_bbt()函數的開始咱們就爲bbt申請了空間。 this->bbt[i >> 3] |= 0x03 << (i & 0x6); 爲啥要右移3bit呢?首先i要右移1bit,由於前面乘以了2。因爲沒個block佔用2bit的空間,一個char變量8bit,因此還再要右移2bit吧。 下面的check_pattern()函數調用不到的。 依次檢測完全部block,creat_bbt()函數也順利返回。 這樣nand_memory_bbt()函數也正確返回。 接着是nand_scan_bbt()一樣順利結束。 最後nand_default_bbt()完成。 整個nand_scan()的工做終於完成咯,好長。
由 於NAND Flash的現有工藝不能保證NAND的Memory Array在其生命週期中保持性能的可靠,所以在NAND芯片出廠的時候,廠家只能保證block 0不是壞塊,對於其它block,則均有可能存在壞塊,並且NAND芯片在使用的過程當中也很容易產生壞塊。所以,咱們在讀寫NAND FLASH 的時候,須要檢測壞塊,同時還需在NAND驅動中加入壞塊管理的功能。
NAND驅動在加載的時候,會調用nand_scan函數,對bad block table的搜尋,創建等操做就是在這個函數的第二部分,即nand_scan_tail函數中完成的。
在 nand_scan_tail函數中,會首先檢查struct nand_chip結構體中的options成員變量是否被賦上了NAND_SKIP_BBTSCAN,這個宏表示跳過掃描bbt。因此,只有當你的 driver中沒有爲options定義NAND_SKIP_BBTSCAN時,MTD纔會繼續與bbt相關工做,即調用struct nand_chip中的scan_bbt函數指針所指向的函數,在MTD中,這個函數指針指向nand_default_bbt函數。
bbt 有兩種存儲 方式,一種是把bbt存儲在NAND芯片中,另外一種是把bbt存儲在內存中。對於前者,好處是驅動加載更快,由於它只會在第一次加載NAND驅動時掃描整 個NAND芯片,而後在NAND芯片的某個block中創建bbt,壞處是須要至少消耗NAND芯片一個block的存儲容量;而對於後者,好處是不會耗 用NAND芯片的容量,壞處是驅動加載稍慢,由於存儲在內存中的bbt每次斷電後都不會保存,因此在每次加載NAND驅動時,都會掃描整個NAND芯片, 以便創建bbt。
若是你係統中的NAND芯片容量不是太大的話,我建議仍是把bbt存儲在內存中比較好,由於根據本人的使用經驗,對一塊容量爲2G bits的NAND芯片,分別採用這兩種存儲方式的驅動的加載速度相差不大,甚至幾乎感受不出來。
創建bbt後,之後在作擦除等操做時,就不用每次都去驗證當前block是不是個壞塊了,由於從bbt中就能夠獲得這個信息。另外,若在讀寫等操做時,發現產生了新的壞塊,那麼除了標誌這個block是個壞塊外,也還需更新bbt。
接下來,介紹一下MTD是如何查找或者創建bbt的。
一、MTD中與bbt相關的結構體
struct nand_chip中的scan_bbt函數指針所指向的函數,即nand_default_bbt函數會首先檢查struct nand_chip中options成員變量,若是當前NAND芯片是AG-AND類型的,會強制把bbt存儲在NAND芯片中,由於這種類型的NAND 芯片中含有廠家標註的「好塊」信息,擦除這些block時會致使丟失壞塊信息。
接着 nand_default_bbt函數會再次檢查struct nand_chip中options成員變量,根據它是否認義了NAND_USE_FLASH_BBT,而爲struct nand_chip中3個與bbt相關的結構體附上不一樣的值,而後再統一調用nand_scan_bbt函數,nand_scan_bbt函數會那3個結 構體的不一樣的值作不一樣的動做,或者把bbt存儲在NAND芯片中,或者把bbt存儲在內存中。
在struct nand_chip中與bbt相關的結構體以下:
struct nand_chip {
……
uint8_t *bbt
struct nand_bbt_descr *bbt_td;
struct nand_bbt_descr *bbt_md;
struct nand_bbt_descr *badblock_pattern;
……
};
bbt指向 一塊在nand_default_bbt函數中分配的內存,若options中沒有定義NAND_USE_FLASH_BBT,MTD就直接在bbt指向 的內存中創建bbt,不然就會先從NAND芯片中查找bbt是否存在,若存在,就把bbt的內容讀出來並保存到bbt指向的內存中,若不存在,則在bbt 指向的內存中創建bbt,最後把它寫入到NAND芯片中去。
bbt_td、bbt_md和badblock_pattern就是在nand_default_bbt函數中賦值的3個結構體。它們雖然是相同的結構體類型,但卻有不一樣的做用和含義。
其 中bbt_td和bbt_md是主bbt和鏡像bbt的描述符(鏡像bbt主要用來對bbt的update和備份),它們只在把bbt存儲在NAND芯片 的狀況下使用,用來從NAND芯片中查找bbt。若bbt存儲在內存中,bbt_td和bbt_md將會被賦值爲NULL。
badblock_pattern就是壞塊信息的pattern,其中定義了壞塊信息在oob中的存儲位置,以及內容(即用什麼值表示這個block是個壞塊)。
通 經常使用1或2個字節來標誌一個block是否爲壞塊,這1或2個字節就是壞塊信息,若是這1或2個字節的內容是0xff,那就說明這個block是好的,否 則就是壞塊。對於壞塊信息在NAND芯片中的存儲位置,small page(每頁512 Byte)和big page(每頁2048 Byte)的兩種NAND芯片不盡相同。通常來講,small page的NAND芯片,壞塊信息存儲在每一個block的第一個page的oob的第六個字節中,而big page的NAND芯片,壞塊信息存儲在每一個block的第一個page的oob的第1和第2個字節中。
我 不能肯定是否全部的NAND芯片都是如此佈局,但應該絕大多數NAND芯片是這樣的,不過,即便某種NAND芯片的壞塊信息不是這樣的存儲方式也不要緊, 由於咱們能夠在badblock_pattern中本身指定壞塊信息的存儲位置,以及用什麼值來標誌壞塊(其實這個值表示的應該是「好塊」,由於MTD會 把從oob中壞塊信息存儲位置讀出的內容與這個值作比較,若相等,則表示是個「好塊」,不然就是壞塊)。
bbt_td、bbt_md和badblock_pattern的結構體類型定義以下:
struct nand_bbt_descr {
int options;
int pages[NAND_MAX_CHIPS];
int offs;
int veroffs;
uint8_t version[NAND_MAX_CHIPS];
int len;
int maxblocks;
int reserved_block_code;
uint8_t *pattern;
};
options:bad block table或者bad block的選項,可用的選擇以及各選項具體表示什麼含義,能夠參考<linux/mtd/nand.h>。
pages:bbt 專用。在查找bbt的時候,若找到了bbt,就把bbt所在的page號保存在這個成員變量中。若沒找到bbt,就會把新創建的bbt的保存位置賦值給 它。由於系統中可能會有多個NAND芯片,咱們能夠爲每一片NAND芯片創建一個bbt,也能夠只在其中一片NAND芯片中創建惟一的一個bbt,因此這 裏的pages是個維數爲NAND_MAX_CHIPS的數值,用來保存每一片NAND芯片的bbt位置。固然,若只創建了一個bbt,那麼就只使用 pages[0]。
offs、len和pattern:MTD會從oob的offs中讀出len長度的內容,而後與pattern指針指向的內容作比較,若相等,則表示找到了bbt,或者表示這個block是好的。
veroffs和version:bbt專用。MTD會從oob的veroffs中讀出一個字節的內容,做爲bbt的版本值保存在version中。
maxblocks:bbt專用。MTD在查找bbt的時候,不會查找NAND芯片中全部的block,而是最多查找maxblocks個block。
二、bbt存儲在內存中時的工做流程
前文說過,無論bbt是存儲在NAND芯片中,仍是存儲在內存中,nand_default_bbt函數都會調用nand_scan_bbt函數。
nand_scan_bbt 函數會判斷bbt_td的值,如果NULL,則表示bbt存儲在內存中,它就在調用nand_memory_bbt函數後返回。 nand_memory_bbt函數的主要工做就是在內存中創建bbt,其實就是調用了create_bbt函數。
create_bbt 函數的工做方式很簡單,就是掃描NAND芯片全部的block,讀取每一個block中第一個page的oob內容,而後根據oob中的壞塊信息創建起 bbt,能夠參見上節關於struct nand_bbt_descr中的offs、len和pattern成員變量的解釋。
三、bbt存儲在NAND芯片時的工做流程
相對於把bbt存儲在內存中,這種方式的工做流程稍顯複雜一點。
nand_scan_bbt函數首先從NAND芯片中讀取bbt的內容,它讀取的方式分爲兩種:
其 一是調用read_abs_bbts函數直接從給定的page地址讀取,那麼這個page地址在何時指定呢?就是在你的NAND driver中指定。前文說過,在struct nand_chip結構體中有兩個成員變量,分別是bbt_td和bbt_md,MTD爲它們附上了default的值,可是你也能夠根據你的須要爲它們 附上你本身定義的值。假如你爲bbt_td和bbt_md的options成員變量定義了NAND_BBT_ABSPAGE,同時又把你的bbt所在的 page地址保存在bbt_td和bbt_md的pages成員變量中,MTD就能夠直接在這個page地址中讀取bbt了。值得一提的是,在實際使用時 通常不這麼幹,由於你不能保證你保存bbt的那個block就永遠不會壞,並且這樣也不靈活;
其二是調用那個search_read_bbts函數試着在NAND芯片的maxblocks(請見上文關於struct nand_bbt_descr中maxblocks的說明)個block中查找bbt是否存在,若找到,就能夠讀取bbt了。
MTD 查找bbt的過程爲:若是你在bbt_td和bbt_md的options 成員變量中定義了 NAND_BBT_LASTBLOCK,那麼MTD就會從NAND芯片的最後一個block開始查找(在default狀況下,MTD就是這麼幹的),否 則就從第一個block開始查找。
與 查找oob中的壞塊信息時相似,MTD會從所查找block的第一個page的oob中讀取內容,而後與bbt_td或bbt_md中patter指向的 內容作比較,若相等,則表示找到了bbt,不然就繼續查找下一個block。順利的狀況下,只需查找一個block中就能夠找到bbt,不然MTD最多會 查找maxblocks個block。
若找到了bbt,就把該bbt所在的page地址保存到bbt_td或bbt_md的pages成員變量中,不然pages的值爲-1。
若是系統中有多片NAND芯片,而且爲每一片NAND芯片都創建一個bbt,那麼就會在每片NAND芯片上重複以上過程。
接 着,nand_scan_bbt函數會調用check_create函數,該函數會判斷是否找到了bbt,其實就是判斷bbt_td或者bbt_md中 pages成員變量的值是否有效。若找到了bbt,就會把bbt從NAND芯片中讀取出來,並保存到struct nand_chip中bbt指針指向的內存中;若沒找到,就會調用create_bbt函數創建bbt(與bbt存儲在內存中時狀況同樣),同時把bbt 寫入到NAND芯片中去。
MTD壞塊管理機制中,起着核心做用的數據結構是nand_chip,在此以TCC8900-Linux中MTD的壞塊管理爲例做一次介紹。
MTD在Linux內核中一樣以模塊的形式被啓用,TCC_MTD_IO_Init()函數完成了nand_chip初始化、mtd_info初始註冊,
壞塊表的管理機制創建等工做。
nand_chip在TCC_MTD_IO_Init函數中的實例名稱是this,mtd_info 的實例名稱爲TCC_mtd,這裏有一個比較巧妙的處理方法:
TCC_mtd=kmalloc(sizeof(struct mtd_info)+sizeof(struct nand_chip),GFP_KERNEL);
this=(struct nand_chip*)(&TCC_mtd[1]);
在之後的操做中,只需得知TCC_mtd便可找到對應的nan_chip實例。
得到必要的信息後(包括nand_chip方法的綁定),流程進入nand_scan(TCC_mtd,1).
nand_scan(struct mdt_info *mtd, int maxchips);
調用nand_scan_ident(mtd,maxchips)和nand_scan_tail(mtd);
nand_scan_ident(...)調用了一個很重要的函數:nand_get_flash_type(...)
*從nand_get_flash_type(...)函數中能夠看出每一個nandflash前幾個字節所表明的意思都是約定好了的:
第一個字節:製造商ID
第二個字節:設備ID
第三個字節:MLC 數據
第四個字節:extid (比較總要)
其中設備ID是訪問nand_flash_ids表的參照,該表在drivers/mtd/nand/nand_ids.c中定義
Linux內核在nand_flash_ids參照表中,經過匹配上述設備ID來查找nandflash的詳細信息,
nand_flash_ids中的舉例以下:
struct nand_flash_dev nand_flash_ids[]={
......
{"NAND 16MiB 1,8V 8-bit", 0x33, 512, 16, 0x4000, 0},
{"NAND 16MiB 3,3V 8-bit", 0x73, 512, 16, 0x4000, 0},
{"NAND 16MiB 1,8V 16-bit", 0x43, 512, 16, 0x4000, NAND_BUSWIDTH_16},
{"NAND 16MiB 3,3V 16-bit", 0x53, 512, 16, 0x4000, NAND_BUSWIDTH_16},
......
}
466 struct nand_flash_dev {
467 char *name;
468 int id;
469 unsigned long pagesize;
470 unsigned long chipsize;
471 unsigned long erasesize;
472 unsigned long options;
473 };
值得一提的是,MTD子系統會把從nand_flash_ids表中找到的chipsize複製給mtd->size,這在有些應用中顯得不合適,
在有些方案中,並非把nandflash的全部存儲空間都劃分爲MTD分區,Telechips的TCC89XX方案就是這樣,4G的nandflash
上,能夠劃分任意大小的MTD分區,錯誤的mtd->size的後果很是嚴重,形成系統啓動慢,整個MTD的壞塊管理機制癱瘓等等。
隨後,nand_get_flash_type經過extid計算出瞭如下信息:
mtd可寫區大小:mtd->writesize=1024<<(extid&0x03);
這裏能夠當作1024*(1*2的(extid&0x03)次方),
mtdoob區大小:extid>>=2;mtd->oobsize = (8<<(extid&0x1))*(mtd->writesize>>9);
每512字節對應(8*2的(extid&0x1)次方)字節oob數據
mtd擦寫塊大小:extid>>=2;mtd->erasesize=(64*1024)<<(extid&0x03);
nand數據寬度 :extid>>=2;busw=(extid&0x01)?NAND_BUSEWIDTH_16:0; 如今大多爲8位數據寬度
能夠看出第四個字節extid的意義:
高|0 | 0 | 00 | 0 | 0 | 00 |低
|無用|數據寬度|擦寫塊算階|無用|oob算階| 可寫區算階|
nand_get_flash_type(...)還確立了nandflash中的壞塊標記在oob信息中的位置:
if(mtd->writesize>512||(busw&NAND_BUSWIDTH_16))
chip->badblockpos = NAND_LARGE_BADBLOCKS_POS;//大頁面flash的壞塊信息存儲地址爲oob信息中的第1個字節開始處
else
chip->badblockpos = NAND_SMALL_BADBLOCKS_POS;//大頁面flash的壞塊信息存儲地址爲oob信息中的第6個字節開始處
對於Samsun和Hynix的MLC型nandflash,壞塊標記所在的頁是每塊的最後一個頁,而Samsung,Hynix,和AMD的SLC型nandflash
中,壞塊標記分別保存在每塊開始的第1,2個頁中,其餘型號的nandflash大多都保存在第一個也中,爲此須要做下標記:
壞塊標記保存在塊的最後一頁中:chip->options |= NAND_BBT_SCANLASTPAGE;
壞塊標記保存在塊的第1,2頁中 :chip->options |= NAND_BBT_SCAN2NDPAGE;
nand_scan以後調用nand_scan_tail(mtd)函數,
nand_scan_tail(...)函數主要完成MTD實例中各類方法的綁定,例如:
3338 mtd->read = nand_read;
3339 mtd->write = nand_write;
3340 mtd->panic_write = panic_nand_write;
3341 mtd->read_oob = nand_read_oob;
3342 mtd->write_oob = nand_write_oob;
3343 mtd->sync = nand_sync;
nand_scan_tail(...)調用chip->scan_bbt()完成壞塊表的有關操做。
chip->scan_bbt的綁定過程是在nand_scan_ident()->nand_set_defaults():chip->scan_bbt = nand_default_bbt.
即真正用於壞塊操做的是nand_default_bbt函數,該函數在nand_bbt.c中被定義。