linux arm64 中断处理流程完整分析 (二)—— 中断处理流程c代码部分 以gic_v3 为例

上一篇的传送门:linux arm64 中断处理流程完整分析 (一)—— 中断向量表、中断处理流程汇编部分

本文以gic 为例,探索linux 内核中断控制器的注册过程,中断回调注册过程,以及相关中断处理流程。

GIC V3 结构简单介绍

为了便于看gic 的代码,这里简单介绍gic 的结构。详细可以参考这个页面,以及附录中的arm 官方文档。

GICv3 包含以下几个逻辑组件:

  1. 一个 Distributor
  2. 每个PE(Process Element,处理器核) 一个Redistributor
  3. 每个PE 一个 CPU interface
    在这里插入图片描述

linux 中断数据结构

irq_domain

irq_domain 用来描述一个中断控制器。在irq-gic-v3.c 中,gic driver在probe 时,会创建一个irq_domian。
一个irq_domain 结构体上可以挂接多个中断号。比如一个gic 上接入了n个中断线,那么对应的irq_domain上就挂接了n 个中断号。
irq_domain->ops 中包含了该控制器对应的操作函数,在irq-gic-v3.c 中,实现了.translate, .alloc, .free, .select 这四个方法。
详细定义如下:

struct irq_domain {
	struct list_head link;
	const char *name;
	const struct irq_domain_ops *ops;
	void *host_data;
	unsigned int flags;
	unsigned int mapcount;

	/* Optional data */
	struct fwnode_handle *fwnode;
	enum irq_domain_bus_token bus_token;
	struct irq_domain_chip_generic *gc;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	struct dentry		*debugfs_file;
#endif

	/* reverse map data. The linear map gets appended to the irq_domain */
	irq_hw_number_t hwirq_max;
	unsigned int revmap_direct_max_irq;
	unsigned int revmap_size;
	struct radix_tree_root revmap_tree;
	struct mutex revmap_tree_mutex;
	unsigned int linear_revmap[];
};

irq_desc

  1. irq_desc 用来描述一个中断(号)。
  2. 在dts 被解析时,如果一个dts 中的设备节点中引用了中断号,那么将对每一个中断号创建一个irq_desc 数据结构。
  3. 所有的irq_desc 会被插入irqdesc.c 中的全局static 变量irq_desc_tree 中:radix_tree_insert(&irq_desc_tree, irq, desc);
    此后通过radix_tree_lookup(&irq_desc_tree, irq); 即可从(int)irq 号获得这个irq_desc。
    如下只列出了该结构体的部分定义。
struct irq_desc {
	struct irq_common_data	irq_common_data;
	struct irq_data		irq_data;				/* 重要 */
	unsigned int __percpu	*kstat_irqs;
	irq_flow_handler_t	handle_irq;
	struct irqaction	*action;	/* IRQ action list */
	unsigned int		status_use_accessors;
	unsigned int		core_internal_state__do_not_mess_with_it;
	unsigned int		depth;		/* nested irq disables */
	unsigned int		wake_depth;	/* nested wake enables */
	unsigned int		tot_count;
	unsigned int		irq_count;	/* For detecting broken IRQs */
	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
	unsigned int		irqs_unhandled;
	atomic_t		threads_handled;
	int			threads_handled_last;
	raw_spinlock_t		lock;
	struct cpumask		*percpu_enabled;
	const struct cpumask	*percpu_affinity;
	...

irq_data

irq_desc 直接包含irq_data。 会以hwirq 为索引,被插入到 irq_domain 的revmap_tree中:radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
同时,通过container_of,就可以从irq_data 得到相应的irq_desc。
irq_data->chip 在解析dts,初始化中断资源时,会指向irq-gic-v3.c 中定义的chip 结构体。irq_chip 的定义与作用见下小节

struct irq_data {
	u32			mask;
	unsigned int		irq;
	unsigned long		hwirq;
	struct irq_common_data	*common;
	struct irq_chip		*chip;
	struct irq_domain	*domain;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data;
#endif
	void			*chip_data;
};

irq_chip

irq_chip 中定义了很多方法,这些方法都是需要中断控制器去实现的,主要用来操作的中断控制器的硬件。
例如,irq_mask 方法在irq-gic-v3.c 中指向gic_mask_irq()。disable_irq() 接口会通过virq找到对应的irq_desc,进而通过irq_desc->irq_data.chip->irq_mask ,最终调用到gic_mask_irq()。这个函数最终会通过写gic 中的 GICD_ICENABLER寄存器来mask 中断。

struct irq_chip {
	struct device	*parent_device;
	const char	*name;
	unsigned int	(*irq_startup)(struct irq_data *data);
	void		(*irq_shutdown)(struct irq_data *data);
	void		(*irq_enable)(struct irq_data *data);
	void		(*irq_disable)(struct irq_data *data);

