Interrupts -2- (irq chip)

 

IRQ Chip

  • 여러 제조사의 인터럽트 컨트롤러들은 각 인터럽트 라인의 통제 방법이 각각 다르다.
  • 리눅스 IRQ 코어 레이어에서 irq chip은 다양한 인터럽트 컨트롤러의 라인별 제어 액션을 통일하여 처리하도록 구현되었다.
    • 인터럽트 라인마다  마스킹 또는 set/clear 동작을 수행하는 서비스를 제공한다.
    • irq_chip에 있는 여러 개의 후크 포인터에 연결된 콜백 함수를 통해 처리한다.
      • irq_mask
      • irq_unmask
      • irq_set_type
    • 각 제조사의 인터럽트 컨트롤러 디바이스 드라이버에는 irq_chip의 후크에 연결될 처리 함수들을 제공해야 한다.
  • 인터럽트 라인의 마스킹 및 set/clear 처리 유형 등의 처리가 각각 다른 경우 irq_chip을 분리하여 구성할 수 있다.
  • 인터럽트 라인 각각 마다 1개의 irq_chip을 지정할 수 있다.
    • irq_set_chip()

 

 

Device Tree로 hierarchy irq domain 구성

샘플 보드인 rpi2를 대상으로 설명하였기에 소스는 rpi의 커널 v4.4 이상을 대상으로한다.

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

  • 실제 rpi2 드라이버는 hierarchy irq domain을 아직 지원하지 않고 irq chip만 부모 관계가 있다.
  • hierarchy irq domain 구현 사례
    • apic를 사용하는 x86
    • arm에서 gic, gic v2m, gic v3, nvic 등

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

 

Device Tree 기반의 인터럽트 컨트롤러 초기화

irqchip_init()

drivers/irqchip/irqchip.c

void __init irqchip_init(void)
{
        of_irq_init(__irqchip_of_table);
}

__irqchip_of_table 섹션에 있는 of_device_id 구조체들을 검사하여 매치되는 인터럽트 컨트롤러 초기화 함수를 찾아 호출한다.

  • rpi2:
    • 2 개의 인터럽트 컨트롤러 드라이버 호출
      • bcm2836_arm_irqchip_l1_intc_of_init()
      • bcm2835_armctrl_of_init()

 

of_irq_init()

drivers/of/irq.c

/**
 * 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)
                        desc->interrupt_parent = NULL;
                list_add_tail(&desc->list, &intc_desc_list);
        }

Device Tree에 있는 인터럽트 컨트롤러와 매치되는 인터럽트 컨트롤러의 초기화 함수를 호출한다. 단 2개 이상의 인터럽트 컨트롤러를 사용하는 경우 상위 인터럽트 컨트롤러부터 초기화한다.

  • 코드 라인 14~15에서 임시로 사용되는 리스트 2개를 초기화한다.
    • intc_desc_list:
      • irqchip 테이블의 이름과 Device Tree의 인터럽트 컨트롤러 드라이버명(compatible)으로 매치되어 구성된 desc 구조체 리스트
        • 보통 ARM 임베디드 시스템은 보통 1 ~ 2개의 인터럽트 컨트롤러를 사용한다.
    • intc_parent_list:
      • 초기화 성공한 인터럽트 컨트롤러의 desc 구조체 리스트로 최상위 인터럽트 컨트롤러가 가장 앞으로 구성된다.
  • 코드 라인 17~20에서 DTB 트리의 모든 노드에서 irqchip 테이블의 이름 과 Device Tree의 인터럽트 컨트롤러 드라이버명(compatible)으로 일치한 노드들에서 “interrupt-controller” 속성을 못찾았거나 “status”가 “ok”가 아닌 경우 skip 한다.
  • 코드 라인 25~27에서 desc 구조체를 할당받아 온다.
  • 코드 라인 29에서 desc->dev에 인터럽트 컨트롤러 노드를 가리키게한다.
  • 코드 라인 30~32에서 desc->interrupt_parent에 상위 인터럽트 컨트롤러 노드를 가리키게 한다. 만일 상위 인터럽트 컨트롤러가 자기 자신을 가리키면 null을 대입한다.
    • 상위 인터럽트 컨트롤러를 찾는 법
      • “interrupt-parent” 속성이 위치한 노드가 가리키는 phandle 값을 알아온 후 그 값으로 노드를 검색하여 알아온다.
  • 코드 라인 33에서 intc_desc_list 임시 리스트의 후미에 desc를 추가한다.

 

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

                        if (desc->interrupt_parent != parent)
                                continue;

                        list_del(&desc->list);
                        match = of_match_node(matches, desc->dev);
                        if (WARN(!match->data,
                            "of_irq_init: no init function for %s\n",
                            match->compatible)) {
                                kfree(desc);
                                continue;
                        }

                        pr_debug("of_irq_init: init %s @ %p, parent %p\n",
                                 match->compatible,
                                 desc->dev, desc->interrupt_parent);
                        irq_init_cb = (of_irq_init_cb_t)match->data;
                        ret = irq_init_cb(desc->dev, desc->interrupt_parent);
                        if (ret) {
                                kfree(desc);
                                continue;
                        }

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

                /* 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);
        }
}
  • 코드 라인 6에서 intc_desc_list 리스트의 모든 엔트리가 없어질 때까지 루프를 돈다.
  • 코드 라인 12에서 intc_desc_list 리스트의 desc 엔트리에 대해 루프를 돈다.
  • 코드 라인 17~18에서 남은 인터럽트 컨트롤러 중 최상위가 아닌 경우 skip 한다.
  • 코드 라인 20에서 initc_desc_list에서 하나를 제거한다.
  • 코드 라인 21~27에서 __irqchip_of_table 에서 해당 드라이버와 매치되는 of_device_id를 가져오고 매치 되지 않는 경우 “of_irq_init: no init function for %s”라는 경고 메시지를 출력하고 skip 한다.
  • 코드 라인 29~31에서 매치되는 인터럽트 컨트롤러 드라이버 이름과 주소 그리고 부모 인터럽트 컨트롤러 주소 정보를 출력한다.
    • “of_irq_init: init <driver_name> @ <addr>, parent <addr>”
  • 코드 라인 32에서 매치된 of_device_id 구조체의 data에서 irq 초기화를 위한 콜백함수를 알아온다.
  • 코드 라인 33~37에서 IRQ 초기화 함수를 호출한다.  만일 초기화가 실패하면 skip 한다.
  • 코드 라인 43에서 초기화 성공한 인터럽트 컨트롤러의 desc 구조체 정보를 intc_parent_list 라는 이름의 임시 리스트에 추가한다.
    • 부모 없는 child 인터럽트 컨트롤러가 있는지 오류 체크를 위함
  • 코드 라인 47~52에서 초기화 성공한 인터럽트 컨트롤러가 없으면 “of_irq_init: children remain, but no parents” 메시지를 출력하고 처리를 중단한다.
    • A: 조부모 IC <- B: 부모 IC, C: 아들 IC 구성 시
      • A-> B -> C 순서대로 초기화되어야 한다.
  • 코드 라인 53~55에서 임시 변수 parent에 처리한 인터럽트 컨트롤러 노드를 가리키게 하고 intc_desc_list 라는 이름의 임시 리스트에서 제거한다.
  • 코드 라인 58~66에서 임시로 사용했던 intc_parent_list 및 intc_desc_list에 등록된 desc 구조체를 모두 할당 해제한다.

 

다음 그림은 2개의 인터럽트 컨트롤러에 대해 초기화 함수가 호출되는 것을 보여준다.

 

irqchip 테이블

__irqchip_of_table

  • .init.data 섹션에서 .dtb.init.rodata 바로 뒤에 위치한다.
  • 엔트리들은 of_device_id 구조체로 구성된다.
  • __irqchip_of_table = .; *(__irqchip_of_table) *(__irqchip_of_table_end)

 

드라이버 내에서 irqchip 초기화 선언

rpi2) irqchip 초기화 함수가 등록되는 예

drivers/irqchip/irq-bcm2835.c

IRQCHIP_DECLARE(bcm2835_armctrl_ic, "brcm,bcm2835-armctrl-ic", armctrl_of_init);
  • __irqchip_of_table 섹션 위치에 of_device_id 구조체가 다음과 같이 정의 된다.
    • .compatible = “brcm,bcm2835-armctrl-ic”
    • .data = armctrl_of_init()

 

IRQCHIP_DECLARE()

drivers/irqchip/irqchip.h

/*
 * 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)

irq_chip 디바이스 드라이버를 구성할 때 사용하는 선언문으로 여기서 만들어진 of_device_id 구조체는 __irqchip_of_table 섹션에 등록된다.

 

include/linux/of.h

#define OF_DECLARE_2(table, name, compat, fn) \
                _OF_DECLARE(table, name, compat, fn, of_init_fn_2)

 

include/linux/of.h

#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  }

 

Chip 정보 설정

irq_set_chip()

kernel/irq/chip.c

/**
 *      irq_set_chip - set the irq chip for an irq
 *      @irq:   irq number
 *      @chip:  pointer to irq chip description structure
 */
