Linux内核MTD字符设备及nandflash控制器驱动代码阅读

文档背景说明及相关资源:

  1. 基于mini2440 linux kernel 2.6版本.
  2. 执行情景分析法进行代码阅读: https://blog.csdn.net/weixin_45154862/article/details/129124851
  3. 文档原稿: https://gitee.com/suiren/mtd_char_device_read/blob/master/mtd_read.c
  4. MTD字符设备测试程序源码: https://gitee.com/suiren/mtd_char_device_read/blob/master/app/mtd_rw.c

mtd框架說明:

  1. 基於struct mtd_info結構體, 用於表示整個flash設備, 也可以用於表示一個mtd分區;
  2. struct nand_chip 結構體 保存nandflash的信息,比如頁大小, 擦除大小等, 也保存各種讀,寫,擦除,chip select等函數指針;
    從mini2440的nandflash控制器驅動來看, 只有代表整個整個flash設備的mtd_info結構體的priv成員才指向nand_chip結構體.
    代表mtd分區所執行的抽象等級更高的讀寫等函數, 最終會調用代表整個flash設備的mtd_info結構體指向的nand_chip結構體.
  3. struct mtd_part slave結構體, 每個mtd分區都分配這麼一個結構體,
    struct mtd_info 结构体, 每个分区都对应一个mtd_info.
    slave與mtd_info結構體關係為:
    slave->mtd.
  4. 控制器的驅動中, 有使用到的數組和隊列:
    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 硬件說明:

  1. 每個頁上的OOB數據是誰寫入的?
    執行寫頁數據函數時, 函數會計算要寫入的頁數據的ECC, 並且因為頁所在的塊是好快, 因此OOB數據內的標準壞塊好壞的內容也同時被設置.
    這是未指定OOB數據內容時的寫頁數據操作流程.
    在linux內核的壞塊掃描函數執行時, 每個頁的OOB數據部分都不為空, 是因為在使用uboot燒錄文件系統時,
    uboot的寫頁數據函數一樣會計算ECC等等並寫入flash的OOB, 或者有些文件系統指定了OOB數據的內容.

nandflash控制器初始化執行流程说明:

  1. 設置nandflash控制器的flash命令的執行時序, 使能nandflash控制器;
  2. 為 struct mtd_info master分配內存, 該結構體代表整個nandflash.
  3. 設置struct nand_chip *chip內的各種讀,寫,擦除,chip select, ecc 計算, 等函數指針的值;
    struct mtd_info master與 struct nand_chip chip的關係為: master->priv = chip.
  4. 發送 READ_ID指令, 讀flash 的信息. 這一步可以獲取flash的ID, 以及寫大小(頁大小),擦除大小(塊大小),每個頁的OOB大小.
  5. 建立壞塊表, 對應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(用戶輸入參數).

读函數執行流程:

  1. ppos偏移會加上分區偏移地址, 為實際基於整個flash的偏移地址. 將最終偏移地址換算為頁編號.
    1.1 內核分配內存"kbuf", 根據頁對齊後的 讀長度.
  2. 讀取頁數據到"kbuf", 使用硬件ECC計算頁數據的ECC值,
  3. 將計算出的ECC值, 與對應頁的OOB數據內的ECC值比較, 不相等即數據錯誤, 也可能是OOB內的ECC本身讀錯誤…
  4. 將頁數據"kbuf", 根據用戶要求的偏移位置和大小, 選取對應"kbuf"的內存區間, 複製到用戶輸入參數buf.

*mtd_read()函數與實際nandflash控制器發送的命令對應關係:

  1. mtd_read()儘管傳入ppos(讀偏移)參數, 但實際在發送flash命令時, 未使用ppos參數;
  2. 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);

擦除操作, 擦除起始地址和 擦除大小必需是 塊大小對齊(物理擦除大小對齊).
執行流程:

  1. 檢查 擦除起始地址和擦除大小是否塊對齊;
  2. 檢查 flash是否寫保存;
  3. 檢查 擦除範圍內, 是否包含壞塊, 有壞處直接返回錯誤…
  4. 取消擦除範圍內的頁的緩存;
  5. 執行擦除命令, ONFI規範命令 0x60.
  6. 等待擦除操作完成, 等待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);

執行流程:

  1. 分配內存內存"kbuf", 將用戶傳入參數"buf" 複製到"kbuf"
  2. 檢查 該mtd分區是否可寫, 否則返回錯誤
  3. 檢查寫地址和寫長度 是否頁對齊, 否則返回錯誤;
  4. 將內核的臨時oob內存 memset()為 0xFF.
  5. 將頁數據寫入nandflash, 並計算其ECC.
  6. 將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; //
}
  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值