Linux中断子系统6

Linux中断子系统6(基于Linux6.6)---中断控制器(GIC)中断号的映射

一、前情回顾

上篇大概介绍了中断控制器的初始化,但实际上还没有形成完整的GIC控制器的框架,本文将介绍从管理机制、GIC结构、GIC驱动、驱动流程、数据结构、中断框架、以及中断号映射方面说明GIC的中断控制器的方面。

二、Linux 中断管理机制

前面在介绍中断相关的基础知识时候,知道Linux 内核中断管理可以分为 4 层:

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

以ARM通用中断控制器(GIC V3)为例说明。

GIC V3.0逻辑图:

如图所示,当中断发生的时候,通过中断控制器,发出中断给 CPU。GIC通过 AMBA(Advanced Microcontroller Bus Architecture)片上总线连接到一个或者多个 ARM 处理器上。从图中可以看出 GIC 是联系外设中断和 CPU 的桥梁,也是各 CPU 之间中断互联的通道(也带有管理功能),它负责检测、管理、分发中断。简化图,如下:

根据简化图,ARM CPU 对外的连接只有 2 个中断: IRQ 和 FIQ ,相对应的处理模式分别是一般中断(IRQ )处理模式和快速中断(FIQ )处理模式。所以 GIC 最后要把中断汇集成 2 条线,与 CPU 对接。

如下图,是GIC中断控制器的结构:

GIC 中断控制器可以分为:仲裁单元和 CPU 接口模块。状态机有:inactive,pending,active,active and pending。

GIC v3主要这几部分组成:Distributor、CPU interface、Redistributor、ITS。 GIC v3中,将cpu interface从GIC中抽离,放入到了cpu中,cpu interface通过AXI Stream,与gic进行通信。 当GIC要发送中断,GIC通过AXI stream接口,给cpu interface发送中断命令,cpu interface收到中断命令后,根据中断线映射配置,决定是通过IRQ还是FIQ管脚,向cpu发送中断。

Distributor

Distributor用于SPI(Shared peripheral interrupts)中断的管理,具有仲裁和分发的作用,会将中断发送给Redistributor。

Distributor提供了一些编程接口或者说是寄存器,我们可以通过Distributor的编程接口实现如下操作, 下面是Distributor主要功能:

  • 全局的开启或关闭CPU的中断。

  • 控制任意一个中断请求的开启和关闭。

  • 中断优先级控制。

  • 指定中断发生时将中断请求发送到那些CPU。

  • interrupt属性设定,设置每个”外部中断”的触发方式(边缘触发或者电平触发)。

CPU interface简介

CPU接口为链接到GIC的处理器提供接口,与Distributor类似它也提供了一些编程接口,我们可以通过CPU接口实现以下功能(列举了几项,详细参考arm手册):

  • 打开或关闭 CPU interface 向连接的 CPU assert 中断事件。

  • 中断确认(acknowledging an interrupt)。

  • 中断处理完毕的通知。

  • 为处理器设置中断优先级掩码。

  • 设置处理器的抢占策略

  • 确定挂起的中断请求中优先级最高的中断请求

简单来说,CPU接口可以开启或关闭发往CPU的中断请求,CPU中断开启后只有优先级高于 “中断优先级掩码”的中断请求才能被发送到CPU。 在任何时候CPU都可以从(CPU接口寄存器)读取当前活动的最高优先级。

Redistributor简介

GICv3中,Redistributor管理SGI,PPI,LPI中断,然后将中断发送给CPU interface,包括下面功能:

  • 启用和禁用 SGI 和 PPI。

  • 设置 SGI 和 PPI 的优先级。

  • 将每个 PPI 设置为电平触发或边缘触发。

  • 将每个 SGI 和 PPI 分配给中断组。

  • 控制 SGI 和 PPI 的状态。

  • 内存中数据结构的基址控制,支持 LPI 的相关中断属性和挂起状态。

  • 电源管理支持。

ITS(Interrupt translation service)

ITS 是GIC v3架构中的一种可选硬件机制,ITS提供了一种将基于消息的中断转换为LPI的软件机制,它是 在支持LPI的配置中可选地支持。ITS接收LPI中断,进行解析,然后发送到对应的redistributor,再由redistributor将中断信息,发送给cpu interface。

