Exception -7- (ARM64 Vector)

<kernel v6.0>

ARM64 Exception

ARM64 Exception 종류

ARM64 Exception 벡터에 포함된 exception 종류는 다음과 같다.  Synchronous를 제외하고 나머지 3개는 비동기이다.

  • Synchronous
    • Instruction Abort
      • MMU를 통해 명령 fetch 에러
        • 예) Execute Never로 마킹된 매핑 주소로 접근하는 경우 발생한다.
    • Data Abort
      • MMU를 통해 데이터 fetch 에러
        • 예) 권한 실패 및 SP 또는 PC alignment 체크에 의해 발생한다.
    • Exception Generating 명령들
      • SVC(Supervisor Call)
        • 유저 모드에서 OS 서비스 요청 (syscall, 동기 exception)
      • HVC
        • Guest OS에서 Hypervisor 요청 (동기 exception)
      • SMC
        • normal(non-secure) 월드에서 secure 월드 서비스 요청 (동기 exception)
  • IRQ
    • 디바이스에서 인터럽트 서비스를 요청하는 경우 발생한다.
    • irq 처리 중 fiq 요청에 대한 preemption은 가능하지만 irq 재진입(irq preemption)은 허용하지 않는다.
    • ARM32 아키텍처는 irq preemption을 허용하지만 ARM 리눅스 커널이 이를 허용하지 않는 설계로 구현되어 있다.
    • Pesudo-NMI를 지원하는 ARM64 아키텍처와 ARM64 커널이 사용될 때 nmi 관련 디바이스에 한해 irq preemption을 허용한다.
  • FIQ
    • IRQ보다 더 빠른 인터럽트 서비스를 요청하는 경우 발생한다.
    • 리눅스 커널의 fiq 핸들러 함수인 handle_fiq_as_nmi()에 처리 코드를 추가하여 사용하여야 한다.
      • 디폴트 처리로 아무것도 하지 않는다. (nop)
      • 예) rpi2의 경우 usb 호스트 드라이버(dwc_otg)에서 사용한다.
    • irq 보다 높은 우선 순위를 갖고, irq 처리 중에도 preemption될 수 있다.
    • 일반적으로 secure 펌웨어에서 fiq를 처리하고, 리눅스 커널에서는 fiq를 처리하지 않는다.
  • SError (System Error)
    • 비동기 Data Abort
      • 예) 캐시 라인을 시스템 메모리에 writeback하느 과정에서 중단되는 경우 발생한다.

 

Reset

최고 레벨의 특별한 exception 이다. 리셋되는 주소는 IMPLEMENTATION DEFINED이고, RVBAR_ELn 레지스터로 지정할 수 있다.

 

Exception 레벨별 벡터 테이블

다음 그림은 VHE 확장이 없는 ARMv8 아키텍처에서 운영되는 각 Exception 레벨에서 운영중인 벡터들을 보여준다.

 

다음 그림은 VHE 확장이 있는 ARMv8 아키텍처에서 운영되는 각 Exception 레벨에서 운영중인 벡터들을 보여준다.

 

리눅스 커널의 EL2 부팅

예) rpi4가 부팅하면 Hyper 모드로 호스트 OS가 동작하도록 EL2로 부팅 후 EL2용 벡터와 핸들러들을 설치한다. 그 후 EL1용 벡터와 핸들러들을 설치한다.  ARMv8.1의 VHE 지원 여부에 따라 호스트 커널은 각각 EL2 또는 EL1에서 동작한다. 즉 처음 부팅된 리눅스 커널이 하이퍼바이저 역할을 수행하는 것이다. 그 후 Qemu/KVM을 사용하여 동작시키는 Guest OS들은 EL1에 벡터와 핸들러들을 설치하고 EL1에서 수행한다.

 

Exception 벡터 테이블 주소

벡터 테이블의 주소는 AArch64 및 AArch32에 대해 다음과 같은 레지스터를 사용하여 지정한다.  리눅스 커널의 경우 boot cpu는 __primary_switched: 레이블에서 EL1용 벡터 위치를 VBAR_EL1에 지정하고, 나머지 cpu들은 __secondary_switched: 레이블에서 지정하였다. 하이퍼모드로 운영되는 경우 install_el2_stub: 레이블에서 EL2용 벡터 위치를 VBAR_EL2에 지정한다.

  • for AArch64
    • VBAR_EL3,  VBAR_EL2,  VBAR_EL1
  • for AArch32
    •  HVBAR

 

Vector 선언

EL1 벡터 (or EL2 VHE)

vectors

arch/arm64/kernel/entry.S

/*
 * Exception vectors for spectre mitigations on entry from EL1 when
 * kpti is not in use.
 */
        .macro generate_el1_vector, bhb
.Lvector_start\@:
        kernel_ventry   1, t, 64, sync          // Synchronous EL1t
        kernel_ventry   1, t, 64, irq           // IRQ EL1t
        kernel_ventry   1, t, 64, fiq           // FIQ EL1h
        kernel_ventry   1, t, 64, error         // Error EL1t

        kernel_ventry   1, h, 64, sync          // Synchronous EL1h
        kernel_ventry   1, h, 64, irq           // IRQ EL1h
        kernel_ventry   1, h, 64, fiq           // FIQ EL1h
        kernel_ventry   1, h, 64, error         // Error EL1h

        .rept   4
        tramp_ventry    .Lvector_start\@, 64, 0, \bhb
        .endr
        .rept 4
        tramp_ventry    .Lvector_start\@, 32, 0, \bhb
        .endr
        .endm
  • 총 16개의 벡터 엔트리들로 구성되며, 각각의 엔트리에 0x80 바이트를 제공한다.
    • ARMv7이 엔트리당 4바이트의 공간만을 제공한 것에 비해 ARMv8은 충분한 크기를 제공하는 것을 알 수 있다.
  • 16개의 벡터 엔트리들을 4개씩 전체 4 세트로 묶는 경우 각 세트들은 다음과  같은 특징을 가지고있다.
    • t와 h는 thread와 handler의 약자이다.
      • 예) ‘el1t
        • el1에서 동작 중인 커널이 SP_EL1을 선택하여 사용할 때 exception이 발생한것이다.
      • 예) ‘el1h
        • el1에서 동작 중인 커널이 SP_EL0을 선택하여 사용할 때 exception이 발생한것이다.
    • 첫 번째 4개 벡터
      • 현재 코드가 수행 중인 exception 레벨과 동일한 exception 레벨에서 excetion이 발생하였고, SP_EL0를 사용하고 있었다.
      • 리눅스 커널은 이에 해당하는 핸들러들을 처리하지 않도록 invalid 핸들러들로 연결되어 있다.
    • 두 번째, 현재 코드가 수행 중인 exception 레벨과 동일한 exception 레벨에서 excetion이 발생하였고, SPn(n>0)을 사용하고 있었다.
      • 커널(EL1)에서 exception이 발생하였고, 이 때 SP_EL1을 사용 중인 경우이다.
      • 예) sync exception이 발생하는 경우 el1_sync: 레이블로 이동하여 sync 관련 루틴을 수행한다.
    • 세 번째, 현재 코드가 수행 중인 exception 레벨보다 하위 exception 레벨이며 AArch64 수행 중이다.
      • AArch64 유저(EL0)에서 exception이 발생한 경우이다.
    • 네 번째, 현재 코드가 수행 중인 exception 레벨보다 하위 exception 레벨이며 AArch32 수행 중이다.
      • AArch32 유저(EL0)에서 exception이 발생한 경우이다.

 

EL2 벡터

KVM 성능을 높여주는 ARMv8.1의 VHE(Virtualization Host Extension) 기능 지원 여부에 따라 다음과 같이 두 개의 el2 벡터를 지원한다.

  • 커널이 el2에서 처음 부팅한 경우엔 hvc 명령만 대응하기 위해 다음 벡터를 사용한다. 커널은 hvc를 제외한 실제 운영을 el1에서수행한다.
    • arch/arm64/kernel/hyp-stub.S에 위치한 __hyp_stub_vectors를 사용한다.
  • 커널이 VHE를 지원하고 el2에서 동작하는 경우 커널이 hvc 및 el2 인터럽트를 처리하기 위해 el1에서 사용하던 vectors를 사용한다.

 

__kvm_hyp_vector – with nVHE

arch/arm64/kernel/hyp-stub.S

       ventry  el2_sync_invalid                // Synchronous EL2t
        ventry  el2_irq_invalid                 // IRQ EL2t
        ventry  el2_fiq_invalid                 // FIQ EL2t
        ventry  el2_error_invalid               // Error EL2t

        ventry  elx_sync                        // Synchronous EL2h
        ventry  el2_irq_invalid                 // IRQ EL2h
        ventry  el2_fiq_invalid                 // FIQ EL2h
        ventry  el2_error_invalid               // Error EL2h

        ventry  elx_sync                        // Synchronous 64-bit EL1
        ventry  el1_irq_invalid                 // IRQ 64-bit EL1
        ventry  el1_fiq_invalid                 // FIQ 64-bit EL1
        ventry  el1_error_invalid               // Error 64-bit EL1

        ventry  el1_sync_invalid                // Synchronous 32-bit EL1
        ventry  el1_irq_invalid                 // IRQ 32-bit EL1
        ventry  el1_fiq_invalid                 // FIQ 32-bit EL1
        ventry  el1_error_invalid               // Error 32-bit EL1
SYM_CODE_END(__hyp_stub_vectors)

 

Vector가 저장되는 섹션 위치

ENTRY_TEXT

include/asm-generic/vmlinux.lds.h

#define ENTRY_TEXT                                                      \
                ALIGN_FUNCTION();                                       \
                __entry_text_start = .;                                 \
                *(.entry.text)                                          \
                __entry_text_end = .;

.extry.text 섹션에 벡터코드가 포함되며 ENTRY_TEXT로 정의되어 있다.

 

arch/arm64/kernel/vmlinux.lds.S

.       .head.text : {
                _text = .;
                HEAD_TEXT
        }
       .text : ALIGN(SEGMENT_ALIGN) {  /* Real text segment            */
                _stext = .;             /* Text and read-only data      */
                        IRQENTRY_TEXT
                        SOFTIRQENTRY_TEXT
                        ENTRY_TEXT
                        TEXT_TEXT
                        SCHED_TEXT
                        CPUIDLE_TEXT
                        LOCK_TEXT
                        KPROBES_TEXT
                        HYPERVISOR_TEXT
                        IDMAP_TEXT
                        *(.gnu.warning)
                . = ALIGN(16);
                *(.got)                 /* Global offset table          */
        }

벡터코드가 포함된 ENTRY_TEXT는 .text 영역에 포함되어 있다.

 

kernel_ventry 매크로

arch/arm64/kernel/entry.S

        .macro kernel_ventry, el:req, ht:req, regsize:req, label:req
        .align 7
.Lventry_start\@:
        .if     \el == 0
        /*
         * This must be the first instruction of the EL0 vector entries. It is
         * skipped by the trampoline vectors, to trigger the cleanup.
         */
        b       .Lskip_tramp_vectors_cleanup\@
        .if     \regsize == 64
        mrs     x30, tpidrro_el0
        msr     tpidrro_el0, xzr
        .else
        mov     x30, xzr
        .endif
.Lskip_tramp_vectors_cleanup\@:
        .endif

        sub     sp, sp, #PT_REGS_SIZE
#ifdef CONFIG_VMAP_STACK
        /*
         * Test whether the SP has overflowed, without corrupting a GPR.
         * Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
         * should always be zero.
         */
        add     sp, sp, x0                      // sp' = sp + x0
        sub     x0, sp, x0                      // x0' = sp' - x0 = (sp + x0) - x0 = sp
        tbnz    x0, #THREAD_SHIFT, 0f
        sub     x0, sp, x0                      // x0'' = sp' - x0' = (sp + x0) - sp = x0
        sub     sp, sp, x0                      // sp'' = sp' - x0 = (sp + x0) - x0 = sp
        b       el\el\ht\()_\regsize\()_\label

0:
        /*
         * Either we've just detected an overflow, or we've taken an exception
         * while on the overflow stack. Either way, we won't return to
         * userspace, and can clobber EL0 registers to free up GPRs.
         */

        /* Stash the original SP (minus PT_REGS_SIZE) in tpidr_el0. */
        msr     tpidr_el0, x0

        /* Recover the original x0 value and stash it in tpidrro_el0 */
        sub     x0, sp, x0
        msr     tpidrro_el0, x0

        /* Switch to the overflow stack */
        adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0

        /*
         * Check whether we were already on the overflow stack. This may happen
         * after panic() re-enables interrupts.
         */
        mrs     x0, tpidr_el0                   // sp of interrupted context
        sub     x0, sp, x0                      // delta with top of overflow stack
        tst     x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?
        b.ne    __bad_stack                     // no? -> bad stack pointer

        /* We were already on the overflow stack. Restore sp/x0 and carry on. */
        sub     sp, sp, x0
        mrs     x0, tpidrro_el0
#endif
        b       el\el\ht\()_\regsize\()_\label
.org .Lventry_start\@ + 128     // Did we overflow the ventry slot?
        .endm

Exception이 발생되면 16개의 exception vector 엔트리 중 해당하는 exception 위치로 jump 되어 수행한다.

  • 코드 라인 1에서 인자 들은 다음과 같은 의미를 가지고 있고 이를 이용하여 총 16개의 점프할 C 함수를 구성한다.
    • @el 은 exception 레벨(0, 1, 2)
    • @ht는 h(handle) 또는 t(thread)
    • @regsize는 시스템 레지스터의 사이즈 32 또는 64
    • @label은 4가지 sync, irq, fiq, error로 점프할 함수명을 조합할 때 사용된다.
  • 코드 라인 2에서 .align 7을 지정하여 벡터간 128바이트 단위로 정렬하도록 한다.
  • 코드 라인 4~15에서 64비트 EL0(유저)에서 exception이 발생하여 진입한 경우 EL0에서 커널을 감추기 위한 보안 목적으로 사용되는 trampoline 벡터를 운영한다. 이러한 경우 trampoline 페이지 테이블 주소를 x30에 백업하고, 0으로 기록한다. 단 32비트 EL0에서 exception이 발생한 경우엔 x30에만 0을 대입한다.
    •  trampoline 벡터를 운영하지 않는 경우를 위해 b  .Lskip_tramp_vectors_cleanup\@  명령을 처음 코드에 사용하였고, 이 코드는 trampoline 벡터를 운영할 때엔 trampoline 벡터의 첫 명령어로 교체된다.
    • 고성능 아키텍처에서 문제가 되었던 추측 공격(Speculation 공격, side 채널 공격)을 통해 유저 영역에서 MMU 권한 체크를 우회하여 커널 액세스가 가능해지는 버그가 있다. 이러한 커널 액세스를 방지하기 위해 유저 영역에서 커널 영역을 완전히 분리하여 액세스를 방지하는 CONFIG_UNMAP_KERNEL_AT_EL0 커널 옵션이며 이를 설정하여 사용하는 경우 성능을 약간 희생한다.
    • 참고로 커널 영역 이외에 벡터 테이블 조차 감추기 위해 trampoline 페이지 테이블을 운영하며 exception이 발생할 경우에만 커널 페이지 테이블로 전환하여 사용한다.
    • 위 보안 기능을 사용하기 위해 fake cpu 기능(capability) 하나를 추가하였다. 이 ARM64_UNMAP_KERNEL_AT_EL0 capability를 지원하는 시스템에서만 코드가 동작하게 제한한다.
  • 코드 라인 19에서 context 전환 목적으로 현재 레지스터들을 백업하기 위해 pt_regs 구조체 사이즈(S_FRAME_SIZE)만큼  스택을 증가시킨다. (growth down)
  • 코드 라인 20에서 CONFIG_VMAP_STACK 커널 옵션을 사용하면 스택을 생성할 때 vmalloc 공간을 이용하여 생성한다. 스택은 물리적으로 연속된 공간이 필요 없고, 가상 주소 공간만 연속되면 문제 없으므로 vmalloc 공간에 리니어 매핑하여 사용하는 것이 메모리의 파편화 방지에 도움이 된다.
  • 코드 라인 26~27에서 sp 레지스터와 x0 레지스터 값을 교환할 목적으로먼저 x0 <- sp를 수행한다.이때 sp에는 sp + x0 값이 잠시 담겨있다.
    • 두 레지스터 간의 교환이 필요할 때 DRAM 메모리를 사용할 수 있다. 그러나 exception 루틴에서 DRAM에 접근하면 수백 사이클의 대기 시간이 소요되어 매우 느려지므로 수학적으로 가감 연산만을 사용하여 두 레지스터를 교환할 수 있다.
  • 코드 라인 28에서 sp 레지스터를 범용 레지스터인 x0 레지스터로 옮겨야 tbnz 명령을 사용하여 특정 비트가 설정되었는지 여부를 확인할 수 있다. 여기서 스택 사이즈 또는 태스크 사이즈로 정렬되었는지 여부를 테스트하여 정렬되지 않은 경우 0: 레이블로 이동한다.
    • 스택 레지스터의 THREAD_SHIFT 위치의 비트가 설정된 경우 스택 정렬되지 않은 것으로 판단한다.
  • 코드 라인 29~30에서 x0 레지스터와 sp 레지스터 값을 다시 원래대로 복귀시킨다.
  • 코드 라인 31에서 이 매크로에 전달된 @el , @ht, @regsize 및 @label을 연결하여 만든 레이블로 점프한다.
    • 문자열과 매크로 인자 값을 연결하는 \() 문자는 접착재 같이 사용되므로 제거한다.
    • 예) \el=1, \ht=h, \regsize=64, \label=irq
      • b el\el\ht\()_\regsize\()_\label -> “b el1h_64_irq
    • 위에서 만들어진 점프 레이블은 총 16가지이며, 이들은 entry_handler 매크로에서 생성된다.
  • 코드 라인 33에서 스택이 정렬되지 않은채로 호출되어 이동해온 0 레이블이다. 이 레이블에서는 깨진 스택 대신 static하게 만들어진 per-cpu 오버플로우용 스택을 사용하는 코드로 진행된다.
  • 코드 라인 41에서 x0 레지스터에는 sp 레지스터 값과 바뀐채로 이동해왔다. 이 값을 잠시 tpidr_el0에 저장해둔다.
    • tpidr_el0 <- sp
  • 코드 라인 44~45에서 원래 x0 레지스터 값으로 복원하여 잠시 tpidrro_el0에 저장해둔다.
    • tpidrro_el0 <- x0
  • 코드 라인 48에서 현재 cpu에 해당하는 오버 플로우용 스택을 지정한다.
    • 스택은 growth down으로 동작하므로 스택의 높은 주소를 지정한다.
    • 정적 per-cpu 선언된 overflow_stack은 4K 크기로 사용된다.
  • 코드 라인 54~57에서 백업해둔 스택 값을 x0 레지스터로 읽어와서 현재 스택 범위 이내에 있는지 비교하여 범위 밖인 경우 __bad_stack 레이블로 이동한다.
  • 코드 라인 60~61에서 x0 레지스터와 스택을 다시 복구하고 이 매크로에 전달된 @el 값과 @label을 연결하여 만든 레이블로 점프한다.
  • 코드 라인 63에서 이 매크로에 전달된 @el , @ht, @regsize 및 @label을 연결하여 만든 레이블로 점프한다.

 

adr_this_cpu 매크로

arch/arm64/include/asm/assembler.h

.       /*
         * @dst: Result of per_cpu(sym, smp_processor_id()) (can be SP)
         * @sym: The name of the per-cpu variable
         * @tmp: scratch register
         */
.       .macro adr_this_cpu, dst, sym, tmp
        adrp    \tmp, \sym
        add     \dst, \tmp, #:lo12:\sym
        get_this_cpu_offset \tmp
        add     \dst, \dst, \tmp
        .endm

현재 cpu가 접근할 per-cpu 변수 @sym의 주소를 @dst에 담아 반환한다. (예: @dst <- 현재 cpu로 접근할 per-cpu @sym) 이 매크로는 스크래치 레지스터 @tmp가 하나 소모된다.

  • 코드 라인 2에서 인자로 전달받은 per-cpu 변수 @sym에 대한 페이지 단위로 절삭된 주소를 @tmp에 담는다.
    • 현재 cpu의 pc 주소로 부터 4G 이내의 상대 주소(relative addressing)를 사용하여 알아온다.
    • 예) @sym(overflow_stack 주소 0x1234_5678)이 현재 cpu의 주소로부터 +-4G 이내인 조건에서 페이지 단위로 절삭된 0x1234_5000 값을 @tmp에 대입한다.
  • 코드 라인 3에서 @sym의 하위 12비트를 @tmp에 더해 @dst에 산출한다.
    • 예) @dst(0x1234_5678) = @tmp(0x1234_5000) + 하위 12비트 @sym(0x678)
  • 코드 라인 4에서 현재 cpu에 대한 per-cpu offset 값이 담겨있는 값을 @tmp에 읽어온다.
  • 코드 라인 5에서 per-cpu offset 값이 적용된 per-cpu 변수 @tmp와 심볼 주소 @dst를 더해 반환한다.

 

adr vs adrp

  • adr 명령은 심볼의 주소를 상대 주소(relative addressing)를 사용하여 알아온다. 20비트(추가 1비트는 부호) 상대 주소 기법을 사용하므로 현재 cpu pc 주소로부터 심볼 위치까지 지원하는 범위가 +-1M 주소로 제한된다.
  • adrp 명령은 adr과 유사하지만 더 넓은 범위를 지원하기 위해 심볼 주소의 하위 12비트를 사용하지 않는 20비트(추가 1비트는 부호, 12비트 좌쉬프트) 상대 주소 기법을 사용하므로 더욱 넓은 범위인 +-4G 주소 범위에 한하여 페이지 단위로 절삭된 주소를 알아온다.

 

get_this_cpu_cpu_offset 매크로

arch/arm64/include/asm/assembler.h

#if defined(__KVM_NVHE_HYPERVISOR__) || defined(__KVM_VHE_HYPERVISOR__)
        .macro  get_this_cpu_offset, dst
        mrs     \dst, tpidr_el2
        .endm
#else
        .macro  get_this_cpu_offset, dst
alternative_if_not ARM64_HAS_VIRT_HOST_EXTN
        mrs     \dst, tpidr_el1
alternative_else
        mrs     \dst, tpidr_el2
alternative_endif
        .endm

각 cpu의 tpidr 레지스터에 per-cpu offset 값이 담겨 있고, 이 매크로에선 현재 cpu의 per-cpu offset 값을 @dst에 반환한다.

  • EL2로 부팅하여 VHE(Virtual Host Extension) 기능이 사용되고 있는 시스템에서는 tpidr_el1 레지스터를 지정해도 tpidr_el2 레지스터를 액세스한다. EL2로 부팅하였지만 EL1에서 운영하는 nVHE 시스템에서는 정확히 tpidr_el2 레지스터를 지정해서 사용해야 한다.

 

16개의 entry_handler 들

arch/arm64/kernel/entry.S

/*
 * Early exception handlers
 */
        entry_handler   1, t, 64, sync
        entry_handler   1, t, 64, irq
        entry_handler   1, t, 64, fiq
        entry_handler   1, t, 64, error

        entry_handler   1, h, 64, sync
        entry_handler   1, h, 64, irq
        entry_handler   1, h, 64, fiq
        entry_handler   1, h, 64, error

        entry_handler   0, t, 64, sync
        entry_handler   0, t, 64, irq
        entry_handler   0, t, 64, fiq
        entry_handler   0, t, 64, error

        entry_handler   0, t, 32, sync
        entry_handler   0, t, 32, irq
        entry_handler   0, t, 32, fiq
        entry_handler   0, t, 32, error

총 16개의 entry_handler는 kernel_ventry 엔트리에서 호출된다.

  • 예) \el=1, \ht=h, \regsize=64, \label=irq
    • bl el\el\ht\()_\regsize\()_\label_handler -> “b el1h_64_irq_handler

entry_handler 매크로

arch/arm64/kernel/entry.S

        .macro entry_handler el:req, ht:req, regsize:req, label:req
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
        kernel_entry \el, \regsize
        mov     x0, sp
        bl      el\el\ht\()_\regsize\()_\label\()_handler
        .if \el == 0
        b       ret_to_user
        .else
        b       ret_to_kernel
        .endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
        .endm

이 매크로는 C 언어로 작성한 엔트리 핸들러를 호출하고, 유저 또는 커널로 되돌아가는 레이블을 호출한다.

 

TPID 레지스터 관련

TPIDR_EL0 (Thread ID Register EL0)
  • 유저(EL0) 영역에서 TLS(Thread Location Storage)를 사용하기 위해 스레드 데이터가 위치한 베이스 주소를 저장한다.
  • Exception이 발생할 때 vmap 스택에서 overflow가 발생하여 overflow용 스택을 사용하는 경우 이 레지스터를 임시 저장 영역으로 사용된다.
  • TPIDR_EL0의 경우 커널 영역(EL1) 및 유저 영역(EL0)에서 읽고 쓰기가 가능하다.

 

TPIDRRO_EL0 (Thread ID Register EL0 with User Read Only)
  • TPIDR_EL0와 같은 목적으로 유저(EL0) 영역에서 TLS(Thread Location Storage)를 사용하기 위해 스레드 데이터가 위치한 베이스 주소를 저장한다.
  • Exception이 발생할 때 vmap 스택에서 overflow가 발생하여 overflow용 스택을 사용하는 경우 이 레지스터도 임시 저장 영역으로 사용된다.
  • 커널 영역(EL1)에서 읽고 쓰기가 가능하며, 유저 영역(EL0)에서는 읽기만 가능하다.

 

TPIDR_EL1 (Thread ID Register EL1)
  • per-cpu 베이스 주소를 기억하기 위해 사용한다.
  • 커널(EL1) 영역에서 읽고 쓸 수 있다.

 

참고

 

 

Interrupts -12- (irq desc)

<kernel v5.4>

IRQ 디스크립터

IRQ 디스크립터를 관리하는 두 가지 방법

CONFIG_SPARSE_IRQ 커널 옵션을 사용유무에 따라 IRQ 번호를 관리하는 방법이 두 가지로 나뉜다.

  • Sparse IRQ
    • 커널 옵션을 사용하는 경우 필요한 IRQ 번호에 대한 irq_desc 구조체를 동적으로 할당하고 Radix Tree를 사용하여 관리한다.
    • arm64 시스템은 기본적으로 CONFIG_SPARSE_IRQ 커널 옵션을 구성하여 사용한다.
    • 관련 함수
      • irq_alloc_desc*()
      • irq_free_desc*()
  • Flat IRQ
    • 커널 옵션을 사용하지 않는 경우 max IRQ 번호만큼 irq_dest 구조체 배열을 컴파일 시 정적으로 할당하여 사용한다.

 

NR_IRQS vs nr_irqs

NR_IRQS는 컴파일 타임에 지정되는 irq 수이고, nr_irqs는 런타임에 결정되어 사용되는 irq 수이다.

  • NR_CPUS vs nr_cpus와 동일하게 사용된다.

 


early irq 초기화

1) Sparse IRQ

early_irq_init() – Sparse

kernel/irq/irqdesc.c

#ifdef CONFIG_SPARSE_IRQ
int __init early_irq_init(void)
{
        int i, initcnt, node = first_online_node;
        struct irq_desc *desc;

        init_irq_default_affinity();

        /* Let arch update nr_irqs and return the nr of preallocated irqs */
        initcnt = arch_probe_nr_irqs();
        printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
               NR_IRQS, nr_irqs, initcnt);

        if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
                nr_irqs = IRQ_BITMAP_BITS;

        if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
                initcnt = IRQ_BITMAP_BITS;

        if (initcnt > nr_irqs)
                nr_irqs = initcnt;

        for (i = 0; i < initcnt; i++) {
                desc = alloc_desc(i, node, 0, NULL, NULL);
                set_bit(i, allocated_irqs);
                irq_insert_desc(i, desc);
        }
        return arch_early_irq_init();
}
#endif

irq 디스크립터를 sparse하게 관리할 수 있는 radix 트리를 구성하고 초기화한다.

  • 코드 라인 7에서 모든 cpu를 대상으로 사용하도록 전역 irq_default_affinity 변수에 cpu 비트맵 매스크를 할당하고 모두 1로 설정한다.
  • 코드 라인 10에서 사용할 최대 irq 갯수를 알아온다.
  • 코드 라인 11~12에서 컴파일 때 지정한 최대 NR_IRQS 값과 재조정된 nr_irqs 값, 그리고 아키텍처에 따라 미리 할당해야 할 irq 수 등을 알아와 로그 정보로 출력한다.
    • 예) “NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0”
  • 코드 라인 14~15에서 전역 nr_irqs가 IRQ_BITMAP_BITS를 초과하지 않도록 제한한다.
    • IRQ_BITMAP_BITS
      • sparse 옵션에서 NR_IRQS+8192개
      • flat 옵션에서 NR_IRQS
  • 코드 라인 17~18에서 initcnt가 IRQ_BITMAP_BITS를 초과하지 않도록 제한한다.
  • 코드 라인 20~21에서 nr_irqs를 아키텍처에 따라 미리 할당해야 할 irq 수로 제한한다.
  • 코드 라인 23~27에서 아키텍처에 따라 미리 할당해야 할 수(initcnt) 수 만큼 순회하며 irq 디스크립터를 할당한다. 전역 allocated_irqs 비트마스크의 irq 번호(0번부터 시작)에 해당하는 비트를 설정하고, 전역 irq_desc_tree에 추가한다.
  • 코드 라인 28에서 아키텍처별로 early irq 초기화 루틴을 수행한다.
    • 현재 x86, ia64 아키텍처에서 제공된다.

 

다음 그림은 sparse하게 irq 디스크립터가 관리되는 모습을 보여준다.

