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 中断控制器可以分为:仲裁单元和 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手册的中断号规定:
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 写入值 │
└─────────────────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ 中断完成处理 │
└──────────────────────────────┘
中断状态机
-
添加挂起状态(A1、A2)
对于一个 SGI,发生以下 2 种情况的 1 种:- 软件写 GICD_SGIR 寄存器,指定目标处理器。
- 目标处理器上软件写 GICD_SPENDSGIRn 寄存器。
对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
* 外设发出一个中断请求信号。
* 软件写 GICD_ISPENDRn 寄存器。 -
删除挂起状态(B1、B2)
对于 SGI,目标处理器写 GICD_CPENDSGIRn 寄存器, 对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
* 电平触发类型中断,信号取消。
* 边沿触发类型中断,软件写 GICD_ICPENDRn 寄存器。 -
挂起到激活(C)
如果中断使能,并且高优先级,软件从 GICC_IAR 寄存器读取时发生状态改变。 -
挂起到激活和挂起(D)
对于 SGI,这种转变发生在以下任一情况下:
* 将 SGI 状态设置为挂起的写入操作与读取 GICC_IAR 几乎同时发生。
* 当多个挂起的 SGI 具有相同 ID 时,并且它们来自同一个源处理器并指向同一个处理器。其中一个 SGI 状态变为激活(C),其他 SGI 状态变为激活和挂起(D)。
对于 SPI 或 PPI,满足以下所有条件,则发生这种转换。
* 中断开启。
* 软件读取 GICC_IAR,读操作将激活状态添加到中断中。
此外,还应满足以下条件之一:
* 对于电平触发中断,中断信号保持。通常都是这样,因为外设直到处理器处理完中断后才会取消触发信号。
* 对于边沿触发中断,是否发生此转换取决于读取 GICC_IAR 的时间(中断再次触发,上一次未处理),读取 GICC_IAR 可能会转换到 C,后面可能会转换到 A2。 -
删除激活状态(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(不映射);
- linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,可以选择线性映射;
- tree map:硬件中断号可能很大,可以选择树映射;
- 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);
}