본문 바로가기

Database/Embedded

부트로더와 플래시 메모리(2)

출처 카페 > 차니의 컴퓨터 마을 / newchany
원본 http://cafe.naver.com/newchany/192

1회 임베디드 프로그래머와 부트로더
2회 보드를 살려보자
3회 부트로더의 기본 구현(1)
4회 부트로더의 기본 구현(2)
5회 부트로더와 플래시 메모리(1)
6회 부트로더와 플래시 메모리(2)
7회 부트로더의 네트워크 구현(1)
8회 부트로더의 네트워크 구현(2)
9회 커널로의 진입

 

임베디드 시스템에서 최근에 가장 많이 사용되는 메모리를 들라고 하면 NAND를 꼽을 수 있다. 리눅스 운영체제를 임베디드 시스템에 적용하는 경우에 일반적인 임베디드 시스템보다 많은 비휘발성 메모리를 필요로 하는데 NAND 플래시는 용량 대비 저렴한 가격의 비휘발성 메모리이므로 그 쓰임새는 더욱 더 증가하리라 본다. 이지부트는 이 NAND 플래시에 커널 이미지와 램디스크 이미지를 저장하도록 구성되어 있다. 이번 연재에서는 부트로더에서 NAND 플래시를 다루기 위한 하드웨어적인 기본 지식과 소프트웨어적인 처리에 대하여 중점적으로 살펴본다.

플래시 메모리 중에서 NAND 플래시 메모리는 임베디드 시스템을 다루는 사람이 생각하는 메모리 소자와는 약간 다르다. 임베디드 시스템을 다루는 메모리라면 주소가 있고 특정 주소에 대입하는 데이터가 있다. 그러나 NAND 플래시 메모리는 PC 시스템과 비유하면 하드디스크와 같다고 봐야 한다. PC에서도 메모리라 하면 주소 공간이 있고 바이트 단위의 접근이 가능하다. 이에 반해 하드디스크는 섹터 단위의 데이터를 써넣고 읽는다. 그리고 전원을 꺼도 내용이 소실되지 않는다. 또 초기에 포맷을 해야 한다. NAND형 플래시도 이와 비슷한 처리를 수행한다. 페이지 단위로 데이터를 써넣고 읽어와야 하고, 전원을 꺼도 내용이 소실되지 않으며, 데이터를 써넣기 위해서는 포맷을 해야 한다. 이렇게 하드디스크와 비슷한 점이 많은 반면 플래시 메모리 소자이기 때문에 데이터를 써넣기 위해서는 지우기 과정을 수행하여야 한다(플래시 메모리의 고유한 특성은 이전 연재에서 설명했기 때문에 따로 설명하지 않겠다).
<그림 1>에서 보여주는 칩의 외형과 핀 설명은 삼성전자에서 생산되는 8비트형 64MB 플래시 메모리이며 형 번호는 K9F1208U0M이다. 메모리의 크기에 비해서 핀 수는 매우 작은 편이다. 사용되는 핀의 용도는 다음과 같다.

◆ I/O0~I/O7: 데이터 버스
◆ CLE : High가 되면 현재 버스의 데이터는 명령임을 나타낸다.
◆ ALE : High가 되면 현재 버스의 데이터는 주소임을 나타낸다.
◆ nCE : 칩 셀렉터
◆ nRE : 버스 읽기 신호
◆ nWE : 버스 쓰기 신호
◆ nWP : Low가 되면 쓰기나 지우기가 되지 않는다. 풀업이나 풀다운으로 연결한다.
◆ R/nB : Low가 되면 현재 작업 중임을 알리는 것이다. 풀업으로 사용한다.


<그림1> NAND 플래시 외형 및 핀 설정

NAND 플래시 회로
NAND 플래시를 사용하기 위한 회로 구성은 여러 가지 형식이 있겠지만 여기서는 EZ-X5에서 사용한 회로 구성을 설명하겠다.


<그림2> EX-X5의 NAND 플래시 회로

<그림 2>는 EZ-X5에서 구성한 NAND 플래시 회로도이다. 이 회로도에서 하드웨어를 설계하는 사람들이 주의해서 봐야 하는 것은 U15로 구성한 NAND용 버퍼 단이다. 이 버퍼는 CPU의 데이터 버스와 NAND 플래시 메모리의 I/O를 분리해 주는 역할을 한다. EZ-X5에서는 I/O 핀에 CPU의 데이터 버스를 그대로 이용하기 때문에 이런 처리를 해줘야 한다. 왜냐하면 CE 단자의 핀 신호를 계속 내려주고 접근해야 하기 때문이다.
CPU의 데이터 버스를 사용하지 않는다면 이런 버퍼 단은 필요가 없다. <그림 2>의 회로도만으로는 NAND 플래시의 회로 처리에 대한 완전한 구성이 아니다. 몇 가지 추가 처리를 해야 하는데 EZ-X5에서는 CPLD를 이용하여 처리하고 있으므로, 이에 대한 구성도와 VHDL 코드만을 예로 설명하겠다. VHDL이라고 해봤자 그리 복잡한 코드가 아니므로 하드웨어를 개발하는 독자들은 쉽게 이해가 될 것이다.
<리스트 1>의 VHDL 코드를 해석해 보자. 우선 nand_sel은 CS1, 즉 0x04000000 번지를 접근하면 동작한다는 의미가 된다. 이 번지 접근시에만 OE_NB 핀을 활성화하여 CPU의 데이터 버스를 NAND 플래시의 I/O에 연결하고 CPU에서 발생한 READ 신호와 WRITE 신호를 NAND 플래시에 전달한다. 가장 중요한 것은 CLE 신호와 ALE 신호, 그리고 CE 신호 처리이다. 특히 CE 처리가 매우 중요하다. <리스트 1>의 VHDL 코드는 프로그램에서 접근하는 번지에 대하여 다음과 같이 대응시키고 있다.

◆ 0x04000000 : CE 신호를 LOW로 만든다.
◆ 0x04000100 : CLE 신호를 HIGH로 만든다.
◆ 0x04000200 : ALE 신호를 HIGH로 만든다.
◆ 0x04000300 : CE 신호를 HIGH로 만든다.



<리스트 1>의 코드를 보면 알겠지만 대분의 CE 신호는 해당 칩에 접근할 경우에만 LOW로 만드는 것이 보통인데, NAND 플래시에서는 CE 신호를 LOW로 만드는 주소와 HIGH로 만드는 주소를 따로 두고 있다. 즉 CE 신호를 제어하겠다는 의미가 된다. 이에 대한 이유는 실제 NAND 플래시를 제어하는 부분에서 다시 설명하겠다.