	void		(*irq_ack)(struct irq_data *data);
	void		(*irq_mask)(struct irq_data *data);
	void		(*irq_mask_ack)(struct irq_data *data);
	void		(*irq_unmask)(struct irq_data *data);
	void		(*irq_eoi)(struct irq_data *data);
	...
};

gic 初始化流程

这里主要做的事情:

  1. 创建一个irq_domain
  2. gic 初始化
  3. 设置内核中断回调,使得中断发生时能从中断向量表跳过来

主要代码分析如下

/*drivers/irqchip/irq-gic-v3.c*/
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
gic_of_init(struct device_node *node, struct device_node *parent)
	"从dts中获取distributor,以及redistributor 的地址"
	gic_init_bases(dist_base, rdist_regs, nr_redist_regions, redist_stride, &node->fwnode);
		"获取一共有多少个中断"
		gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data)
			__irq_domain_add(fwnode, size=0, hwirq_max=~0, direct_max=0, ops, host_data)
				/*这里分配并填充了一个domain*/
				domain = kzalloc_node(...)
				domain->name = name;
				domain->revmap_size = size; /*此处size==0*/
				domain->revmap_direct_max_irq = direct_max; /*此处direct_max==0*/
				...
				/* 这里设置了IRQ_DOMAIN_FLAG_HIERARCHY 这个标志位。表面是层级结构。此处关系到后续中断号的映射方式 */
				irq_domain_check_hierarchy(domain);
					if (domain->ops->alloc)
						domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;
				...
				return domian;
		/*设置irq handle 为gic_handle_irq,使得有中断时从中断向量表跳过来*/
		set_handle_irq(gic_handle_irq);

内核初始化时的中断资源初始化过程

主要做的事情:

  1. 内核启动时,在解析dts 被创建device 时,会对这个dts node 中引用的每个中断进行 irq_create_of_mapping。所谓create mapping 实现的作用就是分配一个irq_desc,并将这个irq_desc 中的irq_data 以hwirq 为index插入所在irq_domain 的 domain->revmap_tree。
  2. 同时virq 也是在此时分配的。virq 是整个内核全局通用的,使用allocated_irqs 这个全局变量来记录(见kernel/irq/irqdesc.c)。hwirq 是domain 内部的。

设备的中断信息会被描述在dts中。内核在解析dts 时,对每个中断资源进行了初始化,主要是创建irq_desc,建立hwirq与virq 的mapping关系。

of_device_alloc 为入口

在 of_device_alloc时(内核是如何调到这个函数的可以参考这个博客),对每个设备节点中描述的中断都进程了资源初始化。具体如下:

of_device_alloc
	of_irq_to_resource_table(np, res, num_irq)
		for (i = 0; i < nr_irqs; i++, res++)
			of_irq_to_resource(dev, i, res)
				irq = of_irq_get(dev, index);
					/*Decode a node's IRQ and return it as a Linux IRQ number*/
					of_irq_parse_one(dev, index, &oirq);
					/*在irq_domain_list 中找到当前irq 对应的irq_domain*/
					domain = irq_find_host(oirq.np);
						/*
						 * 如果domain 都没找到,那就稍后重试(等domian 创建之后)。
						 * domain 是在gic 初始化时创建的,详见本文上一小节
						 */
						if (!domain)	
							return -EPROBE_DEFER;
						irq_create_of_mapping(&oirq)
							of_phandle_args_to_fwspec(irq_data=oirq, &fwspec);
							irq_create_fwspec_mapping(&fwspec);
								/*
								 * 从下边find mapping 的过程就可以看出来,创建mapping,肯定就是去创建data 来记录这个
								 * hwirq。创建mapping 是在后边 irq_domain_alloc_irqs --> irq_domain_insert_irq 中
								 * 完成的。
								 */
								virq = irq_find_mapping(domain, hwirq);
									data = radix_tree_lookup(&domain->revmap_tree, hwirq);
									return data ? data->irq : 0;
								if (virq == 0)
								 	/*对于gic来说,是hierarchy*/
									if (irq_domain_is_hierarchy(domain))
										/*单独分析下边的函数*/
										virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
				res->start = res -> end = irq;
				...

