2012年6月21日 星期四

在iOS上使用html parser

這裡是使用libxml2與hpple來做html parser。

1. 必須先將libxml2加到專案裡,可參考以下連結教學
http://justcoding.iteye.com/blog/1474176

2. 將hpple framework加到專案裡,
   下载hpple from https://github.com/topfunky/hpple
   加入以下檔案:HTFpple.h HTFpple.m HTFppleElement.h HTFppleElement.m XPathQuery.h XPathQuery.m

   NSString *htmlString=[NSString stringWithContentsOfURL:[NSURL URLWithString: @"http://ebook.sunlight.tw/"] encoding: NSUTF8StringEncoding error:nil];          NSData *htmlData=[htmlString dataUsingEncoding:NSUTF8StringEncoding];      TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:htmlData];   
    NSArray *elements  = [xpathParser searchWithXPathQuery:@"/html/body/pre/a"]; // get the title    
    TFHppleElement *element  = [xpathParser peekAtSearchWithXPathQuery:@"/html/body/pre/a"];
    TFHppleElement *element2  = [xpathParser peekAtSearchWithXPathQuery:@"/html/body/pre/a[2]"];
    NSString *elementContent = [element objectForKey:@"href"];
  
    NSLog(@"result = %@",element);
     NSLog(@"result = %@",element2);
    NSLog(@"result = %@",[element attributes]);
    NSLog(@"result = %@",[element2 attributes]);

2012年6月14日 星期四

沙巴自由行

第一天
早上:8:00~11:30 飛機中
中午:Shangri-La Tanjung Aru Resort (MV)
下午:市區觀光。
DSCF3658
晚上:市區酒吧街,飯店海灘


第二天
     美人魚島深潛 AM07:00–PM17:00

晚上:飯店SPA、游泳池、Sunset Bar喝酒

第三天
早上:深海釣魚
中午:吃自己釣的魚
下午:前往Gayana島
     入住加雅娜島Gayana Eco Resort Kota Kinabalu (BAYU VILLA)
         (無人島沒有便利商店…請出發前準備好)

晚上:房間跳海浮潛 (聽說有時候有水母,請小心)
                  

第四天
早上:Gayana PADI 潛水中心,深潛、獨木舟、跳水

下午:Same
晚上:爽就好
第五天
早上:發呆
下午:回市區準備上機
晚上:17:45~21:05回程飛機

2012年6月6日 星期三

ipad上電子書製作

最近有機會要寫一些類似電子書的程式,找到了Baker framework。看起來相當方便,
想自己利用HTML + CSS + JavaScript製作iOS上頭的數位出版品app的朋友,Baker ebook framework,目前的版本,幾乎只要利用HTML、CSS、JavaScript就可以搞定,幾乎免碰Obj-C。但是如果需要修改到介面的話,可能就需要自己動手改。這framework沒有提供到書架的功能有點可惜,希望以後可以support一本以上的電子書。


可以配合Laker就可以製作出相當精美的電子書 http://www.lakercompendium.com/

下列連結為有網友中文化的Baker guide
http://b.hjwu.me/blog/2011/11/07/getting-started-with-Baker-framework/


設計

利用 Baker 創建出一本書就像架設一個網站一樣,你所需要的只有
假設上述兩點你都已經具備了,接下來就是回答下面三個問題
  1. 你要如何將你的書中內容分開成多個 HTML pages ?
    Baker 做出來的書是由多個 HTML page 所組成。由於 HTML page 並沒有限制內容的長度,所以你可以把一整個章節全部放在單一個 page 內。
  2. 你是要放在那種平台上 ?
    Baker 支援 iPhone/iPod 跟 iPad, 所以請選擇你想要放置的平台並針對它去設計你的書。
  3. 你希望書籍是以何種方向呈現
    Baker 支援橫向與縱向兩種樣式。選擇一個你喜歡的書籍樣式,或著針對這兩種提供一個漂亮的樣式。
如果你對上面的問題都有了答案,那你你可以開始設計你的書。別忘了你可以利用所有 HTML5 與 CSS3 的特點,以及適用於 WebKit 的 JavaScript libraries. 如果你想要測試你的成果,試著以 Safari Mobile [註1] 方式瀏覽: Baker 呈現的方式正如同它們在你希望的平台。
[註1]應該是指修改 Safari 的 “開發人員” -> “使用者代理程式”。

套件

現在你的書已經準備好了,接下來就打包它成為一個套件然後丟到 Baker 吧:
  1. 先建立一個名為 “book” 的資料夾,並且複製所有你的書本檔案丟進去 “book” 裡面。所有的 HTML 檔案跟書中內容都需要在 “book” 資料夾中, Baker 只會看到 “book” 資料夾內的內容。
  2. 再建立一個名為 “book.json” 的檔案,裡面包含了 Baker 所需要的參數,以便 Baker 能正確顯示你的書。這個檔案被稱為 manifest 並且需滿足 HPub 標準。你可以在這裡找到所有參數的完整解釋。在做完上面的步驟後,你的 “book” 資料夾將會是一個 HPub 套件,接著就是讓它轉成 APP 吧。
附註: 你可能注意到,HPub 套件只是一堆 HTML pages。 這意味著,一旦你確定所有 page 彼此被鏈接,你能發佈它在網路上而不用另外的工作以及你的使用者能夠在標準的瀏覽器上閱讀它

第一次建置

現在準備開始使用 Baker,在此之前你還需要兩個東西:
  • 最新版本的 Xcode。如果你是註冊過的 Apple developer,你可以從 Apple Developers website 免費下載。 如果你還沒有註冊過, 你可以到這裡註冊. 如果你不想註冊而只是想測試 Baker 在你的機器上,那麼你可以從 Mac App Store 下載。
  • 一臺 Mac。很遺憾,在 Windows 上是沒有 Xcode。
準備好了嗎?讓我們開始在模擬器上測試你的 APP 吧:
  1. 下載最新版本的 Baker Framework package 並將它解壓縮至 Baker 資料夾。
  2. 將你自己的 “book” 資料夾 取代 Baker 資料夾中預設的 “book” 資料夾。
  3. 雙擊 Baker.xcodeproj 這個檔案以讓 Xcode 開啓這個 project。
  4. 檢查左上角的 “Scheme” 是顯示 “Baker > iPad 5.0 Simulator”。
  5. 點擊 “Run” 按鈕。
如果一切都沒問題的話,你應該會看到你的 Baker app 會在 iPad 模擬器上執行,並且展開妳書本中的第一頁。
如果有出錯的話, 請在完全的清除之後重新再執行一次: 移除模擬器上的 app (就如同你在實際的 iPad 上移除的方法) 然後按 Product -> Clean Build Folder 來清理 Xcode 的建置… (打開 Product 選單時,你需要按 alt 才能夠看到這個選項)

個人化你的書

