Linux: 中断实现简析
创始人
2025-05-29 08:02:32

文章目录

  • 1. 前言
  • 2. 分析背景
  • 3. 实现
    • 3.1 硬件基础
    • 3.2 中断初始化
      • 3.2.1 早期初始化
      • 3.2.2 GIC芯片初始化
    • 3.3 注册中断域
    • 3.4 中断配置解析和映射
    • 3.5 中断处理接口注册
    • 3.6 中断处理流程
      • 3.6.1 ARM32 中断向量表
  • 4. 中断控制器级联
    • 4.1 级联中断控制器的初始化
    • 4.2 级联中断使用范例
      • 4.2.1 例1:USB PHY 芯片 GPIO 中断使用
      • 4.2.2 例2:无线网卡唤醒 GPIO 中断使用
    • 4.3 级联中断的处理过程
      • 4.3.1 GPIO 中断电平触发模式切换
  • 5. SGI 中断处理
  • 6. 中断共享
  • 7. 中断信息用户空间接口

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 分析背景

本文分析基于 linux-4.14.132 内核、ARM32 架构代码进行分析。

3. 实现

本篇以 ARM 架构的 GIC 芯片 为例进行分析。

3.1 硬件基础

本文不讲述中断相关的硬件知识,读者可自行请参考手册

《ARM Architecture Reference Manual.pdf》
《DDI0406C_d_armv7ar_arm.pdf》
《IHI0048A_gic_architecture_spec_v1_0.pdf》

的相关章节。

3.2 中断初始化

3.2.1 早期初始化

start_kernel()local_irq_disable();early_boot_irqs_disabled = true;setup_arch()/* 中断向量表设置 */paging_init(mdesc)devicemaps_init(mdesc)void *vectors;vectors = early_alloc(PAGE_SIZE * 2);early_trap_init(vectors)vectors_page = vectors_base;for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)((u32 *)vectors_base)[i] = 0xe7fddef1;memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);map.pfn = __phys_to_pfn(virt_to_phys(vectors));map.virtual = 0xffff0000;map.length = PAGE_SIZE;#ifdef CONFIG_KUSER_HELPERSmap.type = MT_HIGH_VECTORS;#elsemap.type = MT_LOW_VECTORS;#endifcreate_mapping(&map);if (!vectors_high()) {map.virtual = 0;map.length = PAGE_SIZE * 2;map.type = MT_LOW_VECTORS;create_mapping(&map);}/* Now create a kernel read-only mapping */map.pfn += 1;map.virtual = 0xffff0000 + PAGE_SIZE;map.length = PAGE_SIZE;map.type = MT_LOW_VECTORS;create_mapping(&map);early_abt_enable() @ arch/arm/mm/fault.cfsr_info[FSR_FS_AEA].fn = early_abort_handler;local_abt_enable();fsr_info[FSR_FS_AEA].fn = do_bad;/* 初始中断处理接口为 machine 定义的函数 */handle_arch_irq = mdesc->handle_irq;trap_init()early_irq_init()init_irq_default_affinity() /* 初始化默认的CPU中断亲和性 */initcnt = arch_probe_nr_irqs(); /* 探测架构中断数目 *//* 为探测到的架构中断,分配中断描述符 */for (i = 0; i < initcnt; i++) {desc = alloc_desc(i, node, 0, NULL, NULL);set_bit(i, allocated_irqs);irq_insert_desc(i, desc);}arch_early_irq_init()

3.2.2 GIC芯片初始化

上节 3.2.1 章节的分析:

