아이지 시스템 http://www.aijisystem.com 에서 만든 SMDK 2410 보드를 포팅한 과정을 설명하고 있다.
이 보드의 특징이라고 보자면 다음과 같다.
- 2 개의 FLASH(NOR, NAND) 를 가지고 있다. 점퍼 셋팅을 통해 둘 중에 하나를 BOOT FLASH 로 지정할 수 있다.
- 제조사에서 제공되는 부트로더, 커널이미지, 파일시스템이 없다.
보드의 스펙은 다음과 같다.
Processor | S3C2410 (ARM920T) | |
Boot Rom | NOR flash | AMD 8Mbit 1EA (default) |
INTEL Strata 48MB(16MBX3) (option) | ||
NAND flash | SMC 64MB 1 EA | |
Memory | SDRAM 64MB (32MB x 2) | |
LCD | 240 x 320 TFT/STN LCD and Touch Interface | |
UART | 3 ch UART with IrDA | |
USB | Host 1ch, Host/ Device 1ch | |
Ethernet | 1 ch | |
PCMCIA | 1 ch | |
SMC/SD host (MMC) Interface | Yes | |
RTC/ IIC/ IIS/ SPI Interface | Yes | |
ADC/EINT/Extension Interface | Yes | |
JTAG port | Yes |
호스트 PC 설정하기
본격적인 타겟보드의 설정에 앞서 간단하게 나마 호스트 PC 의 설정에 대해서 알아보기로 한다.
호스트 PC 는 멀티부팅을 사용하고 있다. 각각 debian 과 windows 2003 을 사용하고 있다. windows 에서는 vmware 를 이용해서 redhat8 을 기준으로 컴파일 했다.
OS | 버전 | 용도 |
DEBIAN | EACH | tftp, dhcp 서버 |
WINDOWS | 2003 | 컴파일 및 2410 test program 을 통한 파일 업로드 & flash write |
이번에는 호스트 PC 와 타겟보드의 네트워크 환경에 대해서 알아보자.
호스트 PC | 166.104.144.101(eth0), 192.168.10.1(eth1) |
타겟 보드 | 192.168.10.81(eth0) |
호스트 PC 의 eth1 과 타겟보드 eth0 이 크로스 케이블로 연결되어 있다. 호스트 PC 에서의 설정은 개발환경_구축하기 을 참고하기 바란다.
툴 체인 설치하기
가장 애를 먹었던 부분이 바로 툴 체인 이었다. 아이러니하게도, smdk2410 을 판매한 아이지시스템에서는 툴체인을 제공하지 않는다. 때문에 나 나름대로 인터넷을 뒤져서 arm 계열의 툴체인들을 설치하고 지우기를 반복했다. 가장 흔했던 문제는 다음과 같다.
- u-boot 컴파일 도중 에러
- kernel 컴파일 도중 에러
- 커널 이미지를 타겟보드의 메모리에 올리고 실행하는 도중에 CRC 에러
처음에는 단순히 소스파일의 문제인 줄만 알고 코드를 이리저리 수정해봤다. 하지만, 마침내 제대로 컴파일되는 툴체인을 찾았기 때문에 문제는 결국 툴체인에 있었다고 결론 내릴 수 있었다. 툴체인은 일일이 소스 파일을 컴파일해서 만드는 방법이 가장 좋다고 생각되지만, 실제로 만들어봤지만, CRC 에러가 발생했다. 여기서는 ELDK 라고 하는 이미 만들어져 있는 툴체인을 사용할 것이다.
다운로드 주소는 다음과 같다. http://www.denx.de/wiki/DULG/ELDKAvailability
현재 최신버전은 4.0 이다. 하지만, 사용해본 결과 컴파일시에 에러가 발생했다. 아마도 컴파일러 버전이 너무 높아서 인 듯하다.
여기서는 2.1.0 을 사용한다. http://mirror.switch.ch/ftp/mirror/eldk/eldk/2.1.0/eldk-arm-linux-x86/ 아래의 디렉토리와 파일을 모두 다운로드받는다.
리눅스에서는 ncftp 를 사용해서 편리하게 받을 수 있다.
#ncftp ftp://mirror.switch.ch/mirror/eldk/eldk/ ncftp /mirror/eldk/eldk > cd 2.1.0/ ncftp /mirror/eldk/eldk/2.1.0 > get -R eldk-arm-linux-x86 ... ncftp /mirror/eldk/eldk/2.1.0 > bye
#for file in tools/bin/rpm tools/usr/lib/rpm/rpmd install ELDK_MAKEDEV ELDK_FIXOWNER ; do /bin/chmod +x ../eldk-arm-linux-x86/$file; done
이렇게 하면, 설치를 위한 모든 준비가 끝난셈이다. 설치하기 위해 다음을 실행한다.
./install -d /usr/local/arm # 설치디렉토리는 /usr/local/arm
설치가 끝나면, PATH 를 걸어준다. 컴파일러는 'arm_920TDI-gcc' 이다.
부트로더 만들기
여기서는 u-boot 를 사용하도록 한다. http://sourceforge.net/projects/u-boot 에서 받는다. 현재 최신 버전은 1.1.4 이다. 이것을 사용할 것이다. 참고로 SMDK 2410 보드의 경우, 운 좋게도(?) u-boot 에서 기본으로 지원하고 있다. 그래서 별다른 설정파일의 수정없이 그대로 컴파일하면 된다.
압축을 풀고, u-boot-1.0.0 디렉토리에 들어가서 Makefile 을 수정해야 한다. 약 52 줄쯤에 아래와 같은 코드가 있다.
... CROSS_COMPILE = arm_920TDI- ## 추가 ifndef CROSS_COMPILE ifeq ($(HOSTARCH),ppc) CROSS_COMPILE = else ifeq ($(ARCH),ppc) CROSS_COMPILE = ppc_8xx- endif ifeq ($(ARCH),arm) CROSS_COMPILE = arm-linux- endif ifeq ($(ARCH),i386) ifeq ($(HOSTARCH),i386) CROSS_COMPILE = else CROSS_COMPILE = i386-linux- endif endif ifeq ($(ARCH),mips) CROSS_COMPILE = mips_4KC- endif endif endif export CROSS_COMPILE ...
위와 같이 추가해준다. 이제 컴파일을 할 차례다.
#make clobber #make smdk2410_config #make
만일 컴파일 도중에
cc1 : error : invalid option 'abi=apcs-gnu'
와 같은 에러가 뜬다면, cpu/arm920t/config.mk 파일을 열어서 '-mabi=apcs-gnu' 라는 부분을 지운다.
또한 컴파일 도중에
flash.c:75: 'PHYS_FLASH_2' undeclared (first use in this function)
위와 같은 에러가 발생한다면, 해당 smdk2410/flash.c 파일의 75 번째 줄의 'case 1' 문의 break 만 남기고 모두 삭제한다.
u-boot 버전 1.1.5 이상부터 컴파일 에러가 발생한다. 에러 메세지는 다음과 같다.
arm_920TDI-gcc : bzlib.o : No such file or directory arm_920TDI-gcc : unrecgnized option '-MQ' ...
해결방법은 높은 버전의 gcc 를 가진 툴 체인을 사용하는 것이다. 앞에서 설치한 툴 체인의 경우, gcc 의 버전이 2.95.3 이다. 하지만, gcc 3.3 대 버전의 툴 체인(aesop)을 사용해서 컴파일한 결과, 정상적으로 컴파일 되었다.
에러없이 컴파일 되었다면, u-boot.bin 이란 파일이 생성되었을 것이다. 이제 직접 FLASH 에 써줄 차례다. 제조사에서 제공하는 테스트 프로그램을 이용해서 write 할 것이다. '87. NOR flash Program → b : 28F128J3A(16MB) x 2 → y' 을 선택한다. 시리얼 포트로 앞에서 만든 u-boot.bin 파일을 전송한다.
주소 번지는 '0x0' 로 입력하면, write 가 된다. 에러없이 마무리 되었다면, 점퍼 셋팅을 하고 부팅을 해보자!!
부트로더 메세지가 뜬다면 성공이다.
u-boot 의 경우, 명령어 'bdinfo' 를 치면 기본적으로 가지고 있는 환경 변수들이 출력된다. 나중에 커널과 램디스크를 다운로드 하기 위해서는 ethaddr 와 ipaddr 환경 변수가 세팅되어 있어야 한다. 물론 'setenv' 명령어로 지정해 줄 수는 있지만, 매번 지정해주기는 불편하다.
그래서 미리 정의해줄 수가 있다. 바로 컴파일 하기전에 include/configs/smdk2410.h 파일을 열어서 몇 가지 변수들을 추가해주면 된다. 또한 정의된 변수들을 flash 에 write 하기 위해서는 flash 에 대한 설정도 필요하다.
#define CONFIG_BOOTDELAY 3 /*#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600" */ #define CONFIG_ETHADDR 11:22:33:44:55:66 // MAC 주소 지정 #define CONFIG_NETMASK 255.255.255.0 #define CONFIG_IPADDR 192.168.10.81 // IP 주소 지정 #define CONFIG_SERVERIP 192.168.10.1 // 서버 IP 주소 지정 /*#define CONFIG_BOOTFILE "elinos-lart" */ /*#define CONFIG_BOOTCOMMAND "tftp; bootm" */ ... #define CFG_LOAD_ADDR 0x33000000 /* default load address */ ... /*----------------------------------------------------------------------- * FLASH and environment organization */ #define PHYS_FLASH_SIZE 0x02000000 /* 32 MB */ #define PHYS_FLASH_BANK_SIZE 0x02000000 /* 32 MB Banks */ #define PHYS_FLASH_SECT_SIZE 0x00040000 /* 256 KB sectors (x2) */ #define CFG_FLASH_BASE PHYS_FLASH_1 /* * FLASH and environment organization */ #define CFG_MAX_FLASH_BANKS 1 /* max number of memory banks */ #define CFG_MAX_FLASH_SECT 128 /* max number of sectors on one chip */ /* timeout values are in ticks */ #define CFG_FLASH_ERASE_TOUT (25*CFG_HZ) /* Timeout for Flash Erase */ #define CFG_FLASH_WRITE_TOUT (25*CFG_HZ) /* Timeout for Flash Write */ #define CFG_ENV_IS_IN_FLASH 1 #define CFG_ENV_ADDR (PHYS_FLASH_1 + 0x40000) /* Addr of Environment Sector, 1 sector boundary*/ #define CFG_ENV_SIZE 0x40000 /* Total Size of Environment Sector, 256k */ #define CFG_ENV_SECT_SIZE 0x40000 /* Size of the Environment Sector, 128k x 2 = 256k */
컴파일한 후에, FLASH 에 write 후에 보면, 위의 설정이 적용되었음을 알 수 있다.
커널 이미지 만들기
여기서는 2.4.18 커널을 사용할 것이다. 몇가지 패치들이 필요한 데, 다음은 커널이미지를 만드는 데 필요한 파일들이다.
- linux-2.4.18.tar.gz
- patch-2.4.18-rmk7
- patch-2[1].4.18-rmk7-swl8a
먼저 커널의 압축을 풀어 /usr/src 아래에 복사한다. 커널 디렉토리 아래에 두개의 패치 파일을 복사한다.
#cd /usr/src #patch -p1 < patch-2.4.18-rmk7 #patch -p1 < patch-2.4.18-rmk7-swl8a #make menuconfig
나오는 메뉴에서 'Load an Alternate Configuration File' 를 선택해서 'arch/arm/def-configs/s3c2410ramdisk' 을 입력한다. 그리고 빠져나온다. Makefile 을 아래와 같이 수정해야 한다.
CROSS_COMPILE = arm_920TDI- ## 추가 ... ... ... Version: dummy @rm -f include/linux/compile.h uImage: vmlinux ## 추가 $(OBJCOPY) -S -O binary vmlinux vmlinux.bin gzip -vf9 vmlinux.bin mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 \ -e 0x30008000 -n 'ARM Linux-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)' \ -d ./vmlinux.bin.gz $@ cp $@ /tftpboot boot: vmlinux @$(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C arch/$(ARCH)/boot
위에서 추가해준 것은 커널이 로딩되었을때의 메모리상의 주소를 지정해주는 것이다. Makefile 를 수정하였으면, 커널이미지를 만든다.
#make dep #make uImage
오류없이 컴파일 되었다면, /tftpboot 아래에 uImage 파일이 생성되었을 것이다.
램 디스크 만들기
여기서는 이미 만들어져 있는 램디스크 파일(ramdisk.gz)을 사용할 것이다.
메모리상에 커널과 파일시스템 올리기
여기서는 호스트 PC 에서의 tftp 설정에 대해서는 설명하지 않겠다. 모두 정상적으로 동작한다는 가정하에서 설명할 것이다.
SMDK2410 # tftp 33000000 uImage // 메모리의 0x33000000 번지에 커널이미지를 올린다. SMDK2410 # tftp 30800000 ramdisk.gz // 메모리의 0x30800000 번지에 램디스크를 올린다. SMDK2410 # bootm 33000000 // 메모리의 0x33000000 번지로 부팅한다.
로그인 프롬프트가 보인다면, 성공이다.
커널과 램디스크 FLASH 에 저장하기
하지만 여기서 끝이 아니다. 현재는 커널과 램디스크가 램 상에 올라가 있기 때문에, 전원이 꺼질경우에는 위의 작업을 반복해주어야 한다.
이런 수고를 덜기위해, 커널과 램디스크를 flash 에 저장해야 한다.
앞에서도 설명했듯이 현재 flash(nor 기준) 는 총 128 개의 block 으로 되어 있다. 여기서 0 번은 U-BOOT(u-boot.bin) 가 저장되어 있고, 1번은 u-boot 에서 사용하는 환경변수값들이 저장되어 있다. 그러므로 0 ~ 1 번을 제외한 block 는 사용자 마음대로 쓸 수 있다.
우선, 커널 이미지(uImage) 부터 쓸 것이다. 내가 만든 uImage 의 경우, 용량이 0x987a0 이다. 약 3개의 block 를 필요로 하는 용량이다. 2 ~ 4 번의 block 에 커널을 쓸 것이다.
먼저 write 를 하기전에, protect 를 풀어주고, 해당 block 을 erase 해야 한다. 또한 write 후에는 protect 를 다시 걸어주어야 한다. 아래와 같은 명령어로 커널이미지를 flash 에 쓸 수 있다.
SMDK2410 # tftp 33000000 uImage // 다운로드 하고 나서 파일의 용량이 표시된다. HEX 값을 잘 알아두어야 한다. SMDK2410 # protect off 1:2-4 SMDK2410 # erase 1:2-4 ... ... ... SMDK2410 # cp.b 33000000 00080000 0x987a0 // 여기서는 33000000 번지에 업로드한 커널 이미지(uImage) 를 2 번 block(0x00080000) 에서 부터 0x987a0 만큼 write SMDK2410 # protect on 1:2-4
타겟보드 재부팅을 한 후에,
SMDK2410 # bootm 80000
커널이 부팅이 될 것이다. 아직 램 디스크를 올리지 않았기 때문에 커널 패닉이 발생한다. 이번에는 램디스크를 올릴 차례다. ramdisk.gz 파일을 바로 flash 에 올리는 것이 아니라, hex 파일의 형태로 변환시켜야 한다.
#mkimage -A arm -O linux -T ramdisk -C gzip -d ramdisk.gz ramdisk.fat
에러없이 수행되었다면, ramdisk.fat 이라는 파일이 생겼을 것이다. 이 파일을 울트라에디터로 열어보면, hex 코드라는 것을 알 수 있다.
이제 이 파일을 flash 에 write 해보자! 앞에서 커널을 4 번째 block 까지 사용했기 때문에 여기서는 5 번째 block 부터 사용한다. 램디스크의 경우, 용량이 커널에 비해 크기 때문에 여기서는 넉넉하게 5 ~ 100 block 를 사용하기로 했다.
SMDK2410 # tftp 30800000 ramdisk.fat SMDK2410 # protect off 1:5-100 SMDK2410 # erase 1:5-100 SMDK2410 # cp.b 30800000 00140000 0x2e817e // 00140000 번지는 5 번째 block 이다. SMDK2410 # protect on 1:5-100
도중에 에러없이 수행되었다면, 성공이다. 이제 보드를 재부팅하고, 부트로더 프롬프트에서
SMDK2410 # bootm 80000 140000 // 0x80000 은 커널이 저장된 주소이고, 140000 은 램디스크가 저장된 주소이다.
위와 같이 입력한다.
자동 부트 기능 추가하기
부트로더 프롬프트에다가 매번 명령어를 입력하기는 매우 번거로운 일이다. 그래서 부팅 후, 몇 초 동안 아무런 입력이 들어오지 않으면 자동으로 임의의 명령어가 실행되도록 할 수 있다.
SMDK2410 # setenv bootdelay 5 /* 5 초를 기다린다 */ SMDK2410 # setenv bootcmd 'bootm 80000 140000' /* 5 초 후에 자동으로 실행할 명령어 */ SMDK2410 # saveenv /* 앞의 설정을 Flash 에 저장 */
이제 보드를 재부팅 해보자!!
주의할 것들
위의 실행결과, 커널 패닉이 발생할 것이다. 이유는 커널에서 램디스크를 찾지못하기 때문이다. 이 부분을 생각해보자.
분명히 앞에서 나는 각기 다른 block 에 커널 이미지와 램디스크를 write 했다. 또한 부팅시에 각각의 시작 주소를 지정했다. 하지만, 커널에서는 램디스크를 찾지못했다.
프로그램은 flash 가 아닌 RAM 상에 로딩된 상태에서 실행된다. 커널은 0x30800000 에서 램디스크를 찾는다. 이 주소 역시 커널 컴파일시에 지정해줄 수 있다. 앞에서 커널 패치를 했다면, 커널 소스의 include/asm-arm/arch-s3c2410 아래의 S3C2410.h 에서
/* keywoard : Phy2Vir */ #define S3C2410_MEM_SIZE (64*1024*1024) #define MEM_SIZE S3C2410_MEM_SIZE /* Used in arm/kernel/setup.c */ /* used in asm/kernel/setup.c and asm/arch/arch.c */ #define PA_SDRAM_BASE 0x30000000 /* used in asm/arch/arch.c */ #define RAMDISK_DN_ADDR 0x30800000 /* used in asm/arch/arch.c */ <<<----- 바로 여기다!! #define ZIP_RAMDISK_SIZE (10*1024*1024) /* used in asm/arch/arch.c */ /* if CONFIG_BLK_DEV_RAM_SIZE not defined */ #define BLK_DEV_RAM_SIZE (8*1024*1024)
'RAMDISK_DN_ADDR' 이 바로 그것이다.
아무튼 여기서는 0x30800000 에서 램디스크를 찾도록 컴파일 되어졌다.
그래서 여기서는 flash 에 저장되어 있는 램디스크를 부팅시에 커널이 찾는 0x30800000 번지로 복사해주어야 한다.
u-boot 에서 소스코드 lib_arm/armlinux.c 파일을 아래와 같이 수정한다.
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], ulong addr, ulong *len_ptr, int verify) { DECLARE_GLOBAL_DATA_PTR; ulong len = 0, checksum; ulong initrd_start, initrd_end; ulong data; void (*theKernel)(int zero, int arch, uint params); image_header_t *hdr = &header; bd_t *bd = gd->bd; #ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); #endif theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); /* * Check if there is an initrd image */ if (argc >= 3) { SHOW_BOOT_PROGRESS (9); addr = simple_strtoul (argv[2], NULL, 16); printf ("## Loading Ramdisk Image at %08lx ...\n", addr); // 0x140000 /* Copy header so we can blank CRC field for re-calculation */ /* #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash (addr)) { read_dataflash (addr, sizeof (image_header_t), (char *) &header); } else #endif */ memcpy (&header, (char *) addr, sizeof (image_header_t)); if (ntohl (hdr->ih_magic) != IH_MAGIC) { printf ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-10); do_reset (cmdtp, flag, argc, argv); } data = (ulong) & header; len = sizeof (image_header_t); checksum = ntohl (hdr->ih_hcrc); hdr->ih_hcrc = 0; if (crc32 (0, (char *) data, len) != checksum) { printf ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-11); do_reset (cmdtp, flag, argc, argv); } SHOW_BOOT_PROGRESS (10); print_image_hdr (hdr); /* 앞에서 ramdisk.fat 생성시에 mkimage 를 이용해서 만든 커널 헤더를 읽어서 램디스크 정보 출력 */ data = addr + sizeof (image_header_t); len = ntohl (hdr->ih_size); #ifdef CONFIG_HAS_DATAFLASH /* 실행 안됨 */ if (addr_dataflash (addr)) { read_dataflash (data, len, (char *) CFG_LOAD_ADDR); data = CFG_LOAD_ADDR; } #endif memcpy((char *)0x30800000, (char *)data, len); // 추가할 부분 (data : 0x140040, len : 0x34ec24) if (verify) { ulong csum = 0; printf (" Verifying Checksum ... "); csum = crc32 (0, (char *) data, len); if (csum != ntohl (hdr->ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-12); do_reset (cmdtp, flag, argc, argv); } printf ("OK\n"); } SHOW_BOOT_PROGRESS (11); if ((hdr->ih_os != IH_OS_LINUX) || (hdr->ih_arch != IH_CPU_ARM) || (hdr->ih_type != IH_TYPE_RAMDISK)) { printf ("No Linux ARM Ramdisk Image\n"); SHOW_BOOT_PROGRESS (-13); do_reset (cmdtp, flag, argc, argv); }
위의 함수 do_bootm_linux 는 bootm 명령어를 실행할 때, 호출되는 함수이다. 만일 'bootm 80000 140000' 라고 입력했을때, 위에서 추가해준 부분에 인자 값이 들어간다. 그래서 결국 0x00140000 에 저장되어 있는 램디스크를 0x30800000 으로 복사하는 루틴이 추가되었다. 여기서 data 의 값이 0x140000 이 아닌 0x140040 이 들어간 이유는 처음에는 이미지 헤더 구조체가 차지하는 용량이 0x40 이기 때문이다.
이로써, smdk2410 보드 자유롭게 커널과 램디스크를 올려보고, 부팅할 수 있게 되었다.