early_irq_init-1

 

2) Flat IRQ

early_irq_init() – Flat

kernel/irq/irqdesc.c

#else /* !CONFIG_SPARSE_IRQ */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
 [0 ... NR_IRQS-1] = {
 .handle_irq = handle_bad_irq,
 .depth = 1,
 .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
 }
};

int __init early_irq_init(void)
{
        int count, i, node = first_online_node;
        struct irq_desc *desc;

        init_irq_default_affinity();

        printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);

        desc = irq_desc;
        count = ARRAY_SIZE(irq_desc);

        for (i = 0; i < count; i++) {
                desc[i].kstat_irqs = alloc_percpu(unsigned int);
                alloc_masks(&desc[i], node);
                raw_spin_lock_init(&desc[i].lock);
                lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
                mutex_init(&desc[i].request_mutex);
                desc_set_defaults(i, &desc[i], node, NULL);
        }
        return arch_early_irq_init();
}
#endif

irq 디스크립터를 flat하게 관리하도록 전역 irq_desc[] 배열을 초기화한다.

  • 코드 라인 15에서 모든 cpu를 대상으로 사용하도록 전역 irq_default_affinity 변수에 cpu 비트맵 매스크를 할당하고 모두 1로 설정한다.
  • 코드 라인 17에서 컴파일 때 지정한 최대 NR_IRQS 값을 로그 정보로 출력한다.
    • 예) “NR_IRQS: 64”
  • 코드 라인 19~29에서 NR_IRQS 수만큼 순회하며 irq 디스크립터를 초기화한다.
  • 코드 라인 30에서 아키텍처별로 early irq 초기화 루틴을 수행한다.
    • 현재 x86, ia64 아키텍처에서 제공된다.

 

다음 그림은 flat하게 irq 디스크립터가 관리되는 모습을 보여준다.

early_irq_init-2

 


irq affinity

“irqaffinity=” 커널 파라미터 설정

irq_affinity_setup()

kernel/irq/irqdesc.c

static int __init irq_affinity_setup(char *str)
{
        alloc_bootmem_cpumask_var(&irq_default_affinity);
        cpulist_parse(str, irq_default_affinity);
        /*
         * Set at least the boot cpu. We don't want to end up with
         * bugreports caused by random comandline masks
         */
        cpumask_set_cpu(smp_processor_id(), irq_default_affinity);
        return 1;
}
__setup("irqaffinity=", irq_affinity_setup);

“irqaffinity=<cpu ranges>” 커널 파라미터를 설정하여 irq를 처리할 수 있는 cpu를 지정하며, 이외에 기본적으로 boot cpu도 포함시킨다.

  • 예) “irqaffinity=0-1,6-7”

 

디폴트 irq affinity 초기화

init_irq_default_affinity()

kernel/irq/irqdesc.c

static void __init init_irq_default_affinity(void)
{
        if (!cpumask_available(irq_default_affinity))
                zalloc_cpumask_var(&irq_default_affinity, GFP_NOWAIT);
        if (cpumask_empty(irq_default_affinity))
                cpumask_setall(irq_default_affinity);
}

“irqaffinity=” 커널 파라미터가 설정되지 않은 경우 디폴트로 모든 cpu에서 동작하도록 제한하지 않는다.

  • 코드 라인 3~4에서 할당 방식의 cpu 비트맵 마스크인 경우 비트맵 마스크의 메모리를 할당한다.
  • 코드 라인 5~6에서 디폴트 affinity가 설정되지 않은 경우에 한하여 모든 cpu에서 동작하도록 모두 1로 설정한다.

 


이 루틴은 해당 시스템의 인터럽트 컨트롤러를 초기화하고 각각의 인터럽트 번호에 따른 핸들러들을 준비하는 과정이다. 인터럽트 컨트롤러는 시스템마다 다른 각각의 하드웨어를 사용하므로 분석을하고자 하는 시스템의 하드웨어에 대한 지식이 필요한다. 따라서 각 시스템에서 사용하는 인터럽트 컨틀롤러의 데이터 시트를 참고하기 바란다.

 

리눅스 커널에서 구현 방법은 크게 다음과 같이 두 가지로 나뉜다.

  • 아키텍처 전용 머신 코드를 수행하여 인터럽트 컨트롤러를 초기화하고 핸들러를 구성하는 방법
    • arch/arm/mach-로 시작하는 디렉토리
    • 대부분의 임베디드 시스템에서 처음 사용된 방식으로 곧장 각 머신의 인터럽트 컨트롤러 설정 등이 구현되어 있다.
        • rpi 및 rp2 예)
          • 내부 인터럽트 초기화 함수는 Device Tree를 사용한다.
          • 커널 v4.4 부터는 지원되지 않는다.
  • Device Tree에 설정된 내용을 분석하여 해당 드라이버를 구동하고 인터럽트 컨트롤러 하드웨어를 초기화하고 핸들러를 구성하는 방법
    • kernel/chip.c (헬퍼) -> drivers/irqchip 디렉토리의 각 인터럽트 컨트롤러 드라이버
      • irq_domain 등을 사용하여 리눅스 irq와 hw irq의 매핑을 지원하는 구조로 복잡해졌지만 점차 시스템들이 이 방향으로 구성하고 있다.
      • arm64도 Device Tree 구성을 읽어 인터럽트 초기화 함수들을 호출한다.
      • rpi 및 rpi2 예)
        • 커널 v4.4 부터 지원하기 시작하였다.

 

VMAP Stack 지원

64비트 시스템의 경우 vmalloc 공간이 충분하기 때문에 cpu마다 stack 공간으로 vmalloc 공간을 사용할 수 있도록 지원한다.

 


IRQ 초기화

 

init_IRQ() – ARM64

arch/arm64/kernel/irq.c

void __init init_IRQ(void)
{
        init_irq_stacks();
        irqchip_init();
        if (!handle_arch_irq)
                panic("No interrupt controller found.");

        if (system_uses_irq_prio_masking()) {
                /*
                 * Now that we have a stack for our IRQ handler, set
                 * the PMR/PSR pair to a consistent state.
                 */
                WARN_ON(read_sysreg(daif) & PSR_A_BIT);
                local_daif_restore(DAIF_PROCCTX_NOIRQ);
        }
}

인터럽트 처리 핸들러를 위해 인터럽트 컨트롤러의 준비 및 필요한 설정 수행한다.

  • 코드 라인 3에서 per-cpu irq용 스택을 할당하여 준비한다.
  • 코드 라인 4~6에서 인터럽트 컨트롤러를 초기화하기 위한 irqchip 초기화를 수행한다.
  • 코드 라인 8~15에서 시스템이 irq priority 마스킹을 사용할 수 있는 경우 irq를 블럭한다. 단 Pesudo-NMI는 허용한다.

 

init_irq_stacks()

arch/arm64/kernel/irq.c

#ifdef CONFIG_VMAP_STACK
static void init_irq_stacks(void)
{
        int cpu;
        unsigned long *p;

        for_each_possible_cpu(cpu) {
                p = arch_alloc_vmap_stack(IRQ_STACK_SIZE, cpu_to_node(cpu));
                per_cpu(irq_stack_ptr, cpu) = p;
        }
}
#else
/* irq stack only needs to be 16 byte aligned - not IRQ_STACK_SIZE aligned. */
DEFINE_PER_CPU_ALIGNED(unsigned long [IRQ_STACK_SIZE/sizeof(long)], irq_stack);

static void init_irq_stacks(void)
{
        int cpu;

        for_each_possible_cpu(cpu)
                per_cpu(irq_stack_ptr, cpu) = per_cpu(irq_stack, cpu);
}
#endif

per-cpu irq 스택을 생성한다.

  • 디폴트로 사용되는 CONFIG_VMAP_STACK 커널 옵션은 스택을 vmalloc 공간에 매핑하여 사용할 수 있게 한다.

 

init_IRQ() – ARM32

arch/arm/kernel/irq.c

void __init init_IRQ(void)
{
        int ret;

        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
                irqchip_init();
        else
                machine_desc->init_irq();

        if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
            (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
                if (!outer_cache.write_sec)
                        outer_cache.write_sec = machine_desc->l2c_write_sec;
                ret = l2x0_of_init(machine_desc->l2c_aux_val,
                                   machine_desc->l2c_aux_mask);
                if (ret)
                        pr_err("L2C: failed to init: %d\n", ret);
        }

        uniphier_cache_init();
}

인터럽트 처리 핸들러를 위해 인터럽트 컨트롤러의 준비 및 필요한 설정 수행한다. 시스템 구성에 따라 머신 디스크립터 또는 DTB 둘 중 하나의 방법을 사용하여 초기화 한다.

  • 코드 라인 5~8에서 시스템이 Device Tree를 지원하면서 머신 디스크립터에서 init_irq 후크가 설정되어 있지 않으면 Device Tree를 위해 irqchip_init() 함수를 호출하고, 그렇지 않은 경우 머신 디스크립터에 준비한 init_irq 후크에 설정된 콜백 함수를 호출한다.
    • rpi2 예)
      • 머신의 init_irq에 등록한 bcm2709_init_irq() 함수를 호출한다.
    • 참고: A new generic IRQ layer | LWN.net
  • 코드 라인 10~18에서 outer 캐시를 가진 시스템에 대해 초기화를 수행한다.
    • DTB를 사용할 수 있는 커널에서 L2X0 캐시 컨트롤러를 사용하는 시스템이고 머신에 l2c_aux_mask 또는 l2_aux_val이 설정된 경우 outer 캐시에 대한 콜백함수를 준비하고 초기화 함수를 호출한다.
  • 코드 라인 20에서 UniPhier outer 캐시 컨트롤러를 사용하는 시스템을 초기화한다.

 

arch/arm64/kernel/irq.c – arm64 참고

void __init init_IRQ(void)
{
        irqchip_init();
        if (!handle_arch_irq)
                panic("No interrupt controller found.");
}

arm64에서는 DTB 만을 지원하므로 irqchip_init() 함수를 곧바로 호출한다.

 

인터럽트 컨트롤러 초기화

시스템에 따라 다양한 종류의 인터럽트 컨트롤러를 사용하는데 rpi2에서 사용하는 bcm2709용 인터럽트 컨트롤러 초기화 함수를 호출하기로 한다.

  • Device Tree 미 사용시 (CONFIG_OF=n)
    • rpi
      • arch/arm/mach-bcm2708/bcm2708.c – bcm2708_init_irq()
        • 내부 초기화 함수에서는 device tree를 사용한다.
    • rp2
      • arch/arm/mach-bcm2709/bcm2709.c – bcm2709_init_irq()
        • 내부 초기화 함수에서는 device tree를 사용한다.
  • Device Tree 사용 시
    • of_irq_init() 함수를 통해 Device Tree에 있는 인터럽트 컨트롤러 정보를 읽어오고 해당 드라이버의 초기화 함수들을 호출한다.
    • GIC
      • drivers/irqchip/irq-gic.c – gic_of_init()
    • Exynos Combiner
      • drivers/irqchip/exynos-combiner.c – combiner_of_init()
    • rpi
      • drivers/irqchip/irq-bcm2835.c – armctrl_of_init()
    • rpi2
      • 두 개의 드라이버를 사용한다. (커널 v4.4 부터 적용)
        • drivers/irqchip/irq-bcm2835.c – bcm2836_armctrl_of_init()
        • drivers/irqchip/irq-bcm2836.c – bcm2836_arm_irqchip_l1_intc_of_init()

 


IRQ 디스크립터 할당

irq_alloc_descs()

include/linux/irq.h

#define irq_alloc_descs(irq, from, cnt, node)   \
        __irq_alloc_descs(irq, from, cnt, node, THIS_MODULE, NULL)

요청 @irq 번호부터 @cnt 수 만큼 irq 디스크립터를 @node에 할당한다. 요청한 @irq 번호가 지정되지 않은 값(-1)이면 @from 번호부터 검색하여 배정한다. 그리고 현재 모듈을 irq 디스크립터의 owner로 설정한다.

 

irq_alloc_descs_from()

include/linux/irq.h

#define irq_alloc_descs_from(from, cnt, node)   \
        irq_alloc_descs(-1, from, cnt, node)

새로운 irq 번호로, @from에서 검색하여 @cnt 수 만큼 irq 디스크립터를 @node에 할당한다. 그리고 현재 모듈을 irq 디스크립터의 owner로 설정한다.

 

__irq_alloc_descs()

kernel/irq/irqdesc.c

/**
 * irq_alloc_descs - allocate and initialize a range of irq descriptors
 * @irq:        Allocate for specific irq number if irq >= 0
 * @from:       Start the search from this irq number
 * @cnt:        Number of consecutive irqs to allocate.
 * @node:       Preferred node on which the irq descriptor should be allocated
 * @owner:      Owning module (can be NULL)
 * @affinity:   Optional pointer to an affinity mask array of size @cnt which
 *              hints where the irq descriptors should be allocated and which
 *              default affinities to use
 *
 * Returns the first irq number or error code
 */
int __ref
__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
                  struct module *owner, const struct irq_affinity_desc *affinity)
{
        int start, ret;

        if (!cnt)
                return -EINVAL;

        if (irq >= 0) {
                if (from > irq)
                        return -EINVAL;
                from = irq;
        } else {
                /*
                 * For interrupts which are freely allocated the
                 * architecture can force a lower bound to the @from
                 * argument. x86 uses this to exclude the GSI space.
                 */
                from = arch_dynirq_lower_bound(from);
        }

        mutex_lock(&sparse_irq_lock);

        start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,
                                           from, cnt, 0);
        ret = -EEXIST;
        if (irq >=0 && start != irq)
                goto unlock;

        if (start + cnt > nr_irqs) {
                ret = irq_expand_nr_irqs(start + cnt);
                if (ret)
                        goto unlock;
        }
        ret = alloc_descs(start, cnt, node, affinity, owner);
unlock:
        mutex_unlock(&sparse_irq_lock);
        return ret;
}
EXPORT_SYMBOL_GPL(__irq_alloc_descs);

요청 @irq 번호부터 @cnt 수 만큼 irq 디스크립터를 @node에 할당한다. 요청한 @irq 번호가 지정되지 않은 값(-1)이면 @from 번호부터 검색하여 배정한다. 그리고 현재 모듈을 irq 디스크립터의 owner로 설정한다.

  • 코드 라인 7~8에서 @cnt가 지정되지 않은 경우 -EINVAL 에러로 반환한다.
  • 코드 라인 10~13에서 @irq 번호(0번 이상)를 지정한 경우 검색 시작할 번호 @from에 @irq 번호를 대입한다. 단 @from이 고정 irq 번호보다 큰 경우 -EINVAL 에러로 반환한다.
  • 코드 라인 14~21에서 @irq 번호(0번 이상)를 지정하지 않은 경우 새 irq 번호를 발부받기 위해 @from 부터 시작하도록 한다. 단 아키텍처에 따라 from 값이 바뀔 수 있다.
    • x86 아키텍처에서는 GSI 공간을 피해야 한다.
  • 코드 라인 25~26에서 인터럽트 할당 비트맵에서 @from 비트부터 연속된 @cnt 수 만큼의 할당되지 않은 비트를 찾는다.
    • 참고: Bitmap Operations | 문c
    • 예) 0b0000_0000_1111_0000_1111_0000
      • from=5, cnt=2 -> start=8
      • from=5, cnt=5 -> start=16
  • 코드 라인 28~29에서 @irq 번호를 지정하였고, 찾은 시작 irq 번호와 다른 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 31~35에서 할당할 공간이 부족한 경우 irq 공간을 필요한 수 만큼 확장한다. 확장이 불가능한 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 36~39에서start irq 디스크립터 부터 @cnt 수 만큼 @node에 할당한다. 그리고 모듈 owner를 설정하고 start irq 번호를 반환한다.

 

alloc_descs() – FLAT IRQ 용

kernel/irq/irqdesc.c

static inline int alloc_descs(unsigned int start, unsigned int cnt, int node,
                              const struct irq_affinity_desc *affinity,
                              struct module *owner)
{
        u32 i;

        for (i = 0; i < cnt; i++) {
                struct irq_desc *desc = irq_to_desc(start + i);

                desc->owner = owner;
        }
        bitmap_set(allocated_irqs, start, cnt);
        return start;
}

flat irq의 경우 irq 디스크립터가 이미 준비되어 있으므로 별도로 생성할 필요 없고, 사용할 @start irq 번호부터 @cnt 수 만큼 할당 비트들만을 설정한다. 반환하는 값은 @start 값과 동일하다.

 

alloc_descs() – SPARSE IRQ 용

kernel/irq/irqdesc.c

static int alloc_descs(unsigned int start, unsigned int cnt, int node,
                       const struct irq_affinity_desc *affinity,
                       struct module *owner)
{
        struct irq_desc *desc;
        int i;

        /* Validate affinity mask(s) */
        if (affinity) {
                for (i = 0; i < cnt; i++) {
                        if (cpumask_empty(&affinity[i].mask))
                                return -EINVAL;
                }
        }

        for (i = 0; i < cnt; i++) {
                const struct cpumask *mask = NULL;
                unsigned int flags = 0;

                if (affinity) {
                        if (affinity->is_managed) {
                                flags = IRQD_AFFINITY_MANAGED |
                                        IRQD_MANAGED_SHUTDOWN;
                        }
                        mask = &affinity->mask;
                        node = cpu_to_node(cpumask_first(mask));
                        affinity++;
                }

                desc = alloc_desc(start + i, node, flags, mask, owner);
                if (!desc)
                        goto err;
                irq_insert_desc(start + i, desc);
                irq_sysfs_add(start + i, desc);
                irq_add_debugfs_entry(start + i, desc);
        }
        bitmap_set(allocated_irqs, start, cnt);
        return start;

err:
        for (i--; i >= 0; i--)
                free_desc(start + i);
        return -ENOMEM;
}

@start irq 디스크립터 부터 @cnt 수 만큼 irq 디스크립터를 @node에 할당한다. 그리고 모듈 owner를 설정한 후 radix tree에 추가한다. 반환 값은 @start irq 번호이다.

  • 코드 라인 9~14에서 @affinity가 지정된 경우 @cnt 수만큼 @affinity[i].mask가 모두 설정되어 있는지 확인한다. 하나라도 설정되지 않은 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 16에서 에서 인터럽트 수인 @cnt 수만큼 반복한다.
  • 코드 라인 20~28에서 @affinity 정보가 있는 경우 인자로 받은 @node 대신 affinity[i].mask에 지정된 첫 cpu에 대한 노드를 사용한다.
    • affinity++하여 다음 구조체를 선택한다.
  • 코드 라인 30~32에서 @start+i 번호의 irq 디스크립터를 @node에 할당한다. 그리고 모듈 owner를 설정한다.
  • 코드 라인 33에서 할당한 irq 디스크립터를 radix tree에 추가한다.
  • 코드 라인 34에서 할당한 irq 디스크립터를 sysfs에 추가한다.
  • 코드 라인 35에서 할당한 riq 디스크립터를 debugfs에 추가한다.
  • 코드 라인 37에서 전역 allocated_irqs 비트맵에 할당된 인터럽트 번호에 해당하는 비트들을 설정한다.
  • 코드 라인 38에서 성공 하였으므로 @start irq 번호를 반환한다.

 

다음 그림은 하나의 irq descriptor가 할당되고 초기화되는 모습을 보여준다.

alloc_desc-1

 

alloc_masks()

kernel/irq/irqdesc.c

static int alloc_masks(struct irq_desc *desc, int node)
{
        if (!zalloc_cpumask_var_node(&desc->irq_common_data.affinity,
                                     GFP_KERNEL, node))
                return -ENOMEM;

#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
        if (!zalloc_cpumask_var_node(&desc->irq_common_data.effective_affinity,
                                     GFP_KERNEL, node)) {
                free_cpumask_var(desc->irq_common_data.affinity);
                return -ENOMEM;
        }
#endif

#ifdef CONFIG_GENERIC_PENDING_IRQ
        if (!zalloc_cpumask_var_node(&desc->pending_mask, GFP_KERNEL, node)) {
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
                free_cpumask_var(desc->irq_common_data.effective_affinity);
#endif
                free_cpumask_var(desc->irq_common_data.affinity);
                return -ENOMEM;
        }
#endif
        return 0;
}

irq 디스크립터에서 사용하는 여러 cpu 비트맵 마스크를 할당받는다.

 

desc_set_defaults()

kernel/irq/irqdesc.c

static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
                              const struct cpumask *affinity, struct module *owner)
{
        int cpu;

        desc->irq_common_data.handler_data = NULL;
        desc->irq_common_data.msi_desc = NULL;

        desc->irq_data.common = &desc->irq_common_data;
        desc->irq_data.irq = irq;
        desc->irq_data.chip = &no_irq_chip;
        desc->irq_data.chip_data = NULL;
        irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
        irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
        irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
        desc->handle_irq = handle_bad_irq;
        desc->depth = 1;
        desc->irq_count = 0;
        desc->irqs_unhandled = 0;
        desc->tot_count = 0;
        desc->name = NULL;
        desc->owner = owner;
        for_each_possible_cpu(cpu)
                *per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
        desc_smp_init(desc, node, affinity);
}

요청한  @irq 번호에 해당하는 irq 디스크립터 @desc를 디폴트 값으로 초기화하고, @affinity와 @owner를 지정한다.

  • 코드 라인 6에서 인터럽트 핸들러 데이터를 null로 초기화한다.
  • 코드 라인 7에서 msi 디스크립터가 없는 상태로 초기화한다.
  • 코드 라인 9에서 irq 디스크립터의 하이라키를 표현하는 irq_data의 가장 상위에서 irq 디스크립터에 있는 irq 공통 데이터 구조체를 가리킨다.
  • 코드 라인 10에서 irq 디스크립터에 인자로 받은 @irq 번호를 지정한다.
  • 코드 라인 11~12에서 irq를 처리할 irqchip과 chip_data는 지정되지 않은 상태로 초기화한다.
    • 지정되지 않은 경우 no_irq_chip() 함수를 가리킨다.
  • 코드 라인 13에서 irq 디스크립터의 플래그를 모드 제거하고, 디폴트 초기화 플래그를 설정한다.
    • arm32: IRQ_NOPROBE | IRQ_NOREQUEST
    • arm64: 없음
  • 코드 라인 14~15에서 irq 디스크립터에 disabled, masked 플래그들을 설정한다.
  • 코드 라인 16에서 인터럽트 핸들러 함수를 지정하지 않은 상태로 초기화한다.
    • 지정되지 않은 경우 handle_bad_irq() 함수를 가리킨다.
  • 코드 라인 17~24에서 irq 디스크립터의 각종 값들을 초기화한다.
  • 코드 라인 25에서 irq 디스크립터에 @affinity를 적용한다. 지정되지 않은 경우 전역 irq_default_affinity 값을 적용한다.

 

irq_settings_clr_and_set()

kernel/irq/settings.h

static inline void
irq_settings_clr_and_set(struct irq_desc *desc, u32 clr, u32 set)
{       
        desc->status_use_accessors &= ~(clr & _IRQF_MODIFY_MASK);
        desc->status_use_accessors |= (set & _IRQF_MODIFY_MASK);
}

irq 디스크립터의 status_use_accessors에서 인수 @clr로 요청한 비트들을 clear하고 인수 @set으로 요청한 비트들을 설정한다.

 

irqd_set()

kernel/irq/internals.h

static inline void irqd_set(struct irq_data *d, unsigned int mask)
{
        d->state_use_accessors |= mask;
}

irq 디스크립터의 status_use_accessors에서 인수 @mask로 요청한 비트들을 설정한다.

 

desc_smp_init()

kernel/irq/irqdesc.c

static void desc_smp_init(struct irq_desc *desc, int node,
                          const struct cpumask *affinity)
{
        if (!affinity)
                affinity = irq_default_affinity;
        cpumask_copy(desc->irq_common_data.affinity, affinity);

#ifdef CONFIG_GENERIC_PENDING_IRQ
        cpumask_clear(desc->pending_mask);
#endif
#ifdef CONFIG_NUMA
        desc->irq_common_data.node = node;
#endif
}

요청한 irq 디스크립터 @desc에서 사용 가능한 cpu들을 지정하기 위해 @affinity 비트마스크를 지정한다. @affinity가 null인 경우 디폴트 affinity가 사용된다.

 

irq_insert_desc()

kernel/irq/irqdesc.c

static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
{
        radix_tree_insert(&irq_desc_tree, irq, desc);
}

전역 irq_desc_tree에 요청 @irq 번호를 키로 irq 디스크립터 @desc를 추가한다.

 


Local IRQ 제어

  • local
    • 현재 cpu
  • raw
    • 커널 옵션에 따른 논리적인 구현의 차이
  • arch
    • 아키텍처 의존적인 구현의 차이

 

Local IRQ Disable

 local_irq_disable()

include/linux/irqflags.h

#define local_irq_disable()     do { raw_local_irq_disable(); } while (0)

현재 CPU의 인터럽트를 disable

 

raw_local_irq_disable()

include/linux/irqflags.h

#define raw_local_irq_disable()         arch_local_irq_disable()

 

arch_local_irq_disable() – ARM32

arch/arm/include/asm/irqflags.h

static inline void arch_local_irq_disable(void)
{
        asm volatile(
                "       cpsid i                 @ arch_local_irq_disable"
                :
                :
                : "memory", "cc");
}

 

arch_local_irq_disable() – ARM64

arch/arm64/include/asm/irqflags.h

static inline void arch_local_irq_disable(void)
{
        if (system_has_prio_mask_debugging()) {
                u32 pmr = read_sysreg_s(SYS_ICC_PMR_EL1);

                WARN_ON_ONCE(pmr != GIC_PRIO_IRQON && pmr != GIC_PRIO_IRQOFF);
        }

        asm volatile(ALTERNATIVE(
                "msr    daifset, #2             // arch_local_irq_disable",
                __msr_s(SYS_ICC_PMR_EL1, "%0"),
                ARM64_HAS_IRQ_PRIO_MASKING)
                :
                : "r" ((unsigned long) GIC_PRIO_IRQOFF)
                : "memory");
}
  • Pesudo-NMI를 지원하는 경우 Priority Mask 레지스터를 사용하여 디폴트 irq만 disable하고, nmi에 사용하는 irq는 통과시키는 기법을 사용한다.
  • Pesudo-NMI를 지원하지 않는 경우 PSTATE.I 플래그를 설정하여 irq를 disable 한다.

 

Local IRQ Enable

local_irq_enable()

include/linux/irqflags.h”

#define local_irq_enable()      do { raw_local_irq_enable(); } while (0)

현재 CPU의 인터럽트를 enable

 

raw_local_irq_enaable()

include/linux/irqflags.h

#define raw_local_irq_enable()          arch_local_irq_enable()

 

arch_local_irq_enable() – ARM32

arch/arm/include/asm/irqflags.h

static inline void arch_local_irq_enable(void)
{
        asm volatile(
                "       cpsie i                 @ arch_local_irq_enable"
                :
                :
                : "memory", "cc");
}

 

arch_local_irq_enable() – ARM64

arch/arm64/include/asm/irqflags.h

static inline void arch_local_irq_enable(void)
{
        if (system_has_prio_mask_debugging()) {
                u32 pmr = read_sysreg_s(SYS_ICC_PMR_EL1);

                WARN_ON_ONCE(pmr != GIC_PRIO_IRQON && pmr != GIC_PRIO_IRQOFF);
        }

        asm volatile(ALTERNATIVE(
                "msr    daifclr, #2             // arch_local_irq_enable\n"
                "nop",
                __msr_s(SYS_ICC_PMR_EL1, "%0")
                "dsb    sy",
                ARM64_HAS_IRQ_PRIO_MASKING)
                :
                : "r" ((unsigned long) GIC_PRIO_IRQON)
                : "memory");
}
  • Pesudo-NMI를 지원하는 경우 Priority Mask 레지스터를 사용하여 디폴트 irq와 nmi에 사용하는 irq 모두 통과시킨다.
  • Pesudo-NMI를 지원하지 않는 경우 PSTATE.I 플래그를 클리어하여 irq를 enable 한다.

 

Local IRQ 백업

local_irq_save()

include/linux/irqflags.h

#define local_irq_save(flags)                                   \
        do {                                                    \
                raw_local_irq_save(flags);                      \
        } while (0)

현재 CPU의 irq 상태를 백업하고 disable 한다.

 

raw_local_irq_save()

include/linux/irqflags.h

#define raw_local_irq_save(flags)                       \
        do {                                            \
                typecheck(unsigned long, flags);        \
                flags = arch_local_irq_save();          \
        } while (0)

현재 CPU의 cpsr값을 변수에 저장한다.

 

arch_local_irq_save() – ARM32

arch/arm/include/asm/irqflags.h

static inline unsigned long arch_local_irq_save(void)
{
        unsigned long flags;

        asm volatile(
                "       mrs     %0, " IRQMASK_REG_NAME_R "      @ arch_local_irq_save\n"
                "       cpsid   i"
                : "=r" (flags) : : "memory", "cc");
        return flags;
}

 

arch_local_irq_save() – ARM64

arch/arm64/include/asm/irqflags.h

static inline unsigned long arch_local_irq_save(void)
{
        unsigned long flags;

        flags = arch_local_save_flags();

        /*
         * There are too many states with IRQs disabled, just keep the current
         * state if interrupts are already disabled/masked.
         */
        if (!arch_irqs_disabled_flags(flags))
                arch_local_irq_disable();

        return flags;
}

 

arch_local_irq_save_flags() – ARM64

arch/arm64/include/asm/irqflags.h

static inline unsigned long arch_local_save_flags(void)
{
        unsigned long flags;

        asm volatile(ALTERNATIVE(
                "mrs    %0, daif",
                __mrs_s("%0", SYS_ICC_PMR_EL1),
                ARM64_HAS_IRQ_PRIO_MASKING)
                : "=&r" (flags)
                :
                : "memory");

        return flags;
}

 