你已經快做好了。現在在送上 App Store 前,放一些注意力在你的 app 上。
  • 重新命名你的 app
    在 project 的 Navigator 邊欄上選擇(最上面的) “Baker” 然後按 “enter”:你應該能夠改變名字。重新命名為你想要的(這個將會是你的 APP 在 APP Store 上的名字)。
  • 選擇你要建置的機器平台
    在 Project 的主要視窗 (當你點擊 Project Navigator 的最上方選項,它應該會自動被打開) 點擊 “Build Settings”. 搜尋 “Targeted Device Family” 然後選擇你要的機器平台。
  • 選擇適合的 iOS
    在 Project 的主要視窗點擊 “Info” 的 “Deployment Target” 內的 “iOS Deployment Target” [註2]。選擇你希望你的 APP 可以運行的 iOS 版本: Baker 可以在 iOS 4.0 以上的版本運行。
    [註2] 原文是 In the Project Build Settings, right under the “Targeted Device Family” entry, you should see another one called “iOS Deployment Target”. 但是我在 “Targeted Device Family” 下並沒有看到,而是在 “Info” 內看到
  • 改變你的 APP 圖示
    你應該為你的 APP 使用個人的圖示。請參照 Apple Custom Icon and Image Creation Guidelines 來製造你的圖示並將它們放到 “Baker” 資料夾內的 “Resource” 資料夾。
  • 檢查 Baker 的擴充 HPub 參數
    除了標準的 HPub 設定之外,Baker 提供一系列的參數來客制化你的書的外觀。這些參數都列在這裡
就這樣。現在重新再執行你的 app 在模擬器上 : 當你已經準備好的話。
請不要太相信你的模擬器: 記住你的使用者是在真實的 iPad 跟 iPhone 上看你的 APP。請先在實機上測試過後再釋出你的 APP。

發佈在 Apple App Store

在發佈你的 APP 到 App Store 上時,請參照 Apple 提供的詳細教學(需要登入):
  1. 你在進入 iTunes Connect website 時必須準備好應用程式.
  2. 你必須依照 iOS Provisioning Portal under Distribution 的指示建立好編譯過的應用程式以釋出。
  3. 你必須要上傳編譯過的應用程式。
這部分完全由 Apple 控管,所以你可以使用官方的 channels 跟教學來了解如何去做.. 或是在網路上發問。Apple 上也有一些有趣的額外提示。
全部就這樣,歡呼吧 :)

2012年6月3日 星期日

How to use Hitool in EV44B0

Program MBL(Micetek Boot Loader) to EV44B0-II board.
 1)  Open "Hitool for ARM" or "Hitool for UClinux"'s "Startup Configuration",
     select "Maker" to Samsung.
     select "Target CPU" to ARM7TDMI-S3c44b0x.
     select "Protocol" tp PowerProbe.
     select "Endian" to little Endian.
     set the "Port" and "Address" depending on your system.
     select the EV44B0II.INC(on root directory), as initial file.
     click "ok", to exit.
 2)  lanuch the "Hitool for ARM" or "Hitool for UClinux"
     open the "flash programming ..." on "Tools" menu.
     Select the "File' to EV44B0_MBL_v1.0.2.bin(on root\bin directory).
     set "Flash information" as following:
         "Maker" to universal    "Device" to universal
         "Port Size" to 16bits   "Chip count" to 1
         "Flash start" to 0      "Flash size" to 0x200000
     set "RAM Usage" as following:
         "Range" to 0x0c000000    "to" to 0x0c004000
     set "Flash Offset" to 0
     set "Timeout" to 50
     Then click Erase button. When Erase ok, click Flash button. After burn ok,
     reset the board.

如何將做好的file system放到NAND flash中,並燒入修改

 
一、如何將做好的file system放到NAND flash中,
(這樣的做法事實上是間接把mtd3格式化成jffs2,然後再把未壓縮filesystem給copy到已格式化的mtd3上。這樣可以方便我們測試filesystem可不可以適用於此板。)

1. 將file system壓縮成jffs2的格式,
      ./mkfs.jffs2 --pad=0x00F00000 --eraseblock=0x20000 -r target/ -o rootfs.jffs2
(注意一下這邊的pad參數,這樣的做法只把mtd3的部分給格式化成F00000大小,所以可以把它放大到跟mtd3的分割大小)

2. 用NFS開機進去Linux之後,將rootfs.jffs2燒進去,
 flash_eraseall -j /dev/mtd3
 nandwrite -p /dev/mtd3 rootfs.jffs2

3. 將/dev/mtdblock3 掛載起來,並將檔案全數刪除,
mount -t jffs2 /dev/mtdblock3 /mnt/nand
rm -rf /mnt/nand/*

4. 接著把要測試的file system全部重新copy到 /mnt/nand 

5. unmount /mnt/nand; 之後重新掛載mtdblock3,注意有沒有錯誤訊息(應該是沒有)

在編譯kernel需要注意的是kernel config的時候,確定NAND flash有被選起來(預設會有),並在file system裡面的NFS選項,必須把"Root file system on NFS"這個選項disable。
(注意:之後要再重新從NFS開機的話,要在重新把此選項選起來,並重新編譯)

在u-Boot,從NAND flash開機方法如下

uboot # tftp 0x80700000 uImage    /*tftp下載image*/
uboot # setenv bootargs console=ttyS0,115200n8 noinitrd rw ip=140.124.182.1::140.124.182.254:255.255.255.0:dm6446:eth0:off root=/dev/mtdblock3 rw rootfstype=jffs2   /*從mtdblock3開機*/

uboot # bootm
二、如何將kernel燒進去NAND Flash
這邊的做法是從uboot去做下載並燒到flash裡面,
1. 將編譯完成之uImage放置tftp目錄下。

2. uboot # nand erase 0x2060000 0x155400
uboot # tftp 0x80700000 uImage
uboot # nand write 0x80700000 0x2060000 0x155400
uboot # setenv 'bootcmd nand read 80700000 2060000 155400 ; bootm'
(注意:這裡是將kernel放在2060000放置,最好跟linux配置kernel位置一樣mtd2,kernel大小為155400,要視自己的flash配置空間以及kernel大小做修改)
這裡2060000是放置kernel位址,但若是u-boot直接對此位址讀取在第一次是可正常讀取但在重新開機就會產生讀取錯誤的狀況。所以應該要填入60000

3.重新開機可直接進去linux

之後如果要更新kernel的話也可以從linux去更新。
/usr/sbin/flash_eraseall -j /dev/mtd2 
/usr/sbin/nandwrite -p /dev/mtd2 uImage
三、MTD PARTITION

在arch/arm/mach-davinci/board-evm.c裡,我們必須自己規劃flash的配置大小。static struct mtd_partition nand_partitions[] = {
   /* bootloader (U-Boot, etc) in first sector */
   {
         .name             = "bootloader",                                              //mtd0 放 bootloader
         .offset           = 0,
         .size             = SZ_2M,     //配置2MB,視bootloader大小決定
         .mask_flags       = MTD_WRITEABLE, /* force read-only */
   },
   /* bootloader params in the next sector */
   {
         .name             = "params",  //mtd1 放 bootloader的params
         .offset           = MTDPART_OFS_APPEND,                               //接續上一個配置空間
         .size             = SZ_1M,    //配置1MB,視bootloader params大小決定
         .mask_flags       = MTD_WRITEABLE, /* force read-only */
   },
   /* kernel */
   {
         .name             = "kernel", //mtd2 放 kernel(uImage)
         .offset           = MTDPART_OFS_APPEND, //接續上一個配置空間
         .size             = SZ_4M,     //配置4M,視kernel大小決定
         .mask_flags       = 0
   },
   /* file system */
   {
         .name             = "filesystem",    //mtd3 放 file system
         .offset           = MTDPART_OFS_APPEND, //接續上一個配置空間
         .size             = MTDPART_SIZ_FULL,                                     //配置剩下全部空間
         .mask_flags       = 0
   }
{
     //可視情況再增加分割數
}
};


