init_IRQ()

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

 

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

  • 아키텍처 전용 머신 코드를 수행하여 인터럽트 컨트롤러를 초기화하고 핸들러를 구성하는 방법
    • 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 부터 지원하기 시작하였다.

 

IRQ 초기화

 

init_IRQ()

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);
        }
}

인터럽트 처리 핸들러를 위해 인터럽트 컨트롤러의 준비 및 필요한 설정 수행한다. 시스템 구성에 따라 머신 디스크립터 또는 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 캐시에 대한 콜백함수를 준비하고 초기화 함수를 호출한다.

 

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()

 

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

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용 인터럽트 컨트롤러를 초기화하고 각 인터럽트에 대한 핸들러들을 준비한다.

  • 코드 라인 13에서 인터럽트 컨트롤러에 할당된 irq 수 만큼 루프를 돈다.
    • rpi: SPARE_ALLOC_IRQS=394
    • rpi2: SPARE_ALLOC_IRQS=480
  • 코드 라인 15~16에서 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개가 등록되어 있다.
  • 코드 라인 17~20에서 irq 번호가 IRQ_ARM_LOCAL_CNTPSIRQ(96) ~ IRQ_ARM_LOCAL_TIMER(107)번 사이의 local 인터럽트인 경우 core별로 전용 사용되므로 per-cpu 디바이스를 취급하는 핸들러를 설정한다.
  • 코드 라인 21~24에서 그 외의 irq 번호인 경우 일반 핸들러를 설정한다.
  • 코드 라인 25에서 hwirq 번호로 chip_data를 구성한다.
  • 코드 라인 28에서 PM(Power Management)를 위해 armctrl_info 구조체에 인터럽트 정보를 대입해둔다.
  • 코드 라인 29에서 FIQ 인터럽트 핸들러를 준비한다.
  • 코드 라인 30에서 Device Tree 스크립트를 통해 해당 인터럽트 컨트롤러 정보를 읽어 irq_domain을 구성한다.

 

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

 

Device Tree 방식으로 인터럽트 초기화

아래 irq chip에 대한 글에서 rpi2를 대상으로 설명한다. 다만 커널 v4.4이상을 요구한다.

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.