irq_domain_alloc_irqs 详解

/*
 * 这个函数的主要工作:
 * 1. irq_domain_alloc_descs
 * 2. irq_domain_alloc_irq_data
 * 3. domain->ops->alloc
 * 4. irq_domain_insert_irq(virq + i); 将irq_data 以hwirq 作为index插入 domain->revmap_tree_mutex。
 *    此后irq_find_mapping即可返回有效值。
 */
irq_domain_alloc_irqs(domain, nr_irqs = 1, node = NUMA_NO_NODE, arg = fwspec);
	__irq_domain_alloc_irqs(domain, irq_base = -1, nr_irqs = 1, node, arg, realloc = false, affinity = NULL);
		/*irq_domain_alloc_descs在后边展开*/
		virq = irq_domain_alloc_descs(irq_base = -1, nr_irqs = 1, 0, node, affinity);
		irq_domain_alloc_irq_data(domain, virq, nr_irqs = 1)
			/* The outermost irq_data is embedded in struct irq_desc */
			/* virq 对应的irq_data 嵌入在上面已经分配好的irq_desc 中,这里只是找到这个irq_data,然后填充了domain等 */
			irq_data = irq_get_irq_data(virq + i);
			irq_data->domain = domain;
			/* domian->parent不为空时,为其创建了一个irq_data */
			for (parent = domain->parent; parent; parent = parent->parent) {
				irq_data = irq_domain_insert_irq_data(domain = parent, child = irq_data);
					irq_data = kzalloc_node(sizeof(*irq_data), GFP_KERNEL, irq_data_get_node(child));
					child->parent_data = irq_data;
					irq_data->irq = child->irq;
					irq_data->common = child->common;
					irq_data->domain = domain;
					return irq_data;
		mutex_lock(&irq_domain_mutex);
		irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
			domain->ops->alloc(domain, irq_base, nr_irqs, arg);
				/*
				 * 主要作用:
				 * 1. 填充irq_data->hwirq。至此也就建立了virq 和hwirq的联系————两者都保存在irq_data中
				 * 2. 填充了irq_data->chip。
				 * 3. 设置了desc->handle_irq = handle_fasteoi_irq。每次中断的必经之路
				 * 后边有代码分析
				 */
				gic_irq_domain_alloc(domain, irq_base, nr_irqs, arg)		
					
		irq_domain_insert_irq(virq + i);
			data = irq_get_irq_data(virq)
			domain->mapcount++;
			irq_domain_set_mapping(domain, data->hwirq, data);
				mutex_lock(&domain->revmap_tree_mutex);
				radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
				mutex_unlock(&domain->revmap_tree_mutex);
			irq_clear_status_flags(virq, IRQ_NOREQUEST);
		mutex_unlock(&irq_domain_mutex);
irq_domain_alloc_descs 详解
virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity);
	__irq_alloc_descs(irq=-1, from = hint = hwirq % nr_irqs, cnt=1, node, THIS_MODULE, affinity=NULL);
		/*找到并判断该中断desc 是否已经被创建*/
		start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS, from, cnt, 0);
		/*此时的start 大概率还是等于hwirq*/
		alloc_descs(start, cnt, node, affinity, owner);
			for (i = 0; i < cnt; i++)
				desc = alloc_desc(irq = start + i, node, flags, mask, owner);
					desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node);
					/* 为每个cpu 都分配了一个kstat,即该中断在每个cpu 上都有独立的stat 状态 */
					desc->kstat_irqs = alloc_percpu(unsigned int);
					alloc_masks(desc, node);
					/*接下来是一系列的初始化*/
					raw_spin_lock_init(&desc->lock);
					lockdep_set_class(&desc->lock, &irq_desc_lock_class);	/*Linux 死锁检测模块*/
					mutex_init(&desc->request_mutex);
					init_rcu_head(&desc->rcu);	/*rcu, Read Copy Update。一种多线程读,单线程写场景下适用的机制*/
				
					desc_set_defaults(irq, desc, node, affinity, owner);
					irqd_set(&desc->irq_data, flags);
						desc->irq_data.common->state_use_accessors |= flags;
					kobject_init(&desc->kobj, &irq_kobj_type);
												
				irq_insert_desc(start + i, desc);
					/* 将该irq 插入irq_desc_tree 这个radix树 */
					radix_tree_insert(&irq_desc_tree, irq, desc);
				irq_sysfs_add(start + i, desc);
				irq_add_debugfs_entry(start + i, desc);
			/* 表示该中断desc已经被创建 */
			bitmap_set(allocated_irqs, start, cnt);
			return start; /*所以最终的virq 就是allocated_irqs 这个bitsmap中的某个bit的偏移*/
