硬中断--中断号映射

liaocj 2024-12-03 09:02:56
Categories: Tags:

hwirq 和 irq 的映射

int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,
               int node, const struct irq_affinity_desc *affinity)
{
    unsigned int hint;

    if (virq >= 0) {
        virq = __irq_alloc_descs(virq, virq, cnt, node, THIS_MODULE,
                     affinity);
    } else {
        hint = hwirq % nr_irqs;
        if (hint == 0)
            hint++;
        virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,
                     affinity);
        if (virq <= 0 && hint > 1) {
            virq = __irq_alloc_descs(-1, 1, cnt, node, THIS_MODULE,
                         affinity);
        }
    }

    return virq;
}

/**
 * __irq_domain_alloc_irqs - Allocate IRQs from domain
 * @domain:	domain to allocate from
 * @irq_base:	allocate specified IRQ number if irq_base >= 0
 * @nr_irqs:	number of IRQs to allocate
 * @node:	NUMA node id for memory allocation
 * @arg:	domain specific argument: 包含硬件hwirq
 * @realloc:	IRQ descriptors have already been allocated if true
 * @affinity:	Optional irq affinity mask for multiqueue devices
 *
 * Allocate IRQ numbers and initialized all data structures to support
 * hierarchy IRQ domains.
 * Parameter @realloc is mainly to support legacy IRQs.
 * Returns error code or allocated IRQ number
 *
 * The whole process to setup an IRQ has been split into two steps.
 * The first step, __irq_domain_alloc_irqs(), is to allocate IRQ
 * descriptor and required hardware resources. The second step,
 * irq_domain_activate_irq(), is to program the hardware with preallocated
 * resources. In this way, it's easier to rollback when failing to
 * allocate resources.
 * dmar_alloc_hwirq -> __irq_domain_alloc_irqs
 * dmar_alloc_hwirq 中传递hwirq
 */
int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
                unsigned int nr_irqs, int node, void *arg,
                bool realloc, const struct irq_affinity_desc *affinity)
{
    int i, ret, virq;

    if (domain == NULL) {
        domain = irq_default_domain;
        if (WARN(!domain, "domain is NULL; cannot allocate IRQ\n"))
            return -EINVAL;
    }

    if (realloc && irq_base >= 0) {
        virq = irq_base;
    } else {
        virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,
                          affinity);
        if (virq < 0) {
            pr_debug("cannot allocate IRQ(base %d, count %d)\n",
                 irq_base, nr_irqs);
            return virq;
        }
    }

    if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {
        pr_debug("cannot allocate memory for IRQ%d\n", virq);
        ret = -ENOMEM;
        goto out_free_desc;
    }

    mutex_lock(&irq_domain_mutex);
    ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
    if (ret < 0) {
        mutex_unlock(&irq_domain_mutex);
        goto out_free_irq_data;
    }

    for (i = 0; i < nr_irqs; i++) {
        ret = irq_domain_trim_hierarchy(virq + i);
        if (ret) {
            mutex_unlock(&irq_domain_mutex);
            goto out_free_irq_data;
        }
    }

    for (i = 0; i < nr_irqs; i++)
        irq_domain_insert_irq(virq + i);
    mutex_unlock(&irq_domain_mutex);

    return virq;

out_free_irq_data:
    irq_domain_free_irq_data(virq, nr_irqs);
out_free_desc:
    irq_free_descs(virq, nr_irqs);
    return ret;
}

Linux内核支持众多的处理器体系结构,因此从系统角度来看,Linux内核中断管理可以分成如下4层。

硬件层,例如CPU和中断控制器的连接。
处理器架构管理,例如CPU中断异常处理。
中断控制器管理,例如IRQ中断号的映射。
Linux内核通用中断处理器层,例如中断注册和中断处理。

注册中断API函数request_irq()/ request_threaded_irq()是使用Linux内核软件中断号(俗称软件中断号或IRQ中断号),而不是硬件中断号

/* include/linux/interrupt.h */
int request_threaded_irq (unsigned int irq,  irq_handler_t handler,
                irq_handler_t thread_fn, unsigned long irqflags,
                const char *devname, void *dev_id)

其中,参数irq在Linux内核中称为IRQ number或 interrpt line,这是一个Linux内核管理的虚拟中断号,并不是指硬件的中断号。

内核中有一个宏NR_IRQS来表示系统支持中断数量的最大值,NR_IRQS和平台相关.

例如VexpressV2P-CA15_CA7平台的定义。

x86

