中断管理基础学习笔记 - 5.2 ARM64高层中断处理【转】

转自:https://blog.csdn.net/jasonactions/article/details/115751815

目录
1. 前言
2. gic_handle_irq
|- -irq_enter
|- -generic_handle_irq
|- -irq_exit
|- - -local_softirq_pending
参考文档
1. 前言
本专题我们开始学习进程管理部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
本专题记录ARM架构下中断是如何管理的,Linux内核中的中断管理机制是如何设计与实现的,以及常用的下半部机制,如软中断、tasklet、workqueue等。本文及后续中断相关笔记均以qemu 5.0.0内嵌平台为例,中断控制器采用GIC-400控制器,支持GIC version2技术规范。本文开始介绍ARM64的中断处理过程,上一节介绍了底层硬件中断的处理过程,其中会调用到handle_arch_irq,在中断管理基础学习笔记 - 2.中断控制器初始化一节的gic_of_init->set_handle_irq中介绍过,会将handle_arch_irq初始化为gic_handle_irq,这个就作为中断的顶层处理函数,本节会详细说明gic_handle_irq函数的处理流程。

kernel版本:5.10
平台:arm64

注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“

2. gic_handle_irq
<drivers/irqchip/irq-gic.c>
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
|--struct gic_chip_data *gic = &gic_data[0];
|--void __iomem *cpu_base = gic_data_cpu_base(gic);
|--do {
//读取GIC的GICC_IAR寄存器,读取行为本身是对中断的ack
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
//获取发生中断的硬中断号
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (unlikely(irqnr >= 1020))
break;
//写入GICC_EOIR寄存器,通知CPU interface中断处理完成
if (static_branch_likely(&supports_deactivate_key))
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
isb();
//对SGI私有中断的处理
if (irqnr <= 15)
smp_rmb();
this_cpu_write(sgi_intid, irqstat);
handle_domain_irq(gic->domain, irqnr, regs);
} while (1);

中断高层处理,这里有个疑问while循环是如何退出的?难道没有中断的时候GICC_IAR的bit0~bit9全1?

readl_relaxed(cpu_base + GIC_CPU_INTACK);:读取GIC的GICC_IAR寄存器,读取行为本身是对中断的ack,根据中断管理基础学习笔记 - 1.概述一节中介绍的gic状态机,读取GICC_IAR寄存器就是对中断的ACK,会让中断从pending状态进入到active状态;

writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI):写入GICC_EOIR寄存器,通知CPU interface中断处理完成,此时就是deactive 中断,让中断从active状态转到inactive状态;

handle_domain_irq:前面介绍中断控制器初始化一文,对于gic中断控制器来讲会执行gic_of_init初始化,他会创建并注册irq_domain,此处的gic->domain就是gic初始化时创建的,它代表了中断控制器;在创建软硬中断号映射一文中,通过遍历dts中中断节点,为每个硬中断号创建了映射,并将映射关系保存在irq_domain->linear_revmap数组或revmap_tree树中,其中以硬中断号为索引。此处参数irqnr就是硬中断号,通过它可以知道软中断号,以软中断号为索引可以获取到irq_desc,进一步获取到irq_data,并获取到irqaction进行处理。

handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
|--__handle_domain_irq(domain, hwirq, true, regs);
|--unsigned int irq = hwirq;
|--irq_enter();
| //以硬中断号为索引软中断号返回给irq
|--irq = irq_find_mapping(domain, hwirq);
|--generic_handle_irq(irq);
|--irq_exit();

irq_enter:显示的告诉Linux内核现在进入中断上下文,主要通过增加preempt.count的HARDIRQ域计数值来实现;

irq_find_mapping:以硬中断号为索引软中断号返回给irq,这里rq_domian会维护软硬中断号的映射关系,通过irq_domain可以获取到软中断号,其中如果hwirq < domain->revmap_size,则会直接通过domain->linear_revmap[hwirq]返回,否则以hwirq为索引通过domain->revmap_tree查找获得irq_data,irq_data->irq中就保存了对应的软中断号;

generic_handle_irq:为通用中断处理的主函数

irq_exit:与irq_enter相匹配进行递减,并处理软中断

|- -irq_enter
void irq_enter(void)//Enter an interrupt context including RCU update
|--rcu_irq_enter();
|--irq_enter_rcu();
|--if (is_idle_task(current) && !in_interrupt())
| local_bh_disable();
| tick_irq_enter();
| _local_bh_enable();
|--__irq_enter();
|--account_irq_enter_time(current);
|--preempt_count_add(HARDIRQ_OFFSET);
| |-- __preempt_count_add(val);
| |--u32 pc = READ_ONCE(current_thread_info()->preempt.count);
| |--pc += val;
| |--WRITE_ONCE(current_thread_info()->preempt.count, pc);
|--lockdep_hardirq_enter();

从上面可以看出irq_enter->preempt_count_add->__preempt_count_add主要通过操作了current_thread_info()->preempt.count变量,此变量主要用于计数操作,由参数HARDIRQ_OFFSET可知操作的是硬件中断计数器,它占用4个bit,此计数值非0表示当前处于硬件中断上下文处理,此处是累加了preempt.count的HARDIRQ域的计数值,表示当前处于硬件中断上下文,同样在irq_exit时需要匹配递减。

