Interrupts -3- (irq domain)

 

IRQ Domain

각 제조사의 인터럽트 컨트롤러에 고정 매핑 또는 프로그래머블 매핑 기능이 있는 경우가 많이 있다. 그리고 리눅스 인터럽트에 대해 매핑되기 전 실제 하드웨어 인터럽트 번호를 알아내기 위해 리버스 매핑(리눅스 irq -> hw irq) 기능을 필요하게 되었고 이를 구현하기 위해 다음과 같은 방법들을 고려하여 몇 가지의 구현 방법을 준비하였다.

  • 리버스 맵 할당 방법
    • Linear(연속된) 고정 할당
    • Sparse Dynamic 할당
  • 매핑
    • 매핑 변경 가능
    • 매핑 변경 불가능
    • 매핑 불가능

 

매핑 구현 방법

다음과 같은 여러 가지 구현 모델에 대해 “irq_domain_add_”로 시작하는 함수를 제공한다. irq domain 들은 전역

  • Linear
    • irq와 hwirq의 매핑이 자유롭게 동작해야 하는 시스템에서 사용한다.
    • 리니어 리버스 매핑 테이블을 구성하여 사용한다. (hwirq -> irq)
      • hwirq 번호가 수백번 이하인 경우 컴파일 타임에 static 배열을 사용하므로 구현이 간단하다.
    • linear 방식의 irq domain이 추가된 후 부터 dynamic 하게 매핑 함수를 통해 만들고 매핑할 수 있다.
      • irq_create_mapping() 함수 등..
    • 구현
      • irq_domain_add_linear() 함수
  • Tree
    • linear 방식과 동일하게 irq와 hwirq의 매핑이 자유롭게 동작해야 하는 시스템에서 사용한다.
    • 리버스 매핑 테이블을 Radix Tree에 구성하여 할당 관리한다. (hwirq -> irq)
      • hwirq 번호가 매우 큰 경우 dynamic 하게 필요한 hwirq에 대해서만 할당 구성하여 메모리를 낭비하지 않게 할 때 사용한다.
      • arm64의 default 구성이다.
    • tree 방식의 irq domain이 추가된 후 부터 dynamic 하게 매핑 함수를 통해 를 만들고 매핑할 수 있다.
      • irq_create_mapping() 함수 등..
    • 구현
      • irq_domain_add_tree() 함수
  • Nomap
    • irq와 hwirq가 항상 동일하여 매핑을 전혀 필요하지 않는 시스템에서 사용하다.
      • 리버스 매핑 테이블도 만들지 않는다.
    • irq domain을 추가하기 전에 irq 디스크립터들이 미리 구성되어 있어야 한다.
    •  구현
      • irq_domain_add_nomap() 함수
  • Legacy
    • irq와 hwirq의 매핑을 고정한 채로 동작해야 하는 시스템에서 사용한다.
    • 리니어 리버스 매핑 테이블 역시 구성하여 사용하되 중간에 변경될 수 없다. (hwirq -> irq)
    • legacy 방식으로 irq domain을 추가하기 전에 irq 디스크립터들이 미리 할당되어 있어야 한다.
      • rpi2: early_irq_init() 함수에서 irq 디스크립터들이 처음 할당된다.
    • 구현
      • irq_domain_add_legacy() 함수
  • Legacy ISA
    • Legacy와 동일하나 irq 수가 16개로 제한된다.
    • 구현
      • irq_domain_add_legacy_isa()  함수

 

다음 그림은 3개의 인터럽트 컨트롤로에 대응해 nomap, linear, tree 등 3가지 방법을 섞어서 irq_domain을 구성한 예를 보여준다.

 

IRQ Domain hierarchy

2 개 이상의 인터럽트 컨틀롤러가 PM(파워 블럭 관리) 통제를 받는 경우 상위 인터럽트 컨트롤러에 파워가 공급되지 않는 경우 하위 인터럽트 컨트롤러도 인지하여 대응하는 구조가 필요하다. 이러한 경우를 위해 irq domain 하이라키 구조가 도입되었고 구현 시 기존 irq domain 에 parent 필드를 추가하여 사용할 수 있도록 수정하였다.

다음 그림은 인터럽트 컨트롤러가 2개 이상 중첩되는 경우와 한 개의 컨트롤러만 사용하는 경우를 비교하였다.

  • gic가 parent 인터럽트 컨트롤러이며 먼저 초기화된다.

 

IRQ Domain  추가 함수들

irq_domain_add_linear()

kernel/irq/irqdomain.c

/**
 * irq_domain_add_linear() - Allocate and register a linear revmap irq_domain.
 * @of_node: pointer to interrupt controller's device tree node.
 * @size: Number of interrupts in the domain.
 * @ops: map/unmap domain callbacks
 * @host_data: Controller private data pointer
 */
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
                                         unsigned int size,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
        return __irq_domain_add(of_node, size, size, 0, ops, host_data);
}

size 만큼의 선형 리버스 매핑 블럭을 사용할 계획으로 도메인을 할당받아 구성한 후 추가한다.

  • 연속된 리버스 매핑 공간에서 자유로운 인터럽트 매핑 방식이 필요한 인터럽트 컨트롤러 드라이버에 사용된다.
    • 예) 약 30개 드라이버에서 사용한다.
      • arm,gic
      • arm,nvic
      • bcm,bcm2835
      • bcm,bcm2836
      • exynos-combiner
      • samsung,s3c24xx
      • armada-370-xp

 

__irq_domain_add()

kernel/irq/irqdomain.c

/**
 * __irq_domain_add() - Allocate a new irq_domain data structure
 * @of_node: optional device-tree node of the interrupt controller
 * @size: Size of linear map; 0 for radix mapping only
 * @hwirq_max: Maximum number of interrupts supported by controller
 * @direct_max: Maximum value of direct maps; Use ~0 for no limit; 0 for no
 *              direct mapping
 * @ops: domain callbacks
 * @host_data: Controller private data pointer
 *
 * Allocates and initialize and irq_domain structure.
 * Returns pointer to IRQ domain, or NULL on failure.
 */
struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
                                    irq_hw_number_t hwirq_max, int direct_max,
                                    const struct irq_domain_ops *ops,
                                    void *host_data)
{
        struct irq_domain *domain;

        domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                              GFP_KERNEL, of_node_to_nid(of_node));
        if (WARN_ON(!domain))
                return NULL;

        /* Fill structure */
        INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
        domain->ops = ops;
        domain->host_data = host_data;
        domain->of_node = of_node_get(of_node);
        domain->hwirq_max = hwirq_max;
        domain->revmap_size = size;
        domain->revmap_direct_max_irq = direct_max;
        irq_domain_check_hierarchy(domain);

        mutex_lock(&irq_domain_mutex);
        list_add(&domain->link, &irq_domain_list);
        mutex_unlock(&irq_domain_mutex);

        pr_debug("Added domain %s\n", domain->name);
        return domain;
}
EXPORT_SYMBOL_GPL(__irq_domain_add);