NAND 플래시의 구성
삼성전자가 제공하는 매뉴얼을 보면 NAND 플래시의 내부 구성도는 <그림 4>와 같다. <그림 4>를 보면 NAND 플래시 메모리의 주소를 대입하여야 데이터를 읽고 쓸 수 있다. 하지만 외부와 연결된 I/O 버스는 플래시 메모리 블럭과 직접 연결되어 있지 않고 여러 버퍼를 통해서 읽고 쓴다. 그래서 페이지 단위로 읽고 쓸 수 있는 구조로 구성된다. 하지만 구성도를 가지고 플래시 메모리를 설명하기에는 너무 어렵기 때문에 하나의 블랙박스로 보고 NAND를 접근하는 것이 더 편하다.
또한 NAND 플래시를 다루기 위해서는 ‘페이지(읽고 쓰기를 수행하기 위한 단위)’라는 개념과 ‘블럭(지우기를 수행하기 위한 단위)’이라는 개념에 익숙해져야 한다. NAND 플래시에서는 데이터를 읽거나 쓰기 위해서는 페이지 단위로 써넣거나 읽을 수 있다. 1페이지는 528바이트 단위의 크기를 갖는다. 이 페이지는 다시 크게 세 영역으로 나뉜다. A, B, C 영역이라고 하는데, A 영역은 256바이트, B 영역은 256바이트, 그리고 C 영역이 16바이트의 크기를 갖는다.
블럭은 NAND 플래시를 지울 때의 크기 단위로 32개의 페이지를 하나의 블럭으로 본다. 블럭의 구성은 <그림 6>과 같다. 또한 플래시 메모리는 블럭이 모여서 전체 메모리 시스템을 구성한다. 그러므로 플래시 메모리를 <그림 7>과 같은 형태로 정리해 볼 수 있다.
NAND 플래시와 NOR 플래시를 다루는 개념은 거의 같다. 지난 호에서 설명했지만 플래시 메모리를 처리하기 위한 내용을 다시 정리하면 플래시 메모리 ID읽기, 지우기, 쓰기, 읽기로 나눌 수 있다. 이 처리에 대한 소스의 구현이 되어야 NAND 플래시의 기본적인 처리가 가능하다. 이지부트에서 이를 위해 구현한 소스는 include/nand.h와 main/nand.c에 들어 있다. 이 소스를 분석하면서 NAND에 대한 처리를 이지부트에서 어떻게 구현하였는가를 차근차근 살펴보자.


NAND 플래시의 명령
NOR형 플래시와 마찬가지로 NAND형도 무언가의 처리를 하려면 명령을 주어야 한다. NAND형 플래시에서 사용하는 대표적인 명령은 다음과 같다.

◆ ID 읽기 명령 0x90
◆ 상태 읽기 명령 0x70
◆ 지우기 셋업 명령 0x60
◆ 지우기 실행 명령 0xD0
◆ 쓰기 시작 명령 0x80
◆ 쓰기 실행 명령 0x10
◆ A 영역 선택 및 읽기 명령 0x00
◆ B 영역 선택 및 읽기 명령 0x01
◆ C 영역 선택 및 읽기 명령 0x50

이 명령에 대한 자세한 설명은 이후에 다시 한번 설명될 것이다. 이 명령을 선언하기 위해서 이지부트 소스의 include/nand.h에서는 다음과 같이 선언하고 있다(이 명령은 단독으로 쓰이지 않고 각 명령에 따른 구성이 별도로 존재한다. 각 명령에 대한 것은 이지부트 소스를 참고하면서 살펴보기로 한다).

#define NAND_CMD_READ_ID 0x90 // ID 읽기 커맨드
#define NAND_CMD_READ_STATE 0x70 // 상태 읽기 커맨드
#define NAND_CMD_ERASE_SETUP 0x60 // 지우기 셋업 커맨드
#define NAND_CMD_ERASE_RUN 0xD0 // 지우기 실행 커맨드
#define NAND_CMD_WRITE_START 0x80 // 쓰기 시작 커맨드
#define NAND_CMD_WRITE_RUN 0x10 // 쓰기 실행 커맨드
#define NAND_CMD_READ_A 0x00 // A 영역 읽기
#define NAND_CMD_READ_B 0x01 // B 영역 읽기
#define NAND_CMD_READ_C 0x50 // C 영역 읽기

EZ-X5에서 하드웨어 제어 구성


NAND 플래시에 특정 처리를 하려면 다음과 같은 순서를 갖는다.

① 칩 셀렉터인 CE 신호를 LOW 상태로 만든다.
② 명령을 NAND 플래시에 써넣는다.
③ NAND 내부의 접근하고자 하는 주소를 써넣는다.
④ NAND에 데이터를 써넣거나 읽는다.
⑤ NAND의 상태를 감시한다.
⑥ 칩 셀렉터인 CE 신호를 HIGH 상태로 만든다.

이 순서에 따른 처리를 소프트웨어적으로 접근했을 때 하드웨어에서도 자동으로 수행하도록 EZ-X5는 다음과 같은 주소를 각 신호선에 맵핑하고 있다.

◆ 0x04000000 : CE 신호를 LOW로 만든다.
◆ 0x04000100 : CLE 신호를 HIGH로 만든다.
◆ 0x04000200 : ALE 신호를 HIGH로 만든다.
◆ 0x04000300 : CE 신호를 HIGH로 만든다.