start_kernel()...init_IRQ()
void __init init_IRQ(void)
{if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)irqchip_init();elsemachine_desc->init_irq();...
}void __init irqchip_init(void)
{/** 表 __irqchip_of_table[] 内包含由 IRQCHIP_DECLARE() 声明的* 中断控制器驱动列表(由 vmlinux.lds.h 中的 IRQCHIP_OF_MATCH_TABLE() 组织到一起),* 而 DTS 声明中断控制器驱动实例。* of_irq_init() 匹配中断控制器驱动到对应的DTS中的实例,并调用中断控制器* 驱动的初始化接口,初始化中断控制器。*/of_irq_init(__irqchip_of_table);...
}/** 扫描 DT 匹配 @matches 的中断控制器,并初始化它们。** DTS 声明的中断控制器:* gic: interrupt-controller@01c81000 {* 		compatible = "arm,gic-400";*      reg = <0x01c81000 0x1000>,*            <0x01c82000 0x2000>,*            <0x01c84000 0x2000>,*            <0x01c86000 0x2000>;*      interrupt-controller;*      #interrupt-cells = <3>;*      interrupts = ;* };** 匹配的中断控制器驱动声明:@drivers/irqchip/irq-gic.c* IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init); // AllWinner H3* ==>* static const struct of_device_id __of_table_gic_400* 		__used __section(__irqchip_of_table)* = { .compatible = "arm,gic-400", *	   .data = (gic_of_init == (of_init_fn_2)NULL) ? gic_of_init : gic_of_init };*/
void __init of_irq_init(const struct of_device_id *matches)
{const struct of_device_id *match;struct device_node *np, *parent = NULL;struct of_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);/* 扫描 DTS 中匹配 @matches 的中断控制器,放入列表 @intc_desc_list */for_each_matching_node_and_match(np, matches, &match) {/* 属性 "interrupt-controller" 标记节点为中断控制器 */if (!of_property_read_bool(np, "interrupt-controller") ||!of_device_is_available(np))continue;.../* 为中断控制器分配描述对象 */ desc = kzalloc(sizeof(*desc), GFP_KERNEL);desc->irq_init_cb = match->data; /* gic_of_init(),... */...desc->interrupt_parent = of_irq_find_parent(np);if (desc->interrupt_parent == np) /* 指定的父中断控制器和 @np 是相同节点,则置父中断控制器节点为 NULL */desc->interrupt_parent = NULL;list_add_tail(&desc->list, &intc_desc_list); /* 添加到中断控制器列表 */}/* 初始化中断控制器 */ while (!list_empty(&intc_desc_list)) {/* 处理所有 interrupt_parent 为 @parent 的中断控制器 */ list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {if (desc->interrupt_parent != parent) /* 当前只处理以 @parent 为父的中断控制器 */continue;list_del(&desc->list); /* 从中断控制器列表 @intc_desc_list 中移除 */.../* 初始化中断控制器 @desc */ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent); /* gic_of_init(),... *//** 当前已经初始化的中断控制器,可能作为其它中断控制器的父。* 将它添加父中断控制器列表 @intc_parent_list ,后续在此循* 环内以父 @parent 的形式出现,作为初始化的时的第2个参数* 传递给它的子中断控制器。*/ list_add_tail(&desc->list, &intc_parent_list);}desc = list_first_entry_or_null(&intc_parent_list,typeof(*desc), list);/* @desc 将作为新的、可能的父中断控制器,先将其从父中断控制器列表 @intc_parent_list 中移除 */list_del(&desc->list);parent = desc->dev; /* 已经成功初始化过的中断控制器 @desc ,以新父中断控制器的身份重新出场 */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);of_node_put(desc->dev);kfree(desc);}
}/** GIC 芯片初始化。 * * GIC 的 DTS 配置如下* gic: interrupt-controller@01c81000 {*		compatible = "arm,gic-400";*		reg = <0x01c81000 0x1000>,*			  <0x01c82000 0x2000>,*			  <0x01c84000 0x2000>,*			  <0x01c86000 0x2000>;*		interrupt-controller;*		#interrupt-cells = <3>;*		interrupts = ;* };*/
int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{gic = &gic_data[gic_cnt];ret = gic_of_setup(gic, node);...ret = __gic_init_bases(gic, -1, &node->fwnode);...if (parent) { /* 级联情形下的父中断控制器 */irq = irq_of_parse_and_map(node, 0);gic_cascade_irq(gic_cnt, irq);}gic_cnt++; /* 系统中注册的 ARM GIC 芯片 */return 0;
}/* 读取并映射 Distributor 和 CPU interface 寄存器基地址 */ 
static int gic_of_setup(struct gic_chip_data *gic, struct device_node *node)
{/* 读取并映射 Distributor 寄存器基地址: reg[0] => <0x01c81000 0x1000> */gic->raw_dist_base = of_iomap(node, 0);/* 读取并映射 CPU interface 寄存器基地址: reg[1] => <0x01c82000 0x2000> */gic->raw_cpu_base = of_iomap(node, 1);/* 如果寄存器是各接口独立一份的,可以通过 "cpu-offset" 属性来指定个接口寄存器地址偏移 */if (of_property_read_u32(node, "cpu-offset", &gic->percpu_offset))gic->percpu_offset = 0;return 0;
}static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start,struct fwnode_handle *handle)
{if (gic == &gic_data[0]) { /* 顶层 ROOT 中断控制器 */for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff; /* 初始将中断派发给所有 CPU interface 上的 CPU */#ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq); /* 用于处理器之间的通信:发送 SGI 中断到其它处理器 */#endif/* * CPU 热插拔状态 CPUHP_AP_IRQ_GIC_STARTING 处理接口,在* 多核处理系统下,非 BOOT CPU 热插拔启动上线最终达到 * CPUHP_AP_ONLINE 状态,在此之前会经过*  CPUHP_AP_IRQ_GIC_STARTING 态,在该状态下会调用这里注* 册的回调 gic_starting_cpu() 来为对应 CPU 初始化中断。*/cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"irqchip/arm/gic:starting",gic_starting_cpu, NULL);/* 设置中断 GIC 中断处理入口 */set_handle_irq(gic_handle_irq); }/* 这是中断芯片名,这个名字会输出到 /proc/interrupts */name = kasprintf(GFP_KERNEL, "GICv2");gic_init_chip(gic, NULL, name, true); /* 设定中断芯片接口 */ret = gic_init_bases(gic, irq_start, handle);...return ret;
}/* 初始化 GIC 中断芯片 irq_chip */
static void gic_init_chip(struct gic_chip_data *gic, struct device *dev,const char *name, bool use_eoimode1)
{/* Initialize irq_chip */gic->chip = gic_chip;gic->chip.name = name;...if (use_eoimode1) {gic->chip.irq_mask = gic_eoimode1_mask_irq;gic->chip.irq_eoi = gic_eoimode1_eoi_irq;gic->chip.irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity;}#ifdef CONFIG_SMPif (gic == &gic_data[0])gic->chip.irq_set_affinity = gic_set_affinity;
#endif
}static int gic_init_bases(struct gic_chip_data *gic, int irq_start,struct fwnode_handle *handle)
{if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) { /* 每 {Distributor, CPU interface} 的寄存器基址情形(非 Banked) */...}  else { /* 非每 {Distributor, CPU interface} 的寄存器基址情形(Banked) *//* gic_chip_data::percpu_offset 为 0 的情形,* 应该开启 CONFIG_GIC_NON_BANKED 配置 */gic->dist_base.common_base = gic->raw_dist_base;gic->cpu_base.common_base = gic->raw_cpu_base;gic_set_base_accessor(gic, gic_get_common_base);}/* 读取 GIC 实现支持的中断数目 */gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;gic_irqs = (gic_irqs + 1) * 32; /* 计算 GIC 芯片支持的中断数目 */if (gic_irqs > 1020) /* 1021~1023 被保留 */gic_irqs = 1020;gic->gic_irqs = gic_irqs; /* 记录 GIC 支持的中断数目 */if (handle) {		/* DT/ACPI */ /* 使用 DTS 的情形 *//* 为中断芯片注册中断域,细节见 3.3 小节 */gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);} else {		/* Legacy support */...}/** 初始化 GIC Distributor:* . 设置所有 SPI 中断转发到哪些 CPU 中断接口* . 配置所有 SPI 中断的电平触发方式,且低电平触发* . 配置所有 SPI 中断为相同的优先级* . 禁用所有 SPI 中断*/gic_dist_init(gic);ret = gic_cpu_init(gic);...return 0;
}static void gic_dist_init(struct gic_chip_data *gic)
{unsigned int i;u32 cpumask;unsigned int gic_irqs = gic->gic_irqs;void __iomem *base = gic_data_dist_base(gic);writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL);/* * 读取【当前CPU】某个 {SGI, PPI} 的非0派发配置,然后立即返回该配置值。* 这些派发配置只会将中断派发给某个CPU (这里是 CPU 0),因为我们通常读* 到的是当前CPU的PPI派发配置:它们是只读的,只会派发给对应的当前CPU* (也即 BOOT CPU ,只有 BOOT CPU 的中断初始化流程才走这里。BOOT CPU 通常是 CPU 0)。*/ cpumask = gic_get_cpumask(gic);/* 将 gic_get_cpumask() 读回的 8-bit 派发配置复制到 32-bit @cpumask 的所有 4个 8-bit 域 */cpumask |= cpumask << 8;cpumask |= cpumask << 16;/** 寄存器 GIC_DIST_TARGET[0 ~ 7] 是 (SGI + PPI) 中断的固有只读配置,* GIC_DIST_TARGET[8~...] 设置所有 SPI 中断转发到哪些 CPU 中断接口。** @cpumask 通常是 PPI 的配置,所以这里的配置是将所有的 SPI 派发给* 当前 CPU (也即 BOOT CPU ,只有 BOOT CPU 的中断初始化流程才走这* 里。BOOT CPU 通常是 CPU 0)。这也就是说,默认情况下,所有的 SPI* 中断都会派发给当前的 BOOT CPU (BOOT CPU 通常是 CPU 0),到这里还* 是没问题的,毕竟其它CPU核还没有跑起来。*/for (i = 32; i < gic_irqs; i += 4)writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);/** 配置 GIC 芯片 distributor:* . 配置所有 SPI 中断的电平触发方式,且低电平触发* . 配置所有 SPI 中断为相同的优先级* . 禁用所有 SPI 中断*/gic_dist_config(base, gic_irqs, NULL);writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}static int gic_cpu_init(struct gic_chip_data *gic)
{void __iomem *dist_base = gic_data_dist_base(gic);void __iomem *base = gic_data_cpu_base(gic);unsigned int cpu_mask, cpu = smp_processor_id();int i;if (gic == &gic_data[0]) {gic_check_cpu_features();cpu_mask = gic_get_cpumask(gic);gic_cpu_map[cpu] = cpu_mask; /* 当前 CPU @cpu 中断的默认转发配置:只转发给自身 *//** Clear our mask from the other map entries in case they're* still undefined.*/for (i = 0; i < NR_GIC_CPU_IF; i++)if (i != cpu)gic_cpu_map[i] &= ~cpu_mask;}gic_cpu_config(dist_base, NULL);/** 只有优先级高于 GICC_INT_PRI_THRESHOLD 的中断才能传递给 GIC CPU 接口,* 即 优先级值 < GICC_INT_PRI_THRESHOLD 的中断。*/writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);gic_cpu_if_up(gic); /* 启用转发到 GIC CPU 接口的中断 */return 0;
}void gic_cpu_config(void __iomem *base, void (*sync_access)(void))
{int i;/** Deal with the banked PPI and SGI interrupts - disable all* PPI interrupts, ensure all SGI interrupts are enabled.* Make sure everything is deactivated.*/writel_relaxed(GICD_INT_EN_CLR_X32, base + GIC_DIST_ACTIVE_CLEAR);writel_relaxed(GICD_INT_EN_CLR_PPI, base + GIC_DIST_ENABLE_CLEAR);writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);/** Set priority on PPI and SGI interrupts*//* 设置 SGI, PPI 中断的优先级为缺省优先级 */for (i = 0; i < 32; i += 4)writel_relaxed(GICD_INT_DEF_PRI_X4,base + GIC_DIST_PRI + i * 4 / 4);if (sync_access)sync_access();
}static void gic_cpu_if_up(struct gic_chip_data *gic)
{void __iomem *cpu_base = gic_data_cpu_base(gic);u32 bypass = 0;u32 mode = 0;if (gic == &gic_data[0] && static_key_true(&supports_deactivate))mode = GIC_CPU_CTRL_EOImodeNS;/** Preserve bypass disable bits to be written back later*/bypass = readl(cpu_base + GIC_CPU_CTRL);bypass &= GICC_DIS_BYPASS_MASK;/* 启用转发到 GIC CPU 接口的中断 */writel_relaxed(bypass | mode | GICC_ENABLE, cpu_base + GIC_CPU_CTRL);
}