gic_irq_domain_alloc详解

主要作用:

  1. 填充irq_data->hwirq。至此也就建立了virq 和hwirq的联系————两者都保存在irq_data中
  2. 填充了irq_data->chip。
  3. 设置了desc->handle_irq = handle_fasteoi_irq。每次中断的必经之路
gic_irq_domain_alloc(domain, irq_base, nr_irqs, arg)
	/*从dts 的interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH> 中获取hwirq,及中断触发类型*/
	gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
	gic_irq_domain_map(domain, virq, hwirq);
		/* 这里很关键,设置了desc->handle_irq。也就是说中断到来之后,会跑到handle_fasteoi_irq */
		irq_domain_set_info(domain, virq, hwirq, chip=&gic_chip, chip_data=d->host_data,
															handler=handle_fasteoi_irq, NULL, NULL);
			/* 重点! irq_data.chip 就是在这个时候设置的 */
			irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
				irq_data = irq_domain_get_irq_data(domain, virq); 
				irq_data->hwirq = hwirq;
				irq_data->chip = chip ? chip : &no_irq_chip;
				irq_data->chip_data = chip_data;
			__irq_set_handler(virq, handler, is_chained=0, NULL);
				desc->handle_irq = handle;
				desc->name = name;
			irq_set_handler_data(virq, NULL);
				desc->irq_common_data.handler_data = data;
	
		irq_set_probe(irq);
			irq_modify_status(irq, IRQ_NOPROBE, 0);
		
		irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
			/* 没看出来IRQD_SINGLE_TARGET 有什么作用 */
			__irqd_to_state(d) & IRQD_SINGLE_TARGET;

中断注册过程

request_irq,也就是中断注册过程,主要做了以下几件事:

  1. irq_to_desc, 根据irq 找到相应的desc
  2. 分配一个irqaction 数据结构,且将request_irq 所传进来的handler, flags, dev等参数保存在这个action 中
  3. kthread_create 新建一个kthread。这个thread 的函数体为统一的irq_thread,irq_thread 的参数为上面新分配的action
  4. 将上述action 挂接到当前desc 的action 链表上。以便于中断到来时逐个执行action

从request_irq 开始的关键代码分析如下