Local IRQ 복구

local_irq_restore()

include/linux/irqflags.h

#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)

현재 cpu에서 백업해둔 irq 상태를 복구한다.

 

raw_local_irq_restore()
#define raw_local_irq_restore(flags)                    \
        do {                                            \
                typecheck(unsigned long, flags);        \
                arch_local_irq_restore(flags);          \
        } while (0)

변수에 저장된 cpsr값을 읽어 현재 CPU의 cpsr_c 부분을 변경(현재 CPU의 인터럽트 상태를 저장되었던 상태로 되돌림)

 

arch_local_irq_restore() – ARM32

arch/arm/include/asm/irqflags.h

static inline void arch_local_irq_restore(unsigned long flags)
{
        asm volatile(
                "       msr     " IRQMASK_REG_NAME_W ", %0      
                :
                : "r" (flags)
                : "memory", "cc");
}

#define IRQMASK_REG_NAME_W "cpsr_c"

 

arch_local_irq_restore() – ARM64

arch/arm64/include/asm/irqflags.h

static inline void arch_local_irq_restore(unsigned long flags)
{
        asm volatile(ALTERNATIVE(
                        "msr    daif, %0\n"
                        "nop",
                        __msr_s(SYS_ICC_PMR_EL1, "%0")
                        "dsb    sy",
                        ARM64_HAS_IRQ_PRIO_MASKING)
                :
                : "r" (flags)
                : "memory");
}

 


APIs

irq 디스크립터 할당 & 해제

  • irq_alloc_descs()
  • irq_alloc_descs_from()
  • __irq_alloc_descs()
  • alloc_descs()
  • irq_free_descs()
  • irq_alloc_hwirqs()
  • irq_free_hwirqs()

 

irq 핸들러

  • generic_handle_irq()
  • generic_handle_irq_desc()
  • __handle_domain_irq()
  • __irq_set_preflow_handler()

 

기타

  • irq_data_to_desc()
  • irq_desc_get_irq()
  • irq_desc_get_irq_data()
  • irq_desc_get_chip()
  • irq_desc_get_chip_data()
  • irq_desc_get_handler_data()
  • irq_desc_has_action()
  • irq_has_action()
  • irq_set_handler_locked()
  • irq_set_chip_handler_name_locked()
  • irq_balancing_disabled()
  • irq_is_percpu()
  • irq_is_percpu_devid()
  • irq_get_percpu_devid_partition()

 


구조체 및 데이터

IRQ 디스크립터

include/linux/irqdesc.h

/**
 * struct irq_desc - interrupt descriptor
 * @irq_common_data:    per irq and chip data passed down to chip functions
 * @kstat_irqs:         irq stats per cpu
 * @handle_irq:         highlevel irq-events handler
 * @preflow_handler:    handler called before the flow handler (currently used by sparc)
 * @action:             the irq action chain
 * @status:             status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:              disable-depth, for nested irq_disable() calls
 * @wake_depth:         enable depth, for multiple irq_set_irq_wake() callers
 * @tot_count:          stats field for non-percpu irqs
 * @irq_count:          stats field to detect stalled irqs
 * @last_unhandled:     aging timer for unhandled count
 * @irqs_unhandled:     stats field for spurious unhandled interrupts
 * @threads_handled:    stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:               locking for SMP
 * @affinity_hint:      hint to user space for preferred irq affinity
 * @affinity_notify:    context for notification of affinity changes
 * @pending_mask:       pending rebalanced interrupts
 * @threads_oneshot:    bitfield to handle shared oneshot threads
 * @threads_active:     number of irqaction threads currently running
 * @wait_for_threads:   wait queue for sync_irq to wait for threaded handlers
 * @nr_actions:         number of installed actions on this descriptor
 * @no_suspend_depth:   number of irqactions on a irq descriptor with
 *                      IRQF_NO_SUSPEND set
 * @force_resume_depth: number of irqactions on a irq descriptor with
 *                      IRQF_FORCE_RESUME set
 * @rcu:                rcu head for delayed free
 * @kobj:               kobject used to represent this struct in sysfs
 * @request_mutex:      mutex to protect request/free before locking desc->lock
 * @dir:                /proc/irq/ procfs entry
 * @debugfs_file:       dentry for the debugfs file
 * @name:               flow handler name for /proc/interrupts output
 */
struct irq_desc {
        struct irq_common_data  irq_common_data;
        struct irq_data         irq_data;
        unsigned int __percpu   *kstat_irqs;
        irq_flow_handler_t      handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
        irq_preflow_handler_t   preflow_handler;
#endif
        struct irqaction        *action;        /* IRQ action list */
        unsigned int            status_use_accessors;
        unsigned int            core_internal_state__do_not_mess_with_it;
        unsigned int            depth;          /* nested irq disables */
        unsigned int            wake_depth;     /* nested wake enables */
        unsigned int            tot_count;
        unsigned int            irq_count;      /* For detecting broken IRQs */
        unsigned long           last_unhandled; /* Aging timer for unhandled count */
        unsigned int            irqs_unhandled;
        atomic_t                threads_handled;
        int                     threads_handled_last;
        raw_spinlock_t          lock;
        struct cpumask          *percpu_enabled;
        const struct cpumask    *percpu_affinity;
#ifdef CONFIG_SMP
        const struct cpumask    *affinity_hint;
        struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
        cpumask_var_t           pending_mask;
#endif
#endif
        unsigned long           threads_oneshot;
        atomic_t                threads_active;
        wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
        unsigned int            nr_actions;
        unsigned int            no_suspend_depth;
        unsigned int            cond_suspend_depth;
        unsigned int            force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
        struct proc_dir_entry   *dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
        struct dentry           *debugfs_file;
        const char              *dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
        struct rcu_head         rcu;
        struct kobject          kobj;
#endif
        struct mutex            request_mutex;
        int                     parent_irq;
        struct module           *owner;
        const char              *name;
} ____cacheline_internodealigned_in_smp;

한 개의 irq 디스크립터이다.

  •  irq_common_data
    • irq 공통 정보
    • 아래 irq_data 같이 부모 관계로 연결되는 곳에서도 공통으로 사용되는 정보이다.
  •  irq_data
    • chip 기능에 전달되는 irq와 chip 데이터
    • 이 구조체들은 하이라키 도메인 구성된 경우 부모 관계로 연결된다.
  • *kstat_irqs
    • cpu 별 irq 통계 카운터
  • handle_irq
    • 하이 레벨 irq 이벤트 핸들러
  • preflow_handler
    • preflow 핸들러
  • *action
    • irq action 체인으로 유저 디바이스가 요청한 인터럽트 핸들러가 추가되는 리스트이다.
  • status_use_accessors
    • irq 상태 정보
  • core_internal_state__do_not_mess_with_it
    • 코어 내부 상태 정보
    • irqd->state으로 표현( state가 매크로)
  • depth
    • 네스트된 irq_disable() 호출을 위한 disable depth
  • wake_depth
    • 다중 irq_set_irq_wake() 호출을 위한 enable depth
  • irq_count
    • stalled irq들을 감지하기 위한 상태 필드
  • last_unhandled
    • 언핸들드 카운트를 위한 aging 타이머
  • irqs_unhandled
    • 가짜 언핸들된 인터럽트들을 위한 상태 필드
  • threads_handled
    • 스레드 핸들러의 연기된 가짜 감지를 위한 상태 필드
  • threads_handled_last
    • 스레드 핸들러의 연기된 가짜 감지를 위한 비교 필드
  • lock
    • SMP lock
  • *percpu_enabled
    • cpu 비트마스크
  • *percpu_affinity
    • cpu 제한된 경우 cpu affinity를 표현한 비트마스크
  • *affinity_hint
  • *affinity_notify
    • affnity가 변경 시 통지를 위한 context
  • pending_mask
    • 지연된 리밸런스된 인터럽트들
  • threads_oneshot
  • threads_active
    • 현재 동작중인 irqaction 스레드의 수
  • wait_for_threads
    • 스레드된 핸들러를 위해 기다릴 sync_irq를 위한 대기큐
  • nr_actions
    • 이 irq 디스크립터에  설치된 액션 수
  • no_suspend_depth
    • IRQF_NO_SUSPEND 셋을 가진 irq 디스크립터의 irqactions 수
  • cond_suspend_depth
  • force_resume_depth
    • IRQF_FORCE_RESUME 셋을 가진 irq 디스크립터의 irqactions 수
  • dir
    • /proc/irq/에 표시될 procfs 엔트리
    • default로 irq 숫자 (예: /proc/irq/83)
  • *debugfs_file
    • 디버그용 (/sys/fs/debug)
  • *dev_name
    • 디버그용
  • rcu
  • kobj
  • request_mutex
  • parent_irq
    • cascade 연결 시 부모 irq 번호
  • *owner
    • 모듈을 가리킨다.
  • name
    • /proc/interrupts에 출력될 flow 핸들러 이름
    • 예) uart-pl011

 

irq_data 구조체

include/linux/irq.h

/**
 * struct irq_data - per irq chip data passed down to chip functions
 * @mask:               precomputed bitmask for accessing the chip registers
 * @irq:                interrupt number
 * @hwirq:              hardware interrupt number, local to the interrupt domain
 * @common:             point to data shared by all irqchips
 * @chip:               low level interrupt hardware access
 * @domain:             Interrupt translation domain; responsible for mapping
 *                      between hwirq number and linux irq number.
 * @parent_data:        pointer to parent struct irq_data to support hierarchy
 *                      irq_domain
 * @chip_data:          platform-specific per-chip private data for the chip
 *                      methods, to allow shared chip implementations
 */
struct irq_data {
        u32                     mask;
        unsigned int            irq;
        unsigned long           hwirq;
        struct irq_common_data  *common;
        struct irq_chip         *chip;
        struct irq_domain       *domain;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_data         *parent_data;
#endif
        void                    *chip_data;
};

이 구조체는 irqchip들에 제공될 정보들이다. irq 디스크립터에 기본 내장되어 있고, 하이라키 도메인을 구성하는 경우 추가 생성되어 부모 관계로 연결된다.

  •  mask
    • chip 레지스터들에 접근하기 위한 미리 계산된 비트마스크
  • irq
    • 리눅스가 관리하는 인터럽트 번호 (virq)
  • hwirq
    • 하드웨어 인터럽트 번호
  • *common
    • irq 디스크립터에 있는 공통 정보 구조체를 가리킨다.
  • *chip
    • 로우 레벨 인터럽트 컨트롤러 하드웨어 제어를 담당하는 irq_chip 구조체를 가리킨다.
  • *domain
    • irq 도메인을 가리킨다. (hwirq -> irq 변환)
  • *parent_data
    • 하이라키 irq 도메인을 지원하기 위한 부모 irq_data를 가리킨다.
  • chip_data
    • chip 메소드와 공유 chip 구현을 허락하기 위한 플랫폼 specific per-chip private 데이터

 

irq_common_data 구조체

include/linux/irq.h

/**
 * struct irq_common_data - per irq data shared by all irqchips
 * @state_use_accessors: status information for irq chip functions.
 *                      Use accessor functions to deal with it
 * @node:               node index useful for balancing
 * @handler_data:       per-IRQ data for the irq_chip methods
 * @affinity:           IRQ affinity on SMP. If this is an IPI
 *                      related irq, then this is the mask of the
 *                      CPUs to which an IPI can be sent.
 * @effective_affinity: The effective IRQ affinity on SMP as some irq
 *                      chips do not allow multi CPU destinations.
 *                      A subset of @affinity.
 * @msi_desc:           MSI descriptor
 * @ipi_offset:         Offset of first IPI target cpu in @affinity. Optional.
 */
struct irq_common_data {
        unsigned int            __private state_use_accessors;
#ifdef CONFIG_NUMA
        unsigned int            node;
#endif
        void                    *handler_data;
        struct msi_desc         *msi_desc;
        cpumask_var_t           affinity;
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
        cpumask_var_t           effective_affinity;
#endif
#ifdef CONFIG_GENERIC_IRQ_IPI
        unsigned int            ipi_offset;
#endif
};

하이라키 irq 도메인을 구성하는 irq data들이 사용하는 irq 공통 정보이다.

  • state_use_accessors
    • irq chip 기능들에 제공할 irq 디스크립터의 accessor 정보
  • node
    • 노드 인덱스
  • *handler_data
    • irq_chip 메소드를 위한 per-IRQ 데이터
  • *msi_desc
    • MSI 디스크립터를 가리킨다.
  • affinity
    • SMP에서의 IRQ affinity
  • effective_affinity
  • ipi_offset

 

IRQ Data 상태

include/linux/irq.h

/*
 * Bit masks for irq_common_data.state_use_accessors
 *
 * IRQD_TRIGGER_MASK            - Mask for the trigger type bits
 * IRQD_SETAFFINITY_PENDING     - Affinity setting is pending
 * IRQD_ACTIVATED               - Interrupt has already been activated
 * IRQD_NO_BALANCING            - Balancing disabled for this IRQ
 * IRQD_PER_CPU                 - Interrupt is per cpu
 * IRQD_AFFINITY_SET            - Interrupt affinity was set
 * IRQD_LEVEL                   - Interrupt is level triggered
 * IRQD_WAKEUP_STATE            - Interrupt is configured for wakeup
 *                                from suspend
 * IRQD_MOVE_PCNTXT             - Interrupt can be moved in process
 *                                context
 * IRQD_IRQ_DISABLED            - Disabled state of the interrupt
 * IRQD_IRQ_MASKED              - Masked state of the interrupt
 * IRQD_IRQ_INPROGRESS          - In progress state of the interrupt
 * IRQD_WAKEUP_ARMED            - Wakeup mode armed
 * IRQD_FORWARDED_TO_VCPU       - The interrupt is forwarded to a VCPU
 * IRQD_AFFINITY_MANAGED        - Affinity is auto-managed by the kernel
 * IRQD_IRQ_STARTED             - Startup state of the interrupt
 * IRQD_MANAGED_SHUTDOWN        - Interrupt was shutdown due to empty affinity
 *                                mask. Applies only to affinity managed irqs.
 * IRQD_SINGLE_TARGET           - IRQ allows only a single affinity target
 * IRQD_DEFAULT_TRIGGER_SET     - Expected trigger already been set
 * IRQD_CAN_RESERVE             - Can use reservation mode
 */
enum {
        IRQD_TRIGGER_MASK               = 0xf,
        IRQD_SETAFFINITY_PENDING        = (1 <<  8),
        IRQD_ACTIVATED                  = (1 <<  9),
        IRQD_NO_BALANCING               = (1 << 10),
        IRQD_PER_CPU                    = (1 << 11),
        IRQD_AFFINITY_SET               = (1 << 12),
        IRQD_LEVEL                      = (1 << 13),
        IRQD_WAKEUP_STATE               = (1 << 14),
        IRQD_MOVE_PCNTXT                = (1 << 15),
        IRQD_IRQ_DISABLED               = (1 << 16),
        IRQD_IRQ_MASKED                 = (1 << 17),
        IRQD_IRQ_INPROGRESS             = (1 << 18),
        IRQD_WAKEUP_ARMED               = (1 << 19),
        IRQD_FORWARDED_TO_VCPU          = (1 << 20),
        IRQD_AFFINITY_MANAGED           = (1 << 21),
        IRQD_IRQ_STARTED                = (1 << 22),
        IRQD_MANAGED_SHUTDOWN           = (1 << 23),
        IRQD_SINGLE_TARGET              = (1 << 24),
        IRQD_DEFAULT_TRIGGER_SET        = (1 << 25),
        IRQD_CAN_RESERVE                = (1 << 26),
};

irq_common_data.state_use_accessors에서 사용되는 플래그들이다.

  •  IRQD_TRIGGER_MASK
    • 트리거 타입 마스크 (4비트)
  • IRQD_SETAFFINITY_PENDING
    • affinity 설정이 지연되는 중
  • IRQD_NO_BALANCING
    • no 밸런싱
  • IRQD_PER_CPU
    • 코어별 전용 인터럽트 배정되는 경우 per-cpu 인터럽트로 구성
  • IRQD_AFFINITY_SET
    • affnity 설정된 경우
  • IRQD_LEVEL
    • 레벨 트리거 설정된 경우 (그렇지 않으면 edge 트리거)
  • IRQD_WAKERUP_STATE
    • suspend된 작업을 깨워 동작시키는 인터럽트 형태로 설정
  • IRQD_MOVE_PCNTXT
    • 인터럽트가 process context로 이동된 경우
  • IRQD_IRQ_DISABLED
    • 인터럽트를 disable한 상태
  • IRQD_IRQ_MASKED
    • 인터럽트의 masked 상태
  • IRQD_IRQ_INPROGRESS
    • 인터럽트가 진행중인 상태
  • IRQD_WAKERUP_ARMED
    • 꺠우는 모드 armed
  • IRQD_FORWARDED_TO_VCPU
    • 가상화를 담당하는 vCPU 인터페이스로 전달 가능
  • IRQD_IRQ_STARTED
    • 인터럽트가 서비스 시작된 상태
  • IRQD_MANAGED_SHUTDOWN
    • affinity가 지정되지 않아 인터럽트가 shutdown된 상태
  • IRQD_SINGLE_TARGET
    • 하나의 cpu(affinity target)만 지정 가능한 상태
  • IRQD_DEFAULT_TRIGGER_SET
    • 트리거가 설정된 상태
  • IRQD_CAN_RESERVE
    • 예약 모드 사용 가능한 상태

 

참고

 

Interrupts -11- (IC Driver for rpi2)

<kernel v4.0>

Interrupts -11- (IC Driver for rpi2)

Device Tree로 hierarchy irq chip 구성

설명에 사용된 샘플 보드는 다음과 같다.

  • 64비트 rock960
    • GIC v3 인터럽트 컨트롤러
    • rock 사의 rk3399칩셋은 GIC v3를 사용한다.
    • 커널은 v5.4를 기준으로 설명하였다.
  • 32비트 rpi2
    • BCM2836 전용 인터럽트 컨트롤러
    • 커널은 v4.4를 기준으로 설명하였다.

 

다음 그림은 rpi2에서 설정되는 5개의 irq_chip을 보여준다.

3 개의 인터럽트 컨트롤러

  • gpio pinctrl 디바이스
    • gpio를 사용하는 pinctrl 디바이스 드라이버에 구현되어 있다.
      • 인터럽트 컨트롤러 디바이스 드라이버가 아니므로 IRQCHIP_DECLARE()를 사용하지 않았으므로 __chip_id_of_table 섹션에 인터럽트 컨트롤러 엔트리가 등록되지 않는다.
        • 인터럽트 초기화 루틴 irqchip_init()에서 초기화 함수를 호출하지 않는다.
    • DT 스크립트에 “gpio-controller;” 및 “interrupt-controller;” 두 가지 기능의 컨트롤러가 동작한다.
      • 메인 기능이 gpio를 사용하는 pinctrl 디바이스이다.
      • 추가 기능으로 gpio에서 인터럽트 라인 컨트롤 기능을 수행할 수 있다.
        • gpio pin 들을 인터럽트 라인으로 사용할 수 있어서 이들에 대한 set/clear 등의 마스킹 동작을 위해 irq_chip이 구현되어 있다.
  • GPU 인터럽트 컨트롤러
    • 0 ~ 2번 뱅크를 입력으로 받아들이며 각각 최대 32개의 인터럽트 라인을 가지고 있다.
    •  rpi에서도 사용하는 인터럽트 컨트롤러
  • LOCAL 인터럽트 컨트롤러
    • rpi2에서 SMP core 간 IPI 등의 소프트 인터럽트 처리 등을 위해 별도로 추가되었다.
    • 3 개의 irq_chip으로 구성
      • gpu 인터럽트를 처리하기 위한 irq_chip
      • 4개의 timer 인터럽트를 처리하기 위한 irq_chip
      • 1개의 pmu 인터럽트를 처리하기 위한 irq_chip

 


DT용 인터럽트 컨트롤러 초기화 – rpi2

 

부모 컨트롤러 Part

bcm2836_arm_irqchip_l1_intc_of_init()

drivers/irqchip/irq-bcm2836.c

static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node,
                                                      struct device_node *parent)
{
        intc.base = of_iomap(node, 0);
        if (!intc.base) {
                panic("%s: unable to map local interrupt registers\n",
                        node->full_name);
        }

        bcm2835_init_local_timer_frequency();

        intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1,
                                            &bcm2836_arm_irqchip_intc_ops,
                                            NULL);
        if (!intc.domain)
                panic("%s: unable to create IRQ domain\n", node->full_name);

        bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ,
                                         &bcm2836_arm_irqchip_timer);
        bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ,
                                         &bcm2836_arm_irqchip_timer);
        bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ,
                                         &bcm2836_arm_irqchip_timer);
        bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ,
                                         &bcm2836_arm_irqchip_timer);
        bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST,
                                         &bcm2836_arm_irqchip_gpu);
        bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST,
                                         &bcm2836_arm_irqchip_pmu);

        bcm2836_arm_irqchip_smp_init();

        set_handle_irq(bcm2836_arm_irqchip_handle_irq);
        return 0;
}

bcm2836 로컬 인터럽트에 해당하는 1개의  irq domain 과 3 개의 irq chip을 구성하고 사용하는 인터럽트들을 매핑한다.

  • 코드 라인 4~8에서 인수로 전달받은 인터럽트 컨트롤러 노드의 base 레지스터를 IO 매핑하여 가상주소를 알아온다. 만일 매핑이 실패하면 panic 처리를 한다.
    • rpi2: 0xf400_0000
  • 코드 라인 10~14에서 10개의 irq로 리니어 domain을 추가한다. domain의 변환 함수가 ops에 등록되어 운용된다. 만일 irq domain이 추가되지 않는 경우 panic 처리를 한다.
    • .xlate = irq_domain_xlate_onecell
  • 코드 라인 18~25에서 4개의 타이머 인터럽트를 timer용 irq chip에 구성하고 매핑한다.
  • 코드 라인 26~27에서 1개의 gpu 인터럽트를 gpu용 irq chip에 구성하고 매핑한다.
  • 코드 라인 28~29에서 1개의 pmu 인터럽트를 pmu용 irq chip에 구성하고 매핑한다.
  • 코드 라인 31에서 cpu on/off 등 상태 변경에 따른 notify를 받기 위해 연동하고, ipi 호출을 위해 연동함수를 등록한다.
  • 코드 라인 33에서 irq exception 처리 루틴에서 호출하도록 irq 핸들러 함수를 전역 handle_arch_irq에 등록한다.

 

자식 컨트롤러 Part

bcm2836_armctrl_of_init()

drivers/irqchip/irq-bcm2835.c

static int __init bcm2836_armctrl_of_init(struct device_node *node,
                                          struct device_node *parent)
{
        return armctrl_of_init(node, parent, true);
}

처리할 인터럽트 컨트롤러에 대한 디바이스 노드와 상위 인터럽트 컨트롤러에 대한 디바이스 노드 정보를 갖고 armctrl_of_init() 함수를 호출한다.

 

armctrl_of_init()

drivers/irqchip/irq-bcm2835.c

static int __init armctrl_of_init(struct device_node *node,
                                  struct device_node *parent,
                                  bool is_2836)
{
        void __iomem *base;
        int irq, b, i;

        base = of_iomap(node, 0);
        if (!base)
                panic("%s: unable to map IC registers\n",
                        node->full_name);

        intc.base = base;
        intc.domain = irq_domain_add_linear(node, NUMBER_IRQS * 2,
                                            &armctrl_ops, NULL);
        if (!intc.domain)
                panic("%s: unable to create IRQ domain\n", node->full_name);

        for (b = 0; b < NR_BANKS; b++) { 
                intc.pending[b] = base + reg_pending[b];
                intc.enable[b] = base + reg_enable[b];
                intc.disable[b] = base + reg_disable[b];

                for (i = 0; i < bank_irqs[b]; i++) {
                        irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i));
                        BUG_ON(irq <= 0);
                        irq_set_chip_and_handler(irq, &armctrl_chip,
                                handle_level_irq);
                        irq_set_probe(irq);
                }
        }

        if (is_2836) {
                int parent_irq = irq_of_parse_and_map(node, 0);

                if (!parent_irq) {
                        panic("%s: unable to get parent interrupt.\n",
                              node->full_name);
                }
                irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq);
        } else {
                set_handle_irq(bcm2835_handle_irq);
        }

        if (is_2836) {
                intc.local_regmap =
                        syscon_regmap_lookup_by_compatible("brcm,bcm2836-arm-local");
                if (IS_ERR(intc.local_regmap)) {
                        pr_err("Failed to get local register map. FIQ is disabled for cpus > 1\n");
                        intc.local_regmap = NULL;
                }
        }

        /* Make a duplicate irq range which is used to enable FIQ */
        for (b = 0; b < NR_BANKS; b++) {
                for (i = 0; i < bank_irqs[b]; i++) {
                        irq = irq_create_mapping(intc.domain,
                                        MAKE_HWIRQ(b, i) + NUMBER_IRQS);
                        BUG_ON(irq <= 0); 
                        irq_set_chip(irq, &armctrl_chip);
                        irq_set_probe(irq);
                }
        }
        init_FIQ(FIQ_START);

        return 0;
}

 

bcm2836 gpu 인터럽트에 해당하는 irq domain 과 irq chip을 구성하고 사용하는 인터럽트들을 매핑한다.

  • 코드 라인 8~13에서 인수로 전달받은 인터럽트 컨트롤러 노드의 base 레지스터를 IO 매핑하여 가상주소를 알아온다. 만일 매핑이 실패하면 panic 처리를 한다.
    • rpi2: 0xf300_b200
  • 코드 라인 14~17에서 3개 뱅크(각 32개 irq)가 연결되는 96개 irq + 96개 fiq에 대응하도록 irq domain을 구성하고 매핑한다.
    • irq domain의 ops
      • .xlate =armctrl_xlate
  • 코드 라인 19~22에서 각 뱅크의 pending 레지스터와 enable 및 disable을 레지스터의 가상 주소를 대입한다.
  • 코드 라인 24~25에서 각 뱅크에서 처리할 hw 인터럽트 수 만큼을 irq domain과 irq 디스크립터에 매핑한다.
    • 3 개의 각 뱅크에서 매핑할 hwirq 들
      • bank#0
        • hwirq=0~7까지 8개
        • irq=22~29
      • bank#1
        • hwirq=32~63까지 32개
        • irq=32~63
      • bank#2
        • hwirq=64~95까지 32개
        • irq=62~93
  • 코드 라인 27~30에서 “ARMCTRL-level” 이라는 이름의 irq chip을 설정하고 핸들러로 handle_level_irq()를 사용한다.
  • 코드 라인 33~40에서 이 드라이버 코드는 bcm2835(rpi)와 혼용하여 사용하므로 bcm2836(rpi2)인 경우 부모 인터럽트 컨트롤의 hw irq를 알아와서 이 인터럽트 핸들러로 bcm2836_chained_handle_irq() 함수를 대입한다. 만일 Device Tree 스크립트에서 parent irq 번호를 알아오지 못한 경우 panic 처리한다.
    • 모든 gpu 인터럽트들 중 하나라도 인터럽트가 발생하면 부모 인터럽트 컨트롤러의 8번 hwirq도 인터럽트가 발생한다.
  • 코드 라인 41~43에서 bcm2835(rpi)에서 호출한 경우 핸들러로 bcm2835_handle_irq() 함수를 대입한다.
  • 코드 라인 45~52에서 Device Tree에서 System Control Register 정보를 읽어서 base 주소를 대입한다. 못 읽어오는 경우 메시지를 출력한다. 이 때 2 번째 cpu 부터 fiq 인터럽트를 수신할 수 없게된다.
    • rpi2: 물리 주소=0x4000_0000, size=0x100
  • 코드 라인 55~58에서 각 뱅크에서 처리할 hw 인터럽트 수 만큼을 irq domain과 irq 디스크립터에 매핑한다. 이 인터럽트들은 fiq에 해당한다.
    • 3 개의 각 뱅크에서 매핑할 hwirq 들
      • bank#0
        • hwirq=96~103까지 8개
        • irq=94~101
      • bank#1
        • hwirq=128~159까지 32개
        • irq=102~133
      • bank#2
        • hwirq=160~191까지 32개
        • irq=134~165
  • 코드 라인 60~62에서 “ARMCTRL-level” 이라는 이름의 irq chip을 설정하고 probe를 설정한다.
  • 코드 라인 64에서 default FIQ 인터럽트 처리기를 백업하기 위해 fiq 벡터 값과 fiq 모드에서 사용하는 레지스터들 일부(r8 ~ r12, sp, lr)를 백업해둔다.
    • fiq 사용 시 멀티플 레지스터 로드 및 저장 시 깨질 수 있기에 복원을 위해 초기 핸들러를 백업해 둔다.

 

 

GPIO 컨트롤러 Part

인터럽트 컨트롤러에서 초기화하지 않고 GPIO의 pinctrl 드라이버에서 초기화된다.  초기화 과정의 소스는 여기서 설명하지 않고 아래 그림만 참고한다.

 


IRQ exception 처리 후 컨트롤러가 설정한 핸들러 함수 호출

부모 컨트롤러  Part

bcm2836_arm_irqchip_handle_irq()

static void
__exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs)
{
        int cpu = smp_processor_id();
        u32 stat;

        stat = readl_relaxed(intc.base + LOCAL_IRQ_PENDING0 + 4 * cpu);
        if (stat & BIT(LOCAL_IRQ_MAILBOX0)) {
#ifdef CONFIG_SMP
                void __iomem *mailbox0 = (intc.base +
                                          LOCAL_MAILBOX0_CLR0 + 16 * cpu);
                u32 mbox_val = readl(mailbox0);
                u32 ipi = ffs(mbox_val) - 1;

                writel(1 << ipi, mailbox0);
                handle_IPI(ipi, regs);
#endif
        } else if (stat) {
                u32 hwirq = ffs(stat) - 1;

                handle_IRQ(irq_linear_revmap(intc.domain, hwirq), regs);
        }
}