int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
        unsigned long flags;
        struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);

        if (!desc)
                return -EINVAL;

        if (!chip)
                chip = &no_irq_chip;

        desc->irq_data.chip = chip;
        irq_put_desc_unlock(desc, flags);
        /*
         * For !CONFIG_SPARSE_IRQ make the irq show up in
         * allocated_irqs.
         */
        irq_mark_irq(irq);
        return 0;
}
EXPORT_SYMBOL(irq_set_chip);

irq 디스크립터에 해당 irq를 마스킹 또는 set/clear 동작을 시킬 수 있는 irq chip 정보를 설정한다.

  • 코드 라인 14~15에서 만일 인터럽트 컨트롤러 chip을 사용하지 않은 경우  no_irq_chip(name=”none”) 구조체 정보를 대신 대입한다.
  • 코드 라인 17에서 irq 디스크립터의 chip_data에 chip 정보를 기록한다.
  • 코드 라인 23에서 Sparse IRQ를 사용하지 않는 경우allocated_irqs 비트맵에 요청 irq에 해당하는 비트를 mark하여 irq가 사용됨을 표시한다.

 

irq_mark_irq()

kernel/irq/irqdesc.c

void irq_mark_irq(unsigned int irq)
{       
        mutex_lock(&sparse_irq_lock);
        bitmap_set(allocated_irqs, irq, 1);
        mutex_unlock(&sparse_irq_lock);
}

Sparse IRQ를 사용하지 않는 경우 전역 allocated_irqs 비트맵을 사용하여 irq 번호에 해당하는 비트를 설정하여 irq가 사용됨을 표시한다.

 

irq 핸들러 설정

__irq_set_handler()

kernel/irq/chip.c

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
                  const char *name)
{
        unsigned long flags;
        struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

        if (!desc)
                return;

        if (!handle) {
                handle = handle_bad_irq;
        } else {
                struct irq_data *irq_data = &desc->irq_data;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
                /*
                 * With hierarchical domains we might run into a
                 * situation where the outermost chip is not yet set
                 * up, but the inner chips are there.  Instead of
                 * bailing we install the handler, but obviously we
                 * cannot enable/startup the interrupt at this point.
                 */
                while (irq_data) {
                        if (irq_data->chip != &no_irq_chip)
                                break;
                        /*
                         * Bail out if the outer chip is not set up
                         * and the interrrupt supposed to be started
                         * right away.
                         */
                        if (WARN_ON(is_chained))
                                goto out;
                        /* Try the parent */
                        irq_data = irq_data->parent_data;
                }
#endif
                if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
                        goto out;
        }

        /* Uninstall? */
        if (handle == handle_bad_irq) {
                if (desc->irq_data.chip != &no_irq_chip)
                        mask_ack_irq(desc);
                irq_state_set_disabled(desc);
                desc->depth = 1;
        }
        desc->handle_irq = handle;
        desc->name = name;

        if (handle != handle_bad_irq && is_chained) {
                irq_settings_set_noprobe(desc);
                irq_settings_set_norequest(desc);
                irq_settings_set_nothread(desc);
                irq_startup(desc, true);
        }
out:
        irq_put_desc_busunlock(desc, flags);
}
EXPORT_SYMBOL_GPL(__irq_set_handler);

