SMP(Multi core) 운용을 위해 빌드한 커널이 실제 사용 시 UP(Uni core)에서 동작을 시키는 경우 SMP 전용 명령들을 모두 UP 명령으로 치환(fixup)해주는 루틴
- 2010년 9월 kernel v2.6.37-rc1에 처음 추가되었고 2011년 1월 v2.6.38-rc3에서 추가 보강되었다.
전체 순서도
UP 시스템 체크
__fixup_smp:
CPU id를 분석하여 4가지 조건 case에서 매치되는 경우 UP 시스템으로 인식하여 __fixup_smp_on_up: 루틴으로 이동하여 처리하고 SMP인 경우 루틴을 빠져나간다.
1) UP case A: ARMv7이 아니면 UP
MIDR을 분석하여 ARMv7이 아니면 UP로 판단한다.
#ifdef CONFIG_SMP_ON_UP __HEAD __fixup_smp: and r3, r9, #0x000f0000 @ architecture version teq r3, #0x000f0000 @ CPU ID supported? bne __fixup_smp_on_up @ no, assume UP
MIDR을 읽어서 MIDR.architecture가 f가 아니면(armv7이 아니면) UP에서
* 동작한 것이라고 판단한다.
- and r3, r9, #0x000f0000
- r9(MIDR, cpu id)에 비트마스크 0x000f_0000(MIDR.architecture)를 한다.
- MIDR
- Implementer[31..24]: 0x41 =ARM
- Variant[23..20]: 0x0 =Major revision number
- Architecture[19..16]: 0xf =ARMv7
- Primary part number[15..4]: 0xc07 =Cortex-A7 MPCore part number
- Revision[3..0]: 0x3 =Minor revision number
- teq r3, #0x000f0000
- MIDR.architecture와 ARMv7(0x000f_0000)을 비교한다.
- tst는 and 연산 후 상태 반영, teq는 xor 연산 후 상태 반영
- MIDR.architecture가 ARMv7이 아니면 __fixup_smp_on_up으로 이동
2) ARM11MPCore인 경우 SMP
ARMv7인 경우 이 루틴에 도착하는데 cpu id를 분석하여 ARM11MPCore 인지 확인되면 SMP라 판단되어 루틴을 종료한다.
bic r3, r9, #0x00ff0000 bic r3, r3, #0x0000000f @ mask 0xff00fff0 mov r4, #0x41000000 orr r4, r4, #0x0000b000 orr r4, r4, #0x00000020 @ val 0x4100b020 teq r3, r4 @ ARM 11MPCore? reteq lr @ yes, assume SMP
- bic r3, r9, #0x00ff0000
- r3 = MIDR.Variant 및 MIDR.Architecture 비트 clear
- r3 = MIDR.Revision 비트 clear
- r4 = #0x4100_b020
- teq r3, r4
- MIDR.Implementer가 0x41이고 MIDR.Primary part number가 0xb02이면 ARM11MPCore로 판단한다.
- reteq lr
- 이 프로세서는 MP 프로세서 이므로 루틴을 더 이상 실행하지 않고 종료.
3) UP case B: Multiprocessing Extension 포함되었지만 UP로 구성된 시스템은 UP
ARM11MPCore가 아닌 경우 이 루틴으로 오게되는데 아마 Cortex 시리즈들일 것이라 판단된다. MPIDR을 읽어 Multiprocess Extensions이 설정되어 있지만 UP로 구성되어 동작하게 되어 있는 시스템은 여기서 UP로 판단한다.
mrc p15, 0, r0, c0, c0, 5 @ read MPIDR and r0, r0, #0xc0000000 @ multiprocessing extensions and teq r0, #0x80000000 @ not part of a uniprocessor system? bne __fixup_smp_on_up @ no, assume UP
- mrc p15, 0, r0, c0, c0, 5
- MPIDR(Multi Processor Affinity Register)를 읽는다.
- and r0, r0, #0xc0000000
- MPIDR.Bits[31]: Multiprocessing Extensions 구현 포함 여부(0=미포함, 1=포함)
- MPIDR.Bits[30]: 0=MP, 1=UP
- teq r0, #0x80000000
- Multiprocessing Extensions가 구현되었지만 UP인 경우 __fixup_smp_on_up 루틴으로 이동
4) Cortex-A9 MPCore가 아니면 SMP
Cortex-A9 아키텍처는 MP용도로 설계되었다. 그러나 실제 SoC 제작 시 1 코어 용으로 설계된 사례가 있다. 그래서 이경우만 아니면 SMP로 판단한다. (Aegis 플랫폼에 사용된 SoC 중 하나가 ARM Cortex-A9 with single core)
- 여기서 부터 추가된 조건들은 2013년 9월 kernel v.3.12-rc6에 추가된 항목이다.
@ Core indicates it is SMP. Check for Aegis SOC where a single @ Cortex-A9 CPU is present but SMP operations fault. mov r4, #0x41000000 orr r4, r4, #0x0000c000 orr r4, r4, #0x00000090 teq r3, r4 @ Check for ARM Cortex-A9 retne lr @ Not ARM Cortex-A9,
- r4 = 0x4100_c090
- teq r3, r4
- ARM Cortex-A9가 아닌 경우 루틴을 빠져나간다.
5) UP case C – Cortex-A9 이면서 IO base 주소가 0이면 UP
@ If a future SoC *does* use 0x0 as the PERIPH_BASE, then the @ below address check will need to be #ifdef'd or equivalent @ for the Aegis platform. mrc p15, 4, r0, c15, c0 @ get SCU base address teq r0, #0x0 @ '0' on actual UP A9 hardware beq __fixup_smp_on_up @ So its an A9 UP
- mrc p15, 4, r0, c15, c0
- Cortex A9이 UP로 설계되어 있는지 확인하기 위해 CBAR에서 IO 장치의 base 주소를 가져온다.
- IO base 주소가 0이면 UP 이므로 __fixup_smp_on_up 루틴으로 이동한다.
6) UP case D – Cortex-A9 이면서 CPU 수가 1개이면 UP
ldr r0, [r0, #4] @ read SCU Config ARM_BE8(rev r0, r0) @ byteswap if big endian and r0, r0, #0x3 @ number of CPUs teq r0, #0x0 @ is 1? retne lr
- ldr r0, [r0, #4]
- IO base 주소 + 4의 위치에서 SCU 설정값을 읽어온다.
- and r0, r0, #0x3
- 이 값의 하위 2비트로 CPU 수를 표현한다.
- teq r0, #0x0
- 이 값이 0이 아니면 SMP로 판단하여 루틴을 빠져나간다.
- 0=CPU 1개, 1=CPU 2개, 2=CPU 3개, 3=CPU 4개
- 이 값이 0이면 UP로 인식하여 아래 루틴(__fixup_smp_on_up:)으로 계속 진행한다.
- 이 값이 0이 아니면 SMP로 판단하여 루틴을 빠져나간다.
ALT_SMP() & ALT_UP() 매크로
ALT_SMP() 매크로는 SMP에서만 동작하는 명령이 들어가고 ALT_UP() 매크로는 UP 시스템에서만 동작하는 명령이 들어간다.
- SMP 시스템에서는 smp_func() 함수를 수행하는데 UP 시스템에서는 아무것도 하지 않을 때의 예)
- ALT_SMP(bl smp_func);
- ALT_UP(nop);
두 매크로의 규칙은 다음과 같다.
- ALT_SMP()와 ALT_UP()는 항상 짝을 이루어 붙어 다닌다.
- ALT_SMP()와 ALT_UP()에 들어가는 명령(instruction)은 항상 4바이트 이다.
- ALT_SMP()는 실제 코드섹션 영역에 그대로 저장된다.
- ALT_UP()는 .alt.smp.init 섹션에 4바이트씩 두 번 push되는데 처음 word는 ALT_SMP()의 명령주소를 가리키고 있고(나중에 치환할 주소를 알아내기 위함), 두 번째 word는 동작할 UP 코드(명령)이다.
SMP 코드를 UP 코드로 fixup
__fixup_smp_on_up:
__fixup_smp_on_up: adr r0, 1f ldmia r0, {r3 - r5} sub r3, r0, r3 add r4, r4, r3 add r5, r5, r3 b __do_fixup_smp_on_up ENDPROC(__fixup_smp) .align 1: .word . .word __smpalt_begin .word __smpalt_end
- r3: offset
- r4: __smpalt_begin
- r5: __smpalt_end
- 위의 레지스터를 준비한 후 __do_fixup_smp_on_up: 루틴으로 이동
__do_fixup_smp_on_up:
이 루틴에서 루프를 돌며 SMP 코드를 UP 코드로 치환한다.
.text __do_fixup_smp_on_up: cmp r4, r5 reths lr ldmia r4!, {r0, r6} ARM( str r6, [r0, r3] ) THUMB( add r0, r0, r3 ) #ifdef __ARMEB__ THUMB( mov r6, r6, ror #16 ) @ Convert word order for big-endian. #endif THUMB( strh r6, [r0], #2 ) @ For Thumb-2, store as two halfwords THUMB( mov r6, r6, lsr #16 ) @ to be robust against misaligned r3. THUMB( strh r6, [r0] ) b __do_fixup_smp_on_up ENDPROC(__do_fixup_smp_on_up)
- cmp r4, r5
- r4(__smpalt_begin으로 시작되는 카운터)와 r5(__smpalt_end)와 비교
- reths lr
- 끝까지 간 경우 종료(r4 >= r5)
- ldmia r4!, {r0, r6}
- r4 카운터 주소가 가리키는 곳에서 2 워드를 r0와 r6레지스터로 읽어오고 r4 카운터 주소를 1 word 감소시킨다.
- r0: ALT_SMP에 있는 instr 주소
- r6: ALT_UP에 있는 instr 값
- str r6, [r0, r3]
- r6(ALT_UP에 있는 instr 값)을 r0(ALT_SMP를 가리키는 instr 주소) + r3(offset) 주소에 저장한다.
- b __do_fixup_smp_on_up
- 루프를 돌기 위해 처음으로 이동한다.