mailbox0 레지스터를 읽어 pending irq가 있는 경우 IPI 핸들러를 호출하고 그렇지 않은 경우 IRQ 핸들러를 호출한다.

  • rpi2: 부모 인터럽트 컨틀롤러에서 발생한 irq들은 4개의 timer와 pmu 인터럽트이다.

 

  • 코드 라인 7~8에서 현재 cpu에 대한 local pending 레지스터를 읽어서 bit4를 통해 mailbox가 수신되었는지 확인한다. 만일 수신된 경우
  • 코드 라인 9~17에서 수신된 mailbox에서 가장 먼저(msb) 처리할 IPI 번호를 읽어 그 비트를 클리어하고 handle_IPI() 함수를 호출하여 IPI 처리를 수행하게 한다.
  • 코드 라인 18~22에서 hwirq  -> irq 번호로 reversemap을 사용하여 transalation하여 얻은 번호로 handle_irq()를 호출한다.

 

자식 컨트롤러  Part

bcm2836_chained_handle_irq()

drivers/irqchip/irq-bcm2835.c

 

static void bcm2836_chained_handle_irq(struct irq_desc *desc)
{
        u32 hwirq;

        while ((hwirq = get_next_armctrl_hwirq()) != ~0)
                generic_handle_irq(irq_linear_revmap(intc.domain, hwirq));
}

gpu pending 레지스터 값을 읽어서 hwirq 값을 irq로 변환한 후 generic_handle_irq() 함수를 호출한다.

  • 변환한 irq에 등록한 핸들러 함수를 호출한다.
    • rpi2: handle_level_irq()

 

get_next_armctrl_hwirq()

drivers/irqchip/irq-bcm2835.c

static u32 get_next_armctrl_hwirq(void)
{
        u32 stat = readl_relaxed(intc.pending[0]) & BANK0_VALID_MASK;

        if (stat == 0)
                return ~0;
        else if (stat & BANK0_HWIRQ_MASK)
                return MAKE_HWIRQ(0, ffs(stat & BANK0_HWIRQ_MASK) - 1);
        else if (stat & SHORTCUT1_MASK)
                return armctrl_translate_shortcut(1, stat & SHORTCUT1_MASK);
        else if (stat & SHORTCUT2_MASK)
                return armctrl_translate_shortcut(2, stat & SHORTCUT2_MASK);
        else if (stat & BANK1_HWIRQ)
                return armctrl_translate_bank(1);
        else if (stat & BANK2_HWIRQ)
                return armctrl_translate_bank(2);
        else
                BUG();
}

gpu pending 레지스터 값을 읽어서 hwirq로 변환한다.

  • 코드 라인 3에서 로컬 pending  레지스터 값을 읽어서 관련 비트들만 마스크한다.
    • timer 4개, mailbox 4개, gpu 매핑 1개, pmu 매핑 1개, shortcut 11개 비트들
  • 코드 라인 5~6에서 pending 인터럽트가 없는 경우 ~0(호출 함수에서 루프의 끝을 의미)을 반환한다.
  • 코드 라인 7~8에서 local 인터럽트 8개(bit7:0) 중 하나에 속한 경우 0번 뱅크에 해당하는 hwirq로 변환하여 반환한다.
  • 코드 라인 9~10에서 shortcut1에 해당하는 인터럽트가 발생한 경우 1번 뱅크에 해당하는 hwirq로 변환하여 반환한다.
    • bit10 ~ bit14 -> <7, 9, 10, 18, 19> + 32(bank1)
  • 코드 라인 11~12에서 shortcut2에 해당하는 인터럽트가 발생한 경우 2번 뱅크에 해당하는 hwirq로 변환하여 반환한다.
    • bit15 ~ bit20 -> <21, 22, 23, 24, 25, 30> + 64(bank2)
  • 코드 라인 13~14에서 bank1에 해당하는 인터럽트인 경우 gpu#1 pending 레지스터를 읽어 가장 우측 비트(lsb)가 1로 설정된 인터럽트에 해당하는 hwirq로 변환하여 반환한다.
    • bit0 ~ bit31 -> 30 ~ 61
  • 코드 라인 15~16에서 bank2에 해당하는 인터럽트인 경우 gpu#2 pending 레지스터를 읽어 가장 우측 비트(lsb)가 1로 설정된 인터럽트에 해당하는 hwirq로 변환하여 반환한다.
    • bit0 ~ bit31 -> 62 ~ 93

 

armctrl_translate_shortcut()

drivers/irqchip/irq-bcm2835.c

static u32 armctrl_translate_shortcut(int bank, u32 stat)
{
        return MAKE_HWIRQ(bank, shortcuts[ffs(stat >> SHORTCUT_SHIFT) - 1]);
}

shortcut에 해당하는 인터럽트가 발생한 경우 해당 뱅크에 해당하는 hwirq로 변환하여 반환한다.

  • bit10 ~ bit14 -> <7, 9, 10, 18, 19> + 32(bank1)
  • bit15 ~ bit20 -> <21, 22, 23, 24, 25, 30> + 64(bank2)

 

armctrl_translate_bank()

drivers/irqchip/irq-bcm2835.c

/*
 * Handle each interrupt across the entire interrupt controller.  This reads the
 * status register before handling each interrupt, which is necessary given that
 * handle_IRQ may briefly re-enable interrupts for soft IRQ handling.
 */

static u32 armctrl_translate_bank(int bank)
{
        u32 stat = readl_relaxed(intc.pending[bank]);

        return MAKE_HWIRQ(bank, ffs(stat) - 1);
}

gpu#1 또는 gpu#2 pending 레지스터에서 읽은 stat 값에서 가장 우측 비트에 해당하는 hwirq를 반환한다.

  • bit0 ~ bit31 -> 30 ~ 61
  • bit0 ~ bit31 -> 62 ~ 93

 

drivers/irqchip/irq-bcm2835.c

/* Put the bank and irq (32 bits) into the hwirq */
#define MAKE_HWIRQ(b, n)        ((b << 5) | (n))
#define HWIRQ_BANK(i)           (i >> 5)
#define HWIRQ_BIT(i)            BIT(i & 0x1f)

 


irq chip의 콜백 함수 – rpi2

ARMCTRL-level용

armctrl_chip

drivers/irqchip/irq-bcm2835.c

static struct irq_chip armctrl_chip = {
        .name = "ARMCTRL-level",
        .irq_mask = armctrl_mask_irq,
        .irq_unmask = armctrl_unmask_irq
};

bcm2835 인터럽트용 irq_chip 콜백 함수들

 

armctrl_mask_irq()

drivers/irqchip/irq-bcm2835.c

static void armctrl_mask_irq(struct irq_data *d)
{
        if (d->hwirq >= NUMBER_IRQS)
                writel_relaxed(REG_FIQ_DISABLE, intc.base + REG_FIQ_CONTROL);
        else
                writel_relaxed(HWIRQ_BIT(d->hwirq),
                               intc.disable[HWIRQ_BANK(d->hwirq)]);
}

요청한 인터럽트(hwirq) 라인을 마스크하여 인터럽트 진입을 허용하지 않게 한다.

  • 코드 라인 3~4에서 hwirq가 NUMBER_IRQS(96) 이상인 경우 FIQ 전체에 대한 인터럽트 마스크를 수행한다.
  • 코드 라인 5~7에서 요청 IRQ에 대한 마스크를 수행한다.
    • 1개 뱅크당 32개의 인터럽트 라인을 제어할 수 있다.

 

armctrl_unmask_irq()

drivers/irqchip/irq-bcm2835.c

static void armctrl_unmask_irq(struct irq_data *d)
{
        if (d->hwirq >= NUMBER_IRQS) {
                if (num_online_cpus() > 1) {
                        unsigned int data;
                        int ret;

                        if (!intc.local_regmap) {
                                pr_err("FIQ is disabled due to missing regmap\n");
                                return;
                        }

                        ret = regmap_read(intc.local_regmap,
                                          ARM_LOCAL_GPU_INT_ROUTING, &data);
                        if (ret) {
                                pr_err("Failed to read int routing %d\n", ret);
                                return;
                        }

                        data &= ~0xc;
                        data |= (1 << 2);
                        regmap_write(intc.local_regmap,
                                     ARM_LOCAL_GPU_INT_ROUTING, data);
                }

                writel_relaxed(REG_FIQ_ENABLE | hwirq_to_fiq(d->hwirq),
                               intc.base + REG_FIQ_CONTROL);
        } else {
                writel_relaxed(HWIRQ_BIT(d->hwirq),
                               intc.enable[HWIRQ_BANK(d->hwirq)]);
        }
}

요청한 인터럽트(hwirq) 라인을 언마스크하여 인터럽트 진입을 허용하게 한다.

  • 코드 라인 3에서 hwirq가 NUMBER_IRQS(96) 이상인 경우
  • 코드 라인 4~24에서 online cpu가 1개를 초과하는 경우 2개 이상인 경우 bcm2836에 있는 local routing 레지스터를 설정할 수 있고, 여기에서 irq cpu 라우팅을 fiq cpu 라우팅으로 변경한다.
    • local routing  레지스터 값은 하위 3비트만 유효하다.
      • 최하위 비트 2개는 cpu core 번호
      • bit2는 0=irq, 1=fiq
    • 이 조작으로 영향을 받는 인터럽트는 core 전용이 아닌 공통 인터럽트들로 아래와 같다.
      • GPU IRQ
      • Local Timer
      • AXI error
      • 미 사용 15개의 local peripheral 인터럽트들
    • 이 조작으로 영향을 받지 않는 인터럽트들은 core 인터럽트로 아래와 같다.
      • 4개의 코어 타이머 인터럽트들 x 4 cpus
      • 1개의 pm 인터럽트 x 4 cpus
      • 4개의 mailbox 인터럽트들 x 4 cpus
  • 코드 라인 26~27에서 FIQ Control 레지스터의 8번 비트를 켜고 bit0~bit7의 irq(fiq) source를 지정한다.
    • FIQ Control 레지스터: 버스(VC) 주소=0x7e00_b20c, ARM 물리 주소=0x3f00_b20c, ARM 가상 주소=0xf300_b20c
    • 1개의 irq 소스를 fiq로 라우팅할 수 있다.
      • rpi2: 고석 처리가 요구되는 usb를 fiq로 라우팅하여 사용한다.
  • 코드 라인 28~31에서 요청 IRQ를 enable을 시킨다.
    • 3개의 뱅크에서 각 뱅크당 최대 32개의 인터럽트 라인을 제어할 수 있다.

 


Argument 파싱 for DT

armctrl_xlate() – RPI2

arch/arm/mach-bcm2709/armctrl.c

/* from drivers/irqchip/irq-bcm2835.c */
static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr,
        const u32 *intspec, unsigned int intsize,
        unsigned long *out_hwirq, unsigned int *out_type)
{
        if (WARN_ON(intsize != 2))
                return -EINVAL;

        if (WARN_ON(intspec[0] >= NR_BANKS))
                return -EINVAL;

        if (WARN_ON(intspec[1] >= IRQS_PER_BANK))
                return -EINVAL;

        if (WARN_ON(intspec[0] == 0 && intspec[1] >= NR_IRQS_BANK0))
                return -EINVAL;

        if (WARN_ON(intspec[0] == 3 && intspec[1] > 3 && intspec[1] != 5 && intspec[1] != 9))
                return -EINVAL;

        if (intspec[0] == 0) 
                *out_hwirq = ARM_IRQ0_BASE + intspec[1];
        else if (intspec[0] == 1)
                *out_hwirq = ARM_IRQ1_BASE + intspec[1];
        else if (intspec[0] == 2)
                *out_hwirq = ARM_IRQ2_BASE + intspec[1];
        else
                *out_hwirq = ARM_IRQ_LOCAL_BASE + intspec[1];

        /* reverse remap_irqs[] */
        switch (*out_hwirq) {
        case INTERRUPT_VC_JPEG:
                *out_hwirq = INTERRUPT_JPEG;
                break;
        case INTERRUPT_VC_USB:
                *out_hwirq = INTERRUPT_USB;
                break;
        case INTERRUPT_VC_3D:
                *out_hwirq = INTERRUPT_3D;
                break;
        case INTERRUPT_VC_DMA2:
                *out_hwirq = INTERRUPT_DMA2;
                break;
        case INTERRUPT_VC_DMA3:
                *out_hwirq = INTERRUPT_DMA3;
                break;
        case INTERRUPT_VC_I2C:
                *out_hwirq = INTERRUPT_I2C;
                break;
        case INTERRUPT_VC_SPI:
                *out_hwirq = INTERRUPT_SPI;
                break;
        case INTERRUPT_VC_I2SPCM:
                *out_hwirq = INTERRUPT_I2SPCM;
                break;
        case INTERRUPT_VC_SDIO:
                *out_hwirq = INTERRUPT_SDIO;
                break;
        case INTERRUPT_VC_UART:
                *out_hwirq = INTERRUPT_UART;
                break;
        case INTERRUPT_VC_ARASANSDIO:
                *out_hwirq = INTERRUPT_ARASANSDIO;
                break;
        }

        *out_type = IRQ_TYPE_NONE;
        return 0;
}

DT 스크립트에 설정한 argumnet 값으로 hwirq 번호를 알아온다.

  • 인터럽트를 사용하는 디바이스는 “interrupts = { bank, irq }” 속성 값을 전달한다.
    • bcm2708(rpi) 및 bcm2709(rpi2)에서는 2 개의 인수를 받는데 처음 것은 bank 이고, 두 번째 것은 각 뱅크에 해당하는 개별 인터럽트 번호(0~31번 )이다.
    • rpi2 예) out_hwirq = 0~85, 96~99, 101, 105

 

  • 코드 라인 6~7에서 DT 스크립트에서 설정한 “interrupts = { bank, irq }” 형태로 인수가 2개가 아닌 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 9~10에서 bank 값이 4 이상인 경우 -EINVAL 에러를 반환한다.
    • rpi: 0~2까지 가능
    • rpi2: 0~3까지 가능
  • 코드 라인 12~13에서 irq 값이 bank당 최대 값 수(32) 이상인 경우 -EINVAL 에러를 반환한다.
    • rpi & rpi2: bank당 0~31까지 가능
  • 코드 라인 15~16에서 0번 bank를 사용하는 irq 값이 bank #0 최대 인터럽트 수 이상인 경우 -EIVAL 에러를 반환한다.
    • rpi & rpi2: bank당 0~20까지 가능
  • 코드 라인 18~19에서 3번 bank의 경우 irq 번호가 0~3, 5 및 9번이 아닌 경우 -EINVAL 에러를 반환한다.
    • 0~3: core timer, 5=mail box #1, 9=pmu
  • 코드 라인 21~22에서 bank 0번의 경우 64번 부터 hw 인터럽트가 시작된다.
  • 코드 라인 23~24에서 bank 1번의 경우 0번 부터 hw 인터럽트가 시작된다.
  • 코드 라인 25~26에서 bank 2번의 경우 32번 부터 hw 인터럽트가 시작된다.
  • 코드 라인 27~28에서 bank 3번의 경우 96번 부터 hw 인터럽트가 시작된다.
  • 코드 라인 41~65에서 Video Core에서 사용하는 인터럽트들(#0 ~ #64) 중 11개가 공유되어 Local ARM에 라우팅(#74~#84) 된다.
    • 예) 7(vc_usb)  -> 74(arm_usb)
  • 코드 라인 67~68에서 에러가 없는 경우 out_type에 IRQ_TYPE_NONE을 대입한후 0을 반환한다.

 


GPIO pinctrl-bcm2835용

rpi 및 rpi2에는 2개의 뱅크에 나뉘어 총 54개의 gpio 라인이 제공되고 이를 28개, 18개, 8개로 나누어 81~83번의 hw 인터럽트를 발생시킨다.

 

bcm2835_gpio_irq_chip

drivers/pinctrl/bcm/pinctrl-bcm2835.c

static struct irq_chip bcm2835_gpio_irq_chip = {
        .name = MODULE_NAME,
        .irq_enable = bcm2835_gpio_irq_enable,
        .irq_disable = bcm2835_gpio_irq_disable,
        .irq_set_type = bcm2835_gpio_irq_set_type,
};

bcm2835의 gpio 인터럽트용 irq_chip 콜백 함수들

 

bcm2835_gpio_irq_enable()

drivers/pinctrl/bcm/pinctrl-bcm2835.c

static void bcm2835_gpio_irq_enable(struct irq_data *data)
{
        struct bcm2835_pinctrl *pc = irq_data_get_irq_chip_data(data);
        unsigned gpio = irqd_to_hwirq(data);
        unsigned offset = GPIO_REG_SHIFT(gpio);
        unsigned bank = GPIO_REG_OFFSET(gpio);
        unsigned long flags;

        spin_lock_irqsave(&pc->irq_lock[bank], flags);
        set_bit(offset, &pc->enabled_irq_map[bank]);
        bcm2835_gpio_irq_config(pc, gpio, true);
        spin_unlock_irqrestore(&pc->irq_lock[bank], flags);
}

gpio irq를 enable 한다. (hwirq=0~53)

  • 코드 라인 3에서 irq_chip->chip_data에 저장해둔 pc(pin control)를 알아온다.
  • 코드 라인 4에서 gpio hwirq 번호를 알아온다.
  • 코드 라인 5~6에서 hwirq에 대한 bank와 offset을 구한다.
    • 예) hwirq=53인 경우 bank=1, offset=21
  • 코드 라인 10에서 hwirq에 해당하는 enable 비트를 설정한다.
  • 코드 라인 11에서 hwirq의 트리거 타입에 따른 gpio 레지스터를 조작하여 enable 설정을 한다.

 

bcm2835_gpio_irq_disable()

drivers/pinctrl/bcm/pinctrl-bcm2835.c

static void bcm2835_gpio_irq_disable(struct irq_data *data)
{
        struct bcm2835_pinctrl *pc = irq_data_get_irq_chip_data(data);
        unsigned gpio = irqd_to_hwirq(data);
        unsigned offset = GPIO_REG_SHIFT(gpio);
        unsigned bank = GPIO_REG_OFFSET(gpio);
        unsigned long flags;

        spin_lock_irqsave(&pc->irq_lock[bank], flags);
        bcm2835_gpio_irq_config(pc, gpio, false);
        /* Clear events that were latched prior to clearing event sources */
        bcm2835_gpio_set_bit(pc, GPEDS0, gpio);
        clear_bit(offset, &pc->enabled_irq_map[bank]);
        spin_unlock_irqrestore(&pc->irq_lock[bank], flags);
}

gpio irq를 disable 한다. (hwirq=0~53)

  • 코드 라인 3에서 irq_chip->chip_data에 저장해둔 pc(pin control)를 알아온다.
  • 코드 라인 4에서 hwirq 번호를 알아온다.
    • rpi2: 81 ~ 83
  • 코드 라인 5~6에서 hwirq에 대한 bank와 offset을 구한다.
    • 예) hwirq=81인 경우 bank=2, offset=17
  • 코드 라인 10에서 hwirq의 트리거 타입에 따른 gpio 레지스터를 조작하여 disable 설정을 한다.
  • 코드 라인 12에서 2 개의 GPEDS(GPIO Event Detect Status) Register 중 요청 hwirq에 해당하는 비트를 1로 기록하여 이벤트 발생 비트를 클리어한다.
  • 코드 라인 13에서 hwirq에 해당하는 enable 비트를 클리어한다.

 

bcm2835_gpio_irq_set_type()

drivers/pinctrl/bcm/pinctrl-bcm2835.c

static int bcm2835_gpio_irq_set_type(struct irq_data *data, unsigned int type)
{
        struct bcm2835_pinctrl *pc = irq_data_get_irq_chip_data(data);
        unsigned gpio = irqd_to_hwirq(data);
        unsigned offset = GPIO_REG_SHIFT(gpio);
        unsigned bank = GPIO_REG_OFFSET(gpio);
        unsigned long flags;
        int ret;

        spin_lock_irqsave(&pc->irq_lock[bank], flags);

        if (test_bit(offset, &pc->enabled_irq_map[bank]))
                ret = __bcm2835_gpio_irq_set_type_enabled(pc, gpio, type);
        else 
                ret = __bcm2835_gpio_irq_set_type_disabled(pc, gpio, type);

        spin_unlock_irqrestore(&pc->irq_lock[bank], flags);

        return ret;
}

gpio irq에 해당하는 트리거 타입을 설정한다. (hwirq=0~53)

  • 타입 설정 전에 요청 irq에 대해 disable 한 후 트리거 타입을 바꾼다. 그런 후 enable 비트가 설정된 경우 해당 irq에 대해 enable 시킨다.

 


bcm2836-gpu용

bcm2836_arm_irqchip_gpu

drivers/irqchip/irq-bcm2836.c

static struct irq_chip bcm2836_arm_irqchip_gpu = {
        .name           = "bcm2836-gpu",
        .irq_mask       = bcm2836_arm_irqchip_mask_gpu_irq,
        .irq_unmask     = bcm2836_arm_irqchip_unmask_gpu_irq,
};

bcm2836의 GPU 인터럽트용 irq_chip 콜백 함수들

 

bcm2836_arm_irqchip_mask_gpu_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_mask_gpu_irq(struct irq_data *d)
{
}

1개 gpu 인터럽트의 mask 처리 요청에 대해 아무것도 수행하지 않는다.

 

bcm2836_arm_irqchip_unmask_gpu_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_unmask_gpu_irq(struct irq_data *d)
{
}

1개 gpu 인터럽트의 unmask 처리 요청에 대해 아무것도 수행하지 않는다.

 

bcm2836-pmu용

bcm2836_arm_irqchip_pmu

drivers/irqchip/irq-bcm2836.c

static struct irq_chip bcm2836_arm_irqchip_pmu = {
        .name           = "bcm2836-pmu",
        .irq_mask       = bcm2836_arm_irqchip_mask_pmu_irq,
        .irq_unmask     = bcm2836_arm_irqchip_unmask_pmu_irq,
};

bcm2836의 pmu 인터럽트용 irq_chip 콜백 함수들

 

bcm2836_arm_irqchip_mask_pmu_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_mask_pmu_irq(struct irq_data *d)
{
        writel(1 << smp_processor_id(), intc.base + LOCAL_PM_ROUTING_CLR);
}

1개 pmu 인터럽트의 mask 처리 요청을 수행한다.

 

bcm2836_arm_irqchip_unmask_pmu_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_unmask_pmu_irq(struct irq_data *d)
{
        writel(1 << smp_processor_id(), intc.base + LOCAL_PM_ROUTING_SET);
}

1개 pmu 인터럽트의 unmask 처리 요청을 수행한다.

 

bcm2836-timer용

bcm2836_arm_irqchip_timer

drivers/irqchip/irq-bcm2836.c

static struct irq_chip bcm2836_arm_irqchip_timer = {
        .name           = "bcm2836-timer",
        .irq_mask       = bcm2836_arm_irqchip_mask_timer_irq,
        .irq_unmask     = bcm2836_arm_irqchip_unmask_timer_irq,
};

bcm2836의 각 코어에 구성된 4개 local 타이머용 irq_chip 콜백 함수들

 

bcm2836_arm_irqchip_mask_timer_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_mask_timer_irq(struct irq_data *d)
{
        bcm2836_arm_irqchip_mask_per_cpu_irq(LOCAL_TIMER_INT_CONTROL0,
                                             d->hwirq - LOCAL_IRQ_CNTPSIRQ,
                                             smp_processor_id());
}

4개의 local timer 인터럽트는 cpu별로 동작하는 per-cpu 타입인데 이 중 현재 cpu에 요청한 타이머 인터럽트(irq_data->hwirq(0~3))를 마스크 처리한다.

  • cpu별 LOCAL_TIMER_INT_CONTROL 레지스터
    • 0x4000_0040 (#0번 core) ~ 0x4000_004c(#3번 core)
  • 4 개 timer 인터럽트 중 요청한 인터럽트(0 ~ 3번 중 하나)를 마스크한다.

 

bcm2836_arm_irqchip_mask_per_cpu_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_mask_per_cpu_irq(unsigned int reg_offset,
                                                 unsigned int bit,
                                                 int cpu)
{
        void __iomem *reg = intc.base + reg_offset + 4 * cpu;

        writel(readl(reg) & ~BIT(bit), reg);
}

bcm2836 local 인터럽트 컨트롤러에서 per-cpu 인터럽트에 대한 마스크를 수행한다.

  • rpi2:
    • intc.base는 0x4000_0000
    • reg_offset는 0x40

 

bcm2836_arm_irqchip_unmask_timer_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_unmask_timer_irq(struct irq_data *d)
{
        bcm2836_arm_irqchip_unmask_per_cpu_irq(LOCAL_TIMER_INT_CONTROL0,
                                               d->hwirq - LOCAL_IRQ_CNTPSIRQ,
                                               smp_processor_id());
}

4개의 local timer 인터럽트는 cpu별로 동작하는 per-cpu 타입인데 이 중 현재 cpu에 요청한 타이머 인터럽트(irq_data->hwirq(0~3))를 언마스크 처리한다.

  • cpu별 LOCAL_TIMER_INT_CONTROL 레지스터
    • 0x4000_0040 (#0번 core) ~ 0x4000_004c(#3번 core)
  • 4 개 timer 인터럽트 중 요청한 인터럽트(0 ~ 3번 중 하나)를 클리어한다.

 

bcm2836_arm_irqchip_unmask_per_cpu_irq()

drivers/irqchip/irq-bcm2836.c

static void bcm2836_arm_irqchip_unmask_per_cpu_irq(unsigned int reg_offset,
                                                 unsigned int bit,
                                                 int cpu)
{
        void __iomem *reg = intc.base + reg_offset + 4 * cpu;

        writel(readl(reg) | BIT(bit), reg);
}

bcm2836 local 인터럽트 컨트롤러에서 per-cpu 인터럽트에 대한 언마스크를 수행한다.

 


머신 specific 인터럽트 컨트롤러 초기화

디바이스 트리를 사용하지 않는 경우 다음 코드들이 적용된다.

  • 참고로 ARM32 시스템에서 최근 v4.4 이상 커널들과 모든 ARM64 시스템은 모두 디바이스 트리를 사용하는 방식을 사용한다.

 

bcm2708_init_irq() & bcm2709_init_irq()

arch/arm/mach-bcm2708/bcm2708.c

void __init bcm2708_init_irq(void)
{
        armctrl_init(__io_address(ARMCTRL_IC_BASE), 0, 0, 0);
}

인터럽트 컨트롤러의 base 가상 주소를 인수로 인터럽트 컨트롤러를 초기화한다.

 

arch/arm/mach-bcm2709/bcm2709.c

void __init bcm2709_init_irq(void)
{
        armctrl_init(__io_address(ARMCTRL_IC_BASE), 0, 0, 0);
}

인터럽트 컨트롤러의 base 가상 주소를 인수로 인터럽트 컨트롤러를 초기화한다.

 

rpi vs rpi2 ARM 인터럽트 컨트롤러 주소

BCM2708을 사용하는 rpi와 BCM2709를 사용하는 rpi2는 내부에서 사용하는 디바이스들의 구성은 거의 유사하나 peripheral base 물리 주소가 다르고 이에 따른 가상 주소도 다르다.

  • peripheral base 물리 주소 -> 가상 주소
    • rpi: 0x2000_0000 -> 0xf200_0000
    • rpi2: 0x3f00_0000 -> 0xf300_0000

 

rpi 물리 주소

#define BCM2708_PERI_BASE        0x20000000
#define ARM_BASE                 (BCM2708_PERI_BASE + 0xB000)    /* BCM2708 ARM control block */
#define ARMCTRL_IC_BASE          (ARM_BASE + 0x200)           /* ARM interrupt controller */
  • rpi의 인터럽트 컨트롤러 물리 주소 = 0x2000_b200
  • rpi의 인터럽트 컨트롤러 가상 주소 = 0xf200_b200

 

rpi2 물리 주소

#define BCM2708_PERI_BASE        0x3F000000
#define ARM_BASE                 (BCM2708_PERI_BASE + 0xB000)    /* BCM2708 ARM control block */
#define ARMCTRL_IC_BASE          (ARM_BASE + 0x200)           /* ARM interrupt controller */
  • rpi2의 인터럽트 컨트롤러 물리 주소 = 0x3f00_b200
  • rpi2의 인터럽트 컨트롤러 가상 주소 = 0xf300_b200

 

armctrl_init() – bcm2709 용

arch/arm/mach-bcm2709/armctrl.c

/**
 * armctrl_init - initialise a vectored interrupt controller
 * @base: iomem base address
 * @irq_start: starting interrupt number, must be muliple of 32
 * @armctrl_sources: bitmask of interrupt sources to allow
 * @resume_sources: bitmask of interrupt sources to allow for resume
 */
int __init armctrl_init(void __iomem * base, unsigned int irq_start,
                        u32 armctrl_sources, u32 resume_sources)
{
        unsigned int irq;

        for (irq = 0; irq < BCM2708_ALLOC_IRQS; irq++) {
                unsigned int data = irq;
                if (irq >= INTERRUPT_JPEG && irq <= INTERRUPT_ARASANSDIO)
                        data = remap_irqs[irq - INTERRUPT_JPEG];
                if (irq >= IRQ_ARM_LOCAL_CNTPSIRQ && irq <= IRQ_ARM_LOCAL_TIMER) {
                        irq_set_percpu_devid(irq);
                        irq_set_chip_and_handler(irq, &armctrl_chip, handle_percpu_devid_irq);
                        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
                } else {
                        irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq);
                        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE | IRQF_DISABLED);
                }
                irq_set_chip_data(irq, (void *)data);
        }

        armctrl_pm_register(base, irq_start, resume_sources);
        init_FIQ(FIQ_START);
        armctrl_dt_init();
        return 0;
}