Introduction of MTD NAND flash

MTD 驅動程序是專門針對嵌入式Linux的一種驅動程序,相對於常規塊設備驅動程序(比如PC中的IDE硬盤)而言,MTD驅動程序能更好的支持和管理閃存設備,因為它本身就是專為閃存設備而設計的。
具體地講,基於MTD的FLASH驅動,承上可以很好地支持cramfs,jffs2和yaffs等文件系統,啟下也能對FLASH的擦除,讀寫,FLASH壞塊以及損耗平衡進行很好的管理。
所謂損耗平衡,是指對NAND的擦寫不能總是集中在某一個或某幾個block中,這是由NAND chip有限的擦寫次數的特性決定的。 
總之,在現階段,要為FLASH設備開發Linux下的驅動程序,那麼基於MTD的開發將幾乎是省時又省力的唯一選擇!

一、NAND和NOR的區別 
  
Google “Nand Flash和Nor Flash的區別”。 
  
簡單點說,主要的區別就是: 
  
1、 NAND比NOR便宜;NAND的容量比NOR大(指相同成本);NAND的擦寫次數是NOR的十倍;NAND的擦除和寫入速度比NOR快,讀取速度比NOR稍慢; 
  
2、 NAND和NOR的讀都可以以 byte為單位,但NAND的寫以page為單位,而NOR可以隨機寫每一個 byte。 NAND和NOR的擦除都以block為單位,但一般NAND的block比NOR的block小。另外,不管是NAND還是NOR,在寫入前,都必須先進行擦除操作,但是NOR在擦除前要先寫0; 
  
3、 NAND不能在片內運行程序,而NOR可以。但目前很多CPU都可以在上電時,以硬件的方式先將NAND的第一個block中的內容(一般是程序代碼,且也許不足一個block,如2KB大小)自動copy到ram中,然後再運行,因此只要CPU支持,NAND也可以當成啟動設備; 
  
4、 NAND和NOR都可能發生比特位反轉(但NAND反轉的機率遠大於NOR),因此這兩者都必須進行ECC操作;NAND可能會有壞塊(出廠時廠家會對壞塊做標記),在使用過程中也還有可能會出現新的壞塊,因此NAND驅動必須對壞塊進行管理。

二、內核樹中基於MTD的NAND驅動代碼的佈局 
  
在Linux內核中,MTD源代碼放在linux-2.6.22.10/driver/mtd目錄中,該目錄中包含chips、devices、maps、nand、onenand和ubi六個子目錄。 
  
其中只有nand和onenand目錄中的代碼才與NAND驅動相關,不過nand目錄中的代碼比較通用,而onenand目錄中的代碼相對於nand中的代碼而言則簡化了很多,它是針對三星公司開發的另一類Flash chip,即OneNAND Flash。我尚未對OneNand FLASH有過研究,只是通過網上資料得知,OneNand FLASH克服了傳統NAND Flash接口複雜的缺點,具有接口簡單、讀寫速度快、容量大、壽命長、成本低等優點,因此應該是一種較常用NAND先進的FLASH吧,只是目前似乎普及率並不高,本文也將不做討論。 
  
因此,若只是開發基於MTD的NAND驅動程序,那麼我們需要關注的code就基本上全在linux-2.6.22.10/drivers/mtd/nand目錄中了,而該目錄中也不是所有的code文件都與我們將要開發的NAND驅動有關,除了Makefile和Kconfig之外,其中真正與NAND驅動有關的code文件只有6個,即: 
  
1、 nand_base.c: 
定義了NAND驅動中對NAND chip最基本的操作函數和操作流程,如擦除、讀寫page、讀寫oob等。當然這些函數都只是進行一些default的操作,若你的系統在對NAND操作時有一些特殊的動作,則需要在你自己的驅動代碼中進行定義,然後Replace這些default的函數。 (mtd_info)
  
2、 nand_bbt.c: 
定義了NAND驅動中與壞塊管理有關的函數和結構體。

3、 nand_ids.c: 
定義了兩個全域類型的結構體:struct nand_flash_dev nand_flash_ids[ ]和struct nand_manufacturers nand_manuf_ids[ ]。其中前者定義了一些NAND chip的類型,後者定義了NAND chip的幾個廠商。 NAND chip的ID至少包含兩項內容:廠商ID和廠商為自己的NAND chip定義的chip ID。當NAND驅動被掛載的時候,它會去讀取具體NAND chip的ID,然後根據讀取的內容到上述定義的nand_manuf_ids[ ]和nand_flash_ids[ ]兩個結構體中去查找,以此判斷該NAND chip是那個廠商的產品,以及該NAND chip的類型。若查找不到,則NAND驅動就會掛載失敗,因此在開發NAND驅動前必須事先將你的NAND chip添加到這兩個結構體中去(其實這兩個結構體中已經定義了市場上絕大多數的NAND chip,所以除非你的NAND chip實在比較特殊,否則一般不需要額外添加)。值得一提的是,nand_flash_ids[ ]中有三項屬性比較重要,即pagesize、chipsize和erasesize,驅動就是依據這三項屬性來決定對NAND chip進行erase,read and write等操作時的大小。其中pagesize即NAND chip的頁大小,一般為256、512或2048;chipsize即NAND chip的容量;erasesize即每次擦除操作的大小,通常就是NAND chip的block大小。 
  
4、 nand_ecc.c: 
定義了NAND驅動中與softeware ECC有關的函數和結構體,若你的系統支持hardware ECC,且不需要software ECC,則該文件也不需理會。 
  
5、 nandsim.c: 
定義了Nokia開發的模擬NAND設備,默認是Toshiba NAND 8MiB 1,8V 8-bit(根據ManufactureID),開發普通NAND驅動時不用理會。 
  
6、 diskonchip.c: 
定義了片上磁盤(DOC)相關的一些函數,開發普通NAND驅動時不用理會。 
  
除了上述六個文件之外,nand目錄中其他文件基本都是特定系統的NAND驅動程序例子,但本人看來真正有參考價值的只有cafe_nand.c和s3c2410.c兩個,而其中又以cafe_nand .c更為詳細,另外,nand目錄中也似乎只有cafe_nand.c中的驅動程序在讀寫NAND chip時用到了DMA操作。 
  
綜上所述,若要研究基於MTD的NAND驅動,其實所需閱讀的code量也不是很大。

另外,在動手寫NAND驅動之前,也許需要讀一下以下文件:
1、 Linux MTD source code 分析:
該文件可以讓我們對MTD有一個直觀而又相對具體的認識,但它似乎主要是針對NOR FLASH的,對於實際開發NAND驅動的幫助並不是很大。
2、 MTD NAND Driver Programming Interface:
http://www.aoc.nrao.edu/~tjuerges/ALMA/Kernel/mtdnand/
該文檔中關於ECC的說明很有幫助。
3、 MTD的官方網站:
http://www.linux-mtd.infradead.org/

三、NAND相關原理 
  
在我們開始NAND驅動編寫之前,至少應該知道:數據在NAND中是怎樣儲存的,以及怎樣的方式從NAND中讀寫數據時。 
  
1、 NAND的存儲結構和操作方式 
  