/*include/linux/interrupt.h*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
	request_threaded_irq(irq, handler, thread_fn=NULL, irqflags=flags, devname=name, dev_id=dev)
		desc = irq_to_desc(irq);
		/* 关键,这里分配并填充了一个action,保存了request_irq 传递下来的所有参数 */
		action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
		action->handler = handler;
		action->thread_fn = thread_fn;
		action->flags = irqflags;
		action->name = devname;
		action->dev_id = dev_id;
			
		/*电源域上电*/
		irq_chip_pm_get(&desc->irq_data);
		
		/* 这里大有文章。这里假设IRQF_ONESHOT ,且是shared*/
		__setup_irq(irq, desc, new=action);
			/* 将irq 标记为线程化irq */
			irq_setup_forced_threading(new);
				new->flags |= IRQF_ONESHOT;
				set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
				new->thread_fn = new->handler;
				/* 可以看到这个handler 会直接返回IRQ_WAKE_THREAD */
				new->handler = irq_default_primary_handler {return IRQ_WAKE_THREAD;};
			setup_irq_thread(new, irq, secondary=false);
				/*这里创建了一个线程,irq_thread 是一个loop,后边分析*/
				t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
				/* 设置该线程的优先级 */
				sched_setscheduler_nocheck(t, SCHED_FIFO, &param);
				
				/* 引用一下这个task,避免irq_tread 退出后被释放 */
				get_task_struct(t); 
				new->thread = t;
				
				/* IRQTF_AFFINITY 的含义是irq_thread 被要求调整affinity */
				set_bit(IRQTF_AFFINITY, &new->thread_flags);

			/* 还没搞清楚是干嘛的?*/
			irq_request_resources(desc);
			
			/*  */
			old_ptr = &desc->action;
			old = *old_ptr;
			if (old) {
				/* 如果old 存在,表明这个中断是shared。 */
				接下来检查了new 和old 的是否存在冲突,包括以下几个方面:
					IRQF_SHARED, IRQF_TRIGGER_MASK, IRQF_ONESHOT, IRQF_PERCPU
				
				/*
				 * 如下这个循环遍历完,old_ptr 等于最后一个action->next的地址。最后给*old_ptr = new,就相当于
				 * 最后一个action->next = new
				 */
				do {
					//Or all existing action->thread_mask bits,so we can find the next zero bit for thisnew action.
					thread_mask |= old->thread_mask;
					old_ptr = &old->next;
					old = *old_ptr;
				} while (old);
				shared = 1;
			}
			if (new->flags & IRQF_ONESHOT)
				/*
				 * thread_mask 用来标记一个IRQF_ONESHOT thread handler被唤醒。
				 * 当这个thread 完成后,该bit 清0。
				 * 当一个shared 中断的所有thread 完成后,
				 * desc->threads_active becomes zero and the interrupt line is unmasked
				 */
				new->thread_mask = 1UL << ffz(thread_mask);
			
			/* 将当前action 挂到action 链表的末尾 */
			*old_ptr = new;
			
			irq_pm_install_action(desc, new);
				desc->nr_actions++;
			desc->irq_count = 0;
			desc->irqs_unhandled = 0;
			
			/* 释放各种锁 */
			raw_spin_unlock_irqrestore(&desc->lock, flags);
			chip_bus_sync_unlock(desc);
			mutex_unlock(&desc->request_mutex);

			/* 没看懂,测量啥? */
			irq_setup_timings(desc, new);
			/*注册proc 文件节点*/
			register_irq_proc(irq, desc);
			new->dir = NULL;
			/* create /proc/irq/1234/handler/ */
			register_handler_proc(irq, new);

中断响应过程

gic_handle_irq

接下来分析 gic_handle_irq 过程。如何从中断向量表中跳的该函数请参考上一篇文章

/*regs入参是在汇编代码中将系统寄存器保存进来的*/
gic_handle_irq(struct pt_regs *regs)
	do {
		/*此处读的是系统寄存器ICC_IAR1_EL1,及中断号INTID*/
		irqnr = gic_read_iar();
		if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
			/*
			 * eoir(ICC_EOIR1_EL1, End Of Interrupt Register), 
			 * to inform the CPU interface that it has completed 
			 * the processing of the specified Group 1 interrupt.
			 */
			gic_write_eoir(irqnr);
			handle_domain_irq(gic_data.domain, irqnr, regs);
				/*保存中断发生时的现场堆栈到__irq_regs,此后就可以通过get_irq_regs 获得中断发生时的现场堆栈。*/
				old_regs = set_irq_regs(regs);
				/*进入中断上下文。告诉系统,现在正在处理中断的上半部分工作,不可以进行调度*/
				irq_enter();
				if (lookup)
					irq = irq_find_mapping(domain, hwirq);
				generic_handle_irq(irq);
					desc = irq_to_desc(irq);
					generic_handle_irq_desc(desc);
						desc->handle_irq(desc);
							/*后边分析代码*/
							handle_fasteoi_irq(desc)
				/* 退出中断上下文 */
				irq_exit();
				set_irq_regs(old_regs);
		}
	} while ( while (irqnr != ICC_IAR1_EL1_SPURIOUS))