/*
* We put the hardirq and softirq counter into the preemption
* counter. The bitmask has the following meaning:
*
* - bits 0-7 are the preemption count (max preemption depth: 256)
* - bits 8-15 are the softirq count (max # of softirqs: 256)
*
* The hardirq count could in theory be the same as the number of
* interrupts in the system, but we run all interrupt handlers with
* interrupts disabled, so we cannot have nesting interrupts. Though
* there are a few palaeontologic drivers which reenable interrupts in
* the handler, so we need more than one bit here.
*
* PREEMPT_MASK: 0x000000ff
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_MASK: 0x000f0000
* NMI_MASK: 0x00f00000
* PREEMPT_NEED_RESCHED: 0x80000000
*/
/*
*
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 4

#define PREEMPT_SHIFT 0
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)
#define HARDIRQ_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS)
#define NMI_SHIFT (HARDIRQ_SHIFT + HARDIRQ_BITS)

#define __IRQ_MASK(x) ((1UL << (x))-1)

#define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT)
#define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT)
#define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)
#define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)

#define PREEMPT_OFFSET (1UL << PREEMPT_SHIFT)
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT)
#define HARDIRQ_OFFSET (1UL << HARDIRQ_SHIFT)
#define NMI_OFFSET (1UL << NMI_SHIFT)

通过如上可知:current_thread_info()->preempt.count变量的计数值按如下进行划分:


|- -generic_handle_irq
int generic_handle_irq(unsigned int irq)
|--struct irq_desc *desc = irq_to_desc(irq);
|--generic_handle_irq_desc(desc);
|--desc->handle_irq(desc)
|--handle_fasteoi_irq(desc)
|--handle_irq_event(desc);
|--__handle_irq_event_percpu(desc, &flags);
|--for_each_action_of_desc(desc, action)
| //标记中断被强制线程化
|--if (irq_settings_can_thread(desc) &&
| !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
| lockdep_hardirq_threaded();
|--res = action->handler(irq, action->dev_id);
|--switch (res)
//primary handler处理完毕,需要唤醒中断线程
case IRQ_WAKE_THREAD:
__irq_wake_thread(desc, action);
//无中断线程
case IRQ_HANDLED:
*flags |= action->flags;

generic_handle_irq为通用中断处理

desc->handle_irq(desc): 我们在中断管理基础学习笔记 - 3. 创建软硬中断号映射一文的irq_domain_associate中初始化desc->handle_irq为handle_fasteoi_irq,那里我们提到了与handle_arch_irq是何种关系?此处可以做出回答,handle_arch_irq可以理解为中断从底层处理迈入顶层处理的入口,而desc->handle_irq为中断的回调,handle_arch_irq通过层层跋涉最终会调用到desc->handle_irq,并最终调用irqaction->handler,此处desc->handle_irq为handle_fasteoi_irq。

action->handler:此处就是注册中断一文中介绍的,request_threaded_irq会将参数handler传递给action->handler,此函数如果为空,将采用默认的irq_default_primary_handler,他将返回IRQ_WAKE_THREAD,唤醒中断线程执行,中断线程在注册中断时创建;如果primary handler返回的是IRQ_HANDLED,则表示没有中断线程

|- -irq_exit
void irq_exit(void)
|--__irq_exit_rcu();
| |--local_irq_disable();
| |--account_irq_exit_time(current);
| | //与irq_enter的对应域匹配,递减
| |--preempt_count_sub(HARDIRQ_OFFSET);
| | //退出中断上下文并且当前有软中断pending
| |--if (!in_interrupt() && local_softirq_pending())
| | //对软中断进行处理
| | invoke_softirq();
| |--tick_irq_exit();
|--rcu_irq_exit();
|--lockdep_hardirq_exit();

local_softirq_pending:主要用来判断当前是否有软中断pending

invoke_softirq:对软中断进行处理,后面会详细说明

|- - -local_softirq_pending
#define local_softirq_pending() (__this_cpu_read(local_softirq_pending_ref))
1
local_softirq_pending主要用来判断是否有软中断pending,其中local_softirq_pending_ref的定义如下:

#ifndef local_softirq_pending_ref
#define local_softirq_pending_ref irq_stat.__softirq_pending
#endif

这里irq_stat的定义如下:

#ifndef __ARCH_IRQ_STAT
DEFINE_PER_CPU_ALIGNED(irq_cpustat_t, irq_stat);
EXPORT_PER_CPU_SYMBOL(irq_stat)
#endif

irq_cpustat_t的定义如下:

typedef struct {
unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;

由此可见local_softirq_pending就是判断__softirq_pending的值是否为0,如果不为0表示软中断pending,__softirq_pending的不同bit位表示不同的软中断,在触发软中断时会设置。下一节介绍软中断时再来详细说明

参考文档
奔跑吧,Linux内核
————————————————
版权声明:本文为CSDN博主「HZero.chen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jasonactions/article/details/115751815

posted @ 2022-08-22 14:05  Sky&Zhang  阅读(459)  评论(0编辑  收藏  举报