這方面的資料可以從任意一種NAND的datasheet中得到,因為基本上每一種NAND的datasheet都會介紹NAND的組成結構和操作命令,而且事實上,大多數的NAND datasheet都大同小異,所不同的大概只是該NAND chip的容量大小和讀寫速度等基本特性。 
  
這裡以每頁512 byte的NAND FLASH為例簡單說明一下:每一塊NAND chip由n個block組成->每一個block由m個page組成->每一個page由256 byte大小的column1(也稱1st half page)、256 byte大小的column2(也稱2nd half page)和16 byte大小的oob(out-of-band,也稱spare area)組成。至於m和n的大小可以查看特定NAND的datasheet。相應的,若給定NAND中的一個 byte的地址,我們可以根據這個地址算出block地址(即第幾個block)、page地址(即該block中的第幾個page)和column地址(即1st half page,或2nd half page,或oob中的第幾個 byte)。 
  
在擦除NAND時,必須每次至少擦除1個block;在寫NAND時,必須每次寫1個page(有些NAND也支持寫不足一個page大小的數據);在讀NAND時,分為三種情況(對應三種不同的NAND命令),即讀column1、讀column2和讀oob,那麼為什麼要分這三種情況呢?假如知道NAND怎樣根據給定的地址確定它的存儲單元,那麼自然也就能明白原因了,其實也並不復雜,主要是因為給定地址中的A8並不在NAND的視野範圍之內(也許表達並不準確)。 
  
事實上,在寫基於MTD的NAND驅動時,我們並不需要實現精確到讀寫某一個byte地址的函數(除了讀oob之外),這是因為: 
  
基於MTD的NAND驅動在讀寫NAND時,可以分兩種情況,即:(1)不進行ECC檢測時,一次讀寫一整個page中的MAIN部分(也就是那真實存儲數據的512 byte); (2)進行ECC檢測時(不管是hardware ECC還是software ECC),一次讀寫一整個page(包括16 byte的oob部分)。所以部分NAND所支持的寫不足一個page大小數據的功能,對MTD來說是用不著的。 
  
那麼,如果只需要讀寫不足一個page大小的數據怎麼辦?這是MTD更上層的部分需要處理的事。也就是說,對於NAND驅動來說,它只會讀寫整整一個page的數據! 
  
最後值得一提的是,NAND驅動有可能只去讀oob部分,這是因為除了ECC信息之外,壞塊信息也存儲在oob之中,NAND驅動需要讀取oob中描述壞塊的那個 byte(通常是每個block的第一個page的oob中的第六個 byte)來判斷該block是不是一個壞塊。所以,我們只有在讀oob時,才需要實現精確到讀某一個byte地址的函數。 
  
由此,我們也可以額外知道一件事,那就是NAND驅動中用到的column地址只在讀oob時才有用,而在其他情況下,column地址都為0。

2、 ECC相關的結構體 
struct nand_ecclayout { 
           uint32_t eccbytes; 
           uint32_t eccpos[64]; 
           uint32_t oobavail; 
           struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; 
}; 
這是用來定義ECC在oob中佈局的一個結構體。 
  
前面已經提及過,oob中主要存儲兩種信息:壞塊信息和ECC數據。對與small page的NAND chip來說,其中壞塊信息佔據1個 byte(一般固定在第六個 byte),ECC數據佔據三個 byte。所以sturct nand_ecclayout這個結構體,也就是用來告訴那些與ECC操作無關的函數,Nand chip的oob部分中,哪些 byte是用來存儲ECC的(即不可用作它用的),哪些 byte是空閒的,即可用的。 
  
其實之所以有這個結構體,主要是因為硬件ECC的緣故。以寫數據為例,在使用硬件ECC的情況下,那三個 byte的ECC數據是由硬件計算得到,並且寫到NAND chip的oob中去的,同時也是由硬件決定寫到oob的哪三個 byte中去。這些都是由硬件做的,而NAND驅動並不知道,所以就需要用這個結構體來告訴驅動了。 
  
所以,在寫NAND驅動時,就有可能需要對這個結構體進行賦值。這裡說“有可能”,是因為MTD對這個結構體有一個默認的賦值,假如這個賦值所定義的ECC位置與你的硬件一致的話,那就不必在你的驅動中手動賦值了。其實對大多數硬件(這裡所說的硬件,不是指NAND chip,而是NAND控制器)來說,是不必手動賦值的,但也有許多例外。 
  
值得一提的是,這個結構體不僅僅用來定義ECC佈局,也可以用來將你的驅動在oob中需要額外用到的 byte位置保護起來。 
  
現在對struct nand_ecclayout 這個結構體進行一下說明。 
  
uint32_t eccbytes:ECC的 byte數,對於512B-per-page的NAND來說,eccbytes = 3,如果你需要額外用到oob中的數據,那麼也可以大於3. 
uint32_t eccpos[64]:ECC數據在oob中的位置,這里之所以是個64 byte的數組,是因為對於2048-per-page的NAND來說,它的oob有64個 byte。而對於512B-per-page的NAND來說,可以而且只可以定義它的前16個 byte。 
uint32_t oobavail:oob中可用的 byte數,這個值不用賦值,MTD會根據其它三個變量自動計算得到。 
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]:顯示定義空閒的oob byte。

四、基於MTD的NAND驅動架構
 
1、platform_device和platform_driver的定義和註冊
 
對於我們的NAND driver,以下是一個典型的例子:

 static struct platform_driver caorr_nand_driver = {
               .driver = {
                                .name = " caorr-nand",
                                .owner = THIS_MODULE,
                },
                .probe = caorr_nand_probe,
                .remove = caorr_nand_remove,
 };

 static int __init caorr_nand_init(void)
 {
                printk("CAORR NAND Driver, (c) 2008-2009.\n");
                return platform_driver_register(&caorr_nand_driver);
 }

 static void __exit caorr_nand_exit(void)
 {
                platform_driver_unregister(&caorr_nand_driver);
 }

 module_init(caorr_nand_init);
 module_exit(caorr_nand_exit);

 
與大多數嵌入式Linux驅動一樣,NAND驅動也是從module_init開始。 caorr_nand_init是驅動初始化函數,在此函數中註冊platform driver結構體,platform driver結構體中自然需要定義probe和remove函數。其實在大多數嵌入式Linux驅動中,這樣的套路基本已經成了一個定式 
  
至於module_init有什麼作用,caorr_nand_probe又是何時調用的,以及這個driver是怎麼和NAND設備聯繫起來的,就不再多說了,這裡只提三點: 
  
A、以上code只是向kernel註冊了NAND的platform_driver,即caorr_nand_driver,我們當然還需要一個NAND的platform_device,要不然caorr_nand_driver的probe函數就永遠不會被執行,因為沒有device需要這個driver。 
  
B、 向Linux內核註冊NAND的platform_device有兩種方式: 
其一是直接定義一個NAND的platform_device結構體,然後調用platform_device_register函數註冊。作為例子,我們可以這樣定義NAND的platform_device結構體:

 struct platform_device caorr_nand_device = {
          .name = "caorr-nand",
          .id = -1,
          .num_resources = 0,
          .resource = NULL,
          .dev = {
              .platform_data = &caorr_platform_default_nand,
          }
 }; 
 platform_device_register(&caorr_nand_device);