irq descriptor에 handle과 이름을 대입하고 activate 한다. activate 시 관련 콜백 함수들을 호출한다.

  • 코드 라인 6~10에서 irq 디스크립터에 buslock을 걸고 가져온다. 만일 실패하는 경우 함수를 빠져나간다.
  • 코드 라인 12~13 인수 handle이 지정되지 않은 경우 handle_bad_irq() 함수를 대입한다.
  • 코드 라인 14~39에서 커널이 irq hierarchy를 지원하는 경우 irq_data 체인을 루프를 돌며 처음 발견되는 chip 정보를 찾는다.
    • 하나의 irq가 2 개 이상의 인터럽트 컨틀롤러 칩을 거치는 경우 irq_data가 parent irq_data에 연결되어 구성된다.
  • 코드 라인 42~47에서 지정된 핸들러 함수가 없는 경우 irq 상태를 disable로 변경하고 depth=1로 설정한다. 만일 chip 정보가 지정되지 않은 경우 irq를 mask 상태로 바꾼다.
    • irq_desc->irq_data->state_use_accessors에 IRQD_IRQ_DISABLED 추가 (no handler)
    • irq_desc->irq_data->state_use_accessors에 IRQD_IRQ_MASKED 추가 (no chip)
  • 코드 라인 48~49에서 핸들러 함수와 이름을 대입한다.
  • 코드 라인 51~56에서 핸들러 함수가 지정되었고 chain 걸린 경우 irq 상태를 noprobe, norequest, nothread로 바꾸고 irq를 enable하고 activate 한다.
    • irq_desc->irq_data->state_use_accessors에 _IRQ_NOPROBE 추가
    • irq_desc->irq_data->state_use_accessors에 _IRQ_NOREQUEST 추가
    • irq_desc->irq_data->state_use_accessors에 _IRQ_NOTHREAD 추가

 

chip 및 IRQ 핸들러 설정

irq_set_chip_and_handler()

include/linux/irq.h

static inline void irq_set_chip_and_handler(unsigned int irq, struct irq_chip *chip,
                                            irq_flow_handler_t handle)
{
        irq_set_chip_and_handler_name(irq, chip, handle, NULL);
}

irq 디스크립터에 chip, handle 정보를 대입한다. 단 이름(name)에는 null을 대입한다.

 

irq_set_chip_and_handler_name()

kernel/irq/chip.c

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
                              irq_flow_handler_t handle, const char *name)
{
        irq_set_chip(irq, chip);
        __irq_set_handler(irq, handle, 0, name);
}
EXPORT_SYMBOL_GPL(irq_set_chip_and_handler_name);

irq 디스크립터에 chip 정보를 대입한다. 그리고 핸들러와 이름을 대입하다.

 

per-cpu 기반 디바이스 및 IRQ 핸들러 설정

irq_set_percpu_devid()

kernel/irq/irqdesc.c

int irq_set_percpu_devid(unsigned int irq)
{
        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;

        irq_set_percpu_devid_flags(irq);
        return 0;
}

요청 irq를 per-cpu(코어별로 라우팅되는) 처리 용도로 준비한다.

  • 코드 라인 3~6에서 요청 irq번호에 맞는 irq 디스크립터가 없는 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 8~9에서 이미 per-cpu로 설정된 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 11~14에서 cpu 비트맵을 할당하여 percpu_enabled에 대입한다. 할당이 실패한 경우 -ENOMEM 에러를 반환한다.
  • 코드 라인 16에서 irq 상태로 noautoen, per_cpu, nothread, noprobe, per_cpu_devid 플래그를 설정한다.

참고: genirq: Add support for per-cpu dev_id interrupts 

 

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 상태로 noautoen, per_cpu, nothread, noprobe, per_cpu_devid 플래그를 설정한다.

 

인터럽트 핸들러 호출

인터럽트 호출 시 단계

  • 핸들러 설정
    • 핸들러 함수를 전역 handle_arch_irq 함수 포인터에 저장
  • irq exception 시
    • arch/arm/entry-armv.S – irq_handler 매크로 함수가 전역 handle_arch_irq 함수 포인터에 저장된 함수를 호출한다.
  • 여러 형태의 핸들러 함수 들
    • handle_percpu_irq()
      • per-cpu용 인터럽트 핸들러
      • ack -> handle_irq_event_percpu() 호출 -> eoi
    • handle_level_irq()
      • 레벨 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
      • handle_irq_event() 호출 -> cond-unmask
    • handle_simple_irq()
      • handle_irq_event() 호출
    • handle_fasteoi_irq()
      • transparent 컨트롤러들을 위한 핸들러
      • oneshot일때 mask -> preflow -> handle_irq_event() 호출 -> cond-unmask eoi
    • handle_edge_irq()
      • 엣지 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
      • ack -> handle_irq_event() 호출
    • handle_edge_eoi_irq()
      • 엣지 eoi 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
      • handle_irq_event() 호출 -> eoi
    •  handle_nested_irq()
      •  irq thread로부터의 nested irq 핸들러
      • ack -> action->thread_fn() 호출 -> eoi
    • handle_percpu_devid_irq()
      • per-cpu용 인터럽트 핸들러
      • ack -> action->handler() 호출 -> eoi

 

다음 그림은 타이머 인터럽트 핸들러가 호출되는 과정을 보여준다.

 

핸들러 함수 -1-

handle_level_irq()

kernel/irq/chip.c

