Nand作为市面上最主要的非易失性闪存技术之一,应用在各种固态大容量存储解决方案中。由于Nand flash自身的特点,Nand存储器往往需要一款专用的Nand文件系统进行管理。开源的Yaffs文件系统由于其优异的性能,在Nand flash中受到广泛的应用,笔者此处就Yaffs的移植作一个简单的介绍。

1、Yaffs概述

Yaffs是由Aleph One公司所发展出来的Nand flash文件系统,专门为Nand flash存储器设计,适用于大容量的存储设备。在GPL协议下发布,可在其官网上免费获得源码。

Yaffs是基于日志的文件系统,提供了坏块管理、磨损平衡和掉电恢复的健壮性,保证数据在系统对文件系统修改的过程中发生意外也不被破坏。特别针对Nand flash,在启动时间、内存空间占用、读写速度等方面做了优化,已经在Linux、Android、WinCE等商业产品中使用。

2、Yaffs移植

Yaffs文件系统分为文件系统管理层接口、Yaffs内部实现层和Nand接口层,这简化了与系统的接口设计,便于集成到系统中去。移植即为实现Nand接口层。由于Yaffs一直在维护更新,其内部数据结构、函数实现流程等有细微的更新。因此对于时间跨度比较大的版本,再者之间的移植将会有较大的差异。对于可移植的开源项目,一般应在源码包相应的makefile、readme等文档中获知项目的目录架构,提取相应的源码。接口的移植也应参考源码包中的Demo接口移植,了解相应接口应实现的功能需求,便于针对特定设备重新实现类似的接口功能。应用编程也可以参考源码中的应用测试代码。 笔者此处以2015/06版本的源码为例说明Yaffs的移植。

2.1、编译器相关

对于可移植开源项目,不会使用编译器的数据类型、扩展语法等,因为不同体系的cpu、不同编译器这部分是不同的,是不可移植的,开源项目有自己定义的数据类型,这是需要根据具体的cpu、具体的编译器重定义的。Yaffs提供posix文件操作接口,使用了posix文件操作数据类型,而posix为unix下可移植操作系统应用编程接口,并不是c标准,c编译器不必实现posix,因此需自定义Yaffs中使用到的posix数据类型。Yaffs应用编程跟posix文件操作应用编程是完全一致的。即基于posix的应用程序在基于unix类、windows、支持posix的rtos等都是源码级可移植的。

  1. #ifndef __YAFFS_CONFIG_H__
  2. #define __YAFFS_CONFIG_H__
  3. #define CONFIG_YAFFS_DIRECT
  4. #define CONFIG_YAFFS_YAFFS2
  5. #define CONFIG_YAFFS_PROVIDE_DEFS
  6. #define CONFIG_YAFFSFS_PROVIDE_VALUES
  7. #define CONFIG_YAFFS_DEFINES_TYPES
  8. #define inline __inline
  9. typedef unsigned short dev_t;
  10. typedef unsigned short mode_t;
  11. typedef long off_t;
  12. typedef long long loff_t;
  13. #endif

2.2、操作系统相关

Yaffs需要访问操作系统资源,如提供锁、时间戳、系统错误等。对于单线程访问、无操作系统并不需要操作系统的锁等相关功能。在Yaffs中yaffs_osglue.h列出了所需实现的操作系统相关接口函数。

  1. #include"stdio.h"
  2. #include"stdlib.h"
  3. #include"time.h"
  4. static intyaffs_errno;
  5. /*
  6. * yaffs_bug_fn()
  7. * Function to report a bug.
  8. */
  9. void yaffs_bug_fn(constchar *fn, int n)
  10. {
  11. printf("yaffs bug at %s:%d\n", fn,n);
  12. }
  13. /*
  14. * yaffsfs_CurrentTime() retrns a 32-bittimestamp.
  15. *
  16. * Can return 0 if your system does not careabout time.
  17. */
  18. unsigned intyaffsfs_CurrentTime(void)
  19. {
  20. return time(NULL);
  21. }
  22. /*
  23. * yaffsfs_SetError() andyaffsfs_GetLastError()
  24. * Do whatever to set the system error.
  25. * yaffsfs_GetLastError() just fetches the lasterror.
  26. */
  27. voidyaffsfs_SetError(int err)
  28. {
  29. yaffs_errno = err;
  30. }
  31. intyaffsfs_GetLastError(void)
  32. {
  33. return yaffs_errno;
  34. }
  35. /*
  36. * yaffsfs_CheckMemRegion()
  37. * Check that access to an address is valid.
  38. * This can check memory is in bounds and iswritable etc.
  39. *
  40. * Returns 0 if ok, negative if not.
  41. */
  42. intyaffsfs_CheckMemRegion(const void *addr, size_t size, int write_request)
  43. {
  44. if(!addr) {
  45. return -1;
  46. }
  47. return 0;
  48. }
  49. /*
  50. * yaffsfs_malloc()
  51. * yaffsfs_free()
  52. *
  53. * Functions to allocate and free memory.
  54. */
  55. void*yaffsfs_malloc(size_t size)
  56. {
  57. return malloc(size);
  58. }
  59. voidyaffsfs_free(void *ptr)
  60. {
  61. free(ptr);
  62. }
  63. /*
  64. * yaffsfs_Lock()
  65. * yaffsfs_Unlock()
  66. * A single mechanism to lock and unlock yaffs.Hook up to a mutex or whatever.
  67. */
  68. voidyaffsfs_Lock(void)
  69. {
  70. }
  71. voidyaffsfs_Unlock(void)
  72. {
  73. }
  74. voidyaffsfs_OSInitialisation(void)
  75. {
  76. /* No locking used */
  77. }
  78. #if defined(__CC_ARM) /* ARMCC compiler */
  79. // MDK不支持strnlen函数,重新实现
  80. int strnlen(const char *Str, int MaxLen)
  81. {
  82. int i;
  83. for (i=0;i<MaxLen; i++) {
  84. if(Str[i] == 0) {
  85. break;
  86. }
  87. }
  88. return i;
  89. }
  90. #endif