其中num_resources和resource與具體的硬件相關,主要包括一些寄存器地址範圍和中斷的定義。 caorr_platform_default_nand待會兒再說。需要注意的是,這個platform_device中name的值必須與platform_driver->driver->name的值完全一致,因為platform_bus_type的match函數是根據這兩者的name值來進行匹配的。 
其二是用platform_device_alloc函數動態分配一個platform_device,然後再用platform_device_add函數把這個platform_device加入到內核中去。具體不再細說,Linux kernel中有很多例子可以參考。 
相對來說,第一種方式更加方便和直觀一點,而第二種方式則更加靈活一點。 
  
C、 在加載NAND驅動時,我們還需要向MTD Core提供一個信息,那就是NAND的分區信息(MTD partition),caorr_platform_default_nand主要就是起這個作用,更加詳細的容後再說。 
  
2、MTD架構的簡單描述 
  
MTD(memory technology device存儲技術設備)是用於訪問memory設備(ROM、flash)的Linux的子系統。 MTD的主要目的是為了使新的memory設備的驅動更加簡單,為此它在硬件和上層之間提供了一個抽象的接口。 MTD的所有源代碼在/drivers/mtd子目錄下。 MTD設備可分為四層(從設備節點直到底層硬件驅動),這四層從上到下依次是:設備節點、MTD設備層、MTD原始設備層和硬件驅動層。


A、Flash硬件驅動層:硬件驅動層負責驅動Flash硬件。 
  
B、MTD原始設備:原始設備層有兩部分組成,一部分是MTD原始設備的通用代碼,另一部分是各個特定的Flash的數據,例如分區。 
用於描述MTD原始設備的數據結構是mtd_info,這其中定義了大量的關於MTD的數據和操作函數。 mtd_table(mtdcore.c)則是所有MTD原始設備的列表,mtd_part(mtd_part.c)是用於表示MTD原始設備分區的結構,其中包含了mtd_info,因為每一個分區都是被看成一個MTD原始設備加在mtd_table中的,mtd_part.mtd_info中的大部分數據都從該分區的主分區mtd_part->master中獲得。 
在drivers/mtd/maps/子目錄下存放的是特定的flash的數據,每一個文件都描述了一塊板子上的flash。其中調用add_mtd_device()、del_mtd_device()建立/刪除mtd_info結構並將其加入/刪除mtd_table(或者調用add_mtd_partition()、del_mtd_partition()(mtdpart.c)建立/刪除mtd_part結構並將mtd_part.mtd_info加入/刪除mtd_table中)。 
  
C、MTD設備層:基於MTD原始設備,linux系統可以定義出MTD的塊設備(主設備號31)和字符設備(設備號90)。 MTD字符設備的定義在mtdchar.c中實現,通過註冊一系列file operation函數(lseek、open、close、read、write)。 MTD塊設備則是定義了一個描述MTD塊設備的結構mtdblk_dev,並聲明了一個名為mtdblks的指針數組,這數組中的每一個mtdblk_dev和mtd_table中的每一個mtd_info一一對應。 
  
D、設備節點:通過mknod在/dev子目錄下建立MTD字符設備節點(主設備號為90)和MTD塊設備節點(主設備號為31),通過訪問此設備節點即可訪問MTD字符設備和塊設備。 
  
E、根文件系統:在Bootloader中將JFFS(或JFFS2)的文件系統映像jffs.image(或jffs2.img)燒到flash的某一個分區中,在/arch/arm/mach-your/arch.c文件的your_fixup函數中將該分區作為根文件系統掛載。 
  
F、文件系統:內核啟動後,通過mount 命令可以將flash中的其餘分區作為文件系統掛載到mountpoint上。 
  
以上是從網上找到的一些資料,我只是斷斷續續地看過一些code,沒有系統地研究過,所以這裡只能講一下MTD原始設備層與FLASH硬件驅動之間的交互。 
  
一個MTD原始設備可以通過mtd_part分割成數個MTD原始設備註冊進mtd_table,mtd_table中的每個MTD原始設備都可以被註冊成一個MTD設備,有兩個函數可以完成這個工作,即add_mtd_device函數和add_mtd_partitions函數。 
  
其中add_mtd_device函數是把整個NAND FLASH註冊進MTD Core,而add_mtd_partitions函數則是把NAND FLASH的各個分區分別註冊進MTD Core。 
  
add_mtd_partitions函數的原型是:
int add_mtd_partitions(struct mtd_info *master,
            const struct mtd_partition *parts, int nbparts);


其中master就是這個MTD原始設備,parts即NAND的分區信息,nbparts指有幾個分區。那麼parts和nbparts怎麼來? caorr_platform_default_nand就是起這個作用了。
 static struct mtd_partition caorr_platform_default_nand[ ] = {
[0] = {
               .name = "Boot Strap",
               .offset = 0,
               .size = 0x40000,
    },
    [1] = {
               .name = "Bootloader",
               .offset = MTDPART_OFS_APPEND,
               .size = 0x40000,
    },
    [2] = {
               .name = "Partition Table",
               .offset = MTDPART_OFS_APPEND,
               .size = 0x40000,
},
    [3] = {
               .name = "Linux Kernel",
               .offset = MTDPART_OFS_APPEND,
               .size = 0x500000,
},
[4] = {
               .name = "Rootfs",
               .offset = MTDPART_OFS_APPEND,
               .size = MTDPART_SIZ_FULL,
},
 };


其中offset是分區開始的偏移地址,在後4個分區我們設為MTDPART_OFS_APPEND,表示緊接著上一個分區,MTD Core會自動計算和處理分區地址;size是分區的大小,在最後一個分區我們設為MTDPART_SIZ_FULL,表示這個NADN剩下的所有部分。 
  
這樣配置NAND的分區並不是唯一的,需要視具體的系統而定,我們可以在kernel中這樣顯式的指定,也可以使用bootloader傳給內核的參數進行配置。 
  
另外,MTD對NAND chip的讀寫主要分三部分: 
  
A、struct mtd_info中的讀寫函數,如read,write_oob等,這是MTD原始設備層與FLASH硬件層之間的接口; 
  
B、struct nand_ecc_ctrl中的讀寫函數,如read_page_raw,write_page等,主要用來做一些與ecc有關的操作; 
  
C、struct nand_chip中的讀寫函數,如read_buf,cmdfunc等,與具體的NAND controller相關,就是這部分函數與硬件交互,通常需要我們自己來實現。 (注:這裡提到的read,write_oob,cmdfunc等,其實都是些函數指針,所以這裡所說的函數,是指這些函數指針所指向的函數,以後本文將不再另做說明。) 
  
值得一提的是,struct nand_chip中的讀寫函數雖然與具體的NAND controller相關,但是MTD也為我們提供了default的讀寫函數,如果你的NAND controller比較通用(使用PIO模式),對NAND chip的讀寫與MTD提供的這些函數一致,就不必自己實現這些函數了。 
  
這三部分讀寫函數是相互配合著完成對NAND chip的讀寫的。首先,MTD上層需要讀寫NAND chip時,會調用struct mtd_info中的讀寫函數,接著struct mtd_info中的讀寫函數就會調用struct nand_chip或struct nand_ecc_ctrl中的讀寫函數,最後,若調用的是struct nand_ecc_ctrl中的讀寫函數,那麼它又會接著調用struct nand_chip中的讀寫函數。如下圖所示:
以讀NAND chip為例,講解一下這三部分讀寫函數的工作過程。 
  
首先,MTD上層會調用struct mtd_info中的讀page函數,即nand_read函數。 
  