bcm2709용 인터럽트 컨트롤러를 초기화하고 각 인터럽트에 대한 핸들러들을 준비한다.

  • 코드 라인 6에서 인터럽트 컨트롤러에 할당된 irq 수 만큼 루프를 돈다.
    • rpi: SPARE_ALLOC_IRQS=394
    • rpi2: SPARE_ALLOC_IRQS=480
  • 코드 라인 8~9에서 GPU용 리눅스 irq를 hwirq로 변환한다.
    • irq 번호가 INTERRUPT_JPEG(74) ~ INTERRUPT_ARASANSDIO(84) 번까지의 irq는 GPU(VC)가 ARM용으로 routing 한 리눅스 irq 번호이므로 기존 GPU(VC)용 hwirq 번호를 알아낸 후 data에 대입한다.
    • GPU(VC) hardware irq 중 11개의 irq는 ARM에서 같이 공유하여 사용하기 위해 ARM pending 레지스터로 라우팅(변환)하여 사용한다.
    • remap_irqs[]
      • INTERRUPT_VC_JPEG(7)
      • INTERRUPT_VC_USB(9)
      • (..생략..)
      • INTERRUPT_VC_ARASANSDIO(62) 까지 총 11개가 등록되어 있다.
  • 코드 라인 10~13에서 irq 번호가 IRQ_ARM_LOCAL_CNTPSIRQ(96) ~ IRQ_ARM_LOCAL_TIMER(107)번 사이의 local 인터럽트인 경우 core별로 전용 사용되므로 per-cpu 디바이스를 취급하는 핸들러를 설정한다.
  • 코드 라인 14~17에서 그 외의 irq 번호인 경우 일반 핸들러를 설정한다.
  • 코드 라인 18에서 hwirq 번호로 chip_data를 구성한다.
  • 코드 라인 21에서 PM(Power Management)를 위해 armctrl_info 구조체에 인터럽트 정보를 대입해둔다.
  • 코드 라인 22에서 FIQ 인터럽트 핸들러를 준비한다.
  • 코드 라인 23에서 Device Tree 스크립트를 통해 해당 인터럽트 컨트롤러 정보를 읽어 irq_domain을 구성한다.
  • 코드 라인 24에서 성공 값 0을 반환한다.

 

다음 그림은 armctrl_init() 함수의 처리 과정을 보여준다.

 

 


Machine용 IRQ Handler (Kernel v4.0 for rpi2)

 

arch_irq_handler_default 매크로(for RPI2)

arch/arm/mach-bcm2709/include/mach/entry-macro.S

/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
        .macro  arch_irq_handler_default
1:	get_irqnr_and_base r0, r2, r6, lr
	.endm

 

IPI(Inter Process Interrupt) 처리

arch/arm/mach-bcm2709/include/mach/entry-macro.S

        .macro  get_irqnr_and_base, irqnr, irqstat, base, tmp

        /* get core number */
        mrc     p15, 0, \base, c0, c0, 5
        ubfx    \base, \base, #0, #2

        /* get core's local interrupt controller */
        ldr     \irqstat, = __io_address(ARM_LOCAL_IRQ_PENDING0)        @ local interrupt source
        add     \irqstat, \irqstat, \base, lsl #2
        ldr     \tmp, [\irqstat]
#ifdef CONFIG_SMP
        /* test for mailbox0 (IPI) interrupt */
        tst     \tmp, #0x10
        beq     1030f

        /* get core's mailbox interrupt control */
        ldr     \irqstat, = __io_address(ARM_LOCAL_MAILBOX0_CLR0)       @ mbox_clr
        add     \irqstat, \irqstat, \base, lsl #4
        ldr     \tmp, [\irqstat]
        clz     \tmp, \tmp
        rsb     \irqnr, \tmp, #31
        mov     \tmp, #1
        lsl     \tmp, \irqnr
        str     \tmp, [\irqstat]  @ clear interrupt source
        dsb
        mov     r1, sp
        adr     lr, BSYM(1b)
        b       do_IPI
#endif

 

pending 레지스터를 읽어 IPI 처리 요청이 있는 경우 mailbox0를 읽어 인터럽트 소스를 알아와서 모든 설정된 IPI 처리를 한다. 처리 순서는 높은 IPI 번호부터 처리한다. (irqnr=r0, irqstat=r2, base=r6, tmp=lr)

  • 코드 라인 4~10에서 cpu 번호를 구한 후 그 cpu에 해당하는 pending 레지스터 값을 읽어와서 \tmp에 대입한다.
    • 예) cpu #3에 대한 레지스터 가상 주소: ARM_LOCAL_IRQ_PENDING0(0xf400_0060) + cpu(3) * 4 = 0xf400_006c
  • 코드 라인 13~14에서 IPI(mailbox0) 인터럽트 요청이 아닌 경우 1030 레이블로 이동한다.
    • pending 레지스터의 0x10 비트: mailbox0
  • 코드 라인 17~19에서 cpu에 해당하는 mailbox0 값을 읽어와서 \tmp에 대입한다.
  • 코드 라인 20~21에서 가장 좌측에 설정된 인터럽트 비트를 \irqnr에 대입한다. (IPI는 역순으로 우선 처리된다.)
  • 코드 라인 22~24에서 클리어하고자 하는 IPI 인터럽트 비트만 설정하여 읽어왔던 mailbox0에 저장한다.
    • clz: 좌측부터 0으로 시작하는 비트 수
    • mailbox는 str 명령을 통하여 값이 저장되는 것이 아니라 값에 해당하는 인터럽트 소스들을 클리어 한다.
  • 코드 라인 26~28에서 두 번째 인수 r1에 스택에 있는 pt_regs를 대입하고, 복귀 주소로 arch_irq_handler_default 매크로 안에 있는 get_irqnr_and_base 호출 위치를 설정한 다음 IPI 처리를 위해 do_IPI() 함수를 호출한다.
    • IPI 처리가 다 완료될 때 까지 루프를 돈다.
    • 참고: do_IPI() | 문c

 

 

 

ARM(GPU) 및 ARM 인터럽트 처리
1030:
        /* check gpu interrupt */
        tst     \tmp, #0x100
        beq     1040f

        ldr     \base, =IO_ADDRESS(ARMCTRL_IC_BASE)
        /* get masked status */
        ldr     \irqstat, [\base, #(ARM_IRQ_PEND0 - ARMCTRL_IC_BASE)]
        mov     \irqnr, #(ARM_IRQ0_BASE + 31)
        and     \tmp, \irqstat, #0x300           @ save bits 8 and 9
        /* clear bits 8 and 9, and test */
        bics    \irqstat, \irqstat, #0x300
        bne     1010f

        tst     \tmp, #0x100
        ldrne   \irqstat, [\base, #(ARM_IRQ_PEND1 - ARMCTRL_IC_BASE)]
        movne   \irqnr, #(ARM_IRQ1_BASE + 31)
        @ Mask out the interrupts also present in PEND0 - see SW-5809
        bicne   \irqstat, #((1<<7) | (1<<9) | (1<<10))
        bicne   \irqstat, #((1<<18) | (1<<19))
        bne     1010f

        tst     \tmp, #0x200
        ldrne   \irqstat, [\base, #(ARM_IRQ_PEND2 - ARMCTRL_IC_BASE)]
        movne   \irqnr, #(ARM_IRQ2_BASE + 31)
        @ Mask out the interrupts also present in PEND0 - see SW-5809
        bicne   \irqstat, #((1<<21) | (1<<22) | (1<<23) | (1<<24) | (1<<25))
        bicne   \irqstat, #((1<<30))
        beq     1020f
1010:
        @ For non-zero x, LSB(x) = 31 - CLZ(x^(x-1))
        sub     \tmp, \irqstat, #1
        eor     \irqstat, \irqstat, \tmp
        clz     \tmp, \irqstat
        sub     \irqnr, \tmp
        b       1050f

 

pending #0 레지스터를 읽어 ARM(GPU) #1, ARM(GPU)#2, ARM #0 인터럽트 순서대로 발생한 인터럽트 소스의 ISR을 수행한다.

  • 예) ARM(GPU) #1에 배치된 arm dma #0 인터럽트가 요청된 경우
    • pending 0 레지스터 값=0x100, pending 1 레지스터 값=0x1_0000 (bit16), 최종 인터럽트 번호 \irqnr=16
  • 예) ARM(GPU) #2에 배치한 arm gpio 인터럽트가 요청된 경우
    • pending 0 레지스터 값=0x200, pending 2 레지스터 값=0x10_0000 (bit20), 최종 인터럽트 번호 \irqnr=52
  • 예) ARM #0에 배치한 arm uart 인터럽트가 요청된 경우
    • pending 0 레지스터 값=0x8_0000 (bit19), 최종 인터럽트 번호 \irqnr=83

 

  • 코드 라인 3~4에서 ARM(GPU) 인터럽트 처리 요청이 없으면 1040 레이블로 이동한다.
  • 코드 라인 6~8에서 pending #0 레지스터 값을 읽어 /irqstat에 저장한다.
    • ARM 인터럽트 컨트롤러 가상 주소를 구해 \base에 저장한다.
      • =IO_ADDRESS(0x3f00_0000(기본 주소) + 0xb000(peri offset) + 0x200(인터럽트 컨트롤러 offset) = 0xf300_b200
  • 코드 라인 9에서 ARM #0이 담당하는 IRQ 끝 번호 값(95)을 \irqnr에 대입한다.
  • 코드 라인 10~13에서 pending #0 레지스터에서 읽었던 /irqstat에서 ARM #1(GPU #1) 또는 ARM #2(GPU #2)에 해당하는 비트를 클리어한다. 클리어 전에도 설정된 적 없으면 ARM #0 인터럽트를 처리하기 위해 1010 레이블로 이동한다.
  • 코드 라인 15~21에서 ARM #1(GPU #1)에 해당하는 경우 pending #1 레지스터 값을 읽어 /irqstat에 저장하되 VideoCore에서 사용하는 인터럽트에 해당하는 7, 9, 10, 18, 19번 비트들을 제거한다. IRQ #1(GPU #1)의 인터럽트 끝 번호(31)를 \irqnr에 대입한 후 해당 인터럽트 수행을 위해 1010 레이블로 이동한다.
  • 코드 라인 23~29에서 ARM #2(GPU #2)에 해당하는 경우 pending #2 레지스터 값을 읽어 /irqstat에 저장하되 VideoCore에서 사용하는 인터럽트에 해당하는21~25, 30번 비트들을 제거한다. IRQ #2(GPU #2)의 인터럽트 끝 번호(63)를 \irqnr에 대입하고 해당 인터럽트 수행을 위해 계속 아래 1010 레이블로 진행한다.
  • 코드 라인 32~36에서 처리할 인터럽트 끝 번호(irq0=95, irq1=31, ir12=63, local irq=127)에서 \irqstat의 첫 인터럽트 비트에 해당하는 번호를 뺀 후 해당 인터럽트 수행을 위해 1050 레이블로 이동한다.

 

Local Interrupt 처리
1040:
        cmp     \tmp, #0
        beq     1020f

        /* handle local (e.g. timer) interrupts */
        @ For non-zero x, LSB(x) = 31 - CLZ(x^(x-1))
        mov     \irqnr, #(ARM_IRQ_LOCAL_BASE + 31)
        sub     \irqstat, \tmp, #1
        eor     \irqstat, \irqstat, \tmp
        clz     \tmp, \irqstat
        sub     \irqnr, \tmp
1050:
        mov     r1, sp
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        adr     lr, BSYM(1b)
        b       asm_do_IRQ

1020:   @ EQ will be set if no irqs pending
        .endm

local ARM 인터럽트 처리요청이 있는 경우 발생한 인터럽트 소스의 ISR을 수행한다.

  • 예) Local ARM 인터럽트에 배치한 arm timer 인터럽트가 요청된 경우
    • local interrupt controller 레지스터 값=0x8, 최종 인터럽트 번호 \irqnr=99

 

  • 코드 라인 2~3에서 pending 레지스터 값이 0인 경우 더이상 처리할 인터럽트가 없으므로 종료한다.
  • 코드 라인 7~11에서 Local ARM이 처리하는 끝 번호(127) 값을 \irqnr에 대입한다.  끝 번호(127)에서 /tmp 인터럽트 비트들 중 가장 마지막 비트에 해당하는 번호를  뺀다
  • 코드 라인 13~18에서 r1 레지스터에 pt_regs 주소를 대입하고, lr에 이 매크로(get_irqnr_and_base)를 호출하는 곳을 담고 ISR을 호출한다.

 


FIQ 초기화

init_FIQ()

arch/arm/kernel/fiq.c

void __init init_FIQ(int start)
{
        unsigned offset = FIQ_OFFSET;
        dfl_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
        get_fiq_regs(&dfl_fiq_regs);
        fiq_start = start;
}

default FIQ 인터럽트 처리기를 백업하기 위해 fiq 벡터 값과 fiq 모드에서 사용하는 레지스터들 일부(r8 ~ r12, sp, lr)를 백업해둔다.

  • fiq가 필요한 owner task에서 핸들러를 준비하여 요청/해제등을 수행 시 파괴된 레지스터와 fiq 벡터들을 복원시 사용하기 위해 백업해둔다.
    • STM 및 LDM 수행은 보통 atomic 하게 수행되지만 cpu가 low interrupt latency 모드를 사용하는 경우 STM과 LDM 명령이 atomic하게 이루어지지 않아 STM 도중에 fiq exception 되는 경우 완전하게 스택에 기록하지 않는 문제가 발생하고 fiq 인터럽트 서비스 루틴 수행 후 다시 원래 모드 루틴으로 복귀할 때 LDM으로 읽은 값(최종 값이 보통 pc)에 가베지가 로드되어 문제가 발생할 수 있다. 이런 경우를 위해 fiq 모드에서 사용하였던 default 인터럽트 핸들러용 레지스터들을 백업해두고 이를 복원하는 용도로 사용한다.
    • fiq는 irq에 비해 약 10 ~ 20여배 latency가 작다. (10 ~ 20 ns vs 수 백 ns)
    • 참고: [ARM] 3256/1: Make the function-returning ldm’s use sp as the base register
  • rpi2 예)
    • “usb_fiq” 라는 이름의 usb 드라이버에서 fiq를 사용한다.
    • hcd_init_fiq() – drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c

 

  • 코드 라인 3~4에서 fiq에 해당하는 exception 벡터에 기록된 명령을 읽어와서 전역 변수 dfl_fiq_insn에 저장한다.
  • 코드 라인 5에서 fiq 모드로 진입한 후 레지스터 백업 장소 dfl_fiq_regs->r8 위치에 레지스터 r8 ~ r12, sp, lr을 저장하고 다시 svc 모드로 돌아온다.
  • 코드 라인 6에서 fiq_start에 hw irq 시작 번호를 대입한다.
    • rpi: 85
    • rpi2: 128

 

ENTRY(__get_fiq_regs)
        mov     r2, #PSR_I_BIT | PSR_F_BIT | FIQ_MODE
        mrs     r1, cpsr
        msr     cpsr_c, r2      @ select FIQ mode
        mov     r0, r0          @ avoid hazard prior to ARMv4
        stmia   r0!, {r8 - r12}
        str     sp, [r0], #4
        str     lr, [r0]
        msr     cpsr_c, r1      @ return to SVC mode 
        mov     r0, r0          @ avoid hazard prior to ARMv4
        ret     lr
ENDPROC(__get_fiq_regs)

FIQ 모드로 진입한 후 인수 r0 레지스터가 가리키는 주소에 현재 레지스터 r8 ~ r12, sp, lr을 저장하고 다시 SVC 모드로 돌아온다.

 

Device Tree에서 인터럽트 컨트롤러 노드 정보를 읽어 irq domain 생성

armctrl_dt_init()

arch/arm/mach-bcm2709/armctrl.c

void __init armctrl_dt_init(void)
{
        struct device_node *np;
        struct irq_domain *domain;

        np = of_find_compatible_node(NULL, NULL, "brcm,bcm2708-armctrl-ic");
        if (!np)
                return;

        domain = irq_domain_add_legacy(np, BCM2708_ALLOC_IRQS,
                                        IRQ_ARMCTRL_START, 0,
                                        &armctrl_ops, NULL);
        WARN_ON(!domain);
}

디바이스 트리 스크립트에 설정된 인터럽트 컨트롤러 명이 “brcm,bcm2708-armctrl-ic”인 경우 irq_domain을 추가하고 legacy 방법으로 domain을 최대 할당 인터럽트 수 만큼 지정한다.

  • 최대 할당 인터럽트 수
    • rpi: 394
    • rpi2: 480

 

참고

 

Interrupts -10- (irq partition)

<kernel v5.4>

Interrupts -10- (irq partition)

PPI는 per-cpu 인터럽트들은 irq번호가 동일하다. 그런데 클러스터마다 인터럽트 트리거 타입 설정 및 인터럽트 처리 방법이 다른 경우가 발생하여 이를 처리하기 위해 클러스터 단위로 파티션을 나누어 처리하였다.

 

per-cpu 인터럽트(PPI)

다음 그림은 PPI에 해당하는 per-cpu 인터럽트와 SPI에 해당하는 일반 인터럽트가 처리될 때의 흐름을 보여준다.

  • 1개의 per-cpu 인터럽트는 cpu 수 만큼 라인을 가지며 각 cpu에 라우팅되고, 하나의 irq 디스크립터에서 처리된다.

 

다음 그림은 GIC v3 드라이버가 PPI 및 SPI에서 수신된 인터럽트를 처리할 때 사용하는 도메인과 irq_chip의 operations 함수들을 보여준다.

 

IRQ Partition

다음 그림은 per-cpu 인터럽트를 처리할 때 PPI용 irq 아래에 하위 파티션 도메인 및 irq 디스크립터들을 구성하고, 호출하는 과정을 보여준다.

 

다음 그림은 per-cpu 인터럽트를 처리 하기 위해 생성된 파티션 디스크립터의 내부 구조를 보여준다.

  • GIC v3 드라이버는 초기화 시 모든 PPI 수 만큼 파티션 디스크립터를 생성한다. 그러나 실제 운영은 파티션 요청한 디바이스에 한하여 운영된다.

 

다음은 디바이스 트리에서 일반 PPI를 사용하는 디바이스와 irq 파티션을 사용하는 디바이스의 구성을 보여준다.

  • irq 파티션을 사용하려면 인터럽트 컨트롤러 노드내에 ppi-partitions을 구성해야 한다.
  • big/little에 사용하는 pmu 디바이스가 각각의 칩에서 별도의 인터럽트 핸들러를 동작시키기위해 irq 파티션을 사용한다.
    • 디바이스가 irq 파티션을 사용하려면 interrupts 속성에서 4개 이상의 인자가 필요하고 4 번째 인자는 파티션을 가리키는 phandle을 사용해야 한다. (irq 파티션을 사용하지 않을 때 4 번째 인자는 0이다)

 

per-cpu 인터럽트를 파티션으로 나눈 인터럽트는 cascaded(chained) irq 연결에 의해 파티션마다 별도의 처리가 가능하다.

 

다음은 QEMU/KVM를 사용한 Guest OS에서 big/little 코어 각각 1나씩 irq-partition을 만든 경우이다.

GICv3: 256 SPIs implemented
GICv3: 0 Extended SPIs implemented
GICv3: Distributor has no Range Selector support
GICv3: 16 PPIs implemented
GICv3: no VLPI support, no direct LPI support
GICv3: CPU0: found redistributor 0 region 0:0x00000000080a0000
GICv3: GIC: PPI partition interrupt-partition-0[0] { /cpus/cpu@0[0] }
GICv3: GIC: PPI partition interrupt-partition-1[1] { /cpus/cpu@1[1] }

 

arm-pmu 디바이스가 파티션되어 운영되는 모습을 알 수 있다.

  • 하위 파티션 도메인에서 생성된 hwirq는 순서대로 0부터 시작한다.
  • “GICv3-23” 출력된 메시지는 INTID#23 이라는 의미이며, PPI#7과 동일하다.
$ cat /proc/interrupts
           CPU0       CPU1
 12:       1822       1678     GICv3  27 Level     arch_timer
 47:       1527          0     GICv3  78 Edge      virtio0
 48:         17          0     GICv3  79 Edge      virtio1
 50:          0          0     GICv3  34 Level     rtc-pl031
 51:         41          0     GICv3  33 Level     uart-pl011
 52:          0          0  GICv3-23   0 Level     arm-pmu
 53:          0          0  GICv3-23   1 Level     arm-pmu
 54:          0          0  9030000.pl061   3 Edge      GPIO Key Poweroff
IPI0:       854        798       Rescheduling interrupts
IPI1:         8        800       Function call interrupts
IPI2:         0          0       CPU stop interrupts
IPI3:         0          0       CPU stop (for crash dump) interrupts
IPI4:         0          0       Timer broadcast interrupts
IPI5:         0          0       IRQ work interrupts
IPI6:         0          0       CPU wake-up interrupts

 


PPI 파티션들 초기화

gic_populate_ppi_partitions()

drivers/irqchip/irq-gic-v3.c -1/2-

/* Create all possible partitions at boot time */
static void __init gic_populate_ppi_partitions(struct device_node *gic_node)
{
        struct device_node *parts_node, *child_part;
        int part_idx = 0, i;
        int nr_parts;
        struct partition_affinity *parts;

        parts_node = of_get_child_by_name(gic_node, "ppi-partitions");
        if (!parts_node)
                return;

        gic_data.ppi_descs = kcalloc(gic_data.ppi_nr, sizeof(*gic_data.ppi_descs), GFP_KERNEL);
        if (!gic_data.ppi_descs)
                return;

        nr_parts = of_get_child_count(parts_node);

        if (!nr_parts)
                goto out_put_node;

        parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL);
        if (WARN_ON(!parts))
                goto out_put_node;

        for_each_child_of_node(parts_node, child_part) {
                struct partition_affinity *part;
                int n;

                part = &parts[part_idx];

                part->partition_id = of_node_to_fwnode(child_part);

                pr_info("GIC: PPI partition %pOFn[%d] { ",
                        child_part, part_idx);

                n = of_property_count_elems_of_size(child_part, "affinity",
                                                    sizeof(u32));
                WARN_ON(n <= 0);

                for (i = 0; i < n; i++) {
                        int err, cpu;
                        u32 cpu_phandle;
                        struct device_node *cpu_node;

                        err = of_property_read_u32_index(child_part, "affinity",
                                                         i, &cpu_phandle);
                        if (WARN_ON(err))
                                continue;

                        cpu_node = of_find_node_by_phandle(cpu_phandle);
                        if (WARN_ON(!cpu_node))
                                continue;

                        cpu = of_cpu_node_to_id(cpu_node);
                        if (WARN_ON(cpu < 0))
                                continue;

                        pr_cont("%pOF[%d] ", cpu_node, cpu);

                        cpumask_set_cpu(cpu, &part->mask);
                }

                pr_cont("}\n");
                part_idx++;
        }

PPI를 위한 irq 파티션을 생성한다.

  • 코드 라인 9~11에서 디바이스 트리에서 ppi-partitions 노드를 찾을 수 없으면 함수를 빠져나간다.
  • 코드 라인 13~15에서 ppi 수만큼 partition_desc 구조체를 할당한다.
  • 코드 라인 17~20에서 child 노드 수를 읽어와 nr_parts에 대입한다.
  • 코드 라인 22~24에서 파티션 수(nr_parts)만큼 partition_affinity 구조체를 할당하고 디바이스 트리의 파티션 정보를 출력한다.
    • 다음은 두 개의 파티션을 사용하고 있는 rock960 보드의 관련 커널 로그를 출력한 예이다.
      • “GIC: PPI partition interrupt-partition-0[0] { /cpus/cpu@0[0] /cpus/cpu@1[1] /cpus/cpu@2[2] /cpus/cpu@3[3] }”
      • “GIC: PPI partition interrupt-partition-1[1] { /cpus/cpu@100[4] /cpus/cpu@101[5] }”
  • 코드 라인 26~66에서 파티션 수만큼 순회하며 디바이스 트리의 “affinity” 속성에 포함된 cpu 노드의 cpu 번호를 partition_affinity 구조체에 설정한다.

 

drivers/irqchip/irq-gic-v3.c -2/2-

        for (i = 0; i < gic_data.ppi_nr; i++) {
                unsigned int irq;
                struct partition_desc *desc;
                struct irq_fwspec ppi_fwspec = {
                        .fwnode         = gic_data.fwnode,
                        .param_count    = 3,
                        .param          = {
                                [0]     = GIC_IRQ_TYPE_PARTITION,
                                [1]     = i,
                                [2]     = IRQ_TYPE_NONE,
                        },
                };

                irq = irq_create_fwspec_mapping(&ppi_fwspec);
                if (WARN_ON(!irq))
                        continue;
                desc = partition_create_desc(gic_data.fwnode, parts, nr_parts,
                                             irq, &partition_domain_ops);
                if (WARN_ON(!desc))
                        continue;

                gic_data.ppi_descs[i] = desc;
        }

out_put_node:
        of_node_put(parts_node);
}
  • 코드 라인 1~23에서 ppi 수 만큼 순회하며 클러스터 단위의 파티션 별로 구성되어 irq를 나누어 처리하기 위해 irq 도메인 및 irq chip 등을 파티션 구성한다.
    • 참고로 3개의 인자로 생성하여 매핑한 PPI 파티션 디스크립터들을 생성하지만, 하위 파티션용 도메인과 하위 irq 디스크립터가 생성되지는 않는다.

 


파티션 디스크립터 생성

partition_create_desc()

drivers/irqchip/irq-partition-percpu.c

struct partition_desc *partition_create_desc(struct fwnode_handle *fwnode,
                                             struct partition_affinity *parts,
                                             int nr_parts,
                                             int chained_irq,
                                             const struct irq_domain_ops *ops)
{
        struct partition_desc *desc;
        struct irq_domain *d;

        BUG_ON(!ops->select || !ops->translate);

        desc = kzalloc(sizeof(*desc), GFP_KERNEL);
        if (!desc)
                return NULL;

        desc->ops = *ops;
        desc->ops.free = partition_domain_free;
        desc->ops.alloc = partition_domain_alloc;

        d = irq_domain_create_linear(fwnode, nr_parts, &desc->ops, desc);
        if (!d)
                goto out;
        desc->domain = d;

        desc->bitmap = kcalloc(BITS_TO_LONGS(nr_parts), sizeof(long),
                               GFP_KERNEL);
        if (WARN_ON(!desc->bitmap))
                goto out;

        desc->chained_desc = irq_to_desc(chained_irq);
        desc->nr_parts = nr_parts;
        desc->parts = parts;

        return desc;
out:
        if (d)
                irq_domain_remove(d);
        kfree(desc);

        return NULL;
}

하나의 ppi에 해당하는 파티션 디스크립터를 할당하고 구성한다.

  • 코드 라인 12~18에서 파티션 디스크립터(partition_desc 구조체)를 할당하고 인자로 전달받은 @ops와 파티션용 도메인 할당/해제 함수를 지정한다.
    • partition_domain_alloc() & partition_domain_free()
  • 코드 라인 20~23에서 파티션 수 만큼 리니어 irq를 관리하는 도메인을 생성한다.
  • 코드 라인 25~28에서 파티션을 관리하기 위한 비트맵을 할당한다.
  • 코드 라인 30~32에서 파티션 디스크립터에 @chined_irq와 파티션 수 및 할당한 파티션 affinity를 지정한다.
  • 코드 라인 34에서 할당하여 구성한 파티션 디스크립터를 반환한다.

 

파티션용 irq 도메인 할당

다음 파티션용 도메인이 생성되려면 디바이스 트리에서 디바이스가 interrupts 속성에서 파티션을 지정해야 한다.

  • 예) interrupts = <1 7 4 &ppi_cluster0>;

 

partition_domain_alloc()

drivers/irqchip/irq-partition-percpu.c

static int partition_domain_alloc(struct irq_domain *domain, unsigned int virq,
                                  unsigned int nr_irqs, void *arg)
{
        int ret;
        irq_hw_number_t hwirq;
        unsigned int type;
        struct irq_fwspec *fwspec = arg;
        struct partition_desc *part;

        BUG_ON(nr_irqs != 1);
        ret = domain->ops->translate(domain, fwspec, &hwirq, &type);
        if (ret)
                return ret;

        part = domain->host_data;

        set_bit(hwirq, part->bitmap);
        irq_set_chained_handler_and_data(irq_desc_get_irq(part->chained_desc),
                                         partition_handle_irq, part);
        irq_set_percpu_devid_partition(virq, &part->parts[hwirq].mask);
        irq_domain_set_info(domain, virq, hwirq, &partition_irq_chip, part,
                            handle_percpu_devid_irq, NULL, NULL);
        irq_set_status_flags(virq, IRQ_NOAUTOEN);

        return 0;
}