/**
 *      handle_level_irq - Level type irq handler
 *      @irq:   the interrupt number
 *      @desc:  the interrupt description structure for this irq
 *
 *      Level type interrupts are active as long as the hardware line has
 *      the active level. This may require to mask the interrupt and unmask
 *      it after the associated handler has acknowledged the device, so the
 *      interrupt line is back to inactive.
 */
void    
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
        raw_spin_lock(&desc->lock);
        mask_ack_irq(desc);

        if (!irq_may_run(desc))
                goto out_unlock;

        desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
        kstat_incr_irqs_this_cpu(irq, desc);

        /*
         * If its disabled or no action available
         * keep it masked and get out of here
         */ 
        if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
                desc->istate |= IRQS_PENDING;
                goto out_unlock;
        }                               
                                        
        handle_irq_event(desc);
                        
        cond_unmask_irq(desc);

out_unlock:                
        raw_spin_unlock(&desc->lock);
}               
EXPORT_SYMBOL_GPL(handle_level_irq);

레벨 타입의 트리거를 가진 인터럽트 핸들러로 인터럽트 처리 전에 mask와 ack 처리를 수행하고 인터럽트 처리 후에 조건부로 unmask를 수행한다.

  • 코드 라인 15에서 인터럽트 컨트롤러를 통해 mask와 ack 처리를 한다. irqd는 masked 상태로 설정한다.
  • 코드 라인 17~18에서 irq의 실행이 곧 준비되지 않을 것 같으면 인터럽트 처리를 포기하고 빠져나간다.
  • 코드 라인 20에서 istate에서 replay 및 waiting 플래그를 제거한다.
  • 코드 라인 21에서 해당 인터럽트 카운터를 증가시킨다.
  • 코드 라인 27~30에서 irq 디스크립터에 action이 없거나 disable 설정된 경우 istate에 pending 플래그를 추가하고 함수를 빠져나간다.
  • 코드 라인 32에서 irq 디스크립터의 istate에서 pending 플래그를 제거하고, 모든 action에 등록한 인터럽트 핸들러들을 호출하여 수행한다. 수행 중인 동안에는 irq 진행중 상태가 유지된다.
  • 코드 라인 34에서 조건에 따라 unmask 처리를 수행한다.
    • 다음의 조건에서 unmask 처리를 수행하지 않는다.
      • 이미 인터럽트가 disable된 상태
      • 이미 인터럽트가 mask된 상태
      • oneshot 인터럽트의 경우
        • mask 상태를 유지하기 위해 unmask를 수행하지 않는다.

 

핸들러 함수 -2-

handle_percpu_devid_irq()

kernel/irq/chip.c

/**
 * handle_percpu_devid_irq - Per CPU local irq handler with per cpu dev ids
 * @irq:        the interrupt number
 * @desc:       the interrupt description structure for this irq
 *
 * Per CPU interrupts on SMP machines without locking requirements. Same as
 * handle_percpu_irq() above but with the following extras:
 *
 * action->percpu_dev_id is a pointer to percpu variables which
 * contain the real device id for the cpu on which this handler is
 * called
 */
void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc) 
{
        struct irq_chip *chip = irq_desc_get_chip(desc);
        struct irqaction *action = desc->action;
        void *dev_id = raw_cpu_ptr(action->percpu_dev_id); 
        irqreturn_t res;

        kstat_incr_irqs_this_cpu(irq, desc);

        if (chip->irq_ack)
                chip->irq_ack(&desc->irq_data); 

        trace_irq_handler_entry(irq, action); 
        res = action->handler(irq, dev_id);
        trace_irq_handler_exit(irq, action, res);
        
        if (chip->irq_eoi)
                chip->irq_eoi(&desc->irq_data);
}

요청 irq의 action에 등록한 핸들러 함수를 호출 시 irq의 현재 cpu에 대한 dev_id를 인수로 전달한다. 핸들러 함수 호출 전후로 ack 및 eoi 처리를 한다.

  • 코드 라인 17에서 요청한 irq 디스크립터의 action에 등록한 per-cpu dev_id 값을 알아온다.
  • 코드 라인 20에서 현재 cpu에 대한 kstat 카운터를 증가시킨다.
  • 코드 라인 22~23에서 핸들러 함수 호출 전에 irq_chip의 irq_ack 콜백이 등록된 경우 호출한다.
    • 인터럽트 호출되기 전에 컨틀롤러에서 항상 처리해야 할 일이 있는 경우 사용한다.
  • 코드 라인 26에서 등록한 핸들러 함수를 호출할 때 irq 번호와 dev_id를 인수로 넘겨준다.
  • 코드 라인 29~30에서 핸들러 함수 호출 후에 irq_chip의 irq_eoi 콜백이 등록된 경우 호출한다.
    • 인터럽트 호출후에 컨틀롤러에서 항상 처리해야 할 일이 있는 경우 사용한다.

 

핸들러 공용 함수들

irq_may_run()

kernel/irq/chip.c

static bool irq_may_run(struct irq_desc *desc)
{
        unsigned int mask = IRQD_IRQ_INPROGRESS | IRQD_WAKEUP_ARMED;

        /*
         * If the interrupt is not in progress and is not an armed
         * wakeup interrupt, proceed.
         */
        if (!irqd_has_set(&desc->irq_data, mask))
                return true;

        /*
         * If the interrupt is an armed wakeup source, mark it pending
         * and suspended, disable it and notify the pm core about the
         * event.
         */
        if (irq_pm_check_wakeup(desc))
                return false;

        /*
         * Handle a potential concurrent poll on a different core.
         */
        return irq_check_poll(desc);
}

irq의 실행이 곧 준비될 것 같으면 기다린다. 만일 처리가 불가능하면 false를 반환하고, 인터럽트 처리가 가능한 경우 true를 반환한다.

  • 코드 라인 9~10에서 irq 처리가 진행중이거나 armed wakeup 인터럽트가 아닌 경우 true를 반환하여 인터럽트가 계속 진행되게 한다.
  • 코드 라인 17~18에서 CONFIG_PM_SLEEP 커널 옵션을 사용하는 경우 Power Management 기능을 사용할 수 있는데 suspend 하지 않은 경우 true를 반환한다.
    • armed wakeup 인터럽트인 경우 인터럽트 디스크립터의 istate 상태에 suspended 및 pending을 추가하고 깨울 때까지 대기한다.
  • 코드 라인 23에서 irq polling을 중단 설정하지 않은 경우 다른 cpu에서 irq polling을 수행하는 중이면 완료될 때까지 기다린다.

 