handle_fasteoi_irq

handle_fasteoi_irq(struct irq_desc *desc)
	raw_spin_lock(&desc->lock);
	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
	/* 中断统计,会统计每个cpu 以及所有的*/
	kstat_incr_irqs_this_cpu(desc);
	handle_irq_event(desc);
		desc->istate &= ~IRQS_PENDING;
		irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);

		/*此后没有修改desc 的地方,所以unlock了*/
		raw_spin_unlock(&desc->lock);
	
		handle_irq_event_percpu(desc);
			__handle_irq_event_percpu(desc, &flags);
				record_irq_time(desc);

				for_each_action_of_desc(desc, action) {
					irqreturn_t res;
			
					trace_irq_handler_entry(irq, action);
					/*
					 * 在注册中断时,在函数request_irq -> request_threaded_irq -> __setup_irq ->
					 * irq_setup_forced_threading 中,
					 * new->handler = irq_default_primary_handler {return IRQ_WAKE_THREAD;};
					 * 因此下边这里会直接返回 IRQ_WAKE_THREAD。真正的中断回调在被唤醒的线程中
					 */
					res = action->handler(irq, action->dev_id);
					trace_irq_handler_exit(irq, action, res);
					
					switch (res) {
					case IRQ_WAKE_THREAD:
						/*
						 * Catch drivers which return WAKE_THREAD but
						 * did not set up a thread function
						 */
						if (unlikely(!action->thread_fn)) {
							warn_no_thread(irq, action);
							break;
						}
						/* 此处唤醒中断回调线程,即irq_thread 处执行,见下小节 */
						__irq_wake_thread(desc, action);
			
						/* Fall through to add to randomness */
					case IRQ_HANDLED:
						*flags |= action->flags;
						break;
			
					default:
						break;
					}



			/* 将设备两次中断的时间间隔作为噪声源将随机数据加入熵池 */
			add_interrupt_randomness(desc->irq_data.irq, flags);

	
		raw_spin_lock(&desc->lock);
		irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

	cond_unmask_eoi_irq(desc, chip);
	raw_spin_unlock(&desc->lock);

irq_thread

这里就已经不是真正意义的中断上下文了,只是一个普通的线程

static int irq_thread(void *data)
	/* irq_wait_for_interrupt(action)中会schedule(); */
	while (!irq_wait_for_interrupt(action)) {
		action_ret = handler_fn(desc, action);
			irq_forced_thread_fn(desc, action);
				ret = action->thread_fn(action->irq, action->dev_id);
	}

终于写完这一篇,这个可能是耗时最长的一个博客了。
过程中遇到很多linux 进程管理的内容,下一个主题就看进程管理吧。

附录

非IRQ_DOMAIN_FLAG_HIERARCHY 中断mapping 流程

gic 属于HIERARCHY属性的irq_domain,这里额外看一下没有HIERARCHY 属性时,irq_create_mapping 的过程。假设定义了CONFIG_SPARSE_IRQ宏。

irq_create_mapping(domain, hwirq);
	/* 为hwirq号分配一个irq_desc*/
	virq = irq_domain_alloc_descs(-1, cnt=1, hwirq, of_node_to_nid(of_node), NULL);
		
	/* 建立domain, virq, hwirq 的联系 */
	irq_domain_associate(domain, virq(start), hwirq)
		irq_data = irq_get_irq_data(virq);
			desc = irq_to_desc(virq); /*这里就是用radixtree 去查找的*/
			return &desc->irq_data 
			mutex_lock(&irq_domain_mutex);
		irq_data->hwirq = hwirq;
		irq_data->domain = domain;
		/*gic-v3 这个 domain的ops 并没有map 函数,因此这里没有调用map*/
		domain->mapcount++;
		irq_domain_set_mapping(domain, hwirq, irq_data);
			/*注:domain->revmap_size == 0*/
			
			/* 
			 * 以hwirq 为索引,将irq_data 插入&domain->revmap_tree ,此后就可以通过hwirq 得到irq_data 了。
			 * 同时irq_data 隶属于irq_desc,所以根据container_of 可以直接得到对应的irq_desc
			 */
			radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);

		mutex_unlock(&irq_domain_mutex);
		irq_clear_status_flags(virq, IRQ_NOREQUEST);
			desc = irq_get_desc_lock(irq, &flags, 0); /*未完。。。*/	