각 번지의 의미를 정확하게 살펴보자. NAND 플래시에서 CE 신호는 원칙적으로 NAND 플래시에 접근했을 때만 LOW로 처리하여야 한다. 그런데 앞에서도 설명했지만 실제로 사용하면 프로세서 버스와 분리시키는 버퍼를 사용하고, NAND 플래시에 대한 접근이 시작될 때부터 CE를 LOW로 만든 후에 모든 처리가 끝나면 CE를 다시 HIGH 신호 상태로 만드는 것이 유리하다. 그래서 EZ-X5에서는 이를 CPLD 회로를 이용하여 구현하고 있다.
이때 CE 신호와 연관된 주소가 바로 0x04000000과 0x04000300이다. 0x04000000 번지를 읽거나 쓰면 CE 단자는 LOW 상태가 되고 유지된다. 반대로 0x04000300 번지에 데이터를 읽거나 쓰면 CE 단자는 HIGH 상태가 되고 유지된다. 소프트웨어 프로그래머 입장에서 보면 0x04000000 번지는 NAND 플래시를 이제부터 사용하겠다고 알려주는 번지가 되고, 0x04000300은 NAND 플래시를 더 이상 사용하지 않겠다고 알려주는 번지로 이해하면 된다. 이 중에서 0x04000000 번지는 데이터를 써넣거나 읽기 용도로도 사용되도록 하드웨어적으로 구성되어 있다.
NAND 플래시 메모리는 일반적인 데이터만 써넣고 읽는 것이 아니라 처리하고자 하는 명령과 접근하고자 하는 메모리의 주소를 써넣어 주어야 한다. 데이터 포트를 NAND 플래시가 공유하기 때문에 NAND 플래시에 써넣은 데이터가 명령으로 인식되기 위해서는 CLE 단자를 HIGH로 만들어 주어야 한다. 이런 처리를 한꺼번에 할 수 있도록 하드웨어적으로 맵핑시킨 주소가 0x04000100이다. 즉 0x04000100 번지에 데이터를 써넣으면 NAND 플래시는 데이터를 명령으로 인식하게 된다.
이와 마찬가지로 데이터를 주소로 인식시키기 위해서는 ALE 단자를 HIGH로 만들어 주고 데이터를 써넣어야 한다. 이런 처리를 한꺼번에 할 수 있도록 하드웨어적으로 맵핑시킨 주소가 0x04000200이다. 즉 0x04000200 번지에 데이터를 써넣으면 NAND 플래시는 데이터를 주소 데이터로 인식하게 된다. 이와 같은 주소를 소프트웨어적으로 처리하기 위해서 이지부트는 include/nand.h에 다음과 같은 매크로를 선언하고 있다.

#define NAND_ACCESS_START *((volatile short *)(NandDev->BaseAddress + 0x000 ))
#define NAND_DATA *((volatile short *)(NandDev->BaseAddress + 0x000 ))
#define NAND_CMD *((volatile short *)(NandDev->BaseAddress + 0x100 ))
#define NAND_ADDR *((volatile short *)(NandDev->BaseAddress + 0x200 ))
#define NAND_ACCESS_END *((volatile short *)(NandDev->BaseAddress + 0x300 ))

이 매크로에서 사용하는 NandDev->BaseAddress가 굳이 사용되는 이유는 NAND 플래시 메모리가 여러 개 사용될 수도 있기 때문에 동일한 소스를 이용하여 처리 가능하도록 하기 위한 것이다. EZ-X5에는 기본적으로 NAND 플래시가 생산시에 1개만 장착되기 때문에 NandDev->BaseAddress 값은 0x04000000이 된다.



플래시 메모리 ID 읽기
NAND 플래시에서 ID를 읽는 것은 NAND 플래시의 동작이 정상적인가를 확인하기 위한 방법도 되고 플래시를 제어하기 위한 정보로 필요한 메모리 크기를 결정하는 방법도 된다. 플래시 메모리에서 ID를 읽기 위해서는 다음과 같은 순서로 제어하여야 한다.

① CE를 LOW로 만든다.
② 플래시 메모리 ID 읽기 명령 0x90을 써넣는다.
③ 0x00 값을 주소로 써넣는다.
④ 플래시 메모리에서 데이터를 읽는다. 이 데이터는 제조사가 된다.
⑤ 플래시 메모리에서 데이터를 읽는다. 이 데이터는 제품 구별 ID가 된다.
⑥ CE를 HIGH로 만든다.

이지부트에서는 이와 같은 처리를 하기 위해서 NAND_DETECT라는 함수를 정의하고 있다. 소스는 <리스트 2>와 같다.

ID 체크
<리스트 2>의 ①~②가 ID를 읽어오는 루틴이다. 가장 먼저 하는 것은 BaseAddress 매개 변수에서 전달된 NAND 플래시의 베이스 주소를 TNandInfo 구조체 변수인 NandDev의 BaseAddress 필드 변수에 대입한다. 이 NandDev는 다른 NAND 함수에서 사용하기 위한 정보를 담고 있는 변수이다. NandDev->BaseAddress 변수는 NAND_ACCESS_START와 NAND_CMD와 같은 NAND에 접근하기 위한 매크로 함수에서 사용한다.
NandDev->BaseAddress = BaseAddress;

NAND 플래시를 사용하기 위해서는 앞에서 설명했듯이 CE 단자를 LOW로 만든 상태에서 접근하여야 한다. 또한 처리가 끝나면 다시 CE 단자를 HIGH로 만들어야 한다. 이런 처리를 하는 것이 다음 루틴이다. 특별히 데이터를 읽거나 쓰기 위한 루틴이 아니므로 Dummy라는 변수를 이용해 읽기를 시도하여 CE 단자를 처리한다.

Dummy = NAND_ACCESS_START;

:
Dummy = NAND_ACCESS_END;

NAND 플래시에서 ID를 읽기 위해서는 0x90 값을 명령으로 NAND 플래시에 써넣어야 한다. 그리고 주소에 임의의 주소를 써넣어야 하는데 보통 0x00값을 써넣는다. 이러 처리를 다음과 같이 수행한다.

NAND_CMD = NAND_CMD_READ_ID; // ID 읽기 명령
NAND_ADDR = 0x00; // 명령어 이후 부가적인 행동

이렇게 ID 요구 명령을 NAND 플래시에 써넣은 후 데이터를 플래시에서 읽으면 첫 번째 데이터는 제조사 ID가 나오고 다시 두 번째 데이터를 읽으면 NAND 플래시 제품 ID가 나온다.

MakerID = NAND_DATA & 0xFF; // 제조사 읽기
SizeID = NAND_DATA & 0xFF; // 크기 읽기

NAND_DATA에서 데이터를 읽은 후 0xFF값 AND하여 하위 값만을 읽어들이는 이유는 NAND 플래시는 8비트 접근 처리를 하고 있는데 데이터 버스는 16비트 처리가 되고 있기 때문에 필요 없이 들어올 수 있는 상위 8비트를 제거하기 위한 것이다. PXA255는 최소 버스 폭이 16비트이기 때문에 하드웨어적으로 16비트 접근을 하고 있다.
이렇게 MakerID 변수에 제조사 값을 저장하였다면 이 값이 삼성 제품을 표시하는 ID인 0xEC인가를 본다. 만약 이 값이 아니라면 더 이상 처리하지 않는다. 왜냐하면 현재 이지부트는 삼성 NAND에 대한 처리만을 하고 있기 때문이다.
<리스트 1>의 ③루틴에서는 MakerID 값이 삼성이면 이후 삼성에서 생산되고 있는 제품 ID값에 따라서 적절히 NandDev 변수를 초기화한다. 이렇게 설정된 NAND 플래시 정보는 각 NAND 함수에서 사용된다. 현재 삼성에서 생산되는 제품의 ID값을 다시 정리하면 다음과 같다.