하나의 irq 도메인을 추가하고 전달된 인수에 대해 다음과 같이 준비한다.

  • 인수 size가 주어지는 경우 size 만큼의 리니어 리버스 맵을 준비한다.
  • 인수 hwirq_max는 현재 도메인에서 최대 처리할 하드웨어 인터럽트 수이다.
  • 인수 direct_max가 주어지는 경우 해당 범위는 리버스 맵을  사용하지 않는다.
  • 인수 ops는 리버스 매핑과 변환 함수에 대한 콜백 후크를 제공한다.
  • 인수 host_data는 private 데이터 포인터를 전달할 수 있다.

 

  • 코드 라인 21~24에서 irq domain과 리버스 맵 배열을 준비하고 할당이 실패하는 경우 에러 값으로 null을 반환한다.
  • 코드 라인 27에서 tree 형태로 domain을 사용할 수 있도록 radix tree를 초기화한다.
  • 코드 라인 28~33에서 전달받은 인수를 domain에 설정한다.
    • domain->of_node는 인수로 전달받은 디바이스 노드와 연결된다.
  • 코드 라인 34에서 CONFIG_IRQ_DOMAIN_HIERARCHY 커널 옵션을 사용하여 domain이 hierarchy를 지원하는 경우 현재 domain에 HIERARCHY 플래그를 추가한다.
  • 코드 라인 36~38에서 전역 irq_domain_list의 선두에 현재 domain을 추가한다.

 

irq_domain_add_nomap()

kernel/irq/irqdomain.c

static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
                                         unsigned int max_irq,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
        return __irq_domain_add(of_node, 0, max_irq, max_irq, ops, host_data);
}

nomape 만큼의 선형 리버스 매핑 블럭을 사용할 계획으로 도메인을 할당받아 구성한 후 추가한다.

인터럽트 매핑이 불가능한 인터럽트 컨트롤러에 필요하다. 따라서 연속된 리버스 매핑 공간이 필요치 않다.

  • 대부분의 아키텍처에서 사용하지 않는 방식이다. 현재 4 개의 powerpc 아키텍처용 드라이버에서 사용한다.

 

irq_domain_add_legacy()

kernel/irq/irqdomain.c

/**
 * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain.
 * @of_node: pointer to interrupt controller's device tree node.
 * @size: total number of irqs in legacy mapping
 * @first_irq: first number of irq block assigned to the domain
 * @first_hwirq: first hwirq number to use for the translation. Should normally
 *               be '0', but a positive integer can be used if the effective
 *               hwirqs numbering does not begin at zero.
 * @ops: map/unmap domain callbacks
 * @host_data: Controller private data pointer
 *
 * Note: the map() callback will be called before this function returns
 * for all legacy interrupts except 0 (which is always the invalid irq for
 * a legacy controller).
 */  
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
                                         unsigned int size,
                                         unsigned int first_irq,
                                         irq_hw_number_t first_hwirq,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
        struct irq_domain *domain;

        domain = __irq_domain_add(of_node, first_hwirq + size,
                                  first_hwirq + size, 0, ops, host_data);
        if (domain)
                irq_domain_associate_many(domain, first_irq, first_hwirq, size);

        return domain;
}
EXPORT_SYMBOL_GPL(irq_domain_add_legacy);

size 만큼의 이미 만들어놓은 선형 리버스 매핑 블럭을 사용할 계획으로 도메인을 할당받아 구성한다. 매핑 후 변경이 필요 없는 구성에서 사용한다.

  • 미리 구성해 놓은 연속된 리버스 매핑 공간을 사용한 인터럽트 매핑 방식이 필요한 인터럽트 컨트롤러 드라이버에 사용된다.
    • 예) 약 10 여개 드라이버에서 사용한다.
      • arm,gic
      • mmp
      • samsung,s3c24xx
      • hip04

 

 

irq_domain_add_legacy_isa()

kernel/irq/irqdomain.c

static inline struct irq_domain *irq_domain_add_legacy_isa(
                                struct device_node *of_node,
                                const struct irq_domain_ops *ops,
                                void *host_data)
{
        return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops,
                                     host_data);
}
  • 고정된 16(legacy ISA 하드웨어가 사용하는 최대 인터럽트 수)개 인터럽트 수 만큼의 이미 만들어놓은 선형 리버스 매핑 블럭을 사용할 계획으로 도메인을 할당받아 구성한다. 매핑 후 변경이 필요 없는 구성에서 사용한다.
    • 대부분의 아키텍처에서 사용하지 않는 방식이다. 현재 2 개의 powerpc 아키텍처용 드라이버에서 사용한다.

 

irq_domain_add_tree()

kernel/irq/irqdomain.c

static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
        return __irq_domain_add(of_node, 0, ~0, 0, ops, host_data);
}

Radix tree로 관리되는 리버스 매핑 블럭을 사용할 계획으로 도메인을 할당받아 구성한다. 구성된 후 자유롭게 매핑/해제를 할 수 있다.

  • 하드웨어 인터럽트 번호가 매우 큰 시스템에서 메모리 낭비 없이 자유로운 인터럽트 매핑 방식이 필요한 인터럽트 컨트롤러 드라이버에 사용된다.
    • 예) arm에서는 최근 버전의 gic 컨트롤러 드라이버 3개가 사용하고, 다른 몇 개의 아키텍처에서도 사용한다.
      • arm,gic-v2m
      • arm,gic-v3
      • arm,gic-v3-its

 

irq_domain_add_hierarchy()

kernel/irq/irqdomain.c

/**
 * irq_domain_add_hierarchy - Add a irqdomain into the hierarchy
 * @parent:     Parent irq domain to associate with the new domain
 * @flags:      Irq domain flags associated to the domain
 * @size:       Size of the domain. See below
 * @node:       Optional device-tree node of the interrupt controller
 * @ops:        Pointer to the interrupt domain callbacks
 * @host_data:  Controller private data pointer
 *
 * If @size is 0 a tree domain is created, otherwise a linear domain.
 *
 * If successful the parent is associated to the new domain and the
 * domain flags are set.
 * Returns pointer to IRQ domain, or NULL on failure.
 */
struct irq_domain *irq_domain_add_hierarchy(struct irq_domain *parent,
                                            unsigned int flags,
                                            unsigned int size,
                                            struct device_node *node,
                                            const struct irq_domain_ops *ops,
                                            void *host_data)
{
        struct irq_domain *domain;

        if (size)
                domain = irq_domain_add_linear(node, size, ops, host_data);
        else
                domain = irq_domain_add_tree(node, ops, host_data);
        if (domain) {
                domain->parent = parent;
                domain->flags |= flags;
        }

        return domain;
}

주어진 인수 size에 따라 선형 또는 Radix tree로 관리되는 리버스 매핑 블럭을 사용할 계획으로 하이라키 도메인을 구성 시 사용한다. 구성된 후 자유롭게 매핑/해제를 할 수 있다.

  • domain 간 부모관계를 설정하고자 할 때 사용하며, 현재 몇 개의 arm 및 다른 아키텍처 드라이버에서 사용한다.
      • tegra
      • imx-gpcv2
      • mtk-sysirq
      • vf610-mscm-ir
      • crossbar

 