接著nand_read函數會調用struct nand_chip中cmdfunc函數,這個cmdfunc函數與具體的NAND controller相關,它的作用是使NAND controller向NAND chip發出讀命令,NAND chip收到命令後,就會做好準備等待NAND controller下一步的讀取。 
  
接著nand_read函數又會調用struct nand_ecc_ctrl中的read_page函數,而read_page函數又會調用struct nand_chip中read_buf函數,從而真正把NAND chip中的數據讀取到buffer中(所以這個read_buf的意思其實應該是read into buffer ,另外,這個buffer是struct mtd_info中的nand_read函數傳下來的)。 
  
read_buf函數返回後,read_page函數就會對buffer中的數據做一些處理,比如校驗ecc,以及若數據有錯,就根據ecc對數據修正之類的,最後read_page函數返回到nand_read函數中。 
  
對NAND chip的其它操作,如寫,擦除等,都與讀操作類似。 

五、NAND驅動中的probe函數 
  
對於很多嵌入式Linux的外設driver來說,probe函數將是我們遇到的第一個與具體硬件打交道,同時也相對複雜的函數。而且根據我的經驗,對於很多外設的driver來說,只要能成功實現probe函數,那基本上完成這個外設的driver也就成功了一多半,基於MTD的NAND driver就是一個典型的例子。稍後就可以看到,在NAND driver的probe函數中,就已經涉及到了對NAND chip的讀寫。 
  
在基於MTD的NAND driver的probe函數中,主要可以分為兩部分內容,其一是與很多外設driver類似的一些工作,如申請地址,中斷,DMA等資源,kzalloc及初始化一些結構體,分配DMA用的內存等等;其二就是與MTD相關的一些特定的工作,在這裡我們將只描述第二部分內容。 
  
1、probe函數中與MTD相關的結構體 
  
在probe函數中,我們需要為三個與MTD相關的結構體分配內存以及初始化,它們是struct mtd_info、struct mtd_partition和struct nand_chip。其中前兩者已經在前面做過說明,在此略過,這裡只對struct nand_chip做一些介紹。 
  
struct nand_chip是一個與NAND chip密切相關的結構體,主要包含三方面內容: 
  
A. 指向一些操作NAND chip的函數的指針,稍後將對這些函數指針作一些說明; 
  
B. 表示NAND chip特性的成員變量,主要有: 
  
unsigned int options:與具體的NAND chip相關的一些選項,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等,至於這些選項具體表示什麼含義,可以參考<linux/mtd/nand.h>,那裡有較為詳細的說明; 
  
int page_shift:用位表示的NAND chip的page大小,如某片NAND chip的一個page有512 個 byte,那麼page_shift就是9; 
  
int phys_erase_shift:用位表示的NAND chip的每次可擦除的大小,如某片NAND chip每次可擦除16K byte(通常就是一個block的大小),那麼phys_erase_shift就是14; 
  
int bbt_erase_shift:用位表示的bad block table的大小,通常一個bbt佔用一個block,所以bbt_erase_shift通常與phys_erase_shift相等; 
  
int chip_shift:用位表示的NAND chip的容量; 
  
int numchips:表示系統中有多少片NAND chip; 
  
unsigned long chipsize:NAND chip的大小; 
  
int pagemask:計算page number時的掩碼,總是等於chipsize/page大小- 1; 
int pagebuf:用來保存當前讀取的NAND chip的page number,這樣一來,下次讀取的數據若還是屬於同一個page,就不必再從NAND chip讀取了,而是從data_buf中直接得到; 
  
int badblockpos:表示壞塊信息保存在oob中的第幾個 byte。在每個block的第一個page的oob中,通常用1或2個 byte來表示這是否為一個壞塊。對於絕大多數的NAND chip,若page size > 512,那麼壞塊信息從Byte 0開始存儲,否則就存儲在Byte 5,即第六個 byte。 
  
C. 與ecc,oob和bbt (bad block table)相關的一些結構體,對於壞塊及壞塊管理,將在稍後做專門介紹。 
  
2、對NAND chip進行實際操作的函數 
  
前面已經說過,MTD為我們提供了許多default的操作NAND的函數,這些函數與具體的硬件(即NAND controller)相關,而現有的NAND controller都有各自的特性和配置方式,MTD當然不可能為所有的NAND controller都提供一套這樣的函數,所以在MTD中定義的這些函數只適用於通用的NAND controller(使用PIO模式)。 
  
如果你的NAND controller在操作或者說讀寫NAND時有自己獨特的方式,那就必須自己定義適用於你的NAND controller的函數。一般來說,這些與硬件相關的函數都在struct nand_chip結構體中定義,或者應該說是給此結構體中的函數指針賦值。為了更好的理解,我想有必要對struct nand_chip中幾個重要的函數指針做一些說明。
struct nand_chip {
    void __iomem *IO_ADDR_R;
    void __iomem *IO_ADDR_W;

    uint8_t (*read_byte)(struct mtd_info *mtd);
    u16 (*read_word)(struct mtd_info *mtd);
    void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
    void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
    int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
    void (*select_chip)(struct mtd_info *mtd, int chip);
    int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
    int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
    void (*cmd_ctrl)(struct mtd_info *mtd, int dat, unsigned int ctrl);
    int (*dev_ready)(struct mtd_info *mtd);
    void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
    int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
    void (*erase_cmd)(struct mtd_info *mtd, int page);
    int (*scan_bbt)(struct mtd_info *mtd);
    int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
    int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf, int page, int cached, int raw);

    ……

    struct nand_ecc_ctrl ecc;

    ……
}

 
IO_ADDR_R和IO_ADDR_W:8位NAND chip的讀寫地址,如果你的NAND controller是用PIO模式與NAND chip交互,那麼只要把這兩個值賦上合適的地址,就完全可以使用MTD提供的default的讀寫函數來操作NAND chip了。所以這兩個變量視具體的NAND controller而定,不一定用得著; 
  
read_byte和read_word:從NAND chip讀一個 byte或一個字,通常MTD會在讀取NAND chip的ID,STATUS和OOB中的壞塊信息時調用這兩個函數,具體是這樣的流程,首先MTD調用cmdfunc函數,發起相應的命令,NAND chip收到命令後就會做好準備,最後MTD就會調用read_byte或read_word函數從NAND chip中讀取 chip的ID,STATUS或者OOB; 
  
read_buf、write_buf和verify_buf:分別是從NAND chip讀取數據到buffer,把buffer中的數據寫入到NAND chip,和從NAND chip中讀取數據並驗證。調用read_buf時的流程與read_byte和read_word類似,MTD也是先調用cmdfunc函數發起讀命令(如NAND_CMD_READ0命令),接著NAND chip收到命令後做好準備,最後MTD再調用read_buf函數把NAND chip中的數據讀取到buffer中。調用write_buf函數的流程與read_buf相似; 
  
select_chip:因為系統中可能有不止一片NAND chip,所以在對NAND chip進行操作前,需要這個函數來指定一片NAND chip; 
  
cmdfunc:向NAND chip發起命令; 
  
waitfunc:NAND chip在接收到命令後,並不一定能立即響應NAND controller的下一步動作,對有些命令,比如erase,program等命令,NAND chip需要一定的時間來完成,所以就需要這個waitfunc來等待NAND chip完成命令,並再次進入準備好狀態; 
write_page:把一個page的數據寫入NAND chip,這個函數一般不需我們實現,因為它會調用struct nand_ecc_ctrl中的write_page_raw或者write_page函數,關於這兩個函數將在稍後介紹。 
  