◆ 0x73 : 16MB 용량
◆ 0x75 : 32MB 용량
◆ 0x76 : 64MB 용량
◆ 0x79 : 128MB 용량

TNandInfo 구조체
NAND_DETECT 함수는 BaseAddress 매개 변수를 이용하여 NAND 플래시의 ID를 읽어서 NandDev에 정보를 저장한다. 이지부트에서 NAND를 다루는 함수군은 EZ-X5에서 사용되는 NAND 플래시만을 위해서 설계되지는 않았으며, 추가적으로 확장되는 NAND를 다룰 수 있도록 함수가 설계되어 있다. 그래서 이런 하드웨어 정보를 다루기 위해 NAND_DETECT 함수는 TNandInfo 구조체와 NAND 디바이스의 베이스 주소를 필요로 한다. TNandInfo 구조체는 include/nand.h에 다음과 같이 정의되어 있다.

typedef struct
{
unsigned long Type;
unsigned long BaseAddress; // 위치 주소
unsigned long TotalSize; // 총 사용 가능 용량
unsigned long EraseSize; // 삭제 단위 블럭

char BadBlock[1024*8]; // 배드 블럭 표시 ‘X’ : 배드 블럭 / ‘O’ : 정상
unsigned short VirBlock[1024*8];
int BadBlockCount;

TNandPartition *pPartition; // 파티션 정보 주소
int PartitionNR; // 파티션 갯수
} __attribute__ ((packed)) TNandInfo;

Type 필드 변수는 NAND 플래시의 구분 ID이다. 단지 기록만 하고 특별히 처리하지 않는 변수이다. BaseAddress 필드 변수는 NAND 플래시를 접근하기 위한 주소이다. 이것은 하드웨어적으로 어떻게 맵핑되었는가에 의해서 결정된다. 모든 NAND 함수는 이 주소를 기준으로 처리된다.
TotalSize 필드 변수는 NAND 플래시의 전체 크기를 바이트 단위로 표시한다. EraseSize 필드 변수는 NAND 플래시의 지우기를 수행하기 위한 단위 크기를 바이트 단위로 표시한다. 삼성 플래시라면 32섹터 크기, 즉 16×1024바이트의 크기를 갖는다. 그 외에 Bad Block, VirBlock, BadBlockCount, pPartition, PartitionNR 필드 변수는 NAND 플래시에서 다루기 골치 아픈 BAD 블럭의 처리를 위한 것과 파티션 처리를 하기 위해서 필요한 변수들이다.

페이지/블럭 주소 지정법
NAND 플래시에서 데이터를 읽거나 쓸 때, 또는 블럭을 지울 때는 NAND 플래시 내부의 메모리를 지정하여야 한다. 이런 메모리의 위치를 지정하기 위해서는 명령을 준 이후에 주소를 써넣어야 한다. NAND 플래시 자체가 매우 큰 메모리이기 때문에 여러 바이트를 이용해 지정하여야 한다. 이 바이트 수는 상황에 따라서 다르기 때문에 정리할 필요가 있다. 이 바이트 수에 대한 결정은 다음 두 가지 조건에 따라 다르다. 필요한 어드레스의 크기를 정리하면 <표 1>과 같다.

◆ 읽기 쓰기인가, 지우기인가
◆ NAND 플래시 메모리가 64MB 이상인가

읽기 쓰기 페이지의 지정 형식
데이터를 읽고 쓸 때는 페이지 단위여야 한다고 앞에서 설명했다. 이 페이지를 지정하기 위해서 사용하는 포맷은 <그림 8>, <그림 9>와 같다. <그림 8>, <그림 9>에서 보면 버퍼 주소라는 것이 있다. NAND 플래시는 실제 내부에 256바이트 크기의 버퍼가 있는데, 이것을 이용하여 데이터를 읽고 쓰는 구조로 되어 있다. 보통은 버퍼 단위로 읽고 쓰기 때문에 이 버퍼 주소는 대부분 0이 된다. 하지만 배드 블럭을 확인할 경우에는 이 버퍼 주소에 읽고자 하는 위치를 직접 대입하기도 한다. 읽기와 쓰기를 처리할 때는 이 버퍼 주소를 그냥 무시하고 처리하는 것이 더 유리하다.

블럭 지정 형식
NAND 플래시를 지울 때는 버퍼 주소와 페이지 주소를 지정할 필요가 없다. 하지만 실제로 사용되는 형식은 페이지 주소 지정 형식에서 버퍼 주소 지정을 제외한 형식이 사용된다. 블럭을 지정하기 위해서 사용되는 포맷은 <그림 10>, <그림 11>과 같다.











플래시 메모리 지우기
NAND 역시 플래시 메모리이기 때문에 데이터를 써넣기 전에는 지우기를 수행하여야 한다. 이 지우기 단위는 블럭 단위로 지워지게 된다. NAND 플래시를 지우기 위한 절차는 다음과 같다.

① CE를 LOW로 만든다.
② 플래시 메모리 지우기 명령 0x60을 써넣는다.
③ 블럭 주소를 써넣는다.
④ 블럭 주소 설정이 되도록 잠깐 대기한다.
⑤ 플래시 메모리 지우기 시작 명령 0xD0을 써넣는다.
⑥ 지우기 처리가 종료되었는가를 확인하기 위해 상태 명령 0x70을 써넣는다.
⑦ 상태 값을 읽어서 작업 중인가를 확인한다.
⑧ 만약 작업 중이라면 쨄 이후 과정을 반복한다.
⑨ 종료되었을 때 지우기가 되었는가를 확인한다.
⑩ 종료되었을 때 프로텍션 에러인가를 확인한다.
⑪ CE를 HIGH로 만든다.