3.3 注册中断域

要为每个使用的中断芯片,注册一个中断域 irq_domainGIC 芯片也是一样。通过接口 irq_domain_create_linear() 注册中断域:

/** @fwnode   : irq domain (中断控制器) 的 dts 配置节点指针* @size     : irq domain (中断控制器) 支持的中断数量* @ops      : irq domain (中断控制器) 接口* @host_data: 特定 irq domain (中断控制器) 的数据???*/
static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode,unsigned int size,const struct irq_domain_ops *ops,void *host_data)
{return __irq_domain_add(fwnode, size, size, 0, ops, host_data);
}struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,irq_hw_number_t hwirq_max, int direct_max,const struct irq_domain_ops *ops,void *host_data)
{struct device_node *of_node = to_of_node(fwnode);struct irqchip_fwid *fwid;struct irq_domain *domain;/* * 为 irq domain 以及其管理的 hwirq -> virq 的逆向映射表 分配空间。* 其中 (sizeof(unsigned int) * size) 是逆向映射表的空间大小。*/domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),GFP_KERNEL, of_node_to_nid(of_node));if (fwnode && is_fwnode_irqchip(fwnode)) {...}  else if (of_node) { // Yes, we focus on this.char *name;name = kstrdup(of_node_full_name(of_node), GFP_KERNEL);strreplace(name, '/', ':');domain->name = name; /* 设定 irq domain 名称: 中断控制器 DTS 节点全路径 */domain->fwnode = fwnode; /* 设定 irq domain 关联的 DTS 节点 */domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;}of_node_get(of_node);INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);/* * 设定 irq domain 的操作接口: * (如 gic_irq_domain_hierarchy_ops, sunxi_pinctrl_irq_domain_ops, ...) */domain->ops = ops;domain->host_data = host_data; /* 设定 irq domain 的私有数据 (&gic_data[0], ...) */domain->hwirq_max = hwirq_max; /* 设定 irq domain 管理的中断目数 */domain->revmap_size = size; /* 设定 irq domain 管理的映射条目数 */domain->revmap_direct_max_irq = direct_max;irq_domain_check_hierarchy(domain);utex_lock(&irq_domain_mutex);debugfs_add_domain_dir(domain); /* /sys/kernel/debug/domains/XXX */list_add(&domain->link, &irq_domain_list); /* 添加到系统全局 irq domain 列表 */mutex_unlock(&irq_domain_mutex);return domain;
}static void irq_domain_check_hierarchy(struct irq_domain *domain)
{/* Hierarchy irq_domains must implement callback alloc() *//* * irq_domain_ops::alloc 接口由配置 CONFIG_IRQ_DOMAIN_HIERARCHY 使能;* 同时,级联的中断域(中断控制器)必须实现 alloc 接口。*/if (domain->ops->alloc)domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;
}

3.4 中断配置解析和映射

会发送中断信号到 GIC 的硬件模块,其 DTS 节点都会配置 interrupts 属性,我们这里以一个 stmmicro 网卡芯片为例,来说明中断配置的解析和映射建立过程。本章节讨论的是中断信号发送到 ROOT 中断控制器 GIC 的情形,对于级联的情形,将在后面的章节中讨论。
内核提供如下接口,来解析中断配置,以及建立 硬件中断号Linux虚拟中断号 映射。

/* include/linux/of_irq.h */extern int of_irq_get(struct device_node *dev, int index);
extern int of_irq_get_byname(struct device_node *dev, const char *name);extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);
/* include/linux/platform_device.h */extern int platform_get_irq(struct platform_device *, unsigned int);
extern int platform_get_irq_byname(struct platform_device *, const char *);

先来看例子网卡的 DTS 配置:

emac: ethernet@1c30000 {compatible = "allwinner,sun8i-h3-emac";...interrupts = ;interrupt-names = "macirq";...
};

来看中断解析和映射建立的过程:

int irq = platform_get_irq_byname(pdev, "macirq");
int platform_get_irq_byname(struct platform_device *dev, const char *name)
{struct resource *r;if (IS_ENABLED(CONFIG_OF_IRQ) && dev->dev.of_node) {int ret;ret = of_irq_get_byname(dev->dev.of_node, name);if (ret > 0 || ret == -EPROBE_DEFER)return ret;}r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name);return r ? r->start : -ENXIO;
}int of_irq_get_byname(struct device_node *dev, const char *name)
{int index;if (unlikely(!name))return -EINVAL;index = of_property_match_string(dev, "interrupt-names", name);if (index < 0)return index;return of_irq_get(dev, index);
}int of_irq_get(struct device_node *dev, int index)
{int rc;struct of_phandle_args oirq;struct irq_domain *domain;rc = of_irq_parse_one(dev, index, &oirq);if (rc)return rc;/* 找到 @dev 挂接的中断控制器对应的中断域对象 */domain = irq_find_host(oirq.np);if (!domain)return -EPROBE_DEFER;return irq_create_of_mapping(&oirq);
}int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)
{.../* Look for the interrupt parent. */p = of_irq_find_parent(device); /* 找到设备的中断控制器父节点:中断信号发给谁 */if (p == NULL)return -EINVAL;/* Get size of interrupt specifier */if (of_property_read_u32(p, "#interrupt-cells", &intsize)) {res = -EINVAL;goto out;}/* 读取中断配置项 */out_irq->np = p;out_irq->args_count = intsize;for (i = 0; i < intsize; i++) {res = of_property_read_u32_index(device, "interrupts",(index * intsize) + i,out_irq->args + i);if (res)goto out;}...
out:of_node_put(p);return res;	
}unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{struct irq_fwspec fwspec;of_phandle_args_to_fwspec(irq_data, &fwspec);return irq_create_fwspec_mapping(&fwspec);
}unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{/* * 例子中, @fwspec 的数据如下: * fwspec->fwnode = &gic;* fwspec->param_count = 3;* fwspec->param[0..2] = {GIC_SPI, 82, IRQ_TYPE_LEVEL_HIGH};*/if (fwspec->fwnode) {/* 找到中断挂接中断控制器的 irq_domain */domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);} else {...}/** 调用 irq_domain (中断控制器)的中断配置项解析接口,* 提取 【硬件中断号】 和 【中断触发类型】 信息: * @fwspec ==> {@hwirq, @type} */if (irq_domain_translate(domain, fwspec, &hwirq, &type))return 0;/** 分配一个空闲的 Linux 虚拟 IRQ 中断号,映射到 @hwirq ,* 同时分配一个 IRQ 中断描述符 irq_desc 。*/if (irq_domain_is_hierarchy(domain)) {virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);}  else {...}.../* Store trigger type */irqd_set_trigger_type(irq_data, type); /* 设置中断触发类型: RISING, FALLING, HIGH, LOW */return virq; /* 返回分配的 Linux IRQ 虚拟中断号 */
}static int irq_domain_translate(struct irq_domain *d,struct irq_fwspec *fwspec,irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/* 调用 irq_domain (中断控制器)的中断配置解析接口: @fwspec ==> {@hwirq, @type} */if (d->ops->translate)return d->ops->translate(d, fwspec, hwirq, type); /* gic_irq_domain_translate() */
#endif
}/* GIC 中断配置项解析:@fwspec ==> {@hwirq, @type} */
static int gic_irq_domain_translate(struct irq_domain *d,struct irq_fwspec *fwspec,unsigned long *hwirq,unsigned int *type)
{if (is_of_node(fwspec->fwnode)) { /* 普通中断信号源设备DTS节点 */*hwirq = fwspec->param[1] + 16; /* 跳过 SGIs 中断编号区间 */if (!fwspec->param[0]) /* GIC_SPI */*hwirq += 16;*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; /* 中断触发类型,如 IRQ_TYPE_LEVEL_HIGH */return 0;}...
}/* * 1. 中断描述符分配;* 2. 映射 硬件中断号 到 Linux虚拟中断号;* 3. 中断处理接口设置。*/
static inline int irq_domain_alloc_irqs(struct irq_domain *domain,unsigned int nr_irqs, int node, void *arg/*struct irq_fwspec*/)
{return __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false,NULL);
}int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,unsigned int nr_irqs, int node, void *arg,bool realloc, const struct cpumask *affinity)
{int i, ret, virq;.../* 分配中断描述符,以及 Linux虚拟中断号 */virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,affinity);/* 设置中断描述符的中断域(中断控制器) */if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {...ret = -ENOMEM;goto out_free_desc;}.../* 设置中断的 处理接口 和 flag 等 */ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);/* 建立中断域内 硬件中断号 到 Linux虚拟中断号 的 映射关系 */for (i = 0; i < nr_irqs; i++)irq_domain_insert_irq(virq + i);return virq; /* 返回分配的起始 Linux 虚拟中断号 */
}int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain,unsigned int irq_base,unsigned int nr_irqs, void *arg)
{return domain->ops->alloc(domain, irq_base, nr_irqs, arg); /* gic_irq_domain_alloc(), ... */
}static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{int i, ret;irq_hw_number_t hwirq;unsigned int type = IRQ_TYPE_NONE;struct irq_fwspec *fwspec = arg;ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type);for (i = 0; i < nr_irqs; i++) {ret = gic_irq_domain_map(domain, virq + i, hwirq + i);if (ret)return ret;}return 0;
}static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw)
{struct gic_chip_data *gic = d->host_data;if (hw < 32) { /* 每 CPU 的私有中断: SGI + PPI */irq_set_percpu_devid(irq);/* 设置 SGI, PPI 中断处理接口,由它再调用irq_desc::action 接口 */irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,handle_percpu_devid_irq, NULL, NULL);irq_set_status_flags(irq, IRQ_NOAUTOEN);} else { /* 全局于所有 CPU 的 SPI 中断 *//* 设置 SPI 中断处理接口,由它再调用 irq_desc::action 接口 接口 */irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,handle_fasteoi_irq, NULL, NULL);irq_set_probe(irq);irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));}return 0;
}static void irq_domain_insert_irq(int virq)
{struct irq_data *data;for (data = irq_get_irq_data(virq); data; data = data->parent_data) {struct irq_domain *domain = data->domain;domain->mapcount++;irq_domain_set_mapping(domain, data->hwirq, data);...}irq_clear_status_flags(virq, IRQ_NOREQUEST);
}static void irq_domain_set_mapping(struct irq_domain *domain,irq_hw_number_t hwirq,struct irq_data *irq_data)
{if (hwirq < domain->revmap_size) {domain->linear_revmap[hwirq] = irq_data->irq; /* 硬件中断号 到 Linux 虚拟中断号 的映射 */} else { /* 超过 irq_domain 注册时设定的中断数目,用基树建立映射关系 */mutex_lock(&revmap_trees_mutex);radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);mutex_unlock(&revmap_trees_mutex);}
}

到此,完成 硬件中断号Linux虚拟中断号 映射的建立,同时分配了中段描述符 irq_desc ,函数的最终返回值为分配的 Linux虚拟中断号
另外,从代码分析,我们看到了 GIC 硬件中断号 DTS 配置值的一个规律:如果是 PPI 中断,DTS 配置值应该按实际硬件中断号减去 16;如果是 SPI 中断,DTS 配置值应该按实际硬件中断号减去 32

3.5 中断处理接口注册

在章节 3.4 中完成中断配置解析和映射后 ,剩下的就是注册中断处理接口。可以通过 request_irq()request_threaded_irq() 来注册中断处理接口,这里只分析线程化方式的 request_threaded_irq() ,传递给 request_threaded_irq()irq 参数,就是章节 3.4 申请的 Linux虚拟中断号