以上提到的這些函數指針,都是REPLACEABLE的,也就是說都是可以被替換的,根據你的NAND controller,如果你需要自己實現相應的函數,那麼只需要把你的函數賦值給這些函數指針就可以了,如果你沒有賦值,那麼MTD會把它自己定義的default的函數賦值給它們。 
  
順便提一下,以上所說的讀寫NAND chip的流程並不是唯一的,如果你的NAND controller在讀寫NAND chip時有自己獨特的方式,那麼完全可以按照自己的方式來做。就比如我們公司 chip的NAND controller,因為它使用DMA的方式從NAND chip中讀寫數據,所以在我的NAND driver中,讀數據的流程是這樣的:首先在cmdfunc函數中初始化DMA專用的buffer,配置NAND地址,發起命令等,在cmdfunc中我幾乎做了所有需要與NAND chip交互的事情,總之等cmdfunc函數返回後,NAND chip中的數據就已經在DMA專用的buffer中了,之後MTD會再調用read_buf函數,所以我的read_buf函數其實只是把數據從DMA專用的buffer中,拷貝到MTD提供的buffer中罷了。 
  
最後,struct nand_chip結構體中還包含了一個很重要的結構體,即struct struct nand_ecc_ctrl,該結構體中也定義了幾個很重要的函數指針。它的定義如下:
struct nand_ecc_ctrl {

    ……

    void (*hwctl)(struct mtd_info *mtd, int mode);
    int (*calculate)(struct mtd_info *mtd, const uint8_t *dat, uint8_t *ecc_code);
    int (*correct)(struct mtd_info *mtd, uint8_t *dat, uint8_t *read_ecc, uint8_t *calc_ecc);
    int (*read_page_raw)(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf);
    void (*write_page_raw)(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf);
    int (*read_page)(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf);
    void (*write_page)(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t *buf);
    int (*read_oob)(struct mtd_info *mtd, struct nand_chip *chip, int page, int sndcmd);
    int (*write_oob)(struct mtd_info *mtd, struct nand_chip *chip, int page);
};


hwctl:這個函數用來控制硬件產生ecc,其實它主要的工作就是控制NAND controller向NAND chip發出NAND_ECC_READ、NAND_ECC_WRITE和NAND_ECC_READSYN等命令,與struct nand_chip結構體中的cmdfunc類似,只不過發起的命令是ECC相關的罷了; 
  
calculate:根據data計算ecc值; 
  
correct:根據ecc值,判斷讀寫數據時是否有錯誤發生,若有錯,則立即試著糾正,糾正失敗則返回錯誤; 
  
read_page_raw和write_page_raw:從NAND chip中讀取一個page的原始數據和向NAND chip寫入一個page的原始數據,所謂的原始數據,即不對讀寫的數據做ecc處理,該讀寫什麼值就讀寫什麼值。另外,這兩個函數會讀寫整個page中的所有內容,即不但會讀寫一個page中MAIN部分,還會讀寫OOB部分。 
  
read_page和write_page:與read_page_raw和write_page_raw類似,但不同的是,read_page和write_page在讀寫過程中會加入ecc的計算,校驗,和糾正等處理。 
read_oob和write_oob:讀寫oob中的內容,不包括MAIN部分。 
  
其實,以上提到的這幾個read_xxx和write_xxx函數,最終都會調用struct nand_chip中的read_buf和write_buf這兩個函數,所以如果沒有特殊需求的話,我認為不必自己實現,使用MTD提供的default的函數即可。 
  
為進一步理解各函數之間的調用關係,這裡提供一張從網上找到的write NAND chip的流程圖,僅供參考:


3、probe函數的工作流程 
  
由前面的說明可知,我們在要對NAND chip進行實際操作前已經為struct mtd_info、struct mtd_partition和struct nand_chip這三個結構體分配好了內存,接下來就要為它們做一些初始化工作。 
  
其中,我們需要為struct mtd_info所做的初始化工作並不多,因為MTD Core會在稍後為它做很多初始化工作,但是有一點必須由我們來做,那就是把指向struct nand_chip結構體的指針賦給struct mtd_info的priv成員變量,因為MTD Core中很多函數之間的調用都只傳遞struct mtd_info,它需要通過priv成員變量得到struct nand_chip。 
  
對於struct mtd_partition的賦值,前面已經做過介紹,這裡不再贅述。 
  
所以,為struct nand_chip的初始化,才是我們在probe函數中的主要工作。其實這裡所謂的初始化,主要就是為struct nand_chip結構體中的眾多函數指針賦值。 
  
前面已經為struct nand_chip結構體中的函數指針做過說明,想必你已經知道這些函數指針所指向的函數具體實現什麼樣的功能,負責做什麼事情。那麼如何讓這些函數實現既定的功能呢?這就與具體的NAND controller有關了,實在沒辦法多說。根據你的NAND controller,也許你需要做很多工作,為struct nand_chip中的每一個函數指針實現特定的函數,也或許你只需要為IO_ADDR_R和IO_ADDR_W賦上地址,其它則什麼都不做,利用MTD提供的函數即可。 
  
現在假定你定義好了所有需要的與NAND chip交互的函數,並已經把它們賦給了struct nand_chip結構體中的函數指針。當然,此時你還不能保證這些函數一定能正確工作,但是沒有關係,probe函數在接下來的工作中會調用到幾乎所有的這些函數,你可以依次來驗證和調試。當你的probe函數能順利通過後,那麼這些函數也就基本沒什麼問題了,你的NAND驅動也就已經完成了80%了。 
  
接下來,probe函數就會開始與NAND chip進行交互了,它要做的事情主要包括這幾個方面:讀取NAND chip的ID,然後查表得到這片NAND chip的如廠商,page size,erase size以及chip size等信息,接著,根據struct nand_chip中options的值的不同,或者在NAND chip中的特定位置查找bad block table,或者scan整個NAND chip,並在內存中建立bad block table。說起來複雜,但其實所有的這些動作,都可以在MTD提供的一個叫做nand_scan的函數中完成。 
  
我雖然研讀過nand_scan函數中的代碼,但不會在這裡做情景分析式的詳細說明,若你對這部分代碼的實現感興趣,可以參考以下兩篇文章:
http://blog.csdn.net/binghuiliang/archive/2008/03/07/2156927.aspx
http://blog.csdn.net/binghuiliang/archive/2008/03/07/2156929.aspx

關於nand_scan函數,在使用時我想有一個地方值得一提。 
  
nand_scan函數主要有兩個兩個函數組成,即nand_scan_ident函數和nand_scan_tail函數。其中nand_scan_ident函數會讀取NAND chip的ID,而nand_scan_tail函數則會查找或者建立bbt (bad block table)。 
  
在一般情況下,我們可以直接調用nand_scan函數來完成所要做的工作,然而卻並不總是如此,在有些情況下,我們必須分別調用nand_scan_ident函數和nand_scan_tail函數,因為在這兩者之間,我們還需要做一些額外的工作。那麼這裡所謂的額外的工作,具體是做什麼呢? 
  
在《基於MTD的NAND驅動開發(一)》中介紹過一個叫做struct nand_ecclayout的結構體,它用來定義ecc在oob中的佈局。對於small page(每頁512 Byte)和big page(每頁2048 Byte)的兩種NAND chip,它們的ecc在oob中的佈局不盡相同。 
  