이와 같은 처리를 위해서 이지부트는 NAND_EraseBlock 함수(<리스트 3>)을 제공하고 있다.
NAND_EraseBlock 함수는 NAND 플래시를 지우기 위해서 TNandInfo 구조체 정보 NandDev와 지워야 할 블럭 번호 Block Number 매개 변수를 전달받는다. NandDev는 NAND 플래시의 베이스 주소와 삭제 크기 단위를 알기 위해서 전달받는 매개 변수이다.
<리스트 3>의 ① 루틴은 전달된 지우기 블럭 번호를 NAND 플래시에 써넣는 구조로 만들기 위해서 왼쪽으로 5비트 이동시킨다. 이렇게 하면 앞에서 설명했던 블럭을 지우기 위한 블럭 어드레스 값이 된다. ②~③이 블럭을 지우기 위한 준비 과정이다. 가장 먼저 Dummy = NAND_ACCESS_START; 함수를 수행하여 NAND 플래시를 접근 가능하게 만든다. 그리고 삭제 준비를 요청하는 NAND_CMD = NAND_CMD_ERASE_SETUP; 명령을 써넣는다. 이후 블럭 어드레스를 다음과 같이 가장 뒷자리부터 차례로 써넣는다.

NAND_ADDR = (BlockAddr & 0xFF); // Block Page Address L
NAND_ADDR = ((BlockAddr>>8) & 0xFF); // Block Page Address M
if( NandDev->TotalSize >= 64*1024*1024 ) // Block Page Address H
{
NAND_ADDR = ((BlockAddr>>16) & 0xFF);
}

마지막에 써넣는 어드레스가 조건문을 거치는 이유는 크기에 따라서 64MB 이상의 플래시일 경우에 1바이트 처리를 더 해줘야 하기 때문이다. 어드레스를 쓰고 난 이후에는 아주 작은 시간동안 대기를 해줘야 한다. 얼마나 대기해 주어야 하는가는 NAND 플래시의 매뉴얼에 나와 있는데 32비트 프로세서에서는 이 값을 정확하게 연산하기 곤란하므로 적당한 루프를 돌리는 구문을 만들어서 시험하면서 맞추어야 한다. 이렇게 어드레스를 설정하고 난 이후에 대기를 처리하는 함수가 <리스트 3>의 ④루틴인 NAND_AddressSetupWait(); 함수이다. 이 함수는 특별히 하는 일은 없고 for 문장을 일정 횟수 반복한다.
블럭 어드레스가 써넣어진 이후에는 실제로 블럭을 지우도록 명령을 써넣어야 한다. ⑤루틴이 이를 수행한다. 블럭을 지우도록 하는 명령을 써넣으면 NAND 플래시는 내부의 플래시 메모리 블럭을 지우기 시작한다. 블럭을 지우는 시간은 32비트 프로세서 입장에서 매우 긴 시간이 된다. 그래서 플래시 메모리에서 상태 값을 읽어들여서 요구된 작업이 모두 끝났는지 확인하여야 한다.
상태 값을 읽는 것 역시 명령을 써넣어야 읽을 수 있는데, ⑥루틴의 wrhile문이 이를 수행한다. 원칙적으로는 지정된 시간이 지나면 시간 초과를 처리해야 하는데, 이지부트에서는 이런 처리를 수행하지 않고 있다. 대부분의 경우 큰 문제가 없기 때문이다. 리눅스 커널과 같은 운영체제에서는 시간을 감시하므로 상태 값을 요구하는 명령은 부가적인 데이터가 필요 없다. 즉시 데이터를 NAND 플래시에서 읽으면 현재 상태가 된다. 이 상태 값은 include/nand.c에 다음과 같은 형식으로 정의하고 있다.
#define NAND_STATUS_ERASE_OK 0x01 // bit = 0일 경우 지우기 OK
#define NAND_STATUS_PROGRAM_OK 0x01 // bit = 0일 경우 쓰기 OK
#define NAND_STATUS_BUSY 0x40 // bit = 0일 경우 BUSY
#define NAND_STATUS_PROTECTED 0x80 // bit = 0일 경우 LOCK

기본적으로 상태 값이 BUSY 상태를 알리면 NAND 플래시는 작업 중임을 나타낸다. 가장 먼저 이 BUSY가 설정되지 않았는가를 확인하여야 한다. 작업이 끝났다면 우선적으로 플래시의 사용을 종료하기 위해서 CE를 HIGH 상태로 놓는다. 이 처리를 <리스트 3>의 ⑦루틴으로 구현한다.
이후 마지막 상태를 점검하여 정상적으로 작업이 끝났는지 또는 에러가 발생하였는지를 상태 값의 다른 비트를 확인해 처리하여야 한다. ⑧루틴이 이 작업을 수행한다. 원칙상 에러가 발생했을 때 프로텍션에 대한 처리를 해야 하는데 이지부트에서는 이에 대한 처리는 고려하지 않았다.
이런 과정을 끝내면 정상적으로 블럭이 지워진 것이다. 1개의 블럭이 지워지면 32개 페이지의 모든 데이터가 0xFF 값이 된다. 주의할 점은 배드 블럭은 절대로 지우기를 수행하면 안 된다는 것이다(이 부분에 대해서는 배드 블럭을 언급할 때 자세하게 다루겠다).

플래시 메모리 읽기
NAND 플래시 메모리를 읽을 때 대부분의 경우 페이지 단위로 한다. NAND 플래시에서 페이지 안에 있는 세부적인 위치를 지정해서 읽을 수도 있지만 대체로 페이지 단위로 입출력하도록 프로그램하기 때문에 이 연재에서는 페이지 단위에 대해 읽기를 수행하는 것에 한하여 설명한다.
앞서 설명했지만 한 페이지는 3개의 영역으로 구분되어 있는데, 256바이트 크기를 갖는 A, B 영역, 16바이트 크기를 갖는 C 영역이다. 플래시 메모리의 읽기를 구현하기 위해서 지정되는 어드레스 형식은 앞에서 보았듯이 블럭, 페이지, 버퍼를 지정할 수 있다. 문제는 버퍼의 위치를 지정할 수 있는 크기가 1바이트이므로 256 이상은 불가능하다. 그렇다면 A 영역과 B 영역, 그리고 C 영역은 어떻게 지정하는 것일까? 읽기 명령은 3종류로 나누어져 있다. 플래시 메모리에서 읽기 명령은 플래시에서 데이터를 읽겠다고 표현하는 명령이기도 하지만 동시에 A, B, C 영역을 지정하는 명령으로도 사용된다.
그러나 이지부트에서는 A 영역 선택 명령으로 데이터를 읽어오는 방식을 취한다. 왜냐하면 기본적으로 페이지 단위로 데이터를 읽고 써넣는 구조로 이지부트가 설계되어 있고 NAND 플래시 메모리는 A 영역을 초기에 지정한 이후에 계속적으로 데이터를 읽어 오면 자동으로 B 영역과 C 영역의 데이터를 읽어올 수 있기 때문이다. 즉 직접적인 세부 접근이 필요 없기 때문에 가장 처음 위치인 A 영역을 선택하는 명령을 이용한 후 초기 페이지에 대한 주소만 지정하면 이후에 데이터를 읽어올 때마다 NAND 플래시 내부적으로 주소는 1씩 증가하고 A 영역의 256 크기를 넘기면 자동으로 B 영역으로 증가되기 때문이다. 이 증가는 C 영역까지 되고 다른 페이지로도 자동으로 증가된다. 하지만 페이지간의 이동은 이동 시간의 지연 때문에 잘 사용되지 않는다. 하나의 페이지를 읽어오는 과정은 다음과 같다.

