文章目录

  • 一,前言
  • 二,硬件电路
    • 2.1 Nand flash相关
    • 2.2 S3c2440相关
    • 2.3 Nand flash 位反转
  • 三,Nand flash驱动框架
  • 四,S3c2440 Nand Flash驱动的加载过程
    • 4.1 S3c2440 Nand Flash -- 设备注册
    • 4.2 S3c2440 Nand Flash -- 驱动注册
    • 4.3 S3c2440 Nand Flash -- 设备-驱动匹配
      • 4.3.1 设备注册到平台总线时的匹配
      • 4.3.2 驱动注册到平台总线时的匹配
    • 4.4 S3c2440 Nand Flash -- 调用驱动的probe函数
  • 五,驱动的probe函数分析
    • 5.1 使能时钟
    • 5.2 端口映射
    • 5.3 s3c2440 nand flash控制器设置
      • 5.3.1 设置脉冲宽度和间隔
    • 5.4 设置struct nand_chip
    • 5.5 nand_scan流程分析
    • 5.6 添加分区
      • 5.6.1 遍历mtd_notifiers,通过其add接口添加分区
      • 5.6.2 设置mtd_notifiers链表
        • 5.6.2.1 nand flash 字符设备
        • 5.6.2.2 nand flash的块设备
      • 5.6.3 块设备初始化请求队列
  • 六,总结
    • 6.1 nand flash字符设备创建过程
    • 6.2 nand flash块设备创建过程
    • 6.3 一次应用层读取Nand Flash数据的过程

一,前言

nand flash驱动开发总结,涉及到s3c2440芯片nand flash控制器的设置及操作、K9F2G08U0C nand flash的设置及操作、平台总线-驱动-设备模型等相关知识。

二,硬件电路

2.1 Nand flash相关

LDATA0~LDATA7:传输命令、地址和数据。
RnB:nand flash的工作状态,0表示就绪,1表示正忙。
CLE:决定DATA0~DATA7传输的是数据还是命令,1为命令,0为数据。
nFCE:nand flash的片选,1表示选中,0表示未选中(选中才能对其进行操作)。
ALE:决定DATA0~DATA7传输的是数据还是地址,1为地址,0为数据(CLE和ALE为0)。
nFWE:为0表示写操作(写命令、地址、数据)。
nFRE:为0表示读操作。

2.2 S3c2440相关

根据Nand Flash的芯片书册知,其需要五个字节表示地址,即五个地址周期,其一页的大小为2KB,8位数据/地址传输。根据S3c2440芯片书册可知,需要NCON、GPG13、GPG14为1,GPG15为0,即前者加上拉电阻,后者加下拉电阻。


2.3 Nand flash 位反转

由于Nand Flash的固有特性,在读写数据过程中,偶然会产生一位或几位数据错误(这种概率很低),bit位从“1”变为“0”,或者从“1”变为“0”。当位反转发生在关键的代码、数据上时,有可能导致系统崩溃。当仅仅是报告位反转,重新读取即可。如果确实发生了位反转,则必须有相应的错误检测/恢复措施。在NAND Flash上发生位反转的概率很高,推荐使用EDC/ECC进行错误检测和恢复。

三,Nand flash驱动框架

四,S3c2440 Nand Flash驱动的加载过程

S3c2440 Nand Flash驱动使用了平台总线-驱动-设备模型。

4.1 S3c2440 Nand Flash – 设备注册

linux-2.6.22.6/.config

CONFIG_ARCH_S3C2440=y

linux-2.6.22.6/arch/arm/mach-s3c2440/mach-smdk2440.c

MACHINE_START(S3C2440, "SMDK2440")/* Maintainer: Ben Dooks <ben@fluff.org> */.phys_io   = S3C2410_PA_UART,.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,.boot_params   = S3C2410_SDRAM_PA + 0x100,.init_irq  = s3c24xx_init_irq,.map_io     = smdk2440_map_io,.init_machine    = smdk2440_machine_init,.timer     = &s3c24xx_timer,
MACHINE_END