핸들러 최종 호출 단계들

handle_irq_event()

kernel/irq/handle.c

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
        struct irqaction *action = desc->action;
        irqreturn_t ret;

        desc->istate &= ~IRQS_PENDING;
        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        raw_spin_unlock(&desc->lock);

        ret = handle_irq_event_percpu(desc, action);

        raw_spin_lock(&desc->lock);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        return ret;
}

irq 디스크립터의 istate에서 pending 플래그를 제거하고 모든 action에 등록한 인터럽트 핸들러들을 호출하여 수행한다. 수행 중인 동안에는 irq 진행중 상태가 유지된다.

 

handle_irq_event_percpu()

kernel/irq/handle.c

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
        irqreturn_t retval = IRQ_NONE;
        unsigned int flags = 0, irq = desc->irq_data.irq;

        do {
                irqreturn_t res;

                trace_irq_handler_entry(irq, action);
                res = action->handler(irq, action->dev_id);
                trace_irq_handler_exit(irq, action, res);

                if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
                              irq, action->handler))
                        local_irq_disable();

                switch (res) {
                case IRQ_WAKE_THREAD:
                        /*
                         * Catch drivers which return WAKE_THREAD but
                         * did not set up a thread function
                         */
                        if (unlikely(!action->thread_fn)) {
                                warn_no_thread(irq, action);
                                break;
                        }

                        __irq_wake_thread(desc, action);

                        /* Fall through to add to randomness */
                case IRQ_HANDLED:
                        flags |= action->flags;
                        break;

                default:
                        break;
                }

                retval |= res;
                action = action->next;
        } while (action);

        add_interrupt_randomness(irq, flags);

        if (!noirqdebug)
                note_interrupt(irq, desc, retval);
        return retval;
}

irq 디스크립터의 모든 action에 등록한 인터럽트 핸들러들을 호출하여 수행한다.

  • 코드 라인 11에서 action에 등록한 인터럽트 핸들러들을 호출하여 수행한다.
  • 코드 라인 14~16에서 핸들러 수행 후 local irq가 disable 상태를 유지하지 못한 경우 경고 메시지를 출력하고 local irq를 disable 한다.
    • “irq N handler <function> enabled interrupts” 경고 메시지
  • 코드 라인 18~29에서 처리 결과가 IRQ_WAKE_THREAD인 경우 thread를 깨우고 계속 다음 행을 수행한다.
  • 코드 라인 23~29에서 처리 결과가 IRQ_HANDLED인 경우 flags에 action->flags를 추가하고, 그 외의 처리 결과는 skip 한다.
  • 코드 라인 32~34에서 retval에 처리 결과를 누적시키고 더 처리할 action이 있는 경우 루프를 돈다.
  • 코드 라인 44에서 시스템에서 더 확실한 난수 처리를 위해 인터럽트 발생 간격을 이용한다.

 

Chip hook 관련

IRQ startup & shutdown

irq_startup()

kernel/irq/chip.c

int irq_startup(struct irq_desc *desc, bool resend)
{
        int ret = 0;

        irq_state_clr_disabled(desc);
        desc->depth = 0;

        irq_domain_activate_irq(&desc->irq_data);
        if (desc->irq_data.chip->irq_startup) {
                ret = desc->irq_data.chip->irq_startup(&desc->irq_data);
                irq_state_clr_masked(desc);
        } else {
                irq_enable(desc);
        }
        if (resend)
                check_irq_resend(desc, desc->irq_data.irq);
        return ret;
}

요청 irq에 대해 enable 시키고 activate 처리를 수행하며 관련 콜백 함수를 호출한다.

  • 코드 라인 5~6에서 irq 상태에서 disabled를 클리어하고 depth=0으로 한다.
  • 코드 라인 8에서 요청 irq에 대해 irq_data 체인에 구성된 activate 핸들러 함수를 모두 호출한다.
  • 코드 라인 9~11에서 irq_startup 후크에 등록된 함수가 있는 경우 이를 호출하고 masked 상태를 클리어한다.
    • irq_desc->irq_data->state_use_accessors에서_IRQD_IRQ_MASKED 제거
  • 코드 라인 12~14에서 irq_startup 후크에 비어 있는 경우 irq_enable() 함수를 호출하여
  • 코드 라인 15~16에서 resend 요청이 있는 경우 pending된 irq를 다시 호출하게 tasklet을 리스케쥴하다.

 

irq_shutdown()

kernel/irq/chip.c

void irq_shutdown(struct irq_desc *desc)
{
        irq_state_set_disabled(desc);
        desc->depth = 1;
        if (desc->irq_data.chip->irq_shutdown)
                desc->irq_data.chip->irq_shutdown(&desc->irq_data);
        else if (desc->irq_data.chip->irq_disable)
                desc->irq_data.chip->irq_disable(&desc->irq_data);
        else
                desc->irq_data.chip->irq_mask(&desc->irq_data);
        irq_domain_deactivate_irq(&desc->irq_data);
        irq_state_set_masked(desc);
}

요청 irq에 대해 disable 시키고 deactivate 처리를 수행하며 관련 콜백 함수를 호출한다.

  • 코드 라인 3~4에서 irq 상태에서 disabled를 설정하고 depth=1로 한다.
  • 코드 라인 5~10에서 irq_shutdown 후크에 등록된 함수를 호출하고, 등록되지 않은 경우 irq_disable 후크에 등록된 함수를 호출한다. irq_disable 후크 마저 등록되지 않은 경우 irq_mask() 후크에 등록된 함수를 호출한다.
  • 코드 라인 11에서 최상위 irq domain까지 deactivate 후크에 등록한 콜백 함수를 호출한다.
    • irq domain의 deactivate 후크에 등록한 콜백 함수를 호출한다.
    • 재귀 호출로 parent domain에 대해서 반복한다.
  • 코드 라인 12에서 irq_startup 후크에 등록된 함수가 있는 경우 이를 호출하고 masked 상태를 클리어한다.
    • irq_desc->irq_data->state_use_accessors에_IRQD_IRQ_MASKED 설정 추가

 

