ARM GIC简介与Linux中断处理分析
先簡單說明一下GIC(具體詳盡的介紹請查閱ARM GIC相關文檔)
GIC即general interrupt controller。
它是一個架構,版本歷經了GICv1(已棄用),GICv2,GICv3,GICv4。對于不同的GIC版本,arm公司設計了對應的GIC IP
GIC的核心功能:對soc中外設的中斷源的管理,并且提供給軟件,配置以及控制這些中斷源。
下面一張ARM GICv2 的圖
中斷源類型說明:
SGI(Software-generated interrupt):范圍0 - 15,軟件觸發的中斷,一般用于核間通訊
PPI(Private peripheral interrupt ): 范圍16 - 31,私有外設中斷,只對指定的core有效
SPI(Shared peripheral interrupt):范圍32 - 1019,共享中斷,不限定特定的core
控制分兩部分,Distributor和CPU interface
Distributor對中斷的控制包括:
(1)中斷enable或者disable的控制
(2)將當前優先級最高的中斷事件分發到一個或者一組CPU interface
(3)優先級控制
(4)interrupt屬性設定。例如是level-sensitive還是edge-triggered
(5)interrupt group的設定
CPU interface:將GICD發送的中斷信息,通過IRQ,FIQ管腳,傳輸給core
GIC對中斷的處理包括以下4種狀態:
-
inactive:中斷處于無效狀態
-
pending:中斷處于有效狀態,但是cpu沒有響應該中斷
-
active:cpu在響應該中斷
-
active and pending:cpu在響應該中斷,但是該中斷源又發送中斷過來
其轉換過程如下:
發生中斷信號走向:
至此,關于GIC相關的介紹基本可以滿足閱讀Linux內核關于中斷處理的相關code
以下源代碼基于ssd20x官方kernel為例,cortexA7,ARMv7架構,32位
中斷向量入口位于:arch/arm/kernel/entry-armv.S
/** Interrupt handling.*/.macro irq_handler #ifdef CONFIG_MULTI_IRQ_HANDLERldr r1, =handle_arch_irqmov r0, spbadr lr, 9997fldr pc, [r1] #elsearch_irq_handler_default #endif關于宏CONFIG_MULTI_IRQ_HANDLER,表示"允許每臺機器在運行時指定它自己的IRQ處理程序",因為kernel一般支持多種平臺,多種處理器,所以此選項一般開啟
進入irq_handler有兩種情況
一個是用戶模式下發生了中斷
.align 5 __irq_usr:usr_entrykuser_cmpxchg_checkirq_handlerget_thread_info tskmov why, #0b ret_to_user_from_irqUNWIND(.fnend ) ENDPROC(__irq_usr)?另一個是內核態時發生了中斷
.align 5 __irq_svc:svc_entryirq_handler#ifdef CONFIG_PREEMPTldr r8, [tsk, #TI_PREEMPT] @ get preempt countldr r0, [tsk, #TI_FLAGS] @ get flagsteq r8, #0 @ if preempt count != 0movne r0, #0 @ force flags to 0tst r0, #_TIF_NEED_RESCHEDblne svc_preempt #endifsvc_exit r5, irq = 1 @ return from exceptionUNWIND(.fnend ) ENDPROC(__irq_svc)在irq_handler中handle_arch_irq即為動態指定的中斷處理函數
位于arch/arm/kernel/irq.c
#ifdef CONFIG_MULTI_IRQ_HANDLER void __init set_handle_irq(void (*handle_irq)(struct pt_regs *)) {if (handle_arch_irq)return;handle_arch_irq = handle_irq; } #endif在gic初始化drivers/irqchip/irq-gic.c過程進行設置set_handle_irq(gic_handle_irq);
static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start,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; #ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq); #endifcpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"AP_IRQ_GIC_STARTING",gic_starting_cpu, NULL);-----初始化過程中注冊具體的處理函數set_handle_irq(gic_handle_irq);if (static_key_true(&supports_deactivate))pr_info("GIC: Using split EOI/Deactivate mode\n");}if (static_key_true(&supports_deactivate) && 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, irq_start, handle);if (ret)kfree(name);return ret; }?函數gic_handle_irq即為具體的中斷處理函數,此時中斷號是被GIC屏蔽的
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 {
-----從寄存器讀取硬件中斷號
?? ??? ?irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
?? ??? ?irqnr = irqstat & GICC_IAR_INT_ID_MASK;
?????? ?
#if defined(CONFIG_MP_IRQ_TRACE)
???????????? ms_records_irq_count(irqnr);
#endif
-----處理PPI 及 SPI中斷
?? ??? ?if (likely(irqnr > 15 && irqnr < 1020)) {
?? ??? ??? ?if (static_key_true(&supports_deactivate))
?? ??? ??? ??? ?writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
?? ??? ??? ?handle_domain_irq(gic->domain, irqnr, regs);
?? ??? ??? ?continue;
?? ??? ?}
------處理SGI中斷
?? ??? ?if (irqnr < 16) {
?? ??? ??? ?writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
?? ??? ??? ?if (static_key_true(&supports_deactivate))
?? ??? ??? ??? ?writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP
?? ??? ??? ?/*
?? ??? ??? ? * 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_raise_softirq
?? ??? ??? ? */
-----內存屏障
?? ??? ??? ?smp_rmb();
-----進入處理
?? ??? ??? ?handle_IPI(irqnr, regs);
。。。。。
?? ??? ??? ?continue;
?? ??? ?}
?? ??? ?break;
?? ?} while (1);
}
?handle_domain_irq的實現如下:
#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/**
?* __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
?* @domain:?? ?The domain where to perform the lookup
?* @hwirq:?? ?The HW irq number to convert to a logical one
?* @lookup:?? ?Whether to perform the domain lookup or not
?* @regs:?? ?Register file coming from the low-level handling code
?*
?* Returns:?? ?0 on success, or -EINVAL if conversion has failed
?*/
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;
--------關閉搶占
?? ?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 {
------執行處理
?? ??? ?generic_handle_irq(irq);? ----實際為注冊IRQ號對應的函數:desc->handle_irq(desc);???????
?? ?}
----關閉搶占以及檢測是否有軟中斷需要處理
?? ?irq_exit();
?? ?set_irq_regs(old_regs);
?? ?return ret;
}
#endif
關于搶占,即在適當的時機及時調用更高優先級的任務;
幾個搶占點:
從中斷返回到內核空間時;解鎖或使能軟中斷時;調用 preempt_enable 使能搶占;
顯式調度;任務阻塞時;
不可搶占點:
正處于中斷中;持有鎖時;正在調度時;對Per-CPU操作時
搶占的具體實現為對一個preempt_count變量進行互斥操作,位于thread_info結構中
static __always_inline volatile int *preempt_count_ptr(void)
{
return ¤t_thread_info()->preempt_count;
}
static __always_inline void __preempt_count_add(int val)
{
*preempt_count_ptr() += val;
}
static __always_inline void __preempt_count_sub(int val)
{
*preempt_count_ptr() -= val;
}
#define preempt_count_add(val) __preempt_count_add(val)
#define preempt_count_sub(val) __preempt_count_sub(val)
#define preempt_count_dec_and_test() __preempt_count_dec_and_test()
#define preempt_count_inc() preempt_count_add(1)
#define preempt_count_dec() preempt_count_sub(1)
借用一張圖詳細描述
關于軟中斷,中斷底半部的一種實現方式,靜態分配,一共10種,可以充分利用SMP性能;
其執行點一般在irq_exit時或者獨立的內核線程中
在irq_exit中會進行有沒有待處理軟中斷的檢測(其實就是判斷preempt_count的BIT8-15)
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
local_irq_disable();
#else
lockdep_assert_irqs_disabled();
#endif
account_irq_exit_time(current);
-----計數-1
preempt_count_sub(HARDIRQ_OFFSET);
----不在中斷中以及有待處理軟中斷
if (!in_interrupt() && local_softirq_pending())
????????invoke_softirq();? ----實際為__do_softirq或者wakeup_softirqd
tick_irq_exit();
rcu_irq_exit();
trace_hardirq_exit(); /* must be last! */
}
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;
#if defined(CONFIG_MP_IRQ_TRACE)
MSYS_IRQ_INFO irq_info;
#endif
/*
* Mask out PF_MEMALLOC s current task context is borrowed for the
* softirq. A softirq handled such as network RX might set PF_MEMALLOC
* again if the socket is related to swap
*/
current->flags &= ~PF_MEMALLOC;
pending = local_softirq_pending();
account_irq_enter_time(current);
-----軟中斷不可嵌套
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
in_hardirq = lockdep_softirq_start();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
----開啟中斷,此后的部分隨時會被中斷打斷;因此要保證軟中短處理好函數的可重入性
local_irq_enable();
h = softirq_vec;
while ((softirq_bit = ffs(pending))) {
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1;
vec_nr = h - softirq_vec;
prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
#if defined(CONFIG_MP_IRQ_TRACE)
irq_info.IRQNumber = vec_nr;
irq_info.action = h->action;
irq_info.timeStart = sched_clock();
#endif
trace_softirq_entry(vec_nr);
---執行處理函數
h->action(h);
trace_softirq_exit(vec_nr);
#if defined(CONFIG_MP_IRQ_TRACE)
irq_info.timeEnd = sched_clock();
if (irq_info.timeEnd - irq_info.timeStart > 2500000)
{
if(sirq_head_initialized)
{
ms_records_sirq(&irq_info);
}
}
#endif
if (unlikely(prev_count != preempt_count())) {
pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
vec_nr, softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count_set(prev_count);
}
h++;
pending >>= softirq_bit;
}
rcu_bh_qs();
----處理完后關閉中斷
local_irq_disable();
----再次檢測是否有待處理軟中斷(因為軟中斷處理過程中開啟了中斷)
pending = local_softirq_pending();
if (pending) {
----軟中斷有最大執行時間限制
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
---有的話繼續執行軟中斷
goto restart;
----軟中斷積累太多,調用獨立的內核線程ksoftirqd執行,每個core都有一個ksoftirqd,其實際上是一個Per-CPU變量
wakeup_softirqd();
}
lockdep_softirq_end(in_hardirq);
account_irq_exit_time(current);
-----軟中斷底半部使能
__local_bh_enable(SOFTIRQ_OFFSET);
WARN_ON_ONCE(in_interrupt());
tsk_restore_flags(current, old_flags, PF_MEMALLOC);
}
總結:
1. GIC分為兩部分,Distributor(處理優先級,中斷保持,信號分發等)和CPU interface(響應及完成中斷等)
2. Linux中斷不可嵌套
3. Linux中斷處理分為上半部(快速處理和響應)和底半部(延遲執行)
4. 底半部常用手段有軟中斷(可以在不同CPU并發),tasklet(同一個tasklet在同一個CPU排隊執行)以及工作隊列(可睡眠)
5. 關于Per-CPU變量,編譯時位于特定的section中,加載內存后,每個cpu都有一份拷貝,各自用各自的獨立拷貝,不存在多cpu訪問互斥問題,只有本cpu線程和本cpu中斷互斥訪問問題
6. 關于內存屏障,因為cpu大多都是流水線工作方式,并且可以亂序執行,內存屏障保證語句前后的指令具備嚴格的先后執行順序
總結
以上是生活随笔為你收集整理的ARM GIC简介与Linux中断处理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记最近Linux中遇到cpu使用率低lo
- 下一篇: Openwrt按键检测分析-窥探Linu