2.3、 Nand接口相关

Nand驱动在前面章节有详细的描述,一般针对Nand flash的特性,Nand底层驱动应实现Nand初始化、Nand页读、Nand页编程、Nand块擦除、Nand坏块标记、Nand坏块检查。Yaffs通过函数指针的方式实现访问以上的Nand底层驱动接口,需实现的Nand接口函数指针如下:

  1. int(*drv_write_chunk_fn) (struct yaffs_dev *dev, int nand_chunk,
  2. const u8 *data, int data_len,
  3. const u8 *oob, int oob_len);
  4. int(*drv_read_chunk_fn) (struct yaffs_dev *dev, int nand_chunk,
  5. u8 *data, int data_len,
  6. u8 *oob, int oob_len,
  7. enum yaffs_ecc_result *ecc_result);
  8. int(*drv_erase_fn) (struct yaffs_dev *dev, int block_no);
  9. int(*drv_mark_bad_fn) (struct yaffs_dev *dev, int block_no);
  10. int(*drv_check_bad_fn) (struct yaffs_dev *dev, int block_no);
  11. int(*drv_initialise_fn) (struct yaffs_dev *dev);
  12. int(*drv_deinitialise_fn) (struct yaffs_dev *dev);
2.3.1、drv_initialise_fn函数指针

drv_initialise_fn主要实现Nand的初始化,在文件系统挂载时,会最先调用该函数指针对Nand进行初始化。

  1. static int yaffs_nand_drv_Initialise(struct yaffs_dev*dev)
  2. {
  3. Nand_Init();
  4. returnYAFFS_OK;
  5. }
2.3.2、drv_erase_fn函数指针

drv_erase_fn主要对某一个块进行擦除。

  1. static int yaffs_nand_drv_EraseBlock(struct yaffs_dev*dev, int block_no)
  2. {
  3. if (Nand_EraseBlock(block_no)!= 0) {
  4. returnYAFFS_FAIL;
  5. }
  6. returnYAFFS_OK;
  7. }
2.3.3、drv_mark_bad_fn函数指针

drv_mark_bad_fn需实现对某一块进行坏块标记。

  1. static int yaffs_nand_drv_MarkBad(struct yaffs_dev*dev, int block_no)
  2. {
  3. if(Nand_MarkBadBlock(block_no) != 0) {
  4. returnYAFFS_FAIL;
  5. }
  6. returnYAFFS_OK;
  7. }
2.3.4、drv_check_bad_fn函数指针

drv_check_bad_fn需实现对某一块进行检查,是否坏块。

  1. static int yaffs_nand_drv_CheckBad(struct yaffs_dev*dev, int block_no)
  2. {
  3. if(Nand_IsBadBlock(block_no) != 0) {
  4. // badblock
  5. returnYAFFS_FAIL;
  6. }
  7. returnYAFFS_OK;
  8. }
2.3.5、drv_write_chunk_fn函数指针