irq_domain_add_simple()

kernel/irq/irqdomain.c

/**
 * irq_domain_add_simple() - Register an irq_domain and optionally map a range of irqs
 * @of_node: pointer to interrupt controller's device tree node.
 * @size: total number of irqs in mapping
 * @first_irq: first number of irq block assigned to the domain,
 *      pass zero to assign irqs on-the-fly. If first_irq is non-zero, then
 *      pre-map all of the irqs in the domain to virqs starting at first_irq.
 * @ops: domain callbacks
 * @host_data: Controller private data pointer
 *
 * Allocates an irq_domain, and optionally if first_irq is positive then also
 * allocate irq_descs and map all of the hwirqs to virqs starting at first_irq.
 *
 * This is intended to implement the expected behaviour for most
 * interrupt controllers. If device tree is used, then first_irq will be 0 and
 * irqs get mapped dynamically on the fly. However, if the controller requires
 * static virq assignments (non-DT boot) then it will set that up correctly.
 */
struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
                                         unsigned int size,
                                         unsigned int first_irq,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
        struct irq_domain *domain;

        domain = __irq_domain_add(of_node, size, size, 0, ops, host_data);
        if (!domain)
                return NULL;

        if (first_irq > 0) {
                if (IS_ENABLED(CONFIG_SPARSE_IRQ)) {
                        /* attempt to allocated irq_descs */
                        int rc = irq_alloc_descs(first_irq, first_irq, size,
                                                 of_node_to_nid(of_node));
                        if (rc < 0)
                                pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
                                        first_irq);
                }
                irq_domain_associate_many(domain, first_irq, 0, size);
        }

        return domain;
}
EXPORT_SYMBOL_GPL(irq_domain_add_simple);

 

first_irq 번호 부터 0번 hwirq에 1:1로 size 만큼의 선형 리버스 매핑 블럭을 할당하고 도메인을 구성한다. 단 first_irq가 0인 경우 매핑 없이 domain만 추가한다.

  • offset 만큼의 irq 번호와 hwirq를 매핑하면 되는 간단한 시스템 드라이버에서 사용하며 약 10여개 드라이버에서 사용한다.
      • arm,vic
      • versatile-fpga
      • sa11x0

 

IRQ Domain  제거 함수

irq_domain_remove()

kernel/irq/irqdomain.c

/**
 * irq_domain_remove() - Remove an irq domain.
 * @domain: domain to remove
 *
 * This routine is used to remove an irq domain. The caller must ensure
 * that all mappings within the domain have been disposed of prior to
 * use, depending on the revmap type.
 */
void irq_domain_remove(struct irq_domain *domain)
{
        mutex_lock(&irq_domain_mutex);

        /*
         * radix_tree_delete() takes care of destroying the root
         * node when all entries are removed. Shout if there are
         * any mappings left.
         */
        WARN_ON(domain->revmap_tree.height);

        list_del(&domain->link);

        /*
         * If the going away domain is the default one, reset it.
         */
        if (unlikely(irq_default_domain == domain))
                irq_set_default_host(NULL);

        mutex_unlock(&irq_domain_mutex);

        pr_debug("Removed domain %s\n", domain->name);

        of_node_put(domain->of_node);
        kfree(domain);
}
EXPORT_SYMBOL_GPL(irq_domain_remove);

전역 irq_domain_list에서 요청한 irq domain을 제거하고 할당 해제한다.

  • 코드 라인 20에서 전역 irq_domain_list에서 요청한 irq domain을 제거한다.
  • 코드 라인 25~26에서 낮은 확률로 요청 domain이 irq_default_domain인 경우 irq_default_domain을 null로 설정한다.
    • arm이 아닌 하드 코딩된 몇 개의 아키텍처 드라이버에서 irq domain을 구현하지 않고 인터럽트 번호를 사용할 목적으로 irq_create_mapping() 함수에서 domain 대신 NULL을 인자로 사용한다.
  • 코드 라인 32에서 디바이스 노드의 참조를 해제하고 domain을 할당 해제한다.

 

리눅스 irq와 hw irq 매핑 생성

dynamic 매핑 방법을 사용하는 linear, tree 및 hierarchy 방식의 irq domain에서 사용한다.

irq_create_mapping()

kernel/irq/irqdomain.c

/**
 * irq_create_mapping() - Map a hardware interrupt into linux irq space
 * @domain: domain owning this hardware interrupt or NULL for default domain
 * @hwirq: hardware irq number in that domain space
 *
 * Only one mapping per hardware interrupt is permitted. Returns a linux
 * irq number.
 * If the sense/trigger is to be specified, set_irq_type() should be called
 * on the number returned from that call.
 */
unsigned int irq_create_mapping(struct irq_domain *domain,
                                irq_hw_number_t hwirq)
{
        int virq;

        pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq);

        /* Look for default domain if nececssary */
        if (domain == NULL)
                domain = irq_default_domain;
        if (domain == NULL) {
                WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
                return 0;
        }
        pr_debug("-> using domain @%p\n", domain);

        /* Check if mapping already exists */
        virq = irq_find_mapping(domain, hwirq);
        if (virq) {
                pr_debug("-> existing mapping on virq %d\n", virq);
                return virq;
        }
vim 
        /* Allocate a virtual interrupt number */
        virq = irq_domain_alloc_descs(-1, 1, hwirq,
                                      of_node_to_nid(domain->of_node));
        if (virq <= 0) {
                pr_debug("-> virq allocation failed\n");
                return 0;
        }

        if (irq_domain_associate(domain, virq, hwirq)) {
                irq_free_desc(virq);
                return 0;
        }

        pr_debug("irq %lu on domain %s mapped to virtual irq %u\n",
                hwirq, of_node_full_name(domain->of_node), virq);

        return virq;
}
EXPORT_SYMBOL_GPL(irq_create_mapping);

요청한 hwirq로 리눅스 irq를 배정 받아 매핑하고 그 번호를 반환한다.  hwirq를 음수로 지정하는 경우 리눅스 irq를 배정한다.

  • 코드 라인19~20에서 domain이 null로 지정된 경우 default domain을 선택한다.
  • 코드 라인 21~24에서 domain이 여전히 null인 경우 경고 메시지 출력과 함께 0을 반환한다.
  • 코드 라인 28~32에서 domain에서 hwirq 에 매핑된 값이 있는지 알아온다. 만일 이미 매핑이 존재하는 경우 경고 메시지 출력과 함께 0을 반환한다.
  • 코드 라인 35~40에서 domain에서 hwirq에 해당하는 리버스 매핑을 할당한다. 만일 할당하지 못한 경우 0을 반환한다.
  • 코드 라인 42~45에서 domain에서 virq와 hwirq의 매핑을 수행한다. 매핑 실패 시 0을 반환한다.

 

irq_create_direct_mapping()

kernel/irq/irqdomain.c

/**
 * irq_create_direct_mapping() - Allocate an irq for direct mapping
 * @domain: domain to allocate the irq for or NULL for default domain
 *
 * This routine is used for irq controllers which can choose the hardware
 * interrupt numbers they generate. In such a case it's simplest to use
 * the linux irq as the hardware interrupt number. It still uses the linear
 * or radix tree to store the mapping, but the irq controller can optimize
 * the revmap path by using the hwirq directly.
 */
