文档背景说明及相关资源:
- 基于mini2440 linux kernel 2.6版本.
- 执行情景分析法进行代码阅读: https://blog.csdn.net/weixin_45154862/article/details/129124851
- 文档原稿: https://gitee.com/suiren/mtd_char_device_read/blob/master/mtd_read.c
- MTD字符设备测试程序源码: https://gitee.com/suiren/mtd_char_device_read/blob/master/app/mtd_rw.c
mtd框架說明:
- 基於struct mtd_info結構體, 用於表示整個flash設備, 也可以用於表示一個mtd分區;
- struct nand_chip 結構體 保存nandflash的信息,比如頁大小, 擦除大小等, 也保存各種讀,寫,擦除,chip select等函數指針;
從mini2440的nandflash控制器驅動來看, 只有代表整個整個flash設備的mtd_info結構體的priv成員才指向nand_chip結構體.
代表mtd分區所執行的抽象等級更高的讀寫等函數, 最終會調用代表整個flash設備的mtd_info結構體指向的nand_chip結構體. - struct mtd_part slave結構體, 每個mtd分區都分配這麼一個結構體,
struct mtd_info 结构体, 每个分区都对应一个mtd_info.
slave與mtd_info結構體關係為:
slave->mtd. - 控制器的驅動中, 有使用到的數組和隊列:
mtd_table[], 保存所有代表mtd分區的mtd_info結構體指針; mtd_table為mtd框架全局變量, 也包含其他flash設備的mtd分區.
mtd_partitions隊列, mtd_part salve結構體 加入該隊列. 即表示這個隊列內包含所有的mtd分區.
mtd_partitions隊列為mtd框架全局變量, 也包含其他flash設備的mtd分區.
MTD框架结构体及其成员说明:
struct s3c2410_platform_nand *plat <tag1>
->int nr_sets = 1; <obj1>
->struct s3c2410_nand_set *sets; <point to obj6>
struct s3c2410_nand_info *info; <obj4>
->void __iomem *sel_reg = regs + S3C2440_NFCONT; <tag14>
->int sel_bit = S3C2440_NFCONT_nFCE; <tag13>
->int mtd_count; <tag3> <cp from obj1>
->struct nand_hw_control controller; <obj3>
->struct nand_chip *active; <tag55> <point to obj5>
->struct s3c2410_nand_mtd *mtds; <tag4> <obj2> <nmtd>
->struct s3c2410_nand_info *info; <tag8> <point to obj4>
->struct s3c2410_nand_set *set; <tag10> <point to obj6>
->struct mtd_info mtd; <obj11> <master>
->struct nand_ecclayout *ecclayout; <tag45> <point to obj12>
->uint32_t flags; <tag44> <MTD_CAP_NANDFLASH>
->u_char type; <tag43> <MTD_NANDFLASH>
->int subpage_sft; <tag39> <2>
->uint32_t oobavail; /* Available OOB bytes per block*/ <tag36> <cp from obj14>
->uint64_t size; /*Total size of the MTD*/ <tag29> <256*2^20> <256MB>
->void *priv; <tag9> <point to obj5>
->const char *name; <tag16> <point to obj8->name>
->uint32_t writesize; <tag18> <2048> <"写大小即是页大小">
->uint32_t oobsize; <tag19> <64> <"每個頁oob的size">
->uint32_t erasesize; <tag20> <"擦除大小 即是 块大小">
->struct mtd_ecc_stats ecc_stats;
->__u32 corrected; <tag112> /* 記錄每個mtd分區累次讀頁數據時, 總的糾正數據ECC錯誤和ECC本身錯誤的次數. */
->__u32 failed; <tag113> /* 記錄每個mtd分區累次讀頁數據時, 總的ECC錯誤且不能糾正的次數 */
->struct nand_chip chip; <obj5>
->struct nand_bbt_descr *bbt_td;
->uint8_t *bbt; <tag47> <tag61><"设置该bbt表, 此为坏块表">
->struct nand_bbt_descr *badblock_pattern; <tag46> <point to obj16>
->int pagebuf; <tag42> <-1>
->nand_state_t state; <tag41> <FL_READY> <tag56> <FL_READING>
->int subpagesize; <tag40>
/* oob_poi 用於臨時保存一個頁的oob數據 */
->uint8_t *oob_poi; <tag33> <point to obj10+ (obj11->writesize)> <obj19>
->struct nand_buffers *buffers; <tag32>
/* databuf 用於臨時保存一個頁的數據: 正常數據+ oob數據 */
->uint8_t databuf[NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE]; <obj10>
->uint8_t ecccalc[NAND_MAX_OOBSIZE]; <tag99> <"存放根据读出来的数据计算出的ecc">
->uint8_t ecccode[NAND_MAX_OOBSIZE]; <tag100> <"存放 从nandflash的oob读出来的ecc">
->int numchips; <tag27><1>
->int badblockpos; <tag25> <0>
->int chip_shift; <tag24>
->int bbt_erase_shift; <tag23> <value == 17> <"壞塊擦除大小 128KB">
->int pagemask; <tag22> <131071>
->int page_shift; <tag21> <value == 11> <"頁大小 2KB">
->uint64_t chipsize; <tag17> <256 x 2^20>
->unsigned int options; <tag12> <tag26>
->int chip_delay = 50; <tag5>
->void *priv; <tag6> <point to obj2>
/* 在最初讀flash id等信息時, 使用 nand_command()函數, 後續都使用 nand_command_lp()函數 */
->void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr) <tag116 nand_command()>
<tag117 nand_command_lp()>
->struct nand_hw_control *controller; <tag7> <point to obj3>
->struct mtd_oob_ops ops;
->size_t len; <tag98> <user read length>
->uint8_t *datbuf; <tag97> <point to user_read_buffer> <"存放数据buffer">
->struct nand_ecc_ctrl ecc;
->int total; <tag38>
<"一个ecc 保护 ecc->size字节(256)的数据. 一次读一个页, 需要读取 页大小/ecc->size个 ecc. 即steps值">
->int steps; <tag37>
->nand_ecc_modes_t mode= NAND_ECC_HW ; <tag11>
->int size; <tag30> <256>
->int bytes; <tag31> <3>
->struct nand_ecclayout *layout; <tag34> <point to obj12>
struct mtd_oob_ops ops; <tag50> <"局部变量"> <obj20>
size_t ooblen; <tag51> <64>
uint8_t *oobbuf; <tag52> <point to obj10> <tag60> <cp from obj19+ obj20->oobffs(0)>
uint32_t ooboffs; <0>
mtd_oob_mode_t mode; <tag53> <MTD_OOB_PLACE>
size_t oobretlen; <tag62> <64>
struct mtd_part *slave; <tag65> <"slave 数目 对应 分区数目"> <obj43>
->uint64_t offset; <tag72> <cp from obj30->offset>
->struct mtd_info *master; <tag71> <point to obj11>
->struct list_head list; <tag66> <add to list1>
->struct mtd_info mtd; <obj29> <tag88> <add in array1>
->u_char type; <tag67> <cp from obj11->type>
->uint32_t flags; <tag68> <cp from obj11->flags>
->uint32_t writesize; <tag68>
->uint64_t size; /* Total size of the MTD*/ <tag69> <cp from obj30->size>
->const char *name; <tag70> <cp from obj30->name>
->uint32_t erasesize; <tag72> <cp from obj11->erasesize>
->struct nand_ecclayout *ecclayout; <tag74> <point to obj11->ecclayout>
->struct backing_dev_info *backing_dev_info; <tag78> <point to obj25>
->int index; <tag79>
->unsigned int erasesize_shift; <tag80>
->struct mtd_ecc_stats ecc_stats;
->__u32 badblocks; <tag77>
->__u32 corrected; <tag110> /* 記錄累次讀頁數據時, 糾正數據ECC錯誤和ECC本身錯誤的次數. */
->__u32 failed; <tag111> /* 記錄累次讀頁數據時, ECC錯誤且不能糾正的次數 */
->struct device dev;
->struct device_type type; <tag81> <point to obj27>
->struct class mtd_class; <tag82> <point to obj28>
->devt <"mtd 字符设备 设备号"> <tag83>
->p->driver_data <tag85> <point to obj29>
->struct kobject *kobj
->name <tag84> <"mtd%d">
struct file *file <tag89>
->f_mapping->backing_dev_info <tag90> <point to obj25>
->void *private_data <tag94> <point to obj39>
struct mtd_file_info *mfi; <tag91> <obj39>
->struct mtd_info *mtd; <tag93> <point to obj29>
char *kbuf; <read buffer> <tag95>
struct mtd_info *mtd_table[MAX_MTD_DEVICES]; <array1> <"最多32个mtd 字符设备">
struct erase_info_user einfo32; <tag102> <cp from user param> <obj40>
->__u32 start;
->__u32 length;
struct erase_info *erase; <tag101>
->struct mtd_info *mtd; <tag104> <point to obj29>
->uint64_t addr; <tag103> <cp from obj40->start> <tag107><addr += obj43->offset>
->uint64_t len;
->u_long priv; <tag105> <point to obj41>
wait_queue_head_t waitq; <obj41> <tag106>
static struct class mtd_class = { <obj28>
.name = "mtd",
.owner = THIS_MODULE,
.suspend = mtd_cls_suspend,
.resume = mtd_cls_resume,
};
static struct device_type mtd_devtype = { <obj27>
.name = "mtd",
.groups = mtd_groups,
.release = mtd_release,
};
struct backing_dev_info mtd_bdi_unmappable = { <obj25>
.capabilities = BDI_CAP_MAP_COPY,
};
# drivers/mtdmtdpart.c
static LIST_HEAD(mtd_partitions); <list1>
#end drivers/mtdmtdpart.c
struct nand_flash_dev *type = {"NAND 256MiB 3,3V 8-bit",bit0xDA, 0, 256, 0, LP_OPTIONS}; <tag15> <obj8>
static struct nand_ecclayout nand_oob_64; <obj12>
->__u32 oobavail; <tag35> <0 + obj13[0].length> <obj14>
->struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; <obj13>
.eccbytes = 24,
.eccpos = {
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63},
.oobfree = {
{.offset = 2,
.length = 38}}
static struct nand_bbt_descr largepage_memorybased <obj16>
.options = 0, <tag49>
.offs = 0, /* offs 表明 用於表示是否壞塊的信息在 oob的偏移. */
.len = 2,
.pattern = scan_ff_pattern /* 與 oob的是否壞塊信息 比較, 不相同即為壞塊 */
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
nandflash 硬件說明:
- 每個頁上的OOB數據是誰寫入的?
執行寫頁數據函數時, 函數會計算要寫入的頁數據的ECC, 並且因為頁所在的塊是好快, 因此OOB數據內的標準壞塊好壞的內容也同時被設置.
這是未指定OOB數據內容時的寫頁數據操作流程.
在linux內核的壞塊掃描函數執行時, 每個頁的OOB數據部分都不為空, 是因為在使用uboot燒錄文件系統時,
uboot的寫頁數據函數一樣會計算ECC等等並寫入flash的OOB, 或者有些文件系統指定了OOB數據的內容.
nandflash控制器初始化執行流程说明:
- 設置nandflash控制器的flash命令的執行時序, 使能nandflash控制器;
- 為 struct mtd_info master分配內存, 該結構體代表整個nandflash.
- 設置struct nand_chip *chip內的各種讀,寫,擦除,chip select, ecc 計算, 等函數指針的值;
struct mtd_info master與 struct nand_chip chip的關係為: master->priv = chip. - 發送 READ_ID指令, 讀flash 的信息. 這一步可以獲取flash的ID, 以及寫大小(頁大小),擦除大小(塊大小),每個頁的OOB大小.
- 建立壞塊表, 對應nand_scan_tail()函數:
4.1 選擇oob的數據佈局類型, struct nand_chip 的成員ecc.layout. (chip->ecc.layout).
nandflash上每個頁的OOB數據,包含該塊是否壞塊的信息和包含當前頁的數據的ECC信息,不同的OOB數據佈局, 這些信息的位置不同.
4.2 設置 struct nand_chip 成員ecc的ecc讀寫函數;
4.3 在nand_scan_tail()=>nand_default_bbt()函數里. 根據struct nand_chip的成員options, 若其設置了NAND_USE_FLASH_BBT標志位,
則使用保存在nandflash上壞塊表(bbt), 否則就是在內存上建立壞塊表(bbt).
4.4 在nand_scan_tail()=>nand_default_bbt()=>create_bbt()函數里, 進行壞塊掃描. 根據struct nand_chip的成員options,
若其設置了NAND_BBT_SCANALLPAGES標志位, 則執行scan_block_full(), 讀取所有頁的數據,
檢查所有頁的OOB數據來判斷頁所在的塊是否為壞塊.
若未設置NAND_BBT_SCANALLPAGES標志位,則執行scan_block_fast(), 僅讀取每一個塊的第一個頁的數據, 根據頁上的OOB數據判斷是否為壞塊.
4.5 根據struct nand_chip的成員 badblock_pattern->pattern, pattern指向scan_ff_pattern[] = { 0xff, 0xff }.
將讀取到的OOB數據內的存儲壞塊標誌的信息, 與pattern對比, 不相同表示為一個壞塊.
將壞塊信息寫入struct nand_flash 的成員 bbt數組, 數據類型uint8_t.
bbt數組佈局: bbt[x], 一個數組成員描述4個塊的壞塊信息, 即每兩位描述一個塊.bit0,bit1表示blockX, bit2,bit3表示blockX+1等等.
bitX,bitX+1 值為0b11 表示此為壞塊.
至此完成壞塊表的建立
5. 對應add_mtd_partitions(), 根據平台設備的信息, 或者有多少個分區,和分區大小等信息.
有多少個分區, 就執行多少次的 add_one_partition()函數.
5.1 對應add_mtd_partitions()=>add_one_partition()函數, 分配struct mtd_part slave結構體內存, 將salve加入全局隊列mtd_partitions.
將struct mtd_info master 的寫大小,擦除大小等信息複製到slave->mtd裡面, salve->mtd的類型也是 struct mtd_info,
前者 master代表整個flash, 而salve->mtd代表一個mtd分區. slave->mtd->size 保存該分區的size.
根據壞塊表的分區的地址範圍, 統計出當前分區的壞塊數目, 寫入slave->mtd.ecc_stats.badblocks.
5.2 對應 add_mtd_partitions()=>add_one_partition()=>add_mtd_device()函數.
將slave->mtd 保存進全局數組mtd_table[], 後續用戶執行open()函數打開mtd字符設備, 便根據字符設備的次設備號從mtd_table內,
找到對應的struct mtd_info mtd結構體.
註冊MTD字符設備, 名為"mtdX"和"mtdXro", X為mtd分區的順序號加上mtd_table[]已有成員的序號,
這兩個字符設備都對應則同一個struct mdt_info結構體.
static int s3c24xx_nand_probe(struct platform_device *pdev)
{
struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)
{
return dev->dev.platform_data;
}
enum s3c_cpu_type cpu_type;
struct s3c2410_nand_info *info;
struct s3c2410_nand_mtd *nmtd;
struct s3c2410_nand_set *sets;
struct resource *res;
int err = 0;
int size;
int nr_sets;
int setno;
cpu_type = platform_get_device_id(pdev)->driver_data;
pr_debug("s3c2410_nand_probe(%p)\n", pdev);
info = kmalloc(sizeof(*info), GFP_KERNEL);
memset(info, 0, sizeof(*info));
platform_set_drvdata(pdev, info);
spin_lock_init(&info->controller.lock);
init_waitqueue_head(&info->controller.wq);
/* get the clock source and enable it */
info->clk = clk_get(&pdev->dev, "nand");
clk_enable(info->clk);
/* allocate and map the resource */
/* currently we assume we have the one resource */
res = pdev->resource;
size = res->end - res->start + 1;
info->area = request_mem_region(res->start, size, pdev->name);
info->device = &pdev->dev;
info->platform = plat;
info->regs = ioremap(res->start, size);
info->cpu_type = cpu_type;
dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);
/* initialise the hardware */
err = s3c2410_nand_inithw(info);
static int s3c2410_nand_inithw(struct s3c2410_nand_info *info)
{
int ret;
/* 設置nandflash控制器的時序. */
ret = s3c2410_nand_setrate(info);
static int s3c2410_nand_setrate(struct s3c2410_nand_info *info)
{
struct s3c2410_platform_nand *plat = info->platform; <tag1>
int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
int tacls, twrph0, twrph1;
unsigned long clkrate = clk_get_rate(info->clk);
unsigned long uninitialized_var(set), cfg, uninitialized_var(mask);
unsigned long flags;
/* calculate the timing information for the controller */
info->clk_rate = clkrate;
clkrate /= 1000; /* turn clock into kHz for ease of use */
if (plat != NULL) { //
tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
{
int result;
result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ);
pr_debug("result %d from %ld, %d\n", result, clk, wanted);
if (result > max) {
printk("%d ns is too big for current clock rate %ld\n", wanted, clk);
return -1;
}
if (result < 1)
result = 1;
return result;
}
} else {
/* default timings */
tacls = tacls_max;
twrph0 = 8;
twrph1 = 8;
}
if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
dev_err(info->device, "cannot get suitable timings\n");
return -EINVAL;
}
/* s3c24xx-nand s3c2440-nand: Tacls=3, 29ns Twrph0=7 69ns, Twrph1=3 29ns */
dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));
switch (info->cpu_type) {
case TYPE_S3C2410:
mask = (S3C2410_NFCONF_TACLS(3) |
S3C2410_NFCONF_TWRPH0(7) |
S3C2410_NFCONF_TWRPH1(7));
set = S3C2410_NFCONF_EN;
set |= S3C2410_NFCONF_TACLS(tacls - 1);
set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
break;
case TYPE_S3C2440:
case TYPE_S3C2412: //
mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) |
S3C2440_NFCONF_TWRPH0(7) |
S3C2440_NFCONF_TWRPH1(7));
/* 设置 tacls twrphx 阶段的持续时间 */
set = S3C2440_NFCONF_TACLS(tacls - 1);
set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
break;
default:
BUG();
}
local_irq_save(flags);
/* NAND Flash 配置寄存器 */
cfg = readl(info->regs + S3C2410_NFCONF);
cfg &= ~mask;
cfg |= set;
writel(cfg, info->regs + S3C2410_NFCONF);
local_irq_restore(flags);
dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);
return 0; //
}
switch (info->cpu_type) {
case TYPE_S3C2410:
default:
break;
case TYPE_S3C2440:
case TYPE_S3C2412: //
/* enable the controller and de-assert nFCE */
/* NAND Flash 控制器使能 */
writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
}
return 0; //
}
sets = (plat != NULL) ? plat->sets : NULL;
nr_sets = (plat != NULL) ? plat->nr_sets : 1;
info->mtd_count = nr_sets; <tag3>
/* allocate our information */
size = nr_sets * sizeof(*info->mtds);
info->mtds = kmalloc(size, GFP_KERNEL); <tag4>
memset(info->mtds, 0, size);
/* initialise all possible chips */
nmtd = info->mtds;
//for (setno = 0; setno < nr_sets; setno++, nmtd++)
setno = 0;
//setno < nr_sets;
{
pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);
s3c2410_nand_init_chip(info, nmtd, sets);
static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
struct s3c2410_nand_mtd *nmtd,
struct s3c2410_nand_set *set)
{
struct nand_chip *chip = &nmtd->chip;
void __iomem *regs = info->regs;
chip->write_buf = s3c2410_nand_write_buf;
chip->read_buf = s3c2410_nand_read_buf;
chip->select_chip = s3c2410_nand_select_chip;
chip->chip_delay = 50; <tag5>
chip->priv = nmtd; <tag6>
chip->options = 0; <tag12>
chip->controller = &info->controller; <tag7>
switch (info->cpu_type) {
case TYPE_S3C2410:
chip->IO_ADDR_W = regs + S3C2410_NFDATA;
info->sel_reg = regs + S3C2410_NFCONF;
info->sel_bit = S3C2410_NFCONF_nFCE;
chip->cmd_ctrl = s3c2410_nand_hwcontrol;
chip->dev_ready = s3c2410_nand_devready;
break;
case TYPE_S3C2440: //
chip->IO_ADDR_W = regs + S3C2440_NFDATA;
info->sel_reg = regs + S3C2440_NFCONT; <tag14>
info->sel_bit = S3C2440_NFCONT_nFCE; <tag13>
chip->cmd_ctrl = s3c2440_nand_hwcontrol;
chip->dev_ready = s3c2440_nand_devready;
chip->read_buf = s3c2440_nand_read_buf;
chip->write_buf = s3c2440_nand_write_buf;
break;
case TYPE_S3C2412:
chip->IO_ADDR_W = regs + S3C2440_NFDATA;
info->sel_reg = regs + S3C2440_NFCONT;
info->sel_bit = S3C2412_NFCONT_nFCE0;
chip->cmd_ctrl = s3c2440_nand_hwcontrol;
chip->dev_ready = s3c2412_nand_devready;
if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)
dev_info(info->device, "System booted from NAND\n");
break;
}
chip->IO_ADDR_R = chip->IO_ADDR_W;
nmtd->info = info; <tag8>
nmtd->mtd.priv = chip; <tag9>
nmtd->mtd.owner = THIS_MODULE;
nmtd->set = set; <tag10>
if (hardware_ecc) { //
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
chip->ecc.correct = s3c2410_nand_correct_data;
chip->ecc.mode = NAND_ECC_HW; <tag11>
switch (info->cpu_type) {
case TYPE_S3C2410:
chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
break;
case TYPE_S3C2412:
chip->ecc.hwctl = s3c2412_nand_enable_hwecc;
chip->ecc.calculate = s3c2412_nand_calculate_ecc;
break;
case TYPE_S3C2440: //
chip->ecc.hwctl = s3c2440_nand_enable_hwecc;
chip->ecc.calculate = s3c2440_nand_calculate_ecc;
break;
}
} else {
chip->ecc.mode = NAND_ECC_SOFT;
}
if (set->ecc_layout != NULL) {
chip->ecc.layout = set->ecc_layout;
}
if (set->disable_ecc) {
chip->ecc.mode = NAND_ECC_NONE;
}
switch (chip->ecc.mode) {
case NAND_ECC_NONE:
dev_info(info->device, "NAND ECC disabled\n");
break;
case NAND_ECC_SOFT:
dev_info(info->device, "NAND soft ECC\n");
break;
case NAND_ECC_HW:
dev_info(info->device, "NAND hardware ECC\n");
break;
default:
dev_info(info->device, "NAND ECC UNKNOWN\n");
break;
}
/* If you use u-boot BBT creation code, specifying this flag will
* let the kernel fish out the BBT from the NAND, and also skip the
* full NAND scan that can take 1/2s or so. Little things... */
if (set->flash_bbt) {
chip->options |= NAND_USE_FLASH_BBT | NAND_SKIP_BBTSCAN;
}
}
nmtd->scan_res = nand_scan_ident(&nmtd->mtd, (sets) ? sets->nr_chips : 1);
int nand_scan_ident(struct mtd_info *mtd, int maxchips)
{
int i, busw, nand_maf_id;
struct nand_chip *chip = mtd->priv;
struct nand_flash_dev *type;
/* Get buswidth to select the correct functions */
busw = chip->options & NAND_BUSWIDTH_16;
/* Set the default functions */
nand_set_defaults(chip, busw);
static void nand_set_defaults(struct nand_chip *chip, int busw)
{
/* check for proper chip_delay setup, set 20us if not */
if (!chip->chip_delay)
chip->chip_delay = 20;
/* check, if a user supplied command function given */
if (chip->cmdfunc == NULL)
chip->cmdfunc = nand_command; <tag116>
/* check, if a user supplied wait function given */
if (chip->waitfunc == NULL)
chip->waitfunc = nand_wait;
if (!chip->select_chip)
chip->select_chip = nand_select_chip;
if (!chip->read_byte)
chip->read_byte = busw ? nand_read_byte16 : nand_read_byte;
if (!chip->read_word)
chip->read_word = nand_read_word;
if (!chip->block_bad)
chip->block_bad = nand_block_bad;
if (!chip->block_markbad)
chip->block_markbad = nand_default_block_markbad;
if (!chip->write_buf)
chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;
if (!chip->read_buf)
chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;
if (!chip->verify_buf)
chip->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;
if (!chip->scan_bbt)
chip->scan_bbt = nand_default_bbt;
if (!chip->controller) {
chip->controller = &chip->hwcontrol;
spin_lock_init(&chip->controller->lock);
init_waitqueue_head(&chip->controller->wq);
}
}
/* Read the flash type */
/* 讀flash, 獲取其信息 */
type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);
static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
struct nand_chip *chip,
int busw, int *maf_id)
{
struct nand_flash_dev *type = NULL;
int i, dev_id, maf_idx;
int tmp_id, tmp_manf;
/* Select the device */
/* s3c2410_nand_select_chip */
chip->select_chip(mtd, 0);
static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
{
struct s3c2410_nand_info *info;
struct s3c2410_nand_mtd *nmtd;
struct nand_chip *this = mtd->priv;
unsigned long cur;
nmtd = this->priv;
info = nmtd->info;
if (chip != -1 && allow_clk_stop(info)) {
clk_enable(info->clk);
}
cur = readl(info->sel_reg);
if (chip == -1) {
cur |= info->sel_bit;
} else { //
if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
dev_err(info->device, "invalid chip %d\n", chip);
return;
}
if (info->platform != NULL) { //
if (info->platform->select_chip != NULL) {
(info->platform->select_chip) (nmtd->set, chip);
}
}
cur &= ~info->sel_bit;
}
/* 强制使能 nandflash 片选 */
writel(cur, info->sel_reg);
if (chip == -1 && allow_clk_stop(info)) {
clk_disable(info->clk);
}
}
/*
* Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
* after power-up
*/
/* nand_command */
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
/* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
/* nand_read_byte */
*maf_id = chip->read_byte(mtd);
dev_id = chip->read_byte(mtd);
static uint8_t nand_read_byte(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
return readb(chip->IO_ADDR_R);
}
/* Try again to make sure, as some systems the bus-hold or other
* interface concerns can cause random data which looks like a
* possibly credible NAND flash to appear. If the two results do
* not match, ignore the device completely.
*/
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
tmp_manf = chip->read_byte(mtd);
tmp_id = chip->read_byte(mtd);
if (tmp_manf != *maf_id || tmp_id != dev_id) {
printk(KERN_INFO "%s: second ID read did not match "
"%02x,%02x against %02x,%02x\n", __func__,
*maf_id, dev_id, tmp_manf, tmp_id);
return ERR_PTR(-ENODEV);
}
/* Lookup the flash id */
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if (dev_id == nand_flash_ids[i].id) { //i 29
type = &nand_flash_ids[i]; <tag15>
break;
}
}
if (!type)
return ERR_PTR(-ENODEV);
if (!mtd->name) { //
mtd->name = type->name; <tag16>
}
chip->chipsize = (uint64_t)type->chipsize << 20; <tag17>
/* Newer devices have all the information in additional id bytes */
if (!type->pagesize) { //
int extid;
/* 接续 READ_ID指令 的内容 */
/* The 3rd id byte holds MLC / multichip data */
chip->cellinfo = chip->read_byte(mtd);
/* The 4th id byte is the important one */
extid = chip->read_byte(mtd);
/* Calc pagesize */
/* extid & 0x3 获取 页大小 */
/* 写大小, 即是页大小 */
mtd->writesize = 1024 << (extid & 0x3); <tag18>
extid >>= 2;
/* Calc oobsize */
/* extid >>2 (& 0x1) 获取用于空间size */
/* 冗余空间计算公式为: page_size / 512 * (8或16)
* 在一个page中, 每512个字节, 就需要 8或16个字节的冗余空间.*/
mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9); <tag19>
extid >>= 2;
/* Calc blocksize. Blocksize is multiples of 64KiB */
/* 擦除大小, 即是块大小 */
mtd->erasesize = (64 * 1024) << (extid & 0x03); <tag20>
extid >>= 2;
/* Get buswidth information */
busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
} else {
/*
* Old devices have chip data hardcoded in the device id table
*/
mtd->erasesize = type->erasesize;
mtd->writesize = type->pagesize;
mtd->oobsize = mtd->writesize / 32;
busw = type->options & NAND_BUSWIDTH_16;
}
/* erasesize 131072, writesize 2048, oobsize 64 */
pr_err("joker %s:in %d. erasesize %d, writesize %d, oobsize %d\n",
__func__,__LINE__, mtd->erasesize, mtd->writesize, mtd->oobsize);
/* Try to identify manufacturer */
for (maf_idx = 0; nand_manuf_ids[maf_idx].id != 0x0; maf_idx++) {
if (nand_manuf_ids[maf_idx].id == *maf_id)
break;
}
/*
* Check, if buswidth is correct. Hardware drivers should set
* chip correct !
*/
if (busw != (chip->options & NAND_BUSWIDTH_16)) {
printk(KERN_INFO "NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
printk(KERN_WARNING "NAND bus width %d instead %d bit\n",
(chip->options & NAND_BUSWIDTH_16) ? 16 : 8,
busw ? 16 : 8);
return ERR_PTR(-EINVAL);
}
/* Calculate the address shift from the page size */
chip->page_shift = ffs(mtd->writesize) - 1; <tag21>
/* Convert chipsize to number of pages per chip -1. */
chip->pagemask = (chip->chipsize >> chip->page_shift) - 1; <tag22>
chip->bbt_erase_shift = chip->phys_erase_shift = ffs(mtd->erasesize) - 1; <tag23>
if (chip->chipsize & 0xffffffff) { //
chip->chip_shift = ffs((unsigned)chip->chipsize) - 1; <tag24>
}
else {
chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32)) + 32 - 1;
}
/* Set the bad block position */
/* NAND_LARGE_BADBLOCK_POS */
chip->badblockpos = mtd->writesize > 512 ?
NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS; <tag25>
/* page_shift 11, pagemask 131071, bbt_erase_shift 17, chip_shift 28, badblockpos 0 */
pr_err("joker %s:in %d. page_shift %d, pagemask %d, bbt_erase_shift %d, chip_shift %d, badblockpos %d\n",
__func__,__LINE__,
chip->page_shift, chip->pagemask, chip->bbt_erase_shift, chip->chip_shift, chip->badblockpos);
/* Get chip options, preserve non chip based options */
chip->options &= ~NAND_CHIPOPTIONS_MSK;
/* type get from nand_flash_ids */
chip->options |= type->options & NAND_CHIPOPTIONS_MSK; <tag26>
/*
* Set chip as a default. Board drivers can override it, if necessary
*/
chip->options |= NAND_NO_AUTOINCR;
/* Check if chip is a not a samsung device. Do not clear the
* options for chips which are not having an extended id.
*/
if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize) {
chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
}
/* Check for AND chips with 4 page planes */
if (chip->options & NAND_4PAGE_ARRAY) {
chip->erase_cmd = multi_erase_cmd;
}
else { //
chip->erase_cmd = single_erase_cmd;
}
/* Do not replace user supplied command function ! */
if (mtd->writesize > 512 && chip->cmdfunc == nand_command) { //
chip->cmdfunc = nand_command_lp; <tag117>
}
/* NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit)*/
printk(KERN_INFO "NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id,
nand_manuf_ids[maf_idx].name, type->name);
return type;
}
if (IS_ERR(type)) {
printk(KERN_WARNING "No NAND device found!!!\n");
chip->select_chip(mtd, -1);
return PTR_ERR(type);
}
/* Check for a chip array */
for (i = 1; i < maxchips; i++) { /* 未进入 */
chip->select_chip(mtd, i);
/* See comment in nand_get_flash_type for reset */
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
/* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
if (nand_maf_id != chip->read_byte(mtd) ||
type->id != chip->read_byte(mtd)) {
break;
}
}
if (i > 1)
printk(KERN_INFO "%d NAND chips detected\n", i);
/* Store the number of chips and calc total size for mtd */
chip->numchips = i; <tag27>
mtd->size = i * chip->chipsize; <tag29>
return 0;
}
if (nmtd->scan_res == 0) { //
s3c2410_nand_update_chip(info, nmtd);
static void s3c2410_nand_update_chip(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *nmtd)
{
struct nand_chip *chip = &nmtd->chip;
dev_dbg(info->device, "chip %p => page shift %d\n",
chip, chip->page_shift);
if (chip->ecc.mode != NAND_ECC_HW) {
return;
}
/* change the behaviour depending on wether we are using
* the large or small page nand device */
if (chip->page_shift > 10) { //
chip->ecc.size = 256; <tag30>
chip->ecc.bytes = 3; <tag31>
} else {
chip->ecc.size = 512;
chip->ecc.bytes = 3;
chip->ecc.layout = &nand_hw_eccoob;
}
}
nand_scan_tail(&nmtd->mtd);
/*建立壞塊表 */
int nand_scan_tail(struct mtd_info *mtd)
{
int i;
struct nand_chip *chip = mtd->priv;
if (!(chip->options & NAND_OWN_BUFFERS)) { //
chip->buffers = kmalloc(sizeof(*chip->buffers), GFP_KERNEL); <tag32>
}
if (!chip->buffers)
return -ENOMEM;
/* Set the internal oob buffer location, just after the page data */
chip->oob_poi = chip->buffers->databuf + mtd->writesize; <tag33>
/*
* If no default placement scheme is given, select an appropriate one
*/
if (!chip->ecc.layout) {
switch (mtd->oobsize) {
case 8:
chip->ecc.layout = &nand_oob_8;
break;
case 16:
chip->ecc.layout = &nand_oob_16;
break;
case 64: //
chip->ecc.layout = &nand_oob_64; <tag34>
static struct nand_ecclayout nand_oob_64 = {
.eccbytes = 24,
.eccpos = {
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63},
.oobfree = {
{.offset = 2,
.length = 38}}
};
break;
case 128:
chip->ecc.layout = &nand_oob_128;
break;
default:
printk(KERN_WARNING "No oob scheme defined for "
"oobsize %d\n", mtd->oobsize);
BUG();
}
}
if (!chip->write_page) { //
chip->write_page = nand_write_page;
}
/*
* check ECC mode, default to software if 3byte/512byte hardware ECC is
* selected and we have 256 byte pagesize fallback to software ECC
*/
switch (chip->ecc.mode) {
case NAND_ECC_HW_OOB_FIRST:
/* Similar to NAND_ECC_HW, but a separate read_page handle */
if (!chip->ecc.calculate || !chip->ecc.correct ||
!chip->ecc.hwctl) {
printk(KERN_WARNING "No ECC functions supplied; "
"Hardware ECC not possible\n");
BUG();
}
if (!chip->ecc.read_page)
chip->ecc.read_page = nand_read_page_hwecc_oob_first;
case NAND_ECC_HW: //
/* Use standard hwecc read page function ? */
if (!chip->ecc.read_page)
chip->ecc.read_page = nand_read_page_hwecc;
if (!chip->ecc.write_page)
chip->ecc.write_page = nand_write_page_hwecc;
if (!chip->ecc.read_page_raw)
chip->ecc.read_page_raw = nand_read_page_raw;
if (!chip->ecc.write_page_raw)
chip->ecc.write_page_raw = nand_write_page_raw;
if (!chip->ecc.read_oob)
chip->ecc.read_oob = nand_read_oob_std;
if (!chip->ecc.write_oob)
chip->ecc.write_oob = nand_write_oob_std;
case NAND_ECC_HW_SYNDROME: //
if ((!chip->ecc.calculate || !chip->ecc.correct ||
!chip->ecc.hwctl) &&
(!chip->ecc.read_page ||
chip->ecc.read_page == nand_read_page_hwecc ||
!chip->ecc.write_page ||
chip->ecc.write_page == nand_write_page_hwecc)) {
printk(KERN_WARNING "No ECC functions supplied; "
"Hardware ECC not possible\n");
BUG();
}
/* Use standard syndrome read/write page function ? */
if (!chip->ecc.read_page)
chip->ecc.read_page = nand_read_page_syndrome;
if (!chip->ecc.write_page)
chip->ecc.write_page = nand_write_page_syndrome;
if (!chip->ecc.read_page_raw)
chip->ecc.read_page_raw = nand_read_page_raw_syndrome;
if (!chip->ecc.write_page_raw)
chip->ecc.write_page_raw = nand_write_page_raw_syndrome;
if (!chip->ecc.read_oob)
chip->ecc.read_oob = nand_read_oob_syndrome;
if (!chip->ecc.write_oob)
chip->ecc.write_oob = nand_write_oob_syndrome;
if (mtd->writesize >= chip->ecc.size)
break;
printk(KERN_WARNING "%d byte HW ECC not possible on "
"%d byte page size, fallback to SW ECC\n",
chip->ecc.size, mtd->writesize);
chip->ecc.mode = NAND_ECC_SOFT;
case NAND_ECC_SOFT:
chip->ecc.calculate = nand_calculate_ecc;
chip->ecc.correct = nand_correct_data;
chip->ecc.read_page = nand_read_page_swecc;
chip->ecc.read_subpage = nand_read_subpage;
chip->ecc.write_page = nand_write_page_swecc;
chip->ecc.read_page_raw = nand_read_page_raw;
chip->ecc.write_page_raw = nand_write_page_raw;
chip->ecc.read_oob = nand_read_oob_std;
chip->ecc.write_oob = nand_write_oob_std;
if (!chip->ecc.size)
chip->ecc.size = 256;
chip->ecc.bytes = 3;
break;
case NAND_ECC_NONE:
printk(KERN_WARNING "NAND_ECC_NONE selected by board driver. "
"This is not recommended !!\n");
chip->ecc.read_page = nand_read_page_raw;
chip->ecc.write_page = nand_write_page_raw;
chip->ecc.read_oob = nand_read_oob_std;
chip->ecc.read_page_raw = nand_read_page_raw;
chip->ecc.write_page_raw = nand_write_page_raw;
chip->ecc.write_oob = nand_write_oob_std;
chip->ecc.size = mtd->writesize;
chip->ecc.bytes = 0;
break;
default:
printk(KERN_WARNING "Invalid NAND_ECC_MODE %d\n",
chip->ecc.mode);
BUG();
}
/*
* The number of bytes available for a client to place data into
* the out of band area
*/
chip->ecc.layout->oobavail = 0;
for (i = 0; chip->ecc.layout->oobfree[i].length
&& i < ARRAY_SIZE(chip->ecc.layout->oobfree); i++) { //i 0
chip->ecc.layout->oobavail +=
chip->ecc.layout->oobfree[i].length; <tag35>
}
mtd->oobavail = chip->ecc.layout->oobavail; <tag36>
/*
* Set the number of read / write steps for one page depending on ECC
* mode
*/
chip->ecc.steps = mtd->writesize / chip->ecc.size; <tag37>
if(chip->ecc.steps * chip->ecc.size != mtd->writesize) {
printk(KERN_WARNING "Invalid ecc parameters\n");
BUG();
}
chip->ecc.total = chip->ecc.steps * chip->ecc.bytes; <tag38>
/*
* Allow subpage writes up to ecc.steps. Not possible for MLC
* FLASH.
*/
if (!(chip->options & NAND_NO_SUBPAGE_WRITE) &&
!(chip->cellinfo & NAND_CI_CELLTYPE_MSK)) { //
switch(chip->ecc.steps) {
case 2:
mtd->subpage_sft = 1;
break;
case 4:
case 8:
case 16: //
mtd->subpage_sft = 2; <tag39>
break;
}
}
chip->subpagesize = mtd->writesize >> mtd->subpage_sft; <tag40>
/* Initialize state */
chip->state = FL_READY; <tag41>
/* De-select the device */
chip->select_chip(mtd, -1);
/* Invalidate the pagebuffer reference */
chip->pagebuf = -1; <tag42>
/* Fill in remaining MTD driver data */
mtd->type = MTD_NANDFLASH; <tag43>
mtd->flags = MTD_CAP_NANDFLASH; <tag44>
mtd->erase = nand_erase;
mtd->point = NULL;
mtd->unpoint = NULL;
mtd->read = nand_read;
mtd->write = nand_write;
mtd->read_oob = nand_read_oob;
mtd->write_oob = nand_write_oob;
mtd->sync = nand_sync;
mtd->lock = NULL;
mtd->unlock = NULL;
mtd->suspend = nand_suspend;
mtd->resume = nand_resume;
mtd->block_isbad = nand_block_isbad;
mtd->block_markbad = nand_block_markbad;
/* propagate ecc.layout to mtd_info */
mtd->ecclayout = chip->ecc.layout; <tag45>
/* Check, if we should skip the bad block table scan */
if (chip->options & NAND_SKIP_BBTSCAN) {
return 0;
}
/* Build bad block table */
/* nand_default_bbt */
return chip->scan_bbt(mtd); //
int nand_default_bbt(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
/* 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 descriptors */
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);
}
/* Is a flash based bad block table requested ? */
if (this->options & NAND_USE_FLASH_BBT) {
/* Use the default pattern descriptors */
if (!this->bbt_td) {
this->bbt_td = &bbt_main_descr;
this->bbt_md = &bbt_mirror_descr;
}
if (!this->badblock_pattern) {
this->badblock_pattern = (mtd->writesize > 512) ?
&largepage_flashbased : &smallpage_flashbased;
}
} else { //
this->bbt_td = NULL;
this->bbt_md = NULL;
if (!this->badblock_pattern) { //
this->badblock_pattern = (mtd->writesize > 512) ?
&largepage_memorybased : &smallpage_memorybased; <tag46>
static struct nand_bbt_descr largepage_memorybased = {
.options = 0,
.offs = 0,
.len = 2,
.pattern = scan_ff_pattern
};
}
}
return nand_scan_bbt(mtd, this->badblock_pattern); //
/* 讀每個塊上的頁的oob, 判斷是否為壞塊, 並建立壞塊表 */
int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd)
{
struct nand_chip *this = mtd->priv;
int len, res = 0;
uint8_t *buf;
struct nand_bbt_descr *td = this->bbt_td; /* table descriptor */
struct nand_bbt_descr *md = this->bbt_md; /* memory based descriptor */
len = mtd->size >> (this->bbt_erase_shift + 2);
/* Allocate memory (2bit per block) and clear the memory bad block table */
this->bbt = kzalloc(len, GFP_KERNEL); <tag47>
if (!this->bbt) {
printk(KERN_ERR "nand_scan_bbt: Out of memory\n");
return -ENOMEM;
}
/* 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)))
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; <tag49>
return create_bbt(mtd, this->buffers->databuf, bd, -1);
static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
struct nand_bbt_descr *bd, int chip)
{
struct nand_chip *this = mtd->priv;
int i, numblocks, len, scanlen;
int startblock;
loff_t from;
size_t readlen;
printk(KERN_INFO "Scanning device for bad blocks\n");
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;
}
}
if (!(bd->options & NAND_BBT_SCANEMPTY)) { //
/* We need only read few bytes from the OOB area */
scanlen = 0;
readlen = bd->len;
} else {
/* Full page content should be read */
scanlen = mtd->writesize + mtd->oobsize;
readlen = len * mtd->writesize;
}
if (chip == -1) { //
/* Note that numblocks is 2 * (real numblocks) here, see i+=2
* below as it makes shifting and masking less painful */
/* (flash總大小/塊大小)*2 = numblocks */
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 = (loff_t)startblock << (this->bbt_erase_shift - 1);
}
for (i = startblock; i < numblocks;) { // numblocks 4096 , real numblocks is 2048
int ret;
if (bd->options & NAND_BBT_SCANALLPAGES) {
ret = scan_block_full(mtd, bd, from, buf, readlen,
scanlen, len);
}
else { //
/* 每個塊僅讀取其第一個頁的oob,
* 通過該oob判斷該塊是否正常. */
ret = scan_block_fast(mtd, bd, from, buf, len);
static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd,
loff_t offs, uint8_t *buf, int len)
{
struct mtd_oob_ops ops; <tag50>
int j, ret;
ops.ooblen = mtd->oobsize; <tag51>
ops.oobbuf = buf; <tag52>
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.mode = MTD_OOB_PLACE; <tag53>
for (j = 0; j < len; j++) {// len == 1
/*
* Read the full oob until read_oob is fixed to
* handle single byte reads for 16 bit
* buswidth
*/
/* nand_read_oob */
ret = mtd->read_oob(mtd, offs, &ops);
static int nand_read_oob(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
struct nand_chip *chip = mtd->priv;
int ret = -ENOTSUPP;
ops->retlen = 0;
/* Do not allow reads past end of device */
if (ops->datbuf && (from + ops->len) > mtd->size) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt read "
"beyond end of device\n", __func__);
return -EINVAL;
}
nand_get_device(chip, mtd, FL_READING);
static int nand_get_device(struct nand_chip *chip,
struct mtd_info *mtd, int new_state)
{
spinlock_t *lock = &chip->controller->lock;
wait_queue_head_t *wq = &chip->controller->wq;
DECLARE_WAITQUEUE(wait, current);
retry:
spin_lock(lock);
/* Hardware controller shared among independent devices */
if (!chip->controller->active) { //
chip->controller->active = chip; <tag55>
}
if (chip->controller->active == chip && chip->state == FL_READY) {//
chip->state = new_state; <tag56>
spin_unlock(lock);
return 0; /* 从这里 退出 */
}
if (new_state == FL_PM_SUSPENDED) {
spin_unlock(lock);
return (chip->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN;
}
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(wq, &wait);
spin_unlock(lock);
schedule();
remove_wait_queue(wq, &wait);
goto retry;
}
switch(ops->mode) {
case MTD_OOB_PLACE:
case MTD_OOB_AUTO:
case MTD_OOB_RAW:
break;
default:
goto out;
}
if (!ops->datbuf) { //
/* 讀取xx個頁的oob數據, 從第yy個頁開始.
* yy = from / 頁大小
* xx = (ops->ooblen)/(一個頁的oob大小)*/
ret = nand_do_read_oob(mtd, from, ops);
static int nand_do_read_oob(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
int page, realpage, chipnr, sndcmd = 1;
struct nand_chip *chip = mtd->priv;
int blkcheck = (1 <<
(chip->phys_erase_shift - chip->page_shift)) - 1;
int readlen = ops->ooblen;
int len;
uint8_t *buf = ops->oobbuf;
DEBUG(MTD_DEBUG_LEVEL3, "%s: from = 0x%08Lx, len = %i\n",
__func__, (unsigned long long)from, readlen);
if (ops->mode == MTD_OOB_AUTO) {
len = chip->ecc.layout->oobavail;
}
else { //
len = mtd->oobsize;
}
if (unlikely(ops->ooboffs >= len)) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt to start read "
"outside oob\n", __func__);
return -EINVAL;
}
/* Do not allow reads past end of device */
if (unlikely(from >= mtd->size ||
ops->ooboffs + readlen >
((mtd->size >> chip->page_shift) -
(from >> chip->page_shift)) * len)) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Attempt read beyond end "
"of device\n", __func__);
return -EINVAL;
}
chipnr = (int)(from >> chip->chip_shift);
chip->select_chip(mtd, chipnr);
/* Shift to get page */
/* 偏移(單位字節)/頁大小(單位字節) */
realpage = (int)(from >> chip->page_shift);
/* pagemask, 用於限定 page的值在有效範圍內 */
page = realpage & chip->pagemask;
while(1) {
/* nand_read_oob_std */
sndcmd = chip->ecc.read_oob(mtd, chip, page, sndcmd);
static int nand_read_oob_std(struct mtd_info *mtd,
struct nand_chip *chip,
int page, int sndcmd)
{
if (sndcmd) { //
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
sndcmd = 0;
}
/* s3c2440_nand_read_buf */
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize<64>);
static void s3c2440_nand_read_buf(struct mtd_info *mtd,
u_char *buf, int len)
{
struct s3c2410_nand_info *info =
s3c2410_nand_mtd_toinfo(mtd);
readsl(info->regs + S3C2440_NFDATA, buf, len >> 2);
/* cleanup if we've got less than a word to do */
if (len & 3) {
buf += len & ~3;
for (; len & 3; len--)
*buf++ = readb(info->regs + S3C2440_NFDATA);
}
}
return sndcmd;
}
len = min(len, readlen); <len == oobsize> <"64字節">
/* 將讀取到的oob複製到 buf. */
buf = nand_transfer_oob(chip, buf, ops, len); <tag60>
static uint8_t *nand_transfer_oob(struct nand_chip *chip,
uint8_t *oob,
struct mtd_oob_ops *ops, size_t len)
{
switch(ops->mode) {
case MTD_OOB_PLACE:
case MTD_OOB_RAW: //
memcpy(oob, chip->oob_poi + ops->ooboffs, len);
return oob + len;
case MTD_OOB_AUTO: {
struct nand_oobfree *free =
chip->ecc.layout->oobfree;
uint32_t boffs = 0, roffs = ops->ooboffs;
size_t bytes = 0;
for(; free->length && len; free++, len -= bytes) {
/* Read request not from offset 0 ? */
if (unlikely(roffs)) {
if (roffs >= free->length) {
roffs -= free->length;
continue;
}
boffs = free->offset + roffs;
bytes = min_t(size_t, len,
(free->length - roffs));
roffs = 0;
} else {
bytes = min_t(size_t, len, free->length);
boffs = free->offset;
}
memcpy(oob, chip->oob_poi + boffs, bytes);
oob += bytes;
}
return oob;
}
default:
BUG();
}
return NULL;
}
if (!(chip->options & NAND_NO_READRDY)) {
/*
* 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 (!chip->dev_ready) {
udelay(chip->chip_delay);
}
else {
nand_wait_ready(mtd);
}
}
readlen -= len;
if (!readlen) { //
break;
}
/* Increment page address */
realpage++;
page = realpage & chip->pagemask;
/* Check, if we cross a chip boundary */
if (!page) {
chipnr++;
chip->select_chip(mtd, -1);
chip->select_chip(mtd, chipnr);
}
/* Check, if the chip supports auto page increment
* or if we have hit a block boundary.
*/
if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck))
#define NAND_CANAUTOINCR(chip) (!(chip->options & NAND_NO_AUTOINCR))
{
sndcmd = 1;
}
}
ops->oobretlen = ops->ooblen; <tag62>
return 0;
}
}
else {
ret = nand_do_read_ops(mtd, from, ops);
}
out:
nand_release_device(mtd);
static void nand_release_device(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
/* De-select the NAND device */
chip->select_chip(mtd, -1);
/* Release the controller and the chip */
spin_lock(&chip->controller->lock);
chip->controller->active = NULL;
chip->state = FL_READY;
wake_up(&chip->controller->wq);
spin_unlock(&chip->controller->lock);
}
return ret;
}
if (ret) {
return ret;
}
if (check_short_pattern(buf, bd))
static int check_short_pattern(uint8_t *buf, struct nand_bbt_descr *td)
{
int i;
uint8_t *p = buf;
/* td->pattern[0,1] 的值都为 0xff,
* 当读取到的oob内容不为 0xff, 即为坏块*/
/* td->offs 表明 用於表示是否壞塊的信息在 oob的偏移. */
/* Compare the pattern */
for (i = 0; i < td->len; i++) {
pr_err("joker %s:in %d. p[%d] 0x%x bd[%d] 0x%x\n",
__func__,__LINE__, i, p[td->offs + i], i, td->pattern[i]);
if (p[td->offs + i] != td->pattern[i])
return -1; /* find a bad block */
}
return 0;
}
{
return 1; //j 0 find a bad block
}
offs += mtd->writesize; //j 0
}
return 0;
}
}
if (ret < 0) {
return ret;
}
if (ret) { /* if find a bad block */ //
/* bbt[num%6] => bit 0,1 for num%6+0, bit 2,3 for num%6+2, bit 4,5 for num%6+4, bit 6,7 for num%6+6 */
/* 每一个块 使用两位 来表示, 0b11 表示为坏块. */
this->bbt[i >> 3] |= 0x03 << (i & 0x6); <tag61>
/*
Bad eraseblock 44 at 0x000000580000
Bad eraseblock 241 at 0x000001e20000
Bad eraseblock 628 at 0x000004e80000
Bad eraseblock 770 at 0x000006040000
* */
printk(KERN_WARNING "Bad eraseblock %d at 0x%012llx\n",
i >> 1, (unsigned long long)from);
mtd->ecc_stats.badblocks++;
}
// because numblocks 4096 , real numblocks is 2048
i += 2;
from += (1 << this->bbt_erase_shift);
}
return 0; //
}
}
{
printk(KERN_ERR "nand_bbt: Can't scan flash and build the RAM-based BBT\n");
kfree(this->bbt);
this->bbt = NULL;
}
return res; /* 这里退出 */
}
/* 以下未执行 */
/* Allocate a temporary buffer for one eraseblock incl. oob */
/* 计算一个块 所需的OOB空间大小. */
len = (1 << this->bbt_erase_shift);
len += (len >> this->page_shift) * mtd->oobsize;
buf = vmalloc(len);
if (!buf) {
printk(KERN_ERR "nand_bbt: Out of memory\n");
kfree(this->bbt);
this->bbt = NULL;
return -ENOMEM;
}
/* Is the bbt at a given page ? */
if (td->options & NAND_BBT_ABSPAGE) {
res = read_abs_bbts(mtd, buf, td, md);
} else {
/* Search the bad block table using a pattern in oob */
res = search_read_bbts(mtd, buf, td, md);
}
if (res) {
res = check_create(mtd, buf, bd);
}
/* Prevent the bbt regions from erasing / writing */
mark_bbt_region(mtd, td);
if (md) {
mark_bbt_region(mtd, md);
}
vfree(buf);
return res;
}
}
}
s3c2410_nand_add_partition(info, nmtd, sets); /* sets => friendly_arm_nand_sets */
static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
struct s3c2410_nand_mtd *mtd,
struct s3c2410_nand_set *set)
{
struct mtd_partition *part_info;
int nr_part = 0;
if (set == NULL) {
return add_mtd_device(&mtd->mtd);
}
if (set->nr_partitions == 0) {
mtd->mtd.name = set->name;
nr_part = parse_mtd_partitions(&mtd->mtd, part_probes,
&part_info, 0);
} else { //
/* 獲取分區數目和分區信息 */
if (set->nr_partitions > 0 && set->partitions != NULL) { //
nr_part = set->nr_partitions;
part_info = set->partitions;
}
}
if (nr_part > 0 && part_info){
return add_mtd_partitions(&mtd->mtd, part_info, nr_part); /* 从这里 退出 */
int add_mtd_partitions(struct mtd_info *master,
const struct mtd_partition *parts,
int nbparts)
{
struct mtd_part *slave;
uint64_t cur_offset = 0;
int i;
/* Creating 5 MTD partitions on "NAND 256MiB 3,3V 8-bit": */
printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
for (i = 0; i < nbparts; i++) { // i 0~4
slave = add_one_partition(master, parts + i, i, cur_offset);
static struct mtd_part *add_one_partition(struct mtd_info *master,
const struct mtd_partition *part, int partno,
uint64_t cur_offset)
{
struct mtd_part *slave;
/* allocate the partition structure */
slave = kzalloc(sizeof(*slave), GFP_KERNEL); <tag65>
if (!slave) {
printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n",
master->name);
del_mtd_partitions(master);
return NULL;
}
list_add(&slave->list, &mtd_partitions); <tag66>
/* set up the MTD object for this partition */
slave->mtd.type = master->type; <tag67>
slave->mtd.flags = master->flags & ~part->mask_flags;
slave->mtd.size = part->size; <tag69>
slave->mtd.writesize = master->writesize;
slave->mtd.oobsize = master->oobsize;
slave->mtd.oobavail = master->oobavail;
slave->mtd.subpage_sft = master->subpage_sft;
slave->mtd.name = part->name; <tag70>
slave->mtd.owner = master->owner;
slave->mtd.backing_dev_info = master->backing_dev_info;
/* NOTE: we don't arrange MTDs as a tree; it'd be error-prone
* to have the same data be in two different partitions.
*/
slave->mtd.dev.parent = master->dev.parent;
slave->mtd.read = part_read;
slave->mtd.write = part_write;
if (master->panic_write)
slave->mtd.panic_write = part_panic_write;
if (master->point && master->unpoint) {
slave->mtd.point = part_point;
slave->mtd.unpoint = part_unpoint;
}
if (master->get_unmapped_area)
slave->mtd.get_unmapped_area = part_get_unmapped_area;
if (master->read_oob)
slave->mtd.read_oob = part_read_oob;
if (master->write_oob)
slave->mtd.write_oob = part_write_oob;
if (master->read_user_prot_reg)
slave->mtd.read_user_prot_reg = part_read_user_prot_reg;
if (master->read_fact_prot_reg)
slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg;
if (master->write_user_prot_reg)
slave->mtd.write_user_prot_reg = part_write_user_prot_reg;
if (master->lock_user_prot_reg)
slave->mtd.lock_user_prot_reg = part_lock_user_prot_reg;
if (master->get_user_prot_info)
slave->mtd.get_user_prot_info = part_get_user_prot_info;
if (master->get_fact_prot_info)
slave->mtd.get_fact_prot_info = part_get_fact_prot_info;
if (master->sync)
slave->mtd.sync = part_sync;
if (!partno && !master->dev.class && master->suspend && master->resume) {
slave->mtd.suspend = part_suspend;
slave->mtd.resume = part_resume;
}
if (master->writev)
slave->mtd.writev = part_writev;
if (master->lock)
slave->mtd.lock = part_lock;
if (master->unlock)
slave->mtd.unlock = part_unlock;
if (master->block_isbad)
slave->mtd.block_isbad = part_block_isbad;
if (master->block_markbad)
slave->mtd.block_markbad = part_block_markbad;
slave->mtd.erase = part_erase;
slave->master = master; <tag71>
slave->offset = part->offset; <tag72>
if (slave->offset == MTDPART_OFS_APPEND) {
slave->offset = cur_offset;
}
if (slave->offset == MTDPART_OFS_NXTBLK) {
slave->offset = cur_offset;
if (mtd_mod_by_eb(cur_offset, master) != 0) {
/* Round up to next erasesize */
slave->offset = (mtd_div_by_eb(cur_offset, master) + 1) * master->erasesize;
printk(KERN_NOTICE "Moving partition %d: "
"0x%012llx -> 0x%012llx\n", partno,
(unsigned long long)cur_offset, (unsigned long long)slave->offset);
}
}
if (slave->mtd.size == MTDPART_SIZ_FULL) {
slave->mtd.size = master->size - slave->offset;
}
/* 0x000000000000-0x000000040000 : "supervivi" */
/* 0x000000040000-0x000000060000 : "param" */
/* 0x000000060000-0x000000560000 : "Kernel" */
/* 0x000000560000-0x000040560000 : "root" */
/* 0x000000000000-0x000040000000 : "nand" */
printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n", (unsigned long long)slave->offset,
(unsigned long long)(slave->offset + slave->mtd.size), slave->mtd.name);
/* let's do some sanity checks */
if (slave->offset >= master->size) {
/* let's register it anyway to preserve ordering */
slave->offset = 0;
slave->mtd.size = 0;
printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n",
part->name);
goto out_register;
}
if (slave->offset + slave->mtd.size > master->size) { // root, nand patition only
/* 分区的大小过大, 重新设置分区大小. */
slave->mtd.size = master->size - slave->offset;
/* mtd: partition "root" extends beyond the end of device "NAND 256MiB 3,3V 8-bit"
* -- size truncated to 0xfaa0000 */
/* mtd: partition "nand" extends beyond the end of device "NAND 256MiB 3,3V 8-bit"
* -- size truncated to 0x10000000 */
printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\"
-- size truncated to %#llx\n",
part->name, master->name, (unsigned long long)slave->mtd.size);
}
if (master->numeraseregions > 1) {
/* Deal with variable erase size stuff */
int i, max = master->numeraseregions;
u64 end = slave->offset + slave->mtd.size;
struct mtd_erase_region_info *regions = master->eraseregions;
/* Find the first erase regions which is part of this
* partition. */
for (i = 0; i < max && regions[i].offset <= slave->offset; i++)
;
/* The loop searched for the region _behind_ the first one */
if (i > 0)
i--;
/* Pick biggest erasesize */
for (; i < max && regions[i].offset < end; i++) {
if (slave->mtd.erasesize < regions[i].erasesize) {
slave->mtd.erasesize = regions[i].erasesize;
}
}
BUG_ON(slave->mtd.erasesize == 0);
} else { //
/* Single erase size */
slave->mtd.erasesize = master->erasesize; <tag73>
}
if ((slave->mtd.flags & MTD_WRITEABLE) &&
mtd_mod_by_eb(slave->offset, &slave->mtd)) {
/* Doesn't start on a boundary of major erase size */
/* FIXME: Let it be writable if it is on a boundary of
* _minor_ erase size though */
slave->mtd.flags &= ~MTD_WRITEABLE;
printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase block boundary
-- force read-only\n",
part->name);
}
if ((slave->mtd.flags & MTD_WRITEABLE) &&
mtd_mod_by_eb(slave->mtd.size, &slave->mtd)) {
slave->mtd.flags &= ~MTD_WRITEABLE;
printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase block
-- force read-only\n",
part->name);
}
slave->mtd.ecclayout = master->ecclayout; <tag74>
if (master->block_isbad) { //
uint64_t offs = 0;
/* 根据已构建的坏块表, 和 每个分区的范围, 找到每个分区存在多少个坏块. */
while (offs < slave->mtd.size) { //
/* nand_block_isbad */
if (master->block_isbad(master, offs + slave->offset)){//in root,nand had bad block?
slave->mtd.ecc_stats.badblocks++; <tag77>
}
offs += slave->mtd.erasesize;
/* nand_block_isbad */
static int nand_block_isbad(struct mtd_info *mtd, loff_t offs)
{
/* Check for invalid offset */
if (offs > mtd->size)
return -EINVAL;
return nand_block_checkbad(mtd, offs, 1, 0);
static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip,
int allowbbt)
{
struct nand_chip *chip = mtd->priv;
if (!chip->bbt) {
return chip->block_bad(mtd, ofs, getchip);
}
/* Return info from the table */
return nand_isbad_bbt(mtd, ofs, allowbbt); //
int nand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt)
{
struct nand_chip *this = mtd->priv;
int block;
uint8_t res;
/* Get block number * 2 */
block = (int)(offs >> (this->bbt_erase_shift - 1));
res = (this->bbt[block >> 3] >> (block & 0x06)) & 0x03;
DEBUG(MTD_DEBUG_LEVEL2, "nand_isbad_bbt(): bbt info for offs 0x%08x:
(block %d) 0x%02x\n",
(unsigned int)offs, block >> 1, res);
switch ((int)res) {
case 0x00: //
return 0;
case 0x01:
return 1;
case 0x02:
return allowbbt ? 0 : 1;
}
return 1; // if find a bad block , return from here
}
}
}
}
}
out_register:
/* register our partition */
add_mtd_device(&slave->mtd);
int add_mtd_device(struct mtd_info *mtd)
{
int i;
if (!mtd->backing_dev_info) { //
switch (mtd->type) {
case MTD_RAM:
mtd->backing_dev_info = &mtd_bdi_rw_mappable;
break;
case MTD_ROM:
mtd->backing_dev_info = &mtd_bdi_ro_mappable;
break;
default: //
mtd->backing_dev_info = &mtd_bdi_unmappable; <tag78>
break;
}
}
BUG_ON(mtd->writesize == 0);
mutex_lock(&mtd_table_mutex);
for (i=0; i < MAX_MTD_DEVICES; i++) {
if (!mtd_table[i]) { // i 0 supervivi; 1 param; 2 kernel; 3 root; 4 nand
struct mtd_notifier *not;
mtd_table[i] = mtd; <tag88>
mtd->index = i; <tag79>
mtd->usecount = 0;
if (is_power_of_2(mtd->erasesize)) { //
mtd->erasesize_shift = ffs(mtd->erasesize) - 1; <tag80>
}
else {
mtd->erasesize_shift = 0;
}
if (is_power_of_2(mtd->writesize)) { //
mtd->writesize_shift = ffs(mtd->writesize) - 1;
}
else {
mtd->writesize_shift = 0;
}
mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
/* Some chips always power up locked. Unlock them now */
if ((mtd->flags & MTD_WRITEABLE)
&& (mtd->flags & MTD_POWERUP_LOCK) && mtd->unlock) {
if (mtd->unlock(mtd, 0, mtd->size))
printk(KERN_WARNING
"%s: unlock failed, "
"writes may not work\n",
mtd->name);
}
/* Caller should have set dev.parent to match the
* physical device.
*/
mtd->dev.type = &mtd_devtype; <tag81>
mtd->dev.class = &mtd_class; <tag82>
mtd->dev.devt = MTD_DEVT(i); <tag83>
#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
dev_set_name(&mtd->dev, "mtd%d", i); <tag84>
dev_set_drvdata(&mtd->dev, mtd); <tag85>
/* 註冊mtd字符設備 */
if (device_register(&mtd->dev) != 0) {
mtd_table[i] = NULL;
break;
}
if (MTD_DEVT(i)) { //
/* mtdxro 字符設備的 設備號 是mtdx 的設備號+1 */
device_create(&mtd_class, mtd->dev.parent,
MTD_DEVT(i) + 1,
NULL, "mtd%dro", i);
}
DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name);
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
/* 定义 全局变量: static LIST_HEAD(mtd_notifiers);*/
list_for_each_entry(not, &mtd_notifiers, list) {
/* blktrans_notify_add */
not->add(mtd);
static void blktrans_notify_add(struct mtd_info *mtd)
{
struct mtd_blktrans_ops *tr;
if (mtd->type == MTD_ABSENT) {
return;
}
/* 定义 : static LIST_HEAD(blktrans_majors);*/
list_for_each_entry(tr, &blktrans_majors, list) {
/* mtdblock_add_mtd */
tr->add_mtd(tr, mtd);
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr,
struct mtd_info *mtd)
{
struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return;
dev->mtd = mtd;
dev->devnum = mtd->index;
dev->size = mtd->size >> 9;
dev->tr = tr;
if (!(mtd->flags & MTD_WRITEABLE)) {
dev->readonly = 1;
}
add_mtd_blktrans_dev(dev);
int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
{
struct mtd_blktrans_ops *tr = new->tr;
struct mtd_blktrans_dev *d;
int last_devnum = -1;
struct gendisk *gd;
if (mutex_trylock(&mtd_table_mutex)) {
mutex_unlock(&mtd_table_mutex);
BUG();
}
list_for_each_entry(d, &tr->devs, list) {
if (new->devnum == -1) {
/* Use first free number */
if (d->devnum != last_devnum+1) {
/* Found a free devnum. Plug it in here */
new->devnum = last_devnum+1;
list_add_tail(&new->list, &d->list);
goto added;
}
} else if (d->devnum == new->devnum) {
/* Required number taken */
return -EBUSY;
} else if (d->devnum > new->devnum) {
/* Required number was free */
list_add_tail(&new->list, &d->list);
goto added;
}
last_devnum = d->devnum;
}
if (new->devnum == -1) {
new->devnum = last_devnum+1;
}
if ((new->devnum << tr->part_bits) > 256) {
return -EBUSY;
}
list_add_tail(&new->list, &tr->devs);
added:
mutex_init(&new->lock);
if (!tr->writesect) {
new->readonly = 1;
}
gd = alloc_disk(1 << tr->part_bits);
if (!gd) {
list_del(&new->list);
return -ENOMEM;
}
gd->major = tr->major;
gd->first_minor = (new->devnum) << tr->part_bits;
gd->fops = &mtd_blktrans_ops;
if (tr->part_bits) {
if (new->devnum < 26) {
snprintf(gd->disk_name, sizeof(gd->disk_name),
"%s%c", tr->name, 'a' + new->devnum);
}
else {
snprintf(gd->disk_name, sizeof(gd->disk_name),
"%s%c%c", tr->name,
'a' - 1 + new->devnum / 26,
'a' + new->devnum % 26);
}
}
else { //
snprintf(gd->disk_name, sizeof(gd->disk_name),
"%s%d", tr->name, new->devnum);
}
/* 2.5 has capacity in units of 512 bytes while still
having BLOCK_SIZE_BITS set to 10.
Just to keep us amused. */
set_capacity(gd, (new->size * tr->blksize) >> 9);
static inline void set_capacity(struct gendisk *disk,
sector_t size)
{
disk->part0.nr_sects = size;
}
gd->private_data = new;
new->blkcore_priv = gd;
gd->queue = tr->blkcore_priv->rq;
gd->driverfs_dev = &new->mtd->dev;
if (new->readonly) {
set_disk_ro(gd, 1);
}
add_disk(gd);
void add_disk(struct gendisk *disk)
{
struct backing_dev_info *bdi;
dev_t devt;
int retval;
/* minors == 0 indicates to use ext devt from
* part0 and should
* be accompanied with EXT_DEVT flag.
* Make sure all
* parameters make sense.
*/
WARN_ON(disk->minors &&
!(disk->major || disk->first_minor));
WARN_ON(!disk->minors &&
!(disk->flags & GENHD_FL_EXT_DEVT));
disk->flags |= GENHD_FL_UP;
retval = blk_alloc_devt(&disk->part0, &devt);
if (retval) {
WARN_ON(1);
return;
}
disk_to_dev(disk)->devt = devt;
/* ->major and ->first_minor aren't
* supposed to be
* dereferenced from here on,
* but set them just in case.
*/
disk->major = MAJOR(devt);
disk->first_minor = MINOR(devt);
blk_register_region(disk_devt(disk),
disk->minors, NULL,
exact_match, exact_lock, disk);
register_disk(disk);
blk_register_queue(disk);
bdi = &disk->queue->backing_dev_info;
bdi_register_dev(bdi, disk_devt(disk));
retval = sysfs_create_link(
&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
"bdi");
WARN_ON(retval);
}
return 0; //
}
}
}
}
}
mutex_unlock(&mtd_table_mutex);
/* We _know_ we aren't being removed, because
our caller is still holding us here. So none
of this try_ nonsense, and no bitching about it
either. :) */
__module_get(THIS_MODULE);
return 0; // 从这里 退出
}
}
mutex_unlock(&mtd_table_mutex);
return 1;
}
return slave; //
}
if (!slave)
return -ENOMEM;
cur_offset = slave->offset + slave->mtd.size;
}
return 0;
}
}
return add_mtd_device(&mtd->mtd); // 未执行
}
}
if (sets != NULL) { //
sets++;
}
}
// GM add
setno++, nmtd++;
err = s3c2410_nand_cpufreq_register(info);
static inline int s3c2410_nand_cpufreq_register(struct s3c2410_nand_info *info)
{
return 0;
}
if (allow_clk_stop(info))
static inline int allow_clk_stop(struct s3c2410_nand_info *info)
{
return clock_stop;
}
{
dev_info(&pdev->dev, "clock idle support enabled\n");
clk_disable(info->clk);
}
pr_debug("initialised ok\n");
return 0;
exit_error:
s3c24xx_nand_remove(pdev);
if (err == 0)
err = -EINVAL;
return err;
}
测试MTD字符设备读写的程序:
int main(void)
{
int fd;
char buf[4096] = {0};
int ret = 0;
struct erase_info_user erase;
char w_buf[4096] = {'j','o','k','e','r'};
struct mtd_info_user meminfo;
srand(time(NULL));
int r = rand();
fd = open(MTD_DEVICE, O_RDWR);
ret = read(fd, buf, 10);
ret = ioctl(fd, MEMGETINFO, &meminfo);
erase.start = 0;
erase.length = meminfo.erasesize;
ret = ioctl(fd, MEMERASE, &erase);
/* 偏移地址设置为0*/
ret = lseek(fd, 0, SEEK_SET);
/* 确认要写的页是否处于一个坏块内. */
if (ioctl(fd, MEMGETBADBLOCK, &offs) == 0) {
printf(错误);
return -1;
}
ret = write(fd, w_buf, meminfo.writesize);
ret = lseek(fd, 0, SEEK_SET);
ret = read(fd, buf, 10);
return 0;
}
对MTD字符设备测试程序代码与关联的MTD框架代码说明:
open函数说明:
fd = open(MTD_DEVICE, O_RDWR);
根據字符設備的次設備號, 從mtd_table[]數組裡, 找到對應的struct mtd_info結構體.一個mtd_info對應一個mtd分區.
static int mtd_open(struct inode *inode, struct file *file) <tag89>
{
/* 一个mtd分区占2个设备号, 分别是mtdx 和mtdxro */
int minor = iminor(inode);
int devnum = minor >> 1;
int ret = 0;
struct mtd_info *mtd;
struct mtd_file_info *mfi;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
if (devnum >= MAX_MTD_DEVICES)
return -ENODEV;
/* You can't open the RO devices RW */
if ((file->f_mode & FMODE_WRITE) && (minor & 1))
return -EACCES;
lock_kernel();
/* devnum 為mtd字符設備的次設備號 */
/* 根據字符設備的次設備號, 從mtd_table數組裡, 找到對應的struct mtd_info結構體.
* 一個mtd_info對應一個mtd分區.*/
mtd = get_mtd_device(NULL, devnum);
struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)
{
struct mtd_info *ret = NULL;
int i, err = -ENODEV;
mutex_lock(&mtd_table_mutex);
if (num == -1) {
for (i=0; i< MAX_MTD_DEVICES; i++)
if (mtd_table[i] == mtd)
ret = mtd_table[i];
} else if (num < MAX_MTD_DEVICES) { //
ret = mtd_table[num];
if (mtd && mtd != ret)
ret = NULL;
}
if (!ret)
goto out_unlock;
if (!try_module_get(ret->owner))
goto out_unlock;
if (ret->get_device) {
err = ret->get_device(ret);
if (err)
goto out_put;
}
ret->usecount++;
mutex_unlock(&mtd_table_mutex);
return ret; //
out_put:
module_put(ret->owner);
out_unlock:
mutex_unlock(&mtd_table_mutex);
return ERR_PTR(err);
}
if (IS_ERR(mtd)) {
ret = PTR_ERR(mtd);
goto out;
}
/* mtd_open:in 143. minor 8, devnum 4, mtd index 4 */
pr_err("joker %s:in %d. minor %d, devnum %d, mtd index %d\n",__func__,__LINE__,
minor, devnum, mtd->index);
if (mtd->type == MTD_ABSENT) {
put_mtd_device(mtd);
ret = -ENODEV;
goto out;
}
if (mtd->backing_dev_info) { //
file->f_mapping->backing_dev_info = mtd->backing_dev_info; <tag90>
}
/* You can't open it RW if it's not a writeable device */
if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
put_mtd_device(mtd);
ret = -EACCES;
goto out;
}
mfi = kzalloc(sizeof(*mfi), GFP_KERNEL); <tag90>
if (!mfi) {
put_mtd_device(mtd);
ret = -ENOMEM;
goto out;
}
mfi->mtd = mtd; <tag93>
file->private_data = mfi; <tag94>
out:
unlock_kernel();
return ret;
}
获取mtd设备的页大小,擦除大小等信息
ioctl(fd, MEMGETINFO, &meminfo);
ioctl(fd, MEMGETINFO, &meminfo);
static int mtd_ioctl(struct inode *inode, struct file *file,
u_int cmd, u_long arg)
{
pr_err("joker %s:in %d.\n",__func__,__LINE__);
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
void __user *argp = (void __user *)arg;
int ret = 0;
u_long size;
struct mtd_info_user info;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
if (cmd & IOC_IN) {
if (!access_ok(VERIFY_READ, argp, size))
return -EFAULT;
}
if (cmd & IOC_OUT) {
if (!access_ok(VERIFY_WRITE, argp, size))
return -EFAULT;
}
switch (cmd) {
case MEMGETINFO: //
info.type = mtd->type;
info.flags = mtd->flags;
info.size = mtd->size;
info.erasesize = mtd->erasesize;
info.writesize = mtd->writesize;
info.oobsize = mtd->oobsize;
/* The below fields are obsolete */
info.ecctype = -1;
info.eccsize = 0;
if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
return -EFAULT;
break;
}
default:
ret = -ENOTTY;
}
读函数说明:
ret = read(fd, buf, 10);
读函数的参数说明:
ppos偏移,是基於mtd分區的偏移.
count 為讀取的大小, 可以非mtd的頁對齊.但nandflash還是一頁一頁地讀,
把對應偏移和讀取大小的數據複製到 buf(用戶輸入參數).
读函數執行流程:
- ppos偏移會加上分區偏移地址, 為實際基於整個flash的偏移地址. 將最終偏移地址換算為頁編號.
1.1 內核分配內存"kbuf", 根據頁對齊後的 讀長度. - 讀取頁數據到"kbuf", 使用硬件ECC計算頁數據的ECC值,
- 將計算出的ECC值, 與對應頁的OOB數據內的ECC值比較, 不相等即數據錯誤, 也可能是OOB內的ECC本身讀錯誤…
- 將頁數據"kbuf", 根據用戶要求的偏移位置和大小, 選取對應"kbuf"的內存區間, 複製到用戶輸入參數buf.
*mtd_read()函數與實際nandflash控制器發送的命令對應關係:
- mtd_read()儘管傳入ppos(讀偏移)參數, 但實際在發送flash命令時, 未使用ppos參數;
- flash命令(ONFI命令)發送流程:
2.1 發送0x00命令, 表示讀操作命令開始;
2.2 發送column address頁內偏移地址值, 但該值在mtd框架中是固定寫死為0.
2.3 發送row address 頁地址, 使用ppos參數加上分區偏移地址, 換算得出的頁地址.
2.4 發送0x30命令, 表示讀操作命令發送完成.
2.5 讀對應的頁數據, 通過讀nandflash控制器的數據buf寄存器.
static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
size_t retlen=0;
size_t total_retlen=0;
int ret=0;
int len;
char *kbuf;
DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
/* 偏移+讀的字節數 不能大於該mtd分區的大小 */
if (*ppos + count > mtd->size) {
count = mtd->size - *ppos;
}
if (!count) {
return 0;
}
/* FIXME: Use kiovec in 2.5 to lock down the user's buffers
and pass them directly to the MTD functions */
if (count > MAX_KMALLOC_SIZE) {
kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
}
else { //
kbuf=kmalloc(count, GFP_KERNEL); <tag95>
}
if (!kbuf)
return -ENOMEM;
while (count) { // 仅执行1次
if (count > MAX_KMALLOC_SIZE) {
len = MAX_KMALLOC_SIZE;
}
else { //
len = count;
}
switch (mfi->mode) {
case MTD_MODE_OTP_FACTORY:
ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf);
break;
case MTD_MODE_OTP_USER:
ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
break;
case MTD_MODE_RAW:
{
struct mtd_oob_ops ops;
ops.mode = MTD_OOB_RAW;
ops.datbuf = kbuf;
ops.oobbuf = NULL;
ops.len = len;
ret = mtd->read_oob(mtd, *ppos, &ops);
retlen = ops.retlen;
break;
}
default: //
/* part_read */
ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);
static int part_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct mtd_part *part = PART(mtd);
struct mtd_ecc_stats stats;
int res;
stats = part->master->ecc_stats;
if (from >= mtd->size) {
len = 0;
}
else if (from + len > mtd->size) {
len = mtd->size - from;
}
/* nand_read */
/* from + part->offset, from 為基於分區的偏移,加上分區偏移,為時間flash上的偏移地址 */
res = part->master->read(part->master, from + part->offset, len, retlen, buf);
static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, uint8_t *buf)
{
struct nand_chip *chip = mtd->priv;
int ret;
/* Do not allow reads past end of device */
if ((from + len) > mtd->size)
return -EINVAL;
if (!len)
return 0;
nand_get_device(chip, mtd, FL_READING);
chip->ops.len = len; <tag98>
chip->ops.datbuf = buf; <tag97>
chip->ops.oobbuf = NULL;
ret = nand_do_read_ops(mtd, from, &chip->ops);
/* 讀操作可以不頁對齊, 驅動自己頁對齊後, 再返回user要求的數據. */
static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops)
{
int chipnr, page, realpage, col, bytes, aligned;
struct nand_chip *chip = mtd->priv;
struct mtd_ecc_stats stats;
int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
int sndcmd = 1;
int ret = 0;
uint32_t readlen = ops->len;
uint32_t oobreadlen = ops->ooblen;
uint8_t *bufpoi, *oob, *buf;
stats = mtd->ecc_stats;
chipnr = (int)(from >> chip->chip_shift);
chip->select_chip(mtd, chipnr);
/* from換算為頁編號 */
realpage = (int)(from >> chip->page_shift);
page = realpage & chip->pagemask;
/* 用戶指定讀取的偏移"變量from",基於分區的偏移,
* 會被換算為在該頁上的偏移"變量col". */
col = (int)(from & (mtd->writesize - 1));
buf = ops->datbuf;
oob = ops->oobbuf;
while(1) { // 只执行1次
bytes = min(mtd->writesize - col, readlen);
aligned = (bytes == mtd->writesize);
/* Is the current page in the buffer ? */
if (realpage != chip->pagebuf || oob) { //
bufpoi = aligned ? buf : chip->buffers->databuf;
if (likely(sndcmd)) { //
chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
sndcmd = 0;
}
/* Now read the page into the buffer */
if (unlikely(ops->mode == MTD_OOB_RAW)) {
ret = chip->ecc.read_page_raw(mtd, chip, bufpoi, page);
}
else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob) {
ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
}
else { //
/* nand_read_page_hwecc */
ret = chip->ecc.read_page(mtd, chip, bufpoi, page);
static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf, int page)
{
int i, eccsize = chip->ecc.size;
int eccbytes = chip->ecc.bytes;
int eccsteps = chip->ecc.steps;
uint8_t *p = buf;
uint8_t *ecc_calc = chip->buffers->ecccalc; <tag99>
uint8_t *ecc_code = chip->buffers->ecccode; <tag100>
uint32_t *eccpos = chip->ecc.layout->eccpos;
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { //i 0 3 6 ... 21
/* s3c2440_nand_enable_hwecc */
chip->ecc.hwctl(mtd, NAND_ECC_READ);
static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode)
{
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
unsigned long ctrl;
ctrl = readl(info->regs + S3C2440_NFCONT);
/* 初始化 ECC 编码器/译码器 */
writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT);
}
/* 一个24位ecc 保护 eccsize字节(256字节)的数据 */
/* s3c2440_nand_read_buf */
chip->read_buf(mtd, p, eccsize);
/* s3c2440_nand_calculate_ecc */
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
u_char *ecc_code)
{
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
unsigned long ecc = readl(info->regs + S3C2440_NFMECC0);
/* 24位ecc */
ecc_code[0] = ecc;
ecc_code[1] = ecc >> 8;
ecc_code[2] = ecc >> 16;
pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff);
return 0;
}
}
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
/* eccpos[]數組內, 每一個成員,記錄每一位ecc在oob的偏移. */
for (i = 0; i < chip->ecc.total; i++) {// i 0 ~23
ecc_code[i] = chip->oob_poi[eccpos[i]];
}
eccsteps = chip->ecc.steps;
p = buf;
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
int stat;
/* s3c2410_nand_correct_data */
stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]);
/* 将计算出的ecc 和 原本在nandflash上的oob上的ecc对比, 如果不相等,
* 并尝试纠错.*/
/* 無錯誤時返回0, 能夠糾正錯誤返回1, 錯誤位置為ECC數據本身, 返回1.
* 錯誤了但不能糾正 返回-1*/
static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
u_char *read_ecc, u_char *calc_ecc)
{
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
unsigned int diff0, diff1, diff2;
unsigned int bit, byte;
pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc);
diff0 = read_ecc[0] ^ calc_ecc[0];
diff1 = read_ecc[1] ^ calc_ecc[1];
diff2 = read_ecc[2] ^ calc_ecc[2];
pr_debug("%s: rd %02x%02x%02x calc %02x%02x%02x diff %02x%02x%02x\n",
__func__,
read_ecc[0], read_ecc[1], read_ecc[2],
calc_ecc[0], calc_ecc[1], calc_ecc[2],
diff0, diff1, diff2);
if (diff0 == 0 && diff1 == 0 && diff2 == 0) { // 从这里 退出
return 0; /* ECC is ok */
}
/* sometimes people do not think about using the ECC, so check
* to see if we have an 0xff,0xff,0xff read ECC and then ignore
* the error, on the assumption that this is an un-eccd page.
*/
if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff
&& info->platform->ignore_unset_ecc) {
return 0;
}
/* Can we correct this ECC (ie, one row and column change).
* Note, this is similar to the 256 error code on smartmedia */
if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 &&
((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 &&
((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) {
/* calculate the bit position of the error */
bit = ((diff2 >> 3) & 1) |
((diff2 >> 4) & 2) |
((diff2 >> 5) & 4);
/* calculate the byte position of the error */
byte = ((diff2 << 7) & 0x100) |
((diff1 << 0) & 0x80) |
((diff1 << 1) & 0x40) |
((diff1 << 2) & 0x20) |
((diff1 << 3) & 0x10) |
((diff0 >> 4) & 0x08) |
((diff0 >> 3) & 0x04) |
((diff0 >> 2) & 0x02) |
((diff0 >> 1) & 0x01);
dev_dbg(info->device, "correcting error bit %d, byte %d\n",
bit, byte);
dat[byte] ^= (1 << bit);
return 1;
}
/* if there is only one bit difference in the ECC, then
* one of only a row or column parity has changed, which
* means the error is most probably in the ECC itself */
diff0 |= (diff1 << 8);
diff0 |= (diff2 << 16);
if ((diff0 & ~(1<<fls(diff0))) == 0) {
return 1;
}
return -1;
}
if (stat < 0) {
mtd->ecc_stats.failed++; <tag111>
}
else {//i 0 3 6 ... 21
/* stat的值:
* 無錯誤時返回0, 能夠糾正錯誤返回1, 錯誤位置為ECC數據本身, 返回1.
* 因此 corrected記錄錯誤被糾正的次數.*/
mtd->ecc_stats.corrected += stat; <tag110>
}
}
return 0;
}
}
if (ret < 0) {
break;
}
/* Transfer not aligned data */
if (!aligned) { //
if (!NAND_SUBPAGE_READ(chip) && !oob) { //
chip->pagebuf = realpage;
}
memcpy(buf, chip->buffers->databuf + col, bytes);
}
buf += bytes;
if (unlikely(oob)) {
/* Raw mode does data:oob:data:oob */
if (ops->mode != MTD_OOB_RAW) {
int toread = min(oobreadlen,
chip->ecc.layout->oobavail);
if (toread) {
oob = nand_transfer_oob(chip,
oob, ops, toread);
oobreadlen -= toread;
}
} else {
buf = nand_transfer_oob(chip,
buf, ops, mtd->oobsize);
}
}
if (!(chip->options & NAND_NO_READRDY)) {
/*
* 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 (!chip->dev_ready) {
udelay(chip->chip_delay);
}
else {
nand_wait_ready(mtd);
}
}
} else {
/* 用戶指定讀取的偏移"變量from",基於分區的偏移,
* 會被換算為在該頁上的偏移"變量col". */
memcpy(buf, chip->buffers->databuf + col, bytes);
buf += bytes;
}
readlen -= bytes;
if (!readlen) { //
break;
}
/* For subsequent reads align to page boundary. */
col = 0;
/* Increment page address */
realpage++;
page = realpage & chip->pagemask;
/* Check, if we cross a chip boundary */
if (!page) {
chipnr++;
chip->select_chip(mtd, -1);
chip->select_chip(mtd, chipnr);
}
/* Check, if the chip supports auto page increment
* or if we have hit a block boundary.
*/
if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck)) {
sndcmd = 1;
}
}
ops->retlen = ops->len - (size_t) readlen;
if (oob) {
ops->oobretlen = ops->ooblen - oobreadlen;
}
if (ret) {
return ret;
}
if (mtd->ecc_stats.failed - stats.failed)
return -EBADMSG;
/* stats為臨時變量, 保存讀頁數據操作前該分區的mtd->ecc_stats的數據.
* 這裡就是將頁數據讀取完成前後, corrected值是否增加,
* 增加表明曾經遇到ECC錯誤, 但糾正了, 或遇到ECC本身的錯.*/
return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0; //
}
*retlen = chip->ops.retlen;
nand_release_device(mtd);
return ret;
}
if (unlikely(res)) {
if (res == -EUCLEAN) {
/* mtd代表整個nandflash,其corrected變量增加
* 每個mtd分區(part->master)的新增的corrected值.*/
mtd->ecc_stats.corrected += part->master->ecc_stats.corrected - stats.corrected; <tag112>
}
if (res == -EBADMSG) {
mtd->ecc_stats.failed += part->master->ecc_stats.failed - stats.failed; <tag113>
}
}
return res; //
}
}
/* Nand returns -EBADMSG on ecc errors, but it returns
* the data. For our userspace tools it is important
* to dump areas with ecc errors !
* For kernel internal usage it also might return -EUCLEAN
* to signal the caller that a bitflip has occured and has
* been corrected by the ECC algorithm.
* Userspace software which accesses NAND this way
* must be aware of the fact that it deals with NAND
*/
if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) { //
*ppos += retlen;
if (copy_to_user(buf, kbuf, retlen)) {
kfree(kbuf);
return -EFAULT;
}
else { //
total_retlen += retlen;
}
count -= retlen;
buf += retlen;
if (retlen == 0) {
count = 0;
}
}
else {
kfree(kbuf);
return ret;
}
}
kfree(kbuf);
return total_retlen; //
}
擦除操作函数说明:
ret = ioctl(fd, MEMERASE, &erase);
擦除操作, 擦除起始地址和 擦除大小必需是 塊大小對齊(物理擦除大小對齊).
執行流程:
- 檢查 擦除起始地址和擦除大小是否塊對齊;
- 檢查 flash是否寫保存;
- 檢查 擦除範圍內, 是否包含壞塊, 有壞處直接返回錯誤…
- 取消擦除範圍內的頁的緩存;
- 執行擦除命令, ONFI規範命令 0x60.
- 等待擦除操作完成, 等待ONFI規範 中的SR6的值從0變為1;
static int mtd_ioctl(struct inode *inode, struct file *file,
u_int cmd, u_long arg)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
void __user *argp = (void __user *)arg;
int ret = 0;
u_long size;
struct mtd_info_user info;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
if (cmd & IOC_IN) {
if (!access_ok(VERIFY_READ, argp, size))
return -EFAULT;
}
if (cmd & IOC_OUT) {
if (!access_ok(VERIFY_WRITE, argp, size))
return -EFAULT;
}
switch (cmd) {
case MEMERASE:
case MEMERASE64: //
{
struct erase_info *erase;
if(!(file->f_mode & FMODE_WRITE))
return -EPERM;
erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL); <tag101>
if (!erase)
ret = -ENOMEM;
else { //
wait_queue_head_t waitq; <tag106>
DECLARE_WAITQUEUE(wait, current);
init_waitqueue_head(&waitq);
if (cmd == MEMERASE64) {
struct erase_info_user64 einfo64;
if (copy_from_user(&einfo64, argp,
sizeof(struct erase_info_user64))) {
kfree(erase);
return -EFAULT;
}
erase->addr = einfo64.start;
erase->len = einfo64.length;
} else { //
struct erase_info_user einfo32;
if (copy_from_user(&einfo32, argp,
sizeof(struct erase_info_user))) { <tag102>
kfree(erase);
return -EFAULT;
}
/* 用戶傳入參數"argp"的數據,複製到kernel分配的內存 "erase" */
erase->addr = einfo32.start; <tag103>
erase->len = einfo32.length;
}
erase->mtd = mtd; <tag104>
erase->callback = mtdchar_erase_callback;
erase->priv = (unsigned long)&waitq; <tag105>
/*
FIXME: Allow INTERRUPTIBLE. Which means
not having the wait_queue head on the stack.
If the wq_head is on the stack, and we
leave because we got interrupted, then the
wq_head is no longer there when the
callback routine tries to wake us up.
*/
/* part_erase */
ret = mtd->erase(mtd, erase);
static int part_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct mtd_part *part = PART(mtd);
#define PART(x) ((struct mtd_part *)(x))
int ret;
if (!(mtd->flags & MTD_WRITEABLE))
return -EROFS;
if (instr->addr >= mtd->size)
return -EINVAL;
/* 用戶傳入的擦除地址, 是基於分區地址的.
* 因此擦除地址+分區地址, 才是flash上實際的擦除地址.*/
instr->addr += part->offset; <tag107>
/* nand_erase */
ret = part->master->erase(part->master, instr);
static int nand_erase(struct mtd_info *mtd, struct erase_info *instr)
{
return nand_erase_nand(mtd, instr, 0); /* mtd point to master obj11*/
int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
int allowbbt)
{
int page, status, pages_per_block, ret, chipnr;
struct nand_chip *chip = mtd->priv;
loff_t rewrite_bbt[NAND_MAX_CHIPS]={0};
unsigned int bbt_masked_page = 0xffffffff;
loff_t len;
DEBUG(MTD_DEBUG_LEVEL3, "%s: start = 0x%012llx, len = %llu\n",
__func__, (unsigned long long)instr->addr,
(unsigned long long)instr->len);
/* Start address must align on block boundary */
/* 擦除起始地址,必需是塊大小對齊. */
if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Unaligned address\n", __func__);
return -EINVAL;
}
/* Length must align on block boundary */
/* 擦除大小, 必需是物理擦除size對齊(塊大小對齊) */
if (instr->len & ((1 << chip->phys_erase_shift) - 1)) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Length not block aligned\n",
__func__);
return -EINVAL;
}
/* Do not allow erase past end of device */
/* 偏移+擦除大小, 不能大於該mtd分區的大小 */
if ((instr->len + instr->addr) > mtd->size) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Erase past end of device\n",
__func__);
return -EINVAL;
}
instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
/* Grab the lock and see if the device is available */
nand_get_device(chip, mtd, FL_ERASING);
/* Shift to get first page */
/* 將起始地址, 換算為頁編號 */
/* 由於instr->addr是塊對齊, 因此 變量"page"
* 就是當前塊的第一個頁的編號*/
page = (int)(instr->addr >> chip->page_shift);
chipnr = (int)(instr->addr >> chip->chip_shift);
/* Calculate pages in each block */
/* 計算每一個塊, 包含的頁的數目 */
pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
/* Select the NAND device */
/* s3c2410_nand_select_chip */
chip->select_chip(mtd, chipnr);
/* Check, if it is write protected */
if (nand_check_wp(mtd))
static int nand_check_wp(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
/* Check the WP bit */
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
}
{
DEBUG(MTD_DEBUG_LEVEL0, "%s: Device is write protected!!!\n",
__func__);
instr->state = MTD_ERASE_FAILED;
goto erase_exit;
}
/*
* If BBT requires refresh, set the BBT page mask to see if the BBT
* should be rewritten. Otherwise the mask is set to 0xffffffff which
* can not be matched. This is also done when the bbt is actually
* erased to avoid recusrsive updates
*/
if (chip->options & BBT_AUTO_REFRESH && !allowbbt) { //
bbt_masked_page = chip->bbt_td->pages[chipnr] & BBT_PAGE_MASK;
}
/* Loop through the pages */
len = instr->len;
instr->state = MTD_ERASING;
while (len) { /* 仅执行一次 */
/*
* heck if we have a bad block, we do not erase bad blocks !
*/
/* 當擦除的地址內, 包含壞塊, 則返回失敗.
* 程序不進行壞塊的擦除操作....*/
if (nand_block_checkbad(mtd, ((loff_t) page) <<
chip->page_shift, 0, allowbbt)) {
printk(KERN_WARNING "%s: attempt to erase a bad block "
"at page 0x%08x\n", __func__, page);
instr->state = MTD_ERASE_FAILED;
goto erase_exit;
}
/*
* Invalidate the page cache, if we erase the block which
* contains the current cached page
*/
/* 當要擦除的塊內, 有頁緩存, 這些頁緩存也將會無用.
* 因為,塊擦除是這些頁最後的操作, 頁緩存的數據自然也過時了.*/
if (page <= chip->pagebuf && chip->pagebuf <
(page + pages_per_block)) { //
chip->pagebuf = -1;
}
/* single_erase_cmd */
chip->erase_cmd(mtd, page & chip->pagemask);
static void single_erase_cmd(struct mtd_info *mtd, int page)
{
struct nand_chip *chip = mtd->priv;
/* Send commands to erase a block */
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
}
/* nand_wait */
status = chip->waitfunc(mtd, chip);
static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
{
unsigned long timeo = jiffies;
int status, state = chip->state;
if (state == FL_ERASING) { //
timeo += (HZ * 400) / 1000;
}
else {
timeo += (HZ * 20) / 1000;
}
led_trigger_event(nand_led_trigger, LED_FULL);
/* Apply this short delay always to ensure that we do wait tWB in
* any case on any machine. */
ndelay(100);
if ((state == FL_ERASING) && (chip->options & NAND_IS_AND)) {
chip->cmdfunc(mtd, NAND_CMD_STATUS_MULTI, -1, -1);
}
else {
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); //
}
/* 等待讀到的值為1, 即操作完成了. */
while (time_before(jiffies, timeo)) {
if (chip->dev_ready) { //
/* s3c2440_nand_devready */
if (chip->dev_ready(mtd))
static int s3c2440_nand_devready(struct mtd_info *mtd)
{
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
/* 等待讀到的值為1, 即操作完成了. */
return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
}
{ //
break;
}
} else {
if (chip->read_byte(mtd) & NAND_STATUS_READY) {
break;
}
}
cond_resched();
}
led_trigger_event(nand_led_trigger, LED_OFF);
status = (int)chip->read_byte(mtd);
return status; //
}
/*
* See if operation failed and additional status checks are
* available
*/
if ((status & NAND_STATUS_FAIL) && (chip->errstat)) {
status = chip->errstat(mtd, chip, FL_ERASING, status, page);
}
/* See if block erase succeeded */
if (status & NAND_STATUS_FAIL) {
DEBUG(MTD_DEBUG_LEVEL0, "%s: Failed erase, "
"page 0x%08x\n", __func__, page);
instr->state = MTD_ERASE_FAILED;
instr->fail_addr =
((loff_t)page << chip->page_shift);
goto erase_exit;
}
/*
* If BBT requires refresh, set the BBT rewrite flag to the
* page being erased
*/
if (bbt_masked_page != 0xffffffff &&
(page & BBT_PAGE_MASK) == bbt_masked_page) {
rewrite_bbt[chipnr] =
((loff_t)page << chip->page_shift);
}
/* Increment page address and decrement length */
len -= (1 << chip->phys_erase_shift);
page += pages_per_block;
/* Check, if we cross a chip boundary */
if (len && !(page & chip->pagemask)) {
chipnr++;
chip->select_chip(mtd, -1);
chip->select_chip(mtd, chipnr);
/*
* If BBT requires refresh and BBT-PERCHIP, set the BBT
* page mask to see if this BBT should be rewritten
*/
if (bbt_masked_page != 0xffffffff &&
(chip->bbt_td->options & NAND_BBT_PERCHIP)) {
bbt_masked_page = chip->bbt_td->pages[chipnr] &
BBT_PAGE_MASK;
}
}
}
instr->state = MTD_ERASE_DONE;
erase_exit:
ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
/* Deselect and wake up anyone waiting on the device */
nand_release_device(mtd);
/* Do call back function */
if (!ret) { //
mtd_erase_callback(instr);
void mtd_erase_callback(struct erase_info *instr)
{
if (instr->mtd->erase == part_erase) { //
struct mtd_part *part = PART(instr->mtd);
if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN) {
instr->fail_addr -= part->offset;
}
instr->addr -= part->offset;
}
if (instr->callback) { //
/* mtdchar_erase_callback */
instr->callback(instr);
static void mtdchar_erase_callback (struct erase_info *instr)
{
wake_up((wait_queue_head_t *)instr->priv);
}
}
}
}
/*
* If BBT requires refresh and erase was successful, rewrite any
* selected bad block tables
*/
if (bbt_masked_page == 0xffffffff || ret) { //
return ret; //从这里退出
}
for (chipnr = 0; chipnr < chip->numchips; chipnr++) {
if (!rewrite_bbt[chipnr])
continue;
/* update the BBT for chip */
DEBUG(MTD_DEBUG_LEVEL0, "%s: nand_update_bbt "
"(%d:0x%0llx 0x%0x)\n", __func__, chipnr,
rewrite_bbt[chipnr], chip->bbt_td->pages[chipnr]);
nand_update_bbt(mtd, rewrite_bbt[chipnr]);
}
/* Return more or less happy */
return ret;
}
}
if (ret) {
if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN) {
instr->fail_addr -= part->offset;
}
instr->addr -= part->offset;
}
return ret; //
}
if (!ret) { //
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&waitq, &wait);
if (erase->state != MTD_ERASE_DONE &&
erase->state != MTD_ERASE_FAILED)
schedule();
remove_wait_queue(&waitq, &wait);
set_current_state(TASK_RUNNING);
ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
}
kfree(erase);
}
break;
}
}
default:
ret = -ENOTTY;
}
return ret; //
}
偏移设置函数:
ret = lseek(fd, 0, SEEK_SET);
lseek(fd, 0, SEEK_SET);
static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
switch (orig) {
case SEEK_SET:
break;
case SEEK_CUR:
offset += file->f_pos;
break;
case SEEK_END:
offset += mtd->size;
break;
default:
return -EINVAL;
}
if (offset >= 0 && offset <= mtd->size)
return file->f_pos = offset;
return -EINVAL;
}
坏块判断函数说明:
if (ioctl(fd, MEMGETBADBLOCK, &offs) == 0)
檢查偏移地址"offs" 是否處於一個壞塊內, 通過查詢壞塊表.
傳入的偏移地址"offs" 會加上當前分區的偏移, 獲得最終基於整個flash設備的偏移.
因為壞塊不能擦除, 向壞塊執行ioctl(擦除), 會直接返回錯誤.
static int mtd_ioctl(struct inode *inode, struct file *file,
u_int cmd, u_long arg)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
void __user *argp = (void __user *)arg;
int ret = 0;
u_long size;
struct mtd_info_user info;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
if (cmd & IOC_IN) {
if (!access_ok(VERIFY_READ, argp, size))
return -EFAULT;
}
if (cmd & IOC_OUT) {
if (!access_ok(VERIFY_WRITE, argp, size))
return -EFAULT;
}
switch (cmd) {
case MEMGETBADBLOCK: //
{
loff_t offs;
if (copy_from_user(&offs, argp, sizeof(loff_t)))
return -EFAULT;
if (!mtd->block_isbad)
ret = -EOPNOTSUPP;
else
/* part_block_isbad */
return mtd->block_isbad(mtd, offs);
static int part_block_isbad(struct mtd_info *mtd, loff_t ofs)
{
struct mtd_part *part = PART(mtd);
if (ofs >= mtd->size)
return -EINVAL;
ofs += part->offset;
/* nand_block_isbad */
return part->master->block_isbad(part->master, ofs);
}
break;
}
}
return ret;
}
写函数说明:
ret = write(fd, w_buf, meminfo.writesize);
執行流程:
- 分配內存內存"kbuf", 將用戶傳入參數"buf" 複製到"kbuf"
- 檢查 該mtd分區是否可寫, 否則返回錯誤
- 檢查寫地址和寫長度 是否頁對齊, 否則返回錯誤;
- 將內核的臨時oob內存 memset()為 0xFF.
- 將頁數據寫入nandflash, 並計算其ECC.
- 將ECC寫入內核臨時oob內存的ECC位置的內存. 將內核臨時oob內存寫入nandflash.
static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
char *kbuf;
size_t retlen;
size_t total_retlen=0;
int ret=0;
int len;
DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n");
if (*ppos == mtd->size)
return -ENOSPC;
/* 寫size可以大於對應mtd分區的size, 因為會被修正.*/
if (*ppos + count > mtd->size) {
count = mtd->size - *ppos;
}
if (!count) {
return 0;
}
if (count > MAX_KMALLOC_SIZE) {
kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
}
else { //
kbuf=kmalloc(count, GFP_KERNEL);
}
if (!kbuf)
return -ENOMEM;
while (count) { /* 只执行一次... */
if (count > MAX_KMALLOC_SIZE) {
len = MAX_KMALLOC_SIZE;
}
else { //
len = count;
}
if (copy_from_user(kbuf, buf, len)) {
kfree(kbuf);
return -EFAULT;
}
switch (mfi->mode) {
case MTD_MODE_OTP_FACTORY:
ret = -EROFS;
break;
case MTD_MODE_OTP_USER:
if (!mtd->write_user_prot_reg) {
ret = -EOPNOTSUPP;
break;
}
ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
break;
case MTD_MODE_RAW:
{
struct mtd_oob_ops ops;
ops.mode = MTD_OOB_RAW;
ops.datbuf = kbuf;
ops.oobbuf = NULL;
ops.len = len;
ret = mtd->write_oob(mtd, *ppos, &ops);
retlen = ops.retlen;
break;
}
default:
/* part_write */
ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); //
static int part_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct mtd_part *part = PART(mtd);
/* mtd分區沒有 可寫標誌, 返回錯誤. */
if (!(mtd->flags & MTD_WRITEABLE)) {
return -EROFS;
}
if (to >= mtd->size) {
len = 0;
}
else if (to + len > mtd->size) {
len = mtd->size - to;
}
/* nand_write */
return part->master->write(part->master, to + part->offset, len, retlen, buf); //
static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const uint8_t *buf)
{
struct nand_chip *chip = mtd->priv;
int ret;
/* Do not allow reads past end of device */
if ((to + len) > mtd->size)
return -EINVAL;
if (!len)
return 0;
nand_get_device(chip, mtd, FL_WRITING);
chip->ops.len = len;
chip->ops.datbuf = (uint8_t *)buf;
chip->ops.oobbuf = NULL;
ret = nand_do_write_ops(mtd, to, &chip->ops);
static int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
int chipnr, realpage, page, blockmask, column;
struct nand_chip *chip = mtd->priv;
uint32_t writelen = ops->len;
uint8_t *oob = ops->oobbuf;
uint8_t *buf = ops->datbuf;
int ret, subpage;
ops->retlen = 0;
if (!writelen)
return 0;
/* reject writes, which are not page aligned */
/* 寫數據的偏移地址和寫長度, 必需頁對齊, 否則直接返回錯誤. */
if (NOTALIGNED(to) || NOTALIGNED(ops->len)) {
printk(KERN_NOTICE "%s: Attempt to write not "
"page aligned data\n", __func__);
return -EINVAL;
}
column = to & (mtd->writesize - 1);
subpage = column || (writelen & (mtd->writesize - 1));
if (subpage && oob)
return -EINVAL;
chipnr = (int)(to >> chip->chip_shift);
chip->select_chip(mtd, chipnr);
/* Check, if it is write protected */
/* 寫保護檢查 */
if (nand_check_wp(mtd))
return -EIO;
realpage = (int)(to >> chip->page_shift);
page = realpage & chip->pagemask;
blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
/* Invalidate the page cache, when we write to the cached page */
/* 使要寫入的頁的頁緩存無效 */
if (to <= (chip->pagebuf << chip->page_shift) &&
(chip->pagebuf << chip->page_shift) < (to + ops->len)) {
chip->pagebuf = -1;
}
/* If we're not given explicit OOB data, let it be 0xFF */
/* 沒有明確的要寫入flash的的OOB數據, 則默認都寫入0xFF. */
/* 這裡的chip結構體, 由master(代表整個flash的 struct mtd_info 提供, 非代表分區的mtd_info提供). */
if (likely(!oob)) { //
memset(chip->oob_poi, 0xff, mtd->oobsize); <tag114>
}
while(1) { //
int bytes = mtd->writesize;
int cached = writelen > bytes && page != blockmask;
uint8_t *wbuf = buf;
/* Partial page write ? */
if (unlikely(column || writelen < (mtd->writesize - 1))) {
cached = 0;
bytes = min_t(int, bytes - column, (int) writelen);
chip->pagebuf = -1;
memset(chip->buffers->databuf, 0xff, mtd->writesize);
memcpy(&chip->buffers->databuf[column], buf, bytes);
wbuf = chip->buffers->databuf;
}
if (unlikely(oob)) {
oob = nand_fill_oob(chip, oob, ops);
}
/* nand_write_page */
ret = chip->write_page(mtd, chip, wbuf, page, cached, (ops->mode == MTD_OOB_RAW)); //
static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int page, int cached, int raw)
{
int status;
/* 寫nandflash的片上頁緩存/直接寫頁數據? 根據後一個命令而定 */
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
if (unlikely(raw)) {
chip->ecc.write_page_raw(mtd, chip, buf);
}
else { //
/* nand_write_page_hwecc */
chip->ecc.write_page(mtd, chip, buf);
static void nand_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf)
{
int i, eccsize = chip->ecc.size;
int eccbytes = chip->ecc.bytes;
int eccsteps = chip->ecc.steps;
uint8_t *ecc_calc = chip->buffers->ecccalc;
const uint8_t *p = buf;
uint32_t *eccpos = chip->ecc.layout->eccpos;
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { // i 0, 3,6 ... 21
/* s3c2440_nand_enable_hwecc */
chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
/* s3c2440_nand_write_buf */
chip->write_buf(mtd, p, eccsize);
static void s3c2440_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
{
struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
/* writesl() = write long(32位), 按照4個字節來寫入. */
writesl(info->regs + S3C2440_NFDATA, buf, len >> 2);
/* cleanup any fractional write */
/* 非4字節對齊的長度, 剩餘的數據, 就一個字節一個字節地寫入 */
if (len & 3) {
buf += len & ~3;
for (; len & 3; len--, buf++)
writeb(*buf, info->regs + S3C2440_NFDATA);
}
}
/* s3c2440_nand_calculate_ecc */
/* 計算每次寫入的數據的ECC, 得出的ECC寫入ecc_calc[] */
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
}
/* 使用計算得出的ECC填充 臨時oob(chip->oob_poi)內的ECC位置的內容 */
for (i = 0; i < chip->ecc.total; i++) { // i 0~23
chip->oob_poi[eccpos[i]] = ecc_calc[i];
}
/* s3c2440_nand_write_buf */
/* 寫入OOB */
/* 似乎ONFI操作:寫頁緩存, 頁緩存的寫偏移地址會自動累加? */
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
}
}
/*
* Cached progamming disabled for now, Not sure if its worth the
* trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
*/
cached = 0;
if (!cached || !(chip->options & NAND_CACHEPRG)) { //
/* 直接將頁數據 寫入nandflash, 而不是nandflash的片上頁緩存. */
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
status = chip->waitfunc(mtd, chip);
/*
* See if operation failed and additional status checks are
* available
*/
if ((status & NAND_STATUS_FAIL) && (chip->errstat)) {
status = chip->errstat(mtd, chip, FL_WRITING, status, page);
}
if (status & NAND_STATUS_FAIL)
return -EIO;
} else {
chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
status = chip->waitfunc(mtd, chip);
}
#ifdef CONFIG_MTD_NAND_VERIFY_WRITE /* 未开启 */
/* Send command to read back the data */
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
if (chip->verify_buf(mtd, buf, mtd->writesize))
return -EIO;
#endif
return 0; //
}
if (ret) {
break;
}
writelen -= bytes;
if (!writelen) { //
break;
}
column = 0;
buf += bytes;
realpage++;
page = realpage & chip->pagemask;
/* Check, if we cross a chip boundary */
if (!page) {
chipnr++;
chip->select_chip(mtd, -1);
chip->select_chip(mtd, chipnr);
}
}
ops->retlen = ops->len - writelen;
if (unlikely(oob)) {
ops->oobretlen = ops->ooblen;
}
return ret; //
}
*retlen = chip->ops.retlen;
nand_release_device(mtd);
return ret;
}
}
}
if (!ret) { //
*ppos += retlen;
total_retlen += retlen;
count -= retlen;
buf += retlen;
}
else {
kfree(kbuf);
return ret;
}
}
kfree(kbuf);
return total_retlen; //
}