기존의 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 가 발생한다. 다행히(?) 두 줄 모두 주석처리하면 정상동작을 하지만..
아무래도 전역변수에 대한 초기화가 문제인 듯 싶다.