将上面的宏展开

static const struct machine_desc __mach_desc_SMDK2440__attribute_used____attribute__((__section__(".arch.info.init"))) = {.nr = MACH_TYPE_SMDK2410, /* architecture number */.name = "SMDK2440", /* architecture name *//* Maintainer: Jonas Dietsche */.phys_io = S3C2410_PA_UART, /* start of physical io */.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,.boot_params = S3C2410_SDRAM_PA + 0x100, /* tagged list */.map_io = smdk2440_map_io, /* IO mapping function */.init_irq = s3c24xx_init_irq,.init_machine = smdk2440_machine_init,.timer = &s3c24xx_timer,
}

MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(“.arch.info.init”),是初始化数据,Kernel 起来之后将被丢弃。
各个成员函数在不同时期被调用:

  1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
  2. init_irq在start_kernel() -> init_IRQ() -> init_arch_irq() 被调用
  3. map_io 在 setup_arch() -> paging_init() -> devicemaps_init()被调用
    其他主要都在 setup_arch() 中用到。

系统初始化时,会调用smdk2440_machine_init

// linux-2.6.22.6/arch/arm/mach-s3c2440/mach-smdk2440.c
static void __init smdk2440_machine_init(void)
{s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));smdk_machine_init();
}// linux-2.6.22.6/arch/arm/plat-s3c24xx/common-smdk.c
void __init smdk_machine_init(void)
{......// 配置nand flashs3c_device_nand.dev.platform_data = &smdk_nand_info;......// 注册到平台总线platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));......
}// 配置了nand flash各信号脉冲宽度或时间间隔,以及分区
static struct s3c2410_platform_nand smdk_nand_info = {.tacls       = 20,.twrph0       = 60,.twrph1       = 20,.nr_sets  = ARRAY_SIZE(smdk_nand_sets),.sets     = smdk_nand_sets,
};
// 设置nand flash的分区
static struct s3c2410_nand_set smdk_nand_sets[] = {[0] = {.name       = "NAND",.nr_chips   = 1,.nr_partitions = ARRAY_SIZE(smdk_default_nand_part),.partitions   = smdk_default_nand_part,},
};
// 配置nand flash的具体分区,四个分区
static struct mtd_partition smdk_default_nand_part[] = {[0] = {.name   = "bootloader",.size   = 0x00040000,.offset    = 0,},[1] = {.name   = "params",.offset = MTDPART_OFS_APPEND,.size   = 0x00020000,},[2] = {.name   = "kernel",.offset = MTDPART_OFS_APPEND,.size   = 0x00200000,},[3] = {.name   = "root",.offset = MTDPART_OFS_APPEND,.size   = MTDPART_SIZ_FULL,}
};// linux-2.6.22.6/arch/arm/plat-s3c24xx/common-smdk.c
static struct platform_device __initdata *smdk_devs[] = {&s3c_device_nand,......
};// linux-2.6.22.6/arch/arm/plat-s3c24xx/devs.c
struct platform_device s3c_device_nand = {.name          = "s3c2410-nand",.id         = -1,.num_resources    = ARRAY_SIZE(s3c_nand_resource),.resource      = s3c_nand_resource,
};static struct resource s3c_nand_resource[] = {[0] = {.start = S3C2410_PA_NAND,.end   = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,.flags = IORESOURCE_MEM,}
};// 将s3c_device_nand设备注册到平台总线
// linux-2.6.22.6/drivers/base/platform.c
int platform_add_devices(struct platform_device **devs, int num)
{int i, ret = 0;for (i = 0; i < num; i++) {ret = platform_device_register(devs[i]);if (ret) {while (--i >= 0)platform_device_unregister(devs[i]);break;}}return ret;
}

4.2 S3c2440 Nand Flash – 驱动注册

make menuconfig,将s3c2440 nand flash驱动加载到内核中,系统启动时便会自动加载驱动。


