소스는 아래 기준으로 우선 설명합니다.
- linux kernel v4.0.5를 기준으로 설명하며 주요한 변경 사항이 있으면 추가합니다.
- 아키텍처는 ARM 32bit를 가장 우선 시 설명합니다.
- 가끔 결과를 보여주는데 사용하는 시험 보드는 라즈베리파이2 보드입니다.
- linux kernel v4.0.5에는 rpi2(라즈베리파이2)가 사용하는 머신 디렉토리가 없어서 rpi2가 별도로 배포하는 코드에서 다음 화일들만을 가져왔습니다.
부트로더가 호출하는 이 루틴은 커널이 지원하는 몇 가지 압축 방법중 하나로 압축된 커널을 풀기위한 루틴으로 진입 전에 아래와 같은 조건을 갖춰야 한다.
- MMU=off
- D-cache=off
- I-cache=상관 없음
- r0=0
- r1=머신 번호
- r2=ATAG 또는 DTB가 놓인 주소
도입부
디버그 매크로
arch/arm/boot/compressed/head.S
.arch armv7-a
/*
* Debugging stuff
*
* Note that these macros must not contain any code which is not
* 100% relocatable. Any attempt to do so will result in a crash.
* Please select one of the following when turning on debugging.
*/
#ifdef DEBUG
#if defined(CONFIG_DEBUG_ICEDCC)
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)
.macro loadsp, rb, tmp
.endm
.macro writeb, ch, rb
mcr p14, 0, \ch, c0, c5, 0
.endm
#elif defined(CONFIG_CPU_XSCALE)
.macro loadsp, rb, tmp
.endm
.macro writeb, ch, rb
mcr p14, 0, \ch, c8, c0, 0
.endm
#else
.macro loadsp, rb, tmp
.endm
.macro writeb, ch, rb
mcr p14, 0, \ch, c1, c0, 0
.endm
#endif
#endif
#endif
- DEBUG
- 임베디드 장치가 완전히 테스트되어 양산으로 들어갈 때엔 커널 사이즈와 속도를 위해 DEBUG 옵션을 disable해야 하는 것을 추천한다.
- CONFIG_DEBUG_ICEDCC
- ARM 하드웨어 디버거를 사용할 때 설정하여 디버깅을 할 수 있다.
- CONFIG_CPU_V7
- CONFIG_DEBUG_LL_INCLUDE
- Low Level Debugging을 할 수 있는 코드가 담긴 화일을 의미하며, 라즈베리파이의 경우 “debug/pl01x.S” 문자열이 담김.
- Low Level 디버깅은 대부분 UART 포트를 사용하여 printk등의 본격적인 디버깅 코드를 사용할 수 없는 초기에 사용된다. 물론 커널이 아직 초기화 되지 않아 일반적인 커널 함수를 사용할 수 있는 상태가 되면 printk등의 디버깅 코드를 사용할 수 있다.
- rpi2에서는 SoC 내부에 uart 인터페이스가 2개가 있고, 하나는 SoC 내부 통신 용도로 사용되어 외부로 사용할 수 없게 하였고, 나머지 하나만 사용자가 사용할 수 있게 하였다.
- rpi2가 사용하는 addruart, senduart, waituwart, busyuart의 매크로는 다음 화일에 정의되어 있다.
- arch/arm/include/debug/pl01x.S
- writeb 매크로
- loadsp 매크로
- uart 통신을 하기위해 uart 관련 레지스터의 물리 및 가상 BASE 주소를 알아온다.
.macro kputc,val
mov r0, \val
bl putc
.endm
.macro kphex,val,len
mov r0, \val
mov r1, #\len
bl phex
.endm
.macro debug_reloc_start
#ifdef DEBUG
kputc #'\n'
kphex r6, 8 /* processor id */
kputc #':'
kphex r7, 8 /* architecture id */
#ifdef CONFIG_CPU_CP15
kputc #':'
mrc p15, 0, r0, c1, c0
kphex r0, 8 /* control reg */
#endif
kputc #'\n'
kphex r5, 8 /* decompressed kernel start */
kputc #'-'
kphex r9, 8 /* decompressed kernel end */
kputc #'>'
kphex r4, 8 /* kernel execution address */
kputc #'\n'
#endif
.endm
.macro debug_reloc_end
#ifdef DEBUG
kphex r5, 8 /* end of kernel */
kputc #'\n'
mov r0, r4
bl memdump /* dump 256 bytes at start of kernel */
#endif
.endm
- debug_reloc_start 매크로
- debug_reloc_end 매크로
- 커널의 끝 주소를 출력하고 커널 시작 부분 256 바이트의 메모리를 덤프한다.
start:
.section ".start", #alloc, #execinstr
/*
* sort out different calling conventions
*/
.align
.arm @ Always enter in ARM state
start:
.type start,#function
.rept 7
mov r0, r0
.endr
ARM( mov r0, r0 )
ARM( b 1f )
THUMB( adr r12, BSYM(1f) )
THUMB( bx r12 )
.word _magic_sig @ Magic numbers to help the loader
.word _magic_start @ absolute load/run zImage address
.word _magic_end @ zImage end address
.word 0x04030201 @ endianness flag
THUMB( .thumb )
1:
ARM_BE8( setend be ) @ go BE8 if compiled for BE8
mrs r9, cpsr
- .section
- .start 섹션을 선언하고 이 안에 메모리를 할당할 수 있고 실행가능코드라 지정
- .align
- 디폴트로 해당 아키텍처에 최적화된 바이트 단위로 코드와 데이터를 정렬하게 한다.
- ARM 32bit CPU들은 디폴트로 모두 4바이트이다.
- 코드와 데이터를 4바이트 단위로 정렬하여야 메모리로 부터 데이터를 읽을 때 별도의 추가 코드 없이 또는 instruction clock cycle이 가장 빠른 코드를 사용하게 할 수 있다
- .arm
- start:
- .type
- ELF 화일에서 start 레이블을 함수로 인식할 수 있도록 한다.
- 참고
- .rept 7
- ARM 아키텍처 메인 테이너인 Russell King이 어느 오래된 부트로더 하나가 0x20번째 주소로 점프를 하는 관계로 커널이 호환성을 갖기위해 아무일도 하지않는 코드를 사용할 수 밖에 없었다고 한다.
- 참고: http://comments.gmane.org/gmane.linux.ports.arm.kernel/26690
- ARM() 매크로와 THUMB() 매크로
- CONFIG_THUMB2_KERNEL 옵션에 따라 둘 중 하나로만 사용될 수 있다.
- ARM코드로 만들어진 부트로더가 동작중인 경우 커널도 ARM 코드로 시작하는 것이 올바르기 때문에 커널 빌드를 ARM 코드로 빌드해야 함. 만일 특수하게 만든 부트로더가 THUMB2코드로 동작하고 커널로 넘어오게 된 경우를 고려하여 이 때는 커널 빌드 시 THUMB2 모드를 사용할 수 있게 해야 한다.
- THUMB 코드는 2바이트이지만 THUMB2 코드는 4바이트이다.
- b 1f
- branch 1 forward라는 의미로 전진방향으로 1이라는 레이블을 찾아 이동하라는 의미이다.
- BSYM 매크로
- THUMB2 코드로 진행되는 경우 레이블 1: + 1을 가리킨다.
- bx 명령어는 레이블 주소의 하위 비트(LSB)가 1이면 THUMB 모드로 전환하고 0이면 ARM 모드로 전환한다.
- _magic_sig
- zImage의 매직 넘버를 가지고 있다. (0x016f_2818)
- _magic_start
- zImge가 로드되어 동작하는 시작 주소가 담긴다.
- rpi2: 0x0000_0000
- _magic_end
- zImage의 마지막 주소
- rpi2: 0x003c_ca28 – 당연히 이미지의 크기는 커널 설정 조건에 따라 다르다.
- 0x04030201
- 커널의 만들어졌을 때의 엔디안 값으로 _magic_start+0x30 값부터 시작하는 값에 따라 구분된다.
- 0x04 03 02 01: 빅엔디안 커널
- 0x01 02 03 04: 리틀엔디안 커널
- rp2: 리틀엔디안 커널로 만들어졌다.
- ARM_BE8() 매크로
- ARMv6 및 ARMv7 아키텍처는 BIG ENDIAN도 지원한다.
- 커널 설정 중 CONFIG_CPU_ENDIAN_BE8=y일 ARM_BE8() 매크로가 동작한다.
- rpi2: CONFIG_CPU_ENDIAN_BE8가 설정되지 않아 매크로가 동작하지 않는다.
- setend be
- CPU를 빅엔디안으로 설정
- 때 설정이 ARM_BE8은 빅엔디안을 지원하는 경우 BIG ENDIAN 모드로 동작하게 함
- rpi2: 리틀엔디안으로 동작한다.
- mrs r9, cpsr
- cpsr 상태 레지스터 값을 r9에 저장한다.
#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install @ get into SVC mode, reversibly
#endif
mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
/*
* Booting from Angel - need to enter SVC mode and disable
* FIQs/IRQs (numeric definitions from angel arm.h source).
* We only do this if we were in user mode on entry.
*/
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel
mov r0, #0x17 @ angel_SWIreason_EnterSVC
ARM( swi 0x123456 ) @ angel_SWI_ARM
THUMB( svc 0xab ) @ angel_SWI_THUMB
not_angel:
safe_svcmode_maskall r0
msr spsr_cxsf, r9 @ Save the CPU boot mode in
@ SPSR
/*
* Note that some cache flushing and other stuff may
* be needed here - is there an Angel SWI call for this?
*/
/*
* some architecture specific code can be inserted
* by the linker here, but it should preserve r7, r8, and r9.
*/
- CONFIG_ARM_VIRT_EXT
- cpsr 상태값 비교
- cpsr 레지스터를 읽어와서 bit0과 bit1이 모두 0인 경우는 usr 모드이고 tst 명령으로 플래그를 판단하여 usr 모드가 아닌경우 not_angel 레이블로 이동
- 디버거 용도로 사용하는 엔젤 부트로더는 svc 모드로 동작한다.
- 엔젤디버거 인경우
- r0에 0x17을 갖고 소프트웨어 인터럽트 0x123456를 호출하면 엔젤 부트로더에 있는 루틴에서 Svc모드로 바꿀거라 예상됨. THUMB2 코드가 지원되는 경우 swi 대신 THUMB2용 소프트웨어 인터럽트를 호출한다.
- safe_svcmode_maskall 매크로
- CPU 모드가 svc 모드가 아닌경우 svc 모드로 진입한다.
- 모든 인터럽트가 작동하지 않도록 마스크를 설정한다.
- mrs r9, cpsr
- r9 레지스터에 백업해두었던 cpsr 상태 값을 복원한다.
safe_svc_maskall 매크로
/*
* Helper macro to enter SVC mode cleanly and mask interrupts. reg is
* a scratch register for the macro to overwrite.
*
* This macro is intended for forcing the CPU into SVC mode at boot time.
* you cannot return to the original mode.
*/
.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
mrs \reg , cpsr
eor \reg, \reg, #HYP_MODE
tst \reg, #MODE_MASK
bic \reg , \reg , #MODE_MASK
orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB( orr \reg , \reg , #PSR_T_BIT )
bne 1f
orr \reg, \reg, #PSR_A_BIT
adr lr, BSYM(2f)
msr spsr_cxsf, \reg
__MSR_ELR_HYP(14)
__ERET
1: msr cpsr_c, \reg
2:
#else
/*
* workaround for possibly broken pre-v6 hardware
* (akita, Sharp Zaurus C-1000, PXA270-based)
*/
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
#endif
.endm
CPU 모드가 하이퍼 모드인 상태로 커널에 진입하게된 경우 Svc 모드로 바꾼다. 모드 전환이 약간 복잡하여 매크로로 만들어졌다.
- tst \reg, \reg, #HYP_MODE
- cpsr 값을 읽어 모드 값을 확인하는데 하이퍼 모드인지 확인한다.
- HYP_MODE: 0x1a
- MODE_MASK: 0x1f
- cpsr 레지스터 값
- cpsr_f: bit31~24 (flag fields)
- cpsr_s: bit23~16 (status fields)
- cpsr_x: bit15~8 (extension fields)
- ……. bit8:Abort bit(1)
- cpsr_c: bit7~0 (control fields)
- bit7=IRQ(1), bit6=FIQ(1), bit5=T(1), bit4~0=MODE(5)
- 서비스 모드로 진입하기 전에 IRQ, FIQ를 마스크한다.
- bne 1f
- 하이퍼 모드가 아니므로 레이블 1:로 진행한다.
- 현재 HYP_MODE로 진입이된 경우 SVC모드로 진입을 하기 위해 Abort bit를 set 하여 Abort 기능을 disable 하고 lr 레지스터에 레이블 2위치를 기억하여 서브루틴으로 부터 돌아올 장소를 기억한 후 spsr_cxsf 레지스터를 갱신한다.
- __MSR_ELR_HYP(14)
- 하이퍼모드-모니터모드에서 사용하는 ELR 레지스터에 lr(14)값을 옮김,
- __ERET
- 하이퍼모드에서 리턴 –> 모드를 바꾸면서 점프함.
- spsr에 저장된 Svc 모드로 진입하게 됨.
- setmode
- 하이퍼 모드를 지원하지 않는 ARM 아키텍처에서는 단순히 cpsr만 변경하여 Svc 모드로 들어가게 하였다.
- setmode 매크로는 ARMv7이 ARM 모드에서 동작하는 경우 아래의 코드와 같다.
- PSR_F_BIT: 0x0000_0040
- PSR_I_BIT: 0x0000_0080
- SVC_MODE: 0x0000_0000
#define __MSR_ELR_HYP(regnum) __inst_arm_thumb32( \
0xE12EF300 | regnum, \
0xF3808E30 | (regnum << 16) \
)
#define __ERET __inst_arm_thumb32( \
0xE160006E, \
0xF3DE8F00 \
)
#if defined(CONFIG_CPU_V7M)
/*
* setmode is used to assert to be in svc mode during boot. For v7-M
* this is done in __v7m_setup, so setmode can be empty here.
*/
.macro setmode, mode, reg
.endm
#elif defined(CONFIG_THUMB2_KERNEL)
.macro setmode, mode, reg
mov \reg, #\mode
msr cpsr_c, \reg
.endm
#else
.macro setmode, mode, reg
msr cpsr_c, #\mode
.endm
#endif
참고