IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> ARM GIC简介与Linux中断处理分析 -> 正文阅读

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

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-11 12:35:13  更:2021-08-11 12:35:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:42:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码