irq_desc 数组有 NR_IRQS 个元素, NR_IRQS 并不是 256-32(!!!), 实际上, 虽然中断描述符表中一共有 256 项(前 32 项用作异常和 intel 保留), 但并不是所有中断向量都会使用到, 所以中断描述符数组也不一定是 256-32 项, CPU 可以使用多少个中断是由中断控制器(PIC、APIC)或者内核配置决定的, 我们看看 NR_IRQS 的定义

/* IOAPIC 为外部中断控制器 */
#ifdef CONFIG_X86_IO_APIC
#define CPU_VECTOR_LIMIT        (64 * NR_CPUS)
#define NR_IRQS                    \
    (CPU_VECTOR_LIMIT > IO_APIC_VECTOR_LIMIT ?    \
        (NR_VECTORS + CPU_VECTOR_LIMIT)  :    \
        (NR_VECTORS + IO_APIC_VECTOR_LIMIT))
#else /* !CONFIG_X86_IO_APIC: NR_IRQS_LEGACY = 16 */
#define NR_IRQS            NR_IRQS_LEGACY
#endif

此外, Linux内核定义了一个位图(!!!无论是R树存储描述符还是数组存放, 这个位图可用来分配irq!!!)来管理这些中断号
/* [kernel/irq/irqdesc.c] */
#define IRQ_BITMAP_BITS NR_IRQS
static DECLARE_BITMAP (allocated_irqs, IRQ_BITMAP_BITS);

位图变量allocated_irqs分配NR_IRQS比特位(!!!无论是R树存储描述符还是数组存放, 这个位图用来分配irq!!!),每个比特位表示一个中断号。

/**
 * irq_create_mapping_affinity() - 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
 * @affinity: irq affinity
 *
 * 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_affinity(struct irq_domain *domain,
                       irq_hw_number_t hwirq,
                       const struct irq_affinity_desc *affinity)
{
    struct device_node *of_node;
    int virq;

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

    /* Look for default domain if necessary */
    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);

    of_node = irq_domain_get_of_node(domain);

    /* Check if mapping already exists */
    // 如果这个硬件中断号己经映射过了,那么irq_find_mapping()可以找到映射后的软件中断号,在此情景下,该硬件中断号还没有映射
    virq = irq_find_mapping(domain, hwirq);
    if (virq) {
        pr_debug("-> existing mapping on virq %d\n", virq);
        return virq;
    }

    /* Allocate a virtual interrupt number */
    virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node),
                      affinity);
    if (virq <= 0) {
        pr_debug("-> virq allocation failed\n");
        return 0;
    }

    //映射的核心函数,内部调用__irq_domain_alloc_irqs()函数
    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(of_node), virq);

    return virq;
}
EXPORT_SYMBOL_GPL(irq_create_mapping_affinity);

struct irq_desc *irq_to_desc(unsigned int irq)
{
    return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}

struct irq_data *irq_get_irq_data(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    return desc ? &desc->irq_data : NULL;
}

int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
             irq_hw_number_t hwirq)
{
    //virq 就是irq_desc数组的index
    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;
    }

    domain->mapcount++;
    // 根据hwirq反向索引到irq_data
    irq_domain_set_mapping(domain, hwirq, irq_data);
    mutex_unlock(&irq_domain_mutex);

    irq_clear_status_flags(virq, IRQ_NOREQUEST);

    return 0;
}

完成硬件中断号到软件中断号的映射

通过IRQ 中断号和 irq_domain获取struct irq_data 数据结构, 然后把硬件中断号 hwirq设置到 struct irq_data 数据结构中的hwirq 成员中, 这样就完成了硬件中断号到软件中断号的映射(!!!).

/**
 * irq_domain_set_info - Set the complete data for a @virq in @domain
 * @domain:		Interrupt domain to match
 * @virq:		IRQ number
 * @hwirq:		The hardware interrupt number
 * @chip:		The associated interrupt chip
 * @chip_data:		The associated interrupt chip data
 * @handler:		The interrupt flow handler
 * @handler_data:	The interrupt flow handler data
 * @handler_name:	The interrupt handler name
 */
void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
             irq_hw_number_t hwirq, struct irq_chip *chip,
             void *chip_data, irq_flow_handler_t handler,
             void *handler_data, const char *handler_name)
{
    irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
    __irq_set_handler(virq, handler, 0, handler_name);
    irq_set_handler_data(virq, handler_data);
}
EXPORT_SYMBOL(irq_domain_set_info);

[kernel/irq/irqdomain.c]
int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,
                  irq_hw_number_t hwirq, struct irq_chip *chip,
                  void *chip_data)
{
    struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);

    if (!irq_data)
        return -ENOENT;

    irq_data->hwirq = hwirq;
    irq_data->chip = chip ? chip : &no_irq_chip;
    irq_data->chip_data = chip_data;

    return 0;
}