中断状态

  • lnactive: 中断源没有发送中断;
  • Pending: 中断源已经发送中断,等待处理器处理;
  • Active: 处理器已经确认中断,正在处理;
  • Active and Pending: 处理器正在处理中断,相同的中断源 * 又发送了一个中断。

当 GIC 接收到一个中断请求,将其状态设置为 Pending。重新产生一个挂起状态的中断不影响该中断状态。

GIC v3中断类型

GIC v3处理的不同种中断源:

  • SGI (Software Generated Interrupt):软件触发的中断。软件可以通过写 GICD_SGIR寄存器来触发一个中断事件,一般用于核间通信,内核中的IPI:inter-processor interrupts 就是基于SGI。

  • PPI (Private Peripheral Interrupt):私有外设中断,该终端来自于外设,被特定的核处理。GIC 是支持多核的,每个核有自己独有的中断。

  • SPI (Shared Peripheral Interrupt):共享外设中断,所有核共享的中断。中断产生后,可以分发到某一个CPU上。

  • LPI (Locality-specific Peripheral Interrupt):LPI是在 GICv3中引入的,并且与其他三种类型的中断具有非常不同的编程模型,LPI是基于消息的中断,它们的配置保存在表中而不是寄存器。

每个中断都有一个ID号标识,称为INTID,下面是ARM GIC v3手册的中断号规定:

中断ID和中断类型

INTID

中断类型

说明

0-15

SGI

每个CPU核都有自己的16个

16-31

PPI

每个CPU核都有自己的16个

32-1019

SPI

例如GPIO 中断、串口中断等这些外部中断,具体由SOC厂商定义

1020-1023

特殊中断号

1024 - 8191

保留

8192-MAX

LPI

中断处理顺序

                          ┌─────────────────────────────────────────┐
                          │  ① GIC 决定该中断是否使能                  │
                          └─────────────────────────────────────────┘
                                   │
                           如果中断没有被使能
                                   │
                        ┌──────────┴──────────┐
                        │  对 GIC 无影响       │
                        └─────────────────────┘
                                   │
                                   ▼
                   ┌─────────────────────────────────────────┐
                   │  ② 对于每个 Pending 中断,GIC 决定目标处理器 │
                   └─────────────────────────────────────────┘
                                   │
                                   ▼
                   ┌─────────────────────────────────────────┐
                   │  ③ Distributor 根据每个中断的优先级决定最高优先级 |
                   │     中断,并将其传递给目标 CPU Interface   │
                   └─────────────────────────────────────────┘
                                   │
                                   ▼
                   ┌─────────────────────────────────────────┐
                   │  ④ CPU Interface 判断是否有足够的优先级        │
                   │     将中断请求发给 CPU                     │
                   └─────────────────────────────────────────┘
                                   │
                                   ▼
                   ┌─────────────────────────────────────────┐
                   │  ⑤ CPU 开始处理中断,读取 GICC_IAR 并获取   │
                   │     中断 ID,查找中断处理程序               │
                   └─────────────────────────────────────────┘
                                   │
                                   ▼
                  ┌────────────────────────────────────────────┐
                  │  中断状态变化:                               │
                  │  a) 如果中断持续挂起或再次发生,中断从 Pending │
                  │     转化为 Pending & Active 状态               │
                  │  b) 否则,中断状态从 Pending 转为 Active     │
                  └────────────────────────────────────────────┘
                                   │
                                   ▼
                    ┌─────────────────────────────────────────┐
                    │  ⑥ 中断处理完成后通知 GIC,通过 EOIR 寄存器 │
                    │     写入有效值,并接着向 GICC_DIR 写入值  │
                    └─────────────────────────────────────────┘
                                   │
                                   ▼
                          ┌──────────────────────────────┐
                          │     中断完成处理              │
                          └──────────────────────────────┘

 