하위 파티션용 @virq를 파티션 도메인의 매치된 hwirq를 설정하고, 상위 도메인에 체인 연결한다.

  • 코드 라인 11~13에서 fwspec 인자를 가지고 요청한 하위 irq 도메인의 (*translate) 후크 함수를 호출하여 hwirq 및 타입을 알아온다.
    • 파티션용 hwirq의 시작번호는 0번부터 시작한다.
  • 코드 라인 15에서 하위 파티션 @domain의 host_data에 저장된 파티션 디스크립터를 알아온다.
  • 코드 라인 17에서 해당 파티션에 대한 비트맵을 설정한다.
  • 코드 라인 18~19에서 해당 파티션에 대한 chained 핸들러에 partition_handle_irq() 함수를 지정한다.
    • 파티션에 대한 상위 irq 디스크립터는 part->chained_desc 이다.
  • 코드 라인 20에서 하위 @virq에 해당하는 irq 디스크립터의 percpu 기능을 설정한다.
  • 코드 라인 21~22에서 파티션 @domain의 하위 @virq에 관련 hwriq 및 파티션용 irq_chip 정보, 파티션 디스크립터 및 percpu 인터럽트 핸들러들을 설정한다.
  • 코드 라인 23에서 하위 @virq에 해당하는 디스크립터의 플래그에 IRQ_NOAUTOEN 플래그를 설정한다.

 

irq_set_percpu_devid_partition()

kernel/irq/irqdesc.c

int irq_set_percpu_devid_partition(unsigned int irq,
                                   const struct cpumask *affinity)
{
        struct irq_desc *desc = irq_to_desc(irq);

        if (!desc)
                return -EINVAL;

        if (desc->percpu_enabled)
                return -EINVAL;

        desc->percpu_enabled = kzalloc(sizeof(*desc->percpu_enabled), GFP_KERNEL);

        if (!desc->percpu_enabled)
                return -ENOMEM;

        if (affinity)
                desc->percpu_affinity = affinity;
        else
                desc->percpu_affinity = cpu_possible_mask;

        irq_set_percpu_devid_flags(irq);
        return 0;
}

@irq에 해당하는 irq 디스크립터에 percpu 기능을 설정한다.

  • 코드 라인 4~10에서 @irq 번호에 해당하는 irq 디스크립터에서 percpu_enabled가 이미 설정된 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 12~15에서 percpu_enabled에 cpu 비트마스크를 할당한다.
  • 코드 라인 17~20에서 irq 디스크립터에 percpu용 affinity를 설정한다. 인자로 받은 @affinity가 주어진 경우 이 값을 사용하고, 주어지지 않은 경우 모든 possible cpu를 지정한다.
  • 코드 라인 22에서 irq 디스크립터에 percpu 기능 활성화와 관련된 플래그들을 설정한다.

 

irq_set_percpu_devid_flags()

include/linux/irq.h

static inline void irq_set_percpu_devid_flags(unsigned int irq)
{
        irq_set_status_flags(irq,
                             IRQ_NOAUTOEN | IRQ_PER_CPU | IRQ_NOTHREAD |
                             IRQ_NOPROBE | IRQ_PER_CPU_DEVID);
}

@irq 번호에 해당하는 디스크립터에 percpu 기능 활성화와 관련된 플래그들을 설정한다.

 

파티션용 인터럽트 핸들러

partition_handle_irq()

drivers/irqchip/irq-partition-percpu.c

static void partition_handle_irq(struct irq_desc *desc)
{
        struct partition_desc *part = irq_desc_get_handler_data(desc);
        struct irq_chip *chip = irq_desc_get_chip(desc);
        int cpu = smp_processor_id();
        int hwirq;

        chained_irq_enter(chip, desc);

        for_each_set_bit(hwirq, part->bitmap, part->nr_parts) {
                if (partition_check_cpu(part, cpu, hwirq))
                        break;
        }

        if (unlikely(hwirq == part->nr_parts)) {
                handle_bad_irq(desc);
        } else {
                unsigned int irq;
                irq = irq_find_mapping(part->domain, hwirq);
                generic_handle_irq(irq);
        }

        chained_irq_exit(chip, desc);
}

상위 irq에 등록된 파티션용 핸들러이다. irq 디스크립터(@desc)를 사용하여 해당 파티션을 찾고 그 파티션에 등록된 irq 디스크립터의 핸들러 함수를 호출하여 수행한다.

  • 코드 라인 3에서 irq 디스크립터의 핸들러 데이터에 저장된 파티션 디스크립터를 알아온다.
  • 코드 라인 4에서 irq 디스크립터의 irq 칩을 알아온다.
  • 코드 라인 8에서 chained irq의 시작을 선언한다.
  • 코드 라인 10~13에서 파티션 디스크립터의 비트맵을 순회하며 설정된 비트로부터 hwirq를  알아온다.
    • 두 개의 파티션이 사용되는 경우 hwirq=0~1이다.
  • 코드 라인 15~21에서 파티션 디스크립터에 소속된 파티션 도메인에서 hwirq에 해당하는 irq를 찾아 해당 핸들러 함수를 호출한다.
    • PPI 인터럽트 핸들러인 handle_percpu_devid_irq() 함수가 호출된다.
  • 코드 라인 23에서 chained irq의 끝을 선언한다.

 

partition_check_cpu()

drivers/irqchip/irq-partition-percpu.c

static bool partition_check_cpu(struct partition_desc *part,
                                unsigned int cpu, unsigned int hwirq)
{
        return cpumask_test_cpu(cpu, &part->parts[hwirq].mask);
}

파티션 디스크립터의 @hwirq에 해당하는 파티션에 요청한 @cpu가 포함되어 있는지 여부를 반환한다.

 


파티션용 irq_chip Operations

partition_irq_chip

drivers/irqchip/irq-partition-percpu.c

static struct irq_chip partition_irq_chip = {
        .irq_mask               = partition_irq_mask,
        .irq_unmask             = partition_irq_unmask,
        .irq_set_type           = partition_irq_set_type,
        .irq_get_irqchip_state  = partition_irq_get_irqchip_state,
        .irq_set_irqchip_state  = partition_irq_set_irqchip_state,
        .irq_print_chip         = partition_irq_print_chip,
};

하위 irq 디스크립터에 연동되어 사용되는 irq 칩에 대한 operations 함수이다.

 

partition_irq_mask()

drivers/irqchip/irq-partition-percpu.c

static void partition_irq_mask(struct irq_data *d)
{
        struct partition_desc *part = irq_data_get_irq_chip_data(d);
        struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
        struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);

        if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
            chip->irq_mask)
                chip->irq_mask(data);
}

하위 irq 디스크립터 @d에 연동된 상위 chained-irq 디스크립터를 사용해 irq mask를 수행한다.

 

partition_irq_unmask()

drivers/irqchip/irq-partition-percpu.c

static void partition_irq_unmask(struct irq_data *d)
{
        struct partition_desc *part = irq_data_get_irq_chip_data(d);
        struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
        struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);

        if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
            chip->irq_unmask)
                chip->irq_unmask(data);
}

하위 irq 디스크립터 @d에 연동된 상위 chained-irq 디스크립터를 사용해 irq unmask를 수행한다.

 

partition_irq_set_type()

drivers/irqchip/irq-partition-percpu.c

static int partition_irq_set_type(struct irq_data *d, unsigned int type)
{
        struct partition_desc *part = irq_data_get_irq_chip_data(d);
        struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
        struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);

        if (chip->irq_set_type)
                return chip->irq_set_type(data, type);

        return -EINVAL;
}

하위 irq 디스크립터 @d에 연동된 상위 chained-irq 디스크립터를 사용해 irq type을 지정한다.

 

partition_irq_get_irqchip_state()

drivers/irqchip/irq-partition-percpu.c

static int partition_irq_get_irqchip_state(struct irq_data *d,
                                           enum irqchip_irq_state which,
                                           bool *val)
{
        struct partition_desc *part = irq_data_get_irq_chip_data(d);
        struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
        struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);

        if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
            chip->irq_get_irqchip_state)
                return chip->irq_get_irqchip_state(data, which, val);

        return -EINVAL;
}

하위 irq 디스크립터 @d에 연동된 상위 chained-irq 디스크립터를 사용해 irqchip state를 알아온다.

 

partition_irq_set_irqchip_state()

drivers/irqchip/irq-partition-percpu.c

static int partition_irq_set_irqchip_state(struct irq_data *d,
                                           enum irqchip_irq_state which,
                                           bool val)
{
        struct partition_desc *part = irq_data_get_irq_chip_data(d);
        struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
        struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);

        if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
            chip->irq_set_irqchip_state)
                return chip->irq_set_irqchip_state(data, which, val);

        return -EINVAL;
}

하위 irq 디스크립터 @d에 연동된 상위 chained-irq 디스크립터를 사용해 irqchip state를 설정한다.

 

partition_irq_print_chip()

drivers/irqchip/irq-partition-percpu.c

static void partition_irq_print_chip(struct irq_data *d, struct seq_file *p)
{
        struct partition_desc *part = irq_data_get_irq_chip_data(d);
        struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
        struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);

        seq_printf(p, " %5s-%lu", chip->name, data->hwirq);
}

하위 irq 디스크립터 @d에 연동된 상위 chained-irq 디스크립터이 사용중인 irq chip 명과 hwirq를 출력한다.

  • 예) “GICv3-23”
    • INTID#23 -> PPI#7

 


GIC v3 인터럽트 컨트롤러

partition_domain_ops

drivers/irqchip/irq-gic-v3.c

static const struct irq_domain_ops partition_domain_ops = {
        .translate = partition_domain_translate,
        .select = gic_irq_domain_select,
};

파티션 도메인용 opeartions이다.

  • 하위 도메인으로 사용된다.

 

파티션용 irq 도메인 변환

partition_domain_translate()

drivers/irqchip/irq-partition-percpu.c

static int partition_domain_translate(struct irq_domain *d,
                                      struct irq_fwspec *fwspec,
                                      unsigned long *hwirq,
                                      unsigned int *type)
{
        struct device_node *np;
        int ret;

        if (!gic_data.ppi_descs)
                return -ENOMEM;

        np = of_find_node_by_phandle(fwspec->param[3]);
        if (WARN_ON(!np))
                return -EINVAL;

        ret = partition_translate_id(gic_data.ppi_descs[fwspec->param[1]],
                                     of_node_to_fwnode(np));
        if (ret < 0)
                return ret;

        *hwirq = ret;
        *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;

        return 0;
}

파티션 도메인(@d)에사 @fwspec로 irq 디스크립터를 찾아 해당 hwirq 및 type을 알아온다. 성공 시 0을 반환한다.

  • 코드 라인 9~10에서 ppi용 파티션 디스크립터들이 만들어지지 않은 경우 함수를 빠져나간다.
  • 코드 라인 12~14에서 @fwspec으로 전달받은 4번째 인자에 phandle 해당하는 노드를 찾아온다.
    • 찾은 디바이스 노드가 파티션을 위한 클러스터 노드이다.
  • 코드 라인 16~19에서 @fwspec의 두 번째 인자인 PPI hwirq에 대응하는 파티션 디스크립터와 클러스터 노드의 fwnode를 사용하여 hwirq를 알아온다.
  • 코드 라인 21~24에서 출력 인자 @hwirq에 알아온 hwirq를 대입하고, 출력 인자 @type에는 irq 타입 정보를 담아 정상 0 값을 반환한다.

 

partition_translate_id()

drivers/irqchip/irq-partition-percpu.c

int partition_translate_id(struct partition_desc *desc, void *partition_id)
{
        struct partition_affinity *part = NULL;
        int i;

        for (i = 0; i < desc->nr_parts; i++) {
                if (desc->parts[i].partition_id == partition_id) {
                        part = &desc->parts[i];
                        break;
                }
        }

        if (WARN_ON(!part)) {
                pr_err("Failed to find partition\n");
                return -EINVAL;
        }

        return i;
}

파티션 디스크립터와 @partition_id(fwnode)로 hwirq를 찾아온다. 찾지못하는 경우 nr_parts 값이 반환된다.

 

파티션용 irq 도메인 선택

gic_irq_domain_select()

drivers/irqchip/irq-gic-v3.c

static int gic_irq_domain_select(struct irq_domain *d,
                                 struct irq_fwspec *fwspec,
                                 enum irq_domain_bus_token bus_token)
{
        /* Not for us */
        if (fwspec->fwnode != d->fwnode)
                return 0;

        /* If this is not DT, then we have a single domain */
        if (!is_of_node(fwspec->fwnode))
                return 1;

        /*
         * If this is a PPI and we have a 4th (non-null) parameter,
         * then we need to match the partition domain.
         */
        if (fwspec->param_count >= 4 &&
            fwspec->param[0] == 1 && fwspec->param[3] != 0 &&
            gic_data.ppi_descs)
                return d == partition_get_domain(gic_data.ppi_descs[fwspec->param[1]]);

        return d == gic_data.domain;
}

@fwspec에 해당하는 irq 도메인을 선택한다. 입력 인자로 요청한 irq 도메인(@d)과 동일한 경우 1을 반환한다.

  • 코드 라인 6~7에서 인자로 받은 @fwspec->fwnode와 인자로 받은 irq 도메인의 fwnode가 서로 다른 경우 실패 값 0을 반환한다.
  • 코드 라인 10~11에서 디바이스 트리 노드가 아닌 경우 싱글 도메인만 있으므로 1을 반환한다.
  • 코드 라인 17~20에서 PPI이고 4개 이상의 파라미터를 가졌으며 파티션 도메인을 가진 경우 파티션 디스크립터에 등록한 irq 도메인과 인자로 받은 irq 도메인이 동일한지 여부를 반환한다.
    • 반드시 param_count가 4 이상이며 4번째 파라미터에는 클러스터 phandle 값이 있어야 한다.
  • 코드 라인 22에서 파티션 도메인이 아닌 일반 PPI/SPI의 경우 인자로 요청한 irq 도메인(@d)과 gic 기본 도메인이 동일하지 여부를 반환한다.

 

partition_get_domain()

drivers/irqchip/irq-partition-percpu.c

struct irq_domain *partition_get_domain(struct partition_desc *dsc)
{
        if (dsc)
                return dsc->domain;

        return NULL;
}

파티션 디스크립터에 소속된 irq 도메인을 반환한다.

 


구조체

partition_desc 구조체

drivers/irqchip/irq-partition-percpu.c

struct partition_desc {
        int                             nr_parts;
        struct partition_affinity       *parts;
        struct irq_domain               *domain;
        struct irq_desc                 *chained_desc;
        unsigned long                   *bitmap;
        struct irq_domain_ops           ops;
};

파티션 디스크립터이다.

  • nr_parts
    • 구성된 파티션 수이다.
    • 클러스터가 4개인 경우 nr_parts=4이다.
  • *parts
    • 파티션 수만큼 cpu 구성정보를 담는 partition_affinity 구조체들을 가리킨다.
  • *domain
    • 파티션에 소속된 파티션 도메인을 가리킨다.
  • *chained_desc
    • 상위 chained irq (PPI irq)를 가리킨다.
  • *bitmap
    • 운용되는 파티션에 대한 비트맵이다.
  • ops
    • 파티션에서 사용할 파티션 도메인용 operations이다.

 

partition_affinity 구조체

include/linux/irqchip/irq-partition-percpu.h

struct partition_affinity {
        cpumask_t                       mask;
        void                            *partition_id;
};

파티션이 사용할 cpu 구성정보를 담는다.

  •  mask
    • 파티션에 포함된 cpu 마스크
  • *partition_id
    • 디바이스 트리의 파티션 노드에 해당하는 fwnode_handle

 

참고

 

Interrupts -9- (GIC v3 Driver)

<kernel v5.4>

Interrupts -9- (GIC v3 Driver)

GIC v3 & GIC v4 호환

GIC v3 드라이버 코드는 GIC v4도 같이 사용한다. (GIC v4 its를 위한 코드가 일부 추가됨)

 

다음은 rock3399 칩에서 사용하는 gic v3 컨트롤러에 대한 디바이스 트리이다.

arch/arm64/boot/dts/rockchip/rk3399.dtsi

        gic: interrupt-controller@fee00000 {
                compatible = "arm,gic-v3";
                #interrupt-cells = <4>;
                #address-cells = <2>;
                #size-cells = <2>;
                ranges;
                interrupt-controller;

                reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
                      <0x0 0xfef00000 0 0xc0000>, /* GICR */
                      <0x0 0xfff00000 0 0x10000>, /* GICC */
                      <0x0 0xfff10000 0 0x10000>, /* GICH */
                      <0x0 0xfff20000 0 0x10000>; /* GICV */
                interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
                its: interrupt-controller@fee20000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        reg = <0x0 0xfee20000 0x0 0x20000>;
                };

                ppi-partitions {
                        ppi_cluster0: interrupt-partition-0 {
                                affinity = <&cpu_l0 &cpu_l1 &cpu_l2 &cpu_l3>;
                        };

                        ppi_cluster1: interrupt-partition-1 {
                                affinity = <&cpu_b0 &cpu_b1>;
                        };
                };
        };
  • reg
    • 기본적으로 GICD(mandatory), GICR(mandatory), GICC(option), GICH(option), GICV(option) 영역 5개를 사용한다.
  • interrupts
    • VGIC 관리를 위한 인터럽트 소스이다.
  • its: interrupt-controller@fee20000
    • GIC v3-its 드라이버는 생략한다.
  • ppi-partitions
    • PPI affnity를 사용하여 파티션을 구성한다.

 

다음은 hisilicon의 hip07 칩에서 사용하는 gic v3 컨트롤러에 대한 디바이스 트리이다.

arch/arm64/boot/dts/hisilicon/hip07.dtsi

        gic: interrupt-controller@4d000000 {
                compatible = "arm,gic-v3";
                #interrupt-cells = <3>;
                #address-cells = <2>;
                #size-cells = <2>;
                ranges;
                interrupt-controller;
                #redistributor-regions = <4>;
                redistributor-stride = <0x0 0x40000>;
                reg = <0x0 0x4d000000 0x0 0x10000>,     /* GICD */
                      <0x0 0x4d100000 0x0 0x400000>,    /* p0 GICR node 0 */
                      <0x0 0x6d100000 0x0 0x400000>,    /* p0 GICR node 1 */
                      <0x400 0x4d100000 0x0 0x400000>,  /* p1 GICR node 2 */
                      <0x400 0x6d100000 0x0 0x400000>,  /* p1 GICR node 3 */
                      <0x0 0xfe000000 0x0 0x10000>,     /* GICC */
                      <0x0 0xfe010000 0x0 0x10000>,     /* GICH */
                      <0x0 0xfe020000 0x0 0x10000>;     /* GICV */
                interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;

                ...
  • #redistributor-regions
    • GICR 레지스터 영역이 4개의 노드로 구성됨을 알 수 있다.
  • redistributor-stride
    • GICR 노드 레지스터간에 패딩 간격 페이지 수를 지정한다. 이 값은 64K의 배수여야 한다.
      • 위의 예에서는 256K를 사용하였다.
  • reg
    • 기본적으로 GICD, GICR, GICC, GICH, GICV 영역 5개를 사용하지만, 이 컨트롤러는 GICR 영역이 1 -> 4개로 확장되었다.

 

GIC v3 드라이버 초기화

gic_of_init()

drivers/irqchip/irq-gic-v3.c

static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
        void __iomem *dist_base;
        struct redist_region *rdist_regs;
        u64 redist_stride;
        u32 nr_redist_regions;
        int err, i;

        dist_base = of_iomap(node, 0);
        if (!dist_base) {
                pr_err("%pOF: unable to map gic dist registers\n", node);
                return -ENXIO;
        }

        err = gic_validate_dist_version(dist_base);
        if (err) {
                pr_err("%pOF: no distributor detected, giving up\n", node);
                goto out_unmap_dist;
        }

        if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
                nr_redist_regions = 1;

        rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),
                             GFP_KERNEL);
        if (!rdist_regs) {
                err = -ENOMEM;
                goto out_unmap_dist;
        }

        for (i = 0; i < nr_redist_regions; i++) {
                struct resource res;
                int ret;

                ret = of_address_to_resource(node, 1 + i, &res);
                rdist_regs[i].redist_base = of_iomap(node, 1 + i);
                if (ret || !rdist_regs[i].redist_base) {
                        pr_err("%pOF: couldn't map region %d\n", node, i);
                        err = -ENODEV;
                        goto out_unmap_rdist;
                }
                rdist_regs[i].phys_base = res.start;
        }

        if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
                redist_stride = 0;

        gic_enable_of_quirks(node, gic_quirks, &gic_data);

        err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
                             redist_stride, &node->fwnode);
        if (err)
                goto out_unmap_rdist;

        gic_populate_ppi_partitions(node);

        if (static_branch_likely(&supports_deactivate_key))
                gic_of_setup_kvm_info(node);
        return 0;

out_unmap_rdist:
        for (i = 0; i < nr_redist_regions; i++)
                if (rdist_regs[i].redist_base)
                        iounmap(rdist_regs[i].redist_base);
        kfree(rdist_regs);
out_unmap_dist:
        iounmap(dist_base);
        return err;
}
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);

GIC v3 인터럽트 컨트롤러 초기화 시작 함수이다. (GIC v4 인터럽트 컨트롤러도 이 함수를 같이 사용한다)

  • 코드 라인 9~13에서 Distributor 레지스터 영역인 “reg” 속성의 0번째 주소와 사이즈를 읽어와 가상 주소에 매핑한다.
  • 코드 라인 15~19에서 Distributor 레지스터에서 gic v3 또는 v4 여부를 체크한다. (성공 시 0)
  • 코드 라인 21~22에서 ReDistributor 영역의 수를 알아오기 위해 “#redistributor-regions” 속성 값을 알아온다. 속성이 없는 경우 디폴트는 1이다.
  • 코드 라인 24~29에서 ReDistributor 영역의 수만큼 redist_region 구조체를 할당한다.
  • 코드 라인 31~43에서 ReDistributor 영역의 수만큼 “reg” 속성의 두 번째 부터 주소와 사이즈를 읽어 가상 주소에 매핑하고, 매핑한 주소는 rdist_regs[].redist_base에 대입한다. 그리고 rdist_regs[].phys_base에는 물리 주소를 기록한다.
  • 코드 라인 45~46에서 GICR 노드 레지스터간에 패딩 간격 페이지 수를 지정하기 위해 “redistributor-stride” 속성 값을 알아온다. 속성이 없는 경우 디폴트는 0이다.
  • 코드 라인 48에서 gic 컨트롤러들 중 errata 코드가 있는 경우 이를 호출하여 수행한다.
    • 현재 Qualcomm MSM8996, Hisilicon의 hip06 및 hip07이 대상이다.
  • 코드 라인 50~53에서 gic 컨트롤러를 초기화한다.
  • 코드 라인 55에서 모든 PPI들을 위해 irq 파티션을 생성한다.
  • 코드 라인 57~58에서 Guest OS를 위한 KVM 정보를 설정한다.
  • 코드 라인 59에서 성공 값 0을 반환한다.

 

gic_validate_dist_version()

drivers/irqchip/irq-gic-v3.c

static int __init gic_validate_dist_version(void __iomem *dist_base)
{
        u32 reg = readl_relaxed(dist_base + GICD_PIDR2) & GIC_PIDR2_ARCH_MASK;

        if (reg != GIC_PIDR2_ARCH_GICv3 && reg != GIC_PIDR2_ARCH_GICv4)
                return -ENODEV;

        return 0;
}

GICD_PIDR2 레지스터의 버전을 읽어 gic v3 또는 v4인지 여부를 알아온다. 성공시 0을 반환한다.

 

GIC 레지스터들 초기화

gic_init_bases()

drivers/irqchip/irq-gic-v3.c

static int __init gic_init_bases(void __iomem *dist_base,
                                 struct redist_region *rdist_regs,
                                 u32 nr_redist_regions,
                                 u64 redist_stride,
                                 struct fwnode_handle *handle)
{
        u32 typer;
        int err;

        if (!is_hyp_mode_available())
                static_branch_disable(&supports_deactivate_key);

        if (static_branch_likely(&supports_deactivate_key))
                pr_info("GIC: Using split EOI/Deactivate mode\n");

        gic_data.fwnode = handle;
        gic_data.dist_base = dist_base;
        gic_data.redist_regions = rdist_regs;
        gic_data.nr_redist_regions = nr_redist_regions;
        gic_data.redist_stride = redist_stride;

        /*
         * Find out how many interrupts are supported.
         */
        typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
        gic_data.rdists.gicd_typer = typer;

        gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR),
                          gic_quirks, &gic_data);

        pr_info("%d SPIs implemented\n", GIC_LINE_NR - 32);
        pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR);
        gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
                                                 &gic_data);
        irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
        gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
        gic_data.rdists.has_vlpis = true;
        gic_data.rdists.has_direct_lpi = true;

        if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
                err = -ENOMEM;
                goto out_free;
        }

        gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
        pr_info("Distributor has %sRange Selector support\n",
                gic_data.has_rss ? "" : "no ");

        if (typer & GICD_TYPER_MBIS) {
                err = mbi_init(handle, gic_data.domain);
                if (err)
                        pr_err("Failed to initialize MBIs\n");
        }

        set_handle_irq(gic_handle_irq);

        gic_update_rdist_properties();

        gic_smp_init();
        gic_dist_init();
        gic_cpu_init();
        gic_cpu_pm_init();

        if (gic_dist_supports_lpis()) {
                its_init(handle, &gic_data.rdists, gic_data.domain);
                its_cpu_init();
        } else {
                if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
                        gicv2m_init(handle, gic_data.domain);
        }

        gic_enable_nmi_support();

        return 0;

out_free:
        if (gic_data.domain)
                irq_domain_remove(gic_data.domain);
        free_percpu(gic_data.rdists.rdist);
        return err;
}
  • 코드 라인 10~14에서 하이퍼 모드가 운용가능한 상태가 아니면 supports_deactivate_key를 disable 한다.
    • 커널이 하이퍼 모드로  동작하는 경우에 따라 EOI 모드를 priority drop과 deactivate를 split하여 운영한다.
      • 예) “GIC: Using split EOI/Deactivate mode”
    • 참고: 가상화 지원 (하이퍼 모드) | 문c
  • 코드 라인 16~20에서 gic_chip_data 구조체 형태인 전역 gic_data에 인자로 전달된 값들로 초기화한다.
  • 코드 라인 25~26에서 GICD_TYPER 레지스터 값을 저장한다.
  • 코드 라인 28~29에서 GICD_IIDR 레지스터 값을 읽어 관련 디바이스에 대해서만 errtum 코드를 수행한다.
  • 코드 라인 31~32에서 SPI와 확장 SPI 수 정보를 출력한다.
  • 코드 라인 33~35에서 하이라키를 지원하는 irq 도메인을 생성하고, DOMAIN_BUS_WIRED 도메인 타입으로 지정한다.
  • 코드 라인 36~43에서 Redistributor용 per-cpu 자료 구조를 할당 받고 초기화한다.
    • 가상 LPI 지원과 Direct LPI Injection을 true로 미리 설정한다.
    • 추후 Redistributor 레지스터들을 읽어 하나라도 VLPI 및 DirectLPI 값이 0인 경우 false로 바뀐다.
  • 코드 라인 45~47에서 GICD_TYPER.RSS 비트를 읽어 rss 지원 여부를 알아내고 출력한다.
  • 코드 라인 49~53에서 GICD_TYPER.MBIS 비트를 읽어 메시지 기반 인터럽트의 지원하는 경우 mbi 관련 초기화 루틴을 수행한다.
    • “msi-controller” 속성 필요
  • 코드 라인 55에서 이 컨트롤러에서 관리를 위해 사용하는 인터럽트의 핸들러 함수를 지정한다.
  • 코드 라인 57에서 Redistributor 속성을 읽어 갱신한다.
  • 코드 라인 59에서 SMP를 지원하기 위한 초기화를 수행한다.
  • 코드 라인 60에서 Distributtor 레지스터를 사용한 초기화를 수행한다.
  • 코드 라인 61에서 CPU interface 레지스터를 사용한 초기화를 수행한다.
  • 코드 라인 62에서 CPU 절전(suspen/resume) 관련한 초기화를 수행한다.
  • 코드 라인 64~70에서 LPI를 지원하는 경우 ITS츨 초기화한다. 지원하지 않는 경우 GIC 가상화 지원 초기화를 수행한다.
  • 코드 라인 72에서 Pesudo-NNI 기능을 지원하는 경우 이에 대한 초기화를 수행한다.
  • 코드 라인 74에서 성공 값 0을 반환한다.

 

GIC Quirks(ERRATA) 초기화 및 적용

gic_enable_of_quirks()

drivers/irqchip/irq-gic-common.c

void gic_enable_of_quirks(const struct device_node *np,
                          const struct gic_quirk *quirks, void *data)
{
        for (; quirks->desc; quirks++) {
                if (!of_device_is_compatible(np, quirks->compatible))
                        continue;
                if (quirks->init(data))
                        pr_info("GIC: enabling workaround for %s\n",
                                quirks->desc);
        }
}

디바이스 트리로 지정된 디바이스만 erratum 적용한다.

 

gic_enable_quirks()

drivers/irqchip/irq-gic-common.c

void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
                void *data)
{
        for (; quirks->desc; quirks++) {
                if (quirks->compatible)
                        continue;
                if (quirks->iidr != (quirks->mask & iidr))
                        continue;
                if (quirks->init(data))
                        pr_info("GIC: enabling workaround for %s\n",
                                quirks->desc);
        }
}

@iidr 값이 일치하는 디바이스만 erratum 적용한다.

 

gic_quirks

static const struct gic_quirk gic_quirks[] = {
        {
                .desc   = "GICv3: Qualcomm MSM8996 broken firmware",
                .compatible = "qcom,msm8996-gic-v3",
                .init   = gic_enable_quirk_msm8996,
        },
        {
                .desc   = "GICv3: HIP06 erratum 161010803",
                .iidr   = 0x0204043b,
                .mask   = 0xffffffff,
                .init   = gic_enable_quirk_hip06_07,
        },
        {
                .desc   = "GICv3: HIP07 erratum 161010803",
                .iidr   = 0x00000000,
                .mask   = 0xffffffff,
                .init   = gic_enable_quirk_hip06_07,
        },
        {
        }
};