request_threaded_irq(irq, handler, thread_fn, irqflags, devname, dev_id)desc = irq_to_desc(irq);if (!handler) { /* 线程化中断的 @handler 通常设为 NULL */if (!thread_fn)return -EINVAL;handler = irq_default_primary_handler;}action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);action->handler = handler; /* 设置中断处理接口 */action->thread_fn = thread_fn;action->flags = irqflags;action->name = devname;action->dev_id = dev_id;retval = __setup_irq(irq, desc, action);new->irq = irq; /* new = action */if (new->thread_fn && !nested) { /* 中断线程化 */ret = setup_irq_thread(new, irq, false);struct task_struct *t;struct sched_param param = {.sched_priority = MAX_USER_RT_PRIO/2,};t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);get_task_struct(t);new->thread = t;set_bit(IRQTF_AFFINITY, &new->thread_flags);return 0;...}old_ptr = &desc->action;old = *old_ptr;...irqd_get_trigger_type(&desc->irq_data);...*old_ptr = new;irq_pm_install_action(desc, new);desc->irq_count = 0;desc->irqs_unhandled = 0;...irq_setup_timings(desc, new);if (new->thread)wake_up_process(new->thread); /* 唤醒中断处理线程 */register_irq_proc(irq, desc); /* /proc/irq/N/* */irq_add_debugfs_entry(irq, desc);new->dir = NULL;register_handler_proc(irq, new);return 0;...return retval;

3.6 中断处理流程

3.6.1 ARM32 中断向量表

.section .vectors, "ax", %progbits
.L__vectors_start:W(b)	vector_rst /* 复位 */W(b)	vector_und /* 未定义指令异常向量表指针 *//** @ arch/arm/kernel/vmlinux.lds.S** __stubs_start = .;* .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {* 		*(.stubs)* }* . = __stubs_start + SIZEOF(.stubs);* __stubs_end = .;*/W(ldr)	pc, .L__vectors_start + 0x1000 /* 软中断向量(swi) */W(b)	vector_pabtW(b)	vector_dabtW(b)	vector_addrexcptnW(b)	vector_irq /* IRQ */W(b)	vector_fiq /* FIQ */.data.align	2

发生中断后,进入 IRQ 中断处理入口 handle_arch_irq

.data.align	2vector_stub	irq, IRQ_MODE, 4.long	__irq_usr			@  0  (USR_26 / USR_32).long	__irq_invalid			@  1  (FIQ_26 / FIQ_32).long	__irq_invalid			@  2  (IRQ_26 / IRQ_32).long	__irq_svc			@  3  (SVC_26 / SVC_32).long	__irq_invalid			@  4.long	__irq_invalid			@  5.long	__irq_invalid			@  6.long	__irq_invalid			@  7.long	__irq_invalid			@  8.long	__irq_invalid			@  9.long	__irq_invalid			@  a.long	__irq_invalid			@  b.long	__irq_invalid			@  c.long	__irq_invalid			@  d.long	__irq_invalid			@  e.long	__irq_invalid			@  f.align	5
__irq_svc:svc_entryirq_handler#ifdef CONFIG_PREEMPTldr	r8, [tsk, #TI_PREEMPT]		@ get preempt countldr	r0, [tsk, #TI_FLAGS]		@ get flagsteq	r8, #0				@ if preempt count != 0movne	r0, #0				@ force flags to 0tst	r0, #_TIF_NEED_RESCHEDblne	svc_preempt
#endifsvc_exit r5, irq = 1			@ return from exceptionUNWIND(.fnend		)
ENDPROC(__irq_svc).macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLERldr	r1, =handle_arch_irq /* r1 = gic_handle_irq() */mov	r0, spbadr	lr, 9997fldr	pc, [r1]
#elsearch_irq_handler_default
#endif
9997:.endm

现在 handle_arch_irq 指针的值,正是在 GIC 初始化阶段设置的 gic_handle_irq()

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{u32 irqstat, irqnr;struct gic_chip_data *gic = &gic_data[0];void __iomem *cpu_base = gic_data_cpu_base(gic);do {/* 读取硬件中断号 */irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (likely(irqnr > 15 && irqnr < 1020)) { /* 处理 PPI, SPI */...handle_domain_irq(gic->domain, irqnr, regs);continue;}if (irqnr < 16) { /* 处理 SGI */writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);...#ifdef CONFIG_SMP	handle_IPI(irqnr, regs);#endifcontinue;}} while (1);
}static inline int handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
{return __handle_domain_irq(domain, hwirq, true, regs);
}int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,bool lookup, struct pt_regs *regs)
{struct pt_regs *old_regs = set_irq_regs(regs);unsigned int irq = hwirq;int ret = 0;irq_enter();#ifdef CONFIG_IRQ_DOMAINif (lookup)irq = irq_find_mapping(domain, hwirq); /* 查找 硬件中断号 @hwirq 对应的 Linux 虚拟中断号 */
#endif...generic_handle_irq(irq); /* 处理中断 */...irq_exit(); /* 软中断, RCU 等等处理 */set_irq_regs(old_regs);return ret;
}int generic_handle_irq(unsigned int irq)
{struct irq_desc *desc = irq_to_desc(irq);if (!desc)return -EINVAL;generic_handle_irq_desc(desc);return 0;
}static inline void generic_handle_irq_desc(struct irq_desc *desc)
{/** GIC v1 芯片:* . PPI: handle_percpu_devid_irq()* . SPI: handle_fasteoi_irq()** 级联 GPIO 中断控制器:* handle_fasteoi_irq(): 处理 LEVEL 触发中断(高低电平)* handle_edge_irq(): 处理 EDGE 触发中断(上升、下降沿)*/desc->handle_irq(desc);
}/* 处理 PPI 中断 */
void handle_percpu_devid_irq(struct irq_desc *desc)
{struct irq_chip *chip = irq_desc_get_chip(desc);struct irqaction *action = desc->action;unsigned int irq = irq_desc_get_irq(desc);irqreturn_t res;__kstat_incr_irqs_this_cpu(desc); /* 更新中断 @irq 次数统计 */if (chip->irq_ack) /* 中断芯片 ACK 处理 */chip->irq_ack(&desc->irq_data);/* 调用 request_irq(), request_thread_irq() 注册的接口 */res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));if (chip->irq_eoi) /* 中断芯片 EOI 处理 */chip->irq_eoi(&desc->irq_data);
}/* 处理 SPI 中断 */
void handle_fasteoi_irq(struct irq_desc *desc)
{struct irq_chip *chip = desc->irq_data.chip;...kstat_incr_irqs_this_cpu(desc); /* 更新中断当前 CPU 的统计数据:SPI 中断可能派发到任一 CPU 核上 */if (desc->istate & IRQS_ONESHOT) /* 中断标记为只触发一次,触发一次中断后,屏蔽它 */mask_irq(desc);preflow_handler(desc);handle_irq_event(desc);...
}irqreturn_t handle_irq_event(struct irq_desc *desc)
{rqreturn_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);raw_spin_lock(&desc->lock);irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); /* 中断处理完成,清除 "中断正在处理中" 标记 */return ret;
}irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{irqreturn_t retval;unsigned int flags = 0;retval = __handle_irq_event_percpu(desc, &flags);add_interrupt_randomness(desc->irq_data.irq, flags); /* 中断处理为 CRND 随机数做贡献 */...return retval;
}irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{record_irq_time(desc);/* 调用注册到中端 irq 的所有 action handler */for_each_action_of_desc(desc, action) {irqreturn_t res;res = action->handler(irq, action->dev_id); /* 调用 (SGI) 中断处理接口 */switch (res) {/* 线程化中断,主接口应该返回 IRQ_WAKE_THREAD , * 譬如线程化中断的默认主接口 irq_default_primary_handler()*/case IRQ_WAKE_THREAD:__irq_wake_thread(desc, action); /* 唤醒中断线程处理中断 *//* Fall through to add to randomness */case IRQ_HANDLED: /* 中断处理完毕 */*flags |= action->flags;break;default:break;}retval |= res;}return retval;
}