drv_write_chunk_fn需实现对某chunk(page)在Nand data area写入特定长度的数据,通常为1 chunk(page),在Nand spare area写入特定长度的oob数据(tags)。

  1. static int yaffs_nand_drv_WriteChunk(struct yaffs_dev*dev, int nand_chunk,
  2. const u8 *data,int data_len, const u8 *oob, int oob_len)
  3. {
  4. if (!data ||!oob) {
  5. returnYAFFS_FAIL;
  6. }
  7. if(Nand_WriteWithOob(nand_chunk, data, data_len, oob, oob_len) != 0) {
  8. returnYAFFS_FAIL;
  9. }
  10. returnYAFFS_OK;
  11. }
2.3.6、drv_read_chunk_fn函数指针

drv_read_chunk_fn需实现对某chunk(page)在Nand data area读取特定长度的数据,通常为1 chunk(page),在Nand spare area读取特定长度的oob数据(tags)。此处采用Nand驱动硬件ecc,而未使用Yaffs自带的软件ecc,需处理数据是否无错或可纠错。

  1. static int yaffs_nand_drv_ReadChunk(struct yaffs_dev*dev, int nand_chunk,
  2. u8*data, int data_len, u8 *oob, int oob_len,
  3. enumyaffs_ecc_result *ecc_result_out)
  4. {
  5. int ret;
  6. if (data ==NULL) {
  7. data_len= 0;
  8. }
  9. ret =Nand_ReadWithOob(nand_chunk, data, data_len, oob, oob_len);
  10. if (ret != 0){
  11. if(ecc_result_out) {
  12. *ecc_result_out= YAFFS_ECC_RESULT_UNKNOWN;
  13. }
  14. returnYAFFS_FAIL;
  15. } else {
  16. if(ecc_result_out) {
  17. *ecc_result_out= YAFFS_ECC_RESULT_NO_ERROR;
  18. }
  19. }
  20. returnYAFFS_OK;
  21. }
2.3.7、drv_deinitialise_fn函数指针

drv_deinitialise_fn为取消选中Nand flash,do nothing。

  1. static int yaffs_nand_drv_Deinitialise(structyaffs_dev *dev)
  2. {
  3. returnYAFFS_OK;
  4. }
2.3.8、yaffs_start_up函数

Yaffs在挂载使用前,必须先安装Nand驱动,通过yaffs_start_up函数把相应的Nand底层访问接口加载进Yaffs的接口层。

  1. struct yaffs_dev *yaffs_nand_install_drv(const char*dev_name)
  2. {
  3. structyaffs_driver *drv;
  4. structyaffs_dev *dev;
  5. structyaffs_param *param;
  6. dev =malloc(sizeof(struct yaffs_dev));
  7. if (!dev) {
  8. returnNULL;
  9. }
  10. memset(dev,0, sizeof(*dev));
  11. param =&dev->param;
  12. param->name= dev_name;
  13. if(!param->name){
  14. free(dev);
  15. returnNULL;
  16. }
  17. param->total_bytes_per_chunk= 2048;
  18. param->chunks_per_block= 64;
  19. param->n_reserved_blocks= 5;
  20. param->start_block= 32; // First block, reserve 4M for boot
  21. param->end_block= 2048 - 1;
  22. param->is_yaffs2= 1;
  23. param->use_nand_ecc= 1; // use driver's ecc
  24. param->n_caches= 10;
  25. drv =&dev->drv;
  26. drv->drv_write_chunk_fn= yaffs_nand_drv_WriteChunk;
  27. drv->drv_read_chunk_fn= yaffs_nand_drv_ReadChunk;
  28. drv->drv_erase_fn= yaffs_nand_drv_EraseBlock;
  29. drv->drv_mark_bad_fn= yaffs_nand_drv_MarkBad;
  30. drv->drv_check_bad_fn= yaffs_nand_drv_CheckBad;
  31. drv->drv_initialise_fn= yaffs_nand_drv_Initialise;
  32. drv->drv_deinitialise_fn= yaffs_nand_drv_Deinitialise;
  33. /* The yaffsdevice has been configured, install it into yaffs */
  34. yaffs_add_device(dev);
  35. return dev;
  36. }
  37. int yaffs_start_up(void)
  38. {
  39. static u8start_up_called = 0;
  40. if(start_up_called){
  41. return 0;
  42. }
  43. start_up_called= 1;
  44. // Stuff toinitialise anything special (eg lock semaphore).
  45. yaffsfs_OSInitialisation();
  46. yaffs_nand_install_drv("/");
  47. return 0;
  48. }

3、应用测试