버그 등으로 인해 erratum 코드가 적용되어야 할 디바이스들이다.

 

SMP core들 시작시 호출될 함수 지정

gic_smp_init()

drivers/irqchip/irq-gic-v3.c

static void gic_smp_init(void)
{
        set_smp_cross_call(gic_raise_softirq);
        cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                                  "irqchip/arm/gicv3:starting",
                                  gic_starting_cpu, NULL);
}

새로운 cpu가 online될 때 gic에 알려야 할 함수를 지정한다.

 

gic_starting_cpu()

drivers/irqchip/irq-gic-v3.c

static int gic_starting_cpu(unsigned int cpu)
{
        gic_cpu_init();

        if (gic_dist_supports_lpis())
                its_cpu_init();

        return 0;
}

새로운 cpu가 online될 때 콜백 호출되는 함수이다. 해당 cpu interface 레지스터들을 초기화하고, LPI를 지원하는 경우 ITS 초기화 함수를 수행한다.

 


Distributor 초기화

gic_dist_init()

drivers/irqchip/irq-gic-v3.c

static void __init gic_dist_init(void)
{
        unsigned int i;
        u64 affinity;
        void __iomem *base = gic_data.dist_base;

        /* Disable the distributor */
        writel_relaxed(0, base + GICD_CTLR);
        gic_dist_wait_for_rwp();

        /*
         * Configure SPIs as non-secure Group-1. This will only matter
         * if the GIC only has a single security state. This will not
         * do the right thing if the kernel is running in secure mode,
         * but that's not the intended use case anyway.
         */
        for (i = 32; i < GIC_LINE_NR; i += 32)
                writel_relaxed(~0, base + GICD_IGROUPR + i / 8);

        /* Extended SPI range, not handled by the GICv2/GICv3 common code */
        for (i = 0; i < GIC_ESPI_NR; i += 32) {
                writel_relaxed(~0U, base + GICD_ICENABLERnE + i / 8);
                writel_relaxed(~0U, base + GICD_ICACTIVERnE + i / 8);
        }

        for (i = 0; i < GIC_ESPI_NR; i += 32)
                writel_relaxed(~0U, base + GICD_IGROUPRnE + i / 8);

        for (i = 0; i < GIC_ESPI_NR; i += 16)
                writel_relaxed(0, base + GICD_ICFGRnE + i / 4);

        for (i = 0; i < GIC_ESPI_NR; i += 4)
                writel_relaxed(GICD_INT_DEF_PRI_X4, base + GICD_IPRIORITYRnE + i);

        /* Now do the common stuff, and wait for the distributor to drain */
        gic_dist_config(base, GIC_LINE_NR, gic_dist_wait_for_rwp);

        /* Enable distributor with ARE, Group1 */
        writel_relaxed(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1,
                       base + GICD_CTLR);

        /*
         * Set all global interrupts to the boot CPU only. ARE must be
         * enabled.
         */
        affinity = gic_mpidr_to_affinity(cpu_logical_map(smp_processor_id()));
        for (i = 32; i < GIC_LINE_NR; i++)
                gic_write_irouter(affinity, base + GICD_IROUTER + i * 8);

        for (i = 0; i < GIC_ESPI_NR; i++)
                gic_write_irouter(affinity, base + GICD_IROUTERnE + i * 8);
}

GIC Distributor를 초기화한다.

  • 코드 라인 8~9에서 Distributor를 disable하기 위해 GICD_CTRL 값에 0을 기록한다.
  • 코드 라인 17~18에서 모든 SPI들을 non-secure group 1으로 설정하기위해 GICD_IGROUPRn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
    • one security states에서 리눅스 커널이 동작하는 경우 그룹 제어가 가능하다.
    • 그러나 two secure states인 경우 그룹 설정은 secure에서 해야 하기 때문에 non-secure에서 동작하느 리눅스 커널에서 제어는 의미 없게 된다.
  • 코드 라인 21~24에서 모든 확장 SPI들을 disable 및 deactivate 요청하기 위해 GICD_ICENABLERn 과 GICD_ICACTIVERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
  • 코드 라인 26~27에서 모든 확장 SPI들을 non-secure group 1으로 설정하기위해 GICD_IGROUPRnE들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
  • 코드 라인 29~30에서  모든 확장 SPI들을 레벨 sensitive 모드로 설정하기 위해 GICD_ICFGRnE들을 모두 0x0(1 비트당 1 인터럽트)으로 설정한다.
  • 코드 라인 32~33에서 모든 확장 SPI들에 대해 우선 순위를 0xd0으로 설정하기 위해 GICD_IPRIORITYRnE에 0xa0을 기록한다. (8 비트당 1 인터럽트)
    • non-secure에서 설정한 0xa0은 cpu interface로 올라갈 때 내부 변환하면 0xa0 >> 1 | 0x80 = 0xd0에 해당하는 우선 순위이다.
  • 코드 라인 36에서 GIC v1 이상에서 동작하는 공통 Distributor 설정 코드를 수행한다.
  • 코드 라인 39~40에서 Distributor를 활성화할 때 ARE(Affinity Routing Enable), group 1도 활성화한다.
  • 코드 라인 46에서 boot cpu의 affinity 값들을 읽어오기 위해 MPIDR 레지스터를 알아온다.
  • 코드 라인 47~51에서 모든 SPI들 및 확장 SPI들을 대상으로 boot cpu로 라우팅 설정하기 위해 읽어온 affinity 값을 GICD_ROUTERn 및 GICD_ROUTERRnE에 기록한다.

 

gic_dist_wait_for_rwp()

drivers/irqchip/irq-gic-v3.c

/* Wait for completion of a distributor change */
static void gic_dist_wait_for_rwp(void)
{
        gic_do_wait_for_rwp(gic_data.dist_base);
}

distributor 레지스터에 기록한 후 이의 수행이 완료될 때까지 대기한다.

 

gic_do_wait_for_rwp()

drivers/irqchip/irq-gic-v3.c

static void gic_do_wait_for_rwp(void __iomem *base)
{
        u32 count = 1000000;    /* 1s! */

        while (readl_relaxed(base + GICD_CTLR) & GICD_CTLR_RWP) {
                count--;
                if (!count) {
                        pr_err_ratelimited("RWP timeout, gone fishing\n");
                        return;
                }
                cpu_relax();
                udelay(1);
        };
}

GICD_CTLR.RWP 레지스터를 읽어 0이될 때까지 반복한다.

 

GIC v1 이상 공통 Distributor 설정

gic_dist_config()

drivers/irqchip/irq-gic-v3.c

void gic_dist_config(void __iomem *base, int gic_irqs,
                     void (*sync_access)(void))
{
        unsigned int i;

        /*
         * Set all global interrupts to be level triggered, active low.
         */
        for (i = 32; i < gic_irqs; i += 16)
                writel_relaxed(GICD_INT_ACTLOW_LVLTRIG,
                                        base + GIC_DIST_CONFIG + i / 4);

        /*
         * Set priority on all global interrupts.
         */
        for (i = 32; i < gic_irqs; i += 4)
                writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i);

        /*
         * Deactivate and disable all SPIs. Leave the PPI and SGIs
         * alone as they are in the redistributor registers on GICv3.
         */
        for (i = 32; i < gic_irqs; i += 32) {
                writel_relaxed(GICD_INT_EN_CLR_X32,
                               base + GIC_DIST_ACTIVE_CLEAR + i / 8);
                writel_relaxed(GICD_INT_EN_CLR_X32,
                               base + GIC_DIST_ENABLE_CLEAR + i / 8);
        }

        if (sync_access)
                sync_access();
}

모든 GIC 버전에서 동작하는 공통 Distributor 설정 코드를 수행한다.

  • 코드 라인 9~11에서 모든 SPI들에 대한 low active 레벨 sensitive 설정을 위해 GICD_ICFGRn에 0x0을 기록한다. (2 비트당 1 인터럽트)
  • 코드 라인 16~17에서 모든 SPI들에 대해 우선 순위를 0xd0으로 설정하기 위해 GICD_IPRIORITYRn에 0xa0을 기록한다. (8 비트당 1 인터럽트)
    • non-secure에서 설정한 0xa0은 cpu interface로 올라갈 때 내부 변환하면 0xa0 >> 1 | 0x80 = 0xd0에 해당하는 우선 순위이다.
  • 코드 라인 23~28에서 모든 확장 SPI들을 disable 및 deactivate 요청하기 위해 GICD_ICENABLERn 과 GICD_ICACTIVERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
  • 코드 라인 30~31 Distributor 레지스터 적용이 완료될 때까지 대기하도록 인자로 전달받은 @sync_access 함수를 호출한다.

 


CPU Interface 관련 초기화

gic_cpu_init()

drivers/irqchip/irq-gic-v3.c

static void gic_cpu_init(void)
{
        void __iomem *rbase;
        int i;

        /* Register ourselves with the rest of the world */
        if (gic_populate_rdist())
                return;

        gic_enable_redist(true);

        WARN((gic_data.ppi_nr > 16 || GIC_ESPI_NR != 0) &&
             !(gic_read_ctlr() & ICC_CTLR_EL1_ExtRange),
             "Distributor has extended ranges, but CPU%d doesn't\n",
             smp_processor_id());

        rbase = gic_data_rdist_sgi_base();

        /* Configure SGIs/PPIs as non-secure Group-1 */
        for (i = 0; i < gic_data.ppi_nr + 16; i += 32)
                writel_relaxed(~0, rbase + GICR_IGROUPR0 + i / 8);

        gic_cpu_config(rbase, gic_data.ppi_nr + 16, gic_redist_wait_for_rwp);

        /* initialise system registers */
        gic_cpu_sys_reg_init();
}

CPU interface를 초기화한다.

  • 코드 라인 7~10에서 Redistributor를 활성화한다.
  • 코드 라인 17에서 Redistributor에서 SGI 베이스 레지스터 가상 주소를 알아온다.
  • 코드 라인 20~21에서 SGI/PPI들을 모두 non-secure group 1으로 설정하기 위해 GICR_IGROUP0Rn에 0xffff_ffff(1 비트당 1 인터럽트)를 기록한다.
  • 코드 라인 23에서 모든 버전의 GIC를 위한 공통 CPU 설정 코드를 수행한다.
  • 코드 라인 26에서 시스템 레지스터를 초기화한다.

 

GIC v1 이상 공통 CPU 관련 설정

gic_cpu_config()

drivers/irqchip/irq-gic-common.c

void gic_cpu_config(void __iomem *base, int nr, void (*sync_access)(void))
{
        int i;

        /*
         * Deal with the banked PPI and SGI interrupts - disable all
         * private interrupts. Make sure everything is deactivated.
         */
        for (i = 0; i < nr; i += 32) {
                writel_relaxed(GICD_INT_EN_CLR_X32,
                               base + GIC_DIST_ACTIVE_CLEAR + i / 8);
                writel_relaxed(GICD_INT_EN_CLR_X32,
                               base + GIC_DIST_ENABLE_CLEAR + i / 8);
        }

        /*
         * Set priority on PPI and SGI interrupts
         */
        for (i = 0; i < nr; i += 4)
                writel_relaxed(GICD_INT_DEF_PRI_X4,
                                        base + GIC_DIST_PRI + i * 4 / 4);

        /* Ensure all SGI interrupts are now enabled */
        writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);

        if (sync_access)
                sync_access();
}

모든 GIC 버전에서 동작하는 공통 CPU 관련 설정을 수행한다.

  • 코드 라인 9~14에서 모든 SGI/PPI들을 disable 및 deactivate 요청하기 위해 GICD_ICENABLERn 과 GICD_ICACTIVERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
  • 코드 라인 19~21에서 모든 SGI/PPI들에 대해 우선 순위를 0xd0으로 설정하기 위해 GICD_IPRIORITYRn에 0xa0을 기록한다. (8 비트당 1 인터럽트)
    • non-secure에서 설정한 0xa0은 cpu interface로 올라갈 때 내부 변환하면 0xa0 >> 1 | 0x80 = 0xd0에 해당하는 우선 순위이다.
  • 코드 라인 24에서 모든 SGI들을 enable 요청하기 위해 GICD_ISENABLERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
  • 코드 라인 26~27에서 Distributor 레지스터 적용이 완료될 때까지 대기하도록 인자로 전달받은 @sync_access 함수를 호출한다.

 

gic_cpu_sys_reg_init()

drivers/irqchip/irq-gic-v3.c -1/2-

static void gic_cpu_sys_reg_init(void)
{
        int i, cpu = smp_processor_id();
        u64 mpidr = cpu_logical_map(cpu);
        u64 need_rss = MPIDR_RS(mpidr);
        bool group0;
        u32 pribits;

        /*
         * Need to check that the SRE bit has actually been set. If
         * not, it means that SRE is disabled at EL2. We're going to
         * die painfully, and there is nothing we can do about it.
         *
         * Kindly inform the luser.
         */
        if (!gic_enable_sre())
                pr_err("GIC: unable to set SRE (disabled at EL2), panic ahead\n");

        pribits = gic_get_pribits();

        group0 = gic_has_group0();

        /* Set priority mask register */
        if (!gic_prio_masking_enabled()) {
                write_gicreg(DEFAULT_PMR_VALUE, ICC_PMR_EL1);
        } else {
                /*
                 * Mismatch configuration with boot CPU, the system is likely
                 * to die as interrupt masking will not work properly on all
                 * CPUs
                 */
                WARN_ON(gic_supports_nmi() && group0 &&
                        !gic_dist_security_disabled());
        }

        /*
         * Some firmwares hand over to the kernel with the BPR changed from
         * its reset value (and with a value large enough to prevent
         * any pre-emptive interrupts from working at all). Writing a zero
         * to BPR restores is reset value.
         */
        gic_write_bpr1(0);

        if (static_branch_likely(&supports_deactivate_key)) {
                /* EOI drops priority only (mode 1) */
                gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop);
        } else {
                /* EOI deactivates interrupt too (mode 0) */
                gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir);
        }

GIC 시스템 레지스터들을 초기화한다.

  • 코드 라인 3~5에서 현재 cpu에 대한 mpidr 값을 알아와서 aff0 값이 16을 초과한 경우 need_rss에 1을 대입한다.
    • aff0이 16 이상이라는 의미는 최하위 레벨에서 16개 이상의 cpu가 사용되므로 RSS(Range Select Support)=1이되어 최하위 affnity 레벨의 cpu 범위가 0~255까지 라는 의미이다.
  • 코드 라인 16~17에서 SRE(System Register Enable)이 0인 경우 에러를 출력한다.
    • GICv3 하드웨어가 GIC v1~v2 드라이버 코드를 사용하는 경우 SRE=0로 설정하지만, GIC v3 하드웨어에 GIC v3 드라이버 코드를 사용하는 경우 SRE=1로 하여 시스템 레지스터를 사용하는 것이 정확하고, 또 성능에도 유리하다.
  • 코드 라인 19에서 pribits 값을 알아온다.
    • ICC_CTLR_EL1.PRIbits + 1 값
  • 코드 라인 21에서 커널이 group 0를 사용 가능한지 여부를 알아온다.
  • 코드 라인 24~34에서 Pesudo-NMI 기능을 위한 priority masking 기능이 활성화되지 않은 경우 디폴트 priority mask 값 0xf0을 ICC_PMR_EL1.PMR 레지스터에 기록한다.
  • 코드 라인 42에서 펌웨어에서 Binary Point 값을 설정하였을 수도 있으므로 커널에선 이를 0으로 기록하여 리셋되는 기능이 있다.
  • 코드 라인 44~50에서 EL2 부트 여부에 따라 적절한 EOImode를 설정한다.
    • EL2 모드에서 리눅스 커널이 부팅한 경우 호스트 OS로 동작하는 경우이다. 이러한 경우 Guest OS를 지원하기 위해 Host OS는 priority drop과 deactivate를 분리한(ICC_CTLR_EL1_EOImode_drop) EOI 모드를 사용한다.

 

drivers/irqchip/irq-gic-v3.c -2/2-

        /* Always whack Group0 before Group1 */
        if (group0) {
                switch(pribits) {
                case 8:
                case 7:
                        write_gicreg(0, ICC_AP0R3_EL1);
                        write_gicreg(0, ICC_AP0R2_EL1);
                /* Fall through */
                case 6:
                        write_gicreg(0, ICC_AP0R1_EL1);
                /* Fall through */
                case 5:
                case 4:
                        write_gicreg(0, ICC_AP0R0_EL1);
                }

                isb();
        }

        switch(pribits) {
        case 8:
        case 7:
                write_gicreg(0, ICC_AP1R3_EL1);
                write_gicreg(0, ICC_AP1R2_EL1);
                /* Fall through */
        case 6:
                write_gicreg(0, ICC_AP1R1_EL1);
                /* Fall through */
        case 5:
        case 4:
                write_gicreg(0, ICC_AP1R0_EL1);
        }

        isb();

        /* ... and let's hit the road... */
        gic_write_grpen1(1);

        /* Keep the RSS capability status in per_cpu variable */
        per_cpu(has_rss, cpu) = !!(gic_read_ctlr() & ICC_CTLR_EL1_RSS);

        /* Check all the CPUs have capable of sending SGIs to other CPUs */
        for_each_online_cpu(i) {
                bool have_rss = per_cpu(has_rss, i) && per_cpu(has_rss, cpu);

                need_rss |= MPIDR_RS(cpu_logical_map(i));
                if (need_rss && (!have_rss))
                        pr_crit("CPU%d (%lx) can't SGI CPU%d (%lx), no RSS\n",
                                cpu, (unsigned long)mpidr,
                                i, (unsigned long)cpu_logical_map(i));
        }

        /**
         * GIC spec says, when ICC_CTLR_EL1.RSS==1 and GICD_TYPER.RSS==0,
         * writing ICC_ASGI1R_EL1 register with RS != 0 is a CONSTRAINED
         * UNPREDICTABLE choice of :
         *   - The write is ignored.
         *   - The RS field is treated as 0.
         */
        if (need_rss && (!gic_data.has_rss))
                pr_crit_once("RSS is required but GICD doesn't support it\n");
}
  • 코드 라인 2~18에서 커널에서 group 0을 사용가능한 경우 pribits에 따라4 개의 Activate Priorities Group 0 레지스터값에 0을 기록하여 리셋하여 사용한다.
    • ICC_AP0Rn_EL1 레지스터 4개는 EL2에서 physical fiq를 수신하지 않도록 설정된 경우(EL2.HCR_EL2.FMO=0)에만 non-secure EL1 커널에서 access 가능하다.
  • 코드 라인 20~34에서 pribits에 따라4 개의 Activate Priorities Group 1 레지스터값에 0을 기록하여 리셋하여 사용한다.
    • ICC_AP1Rn_EL1 레지스터 4개는 EL2에서 physical irq를 수신하지 않도록 설정된 경우(EL2.HCR_EL2.IMO=0)에만 non-secure EL1 커널에서 access 가능하다.
  • 코드 라인 37에서 non-secure 커널에서 group1 인터럽트를 활성화하기위해 SYS_ICC_IGRPEN1_EL1에 1을 기록한다.
  • 코드 라인 40에서 per-cpu has_rss 변수에 RSS(Range Select Support) 값을 알아온다.
    • SGI 사용시 0인 경우 0~15 cpu, 1인 경우 0~255 cpu
  • 코드 라인 43~51에서 cpu mpidr 값에서 affinity 0에 해당하는 cpu 번호가 RSS 범위를 벗어나는 경우 이를 체크하여 에러 로그를 출력한다.
  • 코드 라인 60~61에서 affinity 0 레벨에서 0~255 cpu 범위를 사용하는데 GIC가 지원하지 않는 경우 에러 로그를 출력한다.

 

gic_enable_sre()

include/linux/irqchip/arm-gic-v3.h

static inline bool gic_enable_sre(void)
{
        u32 val;

        val = gic_read_sre();
        if (val & ICC_SRE_EL1_SRE)
                return true;

        val |= ICC_SRE_EL1_SRE;
        gic_write_sre(val);
        val = gic_read_sre();

        return !!(val & ICC_SRE_EL1_SRE);
}

GIC 시스템 레지스터를 사용할 수 있도록 활성화한다.

  • ICC_SRE_EL1.SRE=1로 활성화한다.

 

gic_get_pribits()

drivers/irqchip/irq-gic-v3.c

static u32 gic_get_pribits(void)
{
        u32 pribits;

        pribits = gic_read_ctlr();
        pribits &= ICC_CTLR_EL1_PRI_BITS_MASK;
        pribits >>= ICC_CTLR_EL1_PRI_BITS_SHIFT;
        pribits++;

        return pribits;
}

priority 레벨링의 단계에 사용되는 비트 수를 알아온다.

  • ICC_CTRL_EL1.PRIbits + 1
    • 이 값이 4인 경우 -> pribits=5가 되고 2^5=32가 된다. (이 칩은 총 32 단계의 priority 레벨을 운영한다.)

 

gic_has_group0()

drivers/irqchip/irq-gic-v3.c

static bool gic_has_group0(void)
{
        u32 val;
        u32 old_pmr;

        old_pmr = gic_read_pmr();

        /*
         * Let's find out if Group0 is under control of EL3 or not by
         * setting the highest possible, non-zero priority in PMR.
         *
         * If SCR_EL3.FIQ is set, the priority gets shifted down in
         * order for the CPU interface to set bit 7, and keep the
         * actual priority in the non-secure range. In the process, it
         * looses the least significant bit and the actual priority
         * becomes 0x80. Reading it back returns 0, indicating that
         * we're don't have access to Group0.
         */
        gic_write_pmr(BIT(8 - gic_get_pribits()));
        val = gic_read_pmr();

        gic_write_pmr(old_pmr);

        return val != 0;
}

EL1에서 동작하는 리눅스 커널에서 group 0를 사용할 수 있는지 여부를 알아온다.

  • group 0가 EL3에서 통제되는 경우 EL1 에서 동작하는 커널에서 최고 높은 우선 순위를 기록하고 읽을 때 그 값이 읽히는 것이 아니라 0이 읽힌다.
  • pribits가 5인 경우 32단계로 priority 레벨링이 동작한다. 그 중 0x00을 제외한 가장 높은 우선 순위인 0x08을 기록해본다.
    • 0x00, 0x08, 0x10, 0x18, 0x20, … 0xf8

 

gic_prio_masking_enabled()

arch/arm64/include/asm/arch_gicv3.h

static inline bool gic_prio_masking_enabled(void)
{
        return system_uses_irq_prio_masking();
}

Pesudo-NMI 기능을 위해 시스템이 irq masking 기능을 사용할 수 있는 경우 true를 반환한다.

 

system_uses_irq_prio_masking()

arch/arm64/include/asm/cpufeature.h

static inline bool system_uses_irq_prio_masking(void)
{
        return IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) &&
               cpus_have_const_cap(ARM64_HAS_IRQ_PRIO_MASKING);
}

CONFIG_ARM64_PSEUDO_NMI 커널 옵션과 IRQ_PRIO_MASKING capability를 가진 경우에 true를 반환한다.

 


Redistributor 초기화

Redistributor 속성들 갱신

gic_update_rdist_properties()

drivers/irqchip/irq-gic-v3.c

static void gic_update_rdist_properties(void)
{
        gic_data.ppi_nr = UINT_MAX;
        gic_iterate_rdists(__gic_update_rdist_properties);
        if (WARN_ON(gic_data.ppi_nr == UINT_MAX))
                gic_data.ppi_nr = 0;
        pr_info("%d PPIs implemented\n", gic_data.ppi_nr);
        pr_info("%sVLPI support, %sdirect LPI support\n",
                !gic_data.rdists.has_vlpis ? "no " : "",
                !gic_data.rdists.has_direct_lpi ? "no " : "");
}

Redistributor들에서 몇 가지 속성을 갱신한다. 그런 후 관련 속성을 정보 출력한다.

 

gic_iterate_rdists()

drivers/irqchip/irq-gic-v3.c

static int gic_iterate_rdists(int (*fn)(struct redist_region *, void __iomem *))
{
        int ret = -ENODEV;
        int i;

        for (i = 0; i < gic_data.nr_redist_regions; i++) {
                void __iomem *ptr = gic_data.redist_regions[i].redist_base;
                u64 typer;
                u32 reg;

                reg = readl_relaxed(ptr + GICR_PIDR2) & GIC_PIDR2_ARCH_MASK;
                if (reg != GIC_PIDR2_ARCH_GICv3 &&
                    reg != GIC_PIDR2_ARCH_GICv4) { /* We're in trouble... */
                        pr_warn("No redistributor present @%p\n", ptr);
                        break;
                }

                do {
                        typer = gic_read_typer(ptr + GICR_TYPER);
                        ret = fn(gic_data.redist_regions + i, ptr);
                        if (!ret)
                                return 0;

                        if (gic_data.redist_regions[i].single_redist)
                                break;

                        if (gic_data.redist_stride) {
                                ptr += gic_data.redist_stride;
                        } else {
                                ptr += SZ_64K * 2; /* Skip RD_base + SGI_base */
                                if (typer & GICR_TYPER_VLPIS)
                                        ptr += SZ_64K * 2; /* Skip VLPI_base + reserved page */
                        }
                } while (!(typer & GICR_TYPER_LAST));
        }

        return ret ? -ENODEV : 0;
}

Redistributor들에서 몇 가지 속성을 갱신한다.

  • 코드 라인 6~16에서 Redistributor 영역 수 만큼 순회하며 GICR_PIDR2.ARCH 값을 읽어 GIC v3 및 v4가 아닌 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 18~22에서 Redistributor 레지스터를 읽어 gic 설정 값을 갱신하기 위해 인자로 전달받은 *fn함수를 호출한다.
    • GIC v3에서 전달한 함수: __gic_update_rdist_properties()
  • 코드 라인 24~25에서 ACPI를 통해 하나의 Residtributor만 구현되었다고 지정된 경우 처리를 중간하고 함수를 빠져나간다.
  • 코드 라인 27~33에서 Resistributor 레지스터들 간에 redist_stride 값 만큼 갭을 둔다. 단 redist_stride가 설정되지 않은 경우 128K/256K(LPIS 지원시) 만큼 갭을 둔다.
  • 코드 라인 34에서 GICR_TYPER.LAST 비트가 0이면 아직 끝나지 않았으므로 다음을 처리하기 위해 계속 루프를 돈다.

 

다음 그림은 4개의 노드에 각각 16개의 cpu를 사용하여 Redistributor를 구성한 모습을 보여준다.

  • Redistributor 레지스터 세트는 각각 256K를 사용하였고, 전체 64 세트이다. (4 redistributor regions * 16 cpus)

 

다음 그림은 1개의 노드에 big/little 클러스터가 같이 구현되어 있는 Redistributor를 구성한 모습과 ppi 파티션이 구성된 모습을 보여준다.

  • Redistributor 레지스터 세트는 각각 128K를 사용하였고, 전체 6 세트이다. (1 redistributor regions * 6 cpus)
  • 2 개의 파티션에 각각 4개 cpu와 2개 cpu가 구성되어 있다.

 

__gic_update_rdist_properties()

drivers/irqchip/irq-gic-v3.c

static int __gic_update_rdist_properties(struct redist_region *region,
                                         void __iomem *ptr)
{
        u64 typer = gic_read_typer(ptr + GICR_TYPER);
        gic_data.rdists.has_vlpis &= !!(typer & GICR_TYPER_VLPIS);
        gic_data.rdists.has_direct_lpi &= !!(typer & GICR_TYPER_DirectLPIS);
        gic_data.ppi_nr = min(GICR_TYPER_NR_PPIS(typer), gic_data.ppi_nr);

        return 1;
}

Redistributor 레지스터를 읽어 속성 갑을 갱신한다.

  • 코드 라인 4에서 Redistributor 레지스터 베이스를 가리키는 ptr을 기반으로 GICR_TYPER 레지스터 값을 읽어온다.
  • 코드 라인 5에서 GICR_TYPER.VLPIS가 하나라도 0이면 false 설정으로 바꾼다.
  • 코드 라인 6에서 GICR_TYPER.DirectLPIS가 하나라도 0이면 false 설정으로 바꾼다.
  • 코드 라인 7에서 GIC를 통해 읽은 PPI 수와 GICR을 통해 읽은 PPI 수 중 작은 값으로 ppi_nr에 대입한다.

 

Redistributor 활성화

gic_populate_rdist()

drivers/irqchip/irq-gic-v3.c

static int gic_populate_rdist(void)
{
        if (gic_iterate_rdists(__gic_populate_rdist) == 0)
                return 0;

        /* We couldn't even deal with ourselves... */
        WARN(true, "CPU%d: mpidr %lx has no re-distributor!\n",
             smp_processor_id(),
             (unsigned long)cpu_logical_map(smp_processor_id()));
        return -ENODEV;
}

Redistributor들에서 cpu의 affinity와 동일한 경우 rd_base와 phys_base를 갱신한다.

 

__gic_populate_rdist()

drivers/irqchip/irq-gic-v3.c

static int __gic_populate_rdist(struct redist_region *region, void __iomem *ptr)
{
        unsigned long mpidr = cpu_logical_map(smp_processor_id());
        u64 typer;
        u32 aff;

        /*
         * Convert affinity to a 32bit value that can be matched to
         * GICR_TYPER bits [63:32].
         */
        aff = (MPIDR_AFFINITY_LEVEL(mpidr, 3) << 24 |
               MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 |
               MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 |
               MPIDR_AFFINITY_LEVEL(mpidr, 0));

        typer = gic_read_typer(ptr + GICR_TYPER);
        if ((typer >> 32) == aff) {
                u64 offset = ptr - region->redist_base;
                gic_data_rdist_rd_base() = ptr;
                gic_data_rdist()->phys_base = region->phys_base + offset;

                pr_info("CPU%d: found redistributor %lx region %d:%pa\n",
                        smp_processor_id(), mpidr,
                        (int)(region - gic_data.redist_regions),
                        &gic_data_rdist()->phys_base);
                return 0;
        }

        /* Try next one */
        return 1;
}