① CE를 LOW로 만든다.
② 플래시 메모리 읽기 명령 0x00을 써넣는다.
③ 페이지 주소를 써넣는다.
④ 페이지 주소 설정이 되도록 잠깐 대기한다.
⑤ 데이터를 512바이트 읽어서 버퍼에 저장한다.
⑥ CE를 HIGH로 만든다.





이와 같은 처리를 위해서 이지부트는 NAND_ReadPage 함수(<리스트 4>)를 제공하고 있다. NAND_ReadPage 함수는 NAND 플래시에서 데이터를 읽어와 주어진 버퍼에 전달하기 위해서 TNandInfo 구조체 정보 NandDev와 읽어야 할 페이지 번호 PageNumber, 그리고 버퍼 주소 pBuf 매개 변수를 전달받는다.
NAND 플래시를 읽기 위한 과정도 블럭 지우기 과정과 비슷하다. 읽기 과정은 다른 특별한 과정 없이 읽기 명령과 페이지 주소를 주고 데이터를 읽어오면 끝난다. 가장 먼저 Dummy = NAND_ ACCESS _START; 함수를 수행하여 NAND 플래시를 접근 가능하게 만든다. 그리고 NAND_CMD = NAND_CMD_READ_A; 함수와 같이 읽기 요청 명령을 써넣는다.
다음으로 페이지 어드레스를 가장 뒷자리부터 차례로 써넣는다. 이 때 블럭 어드레스 지정과 달리 버퍼 주소를 가장 먼저 써넣어야 하는데 항상 페이지 전체를 읽는 처리를 하므로 NAND_ADDR = 0x00;과 같이 그냥 0x00 값을 전달한다. 이후 나머지 페이지 주소를 써넣는다.

NAND_ADDR = (PageNumber & 0xFF); // Page Address L
NAND_ADDR = ((PageNumber>>8) & 0xFF); // Page Address M
if( NandDev->TotalSize >= 64*1024*1024 ) // Page Address H
{
NAND_ADDR = ((PageNumber>>16) & 0xFF);
}

마지막에 써넣는 어드레스가 NAND_AddressSetupWait(); 함수와 같은 조건문을 거치는 이유는 블럭 지우기와 같은 이유이다. 또한 블럭 지우기와 마찬가지로 페이지 주소를 써넣은 후에는 아주 작은 시간 동안 대기해 주어야 한다. 블럭 지우기와 다르게 이렇게 명령과 주소를 지정하면 바로 해당 페이지의 내용을 읽을 수 있다. 512번 루프를 돌면서 매개 변수로 전달된 버퍼 주소에 내용을 저장하면 된다. 이 처리를 수행하는 것이 <리스트 4>의 ①루틴이다. 작업이 끝났다면 플래시의 사용을 종료하기 위해서 CE를 HIGH 상태로 놓는다.

플래시 메모리 쓰기
플래시 메모리 쓰기는 읽기 처리와 블럭 지우기를 합쳐 놓은 형태라고 이해하면 된다. 물론 쓰기 단위는 페이지 단위로 하여야 한다. 먼저 쓰기 처리를 위한 순서를 살펴보자.

① CE를 LOW로 만든다.
② 플래시 메모리 읽기 명령으로 써넣을 페이지의 세부 영역을 지정한다. 처음이므로 0x00을 써넣는다.
③ 쓰기 모드를 지정하는 명령 0x80을 써넣는다.
④ 페이지 주소를 써넣는다.
⑤ 페이지 주소가 설정되도록 잠깐 대기한다.
⑥ 데이터를 512바이트 써넣는다.
⑦ 플래시 메모리 쓰기 시작 명령 0x10을 써넣는다.
⑧ 쓰기 처리가 종료되었는지 확인하기 위하여 상태 명령 0x70을 써넣는다.
⑨ 상태 값을 읽어서 작업 중인지 확인한다.
⑩ 만약 작업 중이라면 쨄 이후 과정을 반복한다.
⑪ 종료가 되었을 때 쓰기가 되었는가를 확인한다.
⑫ 종료가 되었을 때 프로텍션 에러인가를 확인한다.
⑬ CE를 HIGH로 만든다.



이와 같은 처리를 위해서 이지부트는 NAND_WritePage 함수를 <리스트 5>와 같이 제공하고 있다.
NAND_WritePage 함수는 주어진 버퍼의 내용을 NAND 플래시에 써넣기 위해서 TNandInfo 구조체 정보 NandDev와 써넣어야 할 페이지 번호 PageNumber, 그리고 버퍼 주소 pBuf 매개 변수를 전달받는다. NAND 플래시의 쓰기 과정은 페이지 읽기와 블럭 지우기의 각 과정과 동일하므로 자세한 설명은 생략하겠다.