4. 中断控制器级联

中断控制器级联最常见的情形,是将 GPIO 中断控制器,级联到 GIC ,拓扑结构如下:

GPIO 中断信号 -> GPIO中断控制器 -> GIC -> CPU

我们还是以一个 GPIO pinctrl 驱动为例,来分析级联中断的初始化、中断接口注册、中断处理的3个过程。

4.1 级联中断控制器的初始化

先看级联 GPIO 中断控制器的 DTS 配置:

pio: pinctrl@01c20800 {...interrupts = ,;...gpio-controller;#gpio-cells = <3>;interrupt-controller;#interrupt-cells = <3>;...
};

从 DTS 看到,这是一个 GPIO 中断控制器,它输出2个中断信号11,17GIC。看一下 级联的GPIO中断控制器 初始化细节:

/* * 节选自全志 H3 的 gpiochip/pinctrl 复合驱动,代码均为开源代码。* 为方便理解,对逻辑上进行些改动,以突出中断相关部分。*/
struct sunxi_pinctrl {...struct gpio_chip		*chip;...struct irq_domain		*domain;int				virq[2]; /* 记录 DTS 的配置的中断 11,17 的 Linux 虚拟中断号 */...
};/* GPIO 中断控制器 中断域接口 */
static const struct irq_domain_ops sunxi_pinctrl_irq_domain_ops = {.xlate		= sunxi_pinctrl_irq_of_xlate,
};int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,const struct sunxi_pinctrl_desc *desc,unsigned long variant)
{struct sunxi_pinctrl *pctl;int i;pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);.../* 设置将 GPIO PIN ID 映射为其对应 Linux 虚拟中断号 的接口 */pctl->chip->to_irq = sunxi_pinctrl_gpio_to_irq,.../* * DTS 配置的 GPIO 中断控制器输出到 GIC 的2个中断信号的解析* 和映射:* interrupts = ,*			    ;*/pctl->virq[0] = platform_get_irq(pdev, 0);pctl->virq[1] = platform_get_irq(pdev, 1);/* 创建 GPIO 中断控制器 的中断域 irq_domain */	pctl->domain = irq_domain_add_linear(node, 64/*64个GPIO,64个中断*/,&sunxi_pinctrl_irq_domain_ops, pctl);/* 为 GPIO 中断控制器的所有64个GPIO硬件中断,映射分配 Linux虚拟中断号 */for (i = 0; i < 64; i++) {int virq = irq_create_mapping(pctl->domain, i);/* 设置 GPIO 中断 的 【中断芯片底层操作接口】 和 【沿触发模式 中断处理接口】 */irq_set_chip_and_handler(virq, &sunxi_pinctrl_edge_irq_chip,handle_edge_irq);/* 绑定 GPIO 中断 的 中断芯片底层数据 @pctl */irq_set_chip_data(virq, pctl);}/* 我们还要设置 GPIO 中断控制器输出到 GIC 信号的中断处理接口,默认的接口不是我们想要的。 */irq_set_chained_handler_and_data(pctl->virq[0],sunxi_pinctrl_irq_handler, pctl);irq_set_chained_handler_and_data(pctl->virq[1],sunxi_pinctrl_irq_handler, pctl);
}/* 硬件中断号 到 Linux虚拟中断号的 映射过程 */
unsigned int irq_create_mapping(struct irq_domain *domain,irq_hw_number_t hwirq)
{struct device_node *of_node;int virq;...of_node = irq_domain_get_of_node(domain); /* 获取中断域的 DTS 节点 */virq = irq_find_mapping(domain, hwirq); /* 查找硬件中断号 @hwirq 在中断域 @domain 中的映射 */if (virq) { /* 硬件中断号 @hwirq 在中断域 @domain 中已经映射过了 */return virq; /* 已经映射过了,返回映射的 Linux 虚拟中断号 */}/** 为硬件中断号 @hwirq 映射一个虚拟中断号,* 并分配并初始化一个中断描述符,插入全局中断描述符基树。*/virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);/* * . 建立中断域 @domain 内 【硬件中断号 @hwirq】 到 【Linux *   虚拟中断号 @virq】 的映射 ;* . 设定中断 @virq 的中断域为 @domain 。*/if (irq_domain_associate(domain, virq, hwirq)) {irq_free_desc(virq);return 0;}return virq; /* 返回 @hwirq 映射的 Linux虚拟中断号 @virq */
}

到此,我们已经初始化好了级联的 GPIO 中断控制器,接下来看 GPIO 中断的使用过程。

4.2 级联中断使用范例

4.2.1 例1:USB PHY 芯片 GPIO 中断使用

先看 DTS 配置:

usbphy: phy@01c19400 {compatible = "allwinner,sun8i-h3-usb-phy";...usb0_id_det-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */...
};

PG12 中断接口注册流程:

data->id_det_gpio = devm_gpiod_get_optional(dev, "usb0_id_det",GPIOD_IN);
...
data->id_det_irq = gpiod_to_irq(data->id_det_gpio); /* 将 GPIO PIN 映射到 Linux 虚拟中断号 */
/* 注册 PG12 GPIO 中断处理接口 */
ret = devm_request_irq(dev, data->id_det_irq,sun4i_usb_phy0_id_vbus_det_irq,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"usb0-id-det", data);
...

gpiod_to_irq() 有点新鲜,但很常见,看一下它的具体细节:

int gpiod_to_irq(const struct gpio_desc *desc)
{struct gpio_chip *chip;int offset;chip = desc->gdev->chip;offset = gpio_chip_hwgpio(desc);if (chip->to_irq) {int retirq = chip->to_irq(chip, offset); /* sunxi_pinctrl_gpio_to_irq() */...return retirq;}
}static int sunxi_pinctrl_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{struct sunxi_pinctrl *pctl = gpiochip_get_data(chip);struct sunxi_desc_function *desc;unsigned pinnum = pctl->desc->pin_base + offset;unsigned irqnum;...desc = sunxi_pinctrl_desc_find_function_by_pin(pctl, pinnum, "irq");if (!desc)return -EINVAL;irqnum = desc->irqbank * IRQ_PER_BANK + desc->irqnum;return irq_find_mapping(pctl->domain, irqnum); /* 返回 硬件中断号 @irqnum 映射的 Linux 软件中断号 */
}

