소스는 아래 기준으로 우선 설명합니다.
- linux kernel v4.0.5를 기준으로 설명하며 주요한 변경 사항이 있으면 추가합니다.
- 아키텍처는 ARM 32bit를 가장 우선 시 설명합니다.
- 가끔 결과를 보여주는데 사용하는 시험 보드는 라즈베리파이2 보드입니다.
- linux kernel v4.0.5에는 rpi2(라즈베리파이2)가 사용하는 머신 디렉토리가 없어서 rpi2가 별도로 배포하는 코드에서 다음 화일들만을 가져왔습니다.
- rpi2 소스 위치: https://github.com/raspberrypi/linux/tree/rpi-4.0.y
- 가져온 화일 및 디렉토리
- arch/arm/configs/defconfig_bcm2709
- arch/arm/mach-bcm2709 디렉토리
부트로더가 호출하는 이 루틴은 커널이 지원하는 몇 가지 압축 방법중 하나로 압축된 커널을 풀기위한 루틴으로 진입 전에 아래와 같은 조건을 갖춰야 한다.
- 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
- rpi2는 ARMv7 아키텍처이다.
- 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 매크로
- uart 포트를 통해 바이트를 출력한다.
- 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
- kputc 매크로
- NULL로 끝나는 문자열을 출력한다.
- kphex 매크로
- 16진수 형태로 요구한 길이 만큼 덤프한다
.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
- 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: 리틀엔디안 커널로 만들어졌다.
- 커널의 만들어졌을 때의 엔디안 값으로 _magic_start+0x30 값부터 시작하는 값에 따라 구분된다.
- 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
- 가상화 기술로 ARMv7 아키텍처가 지원함.
- 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 모드에서 동작하는 경우 아래의 코드와 같다.
- msr cpsr_c, #\mode
- 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
참고
- start: – (현재 글)
- 영역 검사를 하여 하단부가 중복된 경우만 캐시 on 보류 | 문c
- cache_on: | 문c
- cache_clean_flush: | 문c
- 영역 검사를 하여 하단부가 중복된 경우만 캐시 on 보류 | 문c
- restart: | 문c
- wont_overwrite: | 문c