中斷管理基礎學習筆記 - 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章