中断状态机

  1. 添加挂起状态(A1、A2)
    对于一个 SGI,发生以下 2 种情况的 1 种:

    • 软件写 GICD_SGIR 寄存器,指定目标处理器。
    • 目标处理器上软件写 GICD_SPENDSGIRn 寄存器。

    对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
    * 外设发出一个中断请求信号。
    * 软件写 GICD_ISPENDRn 寄存器。

  2. 删除挂起状态(B1、B2)
    对于 SGI,目标处理器写 GICD_CPENDSGIRn 寄存器, 对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
    * 电平触发类型中断,信号取消。
    * 边沿触发类型中断,软件写 GICD_ICPENDRn 寄存器。

  3. 挂起到激活(C)
    如果中断使能,并且高优先级,软件从 GICC_IAR 寄存器读取时发生状态改变。

  4. 挂起到激活和挂起(D)
    对于 SGI,这种转变发生在以下任一情况下:
    * 将 SGI 状态设置为挂起的写入操作与读取 GICC_IAR 几乎同时发生。
    * 当多个挂起的 SGI 具有相同 ID 时,并且它们来自同一个源处理器并指向同一个处理器。其中一个 SGI 状态变为激活(C),其他 SGI 状态变为激活和挂起(D)。
    对于 SPI 或 PPI,满足以下所有条件,则发生这种转换。
    * 中断开启。
    * 软件读取 GICC_IAR,读操作将激活状态添加到中断中。
    此外,还应满足以下条件之一:
    * 对于电平触发中断,中断信号保持。通常都是这样,因为外设直到处理器处理完中断后才会取消触发信号。
    * 对于边沿触发中断,是否发生此转换取决于读取 GICC_IAR 的时间(中断再次触发,上一次未处理),读取 GICC_IAR 可能会转换到 C,后面可能会转换到 A2。

  5. 删除激活状态(E1、E2)
    软件写入 GICC_EOIR 或 GICC_DIR 来停用中断,

三、GIC 驱动

3.1、节点信息

arch/arm64/boot/dts/rockchip/rk3568.dtsi

	gic: interrupt-controller@fd400000 {
		compatible = "arm,gic-v3";
		#interrupt-cells = <3>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		interrupt-controller;

		reg = <0x0 0xfd400000 0 0x10000>, /* GICD */
		      <0x0 0xfd460000 0 0xc0000>; /* GICR */
		interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
		its: interrupt-controller@fd440000 {
			compatible = "arm,gic-v3-its";
			msi-controller;
			#msi-cells = <1>;
			reg = <0x0 0xfd440000 0x0 0x20000>;
		};
	};

前面文章说过,当内核启动后会将该节点解析成 device_node 结构。

3.2、驱动流程

下面代码说明:

void start_kernel(void)
{
...
	init_IRQ();初始化中断
...
}

void __init init_IRQ(void)
{
	int ret;

#ifdef CONFIG_IRQSTACKS
	init_irq_stacks();
#endif

	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
		irqchip_init();
	else
		machine_desc->init_irq();

	if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
	    (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
		if (!outer_cache.write_sec)
			outer_cache.write_sec = machine_desc->l2c_write_sec;
		ret = l2x0_of_init(machine_desc->l2c_aux_val,
				   machine_desc->l2c_aux_mask);
		if (ret && ret != -ENODEV)
			pr_err("L2C: failed to init: %d\n", ret);
	}

	uniphier_cache_init();
}

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


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;

...
	while (!list_empty(&intc_desc_list)) {
		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
			int ret;

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

			list_del(&desc->list);

			of_node_set_flag(desc->dev, OF_POPULATED);

			pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
				 desc->dev,
				 desc->dev, desc->interrupt_parent);
			ret = desc->irq_init_cb(desc->dev,
						desc->interrupt_parent);
			if (ret) {
				pr_err("%s: Failed to init %pOF (%p), parent %p\n",
				       __func__, desc->dev, desc->dev,
				       desc->interrupt_parent);
				of_node_clear_flag(desc->dev, OF_POPULATED);
				kfree(desc);
				continue;
			}
...
}

typedef int (*of_irq_init_cb_t)(struct device_node *, struct device_node *);


static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
	phys_addr_t dist_phys_base;
	void __iomem *dist_base;
	struct redist_region *rdist_regs;
	struct resource res;
	u64 redist_stride;
	u32 nr_redist_regions;
	int err, i;

	dist_base = gic_of_iomap(node, 0, "GICD", &res);
	if (IS_ERR(dist_base)) {
		pr_err("%pOF: unable to map gic dist registers\n", node);
		return PTR_ERR(dist_base);
	}
...

	err = gic_init_bases(dist_phys_base, dist_base, rdist_regs,
			     nr_redist_regions, redist_stride, &node->fwnode);
	if (err)
		goto out_unmap_rdist;
		
...
}