-> Device Drivers                                                                                               │   │       -> Memory Technology Device (MTD) support (MTD [=y])                                                          │   │         -> NAND Device Support (MTD_NAND [=y])  │ │     <*>   NAND Flash support for S3C2410/S3C2440 SoC                                            │ │   

加载驱动,调用驱动初始化函数–s3c2410_nand_init
这里注册了三个nand flash驱动到平台总线。会根据注册到平台总线的nand flash设备进行匹配,选择使用哪个驱动。

static int __init s3c2410_nand_init(void)
{printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");platform_driver_register(&s3c2412_nand_driver);platform_driver_register(&s3c2440_nand_driver);return platform_driver_register(&s3c2410_nand_driver);
}static struct platform_driver s3c2410_nand_driver = {.probe       = s3c2410_nand_probe,.remove       = s3c2410_nand_remove,.suspend = s3c24xx_nand_suspend,.resume     = s3c24xx_nand_resume,.driver      = {.name   = "s3c2410-nand",.owner  = THIS_MODULE,},
};static struct platform_driver s3c2440_nand_driver = {.probe      = s3c2440_nand_probe,.remove       = s3c2410_nand_remove,.suspend = s3c24xx_nand_suspend,.resume     = s3c24xx_nand_resume,.driver      = {.name   = "s3c2440-nand",.owner  = THIS_MODULE,},
};static struct platform_driver s3c2412_nand_driver = {.probe      = s3c2412_nand_probe,.remove       = s3c2410_nand_remove,.suspend = s3c24xx_nand_suspend,.resume     = s3c24xx_nand_resume,.driver      = {.name   = "s3c2412-nand",.owner  = THIS_MODULE,},
};

4.3 S3c2440 Nand Flash – 设备-驱动匹配

4.3.1 设备注册到平台总线时的匹配

