커널 2.6 기반에서 이지보드에 달려있는 낸드 플래시를 사용하기위한 방법을 소개한다.
Nand 플래시를 사용한다는 것은 파일을 쓰고 지울 수 있어야 한다. 리눅스에서는 이같은 디바이스를 MTD(Memory Technology Device) 라고 부른다.
PC 의 하드디스크를 사용하기 위해 포맷을 하듯이, MTD 또한 사용하기 위해서는 포맷을 해야 한다. 우리가 흔히 사용하는 ext3, xfs 같은 파일시스템은 실린더가 들어있는 하드디스크 같은 디바이스를 위한 것이고, 플래시메모리라는 FTL 을 가진 디바이스에는 따로 준비된 파일시스템이 있다.
가장 많이 사용되는 것이 JFFS2 와 YAFFS2 이다. 이 문서에서는 낸드 플래시를 각각의 파일시스템으로 사용하는 것을 설명한다.
앞서 설명한 플래시 파일시스템을 사용하기 위해서는 커널에 해당 옵션이 포함되어야 한다.
이더넷 드라이버가 있는 것처럼, 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 는 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 는 커널에 기본적으로 포함되지 않기 때문에 패치를 해줘야 한다. 먼저 코드를 다운받기 위해서 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
파일을 삭제하고 생성해보자. 그리고 보드 전원을 끄고, 남아있는지 확인하자.
[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 ~]$