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

 

Cascade 인터럽트 연동

인터럽트 컨트롤러 아래에 gpio 등에서 인터럽트의 멀티플렉싱이 가능한 장치들이 추가로 cascade 연결된 경우 이들의 하위 인터럽트 라인을 식별하기 위해 cascade 연동을 한다. 다음 GPIO 글에서 관련 연동 방법을 소개하기로 한다.

 

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 플래그를 설정한다.

 

Generic Core 인터럽트 핸들러

인터럽트 컨트롤러들은 각 인터럽트 라인별로 H/W 제어를 다르게 설정할 수 있다. 이들을 처리할 수 있는 generic core 인터럽트 핸들러가 각각의 형태별로 미리 준비되어 있다. 이들을 간단히 알아보자.

  • handle_percpu_irq()
    • per-cpu용 인터럽트 핸들러
    • ack -> handle_irq_event_percpu() 호출 -> eoi
  • handle_level_irq()
    • 레벨 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
    • mask_ack -> handle_irq_event() 호출 -> unmask(oneshot 스레드 완료시)
  • handle_simple_irq()
    • 별도의 ack, mask, unmask, eoi 등의 HW 처리가 전혀 필요 없는 핸들러이다.
    • handle_irq_event() 호출
  • handle_fasteoi_irq()
    • transparent 컨트롤러들을 위한 핸들러
    • mask(oneshot 일때) -> preflow 핸들러 호출 -> handle_irq_event() 호출 -> eoiunmask(oneshot 스레드 완료시)
  • handle_edge_irq()
    • 엣지 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
    • handle_level_irq()와 다른 점은 mask/unmask 처리를 하지 않는다.
    • ack -> handle_irq_event() 호출
  • handle_edge_eoi_irq()
    • 엣지 eoi 타입 트리거 설정된 경우 사용할 인터럽트 핸들러
    • powerpc 아키텍처에서만 사용하는 특수한 형태이다.
    • handle_irq_event() 호출 -> eoi
  •  handle_nested_irq()
    •  irq thread로부터의 nested irq 핸들러
    • action->thread_fn() 호출
  • handle_percpu_devid_irq()
    • per-cpu용 인터럽트 핸들러
    • ack -> action->handler() 호출 -> eoi

 

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

  • 1단계: exception vector
  • 2단계: controller 핸들러
  • 3단계: irq 라인(디스크립터)에 설정된 generic core 인터럽트 핸들러 (hierarchy 가능)
  • 4단계: 최종 consumer 디바이스에 위치한 인터럽트 핸들러(action에 등록된)
  • 5단계(option): consumer 디바이스는 별도의 dispatch 이벤트 핸들러를 사용하기도 한다.

 

핸들러 함수 -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되었고 oneshot 스레드 처리중이 아닌 경우 eoi 처리 및 unmask 처리를 한다.
    • 스레드가 완료되지 않고 동작중인 경우 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 관련 플래그들

 

참고

 

댓글 남기기

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