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大多都是流水线工作方式,并且可以乱序执行,内存屏障保证语句前后的指令具备严格的先后执行顺序


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部