platform_add_devices->platform_device_register->platform_device_add->device_add->bus_attach_device(dev)->device_attach(dev)->// 因为是注册到平台总线,// 所以从平台总线的驱动链表中取出每一个驱动和该设备进行匹配bus_for_each_drv(dev->bus, NULL, dev, __device_attach)->__device_attach->driver_probe_device(drv, dev)->  // 因为是注册到平台总线,调用平台总线的匹配函数,// 即platform_matchif (drv->bus->match && !drv->bus->match(dev, drv))// 平台总线的匹配函数,通过比较驱动和设备的名称进行匹配
static int platform_match(struct device * dev, struct device_driver * drv)
{struct platform_device *pdev = container_of(dev, struct platform_device, dev);return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

4.3.2 驱动注册到平台总线时的匹配

platform_driver_register->driver_register->bus_add_driver->driver_attach->// 因为是注册到平台总线,// 所以从平台总线的设备链表中取出每一个设备和该驱动进行匹配bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)->__driver_attach->driver_probe_device->// 因为是注册到平台总线,调用平台总线的匹配函数,// 即platform_matchif (drv->bus->match && !drv->bus->match(dev, drv))// 平台总线的匹配函数,通过比较驱动和设备的名称进行匹配
static int platform_match(struct device * dev, struct device_driver * drv)
{struct platform_device *pdev = container_of(dev, struct platform_device, dev);return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}                 

4.4 S3c2440 Nand Flash – 调用驱动的probe函数

int driver_probe_device(struct device_driver * drv, struct device * dev)
{int ret = 0;if (!device_is_registered(dev))return -ENODEV;if (drv->bus->match && !drv->bus->match(dev, drv))goto done;pr_debug("%s: Matched Device %s with Driver %s\n",drv->bus->name, dev->bus_id, drv->name);ret = really_probe(dev, drv);done:return ret;
}// if (drv->bus->match && !drv->bus->match(dev, drv)) 匹配成功后调用really_probe函数
really_probe->// 因为注册的是平台总线,所以看平台总线是否具有probe函数// 如果平台总线具有probe函数则调用平台总线的probe函数,否则直接调用驱动的probe函数if (dev->bus->probe) {ret = dev->bus->probe(dev);if (ret)goto probe_failed;} else if (drv->probe) {ret = drv->probe(dev);if (ret)goto probe_failed;}
// 平台总线具有probe函数platform_drv_probe
static int platform_drv_probe(struct device *_dev)
{struct platform_driver *drv = to_platform_driver(_dev->driver);struct platform_device *dev = to_platform_device(_dev);// 调用驱动的probe函数return drv->probe(dev);
}// 设备
struct platform_device s3c_device_nand = {.name          = "s3c2410-nand",.id         = -1,.num_resources    = ARRAY_SIZE(s3c_nand_resource),.resource      = s3c_nand_resource,
};
// 驱动
static struct platform_driver s3c2410_nand_driver = {.probe        = s3c2410_nand_probe,.remove       = s3c2410_nand_remove,.suspend = s3c24xx_nand_suspend,.resume     = s3c24xx_nand_resume,.driver      = {.name   = "s3c2410-nand",.owner  = THIS_MODULE,},
};// 通过名称两者匹配,即调用s3c2410_nand_probe

五,驱动的probe函数分析

static int s3c2410_nand_probe(struct platform_device *dev)
{return s3c24xx_nand_probe(dev, TYPE_S3C2410);
}

5.1 使能时钟

info->clk = clk_get(&pdev->dev, "nand");
if (IS_ERR(info->clk)) {dev_err(&pdev->dev, "failed to get clock");err = -ENOENT;goto exit_error;
}
clk_enable(info->clk);

5.2 端口映射

#define S3C2410_PA_NAND     (0x4E000000)
#define S3C24XX_SZ_NAND    SZ_1M
static struct resource s3c_nand_resource[] = {[0] = {.start = S3C2410_PA_NAND,.end   = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,.flags = IORESOURCE_MEM,}
};
res  = pdev->resource;
size = res->end - res->start + 1;info->area = request_mem_region(res->start, size, pdev->name);if (info->area == NULL) {dev_err(&pdev->dev, "cannot reserve register region\n");err = -ENOENT;goto exit_error;
}info->regs   = ioremap(res->start, size);
if (info->regs == NULL) {dev_err(&pdev->dev, "cannot reserve register region\n");err = -EIO;goto exit_error;
}

5.3 s3c2440 nand flash控制器设置

5.3.1 设置脉冲宽度和间隔

脉冲宽度和信号间隔

static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,struct platform_device *pdev)
{struct s3c2410_platform_nand *plat = to_nand_plat(pdev);unsigned long clkrate = clk_get_rate(info->clk);int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;int tacls, twrph0, twrph1;unsigned long cfg = 0;/* calculate the timing information for the controller */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);} 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;}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:cfg = S3C2410_NFCONF_EN;cfg |= S3C2410_NFCONF_TACLS(tacls - 1);cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);break;case TYPE_S3C2440:case TYPE_S3C2412:cfg = S3C2440_NFCONF_TACLS(tacls - 1);cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);/* enable the controller and de-assert nFCE */// 使能 nand flash控制器writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);}dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);writel(cfg, info->regs + S3C2410_NFCONF);return 0;
}