unsigned int irq_create_direct_mapping(struct irq_domain *domain)
{
        unsigned int virq;

        if (domain == NULL)
                domain = irq_default_domain;

        virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node));
        if (!virq) {
                pr_debug("create_direct virq allocation failed\n");
                return 0;
        }
        if (virq >= domain->revmap_direct_max_irq) {
                pr_err("ERROR: no free irqs available below %i maximum\n",
                        domain->revmap_direct_max_irq);
                irq_free_desc(virq);
                return 0;
        }
        pr_debug("create_direct obtained virq %d\n", virq);

        if (irq_domain_associate(domain, virq, virq)) {
                irq_free_desc(virq);
                return 0;
        }

        return virq;
}
EXPORT_SYMBOL_GPL(irq_create_direct_mapping);

irq 디스크립터를 하나 할당받은 후 1:1 직접 매핑 방법으로 hwirq 번호와 연결한다.

  • 코드 라인 15~16에서 domain이 주어지지 않은 경우 default domain을 선택한다.
  • 코드 라인 18~22에서 1개의 irq 디스크립터를 할당받아온다. 만일 할당이 실패한 경우 0을 반환한다.
  • 코드 라인 23~28에서 할당 받은 irq가 no 매핑 방법으로 사용할 수 있는 범위를 초과하는 경우에러 메시지를 출력하고 할당받은 irq 디스크립터를 다시 할당 해제한 후 0을 반환한다.
  • 코드 라인 31~34에서 할당받은 irq 번호로 hwirq 번호를 연결한다. 그런 후 Radix tree로 관리하는 리버스 맵에 hwirq와 irq가 동일하게 추가한다.

 

irq_create_strict_mappings()

kernel/irq/irqdomain.c

/**
 * irq_create_strict_mappings() - Map a range of hw irqs to fixed linux irqs
 * @domain: domain owning the interrupt range
 * @irq_base: beginning of linux IRQ range
 * @hwirq_base: beginning of hardware IRQ range
 * @count: Number of interrupts to map
 *
 * This routine is used for allocating and mapping a range of hardware
 * irqs to linux irqs where the linux irq numbers are at pre-defined
 * locations. For use by controllers that already have static mappings
 * to insert in to the domain.
 *
 * Non-linear users can use irq_create_identity_mapping() for IRQ-at-a-time
 * domain insertion.
 *
 * 0 is returned upon success, while any failure to establish a static
 * mapping is treated as an error.
 */
int irq_create_strict_mappings(struct irq_domain *domain, unsigned int irq_base,
                               irq_hw_number_t hwirq_base, int count)
{
        int ret;

        ret = irq_alloc_descs(irq_base, irq_base, count,
                              of_node_to_nid(domain->of_node));
        if (unlikely(ret < 0))
                return ret;

        irq_domain_associate_many(domain, irq_base, hwirq_base, count);
        return 0;
}
EXPORT_SYMBOL_GPL(irq_create_strict_mappings);

irq_base 번호부터 count 수 만큼 irq 디스크립터를 할당받은 후 hwirq_base 부터 count 수 만큼 매핑 한다.

  • 코드 라인 24~27에서 irq_base 번호부터 count 만큼의 irq 디스크립터를 할당받아온다. 만일 할당이 실패하는 경우 함수를 빠져나가낟.
  • 코드 라인 29에서 irq_base 번호와 hwirq_base 번호를 count 수 만큼 매핑한다.

 

irq_create_of_mapping()

kernel/irq/irqdomain.c

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
        struct irq_domain *domain;
        irq_hw_number_t hwirq;
        unsigned int type = IRQ_TYPE_NONE;
        int virq;

        domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;
        if (!domain) {
                pr_warn("no irq domain found for %s !\n",
                        of_node_full_name(irq_data->np));
                return 0;
        }

        /* If domain has no translation, then we assume interrupt line */
        if (domain->ops->xlate == NULL)
                hwirq = irq_data->args[0];
        else {
                if (domain->ops->xlate(domain, irq_data->np, irq_data->args,
                                        irq_data->args_count, &hwirq, &type))
                        return 0;
        }

        if (irq_domain_is_hierarchy(domain)) {
                /*
                 * If we've already configured this interrupt,
                 * don't do it again, or hell will break loose.
                 */
                virq = irq_find_mapping(domain, hwirq);
                if (virq)
                        return virq;

                virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);
                if (virq <= 0)
                        return 0;
        } else {
                /* Create mapping */
                virq = irq_create_mapping(domain, hwirq);
                if (!virq)
                        return virq;
        }

        /* Set type if specified and different than the current one */
        if (type != IRQ_TYPE_NONE &&
            type != irq_get_trigger_type(virq))
                irq_set_irq_type(virq, type);
        return virq;
}
EXPORT_SYMBOL_GPL(irq_create_of_mapping);

Device Tree 스크립트에서 인터럽트 컨르롤러와 연결되는 디바이스에 대해 매핑을 수행한다.

  • 코드 라인 8에서 phandle이 가리키는 디바이스 노드로 irq domain을 알아온다. 없는 경우 default  domain을 사용한다.
  • 코드 라인 9~13에서 domain이 없는 경우 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 16~17에서 도메인에 등록한 xlate 후크 함수가 없는 경우 첫 번째 인자값을 hwirq로 사용한다.
  • 코드 라인 18~22에서 도메인에 등록한 xlate 후크에 등록한 함수를 호출하여 hwirq 값과 타입을 구해온다. 만일 실패하는 경우 함수를 빠져나간다.
    • Device Tree의 인터럽트를 사용하는 각 장치의 “interrupts = { a1, a2 }”에서 인수 2 개로 hwirq 값을 알아온다.
  • 코드 라인 24~31에서 hierarch를 지원하는 irq domain인 경우이미 hwirq 값으로 매핑된 리눅스 irq 값이 있는 경우 해당 irq 값을 반환한다.
  • 코드 라인 33~35에서 매핑을 못찾은 경우 하나의 irq 디스크립터를 할당받아온다. 만일 할당이 실패하는 경우 0을 반환한다.
  • 코드 라인 36~41에서 디스크립터를 하나 할당받은 후 hwirq에 매핑한 후 할당한 irq 번호를 알아온다. 만일 매핑 생성이 실패하는 경우 0을 반환한다.
  • 코드 라인 44~47에서 해당 irq 번호에 대한 트리거 타입이 지정된 경우 인터럽트 컨트롤러 드라이버(chip)의 후크 함수를 통해 트리거 타입을 설정한다.
    • 해당 인터럽트 컨트롤러는 트리거 컨트롤러 레지스터를 설정하여 해당 인터럽트 라인의 트리거 타입을 설정한다.

 

예) irq2의 uart 디바이스

                uart0: uart@7e201000 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201000 0x1000>;
                        interrupts = <2 25>;
  • uart의 인터럽트의 인자는 2개로 <2 25> -> bank #2, bit 25 인터럽트
  • hwirq로 변환 -> 57
  • 할당한 irq 디스크립터의 irq=83이라고 가정하면 이 irq(83)과 hwirq(57)을 매핑한다.

 

