====== 이지보드-X5 에 디바이스 드라이버 포팅하기 - Nand Flash ====== 커널 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 이제 커널 빌드에 추가하기 위해 옵션을 추가한다. 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 ~]$ ---- {{indexmenu>:#1|skipns=/^(wiki|etc|diary|playground)$/ skipfile=/^(todays|about|guestbook)$/ nsort rsort}} ----