IRQ Enable & Disable

irq_enable()

kernel/irq/chip.c

void irq_enable(struct irq_desc *desc)
{
        irq_state_clr_disabled(desc);
        if (desc->irq_data.chip->irq_enable)
                desc->irq_data.chip->irq_enable(&desc->irq_data);
        else
                desc->irq_data.chip->irq_unmask(&desc->irq_data);
        irq_state_clr_masked(desc);
}

요청 irq를 enable 한다.

  • 코드 라인 3에서 disabled 상태를 클리어한다.
    • irq_desc->irq_data->state_use_accessors에서IRQD_IRQ_DISABLED 제거
  • 코드 라인 4~5에서 chip에 irq_enable 후크가 등록된 경우 이를 호출한다.
  • 코드 라인 6~7에서 chip에 irq_enable 후크가 등록되지 않은 경우 irq_unmask() 콜백 함수를 호출한다.
  • 코드 라인 8에서 masked 상태를 클리어한다.

 

irq_disable()

kernel/irq/chip.c

/**
 * irq_disable - Mark interrupt disabled
 * @desc:       irq descriptor which should be disabled
 *
 * If the chip does not implement the irq_disable callback, we
 * use a lazy disable approach. That means we mark the interrupt
 * disabled, but leave the hardware unmasked. That's an
 * optimization because we avoid the hardware access for the
 * common case where no interrupt happens after we marked it
 * disabled. If an interrupt happens, then the interrupt flow
 * handler masks the line at the hardware level and marks it
 * pending.
 */
void irq_disable(struct irq_desc *desc)
{
        irq_state_set_disabled(desc);
        if (desc->irq_data.chip->irq_disable) {
                desc->irq_data.chip->irq_disable(&desc->irq_data);
                irq_state_set_masked(desc);
        }
}

요청 irq를 disable 한다.

  • 코드 라인 16에서 disabled 플래그를 설정한다.
    • irq_desc->irq_data->state_use_accessors에IRQD_IRQ_DISABLED 플래그 설정
  • 코드 라인 17~20에서 chip->irq_disable 후크에 등록한 콜백함수를 호출하고 masked 플래그를 설정한다.

 

IRQ mask & unmask

mask_irq()

kernel/irq/chip.c

void mask_irq(struct irq_desc *desc)
{
        if (desc->irq_data.chip->irq_mask) {
                desc->irq_data.chip->irq_mask(&desc->irq_data);
                irq_state_set_masked(desc);
        }
}

요청 irq를 마스크한다.

  • 코드 라인 3~6에서 chip->irq_mask 후크에 등록된 콜백 함수를 호출하고 masked 플래그를 설정한다.

 

unmask_irq()

kernel/irq/chip.c

void unmask_irq(struct irq_desc *desc)
{
        if (desc->irq_data.chip->irq_unmask) {
                desc->irq_data.chip->irq_unmask(&desc->irq_data);
                irq_state_clr_masked(desc);
        }
}

요청 irq를 언마스크한다.

  • 코드 라인 3~6에서 chip->irq_unmask 후크에 등록된 콜백 함수를 호출하고 masked 플래그를 클리어한다.

 

cond_unmask_irq()

kernel/irq/chip.c

/*
 * Called unconditionally from handle_level_irq() and only for oneshot
 * interrupts from handle_fasteoi_irq()
 */
static void cond_unmask_irq(struct irq_desc *desc)
{
        /*
         * We need to unmask in the following cases:
         * - Standard level irq (IRQF_ONESHOT is not set)
         * - Oneshot irq which did not wake the thread (caused by a
         *   spurious interrupt or a primary handler handling it
         *   completely).
         */
        if (!irqd_irq_disabled(&desc->irq_data) &&
            irqd_irq_masked(&desc->irq_data) && !desc->threads_oneshot)
                unmask_irq(desc);
}

조건에 따라 요청 irq를 언마스크한다.

  • 다음의 조건에서 unmask 처리를 수행하지 않는다.
    • 이미 인터럽트가 disable된 상태
    • 이미 인터럽트가 mask된 상태
    • oneshot 인터럽트의 경우
      • mask 상태를 유지하기 위해 unmask를 수행하지 않는다.

 

IRQ mask_ack & unmask_eoi

mask_ack_irq()

kernel/irq/chip.c

static inline void mask_ack_irq(struct irq_desc *desc)
{
        if (desc->irq_data.chip->irq_mask_ack)
                desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
        else {
                desc->irq_data.chip->irq_mask(&desc->irq_data);
                if (desc->irq_data.chip->irq_ack)
                        desc->irq_data.chip->irq_ack(&desc->irq_data);
        }
        irq_state_set_masked(desc);
}

요청 irq에 대해 mask와 ack 처리를 하고 masked 플래그를 설정한다.

  • 보통 같은 인터럽트에 대해 계속 진입되는 것을 방지가힉 위해 인터럽트 핸들러 초입에서 호출되며  마스크 처리와 응답 처리를 같이 수행하게 한다.

 

  • 코드 라인 3~4에서 인터럽트 컨트롤러의 irq_mask_ack 후크에 등록한 콜백 함수를 호출한다.
  • 코드 라인 5~8에서 등록되지 않은 경우 irq_mask 및 irq_ack 후크에 등록한 콜백 함수를 연속해서 호출한다.
  • 코드 라인 10에서 irqd를 masked 상태로 설정한다.

 

cond_unmask_eoi_irq()

kernel/irq/chip.c