배드 블럭 처리
가끔 필자가 다니는 회사의 제품인 EZ-X5를 구매한 고객 중에서 부트로더에서 NAND의 배드 블럭 수가 나오므로 문제가 있는 것이 아니냐는 질문을 받는다. 그때 필자의 답변은 간단하다. “원래 그런 겁니다.” 삼성에서 나오는 NAND 플래시는 20개 이하의 배드 블럭이 있는 경우 정상으로 취급하여 출하된다(최근에 나오는 제품은 다행스럽게도 배드 블럭이 거의 없다).
따라서 가끔 배드 블럭이 있는 제품이 나오므로 NAND 플래시 메모리를 다루는 프로그래머는 이 배드 블럭에 대한 처리를 하여야 한다. 배드 블럭은 데이터가 써넣어지더라도 잘못된 데이터가 되므로 해당 블럭은 프로그램에서 무시하도록 처리하여야 한다. 이런 배드 블럭 때문에 이지부트에서는 가상 블럭이라는 개념을 도입하고 있다.
일단 어떤 블럭이 배드 블럭인지 확인할 수 있는 방법을 알고 있어야 한다. 배드 블럭의 정보는 해당 블럭의 첫 번째 페이지의 C 영역에 6번째 바이트를 읽어서 0xFF가 나오지 않으면 배드 블럭으로 보아야 한다. 이렇게 배드 블럭으로 판명된 곳에는 지우기와 쓰기를 시도해서는 안 된다. 특히 지우기를 시도하면 이 배드 블럭에 대한 정보도 지워지게 되므로 특히 조심하여야 한다. 특정 블럭이 배드 블럭인가를 확인하기 위해서 이지부트는 NAND_CheckBadBlock 함수를 사용하고 있다. 이 함수는 페이지 읽기 함수인 NAND_ReadPage 함수와 구현 방식이 거의 동일하다. 단지 C 영역의 6번째 바이트를 직접 지정하는 점과 해당 데이터가 0xFF인가를 확인하는 절차가 다를 뿐이다. 이를 구현한 소스는 <리스트 6>과 같다.



<리스트 6>의 ①루틴을 보면 읽기 구현과 다르게 C 영역 읽기를 지정하는 명령 0x50를 써넣고 있다. 이후에 C 영역의 6번째 바이트를 읽기 위해서 버퍼 위치인 0x05값을 ②루틴과 같이 지정한다. 이렇게 한 후 ③루틴을 이용해 한 바이트를 읽는다. 이 바이트 값이 0xFF이 아니면 배드 블럭으로 판정하고 해당 블럭의 상태가 정상이면 ‘O’를, 배드 블럭이면 ‘X’ 값을 NandDev -> BadBlock 배열에 저장한다.
이지부트는 배드 블럭에 대한 검사를 커널 이미지와 램디스크 이미지를 저장하는 공간에 대한 처리만 한다. 왜냐하면 부트로더가 담당하는 공간이기 때문이며, 이 공간 이외의 영역은 리눅스 커널의 MTD와 YAFF에서 담당한다. 만약 부트로더에서 전체에 대한 검사를 수행하려면 이 루틴을 조금 변경해야 문제가 없을 것이다.
NAND 플래시에 배드 블럭이 있다는 가정 하에 데이터를 관리해야 하기 때문에 이지부트는 가상 블럭이라는 개념을 도입하고 있다. 즉 블럭을 지우거나 데이터를 써넣고 읽을 때 실제 블럭 번호를 그대로 사용하지 않고 배드 블럭이 없는 가상 블럭 테이블을 만들어서 이 가상 블럭의 번호를 이용하여 처리한다. 앞에서 설명한 함수들은 모두 실제 블럭을 대상으로 처리하지만 커널 이미지를 써넣거나 램디스크 이미지를 써넣을 때는 가상 블럭을 이용하여 처리한다. 반대로 읽을 때나 지울 때도 가상 블럭을 이용하여 처리한다. 이런 가상 블럭을 만드는 함수가 NAND_ScanBadBlock 함수이다. 이 함수는 <리스트 7>과 같이 구현된다.



이 함수는 이지부트 초기에 NAND 검출 함수에서 호출된다. 이 함수는 NAND_CheckBadBlock 함수를 이용하여 TNandInfo 구조체의 BadBlock 필드 배열 변수에 문제가 되는 배드 블럭의 상태 값을 저장한다. 예를 들어 세번째 블럭이 배드 블럭이면 NandDev->BadBlock[2]에는 ‘X’ 값이 저장된다. 이런 처리를 수행하는 것이 <리스트 7>의 ①루틴이다.
이렇게 구해진 배드 블럭 상태를 이용하여 TNandInfo 구조체의 VirBlock 필드 배열 변수에 사용 가능한 실제 블럭 번호를 저장한다. 이렇게 가상 블럭 테이블을 만드는 것이 ②루틴이다. 이 처리 과정을 거쳤을 때 다음과 같이 정보들이 저장된다.
NandDev -> VirBlock 블럭에서 실제로 사용 가능한 물리적인 블럭 값을 구하여 처리한다. 예를 들어 <표 2>의 테이블과 같은 상태로 저장되어 있다면 5번 블럭에 접근하면 실제로는 6번 블럭에 접근하게 된다.



파티션 처리
이지부트는 리눅스를 동작시키기 위해서 만들어진 부트로더이다. 그래서 NAND 플래시에는 커널 이미지와 램디스크 이미지, 그리고 사용자가 정의할 수 있는 데이터로 나누어서 처리한다. 이런 처리를 원활하게 하기 위해서 파티션이라는 개념을 사용하고 있다. 물론 리눅스에서 사용하는 그런 거창한 개념은 아니고 단지 NAND의 사용 현황을 알려주는 정보 테이블이다. 이 정보를 관리하는 구조체는 include/nand.h의 TNandPartition에 다음과 같이 선언돼 있다.

typedef struct
{
unsigned char Name[128];
int BaseBlock; // 최초 선두 블럭
int BlockCount; // 점유 블럭 수
} __attribute__ ((packed)) TNandPartition;

이 구조체는 하나의 파티션에 대한 정보를 담고 있는데 실제로 사용되는 것은 BaseBlock과 BlockCount 필드이다. BaseBlock은 파티션이 시작되는 블럭의 주소이고 BlockCount는 해당 파티션이 점유하고 있는 블럭의 예약 크기이다. 실제적인 파티션 정보는 main/nand.c에 다음과 같이 고정적으로 선언되어 있다.

TNandPartition Nand1Partition[NAND_MAX_PATITION] =
{
// NAME START SIZE
{ “Kernel “, 0, 64 }, // 1-MByte Size:0x100000
{ “Ramdisk”, 64, 192 }, // 3-MByte Size:0x300000
{ “Generel”, 256, NAND_PARTITON_REST_SIZE },
{ “” , NAND_PARTITON_END, 0 },
};

또한 각 파티션의 0번 페이지는 정보 영역으로 사용하고 있는데, 현재는 단순하게 해당 이미지의 크기 정보만 담고 있다. 예를 들어 커널 이미지 파티션이면 다운로드한 커널 이미지의 크기 값이다. 이 정보를 관리하는 구조체는 include/nand.h에 TNandPartition 형식으로 다음과 같이 선언돼 있다.