static int __init gic_init_bases(phys_addr_t dist_phys_base,
				 void __iomem *dist_base,
				 struct redist_region *rdist_regs,
				 u32 nr_redist_regions,
				 u64 redist_stride,
				 struct fwnode_handle *handle)
{
	u32 typer;
	int err;

...
	if (!(gic_data.flags & FLAGS_WORKAROUND_CAVIUM_ERRATUM_38539))
		gic_data.rdists.gicd_typer2 = readl_relaxed(gic_data.dist_base + GICD_TYPER2);

	gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
						 &gic_data);
						 
...
	gic_dist_init();
	gic_cpu_init();
	gic_smp_init();
	gic_cpu_pm_init();
...
}


static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data);
}


static void __init gic_smp_init(void)
{
	struct irq_fwspec sgi_fwspec = {
		.fwnode		= gic_data.fwnode,
		.param_count	= 1,
	};
	int base_sgi;

	cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
				  "irqchip/arm/gicv3:starting",
				  gic_starting_cpu, NULL);

	/* Register all 8 non-secure SGIs */
	base_sgi = irq_domain_alloc_irqs(gic_data.domain, 8, NUMA_NO_NODE, &sgi_fwspec);
	if (WARN_ON(base_sgi <= 0))
		return;

	set_smp_ipi_range(base_sgi, 8);
}

前面说过,GIC 的工作,本质上是由中断信号来驱动,因此驱动本身的工作就是完成各类信息的初始化,注册好相应的回调函数,以便能在信号到来之时去执行 set_handle_irq 函数的设置很关键,它将全局函数指针 handle_arch_irq 指向了 gic_handle_irq,而处理器在进入中断异常时,会跳转到 handle_arch_irq 执行,所以,可以认为它就是中断处理的入口函数了。驱动中完成了各类函数的注册,此外还完成了 irq_chip, irq_domain 等结构体的初始化,这些结构在下文会进一步分析。最后,完成 GIC 硬件模块的初始化设置,以及电源管理相关的注册等工作。

3.3、数据结构

再说明一下GIC的数据结构。


struct gic_chip_data {
	struct fwnode_handle	*fwnode;
	phys_addr_t		dist_phys_base;
	void __iomem		*dist_base;
	struct redist_region	*redist_regions;
	struct rdists		rdists;
	struct irq_domain	*domain;
	u64			redist_stride;
	u32			nr_redist_regions;
	u64			flags;
	bool			has_rss;
	unsigned int		ppi_nr;
	struct partition_desc	**ppi_descs;
};

struct irq_domain {
	struct list_head		link;
	const char			*name;
	const struct irq_domain_ops	*ops;
	void				*host_data;
	unsigned int			flags;
	unsigned int			mapcount;
	struct mutex			mutex;
	struct irq_domain		*root;

	/* Optional data */
	struct fwnode_handle		*fwnode;
	enum irq_domain_bus_token	bus_token;
	struct irq_domain_chip_generic	*gc;
	struct device			*dev;
	struct device			*pm_dev;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain		*parent;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	const struct msi_parent_ops	*msi_parent_ops;
#endif

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

struct irq_domain_ops {
	int (*match)(struct irq_domain *d, struct device_node *node,
		     enum irq_domain_bus_token bus_token);
	int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
		      enum irq_domain_bus_token bus_token);
	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);
	int (*activate)(struct irq_domain *d, struct irq_data *irqd, bool reserve);
	void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
	int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
			 unsigned long *out_hwirq, unsigned int *out_type);
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	void (*debug_show)(struct seq_file *m, struct irq_domain *d,
			   struct irq_data *irqd, int ind);
#endif
};


struct irq_chip {
	const char	*name;
	unsigned int	(*irq_startup)(struct irq_data *data);
	void		(*irq_shutdown)(struct irq_data *data);
	void		(*irq_enable)(struct irq_data *data);
	void		(*irq_disable)(struct irq_data *data);

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

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

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

#ifdef CONFIG_DEPRECATED_IRQ_CPU_ONOFFLINE
	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);