5.4 设置struct nand_chip

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;// 提供nand flash读写数据接口chip->write_buf    = s3c2410_nand_write_buf;chip->read_buf     = s3c2410_nand_read_buf;// 提供nand flash 片选接口chip->select_chip  = s3c2410_nand_select_chip;chip->chip_delay   = 50;chip->priv      = nmtd;chip->options    = 0;chip->controller   = &info->controller;switch (info->cpu_type) {case TYPE_S3C2410:// 提供 nand flash控制器数据写入的寄存器地址chip->IO_ADDR_W = regs + S3C2410_NFDATA;info->sel_reg   = regs + S3C2410_NFCONF;info->sel_bit = S3C2410_NFCONF_nFCE;// 提供nand flash 写命令或者地址接口chip->cmd_ctrl  = s3c2410_nand_hwcontrol;// 提供nand flash 判断就绪或忙状态接口chip->dev_ready = s3c2410_nand_devready;break;......if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)dev_info(info->device, "System booted from NAND\n");break;}// 提供 nand flash控制器数据读取的寄存器地址chip->IO_ADDR_R = chip->IO_ADDR_W;nmtd->info     = info;nmtd->mtd.priv       = chip;nmtd->mtd.owner    = THIS_MODULE;// 设置分区信息nmtd->set      = set;// 使用硬件ecc校验还是软件ecc校验,以解决位反转问题。这里使用软件eccif (hardware_ecc) {......} else {chip->ecc.mode     = NAND_ECC_SOFT;}
}

5.5 nand_scan流程分析

nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1) ->nand_scan_ident(mtd, maxchips) ->// 设置默认的nand flash接口,// 比如设置struct nand_chip时提供的nand flash读写数据接口等// 像5.4小节事先提供了则使用提供的,否则使用默认的nand_set_defaults(chip, busw);// 从nand flash中读出其设备id ,和nand_flash_ids数组中定义的各型号flash信息对比,// 得到其flash型号。比如读到K9F2G08U0C nand flash的设备Id为da,// 则从nand_flash_ids数组中可得其信息为// {"NAND 256MiB 3,3V 8-bit",  0xDA, 0, 256, 0, LP_OPTIONS},nand_get_flash_type(mtd, chip, busw, &nand_maf_id);// 填充所有未初始化的函数指针(比如nand_erase等接口),并在适当的情况下扫描坏的块表。nand_scan_tail(mtd);

5.6 添加分区

5.6.1 遍历mtd_notifiers,通过其add接口添加分区

s3c2410_nand_add_partition ->// 在s3c2410_nand_init_chip函数中已经将分区信息设置给了mtd add_mtd_device(&mtd->mtd) ->// 遍历mtd_notifiers,通过其add接口添加分区list_for_each(this, &mtd_notifiers) {struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);not->add(mtd);}

5.6.2 设置mtd_notifiers链表

5.6.2.1 nand flash 字符设备

// linux-2.6.22.6/drivers/mtd/mtdchar.c
init_mtdchar ->  register_mtd_user(&notifier) ->list_add(&new->list, &mtd_notifiers);static struct mtd_notifier notifier = {.add  = mtd_notify_add,.remove   = mtd_notify_remove,
};static void mtd_notify_add(struct mtd_info* mtd)
{if (!mtd)return;// 创建字符设备 设备节点为/dev/mtd%dclass_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),NULL, "mtd%d", mtd->index);// 创建字符设备 设备节点为/dev/mtd%dro (只读)class_device_create(mtd_class, NULL,MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),NULL, "mtd%dro", mtd->index);
}

5.6.2.2 nand flash的块设备