트리거 타입

플래그에서 4 bit를 트리거 타입에 사용한다.

  • IRQ_TYPE_NONE = 0x00000000,
    • 지정되지 않은 트리거 타입
  • IRQ_TYPE_EDGE_RISING = 0x00000001,
    • 상승 시 트리거
  • IRQ_TYPE_EDGE_FALLING = 0x00000002,
    • 하강 시 트리거
  • IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
    • 상승 및 하강 시 둘다 트리거
  • IRQ_TYPE_LEVEL_HIGH = 0x00000004,
    • 레벨이 high에 있는 상태에서 트리거
  • IRQ_TYPE_LEVEL_LOW = 0x00000008,
    • 레벨이 low에 있는 상태에서 트리거

 

irq_find_host()

kernel/irq/irqdomain.c

/**
 * irq_find_host() - Locates a domain for a given device node
 * @node: device-tree node of the interrupt controller
 */
struct irq_domain *irq_find_host(struct device_node *node)
{
        struct irq_domain *h, *found = NULL;
        int rc;

        /* We might want to match the legacy controller last since
         * it might potentially be set to match all interrupts in
         * the absence of a device node. This isn't a problem so far
         * yet though...
         */
        mutex_lock(&irq_domain_mutex);
        list_for_each_entry(h, &irq_domain_list, link) {
                if (h->ops->match)
                        rc = h->ops->match(h, node);
                else
                        rc = (h->of_node != NULL) && (h->of_node == node);

                if (rc) {
                        found = h;
                        break;
                }
        }
        mutex_unlock(&irq_domain_mutex);
        return found;
}
EXPORT_SYMBOL_GPL(irq_find_host);

주어진 디바이스 노드에 해당하는 irq domain을 찾아온다.

  • 코드 라인 16에서 irq_domain_list에 등록한 모든 엔트리에 대해 루프를 돈다.
  • 코드 라인 17~18에서 irq_domain의 ops->match 후크에 등록된 함수가 있으면 해당 irq_domain을 반환한다.
    • 실제 match 후크 함수를 사용한 사례를 못 찾았다.
  • 코드 라인 19~20에서 irq_domain에 등록한 디바이스 노드와 동일한 경우 해당 irq_domain을 반환한다.

 

리버스 매핑 검색 (hwirq -> irq)

irq_find_mappings()

kernel/irq/irqdomain.c

/**
 * irq_find_mapping() - Find a linux irq from an hw irq number.
 * @domain: domain owning this hardware interrupt
 * @hwirq: hardware irq number in that domain space
 */
unsigned int irq_find_mapping(struct irq_domain *domain,
                              irq_hw_number_t hwirq)
{
        struct irq_data *data;

        /* Look for default domain if nececssary */
        if (domain == NULL)
                domain = irq_default_domain;
        if (domain == NULL)
                return 0;

        if (hwirq < domain->revmap_direct_max_irq) {
                data = irq_domain_get_irq_data(domain, hwirq);
                if (data && data->hwirq == hwirq)
                        return hwirq;
        }

        /* Check if the hwirq is in the linear revmap. */
        if (hwirq < domain->revmap_size)
                return domain->linear_revmap[hwirq];

        rcu_read_lock();
        data = radix_tree_lookup(&domain->revmap_tree, hwirq);
        rcu_read_unlock();
        return data ? data->irq : 0;
}
EXPORT_SYMBOL_GPL(irq_find_mapping);

irq domain 내에서 hwirq에 매핑된 irq 번호를 검색한다. 매핑되지 않은 경우 0을 반환한다.

  • 코드 라인 12~13에서 domain이 지정되지 않은 경우 default domain을 사용한다.
  • 코드 라인 14~15에서 여전히 domain이 null인 경우 0을 반환한다.
  • 코드 라인 17~21에서 no 매핑 구간에서 irq(=hwirq) 값을 알아온다.
    • hwirq가 리버스 매핑이 필요 없는 구간인 경우 hwirq 값으로 irq_data를 구해온다. 만일 data->hwirq 값과 동일한 경우에 그 대로 hwirq 값을 반환한다.
  • 코드 라인 24~25에서 리니어 매핑 구간에서 hwirq에 해당하는 irq 값을 알아온다.
    • hwirq가 리버스 매핑 범위 이내인 경우 리니어 리버스 매핑된 값을 반환한다.
  • 코드 라인 27~30에서 tree 매핑 구간에서 hwirq로 검색하여 irq 값을 알아온다.
    • radix tree에서 hwirq로 검색하여 irq_data를 알아온 다음 data->irq를 반환한다.

 

irq domain에 연결할 irq 디스크립터 할당과 해제

irq_domain_alloc_descs()

kernel/irq/irqdomain.c

static int irq_domain_alloc_descs(int virq, unsigned int cnt,
                                  irq_hw_number_t hwirq, int node)
{               
        unsigned int hint;

        if (virq >= 0) {
                virq = irq_alloc_descs(virq, virq, cnt, node);
        } else {
                hint = hwirq % nr_irqs;
                if (hint == 0)
                        hint++;
                virq = irq_alloc_descs_from(hint, cnt, node);
                if (virq <= 0 && hint > 1)
                        virq = irq_alloc_descs_from(1, cnt, node);
        }

        return virq;
}

고정 irq 번호를 요청한 경우 그 번호부터 cnt 수 만큼 irq 디스크립터를 할당하고 현재 모듈을 irq 디스크립터의 owner로 설정한다. 단 고정 irq 번호를 요청한 경우가 아니면 다음과 같은 방식으로 irq 번호를 배정한다.

  • hwirq 번호를 irq 번호로 할당 시도한다. 단 hwirq 번호가 0인 경우 hwirq + 1을 irq 번호로 사용한다.
  • 할당이 실패한 경우 hwirq 번호가 2 이상이면 irq 번호를 1부터 다시 한 번 시도한다.

 

hwirq 번호를 irq 번호(hwirq 번호가 0부터 시작하는 경우 irq 번호는 1부터)로 cnt 수 만큼 할당받는다. 단 hwirq + 1을 사용하여 할당 실패 시 hwirq를 1로 다시 시도한다.

  • 코드 라인 6~7에서 고정 irq 번호를 요청한 경우 그 번호부터 cnt 수 만큼 irq 디스크립터를 할당하고 현재 모듈을 irq 디스크립터의 owner로 설정한다.
  • 코드 라인 8~12에서 hwirq 번호부터 cnt 수 만큼 irq 디스크립터를 할당하고 현재 모듈을 irq 디스크립터의 owner로 설정한다. 만일 hwirq가 0부터 시작하는 경우 irq 번호는 1부터 시작 한다.
  • 코드 라인 13~14에서 만일 할당이 실패한 경우 hwirq 번호가 2 이상이면 irq 번호를 1부터 다시 한 번 시도한다.

 

irq_alloc_descs()

include/linux/irq.h

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

