ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] Interrupt - GICv2 part 1
    Linux/kernel 2023. 2. 24. 00:55

    글의 참고

    - DEN0013D_cortex_a_series_PG.pdf - (Interrupt Hanling - 12.2 The Generic Interrupt Controller p12-7)

    - CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual

    - ARM Generic Interrupt Controller Architecture Specification Version 2.0

    - https://stackoverflow.com/questions/36423059/arm-specific-irq-initialization

    - https://zhuanlan.zhihu.com/p/586238925

    - http://www.wowotech.net/irq_subsystem/gic_driver.html

    - https://www.elecfans.com/d/2260663.html

    - https://www.cnblogs.com/LoyenWang/p/12996812.html

    - https://zhuanlan.zhihu.com/p/520171265?utm_id=0

    - https://zhuanlan.zhihu.com/p/641041561


    구조

    - Overview

    " GIC 는 `Generic Interrupt Controller` 의 약자로 ARM 사에서 만든 인터럽트 컨트롤러 아키텍처를 이르는 말이다. 현재는 GIC 600 까지 나왔는데, 600 또한 GIC v3, v4 아키텍처를 지원하고 있다. v2 같은 경우는 8개의 ARM 코어를 지원했으며, v3/v4 는 주로 서버 시장의 ARM 코어를 타겟해서 만들어진 구조라서 8 개 이상의 다수의 ARM 코어를 지원한다. 이 글은 GIC-v2 기반의 GIC-400 에 대해서 주로 다룬다. 추후에 별도의 글로 v3/v4 를 다룬다.

     

    " GIC 구현체는 2가지 방법으로 만들어 질 수 있다.

    1. 첫 번째 방법은 SoC 제조사에서 자신의 SoC 를 개발할 때, GIC IP 만 별개로 구매하는 것이다. 대표적인 예로, PL390, GIC-400, GIC-500 등이 있다. GIC-500 같은 경우는 128개의 CPU 까지 지원한다. 대신 GIC-500 같은 경우는 반드시 ARMv8 ISA 를 기반이여야 하며, GIC-v3 이여야 한다. 

    2. 두 번째는 ARM SoC 를 직접 구매하는 것이다. 이 때, 구매한 ARM SoC 에 GIC 가 포함되어 있다. 예를 들어, ARM Cortex A9 혹은 A15 는 기본적으로 GIC IP 를 포함하고 있다.

     

     

    " 리눅스 커널에서 GIC 커널 소스는 2 군데 위치한다. 

    1. drivers/irqchip/irq-gic.c : 이 파일은 v2 전용 파일이다. v3 같은 경우는, irq-gic-v3.c 파일을 사용한다.
    2. drivers/irqchip/irq-gic-common.c : 이 소스는 v2/v3 에서 공통으로 사용되는 코드가 들어있다.

     

     

     

    - Basic concept in GIC-v2

    1. Interrupt types

    " GIC 에서 인터럽트의 종류는 크게 2가지로 나뉜다.

    1. Peripheral interrupt (PPI or SPI)
    2. Software-generated interrupt (SGI)

     

     

    " GIC-400 으로 input signals 로 들어오는 `peripheral interrupt(input signal)` 은 2 가지로 나눠 볼 수 있다.

    1. SPI : Shared Peripheral Interupt 의 약자로 모든 CPU 에게 공유되는 인터럽트 라인이라고 보면 된다. 

    2. PPI : Private Peripheral Interupt 의 약자로 CPU 마다 private 하게 사용하는 인터럽트라고 생각하면 된다. Per-core timer 가 PPI 를 사용한다. 여기에 할당된 인터럽트 번호는 16 ~ 31 다.

     

     

    " SGI(`Software-generated interrupt`) 조금 특별하다. SGIs 는 소프트웨어에서 사용하기 위한 인터럽트다. 주로 커널에서는 IPI 가 여기에 할당된다. 그리고, SGI 는 기본적으로 edge-triggered 속성을 가지고 있다. 여기에 할당된 인터럽트 번호는 0 ~ 15 다. 프로세서는 SGI 를 만들고 싶다면, GICD_SGIR 에 생성하려는 SGI ID 를 입력해야 한다. 그리고, SGI 를 전달할 CPU interface ID 도 함께 write 해야 한다.

     


     

     

     

    " 아래 그림은 GIC-400 의 SoC 측면에서 interrupt transfer flow 를 보여준다. 중요한 내용은 다음과 같다.

    1. SPI & PPI 는 physical interrupt signal 라인이 있다. 즉, peripherals 에서 생성된 interrupt 를 의미한다.
    2. SGI 는 physical interrupt signal 라인이 없다. 즉, 특정 CPU 에서 다른 CPUs 들에게 IPI 를 보내기 위해서 AXI progarmming interface 를 통해서 GIC 에게 SGI 를 만들어 달라고 요청한다. 
    CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual

     

     

     

    2. Interrupt IDs 

    " ARMv7 만 지원하는 GIC 아키텍처(Ex. GIC-400) 에서 사용한 interrupt IDs 들은 정해져 있다. 인터럽트 ID[0:31] 은 각 CPU interface 에게 private 하다. 즉, banked registers 개녀미라고 보면 된다. interrupt IDs[32:1019] 는 SPIs 로 할당 되어있고, interrupt IDs[1020:1023] 은 `Special interrupt numbers` 로 reserved 되어있다.

    The GIC assigns interrupt ID numbers ID0-ID1019 as follows:
    • Interrupt numbers ID32-ID1019 are used for SPIs.
    • Interrupt numbers ID0-ID31 are used for interrupts that are private to a CPU interface. These interrupts are banked in the Distributor.

    A banked interrupt is one where the Distributor can have multiple interrupts with the same ID. A banked interrupt is identified uniquely by its ID number and its associated CPU interface number. Of the banked interrupt IDs:
    — ID0-ID15 are used for SGIs
    — ID16-ID31 are used for PPIs
    ....

    Interrupt numbers ID1020-ID1023 are reserved for special purposes, see Special interrupt numbers on page 3-41.

    - 참고 : ARM Generic Interrupt Controller Architecture Specification

     

     

    3. Input

    " GIC input signals(peripherals -> GIC) 은 크게 2 가지로 나뉜다.

    1. PPI : Per-CPU 개념으로 보면된다. 즉, CPU 마다 할당되는 interrupt.
    2. SPI : 모든 CPUs 들에게 shared 한 interrupt 다. 

     

     

    4. Output

    " GIC output signals(GIC -> [CPU, Power Controller etc]) 의 종류는 다음과 같다.

     

    Interrupt output signals AXI slave interface signals
     1. nIRQCPU & nFIRCPU - hardware interrupts 들은 결국에는 이 2 개중에 하나의 exception 으로 translation 된다. 그리고, ARM CPU 는 nIRQCPU 이 asserted 되면 IRQ mode 로, nFIQCPU 가 asserted 되면 FIQ mode 로 진입한다. 

    2. nIRQOUT & nFIQOUT - wake up signal 로 사용된다.


    High-level Considerations for Power Management of a big.LITTLE System Application Note 424

    3. AMBA AXI4 slave interface - GIC Distributor, GIC CPU Interface, virtual CPU inerfaces 등을 컨트롤할 수 있는 라인. 즉, ARM CPU 는 AXI4 를 통해 GIC 를 컨트롤 한다고 볼 수 있다.

     

     

     

    5. Interrupt status

    " ARM Generic Interrupt Controller Architecture Specification Version 2.0 에서 제공하는 interrupt states 의 관계 및 정의는 다음과 같다.

     


    ARM Generic Interrupt Controller Architecture Specification
    1. Inactive : 해당 인터럽트 라인은 pending 도 아니고, active 도 아닌 상태. 즉, 대기중 인 인터럽트도 없고, 처리중 인 인터럽트도 없는 상태. 다른 말로 idle 상태를 의미한다.

    2. Pending : GIC 가 하드웨어 및 소프트웨어 인터럽트를 인지한 상태를 의미한다. 그러나, 아직 프로세서에게 해당 interrupt 를 forwarding 하지 않은 상태를 나타낸다.

    3. Active : 프로세서가 GIC 에서 대기중 인 higest-pending interrupt 를 인지해서, 현재 해당 인터럽트를 처리중 인 상태를 나타낸다. 그러나, 아직 interrupt handling 을 완료는 하지 못한 상태.

    4. Active and Pending : 프로세서가 인터럽트를 처리중 인 상황에서, 이 때, 현재 CPU 가 처리중 인 interrupt 가 GIC 에 또 인지된 경우(발생한 경우)를 의미한다. 즉, 프로세서가 interrupt 를 처리중 이기 때문에 `Active`, GIC 는 interrupt 가 pending 되어 있으므로 `Pending`. CPU & GIC 의 interrupt states 가 합쳐저 `Active and Pending` 이 된 것이다.  

     

     

    " 보충 설명을 하자면, 프로세서가 인터럽트를 인지하면, 인터럽트를 active 상태로 설정한다. 인터럽트 처리가 완료되면, GIC 에게 인터럽트 처리가 완료됬음을 알려야 한다. 그런데, 이 시점에 GIC 에 pending interrupt 가 없다면, GIC 는 해당 인터럽트를 inactive 상태로 설정한다.

     

     

     

    - Internal logic in GIC-v2

    " GIC 의 내부 구조는 크게 2 가지 모듈로 나눠볼 수 있다.


    ARM Generic Interrupt Controller Architecture Specification

     

     

     

    1. Distributor
    " arm64 에 존재하는 모든 하드웨어 인터럽트 라인은 GIC 에 연결된다. GIC 안에는 `distributor` 라는 별도의 모듈이 존재한다. 이 모듈은 GIC 에 연결된 모든 인터럽트 라인의 상태를 감지하고, 발생한 인터럽트를 어떤 CPU interface 로 전달해야 할 지를 결정한다. 이 때, 인터럽트를 받는 CPU interface 는 한 개일 수도 있고, 여러 개일 수 도 있다. 만약에 다수의 인터럽트가 동시에 발생할 경우, 우선 순위가 높은 인터럽트를 먼저 전달한다. distributor 과련 레지스터는 접두사로 `GICD_*` 가 붙는다. 디스트리뷰터가 하는 일을 요약하면 다음과 같다.

    1. Interrupt enable / disable - GIC Distributor 가 interrupt 를 control 하는 방식은 2 가지가 있다.

    1. global interrupt control(GIC_DIST_CTRL) - global interrupt 가 disabled 되면, interrupt sources 에서 발생한 어떠한 interrupts 도 GIC CPU Interface 로 forwarding 되지 않는다.
      
    2. each interrupt control(GIC_DIST_ENABLE_CLEAR) - 특정 interrupt source 에서 발생한 interrupt 만 GIC CPU interface 로 forwarding 되는 것을 막을 수 있다.  

    2. interrupt distribution - 동시에 여러 개의 인터럽트가 발생했을 때, 가장 우선 순위가 높은 인터럽트를 딱 하나의 GIC CPU Interface 로 forwarding 할 수 있다.
     
    3. priority control - global interrupt priority 설정 가능

    4. interrupt trigger moe 설정 - level-sensitive or edge-triggerd

    5. interrupt group 설정 - group 0 or group 1



    2. CPU interface
    " 각 CPU interface 는 GIC Distributor 로 받은 pending interrupt 를 자신과 연결된 processor 에게로 forwarding 한다. 각 GIC CPU Interface 제공하는 기능은 다음과 같다
    (GIC CPU Interface 레지스터는 접두사로 `GICC_*` 가 붙는다).

    1. enable / disalbe nIRQCPU or nFIQCPU - GIC CPU Interface 와 CPU 는 nIRQCPU & nFIQCPU 로 연결되어 있다. 만약, ARM CPU 에서 interrupt flag 를 disable(local interrupts disabled) 하면, GIC Distributor 가 GIC CPU Interface 에게 interrupt 를 forwarding 하더라도, CPU 는 nIRQ or nFIQ 가 asserted 되더라도, 인지하지 못한다.

    2. interrupt acknowledge - ARM CPU 가 GIC CPU Interface 로 부터 highest priority interrupt 를 받으면, 이에 대한 응답을 해야한다(이 응답이라는 것이 단순히 GIC CPU Interface Interrupt Acknowledge Register 를 읽는 것을 의미한다). CPU 가 GIC CPU Interface 로 부터 highest priority interrupt 에 대해 acknowledge 하면, GIC Distributor 는 interrupt status 를 pending 에서 `active` or `active and pending` 으로 변경한다. 이 시점에 GIC CPU Interface 는 processor 에게 또 다른 interrupt 을 signal 할 수 있다(현재 processor 에서 처리중 인 interrupt 를 preempt 할 수 있다는 뜻). 만약, CPU 가 GIC CPU Interface 로 부터 highest priority interrupt 에 대해 acknowledge 한 시점에, GIC CPU Interface 에서 processor 에게 signal 할 만한 interrupts 가 없을 경우(현재 처리중 인 interrupt 보다 우선 순위가 높은 interrupt 가 없을 경우), nIRQCPU 와 nFIQCPU 라인을 deassert 한다.

    3. interrupt 처리가 끝났음을 notification - processor 의 interrupt handler 에서 interrupt processing 이 완료되면, GIC CPU Interface register 에 write 함으로써, GIC 에게 CPU interrupt processing 이 마쳤음을 알린다. 완료 됬음이 알려지면, 2 가지 동작이 수행된다.

    1. GIC Distributor 는 interrupt status 를 de-active 로 변경한다.
    2. GIC CPU Interface 는 priority 를 drop 한다. 이걸 통해 다른 pending interrupts 들이 CPU 로 forwarding 될 수 있게한다.

    4. priority mask 설정 - 우선 순위가 낮은 interrupts 들은 masked 시킬 수 있다. 즉, CPU 에게 forwarding 하지 않는다. 

    5. GIC CPU Interface 에 다수의 인터럽트가 동시에 도착했을 때, 가장 우선 순위가 높은 interupt 를 forwarding 한다. 

     

     

    " GIC-400 에서 physical interrupts 를 처리하는 block diagram 을 보여준다.


    CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual

     

     

    - Banking

    " arm 에서 `bank` 라는 표현은 한 번에 이해하기가 쉽지 않다. 가장 비슷한 표현이 `Per-CPU` 라고 볼 수 있다. 즉, CPU 마다 개별적으로 존재하는 리소스라고 생각하면 좋을 것 같다. GIC-v2 아키텍처에서 사용하는 `banking` 에는 2 가지 의미가 있다.

    1. Interrupt banking : MP 환경에서, PPI`s 와 SGI`s 는 GCI 입장에서 동일한 인터럽트 ID 로 서로 다른 인터럽트에 접근할 수 있는 메커니즘을 제공한다. 즉, CPU 마다 개별적인 인터럽트가 존재한다는 뜻이다. 이러한 인터럽트를 흔히 `banked interrupt` 라고 부른다. 

    2. Register banking : 동일한 레지스터 주소에 access 했지만, GIC 및 CPU 마다 개별적인 레지스터로 access 되는 것을 의미한다. arm 에서 동작 모드를 설명할 때, `banked register` 라는 용어가 있다. Per-CPU 와 비슷한 개념이다. 그러나, 차이가 있다. Per-CPU 는 CPU 마다 개별적으로 존재하는 리소스라는 의미를 가지고 있다면, banked 는 arm 의 동작 모드마다 존재하는 레지스터를 의미한다. 그런데, arm 에서 banked 는 동작 모드에서만 사용되는 것은 아니다. GIC 에서도 banked 라는 용어가 사용된다. 예를 들어, PPI`s , SGI`s 등이 있다. 뿐만 아니라, GIC 는 CPU interface 에게도 banked register 를 제공해서 CPU 마다 자신과 연결된 CPU interface 와 통신할 수 있는 메커니즘을 제공한다.

     

     

     

     

    - GIC-v2 timing diagram [참고1]

    " GIC 는 인터럽트 우선 순위에 따른 선점을 지원한다. 즉, 높은 우선 순위를 갖는 인터럽트는 낮은 우선 순위를 갖는 인터럽트를 선점할 수 있다. GIC distributor 는 pending 상태인 인터럽트 중 우선 순위가 가장 높은 인터럽트를 찾는다. 그리고, 현재 서비스 중 인 인터럽트를 선점한 뒤 우선 순위가 가장 높은 인터럽트를 먼저 처리하도록 한다. 그러나, 이건 GIC 입장에서다. 즉, GIC 가 이렇게 한다는 것이 CPU 가 이걸 받아주냐는 별개의 일이다. 리눅스 커널 입장에서는 이러한 현상을 어떻게 바라볼까?

     

    " 리눅스 커널에서는 만약 CPU 가 낮은 우선 순위의 인터럽트를 처리 중 인 상황에서 GIC 가 더 높은 우선 순위 인터럽트를 보내더라도, CPU 가 인터럽트를 인식하는 시점에 인터럽트를 mask 하기 때문에, CPU 가 인터럽트를 다시 On 하기 전까지는 응답하지 못한다. 이 말은 CPU 플래그 레지스터에서 인터럽트 플래그를 mask 하는 것이 GIC 의 동작에는 어떠한 영향도 주지 않는다는 것을 알 수 있다. 아래 GIC-v2 인터럽트 타이밍 다이어그램을 분석해보자.

     


    https://documentation-service.arm.com/static/5f0434f6cafe527e86f5d6cb?token=

     

     

    " 분석전에 몇 가지를 가정한다.

    1. N,M 은 각각 하드웨어 인터럽트를 의미한다. 우선 순위는 N 이 더 크다.
    2. 2개 모두 SPI type, level-trigger, active-high 라고 가정한다.
    3. 2개 모두 동일한 CPU 로 전달되도록 설정되었다.
    4. 2개 모두 그룹 0 에 매핑되었고 FIQ 를 통해서 인터럽트가 트리거된다. 

     

     

    " 위 타이밍 다이어그램에서 인터럽트의 Input 은 `0` 과 `1` 과 같은 실제 전기적 신호를 의미한다. State 는 GIC 가 인터럽트를 관리하기 위해 내부적으로 사용하는 데이터다. 즉, State 는 전기적 신호가 아님에 주의하자. 이제 각 Tn 에 따른 상황을 분석해보자.

    T1 : distributor 는 그룹 0 의 인터럽트 M 을 감지한다. 

    T2 : distributor 는 인터럽트 M 을 pending state 로 설정한다. 

    T17 : 15 clock cycles 이후에(T2 - T17 이므로 17 - 2 = 15), CPU interface 는 nFIQCPU 라인을 low 로 내린다. 이 시점에 CPU interface 의 `ack register(GICC_IAR)` 에는 인터럽트 M 가 써지게 된다. 주의할 점은 아직도 인터럽트 M 의 상태는 `pending` 인 점에 주의하자. 즉, 아직 CPU 에서 서비스하지 않는다. 그런데 앞에서 언급된 `15 clock cycles 이후에` 는 무슨 뜻일까? nFIQCPU 라인을 assertion 하는데 10 ~ 20 cycles 정도가 소모된다(실제로는 nFIQCPU 라인을 assertion 하는 것보다 distributor 가 이 때, `highest priority pending interrupt` 를 계산하는데 10 ~ 20 cycles 정도를 소모한다). 
     

    T42 : distributor 가 인터럽트 N 을 감지한다. 그런데, 현재 서비스 중인 인터럽트 M 보다 우선 순위가 더 높다는 것을 인지하게 된다.

    T43 : distributor 는 인터럽트 N 이 M 보다 우선 순위가 높기 때문에, highest priority pending interrupt 를 M 에서 N 으로 대체한다. 그리고, 인터럽트 N 의 state 를 `pending` 상태로 설정한다. 아래 GICC_HPPIR 은 GIC 에서 CPU interface 로 전달된 인터럽트 중 가장 우선 순위가 높은 pending interrupt 가 작성되어있다.

     

    T58 : 15 clock cycles 이후에(T43 - T58 이므로 58 - 43 = 15), CPU interface 는 nFIQCPU 라인을 low 로 내린다. 그러나, 이 시점에는 이미 nFIQCPU 라인이 low 이므로, 바뀌지 않는다. CPU interface 는 CPU 가 어떤 인터럽트가 발생했는지를 알려주기 위해 GICC_IAR 레지스터를 인터럽트 N 으로 업데이트한다.

    T61 : 그런데, 왜 T58 에서 T61 로 넘어왔을까? 앞에 T58 의 작업들이 3 clock cycles 을 소모하기 때문이다. 이 시점에 프로세서는 GICC_IAR 을 읽는다. 즉, highest priority pending interrupt(인터럽트 N)를 인지한다. 그리고, 이 시점에 distributor 가 인터럽트 N 의 상태를 `active and pending` 로 변경한다. 왜 active and pending 으로 변경할까? 아래에서 인터럽트가 SPI 혹은 PPI 인 경우, 그리고 level-sensitive 인 경우, 프로세서가 인터럽트를 처리하기 전 까지는 de-asserted 할 수가 없다. 그리고, pending 


    T61-T131 : CPU 가 인터럽트 N 을 서비스 중임을 나타낸다.

    T64 : CPU interface 는 `nFIQCPU[n]` 라인을 de-assert 한다. 이 라인이 release 되는 것에는 어떤 의미가 있을까? IRQ 든, FIQ 든 말 그대로 `interrupt request` 를 의미한다. 즉, 인터럽트 요청이 끝난 것을 의미한다. 왜냐면, T61 부터 프로세서가 인터럽트를 인식해서 처리중이기 때문이다.

    T126 : 이 시점은 위에 3 혹은 15 clock cycles 처럼 명확히 정의가 되어있는 시간이 아니다. ARM 에서 예시 설명을 위해 임의의로 정한 시간이기 때문에, `왜 T126 일까?` 에 대해 고민하지 말자. 이 시점은 T131 에서 실제 인터럽트 처리가 완전히 끝낼 것이어서 먼저 peripheral 에게 인터럽트 처리가 완료됬음을 알리는 시점이다. 엣지 트리거라면 한 번 트리거하고 끝냈겠지만, 레벨 트리거다 보니 인터럽트 처리가 끝날 때 쯤 peripheral 에게 `이제 인터럽트 처리 끝났다잉~` 을 알려줘야 한다. 이 시점에 peripheral 이 인터럽트 N 을 de-assert 한다. 
     
    T128 : T126 에서 peripheral 이 인터럽트 N 을 de-assert 함으로써, 인터럽트 상태에서 pending state 가 제거된다. GIC-v2 스펙에서 레벨 트리거의 경우, pending state 의 유무는 오직 peripheral input signal 이다. 즉, peripheral input signal 가 1 이면, pending state 가 추가되고, 0 이면 제거된다. 이 섹션에서는 아래 스펙중에서 B2 만 알면된다. B2 는 `Active and Pending -> Active` 로의 상태 전환을 의미한다.


    T131 : 프로세서는 인터럽트 N 의 ID 를 `End of Interrupt Register(GICC_EOIR)` 에 쓴다. GICC_EOIR 에 인터럽트 ID 를 쓰면, 해당 인터럽트가 끝났음을 의미한다. 즉, T131 시점에 인터럽트가 처리가 완전히 끝났음을 의미한다. 이 시점에 distributor 는 인터럽트 N 을 de-activate 한다. 여기서 de-activate 란, idle 을 의미한다. 즉, 해당 인터럽트는 대기 중이거나 처리 중 인 인터럽트가 없다는 것을 의미한다.

     

    T146 : GICC_EOIR 에 인터럽트 N 을 write 한 후(T131), Tph 클락 사이클(15 clock cycles)이 지나면, distributor 가 new highest priority pending interrupt(인터럽트 M)를 CPU interface 에게 forward 한다. 그리고, 이 시점에 CPU interface 는 GICC_IAR 에 인터럽트 M 의 ID 를 write 하고 nFIQCPU[n] 을 low 로 asserted 한다.

    T211 : 프로세서가 GICC_IAR 을 읽고 highest priority pending interrupt(인터럽트 M) 를 인지하고, distributor 는 인터럽트 M 을 `active and pending` 상태로 변경한다.

    T214 : T211 이후에 3 clock cycle 이 지나면, CPU Interface 는 nFIQCPU[n] 을 de-assert 한다.

     

     

     

     

    - Initialization of GIC-v2 irq chip driver 

    1. matching process of GIC device node(devce-tree) and GIC irq chip driver

    " 리눅스 커널의 모든 하드웨어 정보는 디바이스 트리에 명시되어 있다. 디바이스 드라이버는 디바이스를 초기화하기 전에 이 정보를 읽어와야 한다. GIC 또한 마찬가지다. GIC 는 먼저 `IRQCHIP_DECLARE` 매크로를 사용해서 `static const struct of_device_id` 변수를 생성한다. 그리고, 이 변수를 `__irqchip_of_table` 섹션에 생성한다.

    // drivers/irqchip/irqchip.h - v3.17-rc7
    /*
     * This macro must be used by the different irqchip drivers to declare
     * the association between their DT compatible string and their
     * initialization function.
     *
     * @name: name that must be unique accross all IRQCHIP_DECLARE of the
     * same file.
     * @compstr: compatible string of the irqchip driver
     * @fn: initialization function
     */
    #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
    // include/linux/of.h - v3.17-rc7
    #ifdef CONFIG_OF
    #define _OF_DECLARE(table, name, compat, fn, fn_type)			\
    	static const struct of_device_id __of_table_##name		\
    		__used __section(__##table##_of_table)			\
    		 = { .compatible = compat,				\
    		     .data = (fn == (fn_type)NULL) ? fn : fn  }
    #else
    #define _OF_DECLARE(table, name, compat, fn, fn_type)					\
    	static const struct of_device_id __of_table_##name		\
    		__attribute__((unused))					\
    		 = { .compatible = compat,				\
    		     .data = (fn == (fn_type)NULL) ? fn : fn }
    #endif
    
    typedef int (*of_init_fn_2)(struct device_node *, struct device_node *);
    typedef void (*of_init_fn_1)(struct device_node *);
    
    #define OF_DECLARE_1(table, name, compat, fn) \
    		_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
    #define OF_DECLARE_2(table, name, compat, fn) \
    		_OF_DECLARE(table, name, compat, fn, of_init_fn_2)

     

     

    " 위에 매크로가 depth 가 내려갈 수 록, 파라미터가 1 개씩 추가되고 있다. 아래 그림은 IRQCHIP_DECLARE() 매크로 함수를 사용했을 때, 최종적으로 생성되는 변수를 보여준다.

     

     

    " IRQCHIP_DECLARE 매크로를 통해서 생성되는 GIC-v2 구현체들은 다음과 같다. 그런데, GIC 드라이버를 구현하는 회사는 arm 만 있을거 같은데, 중간에 퀄컴이 보이는 것 같다. 이 글에서 분석하는 GICv2 코드는 리눅스 오픈 소스로 배포되고 있느 코드다. 그러나, 실제 현업에서 퀄컴 소스를 만나면 지금 현재 오픈되어 있는 코드와는 많이 다른 GICv2 코드를 보게 될 수 있다. 즉,  `qcom,msm-8660-qgic` 와 `qcom,msm-qgic2` 모델은 오픈 소스 GICv2 를 사용한다는 것이다. 그러나, 실제 현업에서 만나게 되는 퀄컴의 다른 모델들은 GICv2 를 사용하더라도, 공개되어 있는 소스를 사용하지 않을 가능성이 높다. 

     

    // /drivers/irqchip/irq-gic.c - v3.17-rc7
    IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
    IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
    IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
    IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
    IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
    IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
    * ARM Generic Interrupt Controller

    ARM SMP cores are often associated with a GIC, providing per processor interrupts (PPI), shared processor interrupts (SPI) and software generated interrupts (SGI).

    Primary GIC is attached directly to the CPU and typically has PPIs and SGIs. Secondary GICs are cascaded into the upward interrupt controller and do not have PPIs or SGIs.

    Main node required properties:

    - compatible : should be one of:
        "arm,arm1176jzf-devchip-gic"
        "arm,arm11mp-gic"
        "arm,cortex-a15-gic"
        "arm,cortex-a7-gic"
        "arm,cortex-a9-gic"
        "arm,eb11mp-gic"
        "arm,gic-400"
        "arm,pl390"
        "arm,tc11mp-gic"
        "brcm,brahma-b15-gic"
        "nvidia,tegra210-agic"
        "qcom,msm-8660-qgic"
        "qcom,msm-qgic2"

    - 참고 : https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt

     

     

    " 많은 GIC 구현체들이 IRQCHIP_DECLARE 매크로 함수를 호출할 때, 초기화 함수로 `gic_of_init` 함수를 등록한다. 그리고, 위에서 언급했듯이 IRQCHIP_DECLARE 매크로를 통해 생성된 모든 변수들은 `__irqchip_of_table` 섹션안에서 생성된다. 이 섹션을 `irq chip table` 이라고 부른다.  IRQCHIP_DECLARE 매크로를 확인해보면, irq chip table 에 들어가는 정보는 결국 `struct of_device_id` 구조체를 통해 생성되는 데이터들이다.

    // include/linux/mod_devicetable.h - v3.17-rc7
    /*
     * Struct used for matching a device
     */
    struct of_device_id
    {
    	char	name[32];
    	char	type[32];
    	char	compatible[128];
    	const void *data;
    };

     

     

    " struct of_device_id 구조체는 device node 와 device driver 를 매칭하기 위해 사용된다. 이 중에서 name, type, compatible 필드들이 매칭 프로세스에서 사용된다(그러나, 사실 `compatible` 필드만 있어도 대부분의 매칭 프로세스가 완료된다). 더 자세한 내용은 `__of_device_is_compatible` 함수를 참고하자.

    - name : device node 의 이름
    - type : device node 의 타입
    - compatible : __of_device_is_compatible() 함수에서 `name, type, compatible` 을 통해 matching process 를 진행한다. 이 중에서 가장 높은 매칭 점수가 부여되는 요소가 `compatible` 이다. 
    - data : GIC 같은 경우는, 여기에 초기화 함수를 저장한다.

     

     

     

    2. deivce node 

    " 이제 디바이스 트리에서 GIC 의 하드웨어 정보가 어떻게 작성되는지 알아보자. 모든 GIC-v2 구현체들이 `drivers/irqchip/irq-gic.c` 드라이버를 사용한다면, 어떻게 차이를 만들어낼까? 즉, 모든 GICv2 구현체들이 `drivers/irqchip/irq-gic.c` 를 사용한다면, 자신들이 만든 GIC 에 최적화된 속성 및 특징을 어떻게 구현할까? 이렇게 자신들만의 속성 및 특징을 `디바이스 트리` 에 작성하면 된다. 이 정보들은 커널이 초기화되는 시점에 device node 와 device driver 가 매칭되면, 초기화 함수의 파라미터로 전달된다.

     

    " device tree subsystem 은 시스템에 존재하는 모든 device nodes 들에 대한 정보를 가지고 있다. 만약, GIC-400 을 사용한다면, device tree node database 에서 GIC-400 node 가 있을 것이다. 그렇다면, device tree node database 에 GIC-400 noed 는 그냥 생기는 것일까? 그렇지 않다. device tree 에 GIC-400 node 를 작성해야 한다. 아래와 같이 말이다(이 글에서는 VGIC 와 MSI 는 다루지 않는다).

     

    // https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
    ....
    - reg : Specifies base physical address(s) and size of the GIC registers. The
      first region is the GIC distributor register base and size. The 2nd region is
      the GIC cpu interface register base and size.
    
    ....
    
    Example:
    
    	intc: interrupt-controller@fff11000 {
    		compatible = "arm,cortex-a9-gic";
    		#interrupt-cells = <3>;
    		#address-cells = <1>;
    		interrupt-controller;
    		reg = <0xfff11000 0x1000>, // GIC distributor register base and size
    		      <0xfff10100 0x100>; // GIC CPU interface register base and size 
         };
    - compatible : driver 와 매칭 프로세스에서 사용될 속성. 
    - interrupt-controller : 해당 노드가 `interrupt controller` 임을 나타낸다.
    - reg : 첫 번째 컬럼은 GIC Distributor 의 <base address size> 를 명시한다. 두 번째 컬럼은 GIC CPU interface 의 <base address size> 를 명시한다.

     

     

     

    3. Matching of device node and irq chip driver

    " 그렇다면, device node 와 GIC-v2 driver 는 언제 매칭될까? `irqchip_init()` 함수는 커널이 초기화되는 시점에 호출되는 함수다(start_kernel() -> init_IRQ() -> irqchip_init()). 이 함수는 내부적으로 `of_irq_init()` 함수를 호출하는데, of_irq_init() 함수에서 device nodes 를 탐색해서 matching 되는 모든 interrupt controllers 들을 초기화한다.

    // drivers/irqchip/irqchip.c - v3.17-rc7
    extern struct of_device_id __irqchip_of_table[];
    
    void __init irqchip_init(void)
    {
    	of_irq_init(__irqchip_of_table);
    }

     

     

    " `__irqchip_of_table` 은 kernel irq chip table 의 first address 다. irq chip table 에는 kernel 에 등록된 모든 interrupt controller 와 device tree 에서 파싱된 device node 들을 matching 시키기 위한 정보가 저장되어 있다. 바로, 모든 interrupt controller 들의 ID 정보가 들어있다. 리눅스 커널의 초기화 루틴에서 `of_irq_init()` 함수가호출되기전에, device tree 초기화가 완료하고, device tree 에 작성된 모든 device nodes 들은 tree structure 를 형성한다(여기서 deivce node 는 실제 물리적인 device 를 디바이스 트리를 통해서 software 적으로 추상화한 객체라고 보면 된다).  

     

    " of_irq_init() 함수가 하는 일은 interrupt controllers 들 간에 tree structue 를 형성하기 위해서 모든 device nodes 를 탐색한다(여기서 "왜 interrupt controllers 들이 tree structure 로 형성되는가?" 같은 의문점이 들 수 있다. 이건 irq domain 에 대한 지식이 필요하다. 이 글에서는 생략하도록 한다).

    // drivers/of/irq.c - v3.17-rc7
    /**
     * of_irq_init - Scan and init matching interrupt controllers in DT
     * @matches: 0 terminated array of nodes to match and init function to call
     *
     * This function scans the device tree for matching interrupt controller nodes,
     * and calls their initialization functions in order with parents first.
     */
    void __init of_irq_init(const struct of_device_id *matches)
    {
    	struct device_node *np, *parent = NULL;
    	struct intc_desc *desc, *temp_desc;
    	struct list_head intc_desc_list, intc_parent_list;
    
    	INIT_LIST_HEAD(&intc_desc_list);
    	INIT_LIST_HEAD(&intc_parent_list);
    
    	for_each_matching_node(np, matches) {
    		if (!of_find_property(np, "interrupt-controller", NULL) ||
    				!of_device_is_available(np))
    			continue;
    		/*
    		 * Here, we allocate and populate an intc_desc with the node
    		 * pointer, interrupt-parent device_node etc.
    		 */
    		desc = kzalloc(sizeof(*desc), GFP_KERNEL);
    		if (WARN_ON(!desc))
    			goto err;
    
    		desc->dev = np;
    		desc->interrupt_parent = of_irq_find_parent(np);
    		if (desc->interrupt_parent == np) // of_irq_find_parent() 함수는 interrupt-parent 가 있으면, parent 를 반환하고, interrupt-parent 속성이 명시되어 있지 않으면 NULL 을 반환. 즉, interrupt-parent 속성이 명시되어 있지 않으면 root interrupt controller 를 의미
    			desc->interrupt_parent = NULL; // desc->interrupt_parent 가 NULL 이면, root interrupt controller 를 의미
    		list_add_tail(&desc->list, &intc_desc_list);
    	}
    
    	/*
    	 * The root irq controller is the one without an interrupt-parent.
    	 * That one goes first, followed by the controllers that reference it,
    	 * followed by the ones that reference the 2nd level controllers, etc.
    	 */
    	while (!list_empty(&intc_desc_list)) { // intc_desc_list 에서 하나가 제거될 때 마다, interrupt controller initialization function 이 호출되면서, intc_parent_list 로 이동함. 즉, intc_desc_list 가 비어있다는 것은 모든 interrupt controllers 들의 initialization function 이 호출되었다는 뜻
    		/*
    		 * Process all controllers with the current 'parent'.
    		 * First pass will be looking for NULL as the parent.
    		 * The assumption is that NULL parent means a root controller.
    		 */
    		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
    			const struct of_device_id *match;
    			int ret;
    			of_irq_init_cb_t irq_init_cb;
    			
                // 부모가 있다면, root 를 다시 돈다.
                // 첫 번째 루프에서 root interrupt controller 의 interrupt_parent 가 NULL 임을 확인했다.
                // 그리고, 이 함수에서 `parent` 지역 변수의 초기값은 NULL 이다.
    			if (desc->interrupt_parent != parent) 
    				continue;
    
    			list_del(&desc->list); // parent interrupt controller 이므로, child interrupt controller list(intc_desc_list) 에서 제거. 그러나, 최종적으로는 모든 interrupt controllers 들이 호출되므로, intc_desc_list 와 intc_parent_list 를 자식 / 부모 리스트로 나누는 것은 결과적으로는 맞지 않다.
    			match = of_match_node(matches, desc->dev); // compatible 을 통해 interrupt controller 의 of_device_id 추출
    			....
    
    			irq_init_cb = (of_irq_init_cb_t)match->data; // match->data 는 gic_of_init 함수를 의미. 즉, irq_init_cb 또한 gic_of_init 함수를 의미
    			ret = irq_init_cb(desc->dev, desc->interrupt_parent); // 자신의 device node 와 부모 device node 를 전달
    			....
    
    			/*
    			 * This one is now set up; add it to the parent list so
    			 * its children can get processed in a subsequent pass.
    			 */
    			list_add_tail(&desc->list, &intc_parent_list); // intc_parent_list 에 제일 앞에 있는 interrupt controller 는 root interrupt controller 로 `interrupt-controller` 프로퍼티는 있지만, `interrupt-parent` 프로퍼티는 없는 interrupt controller 를 의미.
    		}
    
    		/* Get the next pending parent that might have children */
    		desc = list_first_entry_or_null(&intc_parent_list,
    						typeof(*desc), list);
    		if (!desc) {
    			pr_err("of_irq_init: children remain, but no parents\n");
    			break;
    		}
    		list_del(&desc->list);
    		parent = desc->dev;
    		kfree(desc);
    	}
    
    	list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
    		list_del(&desc->list);
    		kfree(desc);
    	}
    err:
    	list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
    		list_del(&desc->list);
    		kfree(desc);
    	}
    }

     

     

    " interrupt controller tree structure 가 형성되면, root interrupt controller node 를 시작으로 irq chip table 에서 각각의 interrupt controller`s device node 를 탐색한다. 그리고, deivce node 와 interrupt controller driver 가 매칭되면, interrupt controller driver 의 초기화 함수를 호출한다. 이 때, irq chip driver 초기화 함수에 인자로 interrupt controller`s device node 와 parent device node 가 전달된다. 

     

     

     

    - GIC-v2 base address initialization

    1. gic_of_init

    " device node 와 interrupt controller driver 가 matched 되면, 초기화 함수가 호출된다. IRQCHIP_DECLARE() 매크로 함수에서 봤겠지만, 모든 GICv2 구현체들은 초기화 함수로 gic_of_init() 함수를 등록하고 있다.

    // drivers/irqchip/irq-gic.c - v3.17-rc7
    #ifdef CONFIG_OF
    static int gic_cnt __initdata;
    
    static int __init gic_of_init(struct device_node *node, struct device_node *parent)
    {
    	void __iomem *cpu_base;
    	void __iomem *dist_base;
    	u32 percpu_offset;
    	int irq;
    
    	if (WARN_ON(!node))
    		return -ENODEV;
    
    	dist_base = of_iomap(node, 0); // device tree 에 작성된 GIC Distributor MMIO 영역을 dist_base 에 mapping 
    	WARN(!dist_base, "unable to map gic dist registers\n");
    
    	cpu_base = of_iomap(node, 1); // device tree 에 작성된 GIC CPU Interface MMIO 영역을 cpu_base 에 mapping
    	WARN(!cpu_base, "unable to map gic cpu registers\n");
    
    	if (of_property_read_u32(node, "cpu-offset", &percpu_offset)) // device tree 에 `cpu-offset` 속성이 있는지 확인 
    		percpu_offset = 0;
    
    	gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
    	if (!gic_cnt)
    		gic_init_physaddr(node);
    
    	if (parent) { // child GICs 라면, interrupt cascade 처리 루틴 진입
    		irq = irq_of_parse_and_map(node, 0);
    		gic_cascade_irq(gic_cnt, irq);
    	}
    	gic_cnt++;
    	return 0;
    }

     

     

    " 먼저 gic_of_init() 함수에 전달되는 파라미터부터 알아보자.

    - node : 자신의 device tree 정보(하드웨어 정보) 를 가지고 있는 객체. 흔히 이걸 `device node` 라고 부른다. 일반적으로, 모든 드라이버 코드들은 probe 시점에 device node 의 정보를 파싱해서 실제 하드웨어를 초기화한다.

    - parent : 자신의 parent interrupt controller`s device node 를 의미한다(interrupt controller 에서 parent 가 이해가 안간다면, irq domain 을 공부하자).

     

     

    " 위에서 GIC-v2 구현체들이 사용해야 할 디바이스 트리 속성들을 살펴봤었다. 그 때, `reg` 프로퍼티의 첫 번째 컬럼은 `GIC Distributor` 의 base address, 두 번째 컬럼은 `GIC CPU interface` 의 base address 를 나타낸다고 했었다. of_iomap 함수는 reg 프로퍼티를 파싱해서 __iomem 형태로 반환해주는 함수다. 두 번째 인자인 index 는 reg 프로퍼티에서 몇 번째 컬럼을 address -> iomem 으로 컨버팅할 것인지를 의미한다(이 글에서는 VGIC 와 MSI 는 다루지 않는다).

     

     

    " GIC 의 디바이스 트리 문서를 보면, `cpu-offset` 이라는 속성이 있다. cpu-offset 을 이해하기 위해서는 banked register 에 대한 개념을 먼저 이해해야한다. banked register 는 한 개의 주소 및 한 개의 레지스터에 접근하는데 각각의 레지스터에 접근되는 것을 의미한다. 예를 들어, 시스템에 4개의 CPU 가 존재한다고 가정하자. 그리고, 특정 시점에 모든 CPU 들이 동일한 주소의 레지스터에 접근한다고 치자. 이 때, 이 레지스터가 banked register 라면, 각 CPU 는 동일 주소에 접근했지만, 서로 다른 레지스터에 접근하게 된다. 그런데, 만약에 GIC 가 banked register 가 없다면, Per-CPU 메커니즘을 이용해서 소프트웨어적으로 이와 비슷하게 구현해야 한다. 이 때, 각 CPU 마다 접근해야 하는 주소는 offset 을 기준으로 달라진다.

    cpu-offset : per-cpu offset within the distributor and cpu interface regions, used when the GIC doesn't have banked registers. The offset is cpu-offset * cpu-nr.

    - 참고 : https://github.com/torvalds/linux/commit/db0d4db22a78d31c59087f7057b8f1612fecc35d

     

     

    " 이 속성은 GIC 가 banked register 를 사용하지 않는 구조로 설계되어있지 않고, per-cpu GIC Distributor & GIC CPU Interface 구조로 설계되었을 때, 사용되는 property 다. 특정 아키텍처에서(Ex. exynos)는 CPU 마다 Distributor 와 CPU interface 의 base address 를 다르게 둔다[참고1]. 즉, arm 과 같이 banked register 를 사용해서 하나의 주소에 access 하지만, CPU 마다 다른 GIC Distributor 와 CPU interface 접근하는 것이 아니라, CPU 마다 고유의 GIC Distributor 와 CPU interface 주소를 두는 것이다. Intel APIC 구조는 각 CPU 마다 local APIC 를 별도로 가지고 있다. GIC 설계가 per-cpu 구조를 사용한다면, Distributor & CPU Interface 에 access 할 주소는 `cpu-offset * cpu number` 형식을 따라야 한다.

    // https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
    ....
    - cpu-offset	: per-cpu offset within the distributor and cpu interface
      regions, used when the GIC doesn't have banked registers. The offset is
      cpu-offset * cpu-nr.
    ....

     

     

    " interrupt controllers 들은 cascaded 로 구성될 수 있다(interrupt controller cascade 를 알고 싶다면, irq domain 을 공부하고 오거나, 혹은, 초기 x86 에서 사용되었던 PIC8259 구조를 공부하고 오자). root generic interupt controller(root GIC) 의 초기화 함수(gic_of_init) 의 `parent` 파라미터는 NULL 이 전달된다. 당연하다. 왜냐면, 자기보다 상위에 존재할 interrupt controller 는 없기 때문이다. 그러므로, root GIC 의 초기화 함수에서는 init_of_parse_and_map(), gic_cascade_irq() 함수는 실행되지 않는다.

     

    " second GICs(root GIC 의 직속 자식들) 들 같은 경우는, parent(root GIC) 의 interupt source 가 된다. second GICs 들이 interrupt source 라면, 이에 대응하는 IRQ handler 도 등록해야 한다. 이러한 관점에서 child GICs 들의 초기화 과정은 2 개의 파트로 나뉘게 된다.

    1. 일반적인 interrupt controller 처럼 동작하는 파트. 해당 역할로 동작할 때는 parent interrupt controller 에게 interrupt forwarding 하는 것에 초점을 둔다.

    2. I/O device 와 같이 interrupt 를 발생시키는 역할을 한다. 즉, 일반적인 device driver 처럼 IRQ handler 를 등록해야 할 필요가 있다. 왜냐면, parent GIC 에게 인터럽트가 도착했을 때, 이게 어떤 인터럽트인지를 먼저 판단해야 한다. 그래서, 자신의 child GIC 의 레지스터를 체크해서 발생한 인터럽트를 검사한다. 그리고, 발생한 인터럽트에 대응하는 IRQ handler 를 호출한다. 이 때, parent GIC 에서 호출하는 IRQ handler 가 child GIC 에서 등록한 IRQ handler 가 된다. 그리고, child GIC IRQ handler 에서 device driver 의 IRQ handler 를 호출한다. 

     

    " `irq_of_parse_and_map()` 함수를 이해하기 위해서는 irq domain 글을 참고하자. 

     

     

     

    2. gic_init_bases

    " GIC 내부에는 여러 가지 모듈이 존재한다. 그 중에서 대표적으로 distributor 와 CPU interface 가 있다. 리눅스 커널에서 이들과 통신하기 위해서는 이들의 base address 를 알아야 한다. gic_init_bases 함수가 

    // drivers/irqchip/irq-gic.c - v3.17-rc7
    void __init gic_init_bases(unsigned int gic_nr, int irq_start,
    			   void __iomem *dist_base, void __iomem *cpu_base,
    			   u32 percpu_offset, struct device_node *node)
    {
    	irq_hw_number_t hwirq_base;
    	struct gic_chip_data *gic;
    	int gic_irqs, irq_base, i;
    	int nr_routable_irqs;
    
    	BUG_ON(gic_nr >= MAX_GIC_NR);
    
    	gic = &gic_data[gic_nr];
    #ifdef CONFIG_GIC_NON_BANKED
    	....
    #endif
    	{	/* Normal, sane GIC... */
    		WARN(percpu_offset,
    		     "GIC_NON_BANKED not enabled, ignoring %08x offset!",
    		     percpu_offset);
    		gic->dist_base.common_base = dist_base;
    		gic->cpu_base.common_base = cpu_base;
    		gic_set_base_accessor(gic, gic_get_common_base);
    	}
    
    	/*
    	 * Initialize the CPU interface map to all CPUs.
    	 * It will be refined as each CPU probes its ID.
    	 */
    	for (i = 0; i < NR_GIC_CPU_IF; i++)
    		gic_cpu_map[i] = 0xff;
    
    	/*
    	 * For primary GICs, skip over SGIs.
    	 * For secondary GICs, skip over PPIs, too.
    	 */
    	if (gic_nr == 0 && (irq_start & 31) > 0) { // --- 1
    		hwirq_base = 16;
    		if (irq_start != -1)
    			irq_start = (irq_start & ~31) + 16;
    	} else {
    		hwirq_base = 32;
    	}
    
    	/*
    	 * Find out how many interrupts are supported.
    	 * The GIC only supports up to 1020 interrupt sources.
    	 */
    	gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; // --- 2
    	gic_irqs = (gic_irqs + 1) * 32;
    	if (gic_irqs > 1020)
    		gic_irqs = 1020;
    	gic->gic_irqs = gic_irqs;
    
    	gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ // --- 3
    
    	if (of_property_read_u32(node, "arm,routable-irqs", // --- 4
    				 &nr_routable_irqs)) {
    		irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, // --- 5
    					   numa_node_id());
    		if (IS_ERR_VALUE(irq_base)) {
    			WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
    			     irq_start);
    			irq_base = irq_start;
    		}
    
    		gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, // --- 6
    					hwirq_base, &gic_irq_domain_ops, gic);
    	} else {
    		gic->domain = irq_domain_add_linear(node, nr_routable_irqs, // --- 6
    						    &gic_irq_domain_ops,
    						    gic);
    	}
    
    	if (WARN_ON(!gic->domain))
    		return;
    
    	if (gic_nr == 0) { // root GIC 에서만 실행하는 루틴. 
    #ifdef CONFIG_SMP
    		set_smp_cross_call(gic_raise_softirq); // --- 7
    		register_cpu_notifier(&gic_cpu_notifier); // --- 8
    #endif
    		set_handle_irq(gic_handle_irq);
    	}
    
    	gic_chip.flags |= gic_arch_extn.flags;
    	gic_dist_init(gic);
    	gic_cpu_init(gic);
    	gic_pm_init(gic);
    }

     

     

    (1) `gic_nr` 은 GIC 의 개수를 의미한다. 즉, root GIC 같은 경우는 gic_nr 이 0 이다(gic_init_base() 함수의 gic_nr 은 gic_of_init() 함수에서 전달되므로, gic_nr 을 알고싶다면, gic_of_init() 함수를 다시 보고 오자). `hwirq(hardware interrupt ID)` 란 GIC 에 등록된 HW interrupt ID 를 의미한다(리눅스 커널에서 관리하는 IRQ number 가 아니다). GIC 의 모든 interrupts(PPI, SGI, SPI) 들이 linux kernel IRQ framework 에 mapping 되는 것은 아니다. GIC 의 SGIs 같은 경우는 CPUs 들끼리 통신하기 위한 software interrupt 로 사용된다. 그래서, 리눅스 커널은 SGIs 를 HW interrupt 라고 보지 않기 때문에, IRQ number 로 mapping 하지 않는다. `hwirq_base` 변수는 GIC 에 mapping 된 base interrupt ID 를 의미한다. 예를 들어, `hwirq_base = 16` 은 GIC 에 할당된 HW interrupt ID 0 ~ 15 번(SGIs) 은 무시하겠다는 뜻이다(SGIs 같은 경우 GIC 에서 고정으로 0 ~ 15 번으로 할당됨). 참고로, root GIC 만 PPIs 를 IRQ number 로 mapping 해서 hwirq_base 가 16 으로 설정되고, 다른 child GICs 들은 PPI 를 mapping 하지 않기 때문에, hwirq_base 가 32 로 설정된다. 

     

     

    (2) `gic_irqs` 변수는 GIC 에서 지원되는 수 있는 최대 interrupt 개수를 저장한다. 이 정보는 GIC 의 특정 register 에 저장되는데,  GIC version 마다 이름이 다르다. GIC-400 문서에서 볼 수 있다시피, maximum interrupt number 는 `(ITLinesNumber+1) * 32` 다. GIC-400 은 ARMv7 에 최적화 되어있다. 그리고, ARMv7 에서 설명하는 GIC 의 maximum interrupt number 는 1020 까지다.

     


    ARM Cortex-A Series Programmer’s Guide
    - GICv1 : GIC_DIST_CTR 레지스터의 하위 5비트(ITLinesNumber)
    - GICv2 : GICD_TYPER(Interrupt Controller Type Register)

    CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual

     

     

    (3) mapping 이 필요없는 GIC 의 HW interrupt ID(SGIs in all GICs or PPIs in childs GICs) 는 커널에서 관리할 필요가 없으므로(커널은 IRQ number 만 관리), 전체 interrupt 개수에 뺀다. 이 시점부터 gic_irqs 변수에 실제로 리눅스 커널에서 관리할 전체 interrupt 개수가 저장된다.

     

     

    (4) `of_property_read_u32()` 함수는 GIC device node 에서 `arm,routable-irqs` 속성을 읽어서 `nr_routable_irqs` 변수에 저장한다. 만약, arm,routable-irqs 속성이 있다면, 0 을 반환하고, 없다면 에러를 반환한다. 일부 SoC 에서는 peripheral interrupt request signal line 이 GIC 에 직접 연결되지 않고, crossbar / multiplexer 를 통해서 GIC 에 동적으로 연결된다. arm,routable-irqs 속성에는 GIC 에 직접적으로 연결되지 않는 interrupt 의 개수를 명시한다.

     

    " 참고로, arm,routable-irqs 속성은 현재는 사용되지 않는 속성이다. 심지어, GIC 디바이스 트리 문서에서 아예 지워진 속성이다(commit message 가 명언이다).

    더보기

    Subject: [PATCH v4 11/21] DT: arm,gic: kill arm,routable-irqs
    Date: Mon, 19 Jan 2015 09:44:05 +0000 [thread overview]
    Message-ID: <1421660655-21394-12-git-send-email-marc.zyngier@arm.com> (raw)
    In-Reply-To: <1421660655-21394-1-git-send-email-marc.zyngier@arm.com>

    Nobody will regret it.

    Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
    ---
     Documentation/devicetree/bindings/arm/gic.txt | 6 ------
     1 file changed, 6 deletions(-)

    diff --git a/Documentation/devicetree/bindings/arm/gic.txt b/Documentation/devicetree/bindings/arm/gic.txt
    index 8112d0c..631cb71 100644
    --- a/Documentation/devicetree/bindings/arm/gic.txt
    +++ b/Documentation/devicetree/bindings/arm/gic.txt
    @@ -52,11 +52,6 @@ Optional
       regions, used when the GIC doesn't have banked registers. The offset is
       cpu-offset * cpu-nr.


    -- arm,routable-irqs : Total number of gic irq inputs which are not directly

    - connected from the peripherals, but are routed dynamically
    - by a crossbar/multiplexer preceding the GIC. The GIC irq
    - input line is assigned dynamically when the corresponding
    - peripheral's crossbar line is mapped.

     

    Example:
      intc: interrupt-controller@fff11000 {
    @@ -64,7 +59,6 @@  Example:
      #interrupt-cells = <3>;
      #address-cells = <1>;
      interrupt-controller;
    - arm,routable-irqs = <160>;
      reg = <0xfff11000 0x1000>,
            <0xfff10100 0x100>;
      };

     

     

    (5) of_property_read_u32() 함수에서 에러를 반환할 경우, peripheral interrupts 들이 GIC 와 직접 연결되었다는 것을 의미한다. 이 시점에, `irq_alloc_descs()` 함수를 호출해서 IRQ number 에 대응하는 interrupt descriptors 들을 할당해야 한다. 만약, irq_start 가 0 보다 크다면, 특정 IRQ number 에 대응하는 interrupt descriptor 를 별도로 할당하겠다는 것이다(irq_start 는 gic_of_init() 함수에서 전달되는 값이다. 그런데, gic_of_init() 함수에서 gic_init_bases() 함수를 호출할 때는 `-1` 을 전달한다(현재 우리가 분석하고 있는 시나리오)). irq_start 가 -1 이면, 특정 IRQ number 를 지정하지 않겠다는 뜻이다.

    /**
     * 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)
     *
     * 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)
    {
    	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);
    	}
    	....
    };

     

    " 두 번째 인자는 interrupt dsecriptor 를 할당받을 start IRQ number 를 명시한다. 세 번째 인자는 몇 개의 interrupt descriptors 를 할당받을지를 명시한다. 실제 interrupt dsecriptor 를 할당하는 함수는 `__irq_alloc_descs()` 함수다. 이 함수 내부를 보면, 2 가지 사실을 알 수 있다.

    1. irq(irq_start) 가 -1 보다 크면, 결국 start IRQ number 를 의미한다. 즉, `from == irq_start(irq)` 라고 볼 수 있다.
    2. irq(irq_start) 가 -1 이라면, from 을 명시하는 것은 의미가 없다. 왜냐면, `arch_dynirq_lower_bound()` 함수에 의해서 override 되기 때문이다.

     

     

     

    (6) 이 코드는 irq domain data structure 를 kernel 에 등록한다. 그런데, 리눅스 커널에서 irq domain 이라는 개념은 왜 필요한걸까? 리눅스 커널에서 IRQ number 는 특정 디바이스가 발생시킨 interrupt request 를 식별하는데 사용된다. 그러나, interrupt controller 는 커널이 사용하는 IRQ number 가 뭔지 모른다. interrupt controller 는 interrupt source(peripheal device`s interrupt request signal) 를 encoded 한 HW interrupt number 로 식별한다.

     

    " 위에 상황을 정리해보면, linux kernel 과 interrupt controller 는 interrupt sources 를 식별하는 방법이 서로 다르다는 것을 알 수 수 있다. 그렇다면, IRQ number 랑 HW interrupt number 를 mapping 할 수 있는 새로운 메커니즘이 필요하다. 여기서 translation data structure 가 필요했고, 그게 바로 `struct irq_domain` 이다.

     

    " interrupt controller 마다 자신만의 irq domain 을 형성할 수 있고, 자신의 irq domain 에 연결된 하위 interrupt sources 들을 parsing 할 책임이있다. 예를 들어, 특정 SoC 의 interrupt controllers 들의 구조가 cascade 방식으로 형성되었다면, non-root interrupt controllers 들은 자신의 parent irq domain 의 interrupt souce 가 된다. 이 내용을 struct irq_domain 에서 확인해보자.

    /**
     * struct irq_domain - Hardware interrupt number translation object
     * ....
     * @ops: pointer to irq_domain methods
     * @host_data: private data pointer for use by owner.  Not touched by irq_domain
     *             core code.
     * ....
     */
    struct irq_domain {
    ....
    	const struct irq_domain_ops *ops;
    	void *host_data;
    ....
    };

     

     

    " `host_data` 변수는 interrupt controller driver 를 만드는 제조사(SoC 제조사인 삼성, 퀄컴, 미디어텍 등) 의 private data 가 저장된다. 그래서, linux kernel interrupt subsystem 에서는 host_data 변수는 건들지 않는다. 예를 들어, GIC driver 같은 경우는 struct irq_domain.host_data 변수에 struct gic_chip_data 를 참조하도록 설정한다.

    //drivers/irqchip/irq-gic.c - v3.17-rc7
    struct gic_chip_data {
    	union gic_base dist_base; // GIC Distributor`s base address space
    	union gic_base cpu_base; // GIC CPU Interface`s base address space 
    #ifdef CONFIG_CPU_PM // GIC power management 
    	u32 saved_spi_enable[DIV_ROUND_UP(1020, 32)];
    	u32 saved_spi_conf[DIV_ROUND_UP(1020, 16)];
    	u32 saved_spi_target[DIV_ROUND_UP(1020, 4)];
    	u32 __percpu *saved_ppi_enable;
    	u32 __percpu *saved_ppi_conf;
    #endif
    	struct irq_domain *domain; // 해당 GIC 가 형성하는 irq domain
    	unsigned int gic_irqs; // GIC 에서 지원하는 IRQ number 개수
    #ifdef CONFIG_GIC_NON_BANKED
    	void __iomem *(*get_base)(union gic_base *);
    #endif
    };

     

     

    " 위에서 계속 언급했었지만, GIC 에서 지원하는 모든 HW interrupt IDs 들을 IRQ numbers 로 mapping 시키는 것은 아니다. 이 말은 SGIs 는 IRQ number 로 mapping 시키지 않기 때문에, 리눅스 커널에서 SGIs 는 irq domain 에 mapping process 를 진행할 필요가 없다. GIC 의 irq domain 을 커널에 등록할 때, 눈여겨 봐야할 또 다른 data structure 는 `struct irq_domain_ops` 다.

    // drivers/irqchip/irq-gic.c - v3.17-rc7
    static const struct irq_domain_ops gic_irq_domain_ops = {
    	.map = gic_irq_domain_map,
    	.unmap = gic_irq_domain_unmap,
    	.xlate = gic_irq_domain_xlate,
    };

     

     

    " irq domain 은 linux kernel interrupt subsystem 에서 사용하는 개념이다. specific irq chip driver 관점에서는 linux kernel interrupt subsystem 이 irq domain 을 분석 및 파싱할 수 있도록 callback functions 들을 제공해야 한다. 예를 들어, specific irq chip driver 에서는 gic_irq_domain_map() 콜백 함수에 IRQ number 와 HW interrupt ID 가 어떻게 mapping 할 것인지에 대한 방법을 구현해야 한다. 그리고, specific irq chip driver 는 커널에 자신의 irq domain 을 등록할 때, irq_domain_add_legacy() 혹은 irq_domain_add_linear() 함수를 호출하면서 함께 gic_irq_domain_ops 를 전달하면 된다. 더 자세한 내용은 이 글을 참고하자. 

     

     

    (7) set_smp_cross_call() 함수는 IPI callback 함수를 설정한다. IPI(Inter-Processor Interrupt) 는 CPU 끼리 데이터를 주고 받을 때, 사용한다. GIC 같은 경우는 `gic_raise_softirq()` 함수를 IPI callback 함수로 지정한다. 예를 들어, CPU[0] 에서 다른 CPU 들에게 IPI 를 날리고 싶다면, smp_cross_call() 함수를 호출하면 된다. 이 때, smp_cross_call() 함수의 첫 번째 인자는 IPI 를 받을 CPU mask 를 전달한다. 그렇면, IPI 를 받는 다른 CPUs 들에서 gic_raise_softirq() 함수가 callback 된다.

     

     

    (8) SMP, 즉, multi-processors 환경에서는 processor 의 상태가 변경되면(예를 들어, hotplug(online or offline)), GIC 에게 이러한 상황을 알려야 한다. 왜냐면, SoC 구조에서 GIC CPU Interface 는 GIC 쪽보다는 CPU 쪽에 더 가깝게 있다. 즉, CPU 의 power domain 에 GIC CPU interface 가 영향을 받는 것이다(PCSA 참고)

     

     

     

    - GIC hardware initialization

    1. Distributor initlization

    " GIC 는 크게 2가지 모듈로 나눌 수 있다고 했다. 우리는 먼저 distributor 초기화 과정에 대해서 알아본다.

    // include/linux/irqchip/arm-gic.h - v6.5
    #define GICD_ENABLE			0x1
    #define GICD_DISABLE			0x0
    ....
    
    #define GIC_DIST_CTRL			0x000
    ....
    #define GIC_DIST_TARGET			0x800
    // drivers/irqchip/irq-gic.c - v3.17-rc7
    static void __init gic_dist_init(struct gic_chip_data *gic)
    {
    	unsigned int i;
    	u32 cpumask;
    	unsigned int gic_irqs = gic->gic_irqs; // gic_irqs 는 GIC 가 지원하는 interrupt 개수
    	void __iomem *base = gic_data_dist_base(gic); // GIC Distributor`s base address
    
    	writel_relaxed(0, base + GIC_DIST_CTRL); // --- 1
    
    	/*
    	 * Set all global interrupts to this CPU only.
    	 */
    	cpumask = gic_get_cpumask(gic); // --- 2
    	cpumask |= cpumask << 8;
    	cpumask |= cpumask << 16; // --- 3
    	for (i = 32; i < gic_irqs; i += 4)
    		writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4); // --- 4
    
    	gic_dist_config(base, gic_irqs, NULL); // --- 5
    
    	writel_relaxed(1, base + GIC_DIST_CTRL); // --- 6
    }

     


    (1) `Distributor Control Register(GIC_DIST_CTRL)` 은 GIC Distributor 에 있는 pending interrupts 를 CPU interface 로 forwarding 할지 말지를 결정한다. GICD_CTLR 에 `0` 을 write 할 경우, distributor 는 pending interrupt 를 CPU interface 로 전달하지 않는다. 그렇다면, 이 시점에 모든 interrupt requests(group 0 & group 1) 를 disable 하는 이유가 뭘까? 아직 모든 interrupt sources 들의 groups 이 설정되지 않았기 때문이다(GIST_DIST_IGROUP).

     

     

    (2) gic_get_cpumask 함수에서 눈 여겨볼 점은 `Interrupt Processor Targets Registers` 다. GIC_DIST_TARGETn(`Interrupt Processor Targets Registers`) 은 GIC Distributor 에 위치해 있으며, pending interrupts 를 어떤 GIC CPU Interface 로 forwarding 할지를 결정한다(여기서 주의할 점은 CPU 로 전달되는게 아니라는 점이다). GICD_ITARGETSRn 레지스터는 32-bit 레지스터로 총 1020 개 interrupt(0xBFC - 0x800) 를 지원한다(아래 문서를 보면, GICD_ITARGETSRn 의 범위는 0x800 ~ 0xBF8 이지만, 0xBF8 은 GICD_ITARGETSR1020 시작 주소이기 때문에 실제 마지막 범위는 0xBFC 가 된다). 각, 레지스터는 8 비트로 나눠져 있으며, 각 필드는 interrupt[n] 이 어떤 GIC CPU Interface 로 forwarding 될지를 명시하고 있다. target CPU 를 8 비트로 표현하기 때문에, 최대 8 개 CPU 까지만 지원한다.

     

    " 그리고, GICD_ITARGETSR0 ~ GICD_ITARGETSR7 레지스터들은 interrupt 0 ~ 31(SGI & PPI) 의 forwarding 을 확인하기 위해서다. 그리고, 이 레지스터들은 `read-only` 속성이며, banked registers 다. 그러나, 그 뒤에, GICD_ITARGETSR8 ~ GICD_ITARGETSR255 는 GIC Distributor 가 SPI 가 발생했을 때, 어떤 GIC CPU Interface 로 forwarding 할지를 명시해야 하기 때문에, `Read-Write` 속성을 갖는다. 그리고, 이 레지스터들은 banked register 가 아니다.

     

     

     

    " gic_get_cpumask() 함수는 왜 mask 가 0 이 아닌 시점에 루프를 종료할까? GICD_ITARGETSR0 ~ GICD_ITARGETSR7 레지스터의 값이 모두 동일하기 때문이다. 왜냐면, 이 레지스터들은 banked register 라서 특정 GIC CPU Interface 로만 forwarding 되도록 설계되어 있을 것이다. 예를 들어, GICD_ITARGETSR0[7:0], GICD_ITARGETSR0[15:8], GICD_ITARGETSR0[23:16], GICD_ITARGETSR0[31:24] 의 값은 모두 동일할 것이다. 나머지 GICD_ITARGETSR1 ~ GICD_ITARGETSR7 들도 마찬가지다. 

     

     

     

    " 그렇다면, 왜 1 바이트를 반환하는 것일까? GICD_ITARGETSR0[7:0], GICD_ITARGETSR0[15:8],  GICD_ITARGETSR0[23:16], GICD_ITARGETSR0[31:24] 들에 값이 모두 동일하기 때문이다.

    //drivers/irqchip/irq-gic.c - v3.17-rc7
    static u8 gic_get_cpumask(struct gic_chip_data *gic)
    {
    	void __iomem *base = gic_data_dist_base(gic);
    	u32 mask, i;
    
    	for (i = mask = 0; i < 32; i += 4) {
    		mask = readl_relaxed(base + GIC_DIST_TARGET + i);
    		mask |= mask >> 16;
    		mask |= mask >> 8;
    		if (mask)
    			break;
    	}
    
    	if (!mask)
    		pr_crit("GIC CPU mask not found - kernel will fail to boot.\n");
    
    	return mask;
    }

     

     

    " 만약, GIC CPU Interface 와 CPU 가 엄격하게 1:1 대응한다면, GIC CPU Interface[n] 과 CPU[n] 이 1:1 대응한다고 볼 수 있다. 예를 들어, pending interrupt 가 GIC CPU Interface[1] 로 전달되면, 해당 interrupt 는 CPU[1] 에게로 전달된다고 볼 수 있다. 그러나, GIC CPU Interfac e와 CPU 가 1:1 로 대응되지 않는 경우도 존재한다. 이 때는 GIC_DIST_TARGET 레지스터를 어떻게 해석해야 할까?

     

     

    " SGI & PPI 는 GIC_DIST_TARGET 이 필요없다. 왜냐면, SGI 같은 경우는 `Software Generated Interrupt Register(GICD_SGIR)` 를 통해서 컨트롤되기 때문이다. GICD_SGIR 레지스터를 보면, `CPUTargetList[23:16]` 영역에서 GIC Distributor 가 SGI 를 어떤 GIC CPU Interface 로 forwarding 할지를 명시한다.

     


    ARM Generic Interrupt Controller Architecture Specification

    ARM Generic Interrupt Controller Architecture Specification

     

     

    " PPI 같은 경우는 hardware level 에서 각 GIC CPU Interface 로 자동으로 forwarding 된다. 그렇기 때문에, 명시적으로 컨트롤할 필요성이 없다(컨트롤할 레지스터도 존재하지 않는다). 그런데, GIC_DIST_TARGET0 ~ GIC_DIST_TARGET7 레지스터들은 interrupt 0 ~ 31 번(SGI & PPI) 에 대한 target CPU 를 명시하고 있다. 위에서 설명했다시피, SGI & PPI 는 target CPU 를 컨트롤할 필요가 없다. 이 말은 GIC_ITARGETSR0 ~ GIC_ITARGETSR7 이 `read-only` 임을 나타내며, 칩을 설계하는 시점에 이미 GIC Distributor 가 어떤 GIC CPU Interface 로 forwarding 해야 할지를 이 레지스터들에 명시되어 있다는 것을 의미한다. 결국, GIC_ITARGETSR0 ~ GIC_ITARGETSR7 레지스터를 읽으면, interrupt 가 발생했을 때, GIC Distributor 가 어떤 GIC CPU Interface 로 forwarding 해야 할지를 알 수 있게 되고, 이 정보를 토대로 나머지  GIC_ITARGETSR8 ~ GIC_ITARGETSR255 를 설정한다.

     

    " 만약, CPU[0] 이 GIC CPU Interface[4] 에 연결되어 있으면, CPU[0] 에서 동작하는 program 이 GIC_ITARGETSRn 을 읽으면 cpu mask 값은 어떻게 될까? 0b0001_0000 이 된다(참고로, 현재 리눅스 커널은 IRQ distribution 을 affinity / irqbalance 에 전적으로 의존한다. 이 기술들은 CPU 아키텍처 차원에서 지원해주며, 대부분의 CPU 에서 지원한다. 그러나, GICD_ITARGETSR 를 통해 IRQ distribution 를 하는 것은 아키텍처 의존적인 기술이므로, 지양하고 있는 것 같다[참고1]).

     

     

    (3) gic_get_cpumask() 함수는 1 바이트를 반환한다. 이 값은 현재 CPU 에 연결된 GIC CPU Interface 를 나타낸다. 그렇다면, 32 ~ 1020 번 interrupts 들도 모두 동일한 GIC CPU interface 로 forwarding 해야 하므로, GIC_ITARGETSR[8] ~ GIC_ITARGETSR[255] 에 cpumask 를 복사하면 된다. 그런데, cpumask 에는 현재 하위 8-bit 만 유효하다. 이 값은 어차피 GIC_ITARGETSR[8] ~ GIC_ITARGETSR[255] 에서 모두 동일하므로, 복사를 쉽게하기 위해 32-bit 로 확장한다. 구체적인 과정은 아래와 같다.

     

     

     

    (4) 모든 SPI 들이 현재 CPU 에 연결된 GIC CPU interface 로 forwarding 되도록 설정한다.

     

     

    (5) GIC Distributor 의 나머지 레지스터들을 설정한다. 주석에서 이미 굉장히 설명이 잘 되어있기 때문에, 구체적인 설명은 생략한다. 그러나, 한 가지 주의할 점이 있다. gic_dist_config() 함수에서 설정되는 값들은 모두 default value 다. 그래서, 다른 drivers 들이 초기화되는 시점에 이 값들은 변경될 가능성이 있다(특히 trigger 방식).

    // drivers/irqchip/irq-gic-common.c - v3.17-rc7
    void __init 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(0, base + GIC_DIST_CONFIG + i / 4);
    
    	/*
    	 * Set priority on all global interrupts.
    	 */
    	for (i = 32; i < gic_irqs; i += 4)
    		writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i);
    
    	/*
    	 * Disable all interrupts.  Leave the PPI and SGIs alone
    	 * as they are enabled by redistributor registers.
    	 */
    	for (i = 32; i < gic_irqs; i += 32)
    		writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i / 8);
    
    	if (sync_access)
    		sync_access();
    }

     

     

    (6) GIC Distributor 의 모든 레지스터 설정이 완료되었으므로, interrupt 를 enable 한다.

     

     

     

    2. CPU interface initlization

    // include/linux/irqchip/arm-gic.h - v3.17-rc7
    #define GIC_CPU_CTRL			0x00
    #define GIC_CPU_PRIMASK			0x04
    ....
    // drivers/irqchip/irq-gic.c - v3.17-rc7
    /*
     * The GIC mapping of CPU interfaces does not necessarily match
     * the logical CPU numbering.  Let's use a mapping as returned
     * by the GIC itself.
     */
    #define NR_GIC_CPU_IF 8
    static u8 gic_cpu_map[NR_GIC_CPU_IF] __read_mostly;
    // drivers/irqchip/irq-gic.c - v3.17-rc7
    static void gic_cpu_init(struct gic_chip_data *gic)
    {
    	void __iomem *dist_base = gic_data_dist_base(gic); // GIC Distributor`s base address 
    	void __iomem *base = gic_data_cpu_base(gic); // GIC CPU Interface`s base address
    	unsigned int cpu_mask, cpu = smp_processor_id(); // logical CPU ID 
    	int i;
    
    	/*
    	 * Get what the GIC says our CPU mask is.
    	 */
    	BUG_ON(cpu >= NR_GIC_CPU_IF); 
    	cpu_mask = gic_get_cpumask(gic); // --- 1
    	gic_cpu_map[cpu] = cpu_mask;
    
    	/*
    	 * Clear our mask from the other map entries in case they're
    	 * still undefined.
    	 */
    	for (i = 0; i < NR_GIC_CPU_IF; i++)
    		if (i != cpu)
    			gic_cpu_map[i] &= ~cpu_mask; // --- 2
    
    	gic_cpu_config(dist_base, NULL); // --- 3
    
    	writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); // --- 4
    	writel_relaxed(1, base + GIC_CPU_CTRL); // --- 5
    }

     

     

    (1) 리눅스 커널에서 실제 사용하는 CPU ID 는 logical CPU ID 다. 이 값은 `smp_processor_id()` 함수를 통해서 얻을 수 있다(logical CPU ID 에 대한 내용은 이 글을 참고하자). 우리는 `GIC Distributor` 섹션에서 gic_get_cpumask() 함수에 대해 알아봤다. 결국 이 함수는 GIC Distributor 가 interrupt 발생시에 어떤 GIC CPU Interface 로 forwarding 해야 할지를 알려준다. `gic_cpu_map` 변수는 배열로 선언되어 있으며, 각 CPU 에 대응하는 interrupt forwarding cpumask 를 가지고 있다. 이 변수는 `gic_init_bases()` 함수에서 0xFF(모든 CPUs 로 interrupt forwarding) 로 초기화 되었다가, gic_cpu_init() 함수에서gic_get_cpumask() 를 통해 특정 cpumask 로 re-initialization 된다.  

     

     

    (2) 여기까지 오게되면, 현재 코드를 실행중 인 CPU 는 interrupt forwarding 작업은 마무리 된 것이라 볼 수 있다. 그러므로, 다른 gic_cpu_map 요소들에서 현재 interrupt forwarding 이 완료된 CPU mask 를 제거해야 한다(gic_cpu_map 의 초기화가 0xFF 로 되므로, 설정이 마무리된 CPU 는 여기서 제거해야 한다). 예를 들어, 시스템에 4 개의 CPUs 가 존재하고 CPU[3] 이 interrupt forwarding 작업이 완료되었고, 아직 다른 CPUs 들은 작업을 완료하지 못했을 경우, gic_cpu_map 은 다음과 같다.

    - gic_cpu_map[0] = 0xFF
    - gic_cpu_map[1] = 0xFF
    - gic_cpu_map[2] = 0xFF
    - gic_cpu_map[3] = 0x08

     

     

    " 다른 gic_cpu_map[0|1|2] 에서 gic_cpu_map[3] 을 제거하면 다음과 같다.

    - gic_cpu_map[0] = 0xF7
    - gic_cpu_map[1] = 0xF7
    - gic_cpu_map[2] = 0xF7
    - gic_cpu_map[3] = 0x08

     

     

    (3) GIC CPU Interface 에서 SGI & PPI 초기화를 진행한다. 주석에서 

    // drivers/irqchip/irq-gic-common.c - v3.17-rc7
    void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
    {
    	int i;
    
    	/*
    	 * Deal with the banked PPI and SGI interrupts - disable all
    	 * PPI interrupts, ensure all SGI interrupts are enabled.
    	 */
    	writel_relaxed(0xffff0000, base + GIC_DIST_ENABLE_CLEAR);
    	writel_relaxed(0x0000ffff, base + GIC_DIST_ENABLE_SET);
    
    	/*
    	 * Set priority on PPI and SGI interrupts
    	 */
    	for (i = 0; i < 32; i += 4)
    		writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
    
    	if (sync_access)
    		sync_access();
    }

     

     

    (4) GIC_CPU_PRIMASK 는 `Interrupt Priority Mask Register` 를 의미한다. 이 레지스터에 작성된 값보다 높은 prioirty 를 가진 interrupt 만이 CPU 로 forwarding 한다. gic_dist_config() 함수에서 모든 SPIs 의 priority 를 0xa0 로 설정했다. 그리고, gic_cpu_config() 함수에서 각 CPU 의 SGI & PPI 의 prioirty 를 0xa0 로 설정한다. 즉, 모든 interrupts 의 default priority 를 0xa0 로 설정한다. gic_cpu_init() 함수에서는 Interrupt Priority Mask Register 에 0xf0 를 설정한다. 참고로, 우선 순위는 값이 작을 수 록, 더 높다. 즉, 0xa0 는 0xf0 보다 우선 순위가 높다. 즉, GIC CPU Interface 에서 모든 interrupt requests 를 CPU 로 forwarding 하겠다는 뜻이다.

     

     

    (5) GICC_CTRL 레지스터는 생각보다 복잡한 레지스터다. 왜냐면, GICv1 과 GICv2 의 레지스터 내용이 다르기 때문이다. 그리고, GICv2 같은 경우는 `Security Extensions` 의 구현 여부에 따라 레지스터의 내용도 달라지고 accessible interrupt group 도 달라지기 때문에 주의가 필요하다.

     

     

     

    " 우리 시나리오에서는 Security Extensions 이 구현되어 있지 않다. 그러므로, interrupt group 0 은 enable 되고, interrupt group 1 은 disabled 된다. 그리고, group 0 interrupts 들은 exception 중에서 FIQ 가 아닌 IRQ 를 trigger 한다.

     

    " 마지막으로 GICv2 에서 Security Extensions 에 따른 2 가지 버전(with Security Extensions & without Security Extensions) 을 보여준다. hardware interrupts 가 발생하면, CPU 에게 forwarding 할 때는 IRQ or FIQ 로 translation 되어야 한다. 이 때, 사용되는 레지스터가 GICD_IGROUP 다. GICD_IGROUP 에 설정된 group 에 따라 IRQ or FIQ 로 translation 하고, 이 exceptions 들은 Security Extensions 구현 여부에 따라 non-secure(IRQ exception) 및 secure software(FIQ exception) 로 대응될 수 도 있고, secure 구분없이 단지 IRQ or FIQ 로 forwarding 될 수 도 있다.

     

    Using IRQs and FIQs to provide Non-secure and Secure interrupts Supporting IRQs and FIQs when not using the processor Security Extensions

     

Designed by Tistory.