先简单说明一下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种状态:
其转换过程如下:
发生中断信号走向:
至此,关于GIC相关的介绍基本可以满足阅读Linux内核关于中断处理的相关code
以下源代码基于ssd20x官方kernel为例,cortexA7,ARMv7架构,32位
中断向量入口位于:arch/arm/kernel/entry-armv.S
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
关于宏CONFIG_MULTI_IRQ_HANDLER,表示"允许每台机器在运行时指定它自己的IRQ处理程序",因为kernel一般支持多种平台,多种处理器,所以此选项一般开启
进入irq_handler有两种情况
一个是用户模式下发生了中断
.align 5
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
?另一个是内核态时发生了中断
.align 5
__irq_svc:
svc_entry
irq_handler
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception
UNWIND(.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_SMP
set_smp_cross_call(gic_raise_softirq);
#endif
cpuhp_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大多都是流水线工作方式,并且可以乱序执行,内存屏障保证语句前后的指令具备严格的先后执行顺序
|