요청 irq 번호부터 cnt 수 만큼 irq 디스크립터를 할당하고  현재 모듈을 irq 디스크립터의 owner로 설정한다.

 

irq_alloc_descs_from()

include/linux/irq.h

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

새로운 irq 번호로 cnt 수 만큼 irq 디스크립터를 할당하고  현재 모듈을 irq 디스크립터의 owner로 설정한다.

 

__irq_alloc_descs()

kernel/irq/irqdesc.c

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

        if (!cnt)
                return -EINVAL;

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

        mutex_lock(&sparse_irq_lock);

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

        if (start + cnt > nr_irqs) {
                ret = irq_expand_nr_irqs(start + cnt);
                if (ret)
                        goto err; 
        }

        bitmap_set(allocated_irqs, start, cnt);
        mutex_unlock(&sparse_irq_lock);
        return alloc_descs(start, cnt, node, owner);

err:
        mutex_unlock(&sparse_irq_lock);
        return ret;
}
EXPORT_SYMBOL_GPL(__irq_alloc_descs);

고정 irq 번호를 요청한 경우 그 번호부터 cnt 수 만큼 irq 디스크립터를 할당하고 irq 디스크립터의 owner를 설정한다. 단 고정 irq 번호를 요청한 경우가 아니면 from 번호부터 배정한다.

  • 코드 라인 17~18에서 cnt가 지정되지 않은 경우 -EINVAL 에러로 반환한다.
  • 코드 라인 20~23에서 고정 irq 번호(0번 이상)를 요청한 경우 from에 고정 irq 번호를 대입한다. 단 from이 고정 irq 번호보다 큰 경우 -EINVAL 에러로 반환한다.
  • 코드 라인 24~31에서 새 irq 번호를 발부받으려 하는 경우 from 부터 시작하도록 한다. 단 아키텍처에 따라 from 값이 바뀔 수 있다.
    • x86에서는 GSI 공간을 피해야 한다.
  • 코드 라인 35~36에서 인터럽트 할당 비트맵에서 from 비트부터 연속된 cnt 수 만큼의 할당되지 않은 비트를 찾는다.
    • 참고: Bitmap Operations | 문c
    • 예) 0b0000_0000_1111_0000_1111_0000
      • from=5, cnt=2 -> start=8
      • from=5, cnt=5 -> start=16
  • 코드 라인 37~39에서 고정 irq 번호를 요청하였으면서 찾은 시작 irq 번호와 다른 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 41~45에서 할당할 공간이 부족한 경우 irq 공간을 필요한 수 만큼 확장한다. 확장이 불가능한 경우 -EEXIST 에러를 반환한다.
  • 코드 라인 47에서 인터럽트 할당 비트맵에 start 비트부터 cnt 수 만큼 1로 설정하여 할당되었음을 표시한다.
  • 코드 라인 49에서start irq 디스크립터 부터 cnt 수 만큼 모듈 owner를 설정하고 start irq 번호를 반환한다.
    • 만일 SPARSE IRQ 설정된 경우는 해당 irq 디스크립터를 할당도 한다.

 

alloc_descs() – FLAT IRQ 용

kernel/irq/irqdesc.c

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

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

                desc->owner = owner;
        }
        return start;
}

start irq 디스크립터 부터 cnt 수 만큼 모듈 owner를 설정하고 start irq 번호를 반환한다.

 

alloc_descs() – SPARSE IRQ 용

kernel/irq/irqdesc.c

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

        for (i = 0; i < cnt; i++) {
                desc = alloc_desc(start + i, node, owner);
                if (!desc)
                        goto err;
                mutex_lock(&sparse_irq_lock);
                irq_insert_desc(start + i, desc);
                mutex_unlock(&sparse_irq_lock);
        }
        return start;

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

        mutex_lock(&sparse_irq_lock);
        bitmap_clear(allocated_irqs, start, cnt);
        mutex_unlock(&sparse_irq_lock);
        return -ENOMEM;
}

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

  • 코드 라인 7~10에서 start 번호부터 시작하여 cnt 수 만큼 irq 디스크립터를 할당하고 모듈 owner를 설정한다.  만일 할당이 실패하면 err 레이블로 이동한다.
  • 코드 라인 11~15에서 할당한 irq 디스크립터를 radix tree에 추가한다. 그런 후 start irq 번호를 반환한다.
  • 코드 라인 17~24에서 이 루틴에서 할당한 모든 irq 디스크립터를 해제하고 allocated_irqs 비트맵에서 해당하는 비트들을 클리어한 후 -ENOMEM 에러를 반환한다.

 

리눅스 irq와 hw irq 연결

irq_domain_associate()

kernel/irq/irqdomain.c

int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
                         irq_hw_number_t hwirq)
{
        struct irq_data *irq_data = irq_get_irq_data(virq);
        int ret;

        if (WARN(hwirq >= domain->hwirq_max,
                 "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
                return -EINVAL;
        if (WARN(!irq_data, "error: virq%i is not allocated", virq))
                return -EINVAL;
        if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
                return -EINVAL;

        mutex_lock(&irq_domain_mutex);
        irq_data->hwirq = hwirq;
        irq_data->domain = domain;
        if (domain->ops->map) {
                ret = domain->ops->map(domain, virq, hwirq);
                if (ret != 0) {
                        /*
                         * If map() returns -EPERM, this interrupt is protected
                         * by the firmware or some other service and shall not
                         * be mapped. Don't bother telling the user about it.
                         */
                        if (ret != -EPERM) {
                                pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
                                       domain->name, hwirq, virq, ret);
                        }
                        irq_data->domain = NULL;
                        irq_data->hwirq = 0;
                        mutex_unlock(&irq_domain_mutex);
                        return ret;
                }

                /* If not already assigned, give the domain the chip's name */
                if (!domain->name && irq_data->chip)
                        domain->name = irq_data->chip->name;
        }

        if (hwirq < domain->revmap_size) {
                domain->linear_revmap[hwirq] = virq;
        } else {
                mutex_lock(&revmap_trees_mutex);
                radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
                mutex_unlock(&revmap_trees_mutex);
        }
        mutex_unlock(&irq_domain_mutex);

        irq_clear_status_flags(virq, IRQ_NOREQUEST);

        return 0;
}
EXPORT_SYMBOL_GPL(irq_domain_associate);

irq 디스크립터와 hwirq를 매핑한다. 도메인의 ops->map 후크에 등록한 콜백 함수가 있는 경우 해당 컨트롤러를 통해 매핑을 설정한다.

  • 코드 라인 4에서 irq 디스크립터의 irq_data를 알아온다.
  • 코드 라인 7~13에서 다음과 같이 에러가 있는 경우 경고 메시지를 출력하고 -EINVAL 에러를 반환한다.
    • hwirq 번호가 도메인의 hwirq_max 이상인 경우
    • irq 디스크립터가 할당되어 있지 않은 경우
    • 이미 domain에 연결되어 있는 경우
  • 코드 라인 16~17에서 irq_data의 hwirq와 domain 정보를 설정한다.
  • 코드 라인 18~19에서 irq domain의 map 후크에 핸들러 함수가 연결된 경우 호출하여 컨트롤러로 하여금 매핑을 수행하게 한다.
  • 코드 라인 20~34에서 만일 매핑이 실패한 경우 허가되지 않은 irq인 경우 단순 메시지만 출력하고 irq_data의 domain과 hwirq에 null과 0을 설정후 함수를 빠져나간다.
  • 코드 라인 37에서 domain에 이름이 설정되지 않은 경우 컨트롤러 이름을 대입한다.
  • 코드 라인 39~40에서 linear 방식에서 사용되는 irq인 경우 리니어용 리버스 맵 테이블에서 hwirq에 대응하는 곳에 irq를 설정한다.
  • 코드 라인 41~45에서 tree 방식에서 사용되는 irq인 경우 radix tree용 리버스 맵에 hwirq를 추가하고 irq_data(컨트롤러 기준 hwirq 번호)를 대입한다.
  • 코드 라인 48에서 irq 라인 상태에에서 norequest 플래그를 클리어한다.
    • IRQ_NOREQUEST
      • 현재 irq 라인에 요청된 인터럽트가 없다.

 

irq_domain_associate_many()

kernel/irq/irqdomain.c

void irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base,
                               irq_hw_number_t hwirq_base, int count)
{
        int i;

        pr_debug("%s(%s, irqbase=%i, hwbase=%i, count=%i)\n", __func__,
                of_node_full_name(domain->of_node), irq_base, (int)hwirq_base, count);

        for (i = 0; i < count; i++) {
                irq_domain_associate(domain, irq_base + i, hwirq_base + i);
        }
}
EXPORT_SYMBOL_GPL(irq_domain_associate_many);

