vpos 를 gcc-3.3.4 로 포팅하는 방법과 포팅할 때 고려해야할 것들에 대해서 설명한다.
기본적으로 vpos 는 2.95.3 에서 컴파일 되었고, 그 상위 컴파일러로 컴파일 시에 어셈 파일(.S) 에서 문법 오류가 발생했다. 문법오류를 고치더라도, 쓰레드를 생성하는 도중에 시스템이 죽어버리는 문제가 있었다.
이번에 gcc-3.3.4 로 포팅하면서, 컴파일러에 대한 특성과 이에 대한 문제점들에 대해 알 수 있다.
현재 새롭게 포팅한 vpos 로 divx 까지 확인한 상태다.
먼저 포팅을 했던 환경 설정에 대해서 설명하겠다.
준비 운동 하기
컴파일러는 http://www.falinux.com FALINUX 와 http://www.aesop-embedded.org AESOP 에서 제공하는 컴파일러를 사용했다. 둘다 같은 gcc 버전을 가지고 있으면서, 둘 중에 어느 것으로 컴파일해도 문제가 없다. 각 툴체인에 대한 설치와 설정은 생략하기로 한다.
포팅할 vpos 소스코드는 나중에 테스트를 위해 최근 버전인 divx 가 포함된 것으로 한다. 이제부터는 포팅하면서 수정한 사항들을 중점으로 설명하겠다.
수정 사항
HAL_arch_startup.S 수정
vpos 의 압축을 풀고 컴파일을 한다.
#cd /opt #tar xzf vpos.tar.gz #cd vpos #make
아래와 같은 에러가 발생한다.
arm-linux-gcc -c -o HAL_arch_startup.o HAL_arch_startup.S HAL_arch_startup.S: Assembler messages: HAL_arch_startup.S:118: Error: invalid pseudo operation -- `str sp,=vk_save_swi_mode_stack_ptr' HAL_arch_startup.S:119: Warning: writeback of base register is UNPREDICTABLE HAL_arch_startup.S:125: Error: invalid pseudo operation -- `str sp,=vk_save_swi_current_tcb_bottom' HAL_arch_startup.S:132: Warning: writeback of base register is UNPREDICTABLE HAL_arch_startup.S:138: Error: invalid pseudo operation -- `str sp,=vk_save_irq_mode_stack_ptr' HAL_arch_startup.S:139: Warning: writeback of base register is UNPREDICTABLE HAL_arch_startup.S:142: Error: invalid pseudo operation -- `str sp,=vk_save_irq_current_tcb_bottom' HAL_arch_startup.S:148: Warning: writeback of base register is UNPREDICTABLE HAL_arch_startup.S:159: Warning: writeback of base register is UNPREDICTABLE make[1]: *** [HAL_arch_startup.o] Error 1 make[1]: Leaving directory `/opt/vpos/hal/cpu' make: *** [all] Error 2
HAL_arch_startup.S 파일을 아래와 같이 수정한다. 여기서 숫자는 라인넘버를 뜻한다.
vh_software_interrupt: vh_entering_swi: str sp, vk_save_swi_mode_stack_ptr // 수정 stmfd sp!, {r0-r14}^ mrs r0, spsr_all stmfd sp!, {r0, lr} mrs r0, cpsr_all orr r0, r0, #0x80 msr cpsr_all, r0 str sp, vk_save_swi_current_tcb_bottom // 수정 ldr r0, [sp, #8] bl vk_swi_classifier vh_leaving_swi: ldmfd sp!, {r0, lr} // msr spsr_all, r0 ldmfd sp!, {r0-r14}^ movs pc, lr vh_irq: vh_entering_interrupt: sub lr, lr, #4 str sp, vk_save_irq_mode_stack_ptr // 수정 stmfd sp!, {r0-r14}^ mrs r0, spsr_all stmfd sp!, {r0, lr} str sp, vk_save_irq_current_tcb_bottom // 수정 bl vh_hwi_classifier vh_leaving_interrupt: ldmfd sp!, {r0, lr} msr spsr_all, r0 ldmfd sp!, {r0-r14}^ movs pc, lr vh_restore_thread_ctx: mrs r1, cpsr_all bic r1, r1, #0xfffffff0 mov r2, #0x2 cmps r1, r2 mov sp, r0 ldmfd sp!, {r0, lr} msr spsr_all, r0 ldmfd sp!, {r0-r14}^ ldreq sp, vk_save_irq_mode_stack_ptr // 수정 ldrne sp, vk_save_swi_mode_stack_ptr // 수정 movs pc, lr vh_save_thread_ctx: mrs r1, cpsr_all bic r1, r1, #0xfffffff0 mov r2, #0x2 cmps r1, r2 ldreq r2, vk_save_irq_current_tcb_bottom // 수정 ldrne r2, vk_save_swi_current_tcb_bottom // 수정 .rept 17 ldr r1, [r2], #4 str r1, [r0], #4 .endr mov pc, lr vk_save_swi_mode_stack_ptr: ------ 1 .long 0 vk_save_swi_current_tcb_bottom: ------ 2 .long 0 vk_save_irq_mode_stack_ptr: ------ 3 .long 0 vk_save_irq_current_tcb_bottom: -------- 4 .long 0
위의 수정한 코드를 보면, 기본의 코드에서 모두 '=' 를 제거한 것을 알 수 있다.
수정의 이유
왜 저렇게 코드를 수정하는지 궁금할 것이다.
예전의 경우, 컴파일 에러가 발생하는 부분의 '=' 만 제거를 했기 때문에 결과적으로 컨텍스트 스위칭이 제대로 일어나지 않아서 abort 가 발생하는 문제가 있었다.
코드의 아래 부분에 보이는 1 ~ 4 까지는 컨텍스트 스위칭이 발생해서 진입한 모드에서의 가장 처음 sp 와 가장 마지막으로 저장한 tcb 의 주소를 저장하는 곳이다. 이 주소에 저장된 값들은 컨텍스트 스위칭이 끝나고 돌아오거나 다시 재진입할 때, 이 값을 읽어들어서 저장하거나 복구한다. 따라서 이 값들이 제대로 설정되어 있지 않으면 컨텍스트 스위칭에 문제가 생기게 된다. 이것은 t32 같은 툴들을 통해서 디스어셈블 해서 보는 것이 훨씬 이해하기 편하다. 나중에 수정할 때를 대비해서 아래의 표를 추가한다. 총 4 개의 주소를 사용한다.
vh_entering_swi | 0x30000204 |
0x30000208 | |
vh_entering_interrupt | 0x3000020c |
0x30000210 | |
vh_restore_thread_ctx | 0x3000020c |
0x300000204 | |
vh_save_thread_ctx | 0x30000210 |
0x300000208 |
T32 로 본 각 주소 영역에 정보이다.
addr/line | code | label | mnemonic |
SR:30000204 | 00000000 | vk_save_swi_mode_stack_ptr | andeq r0,r0,r0 |
SR:30000208 | 00000000 | vk_save_swi_current_tcb_bottom | andeq r0,r0,r0 |
SR:3000020c | 00000000 | vk_save_irq_mode_stack_ptr | andeq r0,r0,r0 |
SR:30000210 | 00000000 | vk_save_irq_current_tcb_bottom | andeq r0,r0,r0 |
숨겨진 이야기
SCV/OS 디버깅일지 에서 언급했듯이, 나는 처음에 저런 방식으로 꽁수(?)를 사용해서 컨텍스트 스위칭을 하게끔 했다. 하지만 여러가지 문제들이 발견되었다.
- printf 문이 t32 에서 실행시에 문자가 깨진다는 점
- vpos_shell 에서 입력문자를 계속해서 출력한다는 점(디버거로 확인결과, uart 루틴이 예상과는 다르게 동작하고 있었음)
- 이외 등등(data abort 발생)
내가 생각해볼때, 미묘한 타이밍 문제인 듯 보였다. 내가 추가한 어셈코드가 기존의 코드보다 몇 라인 더 많았기도 했기 때문에 이에 걸리는 오버헤드 때문인 듯 하다. 내가 생각하는 대로 적어보면, 다음과 같다.
- 컨텍스트 스위칭에 사용하는 타이머의 값 조절
- r3 레지스터의 사용으로 인한 문제
kmalloc.c 수정
메모리를 할당하는 루틴에서 수정해야할 부분이 있다. 2.95.3 버전에서는 전역 변수가 0 으로 초기화 되지만, 3.4.4 버전에서는 0xffffffff 으로 초기화 된다.
void* vk_malloc(unsigned nbytes) { Header *p, *prevp; Header *heap_memory_alloc(void); unsigned nunits; nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1; if ((prevp = freep) == 0xffffffff) { // 수정 /* no free list yet */ base.s.ptr = freep = prevp = &base; base.s.size = 0; } for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) { if (p->s.size >= nunits) { /* big enough */ if (p->s.size == nunits) /* exactly */ prevp->s.ptr = p->s.ptr; else { /* allocate tail end */ p->s.size -= nunits; p += p->s.size; p->s.size = nunits; } freep = prevp; return (void *)(p+1); } if (p == freep) /* wrapped around free list */ if ((p = heap_memory_alloc()) == NULL) return NULL; /* none left */ } }
수정의 이유
아직 이유가 밝혀지진 않았지만, 전역변수를 선언할 때 0 으로 초기화를 시켜도 0xffffffff 으로 초기화되는 현상 때문에, 코드를 수정했다.
flash_write.c 수정
flash write 에 제대로 안되는 문제의 원인은 바로 전역변수가 0xffffffff 으로 초기화되는 문제였다. 그래서 역시 flash_write.c 에 있는 모든 targetOffset 을 모두 지운다.
#include "stdio.h" #include "stdlib.h" #include "printf.h" #include "pthread.h" #include "swi_handler.h" #include "debug.h" #include "linux/string.h" #include "linux/delay.h" #include "serial.h" #include "vh_cpu_hal.h" #include "vh_io_hal.h" // Flash #define _WR(addr,data) *((volatile unsigned int *)(addr))=(unsigned int)data #define _RD(addr) ( *((volatile unsigned int *)(addr)) ) #define _RESET() _WR(targetAddress,0x00ff00ff) #define BIT_ALLMSK (0xffffffff) #define rUTRSTAT0 (*(volatile unsigned *)0x50000010) //UART 0 Tx/Rx status #define RdURXH0() (*(volatile unsigned char *)0x50000024) //////////////////////////////////////////////////////////////////////////// static int Strata_ProgFlash(unsigned int realAddr,unsigned int data); static void Strata_EraseSector(int targetAddr); static int Strata_CheckID(int targetAddr); static int Strata_CheckDevice(int targetAddr); //static int Strata_CheckBlockLock(int targetAddr); static int Strata_BlankCheck(int targetAddr,int targetSize); //static int _WAIT(void); static unsigned int srcAddress; static unsigned int targetOffset=0; // 0 으로 초기화함에도 불구하고 0xffffffff 으로 초기화됨 static unsigned int targetAddress; ㅜ static unsigned int targetSize; static int error_erase=0; static int error_program=0; /////////////////////////////////////////////////////////////////// unsigned int downloadAddress; unsigned int downloadProgramSize; unsigned int downloadImageOK=0; ///////////////////////////////////////////////////////////////////////////////// int Strata_CheckID(int targetAddr) { _WR(targetAddr, 0x00900090); udelay(40); return _RD(targetAddr); } int Strata_CheckDevice(int targetAddr) { _WR(targetAddr, 0x00900090); udelay(40); return _RD(targetAddr+0x4); } #if 0 int Strata_CheckBlockLock(int targetAddr) { _WR(targetAddr, 0x00900090); return _RD(targetAddr+0x8); } #endif void Strata_EraseSector(int Address) { unsigned long ReadStatus; unsigned long bSR5; unsigned long bSR5_2; unsigned long bSR7; unsigned long bSR7_2; _WR(Address, 0x00200020); udelay(100); _WR(Address, 0x00d000d0); udelay(100); _WR(Address, 0x00700070); udelay(100); ReadStatus=_RD(Address); bSR7=ReadStatus & (1<<7); bSR7_2=ReadStatus & (1<<(7+16)); while(!bSR7 | !bSR7_2) { _WR(Address, 0x00700070); udelay(100); ReadStatus=_RD(Address); bSR7=ReadStatus & (1<<7); bSR7_2=ReadStatus & (1<<(7+16)); } _WR(Address, 0x00700070); udelay(100); ReadStatus=_RD(Address); bSR5=ReadStatus & (1<<5); bSR5_2=ReadStatus & (1<<(5+16)); if (bSR5==0 && bSR5_2==0) { printf("Block_%x Erase O.K. \n",Address); } else { printf("Error in Block Erasure!! = 0x%x\n", ReadStatus); _WR(Address, 0x00500050); udelay(100); error_erase=1; } _RESET(); } int Strata_BlankCheck(int targetAddr,int targetSize) { int i,j; for (i=0; i<targetSize; i+=4) { j=*((volatile unsigned int *)(i+targetAddr)); if (j!=0xffffffff) { printf("E : 0x%x = 0x%x\n", (i+targetAddr), j); return 0; } } return 1; } int Strata_ProgFlash(unsigned int realAddr,unsigned int data) { volatile unsigned int *ptargetAddr; unsigned long ReadStatus; unsigned long bSR4; unsigned long bSR4_2; unsigned long bSR7; unsigned long bSR7_2; ptargetAddr = (volatile unsigned int *)realAddr; _WR(realAddr, 0x00400040); udelay(10); *ptargetAddr=data; _WR(realAddr, 0x00700070); udelay(10); ReadStatus=_RD(realAddr); bSR7=ReadStatus & (1<<7); bSR7_2=ReadStatus & (1<<(7+16)); while(!bSR7 || !bSR7_2) { _WR(realAddr, 0x00700070); udelay(5); ReadStatus=_RD(realAddr); bSR7=ReadStatus & (1<<7); bSR7_2=ReadStatus & (1<<(7+16)); } _WR(realAddr, 0x00700070); udelay(10); ReadStatus=_RD(realAddr); bSR4=ReadStatus & (1<<4); bSR4_2=ReadStatus & (1<<(4+16)); if (bSR4==0 && bSR4_2==0) { // printf("Successful Program!!\n"); ; } else { printf("Error Program!!\n"); _WR(realAddr, 0x00500050); udelay(10); error_program=1; } _RESET(); return 0; } void Program28F128J3A (void) { int i,tmp=0; printf("\n[ 28F128J3A Flash Writing Program ]\n\n"); if (!downloadImageOK) { printf("Error! Write image not found!\n"); return; } vh_rINTMSK = vh_Interrupt_All_Disable; targetSize=downloadProgramSize; if(targetSize==0) { printf("\nThe data must be downloaded using ICE or USB from 0x31000000\n"); srcAddress=downloadAddress; } else { srcAddress=downloadAddress+4; } #ifndef MYSEO AGAIN: #endif printf("Source size [0x?] : 0h~%xh\n\n",downloadProgramSize); printf("Source base address = 0x%x\n",srcAddress); printf("Target base address = 0x%x\n",targetAddress); printf("Target offset = 0x%x\n",targetOffset); // 굳이 안 지워도 된다 printf("Target size = 0x%x\n",targetSize); #if 0 // MYSEO tmp = _RD(vh_SRAM_CTRL_REG); tmp = tmp | 0x0406; _WR(vh_SRAM_CTRL_REG, tmp); _RESET(); #endif tmp = Strata_CheckID(targetAddress) & 0xffff; printf(" - Identification check (0x0089) = 0x%04x\n", tmp); if ( tmp != 0x0089 ) // ID number = 0x0089 { printf("Identification check error !!\n"); return; } tmp = Strata_CheckDevice(targetAddress) & 0xffff; printf(" - Device check (0x0018) = 0x%04x\n", tmp); if ( tmp != 0x0018 ) // Device number=0x0018 { printf("Device check error !!\n"); return; } printf("\nErase the sector : 0x%x.\n", targetAddress); for(i=0;i<targetSize;i+=0x20000) { Strata_EraseSector(targetAddress+i); // 수정 } if(!Strata_BlankCheck(targetAddress,targetSize)) // 수정 { #ifdef MYSEO printf("Blank Check Error!!!\n"); return; #else printf("\n\nagain flash write"); goto AGAIN; #endif } printf("\nStart of the data writing...\n"); for (i=0; i<targetSize; i+=4) { Strata_ProgFlash(i+targetAddress, *((unsigned int *)(srcAddress+i))); // 수정 if(i%0x10000==0xfffc) printf("[0x%x]",(i+4)/0x10000); } printf("\nEnd of the data writing \n"); _RESET(); udelay(10); printf("Verifying Start...\n"); for (i=0; i<targetSize; i+=4) { if (*((unsigned int *)(i+targetAddress)) !=*((unsigned int *)(srcAddress+i))) // 수정 { printf("verify error src %08x = %08x\n", srcAddress+i, *((unsigned int *)(srcAddress+i))); printf("verify error des %08x = %08x\n", i+targetAddress, *((unsigned int *)(i+targetAddress))); // 수정 #ifdef MYSEO { int in; printf("\n\nagain flash write (y/n)? "); in = getc(); printf("\n\n"); if (in == 'y') goto AGAIN; } #else printf("\n\nagain flash write"); goto AGAIN; #endif return; } } printf("Verifying End!!!"); } //////////////////////////////////////////////////////////////////////////// static int DownloadData(void) { int i,tmp; unsigned short checkSum=0,dnCS; unsigned int fileSize=10; unsigned char *downPt; downPt=(unsigned char *)downloadAddress; printf("\nDownload Memoey Address = 0x%x\n",downloadAddress); printf("\nFlash write Address = 0x%x\n",targetAddress); printf("\nSTATUS : "); vh_rINTMSK = BIT_ALLMSK; tmp=RdURXH0(); //To remove overrun error state. i=0; while(i<fileSize) { while(!(rUTRSTAT0&0x1)); *(downPt+i)=RdURXH0(); if(i==3) { fileSize=*((unsigned char *)(downloadAddress+0))+ (*((unsigned char *)(downloadAddress+1))<<8)+ (*((unsigned char *)(downloadAddress+2))<<16)+ (*((unsigned char *)(downloadAddress+3))<<24); } if((i%4000)==0) putc('#'); i++; } downloadProgramSize=fileSize-6; for(i=4;i<(fileSize-2);i++) checkSum+=*((unsigned char *)(i+downloadAddress)); dnCS=*((unsigned char *)(downloadAddress+fileSize-2))+ (*((unsigned char *)(downloadAddress+fileSize-1) )<<8); if(checkSum!=dnCS) { printf("Checksum Error!!! MEM : 0x%x DN : 0x%x\n",checkSum,dnCS); return 0; } printf("\nDownload O.K.\n"); return downloadProgramSize; } void *flash_write(void *arg) { int cnt=0; char *argv[1]; char **str; int start_addr; str = (char **)arg; argv[0] = str[0]; start_addr = atoi(argv[0]); if (!start_addr || start_addr < 0x100000 || start_addr > 0x2000000) targetAddress=vh_VPOS_FLASH_BASE; else targetAddress=start_addr; downloadAddress= vh_SDRAM_BASE + 0x00f00000; cnt = DownloadData(); if (cnt) { downloadImageOK=1; Program28F128J3A (); } return (void *)0; }
makefile 수정
컴파일시에
GNU ld version 2.14.90.0.6 20030820 arm-linux-ld: ERROR: /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_divsi3.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_divsi3.o) arm-linux-ld: ERROR: /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_dvmd_lnx.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_dvmd_lnx.o) arm-linux-ld: ERROR: /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_modsi3.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_modsi3.o) arm-linux-ld: ERROR: /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_udivsi3.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_udivsi3.o) arm-linux-ld: ERROR: /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_umodsi3.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/local/arm/lib/gcc-lib/arm-linux/2.95.3/libgcc.a(_umodsi3.o) arm-linux-ld: ERROR: /usr/bin/../arm-linux/lib/libc.a(memmove.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/bin/../arm-linux/lib/libc.a(memmove.o) arm-linux-ld: ERROR: /usr/bin/../arm-linux/lib/libc.a(memset.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/bin/../arm-linux/lib/libc.a(memset.o) arm-linux-ld: ERROR: /usr/bin/../arm-linux/lib/libc.a(memcpy.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/bin/../arm-linux/lib/libc.a(memcpy.o) arm-linux-ld: ERROR: /usr/bin/../arm-linux/lib/libc.a(wordcopy.o) uses hardware FP, whereas vpos_kernel-elf32 uses software FP Success: failed to merge target specific data of file /usr/bin/../arm-linux/lib/libc.a(wordcopy.o) make[1]: *** [vpos_kernel-elf32] Error 1 make[1]: Leaving directory `/opt/vpos/objs' make: *** [all] Error 2
위와 같은 에러가 난다면, makefile 을 수정해야 한다.
... CFLAGS = -O0 -Wall -Wstrict-prototypes -fPIC -mshort-load-bytes -nostdinc -nostartfiles -nostdlib -march=armv4 -mtune=arm9tdmi -fomit-frame-pointer -fno-builtin -mapcs-32 $(INCLUDE) // 수정 OCFLAGS = -O binary -R .note -R .comment -R .stab -R .stabstr -S ...
'-msoft-float' 를 제거하면 된다.
컴파일 및 수행
이제 컴파일 해보자! 제대로 수행되었다면, 생성된 bin 파일을 가지고 제대로 동작하는 지 테스트해보자!