如果你的driver中對這兩種 chip的ecc佈局與MTD中定義的default的佈局一致,那麼就很方便,直接調用nand_scan函數即可。但如果不是,那你就需要為這兩種不同的NAND chip分別定義你的ecc佈局。於是問題來了,因為我們在調用nand_scan_ident函數之前,是不知道系統中的NAND chip是small page類型的,還是big page類型,然而在調用nand_scan_tail函數之前,卻必須確定NAND chip的oob佈局(包括ecc佈局和壞塊信息pattern),因為nand_scan_tail函數在讀取oob以及處理ecc時需要這個信息。 
所以在這種情況下,我們就需要首先調用nand_scan_ident函數,它會調用一個叫做nand_get_flash_type的函數,MTD就是在這個函數中讀取NAND chip的ID,然後就能查表(即全局變量nand_flash_ids)知道這片NAND chip的類型(即writesize的大小)了。 
  
接下來,你就可以在你的NAND驅動中,根據writesize的大小來區分ecc的佈局了。最後,我們就可以順利地調用nand_scan_tail函數了。

六、NAND驅動中的壞塊管理 
  
由於NAND Flash的現有工藝不能保證NAND的Memory Array在其生命週期中保持性能的可靠,因此在NAND chip出廠的時候,廠家只能保證block 0不是壞塊,對於其它block,則均有可能存在壞塊,而且NAND chip在使用的過程中也很容易產生壞塊。因此,我們在讀寫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 chip中,另一種是把bbt存儲在內存中。對於前者,好處是驅動加載更快,因為它只會在第一次加載NAND驅動時掃描整個NAND chip,然後在NAND chip的某個block中建立bbt,壞處是需要至少消耗NAND chip一個block的存儲容量;而對於後者,好處是不會耗用NAND chip的容量,壞處是驅動加載稍慢,因為存儲在內存中的bbt每次斷電後都不會保存,所以在每次加載NAND驅動時,都會掃描整個NAND chip,以便建立bbt。 

如果你係統中的NAND chip容量不是太大的話,我建議還是把bbt存儲在內存中比較好,因為根據本人的使用經驗,對一塊容量為2G bits的NAND chip,分別採用這兩種存儲方式的驅動的加載速度相差不大,甚至幾乎感覺不出來。 

建立bbt後,以後在做擦除等操作時,就不用每次都去驗證當前block是否是個壞塊了,因為從bbt中就可以得到這個信息。另外,若在讀寫等操作時,發現產生了新的壞塊,那麼除了標誌這個block是個壞塊外,也還需更新bbt。 

接下來,介紹一下MTD是如何查找或者建立bbt的。 

1、MTD中與bbt相關的結構體 

struct nand_chip中的scan_bbt函數指針所指向的函數,即nand_default_bbt函數會首先檢查struct nand_chip中options成員變量,如果當前NAND chip是AG-AND類型的,會強制把bbt存儲在NAND chip中,因為這種類型的NAND chip中含有廠家標註的“好塊”信息,擦除這些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 chip中,或者把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 chip中查找bbt是否存在,若存在,就把bbt的內容讀出來並保存到bbt指向的內存中,若不存在,則在bbt指向的內存中建立bbt,最後把它寫入到NAND chip中去。 

bbt_td、bbt_md和badblock_pattern就是在nand_default_bbt函數中賦值的3個結構體。它們雖然是相同的結構體類型,但卻有不同的作用和含義。 
其中bbt_td和bbt_md是主bbt和鏡像bbt的描述符(鏡像bbt主要用來對bbt的update和備份),它們只在把bbt存儲在NAND chip的情況下使用,用來從NAND chip中查找bbt。若bbt存儲在內存中,bbt_td和bbt_md將會被賦值為NULL。 

badblock_pattern就是壞塊信息的pattern,其中定義了壞塊信息在oob中的存儲位置,以及內容(即用什麼值表示這個block是個壞塊)。 

通常用1或2個 byte來標誌一個block是否為壞塊,這1或2個 byte就是壞塊信息,如果這1或2個 byte的內容是0xff,那就說明這個block是好的,否則就是壞塊。對於壞塊信息在NAND chip中的存儲位置,small page(每頁512 Byte)和big page(每頁2048 Byte)的兩種NAND chip不盡相同。一般來說,small page的NAND chip,壞塊信息存儲在每個block的第一個page的oob的第六個 byte中,而big page的NAND chip,壞塊信息存儲在每個block的第一個page的oob的第1和第2個 byte中。 

我不能確定是否所有的NAND chip都是如此佈局,但應該絕大多數NAND chip是這樣的,不過,即使某種NAND chip的壞塊信息不是這樣的存儲方式也沒關係,因為我們可以在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 chip,我們可以為每一片NAND chip建立一個bbt,也可以只在其中一片NAND chip中建立唯一的一個bbt,所以這裡的pages是個維數為NAND_MAX_CHIPS的數值,用來保存每一片NAND chip的bbt位置。當然,若只建立了一個bbt,那麼就只使用pages[0]。 

offs、len和pattern:MTD會從oob的offs中讀出len長度的內容,然後與pattern指針指向的內容做比較,若相等,則表示找到了bbt,或者表示這個block是好的。 

veroffs和version:bbt專用。 MTD會從oob的veroffs中讀出一個 byte的內容,作為bbt的版本值保存在version中。 

maxblocks:bbt專用。 MTD在查找bbt的時候,不會查找NAND chip中所有的block,而是最多查找maxblocks個block。 

2、bbt存儲在內存中時的工作流程 

前文說過,不管bbt是存儲在NAND chip中,還是存儲在內存中,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 chip所有的block,讀取每個block中第一個page的oob內容,然後根據oob中的壞塊信息建立起bbt,可以參見上節關於struct nand_bbt_descr中的offs、len和pattern成員變量的解釋。 

3、bbt存儲在NAND chip時的工作流程 

相對於把bbt存儲在內存中,這種方式的工作流程稍顯複雜一點。 

nand_scan_bbt函數首先從NAND chip中讀取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 chip的maxblocks(請見上文關於struct nand_bbt_descr中maxblocks的說明)個block中查找bbt是否存在,若找到,就可以讀取bbt了。 

MTD查找bbt的過程為:如果你在bbt_td和bbt_md的options 成員變量中定義了NAND_BBT_LASTBLOCK,那麼MTD就會從NAND chip的最後一個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 chip,並且為每一片NAND chip都建立一個bbt,那麼就會在每片NAND chip上重複以上過程。 

接著,nand_scan_bbt函數會調用check_create函數,該函數會判斷是否找到了bbt,其實就是判斷bbt_td或者bbt_md中pages成員變量的值是否有效。若找到了bbt,就會把bbt從NAND chip中讀取出來,並保存到struct nand_chip中bbt指針指向的內存中;若沒找到,就會調用create_bbt函數建立bbt(與bbt存儲在內存中時情況一樣),同時把bbt寫入到NAND chip中去。 
  
  
七、總結 

本文沒有糾纏於MTD中每一句code怎麼實現這種細節,對於開發一個基於NAND的驅動來說,並不需要對MTD中的每一條代碼都徹底細緻的研究,只要能在總體或者大局上有所把握,能了解MTD中主要函數的工作流程,也就可以了。