irq_base로 시작하는 irq 디스크립터를 count 수 만큼 hwirq 번호부터 매핑한다. 해당 도메인의 ops->map 후크에 등록한 콜백 함수가 있는 경우 해당 컨트롤러를 통해 각각의 매핑을 설정한다.

 

리눅스 irq와 hw irq 연결 해제

irq_domain_disassociate()

kernel/irq/irqdomain.c

void irq_domain_disassociate(struct irq_domain *domain, unsigned int irq)
{
        struct irq_data *irq_data = irq_get_irq_data(irq);
        irq_hw_number_t hwirq;

        if (WARN(!irq_data || irq_data->domain != domain,
                 "virq%i doesn't exist; cannot disassociate\n", irq))
                return;

        hwirq = irq_data->hwirq;
        irq_set_status_flags(irq, IRQ_NOREQUEST);

        /* remove chip and handler */
        irq_set_chip_and_handler(irq, NULL, NULL);

        /* Make sure it's completed */
        synchronize_irq(irq);

        /* Tell the PIC about it */
        if (domain->ops->unmap)
                domain->ops->unmap(domain, irq);
        smp_mb();

        irq_data->domain = NULL;
        irq_data->hwirq = 0;

        /* Clear reverse map for this hwirq */
        if (hwirq < domain->revmap_size) {
                domain->linear_revmap[hwirq] = 0;
        } else {
                mutex_lock(&revmap_trees_mutex);
                radix_tree_delete(&domain->revmap_tree, hwirq);
                mutex_unlock(&revmap_trees_mutex);
        }
}

요청 irq에 대해 매핑된 hwirq와의 매핑을 해제한다. 도메인의 ops->unmap 후크에 등록한 콜백 함수가 있는 경우 해당 컨트롤러를 통해 매핑 해제를 설정한다.

 

arch/arm/mach-bcm2709/armctrl.c

static struct irq_domain_ops armctrl_ops = {
        .xlate = armctrl_xlate
};

bcm2708 및 bcm2709는 리눅스 irq와 hw irq의 변환은 인터럽트 컨트롤러에 고정되어 있어 sw로 매핑 구성을 변경할 수 없다. 따라서 매핑 및 언매핑용 함수는 제공되지 않고 argument -> hwirq 변환 함수만 제공한다.

  • argument
    • “interrupts = < x, … > 값

 

armctrl_xlate()

arch/arm/mach-bcm2709/armctrl.c

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

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

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

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

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

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

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

        *out_type = IRQ_TYPE_NONE;
        return 0;
}

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

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

 

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

 

기타 함수

  • irq_set_default_host()
  • irq_domain_insert_irq()
  • irq_domain_remove_irq()
  • irq_domain_insert_irq_data()
  • irq_domain_free_irq_data()
  • irq_domain_alloc_irq_data()
  • irq_domain_get_irq_data()
  • irq_domain_set_hwirq_and_chip()
  • irq_domain_set_info()
  • irq_domain_reset_irq_data()
  • irq_domain_free_irqs_common()
  • irq_domain_free_irqs_top()
  • irq_domain_is_auto_recursive()
  • irq_domain_free_irqs_recursive()
  • irq_domain_alloc_irqs_recursive()
  • __irq_domain_alloc_irqs()
  • irq_domain_free_irqs()
  • irq_domain_alloc_irqs_parent()
  • irq_domain_free_irqs_parent()
  • irq_domain_activate_irq()
  • irq_domain_deactivate_irq()
  • irq_domain_check_hierarchy()
  • irq_domain_get_irq_data()

 

FS 디버깅이 가능한 커널에서 irq domain 구성 참고

/sys/kernel/debug# cat irq_domain_mapping 
 name              mapped  linear-max  direct-max  devtree-node
 IR-IO-APIC            24          24           0  
 DMAR-MSI               2           0           0  
 IR-PCI-MSI             6           0           0  
 (null)             65563       65536           0  
 IR-PCI-MSI             1           0           0  
 (null)             65536       65536           0  
 (null)                 0           0           0  
 (null)                 0           0           0  