typedef struct
{
unsigned char Name[128];
int BaseBlock; // 최초 선두 블럭
int BlockCount; // 점유 블럭 수
} __attribute__ ((packed)) TNandPartition;

NAND 플래시의 처리 저수준 함수
이지부트는 배드 블럭과 파티션 처리가 되도록 NAND 플래시를 처리하기 위해서 저수준 함수와 상위 수준의 함수로 나누어져 있다. 지금까지 설명한 함수가 모두 실제 하드웨어적인 개념으로 NAND 플래시를 처리하는 저수준 함수들이다. 이 함수들의 목록을 다시 정리하면 다음과 같다.

* BOOL NAND_DETECT( unsigned long BaseAddress , TNandInfo *NandDev , TNandPartition *par )
* BOOL NAND_CheckBadBlock( TNandInfo *NandDev, int BlockNumber )
* BOOL NAND_EraseBlock( TNandInfo *NandDev, int BlockNumber )
* void NAND_ReadPage( TNandInfo *NandDev, int PageNumber, unsigned char *pBuf )
* BOOL NAND_WritePage( TNandInfo *NandDev, int PageNumber, unsigned char *pBuf )

NAND 플래시 섹터 처리 함수
앞에서 설명했듯이 파티션과 배드 블럭을 처리하기 위해서는 저수준 함수들만으로는 곤란하다. 그래서 이지부트에서는 NAND 플래시를 처리하기 위한 개념에서 섹터 처리를 도입했다. 즉 페이지 처리는 저수준 함수들이 하고 NAND 플래시를 실제로 다루기 위해서는 논리적으로 연속적인 장치와 파티션으로 나누어진 섹터 처리 방식을 도입하고 있다. 원리는 하드디스크의 논리적인 섹터 개념과 비슷하다.
이지부트의 섹터 개념은 기본 크기가 512바이트 단위로 각 파티션별로 0부터 시작하는 번호를 갖는다. 예를 들어 첫 번째 파티션의 0번 섹터라면 배드 블럭이 없을 경우 0번 페이지가 된다. 두 번째 파티션이 세 번째 블럭에서 시작하고 2번 페이지가 배드 블럭이라면 두 번째 파티션의 33번 페이지는 네 번째 블럭의 4번 페이지가 된다(지금 설명한 것은 1로 시작하는 순서일 때이다). 이런 변환을 수행하는 함수는 다음과 같다.

* int NAND_GetPageFromSector( TNandInfo *NandDev, int PartitionNumber, int
SectorNumber )

이 함수는 파티션 번호와 섹터 번호를 이용하여 실제로 처리할 수 있는 페이지 번호를 반환한다. 자세한 소스의 소개는 지면 관계상 따로 하지 않겠다. 이와 같이 섹터 단위로 처리하는 함수로는 다음과 같은 것이 있다.

* BOOL NAND_WriteSector( TNandInfo *NandDev, int PartitionNumber, int SectorNumber,
unsigned char *pBuf )
* BOOL NAND_ReadSector( TNandInfo *NandDev, int PartitionNumber, int SectorNumber,
unsigned char *pBuf )

NAND 플래시 파티션 처리 함수
앞에서도 언급했지만 이지부트는 커널 이미지와 램디스크 이미지를 파티션 개념으로 다룬다. 그래서 다운로드된 이미지의 쓰기와 읽기 처리는 파티션 개념으로 한다. 이것이 관리하기 더 편하기 때문이다. 자세한 설명은 하지 않고 사용되는 함수명을 나열하고 간단한 설명만 하겠다.

* BOOL Nand_ErasePartition( int pnb, BOOL NoEraseBadBlock ); // pnb로 전달된 파티션 영역을 지운다. 이때 NoEraseBadBlock이 참이면 배드 블럭은 지우지 않는다.
* int Nand_ProgramPartition(int pnb, unsigned int src, int size ); // pnb로 전달된 파티션 영역에 src가 가리키는 메모리의 내용을 size만큼 써넣는다.
* BOOL Nand_VerifyPartition( int pnb, unsigned int src, int size ); // pnb로 전달된 파티션 영역에 src가 가리키는 메모리의 내용과 size만큼 비교한다.
* int Nand_ReadPartition(int pnb, unsigned int dst, int size ); // pnb로 전달된 파티션 영역의 내용을 src가 가리키는 메모리에 size만큼 써넣는다.

NAND 플래시 이미지 복사 함수
실제로 이지부트에서 커널이 명령을 수행했을 때 처리되는 최상위 함수는 다음과 같다.

* int CopyTo_NandFlash_Kernel( unsigned int src, int size ); // src에 전달되는 주소의 내용을 size 크기 만큼 커널 파티션 영역에 써넣는다.
* int CopyTo_NandFlash_Ramdisk( unsigned int src, int size ); // src에 전달되는 주소의 내용을 size 크기 만큼 램디스크 파티션 영역에 써넣는다.
* int CopyTo_SDRAM_Kernel( unsigned int dst ); // NAND 플래시의 커널 파티션 공간의 내용을 dst가 가리키는 주소에 써넣는다.
* int CopyTo_SDRAM_Ramdisk( unsigned int dst ); // NAND 플래시의 램디스크 파티션 공간의 내용을 dst가 가리키는 주소에 써넣는다.

마치며
참 시간이 빨리 간다. 필자가 나이를 자꾸 먹어가서 그런지 모르지만 세월이 갈수록 점점 빨리 지나가는 느낌이다. 특히 원고 마감이 다가올 때마다 벌써 한달이 지났나 하는 생각을 한다. 벌써 부트로더를 다루기 시작한 지 6개월이 지나가 버렸다. 이젠 남은 내용도 네트워크에 관련된 내용과 커널로의 진입이다. 이 내용을 쓸 시간도 금방 와 버릴 것만 같다. 독자 여러분도 끝이 보이므로 힘내기 바란다. 마지막으로 이지부트로더의 소스를 보고 싶다면 http://www.falinux. com/download/sub2.html을 방문하면 된다. 물론 소스는 공개된 것이고 자신이 어떻게 사용하던 관여하지 않는다. 특히 이번에는 소스의 상위 부분이 생략된 것이 많으므로 필히 방문하여 살펴보기 바란다.

반응형

'Database > Embedded' 카테고리의 다른 글

임베디드 시스템 추천 사이트  (0) 2007.06.11