4.2.2 例2:无线网卡唤醒 GPIO 中断使用

先看 DTS 配置:

brcmf: bcrmf@1 {reg = <1>;compatible = "brcm,bcm4329-fmac";interrupt-parent = <&pio>;interrupts = <6 10 IRQ_TYPE_LEVEL_LOW>; /* PG10 / EINT10 */interrupt-names = "host-wake";};

注意,interrupt-parent = <&pio>; 表明该设备的中断信号是传递给 GPIO 中断控制器 pio 的。看看中断配置解析映射的过程:

if (!of_find_property(np, "interrupts", NULL))return;irq = irq_of_parse_and_map(np, 0); /* hwirq ==> irq */
irqf = irqd_get_trigger_type(irq_get_irq_data(irq));sdio->oob_irq_supported = true;
sdio->oob_irq_nr = irq;
sdio->oob_irq_flags = irqf;
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{struct of_phandle_args oirq;/** oirq.np = &pio;* oirq.args_count = 3;* oirq.args[0..2] = {6, 10, IRQ_TYPE_LEVEL_LOW};*/if (of_irq_parse_one(dev, index, &oirq))return 0;return irq_create_of_mapping(&oirq);
}unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{struct irq_fwspec fwspec;of_phandle_args_to_fwspec(irq_data, &fwspec);return irq_create_fwspec_mapping(&fwspec);
}unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{struct irq_domain *domain;if (fwspec->fwnode) {/* 找到中断挂接中断控制器的 irq_domain */domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);} else {...}/** 调用 irq_domain (中断控制器)的中断配置项解析接口,* 提取 硬件中断号 和 中断触发类型 信息: * @fwspec ==> {@hwirq, @type} */if (irq_domain_translate(domain, fwspec, &hwirq, &type))return 0;if (irq_domain_is_hierarchy(domain)) {...} else {/* Create mapping *//* 建立 硬件中断号 到 Linux虚拟中断号 的映射 */virq = irq_create_mapping(domain, hwirq);if (!virq)return virq;}irqd_set_trigger_type(irq_data, type); /* 设置中断触发类型: RISING, FALLING, HIGH, LOW */return virq; /* 返回分配的 Linux IRQ 虚拟中断号 */
}static int irq_domain_translate(struct irq_domain *d,struct irq_fwspec *fwspec,irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY...
#endif	if (d->ops->xlate) /* 级联的 GPIO 中断控制器中断域的接口 sunxi_pinctrl_irq_of_xlate() */return d->ops->xlate(d, to_of_node(fwspec->fwnode),fwspec->param, fwspec->param_count,hwirq, type);...
}static int sunxi_pinctrl_irq_of_xlate(struct irq_domain *d,struct device_node *node,const u32 *intspec,unsigned int intsize,unsigned long *out_hwirq,unsigned int *out_type)
{struct sunxi_pinctrl *pctl = d->host_data;struct sunxi_desc_function *desc;int pin, base;...base = PINS_PER_BANK * intspec[0]; /* intspec[0]: GPIO bank */pin = pctl->desc->pin_base + base + intspec[1]; /* intspec[1]: GPIO pin *//* 查询 GPIO PIN 的中断属性信息 */desc = sunxi_pinctrl_desc_find_function_by_pin(pctl, pin, "irq");if (!desc)return -EINVAL;*out_hwirq = desc->irqbank * PINS_PER_BANK + desc->irqnum; /* 返回硬件中断号 */*out_type = intspec[2]; /* 解析中断触发类型 */return 0;
}

中断信号处理接口注册:

ret = request_irq(pdata->oob_irq_nr, brcmf_sdiod_oob_irqhandler,pdata->oob_irq_flags, "brcmf_oob_intr",&sdiodev->func1->dev);

4.3 级联中断的处理过程

前一部分和章节 3.6 的流程一样:

gic_handle_irq()handle_domain_irq(gic->domain, irqnr, regs)__handle_domain_irq(domain, hwirq, true, regs)struct pt_regs *old_regs = set_irq_regs(regs);unsigned int irq = hwirq;int ret = 0;irq_enter();irq = irq_find_mapping(domain, hwirq); /* 查找 硬件中断号 @hwirq 对应的 Linux 虚拟中断号 */generic_handle_irq(irq);struct irq_desc *desc = irq_to_desc(irq);generic_handle_irq_desc(desc);desc->handle_irq(desc) = sunxi_pinctrl_irq_handler()irq_exit(); /* 软中断, RCU 等等处理 */set_irq_regs(old_regs);return ret;

sunxi_pinctrl_irq_handler() 处开始不同,继续看 sunxi_pinctrl_irq_handler() 细节:

static void sunxi_pinctrl_irq_handler(struct irq_desc *desc)
{unsigned int irq = irq_desc_get_irq(desc);...val = readl(pctl->membase + reg); /* 读取触发的 GPIO 中断 */if (val) {int irqoffset;chained_irq_enter(chip, desc);for_each_set_bit(irqoffset, &val, IRQ_PER_BANK) { /* 处理所有触发的 GPIO 中断 */int pin_irq = irq_find_mapping(pctl->domain,bank * IRQ_PER_BANK + irqoffset); /* 找到硬件中断的 Linux 虚拟中断号 *//* okay, 这里没有递归形成。 */generic_handle_irq(pin_irq); /* 处理一个 GPIO 中断 */}chained_irq_exit(chip, desc);}
}int generic_handle_irq(unsigned int irq)
{struct irq_desc *desc = irq_to_desc(irq);if (!desc)return -EINVAL;generic_handle_irq_desc(desc);return 0;
}static inline void generic_handle_irq_desc(struct irq_desc *desc)
{/** GIC v1 芯片:* . PPI: handle_percpu_devid_irq()* . SPI: handle_fasteoi_irq()* . 级联中断信号: sunxi_pinctrl_irq_handler()** 级联 GPIO 中断控制器:* handle_fasteoi_irq(): 处理 LEVEL 触发中断(高低电平)* handle_edge_irq(): 处理 EDGE 触发中断(上升、下降沿)*/desc->handle_irq(desc);
}/* 处理 GPIO 沿触发方式中断 */
void handle_edge_irq(struct irq_desc *desc)
{...kstat_incr_irqs_this_cpu(desc); /* 中断数据统计 */...desc->irq_data.chip->irq_ack(&desc->irq_data); /* sunxi_pinctrl_irq_ack() */do {...handle_irq_event(desc);}  while ((desc->istate & IRQS_PENDING) &&!irqd_irq_disabled(&desc->irq_data));...
}irqreturn_t handle_irq_event(struct irq_desc *desc)
{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);raw_spin_lock(&desc->lock);irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); /* 中断处理完成,清除 "中断正在处理中" 标记 */return ret;
}irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{irqreturn_t retval;unsigned int flags = 0;retval = __handle_irq_event_percpu(desc, &flags);add_interrupt_randomness(desc->irq_data.irq, flags); /* 中断处理为 CRND 随机数做贡献 */...return retval;
}irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{/* 调用注册到中端 irq 的所有 action handler */for_each_action_of_desc(desc, action) {irqreturn_t res;/* sun4i_usb_phy0_id_vbus_det_irq() / brcmf_sdiod_oob_irqhandler() */res = action->handler(irq, action->dev_id);switch (res) {/* 线程化中断,主接口应该返回 IRQ_WAKE_THREAD , * 譬如线程化中断的默认主接口 irq_default_primary_handler()*/case IRQ_WAKE_THREAD:__irq_wake_thread(desc, action); /* 唤醒中断线程处理中断 *//* Fall through to add to randomness */case IRQ_HANDLED: /* 中断处理完毕 */*flags |= action->flags;break;default:break;}retval |= res;}return retval;
}