#endif
	void		(*irq_suspend)(struct irq_data *data);
	void		(*irq_resume)(struct irq_data *data);
	void		(*irq_pm_shutdown)(struct irq_data *data);

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

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

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

	int		(*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
	int		(*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

	int		(*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

	void		(*ipi_send_single)(struct irq_data *data, unsigned int cpu);
	void		(*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

	int		(*irq_nmi_setup)(struct irq_data *data);
	void		(*irq_nmi_teardown)(struct irq_data *data);

	unsigned long	flags;
};

GIC 驱动中,使用 struct gic_chip_data结构体来描述 GIC 控制器的信息,整个驱动都是围绕着该结构体的初始化,驱动中将函数指针都初始化好,实际的工作是由中断信号触发,也就是在中断来临的时候去进行回调 struct irq_chip 结构,描述的是中断控制器的底层操作函数集,这些函数集最终完成对控制器硬件的操作 struct irq_domain 结构,用于硬件中断号和 Linux IRQ 中断号(virq,虚拟中断号)之间的映射;

每个中断控制器都对应一个 IRQ Domain;

中断控制器驱动通过 irq_domain_add_*() 接口来创建 IRQ Domain;
IRQ Domain 支持三种映射方式:linear map(线性映射),tree map(树映射),no map(不映射);

  1. linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,可以选择线性映射;
  2. tree map:硬件中断号可能很大,可以选择树映射;
  3. no map:硬件中断号直接就是 Linux 的中断号;

3.4、ARM 中断架构

 


中断也是异常模式的一种,当外设触发中断时,处理器会切换到特定的异常模式进行处理,而这部分代码都是架构相关的;ARM64 的代码位于 arch/arm64/kernel/entry.S。

ARM64 处理器有四个异常级别 Exception Level:0~3,EL0 级对应用户态程序,EL1 级对应操作系统内核态,EL2 级对应 Hypervisor,EL3 级对应 Secure Monitor;

异常触发时,处理器进行切换,并且跳转到异常向量表开始执行,针对中断异常,最终会跳转到 irq_handler 中;

中断触发,处理器去异常向量表找到对应的入口,比如 EL0 的中断跳转到 el0_irq 处,EL1 则跳转到 el1_irq 处;

在 GIC 驱动中,会调用 set_handle_irq 接口来设置 handle_arch_irq 的函数指针,让它指向 gic_handle_irq,因此中断触发的时候会跳转到 gic_handle_irq 处执行;gic_handle_irq 函数处理时,分为两种情况,一种是外设触发的中断,硬件中断号在 16 ~ 1020 之间,一种是软件触发的中断,用于处理器之间的交互,硬件中断号在 16 以内;

外设触发中断后,根据 irq domain 去查找对应的 Linux IRQ 中断号,进而得到中断描述符 irq_desc,最终也就能调用到外设的中断处理函数;

#define __exception_irq_entry	__irq_entry

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	if (unlikely(gic_supports_nmi() && !interrupts_enabled(regs)))
		__gic_handle_irq_from_irqsoff(regs);
	else
		__gic_handle_irq_from_irqson(regs);
}

static void __gic_handle_irq_from_irqson(struct pt_regs *regs)
{
	bool is_nmi;
	u32 irqnr;

	irqnr = gic_read_iar();

	is_nmi = gic_rpr_is_nmi_prio();

	if (is_nmi) {
		nmi_enter();
		__gic_handle_nmi(irqnr, regs);
		nmi_exit();
	}

	if (gic_prio_masking_enabled()) {
		gic_pmr_mask_irqs();
		gic_arch_enable_irqs();
	}

	if (!is_nmi)
		__gic_handle_irq(irqnr, regs);
}

static void __gic_handle_irq(u32 irqnr, struct pt_regs *regs)
{
	if (gic_irqnr_is_special(irqnr))
		return;

	gic_complete_ack(irqnr);

	if (generic_handle_domain_irq(gic_data.domain, irqnr)) {
		WARN_ONCE(true, "Unexpected interrupt (irqnr %u)\n", irqnr);
		gic_deactivate_unhandled(irqnr);
	}
}

int generic_handle_domain_irq(struct irq_domain *domain, unsigned int hwirq)
{
	return handle_irq_desc(irq_resolve_mapping(domain, hwirq));
}

static inline struct irq_desc *irq_resolve_mapping(struct irq_domain *domain,
						   irq_hw_number_t hwirq)
{
	return __irq_resolve_mapping(domain, hwirq, NULL);
}

int handle_irq_desc(struct irq_desc *desc)
{
	struct irq_data *data;

	if (!desc)
		return -EINVAL;

	data = irq_desc_get_irq_data(desc);
	if (WARN_ON_ONCE(!in_hardirq() && handle_enforce_irqctx(data)))
		return -EPERM;

	generic_handle_irq_desc(desc);
	return 0;
}

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值