nand flash具有大容量、改写速度快、接口简单等优点,适用于大量数据的存储,为固态大容量存储提供了廉价有效的解决方案。Linux内核已经支持s3c2416的nand控制器,可以支持各种容量的nand flash。
1. nand设备
nand设备包含了名字、独有的资源等等一些驱动程序的硬件或自定义信息。通过platform_add_devices(platform_device_register)函数将定义的平台设备注册到内核中,用于匹配设备驱动。
内核在drivers\mtd\nand\ s3c2410.c目录中实现了s3c2416 nand驱动, nand设备平台代码如下。
static struct mtd_partitionhome_default_nand_part[] = {
[0]= {
.name = "Bootloader",
.offset = 0,
.size = 0x100000,
.mask_flags = MTD_CAP_NANDFLASH,
},
[1]= {
.name = "Logo",
.offset = 0x100000,
.size = 0x200000,
.mask_flags = MTD_CAP_NANDFLASH,
},
[2]= {
.name = "Kernel",
.offset = 0x300000,
.size = 0x400000,
.mask_flags = MTD_CAP_NANDFLASH,
},
[3]= {
.name = "Rootfs",
.offset = 0x700000,
.size = MTDPART_SIZ_FULL,
},
};
static struct s3c2410_nand_sethome_nand_sets[] = {
[0]= {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(home_default_nand_part),
.partitions = home_default_nand_part,
},
};
static struct s3c2410_platform_nandhome_nand_info = {
.tacls = 10,
.twrph0 = 20,
.twrph1 = 10,
.ecc_mode = NAND_ECC_HW,
.nr_sets = ARRAY_SIZE(home_nand_sets),
.sets = home_nand_sets,
};
staticstruct resource s3c_nand_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_NAND, SZ_1M),
};
structplatform_device s3c_device_nand = {
.name ="s3c2410-nand",
.id =-1,
.num_resources = ARRAY_SIZE(s3c_nand_resource),
.resource =s3c_nand_resource,
};
nand ecc校验方式在home_nand_info结构体中设置,设置为NAND_ECC_HW。在板级初始化函数home2416_machine_init()中加入nand平台数据s3c_nand_set_platdata(&home_nand_info),在static struct platform_device *home2416_devices[]板级平台设备列表中加入&s3c_device_nand,使nand设备能够注册到内核中。
2. 内核配置
Linux配置支持nand设备驱动,选中Device Drivers->Memory Technology Device(MTD) support->NAND Device Support->NAND Flash support for Samsung S3CSoCs。
nand ecc校验方式在nand platform data中设置,由于nand flash会出现位翻转的问题,一般需要对nand flash进行ecc校验和纠错。Linux内核支持无ecc、软件ecc、硬件ecc这三种方式,内核无ecc情况下,应该保证数据的其他地方有ecc校验(如yaffs允许使用自身的ecc校验),不然数据可能是不可靠的。一般情况下是在内核做ecc校验,对于支持硬件方式产生ecc的nand控制器,驱动层应优先让内核采用硬件ecc,软件ecc会耗费更多的cpu资源,也远达不到硬件ecc的速度性能。
3. nand测试
cat /proc/mtd可以知道分区信息。我们在nand平台设备中设置了四个分区,第一个分区用来存放bootloader,第二个分区用来存放Logo,第三个分区用来存放Linux内核,剩余空间作为第四个分区根文件系统区域。
cat /proc/partitions可以知道有四个mtd设备,分别对应nand flash的四个分区。在/dev目录中创建这四个mtd设备文件。
mknod /dev/mtdblock0 c 31 0
mknod /dev/mtdblock1 c 31 1
mknod /dev/mtdblock2 c 31 2
mknod /dev/mtdblock3 c 31 3
bootloader是上电启动后最先运行的一段代码,它的固化以及启动是与芯片平台息息相关的,需要参考相应芯片平台手册。例如对于s3c2416 nand启动,要求bootloader必须放在nand flash的0地址处,其中的8k SteppingStone必须采用8位ecc校验。
对于Linux内核,根文件系统,数据(如Logo)是由bootloader来固化的,其中Linux内核是由bootloader加载的,Linux内核固化的方式,如nand flash固化地址、固化采用的ecc校验等并无强制要求,只需bootloader采用与固化时相同的方式去加载即可。
根文件系统是由Linux内核启动去挂载的。因此,Linux内核在挂载根文件系统时,就采用内核的ecc校验方式去读取nand flash。这就要求bootloader在固化根文件系统时,固化的方式(如ecc layout、ecc校验纠错方式)必须与内核nand驱动层采用的方式完全一致,不然内核将无法读取根文件系统中的数据并挂载。
我们在Linux内核配置中选择了硬件ecc,驱动层每256 byte产生3 byte的硬件ecc,ecc layout方式可以查看Linux内核源码。笔者在bootloader中实现了跟Linux内核一致的方式,可以固化并启动Linux系统,具体实现可以参考笔者的bootloader源码。
读取Bootloader分区的512字节数据,bootloader有相应的ecc校验方式,与Linux的ecc校验方式不一致,Linux内核无法读取数据。
读取Kernel分区512字节的数据,bootloader固化Linux内核时,采用了与Linux内核nand驱动层一致的方式,可以正确地读取数据。