*(null)                31           0           0  
irq    hwirq    chip name        chip data           active  type            domain
    1  0x00001  IR-IO-APIC       0xffff8802158a6f80     *    LINEAR          IR-IO-APIC
    3  0x00003  IR-IO-APIC       0xffff880214972180          LINEAR          IR-IO-APIC
    4  0x00004  IR-IO-APIC       0xffff880214972280          LINEAR          IR-IO-APIC
    5  0x00005  IR-IO-APIC       0xffff880214972380          LINEAR          IR-IO-APIC
    6  0x00006  IR-IO-APIC       0xffff880214972480          LINEAR          IR-IO-APIC
    7  0x00007  IR-IO-APIC       0xffff880214972580          LINEAR          IR-IO-APIC
    8  0x00008  IR-IO-APIC       0xffff880214972680     *    LINEAR          IR-IO-APIC
    9  0x00009  IR-IO-APIC       0xffff880214972780     *    LINEAR          IR-IO-APIC
   10  0x0000a  IR-IO-APIC       0xffff880214972880          LINEAR          IR-IO-APIC
   11  0x0000b  IR-IO-APIC       0xffff880214972980          LINEAR          IR-IO-APIC
   12  0x0000c  IR-IO-APIC       0xffff880214972a80     *    LINEAR          IR-IO-APIC
   13  0x0000d  IR-IO-APIC       0xffff880214972b80          LINEAR          IR-IO-APIC
   14  0x0000e  IR-IO-APIC       0xffff880214972c80          LINEAR          IR-IO-APIC
   15  0x0000f  IR-IO-APIC       0xffff880214972d80          LINEAR          IR-IO-APIC
   16  0x00010  IR-IO-APIC       0xffff8800d3e4d940     *    LINEAR          IR-IO-APIC
   17  0x00011  IR-IO-APIC       0xffff8800d3e4da40          LINEAR          IR-IO-APIC
   18  0x00012  IR-IO-APIC       0xffff8800d3e4db40          LINEAR          IR-IO-APIC
   19  0x00013  IR-IO-APIC       0xffff8802147d3bc0          LINEAR          IR-IO-APIC
   20  0x00014  IR-IO-APIC       0xffff8800350411c0          LINEAR          IR-IO-APIC
   22  0x00016  IR-IO-APIC       0xffff88020f9e8e40          LINEAR          IR-IO-APIC
   23  0x00017  IR-IO-APIC       0xffff880035670000     *    LINEAR          IR-IO-APIC
   24  0x00000  DMAR-MSI                     (null)     *     RADIX          DMAR-MSI
   25  0x00001  DMAR-MSI                     (null)     *     RADIX          DMAR-MSI
   26  0x50000  IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI
   27  0x7d000  IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI
   28  0x58000  IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI
   29  0x08000  IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI
   30  0x6c000  IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI
   31  0x64000  IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI
   32  0x180000 IR-PCI-MSI                   (null)     *     RADIX          IR-PCI-MSI

 

irq_domain 구조체

include/linux/irqdomain.h

/**
 * struct irq_domain - Hardware interrupt number translation object
 * @link: Element in global irq_domain list.
 * @name: Name of interrupt domain
 * @ops: pointer to irq_domain methods
 * @host_data: private data pointer for use by owner.  Not touched by irq_domain
 *             core code.
 * @flags: host per irq_domain flags
 *
 * Optional elements
 * @of_node: Pointer to device tree nodes associated with the irq_domain. Used
 *           when decoding device tree interrupt specifiers.
 * @gc: Pointer to a list of generic chips. There is a helper function for
 *      setting up one or more generic chips for interrupt controllers
 *      drivers using the generic chip library which uses this pointer.
 * @parent: Pointer to parent irq_domain to support hierarchy irq_domains
 *
 * Revmap data, used internally by irq_domain
 * @revmap_direct_max_irq: The largest hwirq that can be set for controllers that
 *                         support direct mapping
 * @revmap_size: Size of the linear map table @linear_revmap[]
 * @revmap_tree: Radix map tree for hwirqs that don't fit in the linear map
 * @linear_revmap: Linear table of hwirq->virq reverse mappings
 */
struct irq_domain {
        struct list_head link;
        const char *name;
        const struct irq_domain_ops *ops;
        void *host_data;
        unsigned int flags;

        /* Optional data */
        struct device_node *of_node;
        struct irq_domain_chip_generic *gc;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_domain *parent;
#endif

        /* reverse map data. The linear map gets appended to the irq_domain */
        irq_hw_number_t hwirq_max;
        unsigned int revmap_direct_max_irq;
        unsigned int revmap_size;
        struct radix_tree_root revmap_tree;
        unsigned int linear_revmap[];
};

리눅스 인터럽트와 하드웨어 인터럽트 번호를 매핑 및 변환하기 위한 테이블 관리와 함수를 제공한다.

  •  link
    • 전역 irq_domain_list에 연결될 때 사용하다.
  • name
    • irq domain 명
  • ops
    • irq domain 메소드들의 주소가 담긴다.
  • host_data
    • private data 포인터 주소(irq domain core 코드에서 사용하지 않는다)
  • flags
    • irq domain의 플래그
      • IRQ_DOMAIN_FLAG_HIERARCHY
        • tree 구조 irq domain
      • IRQ_DOMAIN_FLAG_AUTO_RECURSIVE
        • tree 구조에서 recursive 할당/할당해제를 사용하는 경우
      • IRQ_DOMAIN_FLAG_NONCORE
        • generic 코어 코드를 사용하지 않고 직접 irq domain에 대해 specific한 처리를 한 경우 사용
  • of_node
    • 연결된 device tree 노드
  • gc
    • 연결된 generic chip 리스트 (인터럽트 컨트롤러 리스트)
  • parent
    • 상위 irq domain
    • irq domain 하이라키를 지원하는 경우 사용
    • arm64는 기본 사용
  • revmap_direct_max_irq
    • 직접 매핑을 지원하는 컨트롤러들에서 설정되는 최대 hw irq 번호
  • revmap_size
    • 리니어 맵 테이블 사이즈
  • revmap_tree
    • hwirq를 위한 radix tree 맵
  • linear_revmap
    • hwirq -> virq에 리버스 매핑에 대한 리니어 테이블

 

irq_domain_ops 구조체

include/linux/irqdomain.h

/**
 * struct irq_domain_ops - Methods for irq_domain objects
 * @match: Match an interrupt controller device node to a host, returns
 *         1 on a match
 * @map: Create or update a mapping between a virtual irq number and a hw
 *       irq number. This is called only once for a given mapping.
 * @unmap: Dispose of such a mapping
 * @xlate: Given a device tree node and interrupt specifier, decode
 *         the hardware irq number and linux irq type value.
 *
 * Functions below are provided by the driver and called whenever a new mapping
 * is created or an old mapping is disposed. The driver can then proceed to
 * whatever internal data structures management is required. It also needs
 * to setup the irq_desc when returning from map().
 */
struct irq_domain_ops {
        int (*match)(struct irq_domain *d, struct device_node *node);
        int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
        void (*unmap)(struct irq_domain *d, unsigned int virq);
        int (*xlate)(struct irq_domain *d, struct device_node *node,
                     const u32 *intspec, unsigned int intsize,
                     unsigned long *out_hwirq, unsigned int *out_type);

#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
        /* extended V2 interfaces to support hierarchy irq_domains */
        int (*alloc)(struct irq_domain *d, unsigned int virq,
                     unsigned int nr_irqs, void *arg);
        void (*free)(struct irq_domain *d, unsigned int virq,
                     unsigned int nr_irqs);
        void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
        void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
#endif
};
  • match
    • 호스와 인터럽트 컨트롤러 디바이스 노드와 매치 여부를 수행하는 함수 포인터. 매치되면 1을 반환한다
  • map
    • 가상 irq와 하드웨어 irq 번호끼리의 매핑을 만들거나 업데이트 하는 함수 포인터.
  • unmap
    • 매핑을 해제하는 함수 포인터.
  • xlate
    • 하드웨어 irq와 리눅스 irq와의 변환을 수행하는 함수 포인터.
  • alloc
    • irq domain의 tree 구조 지원시 irq 할당 함수 포인터
  • free
    • irq domain의 tree 구조 지원시 irq 할당 해제 함수 포인터
  • activate
    • irq domain의 tree 구조 지원시 irq activate 함수 포인터
  • deactivate
    • irq domain의 tree 구조 지원시 irq deactivate 함수 포인터

 

참고

답글 남기기

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