MPIDR을 통해 알아온 4단계 affinity 값과 GICR_TYPER을 통해 읽어온 4 단계 affinity 값이 같은 경우에 한해 rd_base와 phys_base를 갱신하고 로그 정보를 출력한다. 성공 시 0을 반환한다.

  • GICR_TYPER 레지스터는 64비트 값을 가지고 있고 bits[61:32]에 4단계 affinity 값이 담겨있다.

 

gic_enable_redist()

drivers/irqchip/irq-gic-v3.c

static void gic_enable_redist(bool enable)
{
        void __iomem *rbase;
        u32 count = 1000000;    /* 1s! */
        u32 val;

        if (gic_data.flags & FLAGS_WORKAROUND_GICR_WAKER_MSM8996)
                return;

        rbase = gic_data_rdist_rd_base();

        val = readl_relaxed(rbase + GICR_WAKER);
        if (enable)
                /* Wake up this CPU redistributor */
                val &= ~GICR_WAKER_ProcessorSleep;
        else
                val |= GICR_WAKER_ProcessorSleep;
        writel_relaxed(val, rbase + GICR_WAKER);

        if (!enable) {          /* Check that GICR_WAKER is writeable */
                val = readl_relaxed(rbase + GICR_WAKER);
                if (!(val & GICR_WAKER_ProcessorSleep))
                        return; /* No PM support in this redistributor */
        }

        while (--count) {
                val = readl_relaxed(rbase + GICR_WAKER);
                if (enable ^ (bool)(val & GICR_WAKER_ChildrenAsleep))
                        break;
                cpu_relax();
                udelay(1);
        };
        if (!count)
                pr_err_ratelimited("redistributor failed to %s...\n",
                                   enable ? "wakeup" : "sleep");
}

Redistributor를 깨우거나 슬립시킨다. @enable=1일 때 깨우고, @enable=0일 때 슬립한다.

  • 코드 라인 7~8에서 FLAGS_WORKAROUND_GICR_WAKER_MSM8996 워크어라운드 플래그가 설정된 경우 함수를 빠져나간다.
  • 코드 라인 10~18에서 GICR_WAKER.ProcessorSleep 필드를 제거(@enable=1)하거나 추가(@enable=0)한다.
  • 코드 라인 20~24에서 @enable=0일 때 GICR_WAKER.ProcessorSleep 을 다시 읽어 0인 경우 PM을 지원하지 않으므로 함수를 빠져나간다.
  • 코드 라인 26~32에서 최대 1초간 busy wait하며 GICR_WAKER.ChildrenAsleep을 읽은 값과 enable이 서로 다른 경우 루프를 벗어난다.
  • 코드 라인 33~35에서 1초 동안 설정되지 않은 경우 “Redistributor failed to (wakeup|sleep)”을 출력한다.

 


KVM 지원

gic_of_setup_kvm_info()

irq-gic-v3.c

static void __init gic_of_setup_kvm_info(struct device_node *node)
{
        int ret;
        struct resource r;
        u32 gicv_idx;

        gic_v3_kvm_info.type = GIC_V3;

        gic_v3_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
        if (!gic_v3_kvm_info.maint_irq)
                return;

        if (of_property_read_u32(node, "#redistributor-regions",
                                 &gicv_idx))
                gicv_idx = 1;

        gicv_idx += 3;  /* Also skip GICD, GICC, GICH */
        ret = of_address_to_resource(node, gicv_idx, &r);
        if (!ret)
                gic_v3_kvm_info.vcpu = r;

        gic_v3_kvm_info.has_v4 = gic_data.rdists.has_vlpis;
        gic_set_kvm_info(&gic_v3_kvm_info);
}

KVM을 통해 Guest OS를 동작시킬 때 사용할 gic 정보를 저장한다.

 

gic_set_kvm_info()

drivers/irqchip/irq-gic-common.c

void gic_set_kvm_info(const struct gic_kvm_info *info)
{
        BUG_ON(gic_kvm_info != NULL);
        gic_kvm_info = info;
}

virt/kvm/arm/vgic/vgic-init.c – kvm_vgic_hyp_init() 함수에서 gic-v3가 제공한 정보를 가져다 virtual gic를 초기화할 떄 사용한다.

 


GIC v3 Operations

GIC v3용 irq_chip Operations는 가상화 지원 관련하여 두 가지가 사용된다.

  • gic_eoimode1_chip
    • 호스트 OS용
  • gic_chip
    • Guest OS용

 

호스트 OS용 gic_eoimode1_chip

drivers/irqchip/irq-gic-v3.c

static struct irq_chip gic_eoimode1_chip = {
        .name                   = "GICv3",
        .irq_mask               = gic_eoimode1_mask_irq,
        .irq_unmask             = gic_unmask_irq,
        .irq_eoi                = gic_eoimode1_eoi_irq,
        .irq_set_type           = gic_set_type,
        .irq_set_affinity       = gic_set_affinity,
        .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
        .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
        .irq_set_vcpu_affinity  = gic_irq_set_vcpu_affinity,
        .irq_nmi_setup          = gic_irq_nmi_setup,
        .irq_nmi_teardown       = gic_irq_nmi_teardown,
        .flags                  = IRQCHIP_SET_TYPE_MASKED |
                                  IRQCHIP_SKIP_SET_WAKE |
                                  IRQCHIP_MASK_ON_SUSPEND,
};

 

Guest OS용 gic_chip

drivers/irqchip/irq-gic-v3.c

static struct irq_chip gic_chip = {
        .name                   = "GICv3",
        .irq_mask               = gic_mask_irq,
        .irq_unmask             = gic_unmask_irq,
        .irq_eoi                = gic_eoi_irq,
        .irq_set_type           = gic_set_type,
        .irq_set_affinity       = gic_set_affinity,
        .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
        .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
        .irq_nmi_setup          = gic_irq_nmi_setup,
        .irq_nmi_teardown       = gic_irq_nmi_teardown,
        .flags                  = IRQCHIP_SET_TYPE_MASKED |
                                  IRQCHIP_SKIP_SET_WAKE |
                                  IRQCHIP_MASK_ON_SUSPEND,
};

 

인터럽트 Mask & Unmask

gic_eoimode1_mask_irq()

drivers/irqchip/irq-gic-v3.c

static void gic_eoimode1_mask_irq(struct irq_data *d)
{
        gic_mask_irq(d);
        /*
         * When masking a forwarded interrupt, make sure it is
         * deactivated as well.
         *
         * This ensures that an interrupt that is getting
         * disabled/masked will not get "stuck", because there is
         * noone to deactivate it (guest is being terminated).
         */
        if (irqd_is_forwarded_to_vcpu(d))
                gic_poke_irq(d, GICD_ICACTIVER);
}

해당 인터럽트를 mask 한다.

  • 코드 라인 3에서 GICD_ICENABLERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 physical 인터럽트를 mask 한다.
  • 코드 라인 12~13에서 Guest OS에 vcpu를 통해 인터럽트를 전달 가능한 경우 GICD_ICACTIVERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 deactivate 요청을 한다.

 

gic_mask_irq()

drivers/irqchip/irq-gic-v3.c

static void gic_mask_irq(struct irq_data *d)
{
        gic_poke_irq(d, GICD_ICENABLER);
}

해당 인터럽트를 mask 한다.

  • 코드 라인 3에서 GICD_ICENABLERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 physical 인터럽트를 mask 한다.

 

 

gic_unmask_irq()

drivers/irqchip/irq-gic-v3.c

static void gic_unmask_irq(struct irq_data *d)
{
        gic_poke_irq(d, GICD_ISENABLER);
}

해당 인터럽트를 unmask 한다.

  • 코드 라인 3에서 GICD_ISENABLERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 physical 인터럽트를 unmask 한다.

 

레지스터 Peek & Poke

gic_peek_irq()

drivers/irqchip/irq-gic-v3.c

static int gic_peek_irq(struct irq_data *d, u32 offset)
{
        void __iomem *base;
        u32 index, mask;

        offset = convert_offset_index(d, offset, &index);
        mask = 1 << (index % 32);

        if (gic_irq_in_rdist(d))
                base = gic_data_rdist_sgi_base();
        else
                base = gic_data.dist_base;

        return !!(readl_relaxed(base + offset + (index / 32) * 4) & mask);
}

@offset 위치 레지스터의 해당 인터럽트에 대응하는 비트 값을 읽어온다. (1=설정, 0=클리어)

  • 코드 라인 6에서 해당 인터럽트의 @offset에 해당하는 offset을 알아오고, @index에는 세 가지(PPI/SPI, EPPI 및 ESPI) 타입의 0부터 시작하는 값을 알아온다.
  • 코드 라인 9~12에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 베이스 주소를 sgi_base로, 그 외의 경우 dist_base로 한다.
  • 코드 라인 14에서 레지스터 베이스에 offset을 더한 주소에서 산출된 mask 비트 값을 읽어온다.

 

gic_poke_irq()

drivers/irqchip/irq-gic-v3.c

static void gic_poke_irq(struct irq_data *d, u32 offset)
{
        void (*rwp_wait)(void);
        void __iomem *base;
        u32 index, mask;

        offset = convert_offset_index(d, offset, &index);
        mask = 1 << (index % 32);

        if (gic_irq_in_rdist(d)) {
                base = gic_data_rdist_sgi_base();
                rwp_wait = gic_redist_wait_for_rwp;
        } else {
                base = gic_data.dist_base;
                rwp_wait = gic_dist_wait_for_rwp;
        }

        writel_relaxed(mask, base + offset + (index / 32) * 4);
        rwp_wait();
}

@offset 위치 레지스터의 해당 인터럽트에 대응하는 비트에 1을 기록한다.

  • clear-request 또는 set-request 타입 레지스터에 사용한다

 

convert_offset_index()

drivers/irqchip/irq-gic-v3.c

/*
 * Routines to disable, enable, EOI and route interrupts
 */
static u32 convert_offset_index(struct irq_data *d, u32 offset, u32 *index)
{
        switch (get_intid_range(d)) {
        case PPI_RANGE:
        case SPI_RANGE:
                *index = d->hwirq;
                return offset;
        case EPPI_RANGE:
                /*
                 * Contrary to the ESPI range, the EPPI range is contiguous
                 * to the PPI range in the registers, so let's adjust the
                 * displacement accordingly. Consistency is overrated.
                 */
                *index = d->hwirq - EPPI_BASE_INTID + 32;
                return offset;
        case ESPI_RANGE:
                *index = d->hwirq - ESPI_BASE_INTID;
                switch (offset) {
                case GICD_ISENABLER:
                        return GICD_ISENABLERnE;
                case GICD_ICENABLER:
                        return GICD_ICENABLERnE;
                case GICD_ISPENDR:
                        return GICD_ISPENDRnE;
                case GICD_ICPENDR:
                        return GICD_ICPENDRnE;
                case GICD_ISACTIVER:
                        return GICD_ISACTIVERnE;
                case GICD_ICACTIVER:
                        return GICD_ICACTIVERnE;
                case GICD_IPRIORITYR:
                        return GICD_IPRIORITYRnE;
                case GICD_ICFGR:
                        return GICD_ICFGRnE;
                case GICD_IROUTER:
                        return GICD_IROUTERnE;
                default:
                        break;
                }
                break;
        default:
                break;
        }

        WARN_ON(1);
        *index = d->hwirq;
        return offset;
}

해당 인터럽트 타입과 @offset 주소로 레지스터에 대한 offset을 알아온다. 그리고 출력 인자 *index에는 hwirq 번호를 대입한다.

  • 코드 라인 3~7에서 해당 인터럽트가 PPI 및 SPI 범위인 경우 @offset을 그대로 반환하고, @index에는 d->hwirq역시 그대로 반환한다.
  • 코드 라인 8~15에서 해당 인터럽트가 확장 PPI 범위인 경우 @offset을 그대로 반환하고, @index에는 d->hwirq – EPPI_BASE_INTID(1024 + 32) + 32 값을 반환한다.
    • 결국 @index = d->hwirq – 1024 (확장 SPI에 대한 @index는 0 ~ 1023)
  • 코드 라인 16~36에서 해당 인터럽트가 확장 SPI 범위인 경우 @offset에 따라 레지스터 offset을 반환하고, @index에는 d->hwirq – ESPI_BASE_INTID 값을 반환한다.
    • 결국 @index = d->hwirq – 4096 (확장 SPI의 INTID 는 4096 부터 시작, @index는 0부터)
  • 코드 라인 37~47에서 그 외의 범위인 경우 @offset을 그대로 반환하고, @index에는 d->hwirq역시 그대로 반환한다.

 

인터럽트 타입 설정

gic_set_type()

drivers/irqchip/irq-gic-v3.c

static int gic_set_type(struct irq_data *d, unsigned int type)
{
        enum gic_intid_range range;
        unsigned int irq = gic_irq(d);
        void (*rwp_wait)(void);
        void __iomem *base;
        u32 offset, index;
        int ret;

        /* Interrupt configuration for SGIs can't be changed */
        if (irq < 16)
                return -EINVAL;

        range = get_intid_range(d);

        /* SPIs have restrictions on the supported types */
        if ((range == SPI_RANGE || range == ESPI_RANGE) &&
            type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING)
                return -EINVAL;

        if (gic_irq_in_rdist(d)) {
                base = gic_data_rdist_sgi_base();
                rwp_wait = gic_redist_wait_for_rwp;
        } else {
                base = gic_data.dist_base;
                rwp_wait = gic_dist_wait_for_rwp;
        }

        offset = convert_offset_index(d, GICD_ICFGR, &index);

        ret = gic_configure_irq(index, type, base + offset, rwp_wait);
        if (ret && (range == PPI_RANGE || range == EPPI_RANGE)) {
                /* Misconfigured PPIs are usually not fatal */
                pr_warn("GIC: PPI INTID%d is secure or misconfigured\n", irq);
                ret = 0;
        }

        return ret;
}

해당 인터럽트에 대한 트리거 타입을 지정한다.

  • 코드 라인 11~12에서 SGI는 에러를 반환한다.
  • 코드 라인 14~19에서 SPI 또는 확장 SPI 영역인 경우 level high 또는 edge rising 트리거 타입만 지정할 수 있다.
  • 코드 라인 21~27에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 sgi_base를 그 외의 경우 dist_base를 레지스터 베이스로 지정한다.
  • 코드 라인 29에서 PPI/SPI인 경우 GICD_ICFGRn 또는 EPPI/ESPI인 경우 GICD_ICFGRnE 레지스터에 대한 offset 주소를 알아오고 index에는 해당 인터럽트에 대한 0부터 시작하는 인덱스 값을 알아온다.(n)
  • 코드 라인 31~36에서 위에서 알아온 베이스 주소 + offset 주소의 레지스터에 index에 해당하는 인터럽트 위치에 type을 기록하고, rwp_wait 함수를 통해 레지스터에 기록이 완료될 때까지 대기한다.

 

get_intid_range()

drivers/irqchip/irq-gic-v3.c

static enum gic_intid_range get_intid_range(struct irq_data *d)
{
        return __get_intid_range(d->hwirq);
}

해당 인터럽트에 대한 레인지 타입을 알아온다.

 

__get_intid_range()

drivers/irqchip/irq-gic-v3.c

static enum gic_intid_range __get_intid_range(irq_hw_number_t hwirq)
{
        switch (hwirq) {
        case 16 ... 31:
                return PPI_RANGE;
        case 32 ... 1019:
                return SPI_RANGE;
        case EPPI_BASE_INTID ... (EPPI_BASE_INTID + 63):
                return EPPI_RANGE;
        case ESPI_BASE_INTID ... (ESPI_BASE_INTID + 1023):
                return ESPI_RANGE;
        case 8192 ... GENMASK(23, 0):
                return LPI_RANGE;
        default:
                return __INVALID_RANGE__;
        }
}

@hwirq 값에 따른 인터럽트에 대한 레인지 타입을 반환한다.

  • PPI_RANGE: hwirq 16 ~ 31
  • SPI_RANGE: hwirq: 32 ~ 1019
  • EPPI_RANGE: 1024 ~ 1024 + 63
  • ESPI_RANGE: 4096 ~ 4096 + 1023
  • LPI_RANGE: 8192 ~ 0x80_0000 (2^23)

 

gic_intid_range

drivers/irqchip/irq-gic-v3.c

enum gic_intid_range {
        PPI_RANGE,
        SPI_RANGE,
        EPPI_RANGE,
        ESPI_RANGE,
        LPI_RANGE,
        __INVALID_RANGE__
};

 

gic_configure_irq()

drivers/irqchip/irq-gic-common.c

int gic_configure_irq(unsigned int irq, unsigned int type,
                       void __iomem *base, void (*sync_access)(void))
{
        u32 confmask = 0x2 << ((irq % 16) * 2);
        u32 confoff = (irq / 16) * 4;
        u32 val, oldval;
        int ret = 0;
        unsigned long flags;

        /*
         * Read current configuration register, and insert the config
         * for "irq", depending on "type".
         */
        raw_spin_lock_irqsave(&irq_controller_lock, flags);
        val = oldval = readl_relaxed(base + confoff);
        if (type & IRQ_TYPE_LEVEL_MASK)
                val &= ~confmask;
        else if (type & IRQ_TYPE_EDGE_BOTH)
                val |= confmask;

        /* If the current configuration is the same, then we are done */
        if (val == oldval) {
                raw_spin_unlock_irqrestore(&irq_controller_lock, flags);
                return 0;
        }

        /*
         * Write back the new configuration, and possibly re-enable
         * the interrupt. If we fail to write a new configuration for
         * an SPI then WARN and return an error. If we fail to write the
         * configuration for a PPI this is most likely because the GIC
         * does not allow us to set the configuration or we are in a
         * non-secure mode, and hence it may not be catastrophic.
         */
        writel_relaxed(val, base + confoff);
        if (readl_relaxed(base + confoff) != val)
                ret = -EINVAL;

        raw_spin_unlock_irqrestore(&irq_controller_lock, flags);

        if (sync_access)
                sync_access();

        return ret;
}

@irq에 해당하는 2 비트의 @type을 일반/확장 인터럽트 여부에 따라 GICD_TYPERn 또는 GICD_TYPERnE에 기록한다. 기록한 후에는 기록이 완료될 때까지 잠시 대기한다.

 


Pseudo-NMI 지원

Pseudo-NMI 초기화

gic_enable_nmi_support()

drivers/irqchip/irq-gic-v3.c

static void gic_enable_nmi_support(void)
{
        int i;

        if (!gic_prio_masking_enabled())
                return;

        if (gic_has_group0() && !gic_dist_security_disabled()) {
                pr_warn("SCR_EL3.FIQ is cleared, cannot enable use of pseudo-NMIs\n");
                return;
        }

        ppi_nmi_refs = kcalloc(gic_data.ppi_nr, sizeof(*ppi_nmi_refs), GFP_KERNEL);
        if (!ppi_nmi_refs)
                return;

        for (i = 0; i < gic_data.ppi_nr; i++)
                refcount_set(&ppi_nmi_refs[i], 0);

        static_branch_enable(&supports_pseudo_nmis);

        if (static_branch_likely(&supports_deactivate_key))
                gic_eoimode1_chip.flags |= IRQCHIP_SUPPORTS_NMI;
        else
                gic_chip.flags |= IRQCHIP_SUPPORTS_NMI;
}

Pesudo-NMI를 지원하는 경우 초기화 한다.

  • 코드 라인 5~6에서 Pesudo-NMI 기능을 위해 시스템이 irq masking 기능이 필요한데 사용할 수 없으면 그냥 함수를 빠져나간다.
  • 코드 라인 8~11에서 커널에서 gorup 0를 지원하면서 secure를 동작 중인 경우 경고 메시지를 출력하고 함수를 빠져나간다.
    • EL3에서 secure가 동작 중이면 SCR_EL3.FIQ가 설정되어 FIQ를 커널에서 처리해야 한다.
  • 코드 라인 13~15에서 ppi_nmi_refs 레퍼런스 카운터를 ppi 수 만큼 할당한다.
  • 코드 라인 17~18에서 ppi_nmi_refs[]를 모두 0으로 설정한다.
  • 코드 라인 20에서 Pesudo-NMI 기능이 동작한다는 static key를 활성화한다.
  • 코드 라인 22~25에서 EL2 호스트 OS 운영에 따라 irq_chip operations의 플래그에 NMI 지원을 추가한다.

 

Pseudo-NMI 인터럽트 설정

gic_irq_nmi_setup()

drivers/irqchip/irq-gic-v3.c

static int gic_irq_nmi_setup(struct irq_data *d)
{
        struct irq_desc *desc = irq_to_desc(d->irq);

        if (!gic_supports_nmi())
                return -EINVAL;

        if (gic_peek_irq(d, GICD_ISENABLER)) {
                pr_err("Cannot set NMI property of enabled IRQ %u\n", d->irq);
                return -EINVAL;
        }

        /*
         * A secondary irq_chip should be in charge of LPI request,
         * it should not be possible to get there
         */
        if (WARN_ON(gic_irq(d) >= 8192))
                return -EINVAL;

        /* desc lock should already be held */
        if (gic_irq_in_rdist(d)) {
                u32 idx = gic_get_ppi_index(d);

                /* Setting up PPI as NMI, only switch handler for first NMI */
                if (!refcount_inc_not_zero(&ppi_nmi_refs[idx])) {
                        refcount_set(&ppi_nmi_refs[idx], 1);
                        desc->handle_irq = handle_percpu_devid_fasteoi_nmi;
                }
        } else {
                desc->handle_irq = handle_fasteoi_nmi;
        }

        gic_irq_set_prio(d, GICD_INT_NMI_PRI);

        return 0;
}

해당 인터럽트를 Pesudo-NMI로 설정한다.

  • 코드 라인 5~6에서 Pesudo-NMI를 지원하지 않는 시스템인 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 8~11에서 해당 인터럽트가 이미 enable 된 경우 에러 메시지를 출력하고 -EINVAL 에러를 반환한다.
  • 코드 라인 17~18에서 LPI인 경우 지원하지 않으므로 -EINVAL을 반환한다.
  • 코드 라인 21~31에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 다음과 같이 설정한다.
    • PPI/EPPI 타입인 경우 해당 인터럽트 번호별로 첫 번째 설정인 경우에만 핸들러를 handle_percpu_devid_fasteoi_nmi() 함수로 설정한다.
    • 그 외 타입인 경우 핸들러를 handle_fasteoi_nmi() 함수로 지정한다.
    • 참고: Interrupts -2- (irq chip) | 문c
  • 코드 라인 33에서 인터럽트 우선 순위 값을 0x20 (내부 변환 후 0x90)으로 설정한다.
    • non-secure EL1에서 동작하는 커널에서 우선 순위 값을 설정하면 group 1 인터럽트들만 설정 가능하며 다음과 같은 기준으로 변경되어 설정된다.
      • prio(0x20)  >> 1 | 0x80 = 변환 prio(0x90)

 

gic_supports_nmi()

drivers/irqchip/irq-gic-v3.c

static inline bool gic_supports_nmi(void)
{
        return IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) &&
               static_branch_likely(&supports_pseudo_nmis);
}

Pesudo-NMI를 지원하는지 여부를 반환한다.

 

gic_irq_in_rdist()

drivers/irqchip/irq-gic-v3.c

static inline int gic_irq_in_rdist(struct irq_data *d)
{
        enum gic_intid_range range = get_intid_range(d);
        return range == PPI_RANGE || range == EPPI_RANGE;
}

해당 인터럽트가 Redistributor를 사용해야 하는지 여부를 알아온다. (PPI 또는 EPPI인 경우)

 

인터럽트 우선 순위 설정

gic_irq_set_prio()

drivers/irqchip/irq-gic-v3.c

static void gic_irq_set_prio(struct irq_data *d, u8 prio)
{
        void __iomem *base = gic_dist_base(d);
        u32 offset, index;

        offset = convert_offset_index(d, GICD_IPRIORITYR, &index);

        writeb_relaxed(prio, base + offset + index);
}

해당 인터럽트의 우선 순위를 설정한다.

  • non-secure EL1에서 동작하는 커널에서 우선 순위 값을 설정하면 group 1 인터럽트들만 설정 가능하며 다음과 같은 기준으로 변경되어 설정된다.
    • prio  >> 1 | 0x80 = 변환 prio (cpu interface로 전달되는 최종 priority 값이다)

 

Pseudo-NMI 인터럽트 해제

kernel/irq/chip.c

static void gic_irq_nmi_teardown(struct irq_data *d)
{
        struct irq_desc *desc = irq_to_desc(d->irq);

        if (WARN_ON(!gic_supports_nmi()))
                return;

        if (gic_peek_irq(d, GICD_ISENABLER)) {
                pr_err("Cannot set NMI property of enabled IRQ %u\n", d->irq);
                return;
        }

        /*
         * A secondary irq_chip should be in charge of LPI request,
         * it should not be possible to get there
         */
        if (WARN_ON(gic_irq(d) >= 8192))
                return;

        /* desc lock should already be held */
        if (gic_irq_in_rdist(d)) {
                u32 idx = gic_get_ppi_index(d);

                /* Tearing down NMI, only switch handler for last NMI */
                if (refcount_dec_and_test(&ppi_nmi_refs[idx]))
                        desc->handle_irq = handle_percpu_devid_irq;
        } else {
                desc->handle_irq = handle_fasteoi_irq;
        }

        gic_irq_set_prio(d, GICD_INT_DEF_PRI);
}

해당 인터럽트를 Pesudo-NMI에서 일반 인터럽트로 변경하여 설정한다.

  • 코드 라인 5~6에서 Pesudo-NMI를 지원하지 않는 시스템인 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 8~11에서 해당 인터럽트가 이미 enable 된 경우 에러 메시지를 출력하고 -EINVAL 에러를 반환한다.
  • 코드 라인 17~18에서 LPI인 경우 지원하지 않으므로 -EINVAL을 반환한다.
  • 코드 라인 21~29에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 다음과 같이 설정한다.
    • PPI/EPPI 타입인 경우 해당 인터럽트 번호별로 첫 번째 설정인 경우에만 핸들러를 handle_percpu_devid_irq() 함수로 설정한다.
    • 그 외 타입인 경우 핸들러를 handle_fasteoi_irq() 함수로 지정한다.
  • 코드 라인 31에서 인터럽트 우선 순위 값을 0xa0 (내부 변환 후 0xd0)으로 설정한다.
    • non-secure EL1에서 동작하는 커널에서 우선 순위 값을 설정하면 group 1 인터럽트들만 설정 가능하며 다음과 같은 기준으로 변경되어 설정된다.
      • prio(0xa0)  >> 1 | 0x80 = 변환 prio(0xd0)

 


구조체

gic_chip_data 구조체

drivers/irqchip/irq-gic-v3.c

struct gic_chip_data {
        struct fwnode_handle    *fwnode;
        void __iomem            *dist_base;
        struct redist_region    *redist_regions;
        struct rdists           rdists;
        struct irq_domain       *domain;
        u64                     redist_stride;
        u32                     nr_redist_regions;
        u64                     flags;
        bool                    has_rss;
        unsigned int            ppi_nr;
        struct partition_desc   **ppi_descs;
};

GIC v3 컨트롤러의 내부 칩 데이터를 구성한다.

  • *fwnode
    • 펌웨어 노드 정보
  • *dist_base
    • Distributor 영역 가상 주소 베이스
  • *redist_regions
    • Redistributor 영역 수 만큼 redist_region 구조체를 할당하여 사용한다.
  • *domain
    • GIC가 사용하는 irq domain을 가리킨다.
  • redist_stride
    • Distributor들간의 주소 간격이 지정된다.
  • nr_redist_regions
    • Redistributor 영역 수
  • flags
  • has_rss
    • affinity 0 레벨에서 0~255 범위의 cpu 번호를 지원해야 하는지 여부 (Range Select Support)
  • **ppi_descs
    • PPI 파티션을 사용하는 경우 파티션 수 만큼 partition_desc 구조체를 할당하여 사용한다.

 

redist_region 구조체

drivers/irqchip/irq-gic-v3.c

struct redist_region {
        void __iomem            *redist_base;
        phys_addr_t             phys_base;
        bool                    single_redist;
};

하나의 Redistributor 정보를 관리한다.

  •  *redist_base
    • Redistributor 레지스터 가상 주소 베이스
  • phys_base
    • Redistributor 레지스터 물리 주소 베이스
  • single_redist
    • Redistributor 영역 내에 single Redistributor인지 여부

 

rdists 구조체

include/linux/irqchip/arm-gic-v3.h

struct rdists {
        struct {
                void __iomem    *rd_base;
                struct page     *pend_page;
                phys_addr_t     phys_base;
                bool            lpi_enabled;
        } __percpu              *rdist;
        phys_addr_t             prop_table_pa;
        void                    *prop_table_va;
        u64                     flags;
        u32                     gicd_typer;
        bool                    has_vlpis;
        bool                    has_direct_lpi;
};

하나의 Redistributor region 정보를 관리한다.

  •  *rdist
    • cpu 수 만큼 다음 4 항목을 구성한다.
  • *rd_base
    • Redistributor 레지스터 가상 주소 베이스
  • *pend_page
  • phys_base
    • Redistributor 레지스터 물리 주소 베이스
  • lpi_enabled
    • LPI 활성화 여부
  • prop_table_pa
  • *prop_table_va
  • flags
  • gicd_typer
  • has_vlpis
    • VLPIS 지원 여부
    • for GIC v4
  • has_direct_lpi
    • Direct LPI 지원 여부

 

참고