一、硬中断和软中断的区别
1、硬中断是硬件发出一个信号,通知到CPU,然后在执行中断处理函数;而软中断是一种推后执行的机制,是实现把复杂的处理时间久的中断处理函数的一部分拿出来,放在软中断进行调度处理,即可以容忍相对时间延迟,这样既可以解决硬中断长时间的占据CPU的问题;
2、硬中断是物理触发,而软中断是逻辑触发的。硬中断的物理是硬中断控制器,而软中断是执行中断指令产生的。
3、硬中断是可屏蔽的,软中断不可屏蔽。
4、中断嵌套
Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但同种中断除外。软中断不能嵌套,但相同类型的软中断可以在不同CPU上并行执行。
二、软中断代码中,会有标志位代表是否为同一类软中断
在 Linux 内核中,softirq、tasklet 和 workqueue 都是用于处理延迟执行的任务的机制,它们有一定的执行顺序和互斥关系。
-
Softirq(软中断):Softirq 是一种在内核上下文中执行的延迟处理机制,用于处理高优先级的任务。Softirq 是依次执行的,即同一时刻只能执行一个 Softirq。因此,不同的 Softirq 之间是互斥的,不能同时执行。
-
Tasklet(任务队列):Tasklet 是一种比 Softirq 更轻量级的延迟处理机制,也在内核上下文中执行。Tasklet 之间是可以并行执行的,但同一个 Tasklet 是串行执行的,即同一时刻只能执行一个 Tasklet。不同的 Tasklet 可以同时执行,但同一 Tasklet 不会被打断。
-
Workqueue(工作队列):Workqueue 是一种在内核线程上下文中执行的延迟处理机制,用于处理较低优先级的任务。Workqueue 通过创建内核线程来执行任务,因此,不同的 Workqueue 之间是可以并行执行的。同一个 Workqueue 中的任务是串行执行的,即同一时刻只能执行一个任务。
-
代码示例后面写出
三、硬中断的源码流程
网上总结中断的执行过程的大致流程是:
1、保存中断发生时CPSR寄存器内容到SPSR_irq寄存器中
2、修改CPSR寄存器,让CPU进入处理器模式(processor mode)中的IRQ模式,即修改CPSR寄存3、器中的M域设置为IRQ Mode。
4、硬件自动关闭中断IRQ或FIQ,即CPSR中的IRQ位或FIQ位置1。
5、保存返回地址到LR_irq寄存器中。
6、硬件自动调转到中断向量表的IRQ向量。,从此处开始进入中断函数
Linux kernel 源码中,就是其中断向量表,然后通过连接脚本和处理并在启动过程中将其放到内存的特定地址上去并通过CP13寄存器指示。
源码路径:arch/arm/kernel/entry-armv.S //针对arm32的汇编 先分析这个文件
源码路径:arch/arm64/kernel/entry.S //针对arm64 的汇编
.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq/*
* Interrupt dispatcher 中断分发器
*/
vector_stub irq, IRQ_MODE, 4 //vector_stub定义 vector_irq.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)//在 ARM32 架构中,Supervisor 模式的模式数值是 0x13,与上0xF,就是3
.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
这段代码片段是 ARM 架构中的中断向量表(Interrupt Vector Table)的一部分。中断向量表是一个存储中断处理程序地址的数据结构,用于在发生中断时跳转到相应的处理程序。
在给定的代码片段中,定义了一个名为 vector_stub
的标号,后面跟着四个 .long
指令,每个指令都指定了一个中断处理程序的地址。
解释如下:
- 第一个
.long
指令:__irq_usr
是一个中断处理程序的地址,用于处理用户模式下的中断(USR_26 或 USR_32)。 - 第二个
.long
指令:__irq_invalid
是一个中断处理程序的地址,用于处理无效的中断(FIQ_26 或 FIQ_32)。 - 第三个
.long
指令:__irq_invalid
是一个中断处理程序的地址,用于处理无效的中断(IRQ_26 或 IRQ_32)。 - 第四个
.long
指令:__irq_svc
是一个中断处理程序的地址,用于处理服务调用中断(SVC_26 或 SVC_32)。 - 。。。。
这些中断处理程序的具体实现代码可能在其他地方定义。在中断发生时,处理器会根据中断类型选择相应的中断处理程序,并跳转到对应的地址执行相应的处理逻辑
备注:SVC 模式:
在 ARM 架构中,SVC(Supervisor Call)指令用于触发服务调用中断。当执行 SVC 指令时,处理器会进入特权级别为 Supervisor 的模式。
在 ARM32 架构中,Supervisor 模式的模式数值是 0x13。
在 ARM64 架构中,Supervisor 模式的模式数值是 0x1F。
请注意,不同的 ARM 架构可能有不同的模式数值,上述数值适用于常见的 ARM32 和 ARM64 架构。
@汇编 vector_stub 的实现
/*
* Vector stubs.
*
* This code is copied to 0xffff1000 so we can use branches in the
* vectors, rather than ldr's. Note that this code must not exceed
* a page size.
*
* Common stub entry macro:
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.macro vector_stub, name, mode, correction=0@macro 就是汇编宏定义
.align 5vector_\name:
.if \correction
sub lr, lr, #\correction
.endif@ Save r0, lr_<exception> (parent PC)
stmia sp, {r0, lr} @ save r0, lr@ Save spsr_<exception> (parent CPSR)
2: mrs lr, spsr
str lr, [sp, #8] @ save spsr@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr@修改CPSR寄存器的控制域为SVC模式,为了使中断处理在SVC模式下执行
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0@
@ the branch table must immediately follow this code
@@低4为反映了进入中断前CPU 的运行模式,9为USR,3为SVC模式
and lr, lr, #0x0f
THUMB( adr r0, 1f )@根据中断发生点所在的模式,给lr寄存器赋值,__irq_usr或者__irq_svc标签处
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp@得到的lr就是 “.long __irq_svc”
ARM( ldr lr, [pc, lr, lsl #2] )@把lr的赋值给pc指针,跳转到__irq_usr或者__irq_svc.
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
vector_irq这个中断服务函数这个函数内读取了一些寄存器,然后根据进入中断前的CPU状态跳转到__irq_usr或者 __irq_svc。这里暂时分析在内核态时的情况所以是跳转到__irq_svc这个函数这一部分是中断上下文直接调用的所以他也就还在中断上下文中执行,接着看__irq_svc.
//在 ARM32 架构中,Supervisor 模式的模式数值是 0x13,与上0xF,就是3,
.long __irq_svc @ 3 (SVC_26 / SVC_32)
@__irq_svc 的汇编函数
__irq_svc:
svc_entry
irq_handler@中断结束后,发生抢占的地方
#ifdef CONFIG_PREEMPTION
@获取thread_info->preempt_count变量;preempt_count 为0,说明可以抢占进程;preempt_count大于0,表示不能抢占;
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
teq r8, #0 @ if preempt count != 0
bne 1f @ return from exeption
ldr r0, [tsk, #TI_FLAGS] @ get flags@判断是否设置了_TIF_NEED_RESCHED 标志位
tst r0, #_TIF_NEED_RESCHED @ if NEED_RESCHED is set
blne svc_preempt @ preempt!ldr r8, [tsk, #TI_PREEMPT_LAZY] @ get preempt lazy count
teq r8, #0 @ if preempt lazy count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED_LAZY
blne svc_preempt
1:
#endifsvc_exit r5, irq = 1 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
__irq_svc处理发生在内核空间的中断,主要svc_entry保护中断现场;irq_handler执行中断处理,如果打开抢占功能后续还要检查是否可以抢占这里暂时不看;最后svc_exit执行中断退出处理。到irq_handler就会调用C代码部分的通用中断子系统处理过程。直接上源码。
@irq_handler 的汇编实现
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp@设置返回地址
badr lr, 9997f@调用handler_arch_irq 保存的处理函数,这里使用的ldr,所以不会覆盖lr寄存器,从而保证 了正常返回
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
其中handle_arch_irq的是在中断子系统初始化过程中绑定的,在gic控制器的环境下为gic_handle_irq的,其中的处理分中断后大于15和小于等于的两种情况见源码。源码位置为
gic : drivers/irqchip/irq-gic.c
gic-v3: drivers/irqchip/irq-gic-v3.c
两个控制器的区别,之后在做分析,先看gic : drivers/irqchip/irq-gic.c 的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 {
//读取IAR寄存器
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);//GICC_IAR_INT_ID_MASK 为0x3ff,即低10位,所以中断最多是从 0-1023
irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (unlikely(irqnr >= 1020))
break;if (static_branch_likely(&supports_deactivate_key))
//直接写EOI寄存器,表示结束中断
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
isb();/*
* Ensure any shared data written by the CPU sending the IPI
* is read after we've read the ACK register on the GIC.
*
* Pairs with the write barrier in gic_ipi_send_mask
*///SGI类型的中断是CPU核间通信所用,只有定义了CONFIG_SMP才有意义
if (irqnr <= 15) {
smp_rmb();/*
* The GIC encodes the source CPU in GICC_IAR,
* leading to the deactivation to fail if not
* written back as is to GICC_EOI. Stash the INTID
* away for gic_eoi_irq() to write back. This only
* works because we don't nest SGIs...
*/
this_cpu_write(sgi_intid, irqstat);
}handle_domain_irq(gic->domain, irqnr, regs);
} while (1);
}
通过while(1)循环处理所有的硬件中断,根据GIC中断控制器的原理小于16的中断是用于SMP系统的核间通讯的所以,只在SMP架构下起作用。所以常用中断处理流程走的是irqnr > 15 && irqnr < 1021 这个分支调用---> handle_domain_irq(gic->domain, irqnr, regs)//irqnr 是硬件中断号;reg是svc_entry保存的中断现场;domain是中断子系统初始化时根据中断控制器的不同初始化的一个中断操作接口。由源码可以看到中断处理最重要的是通过调用__handle_domain_irq接口,并且在lookup中传入了ture。
####
补充一下,中断子系统的注册过程,gic_handle_irq是如何注册,源码如下:
设备树包含对应的设备树名,匹配成功后,会调用 __of_table_##name->data 进行初始化,
通过IRQCHIP_DECLARE 定义,__of_table_##name->data = gic_of_init。
中断子系统的注册过程:
__of_table_##name->data = gic_of_init ,调用__gic_init_bases(gic, &node->fwnode);,然后set_handle_irq(gic_handle_irq);
@以设备树的方式注册进系统
int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
struct gic_chip_data *gic;
int irq, ret;if (WARN_ON(!node))
return -ENODEV;if (WARN_ON(gic_cnt >= CONFIG_ARM_GIC_MAX_NR))
return -EINVAL;gic = &gic_data[gic_cnt];
ret = gic_of_setup(gic, node);
if (ret)
return ret;/*
* Disable split EOI/Deactivate if either HYP is not available
* or the CPU interface is too small.
*/
if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base))
static_branch_disable(&supports_deactivate_key);ret = __gic_init_bases(gic, &node->fwnode);
if (ret) {
gic_teardown(gic);
return ret;
}if (!gic_cnt) {
gic_init_physaddr(node);
gic_of_setup_kvm_info(node);
}if (parent) {
irq = irq_of_parse_and_map(node, 0);
gic_cascade_irq(gic_cnt, irq);
}if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);gic_cnt++;
return 0;
}//IRQCHIP_DECLARE 在系统中,建立设备树表格,设备与初始化函数绑定
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);
//以ACPI表的方式注册进系统
static int __init gic_v2_acpi_init(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_generic_distributor *dist;
struct fwnode_handle *domain_handle;
struct gic_chip_data *gic = &gic_data[0];
int count, ret;/* Collect CPU base addresses */
count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT,
gic_acpi_parse_madt_cpu, 0);
if (count <= 0) {
pr_err("No valid GICC entries exist\n");
return -EINVAL;
}gic->raw_cpu_base = ioremap(acpi_data.cpu_phys_base, ACPI_GIC_CPU_IF_MEM_SIZE);
if (!gic->raw_cpu_base) {
pr_err("Unable to map GICC registers\n");
return -ENOMEM;
}dist = (struct acpi_madt_generic_distributor *)header;
gic->raw_dist_base = ioremap(dist->base_address,
ACPI_GICV2_DIST_MEM_SIZE);
if (!gic->raw_dist_base) {
pr_err("Unable to map GICD registers\n");
gic_teardown(gic);
return -ENOMEM;
}/*
* Disable split EOI/Deactivate if HYP is not available. ACPI
* guarantees that we'll always have a GICv2, so the CPU
* interface will always be the right size.
*/
if (!is_hyp_mode_available())
static_branch_disable(&supports_deactivate_key);/*
* Initialize GIC instance zero (no multi-GIC support).
*/
domain_handle = irq_domain_alloc_fwnode(&dist->base_address);
if (!domain_handle) {
pr_err("Unable to allocate domain handle\n");
gic_teardown(gic);
return -ENOMEM;
}ret = __gic_init_bases(gic, domain_handle);
if (ret) {
pr_err("Failed to initialise GIC\n");
irq_domain_free_fwnode(domain_handle);
gic_teardown(gic);
return ret;
}acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, domain_handle);
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_init(NULL, gic_data[0].domain);if (static_branch_likely(&supports_deactivate_key))
gic_acpi_setup_kvm_info();return 0;
}//IRQCHIP_ACPI_DECLARE 在系统中,建立ACPI表,绑定函数
IRQCHIP_ACPI_DECLARE(gic_v2, ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR,
gic_validate_dist, ACPI_MADT_GIC_VERSION_V2,
gic_v2_acpi_init);
IRQCHIP_ACPI_DECLARE(gic_v2_maybe, ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR,
gic_validate_dist, ACPI_MADT_GIC_VERSION_NONE,
gic_v2_acpi_init);
static int __init __gic_init_bases(struct gic_chip_data *gic,
struct fwnode_handle *handle)
{
char *name;
int i, ret;if (WARN_ON(!gic || gic->domain))
return -EINVAL;if (gic == &gic_data[0]) {
/*
* Initialize the CPU interface map to all CPUs.
* It will be refined as each CPU probes its ID.
* This is only necessary for the primary GIC.
*/
for (i = 0; i < NR_GIC_CPU_IF; i++)
gic_cpu_map[i] = 0xff;set_handle_irq(gic_handle_irq);
if (static_branch_likely(&supports_deactivate_key))
pr_info("GIC: Using split EOI/Deactivate mode\n");
}if (static_branch_likely(&supports_deactivate_key) && gic == &gic_data[0]) {
name = kasprintf(GFP_KERNEL, "GICv2");
gic_init_chip(gic, NULL, name, true);
} else {
name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));
gic_init_chip(gic, NULL, name, false);
}ret = gic_init_bases(gic, handle);
if (ret)
kfree(name);
else if (gic == &gic_data[0])
gic_smp_init();return ret;
}
接着 gic_handle_irq 调用 handle_domain_irq(gic->domain, irqnr, regs),根据调用,进入__handle_domain_irq,源码如下:
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;//通过显式增加hardirq 域计数,通过进入中断上下文
irq_enter();
#ifdef CONFIG_IRQ_DOMAIN
//根据硬件中断号找到对应的软件中断号
if (lookup)
irq = irq_find_mapping(domain, hwirq);
#endif/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {//开始具体某一个中断的处理,此irq是Linux irq中断号
generic_handle_irq(irq);
}irq_exit(); //退出中断上下文
set_irq_regs(old_regs);
return ret;
}
其中处理中断的接口是generic_handle_irq,进入到generic_handle_irq参数是irq号,irq_to_desc()根据irq号找到对应的struct irq_desc。然后调用generic_handle_irq_desc(irq,desc)进一步调用调用irq_desc->handle_irq处理对应的中断。
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
struct irq_data *data;if (!desc)
return -EINVAL;data = irq_desc_get_irq_data(desc);
if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
return -EPERM;generic_handle_irq_desc(desc);
return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);、、
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc);
}
这里需要明白中断描述符中的handle_irq接口的初始化也是分情况的。每个中断描述符注册的时候,由gic_irq_domain_map根据hwirq号决定。在gic_irq_domain_map的时候根据hw号决定handle,hw硬件中断号大于32指向handle_fasteoi_irq。驱动编程通常情况都是使外部中断而非内部异常所以中断号>32,所以handle_irq = handle_fasteoi_irq
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;
struct irq_data *irqd = irq_desc_get_irq_data(irq_to_desc(irq));switch (hw) {
case 0 ... 15:
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_percpu_devid_fasteoi_ipi,
NULL, NULL);
break;
case 16 ... 31:
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
break;
default:
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(irqd);
break;
}/* Prevents SW retriggers which mess up the ACK/EOI ordering */
irqd_set_handle_enforce_irqctx(irqd);
return 0;
}
void handle_fasteoi_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;raw_spin_lock(&desc->lock);
if (!irq_may_run(desc))
goto out;desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/*
* If its disabled or no action available
* then mask it and get out of here:
*///如果该中断没有指定action 描述符或者该中断被关闭了&desc->irq_data,就设置该中断状态为IRQS_PENDING,且mask_irq 屏蔽该中断。
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
mask_irq(desc);
goto out;
}kstat_incr_irqs_this_cpu(desc);
//如果该中断是IRQS_ONESHOT,不支持中断嵌套,就mask_irq(),屏蔽中断。
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);//handle_irq_event 是实际的中断处理函数
handle_irq_event(desc);
//根据不同条件执行unmask_irq()解除中断屏蔽,或者执行irq_chip->irq_eoi发生EOI信号,通知GIC中断处理完毕。
cond_unmask_eoi_irq(desc, chip);
raw_spin_unlock(&desc->lock);
return;
out:
if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
chip->irq_eoi(&desc->irq_data);
raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_fasteoi_irq);
handle_irq_event调用handle_irq_event_percpu,执行action->handler(),如有需要唤醒内核中断线程执行action->thread_fn见源码如下:
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;//清除IRQS_PENDING 标志位,
desc->istate &= ~IRQS_PENDING;
//设置标志位IRQD_IRQ_INPROGRESS,正在处理硬件中断
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_IRQ_INPROGRESS标志位,表示结束中断
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);
if (!noirqdebug)
note_interrupt(desc, retval);
return retval;
}
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;record_irq_time(desc);
//遍历中断描述符的链表action,
for_each_action_of_desc(desc, action) {
irqreturn_t res;/*
* If this IRQ would be threaded under force_irqthreads, mark it so.
*/
if (irq_settings_can_thread(desc) &&
!(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
lockdep_hardirq_threaded();trace_irq_handler_entry(irq, action);
//执行handler 函数,这就是申请注册中断接口时指定的中断服务函数
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
irq, action->handler))
local_irq_disable();//根据中断服务函数返回值的不同进行操作。
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}//唤醒中断线程,并执行
__irq_wake_thread(desc, action);
fallthrough; /* to add to randomness */
case IRQ_HANDLED: //处理完毕,可以结束了
*flags |= action->flags;
break;default:
break;
}retval |= res;
}return retval;
}
到这里应该大部分处理流程都清楚了,中断发生后通过以部分arch层相关的代码处理后得到Linux中断子系统的中断号进而找到对应的中断,而驱动在编程过程中已经通过request_irq接口注册好action接口函数到对应的中断。中断执行过程中直接找到对应的接口进行调用就完成了中断的响应处理,然后在根据中断handler处理的返回值如果是IRQ_WAKE_THREAD则唤醒中断注册时候创建的线程,这个线程的具体内容后面中断注册内容部分再具体学习。这里说明request_irq 申请注册的中断服务函数的执行时在中断上下文中的,但是实际上内核为了提高实时性进行了一部分优化通过强制中断服务接口线程化,这个后面学习。 先来看上面提到的区分中断号小于32的处理接口handle_percpu_devid_irq()。
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;/*
* PER CPU interrupts are not serialized. Do not touch
* desc->tot_count.
*/
__kstat_incr_irqs_this_cpu(desc);if (chip->irq_ack)
chip->irq_ack(&desc->irq_data);if (likely(action)) {
trace_irq_handler_entry(irq, action);
res = action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
trace_irq_handler_exit(irq, action, res);
} else {
unsigned int cpu = smp_processor_id();
bool enabled = cpumask_test_cpu(cpu, desc->percpu_enabled);if (enabled)
irq_percpu_disable(desc, cpu);pr_err_once("Spurious%s percpu IRQ%u on CPU%u\n",
enabled ? " and unmasked" : "", irq, cpu);
}if (chip->irq_eoi)
chip->irq_eoi(&desc->irq_data);
}
首先响应IAR,然后执行handler这里的接口是内核注册的接口不是驱动开发过程使用(在开发过程中,使用的中断号都是大于32,不会自己写上面的handler,暂时这么认为),最后发送EOI基本上。它的处理流程不同于handle_fasteoi_irq。
上面中断流程已经介绍到 action->handler,而handler 根据驱动开发注册的函数,下面结束如何注册?
四、中断注册流程
内核驱动的开发过程常用申请中断的接口是request_irq,他实际上是对另一个接口的调用封转如下,所以进一步查看request_threaded_irq的处理过程:
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_threaded_irq的处理过程才是正真的中断注册接口,而request_irq是一个将thread_fn设置为NULL的封装。这做其实是和他的实现有关系的继续往下看就明白了
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)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*
* Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
* it cannot be set along with IRQF_NO_SUSPEND.
*/
if (((irqflags & IRQF_SHARED) && !dev_id) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;//handler 和thread_fn 不能同时为NULL
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}//申请一个action 结构,这个会在setup_irq中,绑定到中断描述符中,使得发生中断时,调用action 的handler.
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}//绑定
retval = __setup_irq(irq, desc, action);
if (retval) {
irq_chip_pm_put(&desc->irq_data);
kfree(action->secondary);
kfree(action);
}#ifdef CONFIG_DEBUG_SHIRQ_FIXME
if (!retval && (irqflags & IRQF_SHARED)) {
/*
* It's a shared IRQ -- the driver ought to be prepared for it
* to happen immediately, so let's make sure....
* We disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;disable_irq(irq);
local_irq_save(flags);handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
EXPORT_SYMBOL(request_threaded_irq);
接下来是 __setup_irq 是重要函数,实时性的优化就在这个函数里,
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;if (!desc)
return -EINVAL;if (desc->irq_data.chip == &no_irq_chip)
return -ENOSYS;
if (!try_module_get(desc->owner))
return -ENODEV;new->irq = irq;
/*
* If the trigger type is not specified by the caller,
* then use the default for this interrupt.
*/
if (!(new->flags & IRQF_TRIGGER_MASK))
new->flags |= irqd_get_trigger_type(&desc->irq_data);/*
* Check whether the interrupt nests into another interrupt
* thread.
*///对于设置了 nested 嵌套类型的中断描述符,必须指定thread_fn
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
/*
* Replace the primary handler which was provided from
* the driver for non nested interrupt handling by the
* dummy function which warns when called.
*///嵌套类型的中断的线程函数由这个替换,这个在前面设置过,所以这里将原来指定的handler替换为新的函数irq_nested_primary_handler,这个接口只是输出一个警告,所以不支持嵌套的handler
new->handler = irq_nested_primary_handler;
} else {
if (irq_settings_can_thread(desc)) {//强制中断线程化,除非使用IRQF_NO_ THREAD
ret = irq_setup_forced_threading(new);
if (ret)
goto out_mput;
}
}/*
* Create a handler thread when a thread function is supplied
* and the interrupt does not nest into another interrupt
* thread.
*///线程化中断,创建线程
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
if (new->secondary) {
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
}/*
* Drivers are often written to work w/o knowledge about the
* underlying irq chip implementation, so a request for a
* threaded irq without a primary hard irq context handler
* requires the ONESHOT flag to be set. Some irq chips like
* MSI based interrupts are per se one shot safe. Check the
* chip flags, so we can avoid the unmask dance at the end of
* the threaded handler for those.
*/
if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
new->flags &= ~IRQF_ONESHOT;/*
* Protects against a concurrent __free_irq() call which might wait
* for synchronize_hardirq() to complete without holding the optional
* chip bus lock and desc->lock. Also protects against handing out
* a recycled oneshot thread_mask bit while it's still in use by
* its previous owner.
*/
mutex_lock(&desc->request_mutex);/*
* Acquire bus lock as the irq_request_resources() callback below
* might rely on the serialization or the magic power management
* functions which are abusing the irq_bus_lock() callback,
*/
chip_bus_lock(desc);/* First installed action requests resources. */
if (!desc->action) {
ret = irq_request_resources(desc);
if (ret) {
pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
new->name, irq, desc->irq_data.chip->name);
goto out_bus_unlock;
}
}/*
* The following block of code has to be executed atomically
* protected against a concurrent interrupt and any of the other
* management calls which are not serialized via
* desc->request_mutex or the optional bus lock.
*///共享中断可能有多个action,这里是把他们形成链表
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;if (old) {
/*
* Can't share interrupts unless both agree to and are
* the same type (level, edge, polarity). So both flag
* fields must have IRQF_SHARED set and the bits which
* set the trigger type must match. Also all must
* agree on ONESHOT.
* Interrupt lines used for NMIs cannot be shared.
*/
unsigned int oldtype;if (desc->istate & IRQS_NMI) {
pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n",
new->name, irq, desc->irq_data.chip->name);
ret = -EINVAL;
goto out_unlock;
}/*
* If nobody did set the configuration before, inherit
* the one provided by the requester.
*/
if (irqd_trigger_type_was_set(&desc->irq_data)) {
oldtype = irqd_get_trigger_type(&desc->irq_data);
} else {
oldtype = new->flags & IRQF_TRIGGER_MASK;
irqd_set_trigger_type(&desc->irq_data, oldtype);
}if (!((old->flags & new->flags) & IRQF_SHARED) ||
(oldtype != (new->flags & IRQF_TRIGGER_MASK)) ||
((old->flags ^ new->flags) & IRQF_ONESHOT))
goto mismatch;/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;/* add new interrupt at end of irq queue */
do {
/*
* Or all existing action->thread_mask bits,
* so we can find the next zero bit for this
* new action.
*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}/*
* Setup the thread mask for this irqaction for ONESHOT. For
* !ONESHOT irqs the thread mask is 0 so we can avoid a
* conditional in irq_wake_thread().
*/
if (new->flags & IRQF_ONESHOT) {
/*
* Unlikely to have 32 resp 64 irqs sharing one line,
* but who knows.
*/
if (thread_mask == ~0UL) {
ret = -EBUSY;
goto out_unlock;
}
/*
* The thread_mask for the action is or'ed to
* desc->thread_active to indicate that the
* IRQF_ONESHOT thread handler has been woken, but not
* yet finished. The bit is cleared when a thread
* completes. When all threads of a shared interrupt
* line have completed desc->threads_active becomes
* zero and the interrupt line is unmasked. See
* handle.c:irq_wake_thread() for further information.
*
* If no thread is woken by primary (hard irq context)
* interrupt handlers, then desc->threads_active is
* also checked for zero to unmask the irq line in the
* affected hard irq flow handlers
* (handle_[fasteoi|level]_irq).
*
* The new action gets the first zero bit of
* thread_mask assigned. See the loop above which or's
* all existing action->thread_mask bits.
*/
new->thread_mask = 1UL << ffz(thread_mask);//handler 使用默认irq_nested_primary_handler,如果中断触发类型是LEVEL,如果中断触发后,不清中断容易引发中断风暴。提醒驱动开发时,没有primary handler 且中断控制器不支持硬件oneshot,必须显式指定IRQF_ONESHOT表示位。
} else if (new->handler == irq_default_primary_handler &&
!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
/*
* The interrupt was requested with handler = NULL, so
* we use the default primary handler for it. But it
* does not have the oneshot flag set. In combination
* with level interrupts this is deadly, because the
* default primary handler just wakes the thread, then
* the irq lines is reenabled, but the device still
* has the level irq asserted. Rinse and repeat....
*
* While this works for edge type interrupts, we play
* it safe and reject unconditionally because we can't
* say for sure which type this interrupt really
* has. The type flags are unreliable as the
* underlying chip implementation can override them.
*/
pr_err("Threaded irq requested with handler=NULL and !ONESHOT for %s (irq %d)\n",
new->name, irq);
ret = -EINVAL;
goto out_unlock;
}//非共享中断
if (!shared) {
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc,
new->flags & IRQF_TRIGGER_MASK);if (ret)
goto out_unlock;
}/*
* Activate the interrupt. That activation must happen
* independently of IRQ_NOAUTOEN. request_irq() can fail
* and the callers are supposed to handle
* that. enable_irq() of an interrupt requested with
* IRQ_NOAUTOEN is not supposed to fail. The activation
* keeps it in shutdown mode, it merily associates
* resources if necessary and if that's not possible it
* fails. Interrupts which are in managed shutdown mode
* will simply ignore that activation request.
*/
ret = irq_activate(desc);
if (ret)
goto out_unlock;desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
IRQS_ONESHOT | IRQS_WAITING);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);if (new->flags & IRQF_PERCPU) {
irqd_set(&desc->irq_data, IRQD_PER_CPU);
irq_settings_set_per_cpu(desc);
}if (new->flags & IRQF_ONESHOT)
desc->istate |= IRQS_ONESHOT;/* Exclude IRQ from balancing if requested */
if (new->flags & IRQF_NOBALANCING) {
irq_settings_set_no_balancing(desc);
irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
}if (irq_settings_can_autoenable(desc)) {
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
} else {
/*
* Shared interrupts do not go well with disabling
* auto enable. The sharing interrupt might request
* it while it's still disabled and then wait for
* interrupts forever.
*/
WARN_ON_ONCE(new->flags & IRQF_SHARED);
/* Undo nested disables: */
desc->depth = 1;
}} else if (new->flags & IRQF_TRIGGER_MASK) {
unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
unsigned int omsk = irqd_get_trigger_type(&desc->irq_data);if (nmsk != omsk)
/* hope the handler works with current trigger mode */
pr_warn("irq %d uses trigger mode %u; requested %u\n",
irq, omsk, nmsk);
}*old_ptr = new;
irq_pm_install_action(desc, new);
/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;/*
* Check whether we disabled the irq via the spurious handler
* before. Reenable it and give it another chance.
*/
if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
desc->istate &= ~IRQS_SPURIOUS_DISABLED;
__enable_irq(desc);
}raw_spin_unlock_irqrestore(&desc->lock, flags);
chip_bus_sync_unlock(desc);
mutex_unlock(&desc->request_mutex);irq_setup_timings(desc, new);
//唤醒中断进程,唤醒两个
wake_up_and_wait_for_irq_thread_ready(desc, new);
wake_up_and_wait_for_irq_thread_ready(desc, new->secondary);//创建文件系统接口的内容
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);
return 0;mismatch:
if (!(new->flags & IRQF_PROBE_SHARED)) {
pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",
irq, new->flags, new->name, old->flags, old->name);
#ifdef CONFIG_DEBUG_SHIRQ
dump_stack();
#endif
}
ret = -EBUSY;out_unlock:
raw_spin_unlock_irqrestore(&desc->lock, flags);if (!desc->action)
irq_release_resources(desc);
out_bus_unlock:
chip_bus_sync_unlock(desc);
mutex_unlock(&desc->request_mutex);out_thread:
if (new->thread) {
struct task_struct *t = new->thread;new->thread = NULL;
kthread_stop(t);
put_task_struct(t);
}
if (new->secondary && new->secondary->thread) {
struct task_struct *t = new->secondary->thread;new->secondary->thread = NULL;
kthread_stop(t);
put_task_struct(t);
}
out_mput:
module_put(desc->owner);
return ret;
}
这个函数的接口的整个执行流程就是,如果当前接口是要支持嵌套的则直接修改handler接口为irq_nested_primary_handler这个接口后面说明,如果不支持嵌套且没有强制指定为不可以线程化的时候都是进行线程化中断服务接口的通过irq_setup_forced_threading()同样是后面详细说明,线程化中断服务接口后就会为这个中断创建对应的线程并配置调度方式为FIFO和CPU亲和性。之后就是中断抽象数据的配置和中断使能和文件系统接口的问的创建在/proc目录下。到这里处理流程就算结束了,接下来在完善上面接口的详细内容的探究。
static int irq_setup_forced_threading(struct irqaction *new)
{//实时补丁,这个设置为TRUE
if (!force_irqthreads)
return 0;
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
return 0;/*
* No further action required for interrupts which are requested as
* threaded interrupts already
*/
if (new->handler == irq_default_primary_handler)
return 0;new->flags |= IRQF_ONESHOT;
/*
* Handle the case where we have a real primary handler and a
* thread handler. We force thread them as well by creating a
* secondary action.
*///如果handler 和thread_fn 同时存在,会在创建一个irqaction,把thread_fn赋值给new->secondary->thread_fn,在第二个线程进行执行;handler 被替换。
if (new->handler && new->thread_fn) {
/* Allocate the secondary action */
new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!new->secondary)
return -ENOMEM;
new->secondary->handler = irq_forced_secondary_handler;
new->secondary->thread_fn = new->thread_fn;
new->secondary->dev_id = new->dev_id;
new->secondary->irq = new->irq;
new->secondary->name = new->name;
}
/* Deal with the primary handler */
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
return 0;
}
static irqreturn_t irq_forced_secondary_handler(int irq, void *dev_id)
{
WARN(1, "Secondary action handler called for irq %d\n", irq);
return IRQ_NONE;
}
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD; //再执行中断函数的路径中,有根据这个值判断,如何执行,请往上翻。
}
所以到这里就应该明白通过request_irq接口注册的中断服务接口在内核是会有可能被线程化的所以,这个接口注册的中断服务函数在指定不可以线程化的标志后是运行在中断上下文的,而其他情况是运行在进程上下文的。这也是我一开始想来研究硬中断到底运行在哪个上下文的,因为并发的一些接口是需要区别中断上下文还是进程上下文的。最后再来看一下内核将中断服务进程线程化的线程是怎样执行的
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
}if (IS_ERR(t))
return PTR_ERR(t);//设置调度方式为FIFO
sched_set_fifo(t);
/*
* We keep the reference to the task struct even if
* the thread dies to avoid that the interrupt code
* references an already freed task_struct.
*///加入调度
new->thread = get_task_struct(t);
/*
* Tell the thread to set its affinity. This is
* important for shared interrupt handlers as we do
* not invoke setup_affinity() for the secondary
* handlers as everything is already set up. Even for
* interrupts marked with IRQF_NO_BALANCE this is
* correct as we want the thread to move to the cpu(s)
* on which the requesting code placed the interrupt.
*///设置亲和性
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}
通过kthread_create 创建线程,绑定irq_thread 函数,
/*
* Interrupt handler thread
*/
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);irq_thread_set_ready(desc, action);
//两个不同函数进行执行
if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;init_task_work(&on_exit_work, irq_thread_dtor);
task_work_add(current, &on_exit_work, TWA_NONE);irq_thread_check_affinity(desc, action);
//通过等待队列,等待中断的到来,然后唤醒
while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;irq_thread_check_affinity(desc, action);
//执行中断内核线程函数
action_ret = handler_fn(desc, action);
if (action_ret == IRQ_WAKE_THREAD)
irq_wake_secondary(desc, action); //唤醒第二个wake_threads_waitq(desc); //唤醒wait_for_threads 等待队列
}/*
* This is the regular exit path. __free_irq() is stopping the
* thread via kthread_stop() after calling
* synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the
* oneshot mask bit can be set.
*/
task_work_cancel(current, irq_thread_dtor);
return 0;
}
这和线程接受一个struct irqaction作为参数,运行起来后会在irq_wait_for_interrupt()下阻塞挂起直到中断上下文执行线程唤醒时才会继续运行。就在中断执行为完handler后判断其返回值是否是IRQ_WAKE_THREAD,进而 调用__irq_wake_thread(desc, action);唤醒中断服务程序线程。而线程化则是将handler直接绑定到一个返回IRQ_WAKE_THREAD的接口上从而保证进程一定会被唤。