커널 2.6 기반에서 이지보드에 달려있는 낸드 플래시를 사용하기위한 방법을 소개한다.
Nand 플래시를 사용한다는 것은 파일을 쓰고 지울 수 있어야 한다. 리눅스에서는 이같은 디바이스를 MTD(Memory Technology Device) 라고 부른다.
필요한 것들
플래시 파일시스템 기능이 추가된 커널
PC 의 하드디스크를 사용하기 위해 포맷을 하듯이, MTD 또한 사용하기 위해서는 포맷을 해야 한다. 우리가 흔히 사용하는 ext3, xfs 같은 파일시스템은 실린더가 들어있는 하드디스크 같은 디바이스를 위한 것이고, 플래시메모리라는 FTL 을 가진 디바이스에는 따로 준비된 파일시스템이 있다.
가장 많이 사용되는 것이 JFFS2 와 YAFFS2 이다. 이 문서에서는 낸드 플래시를 각각의 파일시스템으로 사용하는 것을 설명한다.
앞서 설명한 플래시 파일시스템을 사용하기 위해서는 커널에 해당 옵션이 포함되어야 한다.
낸드 플래시 드라이버
이더넷 드라이버가 있는 것처럼, Nand 플래시를 사용하기 위해서는 드라이버가 필요하다. 이 드라이버는 파일을 생성하거나 지울 때, Nand 플래시를 컨드롤하는 코드를 담고 있다.
Nand 드라이버가 없거나 잘못된다면, 커널에서 낸드 플래시를 인식하지 못하거나, 파일을 생성하거나 지울 수 없다.
Nand 플래시 드라이버 추가
먼저 드라이버를 작성하자. drivers/mtd/nand 아래에 wj_nand.c 파일을 만들고, 아래와 같이 입력한다.
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/partitions.h> #include <linux/string.h> #include <linux/module.h> #include <linux/delay.h> #include <asm/io.h> #include <asm/uaccess.h> #include <mach/hardware.h> #include <mach/pxa-regs.h> #define EZ_NAND_BASE_PHY (PXA_CS1_PHYS+0x000000) #define EZ_NAND_RANGE (0x1000) #define EZ_NAND_ACCESS_START() GPCR(81) = GPIO_bit(81) #define EZ_NAND_DATA (0x000) #define EZ_NAND_CMD (0x100) #define EZ_NAND_ADDR (0x200) //#define EZ_NAND_ACCESS_END() GPSR(81) = GPIO_bit(81) #define EZ_NAND_ACCESS_END (0x300) #define NAND_BIG_DELAY_US 25 #define NAND_SMALL_DELAY_US 15 static struct mtd_info *ez_board_nand_mtd = NULL; //#define SZ_1M (1024*1024) static char *cmdline_par; /* * Define partitions for flash device */ #ifdef CONFIG_MTD_PARTITIONS static struct mtd_partition partition_info[] = { { .name = "EZ-X5 Kernel partition", .offset = 0x000000, .size = 2*SZ_1M }, { .name = "EZ-X5 Ramdisk partition", .offset = 0x200000, .size = 5*SZ_1M }, { .name = "EZ-X5 Data partition 0", .offset = 0x700000, .size = 57*SZ_1M } }; #define EZ_X5_NAND_NUM_PARTITIONS 3 #endif // Ä¿³Î Ä¿¸Çµå¶óÀÎÀ» ÆÄ½ÌÇÑ´Ù. static void fixup_partition_info( void ) { char *delim_ = ","; int argc; char *argv[256]; char *tok; int size[3]; argc = 0; argv[argc] = NULL; for (tok = strsep( &cmdline_par, delim_); tok; tok = strsep( &cmdline_par, delim_)) { argv[argc++] = tok; } if ( argc == EZ_X5_NAND_NUM_PARTITIONS ) { size[0] = simple_strtoul( argv[0],NULL,0 ); size[1] = simple_strtoul( argv[1],NULL,0 ); size[2] = simple_strtoul( argv[2],NULL,0 ); if ( ( size[0] > 0 ) && ( size[1] > 0 ) && ( size[2] > 0 ) ) { partition_info[0].offset = 0; partition_info[0].size = size[0]*SZ_1M; partition_info[1].offset = size[0]*SZ_1M; partition_info[1].size = size[1]*SZ_1M; partition_info[2].offset = (size[0]+size[1])*SZ_1M; partition_info[2].size = size[2]*SZ_1M; } } } /* * hardware specific access to control-lines */ void ez_board_nand0_hwcontrol(struct mtd_info *mtd, int cmd) { int dummy; register struct nand_chip *this = mtd->priv; register unsigned long NAND_IO_ADDR = this->IO_ADDR_W; #if 0 switch(cmd) { case NAND_NCE: EZ_NAND_ACCESS_START(); break; case NAND_CLE: EZ_NAND_ACCESS_END(); break; } #else switch(cmd) { case NAND_NCE: dummy = readb(NAND_IO_ADDR + EZ_NAND_DATA) ; break; case NAND_CLE: dummy = readb(NAND_IO_ADDR + EZ_NAND_ACCESS_END); break; } #endif } /* * Send command to NAND device */ void ez_board_nand_command (struct mtd_info *mtd, unsigned command, int column, int page_addr) { register struct nand_chip *this = mtd->priv; register unsigned long NAND_IO_ADDR = this->IO_ADDR_W; // Write out the command to the device. if (command != NAND_CMD_SEQIN) { writeb (command, NAND_IO_ADDR + EZ_NAND_CMD ); } else { if (mtd->writesize == 256 && column >= 256) { column -= 256; writeb (NAND_CMD_READOOB, NAND_IO_ADDR + EZ_NAND_CMD ); writeb (NAND_CMD_SEQIN , NAND_IO_ADDR + EZ_NAND_CMD ); } else if (mtd->writesize == 512 && column >= 256) { if (column < 512) { column -= 256; writeb (NAND_CMD_READ1, NAND_IO_ADDR + EZ_NAND_CMD); writeb (NAND_CMD_SEQIN, NAND_IO_ADDR + EZ_NAND_CMD); } else { column -= 512; writeb (NAND_CMD_READOOB, NAND_IO_ADDR + EZ_NAND_CMD); writeb (NAND_CMD_SEQIN , NAND_IO_ADDR + EZ_NAND_CMD); } } else { writeb (NAND_CMD_READ0 , NAND_IO_ADDR + EZ_NAND_CMD); writeb (NAND_CMD_SEQIN , NAND_IO_ADDR + EZ_NAND_CMD); } } // Serially input address if (column != -1 || page_addr != -1) { if (column != -1) writeb (column, NAND_IO_ADDR + EZ_NAND_ADDR); if (page_addr != -1) { writeb ((unsigned char) (page_addr & 0xff), NAND_IO_ADDR + EZ_NAND_ADDR); writeb ((unsigned char) ((page_addr >> 8) & 0xff), NAND_IO_ADDR + EZ_NAND_ADDR); // One more address cycle for higher density devices if (mtd->size & 0x0c000000) { writeb ((unsigned char) ((page_addr >> 16) & 0x0f), NAND_IO_ADDR + EZ_NAND_ADDR); } } } switch (command) { case NAND_CMD_PAGEPROG: case NAND_CMD_ERASE1: case NAND_CMD_ERASE2: case NAND_CMD_SEQIN: case NAND_CMD_STATUS: return; case NAND_CMD_RESET: if( this->dev_ready ) break; writeb (NAND_CMD_STATUS, NAND_IO_ADDR + EZ_NAND_CMD); while ( !(readb (this->IO_ADDR_R) & 0x40)); return; default: if (!this->dev_ready) { udelay (this->chip_delay); return; } } while (!this->dev_ready(mtd)); } /* * Main initialization routine */ static int __init ez_board_nand_init (void) { struct nand_chip *this; unsigned long nand_base_virt; // Allocate memory for MTD device structure and private data ez_board_nand_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip), GFP_KERNEL); if (!ez_board_nand_mtd) { printk ("Unable to allocate EZ-X5-NAND MTD device structure.\n"); return -ENOMEM; } // Get pointer to private data this = (struct nand_chip *) (&ez_board_nand_mtd[1]); // Initialize structures memset((char *) ez_board_nand_mtd, 0, sizeof(struct mtd_info)); memset((char *) this, 0, sizeof(struct nand_chip)); // Link the private data with the MTD structure ez_board_nand_mtd->priv = this; // Set address of NAND IO lines nand_base_virt = (unsigned long ) ioremap( EZ_NAND_BASE_PHY, EZ_NAND_RANGE ); this->IO_ADDR_R = nand_base_virt + EZ_NAND_DATA; this->IO_ADDR_W = nand_base_virt + EZ_NAND_DATA; // Set address of hardware control function this->cmd_ctrl = ez_board_nand0_hwcontrol; // Set commamd function this->cmdfunc = ez_board_nand_command ; // 15 us command delay time this->chip_delay = NAND_SMALL_DELAY_US; this->ecc.mode = NAND_ECC_SOFT; // Scan to find existence of the device if (nand_scan (ez_board_nand_mtd,1)) { kfree (ez_board_nand_mtd); return -ENXIO; } // Allocate memory for internal data buffer this->buffers = kmalloc (sizeof(u_char) * (ez_board_nand_mtd->writesize + ez_board_nand_mtd->oobsize), GFP_KERNEL); if (!this->buffers) { printk ("Unable to allocate NAND data buffer for EZ_IXP42X-NAND.\n"); kfree (ez_board_nand_mtd); return -ENOMEM; } // Ä¿³ÎÄ¿¸Çµå¿¡¼ Á¤º¸¸¦ ¾ò´Â´Ù fixup_partition_info(); // Register the partitions add_mtd_partitions(ez_board_nand_mtd, partition_info, EZ_X5_NAND_NUM_PARTITIONS); // Return happy return 0; } module_init(ez_board_nand_init); /* * Clean up routine */ #ifdef MODULE static void __exit ez_board_nand_cleanup (void) { struct nand_chip *this = (struct nand_chip *) &ez_board_nand_mtd[0]; // Unregister the device del_mtd_device (ez_board_nand_mtd); // Free internal data buffer kfree (this->data_buf); // Free the MTD device structure kfree (ez_board_nand_mtd); } module_exit(ez_board_nand_cleanup); #endif static int __init nandpart_setup(char *s) { cmdline_par = s; return 1; } __setup("nandparts=", nandpart_setup); MODULE_AUTHOR("You Youngchang,jang hyung-gi <frog@falinux.com"); MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on EZ-X5-NAND board"); MODULE_LICENSE("GPL");
이제 커널 빌드에 추가하기 위해 옵션을 추가한다. drivers/mtd/nand/Kconfig 파일에 다음을 추가한다.
... config MTD_NAND_WJ_X5 tristate "NAND Flash device on WJ-X5 board" depends on MACH_WJ_X5 && MTD_NAND help This enables the NAND flash driver on the WJ-X5 Board. ... config MTD_NAND_S3C2410
마지막으로 drivers/mtd/nand/Makefile 파일을 추가한다.
... obj-$(CONFIG_MTD_NAND_WJ_X5) += wj_nand.o ...
커널 옵션 추가
이제 앞서 추가한 커널 옵션을 추가해보자!
Device Drivers ---> <*> Memory Technology Device (MTD) support ---> [*] MTD partitioning support < > RedBoot partition table parsing [ ] Command line partition table parsing < > ARM Firmware Suite partition parsing < > TI AR7 partitioning support *** User Modules And Translation Layers *** <*> Direct char device access to MTD devices -*- Common interface to block layer for MTD 'translation layers <*> Caching block device access to MTD devices <*> NAND Device Support ---> <*> NAND Flash device on WJ-X5 board
커널 빌드 후에 부팅시켜보자. 아래와 같이 낸드 플래시에 대한 로그가 보인다면 제대로 인식한 것이다.
NAND device: Manufacturer ID:0xec, Chip ID:0x76 (Samsung NAND 64MiB 3,3V 8-bit) Scanning device for bad blocks Creating 3 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00100000 : "falinux boot/config/logo partition" 0x00100000-0x00900000 : "falinux kernel/ramdisk partition" 0x00900000-0x04000000 : "falinux yaffs partition"
JFFS2 사용하기
이제는 인식한 낸드 플래시를 사용하기 위해 파일시스템을 커널에 추가해보자. 우선 JFFS2 부터 하겠다.
JFFS2 는 YAFFS2 와는 달리 커널에 포함되어 있기 때문에 별도의 패치 없이 바로 커널 옵션에서 추가할 수 있다.
File systems ---> Miscellaneous filesystems ---> <*> Journalling Flash File System v2 (JFFS2) support (0) JFFS2 debugging verbosity (0 = quiet, 2 = noisy) (NEW) [*] JFFS2 write-buffering support (NEW) [ ] Verify JFFS2 write-buffer reads (NEW) [ ] JFFS2 summary support (EXPERIMENTAL) (NEW) [ ] JFFS2 XATTR support (EXPERIMENTAL) (NEW) [ ] Advanced compression options for JFFS2 (NEW)
커널을 빌드하자.
이제 마지막으로 낸드 플래시를 JFFS2 로 포맷할 프로그램이 필요하다. MTD Utilities 이라는 소스코드를 따로 빌드해도 되지만, 여기서는 busybox 에도 동일한 코드가 들어있기 때문에 이를 사용한다. 우리가 필요한 것은 flash_eraseall 이라는 실행파일이다.
빌드한 busybox(빌드시 flash_eraseall 를 선택했다)를 타겟보드에 올리고, 다음과 같이 실행한다.
[root@falinux ~]$ cat /proc/mtd dev: size erasesize name mtd0: 00100000 00004000 "EZ-X5 Kernel partition" mtd1: 00800000 00004000 "EZ-X5 Ramdisk partition" mtd2: 03700000 00004000 "EZ-X5 Data partition 0"
현재 낸드 플래시의 파티션이 보인다. 여기서는 가장 큰 사이즈를 차지하는 mtd2 파티션을 포맷한다. -j 옵션은 JFFS2 를 위한 포맷이다.
#./busybox flash_eraseall -j /dev/mtd2 Erasing 64 Kibyte @ 10000 -- 100 % complete
이제 마운트를 해보자.
#mount -t jffs2 /dev/mtdblock2 /app
에러없이 수행되었다면, 해당 디렉토리에서 파일을 생성하고 지워보자. 그리고 보드 전원을 끄고, 남아있는지 확인하자.
YAFFS2 사용하기
YAFFS2 는 커널에 기본적으로 포함되지 않기 때문에 패치를 해줘야 한다. 먼저 코드를 다운받기 위해서 http://www.yaffs.net/ 에 접속하자.
패치하는 방법은 다음과 같다.
#cd yaffs2 #./patch-ker.sh c m /opt/linux-2.6.28 // c 는 copy, m 은 멀티버전, 그리고 커널 소스 경로를 적어준다.
이제 menuconfig 를 통해 커널 옵션을 지정해보자.
File systems ---> Miscellaneous filesystems ---> <*> yaffs2 file system support -*- 512 byte / page devices [*] Use older-style on-NAND data format with pageStatus byte -*- 2048 byte (or larger) / page devices [ ] Autoselect yaffs2 format [*] Disable yaffs from doing ECC on tags by default [*] Force chunk erase check [*] Empty lost and found on boot [*] Disable yaffs2 block refreshing [*] Disable yaffs2 background processing [*] Enable yaffs2 xattr support <*> Journalling Flash File System v2 (JFFS2) support (0) JFFS2 debugging verbosity (0 = quiet, 2 = noisy) [*] JFFS2 write-buffering support [ ] Verify JFFS2 write-buffer reads [ ] JFFS2 summary support (EXPERIMENTAL) [ ] JFFS2 XATTR support (EXPERIMENTAL) [ ] Advanced compression options for JFFS2
이제 빌드하고 부팅해서, 앞서와 마찬가지로 파티션을 포맷하자.
#./busybox flash_eraseall /dev/mtd2 Erasing 64 Kibyte @ 10000 -- 100 % complete
그리고 마운트를 해보자.
#mount -t yaffs2 /dev/mtdblock2 /app
파일을 삭제하고 생성해보자. 그리고 보드 전원을 끄고, 남아있는지 확인하자.
FAQ
YAFFS2 에서 파일 생성시 Cannot allocate memory 메세지가 발생
[root@falinux app]$ cat /proc/mtd dev: size erasesize name mtd0: 00200000 00004000 "EZ-X5 Kernel partition" mtd1: 00500000 00004000 "EZ-X5 Ramdisk partition" mtd2: 03900000 00004000 "EZ-X5 Data partition 0" YAFFS built:Jan 13 2013 23:57:58 $Id: yaffs_fs.c,v 1.32 2003/10/29 20:42:34 charles $Id: yaffs_guts.c,v 1.33 2003/11/16 07:40:42 charles Device yaffs startBlock......... 1 endBlock........... 3647 chunkGroupBits..... 1 chunkGroupSize..... 2 nErasedBlocks...... 3646 nTnodesCreated..... 100 nFreeTnodes........ 67 nObjectsCreated.... 100 nFreeObjects....... 92 nFreeChunks........ 116699 nPageWrites........ 4 nPageReads......... 22 nBlockErasures..... 0 nGCCopies.......... 0 garbageCollections. 0 passiveGCs......... 0 nRetriedWrites..... 0 nRetireBlocks...... 0 eccFixed........... 0 eccUnfixed......... 0 tagsEccFixed....... 0 tagsEccUnfixed..... 3652 cacheHits.......... 0 nDeletedFiles...... 0 nUnlinkedFiles..... 0 nBackgroudDeletions 0 useNANDECC......... 0 [root@falinux app]$
성공 화면
Copy Kernel Image ..... Copy Ramdisk Image ..... Starting kernel [MARCH 3002]... kernel command [EZBOOT mem=64M initrd=0xA0800000,5M root=/dev/ram ramdisk=16384 console=ttyPXA2,115200 ip0=1.1.1.2 mac=00:FA:07:78:65:05 netmask=255.255.255.0 gw=192.] Uncompressing Linux............................................................................................................. done, booting the kernel. Linux version 2.6.21-falinux (gemini@hw27) (gcc version 3.4.3) #1 Mon Jun 30 20:37:01 KST 2008 CPU: XScale-PXA255 [69052d06] revision 6 (ARMv5TE), cr=0000397f Machine: FALinux EZ-X5 Development Platform Memory policy: ECC disabled, Data cache writeback Memory clock: 99.53MHz (*27) Run Mode clock: 398.13MHz (*4) Turbo Mode clock: 398.13MHz (*1.0, inactive) CPU0: D VIVT undefined 5 cache CPU0: I cache: 32768 bytes, associativity 32, 32 byte lines, 32 sets CPU0: D cache: 32768 bytes, associativity 32, 32 byte lines, 32 sets Built 1 zonelists. Total pages: 16256 Kernel command line: EZBOOT mem=64M initrd=0xA0800000,5M root=/dev/ram ramdisk=16384 console=ttyPXA2,115200 ip0=1.1.1.2 mac=00:FA:07:78:65:05 netmask=255.255.255.0 gw PID hash table entries: 256 (order: 8, 1024 bytes) Console: colour dummy device 80x30 Dentry cache hash table entries: 8192 (order: 3, 32768 bytes) Inode-cache hash table entries: 4096 (order: 2, 16384 bytes) Memory: 64MB = 64MB total Memory: 56320KB available (3012K code, 361K data, 108K init) Security Framework v1.0.0 initialized SELinux: Initializing. selinux_register_security: Registering secondary module capability Capability LSM initialized as secondary Mount-cache hash table entries: 512 CPU: Testing write buffer coherency: ok NET: Registered protocol family 16 SCSI subsystem initialized NET: Registered protocol family 2 Time: pxa_timer clocksource has been installed. IP route cache hash table entries: 1024 (order: 0, 4096 bytes) TCP established hash table entries: 2048 (order: 2, 16384 bytes) TCP bind hash table entries: 2048 (order: 1, 8192 bytes) TCP: Hash tables configured (established 2048 bind 2048) TCP reno registered checking if image is initramfs...it isn't (no cpio magic); looks like an initrd Freeing initrd memory: 5120K NetWinder Floating Point Emulator V0.97 (double precision) audit: initializing netlink socket (disabled) audit(8.699:1): initialized yaffs Jun 30 2008 20:35:14 Installing. io scheduler noop registered io scheduler anticipatory registered io scheduler deadline registered io scheduler cfq registered (default) pxa2xx-uart.0: ttyPXA0 at MMIO 0x40100000 (irq = 15) is a FFUART pxa2xx-uart.1: ttyPXA1 at MMIO 0x40200000 (irq = 14) is a BTUART pxa2xx-uart.2: ttyPXA2 at MMIO 0x40700000 (irq = 13) is a STUART pxa2xx-uart.3: ttyPXA3 at MMIO 0x41600000 (irq = 0) is a HWUART RAMDISK driver initialized: 16 RAM disks of 16384K size 1024 blocksize loop: loaded (max 8 devices) eth0: cs8900 rev J found at 0xf1000300 [Cirrus EEPROM] cs89x0 media RJ-45, IRQ 44, programmed I/O, MAC 00:fa:07:78:65:05 NAND device: Manufacturer ID:0xec, Chip ID:0x76 (Samsung NAND 64MiB 3,3V 8-bit) Scanning device for bad blocks Creating 3 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00100000 : "falinux boot/config/logo partition" 0x00100000-0x00900000 : "falinux kernel/ramdisk partition" 0x00900000-0x04000000 : "falinux yaffs partition" mice: PS/2 mouse device common for all mice i2c /dev entries driver I2C: i2c-0: PXA I2C adapter TCP cubic registered NET: Registered protocol family 1 NET: Registered protocol family 10 lo: Disabled Privacy Extensions Mobile IPv6 IPv6 over IPv4 tunneling driver sit0: Disabled Privacy Extensions NET: Registered protocol family 17 XScale DSP coprocessor detected. RAMDISK: Compressed image found at block 0 VFS: Mounted root (ext2 filesystem) readonly. Freeing init memory: 108K INIT: version 2.86 booting INIT: Entering runlevel: 3 eth0: using full-duplex 10Base-T (RJ-45) route: SIOC[ADD|DEL]RT: Network is unreachable yaffs: dev is 32505858 name is "mtdblock2" yaffs: passed flags "" yaffs: Attempting MTD mount on 31.2, "mtdblock2" yaffs: auto selecting yaffs1 /usr/local/apache/bin/apachectl start: httpd started Starting system logger: [ OK ] Starting INET services: [ OK ] Welcome to FALinux (www.falinux.com) Linux Kernel 2.6.21-falinux falinux login: root [root@falinux ~]$ df Filesystem 1k-blocks Used Available Use% Mounted on /dev/ram 15863 11945 3099 79% / /dev/mtdblock2 56320 100 56220 0% /app [root@falinux ~]$