// linux-2.6.22.6/drivers/mtd/mtd_blkdevs.c
register_mtd_blktrans->register_mtd_user(&blktrans_notifier) ->list_add(&new->list, &mtd_notifiers);static struct mtd_notifier blktrans_notifier = {.add = blktrans_notify_add,.remove = blktrans_notify_remove,
};static void blktrans_notify_add(struct mtd_info *mtd)
{struct list_head *this;if (mtd->type == MTD_ABSENT)return;list_for_each(this, &blktrans_majors) {struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);tr->add_mtd(tr, mtd);}}blktrans_notify_add ->// 遍历blktrans_majors 通过其add_mtd函数添加分区list_for_each(this, &blktrans_majors) {struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);tr->add_mtd(tr, mtd);}// 设置blktrans_majors链表 两个地方设置,一个mtdblock_ro.c 一个mtdblock.c// linux-2.6.22.6/drivers/mtd/mtdblock_ro.c
mtdblock_init->register_mtd_blktrans(&mtdblock_tr) -> list_add(&tr->list, &blktrans_majors);static struct mtd_blktrans_ops mtdblock_tr = {.name       = "mtdblock",.major      = 31,.part_bits    = 0,.blksize   = 512,.readsect    = mtdblock_readsect,.writesect = mtdblock_writesect,.add_mtd  = mtdblock_add_mtd,.remove_dev = mtdblock_remove_dev,.owner       = THIS_MODULE,
};mtdblock_add_mtd ->add_mtd_blktrans_dev(dev);alloc_disk(1 << tr->part_bits);set_capacity(gd, (new->size * tr->blksize) >> 9);add_disk(gd);// linux-2.6.22.6/drivers/mtd/mtdblock.c
init_mtdblock -> register_mtd_blktrans(&mtdblock_tr);list_add(&tr->list, &blktrans_majors);static struct mtd_blktrans_ops mtdblock_tr = {.name       = "mtdblock",.major      = 31,.part_bits    = 0,.blksize   = 512,.open        = mtdblock_open,.flush     = mtdblock_flush,.release  = mtdblock_release,.readsect   = mtdblock_readsect,.writesect = mtdblock_writesect,.add_mtd  = mtdblock_add_mtd,.remove_dev = mtdblock_remove_dev,.owner       = THIS_MODULE,
};mtdblock_add_mtd -> add_mtd_blktrans_dev(dev)->alloc_disk(1 << tr->part_bits);set_capacity(gd, (new->size * tr->blksize) >> 9);add_disk(gd);

5.6.3 块设备初始化请求队列

// linux-2.6.22.6/drivers/mtd/mtdblock.c
init_mtdblock -> register_mtd_blktrans(&mtdblock_tr);tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);// linux-2.6.22.6/drivers/mtd/mtdblock_ro.c
mtdblock_init->register_mtd_blktrans(&mtdblock_tr) -> tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);

六,总结

6.1 nand flash字符设备创建过程

系统启动,内核初始化时加载mtdchar模块,调用init_mtdchar,将 struct mtd_notifier 结构注册到mtd_notifiers链表中,供后续nand flash驱动程序使用。

// linux-2.6.22.6/drivers/mtd/mtdchar.c
init_mtdchar ->  register_mtd_user(&notifier) ->list_add(&new->list, &mtd_notifiers);static struct mtd_notifier notifier = {.add  = mtd_notify_add,.remove   = mtd_notify_remove,
};static void mtd_notify_add(struct mtd_info* mtd)
{if (!mtd)return;// 创建字符设备 设备节点为/dev/mtd%dclass_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),NULL, "mtd%d", mtd->index);// 创建字符设备 设备节点为/dev/mtd%dro (只读)class_device_create(mtd_class, NULL,MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),NULL, "mtd%dro", mtd->index);
}

nand flash驱动程序,调用s3c2410_nand_add_partition接口添加分区时,调用struct mtd_notifier结构中的add函数添加分区,创建字符设备节点。

s3c2410_nand_add_partition ->// 在s3c2410_nand_init_chip函数中已经将分区信息设置给了mtd add_mtd_device(&mtd->mtd) ->// 遍历mtd_notifiers,通过其add接口添加分区list_for_each(this, &mtd_notifiers) {struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);not->add(mtd);}

6.2 nand flash块设备创建过程

系统启动,内核初始化时加载mtdblock模块,调用init_mtdblock,将struct mtd_notifier 结构注册到mtd_notifiers链表中,注册块设备,初始化请求队列,注册struct mtd_blktrans_ops结构到blktrans_majors链表,供后续nand flash驱动程序使用。(mtdblock_ro.c 和 mtdblock.c类似操作)