参考文献

  1. Generic Interrupt Controller Architecture Specification GIC architecture version 3 and version 4
  2. irq_enter, irq_exit
  3. linux中断子系统 - irq_desc的创建
  4. Linux中断子系统domain
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: "Linux ARM GIC 中断流程" 是指在 ARM 架构的 Linux 操作系统中,使用 Generic Interrupt Controller(GIC处理器来管理系统中各种中断流程。它负责将来自不同设备和外部事件的中断请求,分派给适当的处理程序来处理,并确保系统按顺序执行这些处理程序。中断处理是系统中一个重要的组成部分,它允许设备和软件互相通信,并提高了系统的稳定性和可靠性。 ### 回答2: Linux是一个非常流行的开源操作系统,可以在不同架构的计算机上运行。ARM架构是一种广泛使用的嵌入式系统架构,因此,许多嵌入式设备都使用Linux作为其操作系统。在ARM架构上,通常会使用GIC(通用中断控制器)来管理中断,这个过程可以分为中断触发、CPU响应和中断处理三个部分中断触发是指中断信号从设备到达GIC的过程。当一个设备需要发送一个中断时,它会向GIC发送一个中断请求信号,并指定中断号,这个中断号是唯一的,用于区分不同的中断GIC会根据中断号去查找到这个中断对应的中断控制器,进而把这个请求传递给指定的CPU。 CPU响应是指CPU接收到中断请求信号后的响应过程。当GIC中断请求传递给CPU时,CPU需要检查是否允许这个中断请求,也就是检查中断屏蔽寄存器(Interrupt Mask Register)。如果这个中断请求已被屏蔽,则CPU不会响应,否则,它会设置自己的中断挂起寄存器(Interrupt Pending Register),告诉GIC它已经准备好去处理这个中断中断处理是指CPU执行中断处理程序,处理具体的中断。当CPU设置了它的中断挂起寄存器后,GIC会向CPU发送一个中断信号。CPU会暂停当前的进程,并把当前的上下文信息(比如,寄存器值)保存到内存中。之后,CPU会跳转到中断处理程序(Interrupt Service Routine),开始执行具体的中断处理代码中断处理程序完成后,CPU会从内存中恢复之前保存的上下文信息,并恢复之前进程的执行。 总的来说,ARM架构上的Linux操作系统通常使用GIC来管理中断,其中包括中断触发、CPU响应和中断处理三个方面。这个流程对于保证系统的稳定性和快速响应非常重要。 ### 回答3: 在ARM架构的Linux系统中,GIC(Generic Interrupt Controller)被用来管理中断。当发生中断时,GIC会将中断信号发送到CPU,然后CPU会停止当前的进程并处理中断GIC中断流程如下: 1. 报告中断:设备或其他外部事件引发中断信号,设备向GIC发送中断信号,GIC会产生一个中断源标识符,然后将其发给CPU。 2. 响应中断:CPU根据中断源标识符查询GIC,查看中断请求的优先级和处理器状态,如果中断请求的优先级高于当前中断处理器,那么CPU会中止当前进程,执行中断处理程序。 3. 中断处理中断处理程序会读取设备状态,进行相应的操作,处理完成后会发出一个中断信号,通知GIC中断已被处理。 4. 中断结束:GIC收到来自设备的中断完成信号后,将中断源标识符置为未激活状态。 在这个流程中,GIC起到了一种路由的作用,将中断信号从设备传输到CPU,同时根据中断请求的优先级来优先处理高优先级的中断。这样就可以保证系统的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值