====== VPOS gcc3 으로 포팅하기 ======
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 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 파일을 가지고 제대로 동작하는 지 테스트해보자!