init_mtdblock -> register_mtd_blktrans(&mtdblock_tr) ->// 将struct mtd_notifier 结构注册到mtd_notifiers链表中if (!blktrans_notifier.list.next)register_mtd_user(&blktrans_notifier);// 注册块设备 /dev/mtdblockret = register_blkdev(tr->major, tr->name);// 初始化请求队列tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);// 注册struct mtd_blktrans_ops结构到blktrans_majors链表list_add(&tr->list, &blktrans_majors);static struct mtd_notifier blktrans_notifier = {.add = blktrans_notify_add,.remove = blktrans_notify_remove,
};static void blktrans_notify_add(struct mtd_info *mtd)
{struct list_head *this;if (mtd->type == MTD_ABSENT)return;list_for_each(this, &blktrans_majors) {struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);tr->add_mtd(tr, mtd);}}static struct mtd_blktrans_ops mtdblock_tr = {.name        = "mtdblock",.major      = 31,.part_bits    = 0,.blksize   = 512,.open        = mtdblock_open,.flush     = mtdblock_flush,.release  = mtdblock_release,.readsect   = mtdblock_readsect,.writesect = mtdblock_writesect,.add_mtd  = mtdblock_add_mtd,.remove_dev = mtdblock_remove_dev,.owner       = THIS_MODULE,
};

nand flash驱动程序,调用s3c2410_nand_add_partition接口添加分区时,调用struct mtd_notifier结构中的add函数添加分区,调用struct mtd_notifier结构中的add函数(blktrans_notify_add),即调用struct mtd_blktrans_ops结构中的add_mtd函数(mtdblock_add_mtd)。

s3c2410_nand_add_partition ->// 在s3c2410_nand_init_chip函数中已经将分区信息设置给了mtd add_mtd_device(&mtd->mtd) ->// 遍历mtd_notifiers,通过其add接口添加分区list_for_each(this, &mtd_notifiers) {struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);not->add(mtd);}blktrans_notify_add -> mtdblock_add_mtd -> add_mtd_blktrans_dev(dev)->alloc_disk(1 << tr->part_bits);set_capacity(gd, (new->size * tr->blksize) >> 9);add_disk(gd);

6.3 一次应用层读取Nand Flash数据的过程

// 对块设备节点/dev/mtdblock进行访问操作//一个读数据的bio,被合并或被生成一个请求,触发请求队列的请求处理函数
mtd_blktrans_request ->// 唤醒一个休眠线程--mtd_blktrans_thread,该线程在register_mtd_blktrans中启动wake_up_process(tr->blkcore_priv->thread);// 该线程从请求队列中取出一个请求调用do_blktrans_request接口进行处理
mtd_blktrans_threaddo_blktrans_request -> //tr->readsect 即 struct mtd_blktrans_ops 中的 mtdblock_readsect接口tr->readsect(dev, block, buf) ->do_cached_read(mtdblk, block<<9, 512, buf) -> //mtd->read 即 struct mtd_info中的 nand_read接口//在 nand_scan_tail接口中被设置 mtd->read = nand_read;mtd->read(mtd, pos, size, &retlen, buf) ->nand_do_read_ops(mtd, from, &chip->ops) ->chip->ecc.read_page_raw(mtd, chip, bufpoi) ->nand_read_page_raw -> // chip->read_buf 即 s3c2410_nand_read_buf// 在驱动程序中 s3c2410_nand_init_chip函数里设置// chip->read_buf = s3c2410_nand_read_buf;chip->read_buf(mtd, buf, mtd->writesize); chip->read_buf(mtd, chip->oob_poi, mtd->oobsize) ->// 从nand flash控制器的NFDATA寄存器中读取数据// chip->IO_ADDR_W = regs + S3C2410_NFDATA;// chip->IO_ADDR_R = chip->IO_ADDR_W;readsb(this->IO_ADDR_R, buf, len);