4.3.1 GPIO 中断电平触发模式切换

前面看到的GPIO中断控制器处理流程,是关于沿触发方式的中断处理,那 GPIO的 电平触发方式 的处理是怎样的?首先,要将 GPIO 的中断触发方式进行切换,用户空间通过向 /sys/class/gpio/gpioN/edge 写入 risingfalling ,我们例子中的最终会调用接口 sunxi_pinctrl_irq_set_type()

static int sunxi_pinctrl_irq_set_type(struct irq_data *d, unsigned int type)
{struct sunxi_pinctrl *pctl = irq_data_get_irq_chip_data(d);u32 reg = sunxi_irq_cfg_reg(d->hwirq, pctl->desc->irq_bank_base);u8 index = sunxi_irq_cfg_offset(d->hwirq);unsigned long flags;u32 regval;u8 mode;switch (type) {case IRQ_TYPE_EDGE_RISING:mode = IRQ_EDGE_RISING;break;case IRQ_TYPE_EDGE_FALLING:mode = IRQ_EDGE_FALLING;break;case IRQ_TYPE_EDGE_BOTH:mode = IRQ_EDGE_BOTH;break;case IRQ_TYPE_LEVEL_HIGH:mode = IRQ_LEVEL_HIGH;break;case IRQ_TYPE_LEVEL_LOW:mode = IRQ_LEVEL_LOW;break;default:return -EINVAL;}raw_spin_lock_irqsave(&pctl->lock, flags);if (type & IRQ_TYPE_LEVEL_MASK)irq_set_chip_handler_name_locked(d, &sunxi_pinctrl_level_irq_chip,handle_fasteoi_irq, NULL);elseirq_set_chip_handler_name_locked(d, &sunxi_pinctrl_edge_irq_chip,handle_edge_irq, NULL);...raw_spin_unlock_irqrestore(&pctl->lock, flags);return 0;
}

从上面的逻辑可以看出,处理电平模式 IRQ_TYPE_LEVEL_HIGHIRQ_TYPE_LEVEL_LOW ,最终会调用 handle_fasteoi_irq() 进行中断处理。

5. SGI 中断处理

前面我们分析 SGI 中断的处理,现在来看一下:

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{u32 irqstat, irqnr;struct gic_chip_data *gic = &gic_data[0];void __iomem *cpu_base = gic_data_cpu_base(gic);do {/* 读取硬件中断号 */irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (likely(irqnr > 15 && irqnr < 1020)) { /* 处理 PPI, SPI */...}if (irqnr < 16) { /* 处理 SGI */writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);...#ifdef CONFIG_SMP/* SGI 中断用于处理器间的通信,所以只有多核处理器架构下才有 */smp_rmb();handle_IPI(irqnr, regs);#endifcontinue;	}break;} while (1);
}void handle_IPI(int ipinr, struct pt_regs *regs)
{unsigned int cpu = smp_processor_id();struct pt_regs *old_regs = set_irq_regs(regs);if ((unsigned)ipinr < NR_IPI) {trace_ipi_entry_rcuidle(ipi_types[ipinr]);__inc_irq_stat(cpu, ipi_irqs[ipinr]);}switch (ipinr) {case IPI_WAKEUP:break;#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCASTcase IPI_TIMER:irq_enter();tick_receive_broadcast();irq_exit();break;
#endifcase IPI_RESCHEDULE: scheduler_ipi();break;case IPI_CALL_FUNC:irq_enter();generic_smp_call_function_interrupt();irq_exit();break;case IPI_CPU_STOP:irq_enter();ipi_cpu_stop(cpu);irq_exit();break;#ifdef CONFIG_IRQ_WORKcase IPI_IRQ_WORK:irq_enter();irq_work_run();irq_exit();break;
#endifcase IPI_COMPLETION:irq_enter();ipi_complete(cpu);irq_exit();break;case IPI_CPU_BACKTRACE:printk_nmi_enter();irq_enter();nmi_cpu_backtrace(regs);irq_exit();printk_nmi_exit();break;default:pr_crit("CPU%u: Unknown IPI message 0x%x\n",cpu, ipinr);break;}if ((unsigned)ipinr < NR_IPI)trace_ipi_exit_rcuidle(ipi_types[ipinr]);set_irq_regs(old_regs);
}

6. 中断共享

在调用 request_irq() 传递 IRQF_SHARED 标记,和自身独特的数据指针标记。 这里不讨论中断共享的细节,感兴趣的读者可自行阅读代码分析。

7. 中断信息用户空间接口

/proc/irq/default_smp_affinity/proc/irq//affinity_hint
/proc/irq//effective_affinity
/proc/irq//effective_affinity_list
/proc/irq//node
/proc/irq//smp_affinity
/proc/irq//smp_affinity_list
/proc/irq//spurious/sys/kernel/irq//actions
/sys/kernel/irq//chip_name
/sys/kernel/irq//hwirq
/sys/kernel/irq//name
/sys/kernel/irq//per_cpu_count
/sys/kernel/irq//type/proc/interrupts...

看一个 cat /proc/interrupts 输出:
请添加图片描述

相关内容

热门资讯

今年我省粮食产量达515.56... (来源:辽宁日报)转自:辽宁日报 图为在中储粮(盘锦)储运有限公司,装运粮食的重型卡车排起长队...
国家发展改革委部署促进投资止跌... (来源:辽宁日报)转自:辽宁日报 新华社北京12月13日电 (记者魏玉坤) 记者13日从全国发展和改...
江苏省实施《中华人民共和国森林... (来源:新华日报) 目 录 第一章 总则 第二章 森林、林木和林地权属管理...
姜堰数字化产品讲“活”理论 (来源:新华日报) □ 本报记者 卢佳乐 通讯员 姜宣 “王教授,您约我‘喝茶论道’,...
联合国维和部队在苏丹遇袭 6人... 转自:财联社【联合国维和部队在苏丹遇袭 6人死亡】财联社12月14日电,当地时间13日,苏丹武装部队...