저번 포스트에서는 아래 실행 흐름으로 IPI Call을 요청하는 함수를 알아봤습니다.
 
 
이어서 Arm 아키텍처에서 제공하는 SGI를 이용해 IPI Call을 처리하는 아키텍처에 의존적인 루틴을 분석하겠습니다.
 
다음은 gic_ipi_send_mask() 함수의 구현부입니다.
 
01 static void gic_ipi_send_mask(struct irq_data *d, const struct cpumask *mask)
02 {
03 int cpu;
04
05 if (WARN_ON(d->hwirq >= 16))
06 return;
07
08 /*
09  * Ensure that stores to Normal memory are visible to the
10  * other CPUs before issuing the IPI.
11  */
12 wmb();
13
14 for_each_cpu(cpu, mask) {
15 u64 cluster_id = MPIDR_TO_SGI_CLUSTER_ID(cpu_logical_map(cpu));
16 u16 tlist;
17
18 tlist = gic_compute_target_list(&cpu, mask, cluster_id);
19 gic_send_sgi(cluster_id, tlist, d->hwirq);
20 }
21
22 /* Force the above writes to ICC_SGI1R_EL1 to be executed */
23 isb();
24 }
 
핵심 루틴은 gic_send_sgi() 함수를 호출하는 19번째 줄인데요.
 
중요한 함수이니 라인 별로 코드를 분석하겠습니다.
 
먼저 05~06번째 줄을 보겠습니다.
 
05 if (WARN_ON(d->hwirq >= 16))
06 return;
 
'd->hwirq' 에서 지정된 인터럽트 아이디(Interrupt ID)가 16 이상인 경우 리턴 처리하는 루틴입니다.
GICv3 아키텍처에서 SGI의 인터럽트 아이디의 범위는 0~15이라는 점을 기억합시다.
 
이어서 08~12번째 줄을 보겠습니다.
 
08 /*
09  * Ensure that stores to Normal memory are visible to the
10  * other CPUs before issuing the IPI.
11  */
12 wmb();
 
주석을 보면 아마 Arm 개발자가 추가한 듯 보입니다. 
09번째 줄에 있는 Normal memory는 Armv8 아키텍처에서 정의된 메모리 모델인데요.
 
Armv8 에서는 메모리를 Normal Memory와 Device Memory 타입으로 분류합니다.
Normal Memory는 메인 메모리에 접근해 데이터를 로딩하거나 코드를 실행하는 메모리 타입인데요.
Normal Memory로 지정된 루틴은 캐싱(Caching)을 지원합니다.
 
Arm 프로세서 내부에서 프로세서의 성능을 키우기 위해 명령어의 순서를 재배치해 실행하는데요.
 
19번째 줄에 있는 함수를 실행하면 ICC_SGI1R_EL1 시스템 레지스터의 값을 변경하는데,
이전에 'dsb st' 명령어를 실행해 Arm 프로세서 내부에 있는 파이프라인에서 실행되는 명령어와 데이터를 플러시하기 위한 목적으로 12번째 줄이 실행됩니다.
 
다음은 어셈블리 명령어로 본 gic_ipi_send_mask() 함수의 구현부입니다.
각 명령어 옆에 있는 주석문을 참고합시다.
 
ffffffc01047c698 <gic_ipi_send_mask>:
ffffffc01047c698:   71003c3f    cmp w1, #0xf
...
ffffffc01047c6b8:   a9046bf9    stp x25, x26, [sp,#64]
ffffffc01047c6bc:   d5033e9f    dsb st // Store Data Synchronization Barrier 실행
...
ffffffc01047c784:   d518cbb9    msr s3_0_c12_c11_5, x25 // ICC_SGI1R_EL1 설정
ffffffc01047c788:   2a1303e0    mov w0, w19
ffffffc01047c78c:   aa1403e1    mov x1, x20
...
ffffffc01047c7a4:   d5033fdf    isb // Instruction Synchronization Barrier 실행
 
다음으로 19번째 줄을 봅시다.
 
19 gic_send_sgi(cluster_id, tlist, d->hwirq);
 
gic_send_sgi() 함수를 호출합니다.
 
gic_send_sgi() 함수를 보겠습니다.
 
static void gic_send_sgi(u64 cluster_id, u16 tlist, unsigned int irq)
{
u64 val;
 
val = (MPIDR_TO_SGI_AFFINITY(cluster_id, 3) |
       MPIDR_TO_SGI_AFFINITY(cluster_id, 2) |
       irq << ICC_SGI1R_SGI_ID_SHIFT |
       MPIDR_TO_SGI_AFFINITY(cluster_id, 1) |
       MPIDR_TO_SGI_RS(cluster_id) |
       tlist << ICC_SGI1R_TARGET_LIST_SHIFT);
 
pr_devel("CPU%d: ICC_SGI1R_EL1 %llx\n", smp_processor_id(), val);
gic_write_sgi1r(val);
}
 
val로 아규먼트를 지정한 다음에 gic_write_sgi1r() 함수를 호출합니다.
 
이어서 gic_write_sgi1r() 함수를 보겠습니다.
 
static inline void gic_write_sgi1r(u64 val)
{
write_sysreg_s(val, SYS_ICC_SGI1R_EL1);
}
 
GIC에서 정의된 CPU Interface에서 제공하는 ICC_SGI1R_EL1 시스템 레지스터에
값을 써주는 루틴입니다. 
 
gic_write_sgi1r() 함수가 호출되면 GIC에서 인터럽트를 유발해 다음과 같은 기준으로
프로그램 카운터를 브랜치합니다.
 
   "VBAR_EL1 + 0x280"

+ Recent posts