《Linux驱动:nand flash驱动看这一篇就够了》相关推荐

  1. ComeFuture英伽学院——2020年 全国大学生英语竞赛【C类初赛真题解析】(持续更新)

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  2. ComeFuture英伽学院——2019年 全国大学生英语竞赛【C类初赛真题解析】大小作文——详细解析

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  3. 信息学奥赛真题解析(玩具谜题)

    玩具谜题(2016年信息学奥赛提高组真题) 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业.有一天, 这些玩具小人把小南的眼镜藏了起来.小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的 ...

  4. 信息学奥赛之初赛 第1轮 讲解(01-08课)

    信息学奥赛之初赛讲解 01 计算机概述 系统基本结构 信息学奥赛之初赛讲解 01 计算机概述 系统基本结构_哔哩哔哩_bilibili 信息学奥赛之初赛讲解 02 软件系统 计算机语言 进制转换 信息 ...

  5. 信息学奥赛一本通习题答案(五)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  6. 信息学奥赛一本通习题答案(三)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  7. 信息学奥赛一本通 提高篇 第六部分 数学基础 相关的真题

    第1章   快速幂 1875:[13NOIP提高组]转圈游戏 信息学奥赛一本通(C++版)在线评测系统 第2 章  素数 第 3 章  约数 第 4 章  同余问题 第 5 章  矩阵乘法 第 6 章 ...

  8. 信息学奥赛一本通题目代码(非题库)

    为了完善自己学c++,很多人都去读相关文献,就比如<信息学奥赛一本通>,可又对题目无从下手,从今天开始,我将把书上的题目一 一的解析下来,可以做参考,如果有错,可以告诉我,将在下次解析里重 ...

  9. 信息学奥赛一本通(C++版) 刷题 记录

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 刷题 记录 http://ybt.ssoier. ...

  10. 最近公共祖先三种算法详解 + 模板题 建议新手收藏 例题: 信息学奥赛一本通 祖孙询问 距离

    首先什么是最近公共祖先?? 如图:红色节点的祖先为红色的1, 2, 3. 绿色节点的祖先为绿色的1, 2, 3, 4. 他们的最近公共祖先即他们最先相交的地方,如在上图中黄色的点就是他们的最近公共祖先 ...

最新文章

  1. css实现左边div固定宽度,右边div自适应撑满剩下的宽度
  2. python3.6与3.7有什么区别_Python3.6.6和Python3.7.0的坑
  3. redis value多大会影响性能_选择合适Redis数据结构,减少80%的内存占用
  4. 【活动】侬好上海,Microsoft Reactor来啦
  5. 容器编排技术 -- Kubernetes 为 Namespace 设置最小和最大内存限制
  6. javascript Date
  7. 即时通信(IM)和实时通信(RTC)的区别
  8. 可伸缩多线程任务队列
  9. SE 2014 年4月21日(二)
  10. inner/left/right inner
  11. VS2010/VS2012/VS2015下openGL环境配置(转)
  12. linux查看内网命令,nmap命令查看内网信息的几个...-centos6.3中lspci查看硬件信息提...-学习linux cut 命令的用法_169IT.COM...
  13. JAXB XML和Bean互相转换
  14. 实验 VoIP通信的配置
  15. win10解压安装mysql缺少MSVCR120.dll文件的问题
  16. Windows环境下用C语言实现CS模型(基于TCP协议)
  17. LNB电源市场现状及未来发展趋势分析
  18. 一个有趣的时间段重叠问题
  19. 如何利用计算机班级成绩分析,计算机二级excel真题:制作期末成绩分析表
  20. 《白帽子讲Web安全 》 随手记(二)

热门文章

  1. 迅为IMX6ULL开发板更新资料介绍
  2. 阿里蚂蚁金服五面,java银行驻场开发
  3. Win10(Win7)设置固定IP地址
  4. 查单词神器:欧路词典
  5. Android--智能下拉刷新框架(SmartRefreshLayout)
  6. 大学都需要学习哪些软件
  7. Dinornis – Rendering your Model in Mudbox by RenderMan Directly !
  8. 论文阅读 8 | Contrastive Decoder Generator for Few-shot Learning in Product Quality Prediction
  9. php中with方法可以分页吗,分页显示详解(with php)
  10. 《高级软件工程》课程总结