static void cond_unmask_eoi_irq(struct irq_desc *desc, struct irq_chip *chip)
{
        if (!(desc->istate & IRQS_ONESHOT)) {
                chip->irq_eoi(&desc->irq_data);
                return;
        }
        /*
         * We need to unmask in the following cases:
         * - Oneshot irq which did not wake the thread (caused by a
         *   spurious interrupt or a primary handler handling it
         *   completely).
         */
        if (!irqd_irq_disabled(&desc->irq_data) &&
            irqd_irq_masked(&desc->irq_data) && !desc->threads_oneshot) {
                chip->irq_eoi(&desc->irq_data);
                unmask_irq(desc);
        } else if (!(chip->flags & IRQCHIP_EOI_THREADED)) {
                chip->irq_eoi(&desc->irq_data);
        }
}

요청 irq의 eoi 처리를 하고 조건에 따라 unmask 처리한다.

  • 코드 라인 3~6에서 인터럽트가 oneshot 설정되지 않은 경우 eoi 처리 후 함수를 빠져나간다.
  • 코드 라인 13~16에서 irq가 enable된 상태에서 mask되었고 threads oneshot이 아닌 경우 eoi 처리 및 unmask 처리를 한다.
  • 코드 라인 17~19에서 eoi 스레드 플래그가 설정되지 않은 경우 eoi 처리만 한다.

 

기타

irq_domain_activate_irq()

kernel/irq/irqdomain.c

/**
 * irq_domain_activate_irq - Call domain_ops->activate recursively to activate
 *                           interrupt
 * @irq_data:   outermost irq_data associated with interrupt
 *
 * This is the second step to call domain_ops->activate to program interrupt
 * controllers, so the interrupt could actually get delivered.
 */
void irq_domain_activate_irq(struct irq_data *irq_data)
{
        if (irq_data && irq_data->domain) {
                struct irq_domain *domain = irq_data->domain;
                        
                if (irq_data->parent_data)
                        irq_domain_activate_irq(irq_data->parent_data);
                if (domain->ops->activate)
                        domain->ops->activate(domain, irq_data);
        }
}

irq domain에 구성된 최상위까지 activate 후크에 등록한 콜백 함수를 호출한다.

  • parent_data가 상위 irq_data를 가리키는 경우 재귀 호출된다.

 

irq_domain_deactivate_irq()

kernel/irq/irqdomain.c

/**             
 * irq_domain_deactivate_irq - Call domain_ops->deactivate recursively to
 *                             deactivate interrupt 
 * @irq_data: outermost irq_data associated with interrupt
 *                                 
 * It calls domain_ops->deactivate to program interrupt controllers to disable
 * interrupt delivery.
 */
void irq_domain_deactivate_irq(struct irq_data *irq_data)
{
        if (irq_data && irq_data->domain) {
                struct irq_domain *domain = irq_data->domain;

                if (domain->ops->deactivate)
                        domain->ops->deactivate(domain, irq_data);
                if (irq_data->parent_data)
                        irq_domain_deactivate_irq(irq_data->parent_data);
        }                                
}

irq domain에 구성된 최상위까지 deactivate 후크에 등록한 콜백 함수를 호출한다.

  • parent_data가 상위 irq_data를 가리키는 경우 재귀 호출된다.

 

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 사용 시 멀티플 레지스터 로드 및 저장 시 깨질 수 있기에 복원을 위해 초기 핸들러를 백업해 둔다.

 

irq_set_chained_handler()

include/linux/irq.h

/*
 * Set a highlevel chained flow handler for a given IRQ.
 * (a chained handler is automatically enabled and set to
 *  IRQ_NOREQUEST, IRQ_NOPROBE, and IRQ_NOTHREAD) 
 */
static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
        __irq_set_handler(irq, handle, 1, NULL);
}

요청 irq에 chained 핸들러를 설정한다.

  • rpi2: 로컬 인터럽트 컨트롤러가 부모이고 hwirq=8에 chained 핸들러를 설정한다.
    • chained handler: bcm2836_chained_handle_irq()

 

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 레지스터를 읽어 값이 있는 경우 IPI 핸들러를 호출하고 그렇지 않은 경우 IRQ 핸들러를 호출한다.

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

 

자식 컨트롤러  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)

 

generic_handle_irq()

kernel/irq/irqdesc.c

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:        The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
        struct irq_desc *desc = irq_to_desc(irq);

        if (!desc)
                return -EINVAL;
        generic_handle_irq_desc(irq, desc);
        return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

요청한 irq에 해당하는 핸들러 함수를 호출한다.

 

generic_handle_irq_desc()

include/linux/irqdesc.h

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
        desc->handle_irq(irq, desc);
}

요청한 irq 디스크립터의 등록한 핸들러 함수를 호출한다.

 

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개의 인터럽트 라인을 제어할 수 있다.

 

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 인터럽트에 대한 언마스크를 수행한다.

 

IRQ 설정

각 디바이스 드라이버에서 irq를 설정할 때 사용하는 함수는 다음 그림과 같다.

 

구조체

irq_chip 구조체

include/linux/irq.h

/*
 * @name:               name for /proc/interrupts
 * @irq_startup:        start up the interrupt (defaults to ->enable if NULL)
 * @irq_shutdown:       shut down the interrupt (defaults to ->disable if NULL)
 * @irq_enable:         enable the interrupt (defaults to chip->unmask if NULL)
 * @irq_disable:        disable the interrupt
 * @irq_ack:            start of a new interrupt
 * @irq_mask:           mask an interrupt source
 * @irq_mask_ack:       ack and mask an interrupt source
 * @irq_unmask:         unmask an interrupt source
 * @irq_eoi:            end of interrupt
 * @irq_set_affinity:   set the CPU affinity on SMP machines
 * @irq_retrigger:      resend an IRQ to the CPU
 * @irq_set_type:       set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:       enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:       function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online:     configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:    un-configure an interrupt source for a secondary CPU
 * @irq_suspend:        function called from core code on suspend once per chip
 * @irq_resume:         function called from core code on resume once per chip
 * @irq_pm_shutdown:    function called from core code on shutdown once per chip
 * @irq_calc_mask:      Optional function to set irq_data.mask for special cases
 * @irq_print_chip:     optional to print special chip info in show_interrupts
 * @irq_request_resources:      optional to request resources before calling
 *                              any other callback related to this irq
 * @irq_release_resources:      optional to release resources acquired with
 *                              irq_request_resources
 * @irq_compose_msi_msg:        optional to compose message content for MSI
 * @irq_write_msi_msg:  optional to write message content for MSI
 * @flags:              chip specific flags
 */
