PXA255 포팅하기
기존의 S3C2410 에 포팅되어 있는 SCV/OS 를 PXA255 아키텍처로 포팅하는 과정을 문서로 담고 있다.
s3c2410 의 경우, t32 같은 디버깅 장비가 있기 때문에 디버깅하기가 수월했지만, pxa255 의 경우에는 디버깅 장비가 없는 상태에서 디버깅해야 하기 때문에 어려운 점이 있다. 타겟보드로는 falinux 의 ez-x5 를 사용하였다. 보드의 사양은 http://falinux.com 을 참고하기 바란다.
수정할 것들
아무래도 칩 제조회사와 아키텍처가 다른 만큼, 비슷한 부분도 있지만 다른 부분도 있다. 때문에 기존의 SCV/OS 에서 수정해야할 부분이 있다.
수정이 필요한 부분은 hal 쪽 부분이다.
좀 더 구체적으로 얘기하자면, bootloader, serial, timer 이다.
차례대로 하나씩 설명해 보겠다.
bootloader
cpu 와 sdram 을 초기화 해주고, 커널을 메모리 상에 올리는 작업을 해야 하기 때문에, 우선적으로 진행되어야 할 것들은 이 것들에 대한 설정이다.
여기서 한가지 염두해야 할 사항은 rtos 의 경우, cache 와 buffer 를 사용하지 않는다는 점과 현재 scv/os 는 mmu 를 사용하지 않는다는 점이다.
이에 대한 설정은 ezboot 를 참조했다.
#include <cpu.h>
.global reset
.text
reset:
b start
b undefined_instruction
b swi_handler
b abort_prefetch
b abort_data
b not_used
b irq_handler
b fiq_handler
start:
//-------------------------------------------
// change Supervisor mode & IRQ/FIQ Disable
//-------------------------------------------
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #(0x13 | 0xc0)
msr CPSR, r0
//-------------------------------------------
// Change CPU SPEED
//-------------------------------------------
// CCCR 레지스터를 설정하여 CPU 속도를 맞춘다.
ldr r0, =PXA_REG_CCCR
ldr r1, =CPU_SPEED
str r1, [r0]
// 동작속도를 변경한다.
mov r0, #PXA_COP_CCLKCFG_FCS
mcr p14, 0, r0, c6, c0, 0
// 터보모드가 있는지 확인한다.
and r1, r1, #(0x7 << 7) // CCCR_BF_N Mask
cmp r1, #CCCR_BF_N_RUN_X10
beq 10f
// 터보모드를 활성화 한다.
mov r0, #PXA_COP_CCLKCFG_TURBO
mcr p14, 0, r0, c6, c0, 0
10:
//-------------------------------------------
// Disable I-Chache ( MMU, cache, buffer 를 사용하지 않음 )
//-------------------------------------------
ldr r0, =0x00000078 /* Disable MMU, caches, write buffer */
mcr p15,0,r0,c1,c0,0
nop
nop
nop
ldr r0, =0x00000000
mcr p15,0,r0,c8,c7,0 /* flush TLB's */
mcr p15,0,r0,c7,c7,0 /* flush Caches */
mcr p15,0,r0,c7,c10,4 /* Flush Write Buffer */
nop
nop
nop
//-------------------------------------------
// GPIO Setting ( Uart 를 위한 설정 )
//-------------------------------------------
// GPIO Alternate function
ldr r0, =PXA_REG_GP_BASE
ldr r1, =GAFR0_L_VALUE
str r1, [r0, #PXA_REG_OFFSET_GAFR0_L]
ldr r1, =GAFR0_U_VALUE
str r1, [r0, #PXA_REG_OFFSET_GAFR0_U]
ldr r1, =GAFR1_L_VALUE
str r1, [r0, #PXA_REG_OFFSET_GAFR1_L]
ldr r1, =GAFR1_U_VALUE
str r1, [r0, #PXA_REG_OFFSET_GAFR1_U]
ldr r1, =GAFR2_L_VALUE
str r1, [r0, #PXA_REG_OFFSET_GAFR2_L]
ldr r1, =GAFR2_U_VALUE
str r1, [r0, #PXA_REG_OFFSET_GAFR2_U]
ldr r1, =GPDR0_VALUE
str r1, [r0, #PXA_REG_OFFSET_GPDR0]
ldr r1, =GPDR1_VALUE
str r1, [r0, #PXA_REG_OFFSET_GPDR1]
ldr r1, =GPDR2_VALUE
str r1, [r0, #PXA_REG_OFFSET_GPDR2]
// PSSR 설정 - GPIO 입력 활성화
ldr r0, =PXA_REG_PSSR
ldr r1, =PSSR_VALUE
str r1, [r0]
// debug led off
ldr r0, =PXA_REG_GP_BASE
mov r1, #(_LED_3|_LED_2|_LED_1|_LED_0)
str r1, [r0, #PXA_REG_OFFSET_GPSR0]
//-------------------------------------------
// SD Memory Setting
//-------------------------------------------
ldr r0, =PXA_REG_OSCC
ldr r1, =OSCC_VALUE
str r1, [r0]
// MSC 0 설정
ldr r0, =PXA_REG_MSC0
ldr r1, =MSC0_VALUE
str r1, [r0]
ldr r1, [r0] // must be read after it is written
// MSC 1 설정
ldr r0, =PXA_REG_MSC1
ldr r1, =MSC1_VALUE
str r1, [r0]
ldr r1, [r0] // must be read after it is written
// MSC 2 설정
ldr r0, =PXA_REG_MSC2
ldr r1, =MSC2_VALUE
str r1, [r0]
ldr r1, [r0] // must be read after it is written
// CKEN 설정
ldr r0, =PXA_REG_CKEN
ldr r1, =CKEN_VALUE
str r1, [r0]
// Syncronous Static Memory 설정 ==========================
ldr r0, =PXA_REG_SXCNFG
ldr r1, =SXCNFG_VALUE
str r1, [r0]
// SDRAM Clock 설정 ========================================
ldr r0, =PXA_REG_MDREFR
ldr r1, =MDREFR_VALUE
// diable E1PIN
bic r1, r1, #MDREFR_E1PIN
str r1, [r0]
// enable E1PIN
orr r1, r1, #MDREFR_E1PIN
str r1, [r0]
nop
nop
// SDRAM Config 설정 =======================================
ldr r0, =PXA_REG_MDCNFG
ldr r1, =MDCNFG_VALUE
// SDRAM Partition 0/1 disable
bic r1, r1, #(MDCNFG_DE0 | MDCNFG_DE1)
str r1, [r0]
// CBR refresh cycles 8
ldr r3, =SDRAM_BASE_ADDRESS
.rept 8
str r3, [r3]
.endr
// SDRAM Partition 0/1 enable
ldr r1, [r0]
orr r1, r1, #(MDCNFG_DE0|MDCNFG_DE1)
str r1, [r0]
// SDRAM Burst length 설정 =================================
ldr r0, =PXA_REG_MDMRS
ldr r1, =MDMRS_VALUE
str r1, [r0]
//-------------------------------------------
// Loading Memory
//-------------------------------------------
ldr r0,=SDRAM_BASE_ADDRESS
ldr r1,=LOAD_ADDRESS
mov r2,#0x00000000
memory_clean:
str r2,[r0],#4
cmp r0,r1
bne memory_clean
relocate:
ldr r0,=SDRAM_BASE_ADDRESS
ldr r2,=KERNEL_BASE_ADDRESS
copy_loop:
ldr r3,[r2],#4
str r3,[r0],#4
cmp r0, r1
bne copy_loop
jump_kernel:
ldr pc,=SDRAM_BASE_ADDRESS
undefined_instruction:
ldr pc,undefined_instruction_addr
swi_handler:
ldr pc,swi_handler_addr
abort_prefetch:
ldr pc,abort_prefetch_addr
abort_data:
ldr pc,abort_data_addr
not_used:
ldr pc,not_used_addr
irq_handler:
ldr pc,irq_handler_addr
fiq_handler:
ldr pc,fiq_handler_addr
.align 4
undefined_instruction_addr:
.long SDRAM_Remapped_undef
swi_handler_addr:
.long SDRAM_Remapped_swi
abort_prefetch_addr:
.long SDRAM_Remapped_prefetch_abort
abort_data_addr:
.long SDRAM_Remapped_data_abort
not_used_addr:
.long SDRAM_Remapped_not_used
irq_handler_addr:
.long SDRAM_Remapped_irq
fiq_handler_addr:
.long SDRAM_Remapped_fiq
앞에서 언급한 내용을 제외하면, scvos bootloader 와 별반 다르지 않다.
그럼 여기서 cpu.h 를 보기로 하자! h/w 가 바뀌었기 때문에, 각 변수의 주소가 바뀌었다. 이 파일 역시, ezboot 를 부분 참조했다.
pxa255 의 경우, 메모리맵을 보면, 0xA0000000 에서 시작하기 때문에, SDRAM_BASE_ADDRESS 가 바뀐 것을 알 수 있다.
또한 s3c2410 에 해당하는 os timer 라는 것이 있어서 이에 대한 주소값들을 추가해주어야 한다.
... /* basic */ #define SDRAM_BASE_ADDRESS 0xA0000000 #define io_p2v(PhAdd) (PhAdd) #define __REG(x) (*((volatile Word *) io_p2v (x))) #define __REG2(x,y) (*(volatile Word *)((Word)&__REG(x) + (y))) #define BOOT_LOADER_SIZE (1024*20) #define LOAD_ADDRESS SDRAM_BASE_ADDRESS + BOOT_LOADER_SIZE #define KERNEL_BASE_ADDRESS 0x400 #define SDRAM_Remapped_undef (SDRAM_BASE_ADDRESS + 0x04) #define SDRAM_Remapped_swi (SDRAM_BASE_ADDRESS + 0x08) #define SDRAM_Remapped_prefetch_abort (SDRAM_BASE_ADDRESS + 0x0c) #define SDRAM_Remapped_data_abort (SDRAM_BASE_ADDRESS + 0x10) #define SDRAM_Remapped_not_used (SDRAM_BASE_ADDRESS + 0x14) #define SDRAM_Remapped_irq (SDRAM_BASE_ADDRESS + 0x18) #define SDRAM_Remapped_fiq (SDRAM_BASE_ADDRESS + 0x1c) // TIMER #define INT_BASE 0x40D00000 // Interrupt controller IRQ pending register #define INT_ICMR 0x04 // Interrupt controller mask register #define CLK_CCCR 0x00 // Core Clock Configuration Register #define TMR_BASE 0x40A00000 #define CLK_BASE 0x41300000 #define TMR_OSCR 0x10 // OS timer counter register #define OSCR1 __REG(0x40A00010) // OS Timer Counter Register #define vh_rSRCPND (*(volatile unsigned *)0x40d00000) #define ICCR (*(volatile unsigned *)0x40d00014) #define ICLR (*(volatile unsigned *)0x40d00008) #define ICMR (*(volatile unsigned *)0x40d00004) #define vh_BIT_TIMER (0x1<<26) #define OSMR0 (*(volatile unsigned *)0x40A00000) // #define OSMR1 (*(volatile unsigned *)0x40A00004) // #define OSMR2 (*(volatile unsigned *)0x40A00008) // #define OSMR3 (*(volatile unsigned *)0x40A0000C) // #define OSCR (*(volatile unsigned *)0x40A00010) // OS Timer Counter Register #define OSSR (*(volatile unsigned *)0x40A00014) // OS Timer Status Register #define OWER (*(volatile unsigned *)0x40A00018) // OS Timer Watchdog Enable Register #define OIER (*(volatile unsigned *)0x40A0001C) // OS Timer Interrupt Enable Register #define OSSR_M3 (1 << 3) // Match status channel 3 #define OSSR_M2 (1 << 2) // Match status channel 2 #define OSSR_M1 (1 << 1) // Match status channel 1 #define OSSR_M0 (1 << 0) // Match status channel 0 #define OWER_WME (1 << 0) // Watchdog Match Enable #define OIER_E3 (1 << 3) // Interrupt enable channel 3 #define OIER_E2 (1 << 2) // Interrupt enable channel 2 #define OIER_E1 (1 << 1) // Interrupt enable channel 1 #define OIER_E0 (1 << 0) // Interrupt enable channel 0 ...
메모리 주소가 바뀌었기때문에, 각 모드의 스택 영역도 바뀌어야 한다. 다음은 kernel_start.S 파일을 보자!
.extern HaltUndef
.extern HaltSwi
.extern HaltPabort
.extern HaltDabort
.extern HaltNouse
.extern HaltIrq
.extern HaltFiq
.extern vk_swi_classifier
.extern vh_hwi_classifier
.extern my_bt_return_address
.equ USERMODE, 0x10
.equ FIQMODE, 0x11
.equ IRQMODE, 0x12
.equ SVCMODE, 0x13
.equ ABORTMODE, 0x17
.equ UNDEFMODE, 0x1b
.equ MODEMASK, 0x1f
.equ NOINT, 0xc0
.equ user_stack_size, 0xA0600000 // 수정
.equ svc_stack_size, 0xA0800000 // 수정
.equ undef_stack_size, 0xA1000000 // 수정
.equ abort_stack_size, 0xA1000000 // 수정
.equ irq_stack_size, 0xA0900000 // 수정
.equ fiq_stack_size, 0xA1100000 // 수정
.global kernel_reset
.global vk_save_swi_mode_stack_ptr
.global vk_save_irq_mode_stack_ptr
.global vk_save_swi_current_tcb_bottom
.global vk_save_irq_current_tcb_bottom
.global vh_restore_thread_ctx
.global scv_swi_handler
.global scv_irq_handler
.global vh_save_thread_ctx
.text
kernel_reset:
b scv_start
b HaltUndef
b scv_swi_handler
b HaltPabort
b HaltDabort
b HaltNouse
b scv_irq_handler
b HaltFiq
scv_start:
/* stack area setting */
/* first change svc mode */
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE|NOINT
msr cpsr_cxsf,r1
/* under mode */
mrs r0,cpsr
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=undef_stack_size
/* abort mode */
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=abort_stack_size
/* irq mode */
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=irq_stack_size
/* fiq mode */
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=fiq_stack_size
/* svc mode */
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=svc_stack_size
/* user mode */
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#USERMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=user_stack_size
b SCV_Main
scv_swi_handler:
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
scv_irq_handler:
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:
.long 0
vk_save_swi_current_tcb_bottom:
.long 0
vk_save_irq_mode_stack_ptr:
.long 0
vk_save_irq_current_tcb_bottom:
.long 0
다른 것들은 기존의 것과 동일하다.
serial
타겟보드가 gpio 46, 47 을 사용하기 때문에 이에 대한 설정을 해야 한다.
void SCV_Serial_Init(void)
{
volatile unsigned int *ClockEnableRegister = (volatile unsigned *)PXA_REG_CKEN;
// 포트를 선택한다.
*ClockEnableRegister |= CKEN_STUART;
set_GPIO_mode( GPIO46_STRXD_MD );
set_GPIO_mode( GPIO47_STTXD_MD );
// 모든 인터럽트를 금지 시킨다.
UART_IER = 0;
// FIFO 를 정리한다.
UART_FCR = 7;
UART_FCR = 1;
// baud, 8BIT 1STOP NO PARITY 로 설정한다.
UART_LCR = (LCR_WLS1|LCR_WLS0|LCR_DLAB);
UART_DLL = 8;
UART_DLH = 0x00;
UART_LCR = (LCR_WLS1|LCR_WLS0);
// UART를 동작 시킨다.
UART_IER = IER_UUE;
}
다음은 가장 중요한 timer 이다.
timer
내가 가장 혼동했던 부분이 바로 타이머를 초기화 해주는 부분이었다. 타이머 초기화와 인터럽트를 등록시키는 부분이 s3c2410 과는 달랐기 때문이다.
s3c2410 의 경우, PWM TIMER 라고 해서 각각의 TIMER 의 주기를 지정할 수 있고, TIMER 의 특징(auto reload, start/stop) 을 레지스터로 직접 지정하는 방식이었다. 또한 카운터 레지스터에 값을 적으면, 값이 감소하면서 0 이 될 때 인터럽트가 발생하는 방식이었다.
하지만, pxa255 에서는 OS TIMER 라는 이름으로 TIMER 레지스터가 존재했다.
pxa255 에서는 총 4 개의 timer[0-3] 를 제공하는 데, 마지막 timer 3 은 watchdog 으로도 사용이 가능하다. 먼저 OSMR[0-3] 에 타이머 주기 값을 설정하면, OSCR(카운터 레지스터)가 증가하다가 OSMR 값과 같아지면 인터럽트가 발생한다.
코드에서는 timer 0 을 사용한다. 인터럽트 번호가 26 이다.
void Timer_init(void)
{
int TIMER = 26; // timer 0 사용
vu_register_dev(0, "Timer", &timer_device_driver_fops, TIMER);
// Interrupt_unmask(TIMER);
OSCR = 0; // 카운터 레지스터 초기화
OSMR0= 36864; // 타이머 주기 설정 (36864 = 10 ms)
OIER = OIER_E0; // timer 0 인터럽트 활성화
OWER = 0x0; // watchdog 으로는 사용하지 않음
OSSR = OSSR_M0; // OS TIMER 카운터가 OSMR[0] 과 match 된다.
// Interrupt handler
ICCR = 0x0; // 모든 활성화된 인터럽트는 idle mode 의 프로세서를 깨운다.
ICLR = 0x0; // TIMER 인터럽트가 발생하면, IRQ 모드로 설정한다.
ICMR = vh_BIT_TIMER; // TIMER 인터럽트를 CPU 로 전달한다.
}
다음은 timer 인터럽트가 발생했을 때 수행되는 인터럽트 핸들러에 대한 코드를 보자!
void vh_hwi_classifier(void)
{
unsigned long read = 0;
unsigned int test = 0;
9
read = rINTOFFSET; // 0x40d00010 레지스터를 읽어서 read 에 저장, timer 인터럽트라면, 26 번째 bit 가 1 로 설정되어 있다
if(read == vh_BIT_TIMER) test = 26; // timer 인터럽트라면, test 에 26 저장
vk_dd_table[vk_idt_table[test]].fop_list->vk_interrupt(); // 인터럽트 핸들러 호출
}
...
...
int vh_timer_interrupt_handler(void)
{
OSSR = 0x01; // OSMR[0] 과 OS 타이머 카운터가 match 되었음
OSMR0 = OSCR1 + 36864; // OSMR0 에 현재 타이머 카운터에 36864 를 더함으로서, 지금으로 부터 10 ms 후에 다시 인터럽트가 발생하도록 한다
if(vk_sched_lock==0) vk_scheduler();
// printf("after\n");
return 0;
}
타이머 인터럽트를 검출하는 부분도 약간 다르기 때문에, 기존의 코드를 수정했다.
문제점
UART 가 안되거나 문자가 깨져나올 때
이 문제는 이미 SCV/OS_디버깅 일지 에서 언급했던 내용이었다.
포팅하는 과정에서 이런 문제가 발생했을 때, 어떻게 해결해야 하는지 보여주겠다.
부트로더부분이 바뀌었기 때문에, bootloader 의 크기와 로딩할 주소를 확인해야 한다. cpu.h 파일에서
... #define SDRAM_BASE_ADDRESS 0xA0000000 #define BOOT_LOADER_SIZE (1024*20) #define KERNEL_BASE_ADDRESS 0X400 ...
위의 항목을 확인한다.
컨텍스트 스위칭이 제대로 안될 때
다른 문서에서 예를 들었던 wiki.php?SCV/OS%EB%94%94%EB%B2%84%EA%B9%85%EC%9D%BC%EC%A7%80#s-10 코드]를 가지고 테스트 해본 결과, 다음과 같은 문제가 발생했다.
Command Help11111111111 Command Help2222222222
나 나름대로 추측하건데, 컨텍스트 스위칭이 2번(?) 이상 발생하지 않는 것 같다.
하지만, 문제는 아주 엉뚱하게 풀렸다.
컴파일 시에 '-fomit-frame-pointer' 를 제거해주면 된다.
makefile 을 아래와 같이 수정한다.
... CFLAGS = -g -O0 -Wall -Wall-Wstrict-prototypes -fPIC -mshort-load-bytes -nostdlib -nostartfiles -mcpu=arm920t -mtune=arm9tdmi -fno-builtin -fomit-frame-pointer -mapcs-32 -nostdinc $(INCLUDE) ...
여기서 '-fomit-frame-pointer' 옵션의 역할에 대해서 알아보자!
| fomit-frame-pointer | frame pointer가 필요없는 함수들을 컴파일할 때, frame pointer를 생략합니다. 따라서 frame-pointer를 저장하고, 다시 불러오는 부분에 해당하는 기계어를 만들지 않기 때문에 약간의 성능 향상을 기대할 수 있습니다. |
해결되어야 할 것들
vk_scheduler_lock() 문제
++(vk_current_thread->sched_lock_counter); // data abort vk_current_thread->saved_sched_lock = vk_sched_lock; // data abort vk_sched_lock = 1;
위의 루틴에서 앞에 2 줄 모두 또는 둘 중에 하나를 수행하면, 바로 data abort 가 발생한다. 다행히(?) 두 줄 모두 주석처리하면 정상동작을 하지만..
아무래도 전역변수에 대한 초기화가 문제인 듯 싶다.