Yaffs提供了posix文件操作应用接口,因此Yaffs应用编程实际与posix文件操作应用编程完全一致。此处测试在”/”目录下可通过选择创建test.txt测试文件,每次可对该文件累计写入测试字符串,通过相应选项读出该文件的内容,可选择列举所有”/”目录下的所有文件,在第一次使用Yaffs时,必须先对Nand flash进行格式化,不然不同的Nand驱动标记的脏数据会造成Nand信息的出错。

  1. #include "ProjectConfig.h"
  2. #include "Uart.h"
  3. #include "Speed.h"
  4. #include "NAND.h"
  5. #include "yaffsfs.h"
  6. #include "yaffs_guts.h"
  7. static const char test[] = "This is yaffs testfile\r\n";
  8. static const char *yaffs_file_type_str(structyaffs_stat *stat)
  9. {
  10. switch(stat->st_mode & S_IFMT) {
  11. case S_IFREG:return "regular file";
  12. case S_IFDIR:return "directory";
  13. case S_IFLNK:return "symlink";
  14. default:return "unknown";
  15. }
  16. }
  17. int yaffs_ls(const char *mountpt, int longlist)
  18. {
  19. int i;
  20. yaffs_DIR *d;
  21. struct yaffs_dirent *de;
  22. struct yaffs_stat stat;
  23. char tempstr[255];
  24. d = yaffs_opendir(mountpt);
  25. if (!d) {
  26. printf("opendirfailed, %s\n", yaffs_error_to_str(yaffsfs_GetLastError()));
  27. return -1;
  28. }
  29. for (i = 0; (de = yaffs_readdir(d)) != NULL; i++) {
  30. if (longlist){
  31. sprintf(tempstr,"%s/%s", mountpt, de->d_name);
  32. yaffs_lstat(tempstr,&stat);
  33. printf("%-25s\t%7ld",
  34. de->d_name,
  35. (long)stat.st_size);
  36. printf("%5d %s\n",
  37. stat.st_ino,
  38. yaffs_file_type_str(&stat));
  39. } else {
  40. printf("%s\n",de->d_name);
  41. }
  42. }
  43. yaffs_closedir(d);
  44. return 0;
  45. }
  46. int main(void)
  47. {
  48. char Text[1024];
  49. int handle;
  50. int BytesRead;
  51. uint8_t Command;
  52. Uart_Init();
  53. printf("CPU: S3C2416@%dMHz\n", get_ARMCLK()/1000000);
  54. printf(" Fclk = %dMHz, Hclk = %dMHz, Pclk = %dMHz\n",
  55. get_FCLK()/1000000,get_HCLK()/1000000, get_PCLK()/1000000);
  56. yaffs_start_up();
  57. yaffs_mount("/");
  58. while (1) {
  59. printf("1: Write test.txt\n");
  60. printf("2: View test.txt\n");
  61. printf("3: List files in nand\n");
  62. printf("4: Format nand flash\n");
  63. Command = Uart_WaitChar();
  64. switch (Command) {
  65. case '1':
  66. handle = yaffs_open("/test.txt",O_CREAT|O_WRONLY|O_APPEND, S_IREAD|S_IWRITE);
  67. if (handle ==-1) {
  68. printf("Createtest.txt failed\n");
  69. break;
  70. }
  71. yaffs_write(handle,test, strlen(test));
  72. yaffs_close(handle);
  73. break;
  74. case '2':
  75. handle =yaffs_open("/test.txt", O_RDONLY, S_IREAD|S_IWRITE);
  76. if (handle ==-1) {
  77. printf("Opentest.txt failed\n");
  78. break;
  79. }
  80. while (1) {
  81. BytesRead= yaffs_read(handle, Text, sizeof(Text)-1);
  82. if(BytesRead == -1) {
  83. printf("Readtest.txt error\n");
  84. yaffs_close(handle);
  85. break;
  86. }
  87. Text[BytesRead]= 0;
  88. printf("%s",Text);
  89. if(BytesRead < (sizeof(Text)-1)) {
  90. yaffs_close(handle);
  91. break;
  92. }
  93. }
  94. break;
  95. case '3':
  96. yaffs_ls("/",1);
  97. break;
  98. case '4':
  99. yaffs_unmount("/");
  100. // format nandflash to yaffs2
  101. nand_format();
  102. yaffs_mount("/");
  103. printf("Formattingis complete\n");
  104. break;
  105. default:
  106. break;
  107. }
  108. printf("\n");
  109. }
  110. }

4、附录

S3C2416_MDK_Yaffs.rar,Yaffs在MDK下的移植工程,包括S3C2416 Bootloader、Yaffs源码、以及相应的Nand flash驱动。 http://pan.baidu.com/s/1eQz3uhW