struct irq_chip {
        const char      *name;
        unsigned int    (*irq_startup)(struct irq_data *data);
        void            (*irq_shutdown)(struct irq_data *data);
        void            (*irq_enable)(struct irq_data *data);
        void            (*irq_disable)(struct irq_data *data);

        void            (*irq_ack)(struct irq_data *data);
        void            (*irq_mask)(struct irq_data *data);
        void            (*irq_mask_ack)(struct irq_data *data);
        void            (*irq_unmask)(struct irq_data *data);
        void            (*irq_eoi)(struct irq_data *data);

        int             (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
        int             (*irq_retrigger)(struct irq_data *data);
        int             (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
        int             (*irq_set_wake)(struct irq_data *data, unsigned int on);

        void            (*irq_bus_lock)(struct irq_data *data);
        void            (*irq_bus_sync_unlock)(struct irq_data *data);

        void            (*irq_cpu_online)(struct irq_data *data);
        void            (*irq_cpu_offline)(struct irq_data *data);

        void            (*irq_suspend)(struct irq_data *data);
        void            (*irq_resume)(struct irq_data *data);
        void            (*irq_pm_shutdown)(struct irq_data *data);

        void            (*irq_calc_mask)(struct irq_data *data);

        void            (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
        int             (*irq_request_resources)(struct irq_data *data);
        void            (*irq_release_resources)(struct irq_data *data);

        void            (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
        void            (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

        unsigned long   flags;
}
  • name
    • /proc/interrupts에 출력할 이름
  • irq_startup
    • 인터럽트 startup
    • core – irq_startup() 함수에서 호출하는데 이 후크가 지정되지 않으면 core – irq_enable() 함수를 호출한다.
  • irq_shutdown
    • 인터럽트 shutdown
    • core – irq_shutdown() 함수에서 호출하는데 이 후크가 지정되지 않으면 core – irq_disable() 함수를 호출한다.
  • irq_enable
    • 인터럽트 enable
    • core – irq_enable() 함수에서 호출하는데 이 후크가 지정되지 않으면 chip->irq_unmask() 후크를 호출한다.
  • irq_disable
    • 인터럽트 disable
    • core – irq_disable() 함수에서 호출한다.
  • irq_ack
    • 인터럽트 핸들러에서 새 인터럽트가 들어온 경우 호출한다. (새 인터럽트의 시작)
  • irq_mask
    • 인터럽트 소스 마스크
    • c0re – mask_irq() 함수에서 호출한다.
  • irq_mask_ack
    • 인터럽트 핸들러에서 새 인터럽트가 들어온 경우 호출하며 irq 마스크도 같이 수행한다.
    • core – mask_ack_irq() 함수에서 호출하는데 이 후크가 지정되지 않으면 chip->irq_mask() 후크와 chip->irq_ack() 후크를 따로 하나씩 호출한다.
  • irq_unmask
    • 인터럽트 소스 언마스크
    • c0re – unmask_irq() 함수에서 호출한다.
  • irq_eoi
    • 인터럽트 핸들러 처리의 종료 시 호출한다.
  • irq_set_affinity
    • SMP 머신에서 irq 소스를 허용할 cpu affinity를 설정한다.
    • core – irq_do_set_affinity() 함수에서 호출한다.
  • irq_retrigger
    • cpu로 irq를 재 전송
  • irq_set_type
    • irq 라인 트리거 타입을 설정한다.
  • irq_set_wake
    • irq의 pm wake-on의 enable/disable 여부 설정
    • core – irq_set_irq_wake() -> set_irq_kake_real() 함수에서 호출한다.
  • irq_bus_lock
    • slow bus chip에 대한 lock으로 인터럽트를 구성하고 해제할 때 lock을 사용 한다.
      • 사용 함수
        • setup_irq()
        • free_irq()
        • request_threaded_irq()
        • setup_percpu_irq()
        • free_percpu_irq()
        • request_percpu_irq()
        • irq_finalize_oneshot()
  • irq_bus_sync_unlock
    • slow bus chip에 대한 sync와 unlock
  • irq_cpu_online
    • cpu가 online될 때 core – irq_cpu_online() 함수에서 호출한다.
  • irq_cpu_offline
    • cpu가 offline될 때 core – irq_cpu_offline() 함수에서 호출한다.
  • irq_suspend
    • PM(Power Management) 기능을 사용하는 경우 core가 suspend 되는 경우 호출되어 irq chip도같이 suspend 동작을 알려 동작을 멈추게 한다.
  • irq_resume
    • PM(Power Management) 기능을 사용하는 경우 suspend된 irq chip이 깨어나 다시 동작하게 한다.
  • irq_pm_shutdown
    • irq에 대한 전원 소비를 없애기 위해 shutdown 한다.
  • irq_calc_mask
    • irq_data.mask의 설정을 사용한 특별한 옵션 기능
  • irq_print_chip
    • show interruptes 시 추가 옵션 칩 정보를 출력
  • irq_request_resources
    • irq와 관련된 모든 콜백 함수를 호출하기 전에 리소스를 요청하기 위한 옵션
  • irq_release_resources
    • irq_request_resources에서 얻은 리소스를 해제
  • irq_compose_msi_msg
    • MSI를 위한 메시지 구성
  • irq_write_msi_msg
    • MSI를 위한 메시지 기록 옵션
  • flags
    • chip 관련 플래그들

 

참고

답글 남기기

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