Linux中断系统

Linux中断系统

前言

在学习linux的中断系统之前,首先了解中断的概念。

中断的本质是什么?
中断的本质就是一个信号,这个信号表示cpu需要停止当前的指令,去处理一些其他的事情。

一、中断处理流程

首先我们来看看linux下的中断是如何处理的:

假设GPIO1_2作为一个按键,设置为电平触发,当按下按键时,GPIO1_2输入为低电平,产生中断,cpu如何处理这个中断信号。

当CPU接收到IRQ中断时,首先会到中断向量表处执行irq_handler:

这汇编代码意思是执行 handle_arch_irq 函数。

/*
 * Interrupt handling.
 */
	.macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq
	mov	r0, sp
	adr	lr, BSYM(9997f)
	ldr	pc, [r1]
#else
	arch_irq_handler_default
#endif

handle_arch_irq是一个全局的函数指针,在中断初始化时(以GIC为例),会被设置指向gic_handle_irq
gic_handle_irq非常简单,首先获取gic对象,循环读取GIC的寄存器,获取中断号,然后调用__handle_domain_irq处理中断,直到gic没有中断挂起。

//GIC中断处理函数,此时的regs是SP
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	//获取第一个gic对象
	struct gic_chip_data *gic = &gic_data[0];
	//获取GIC CPU Interface基地址
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	//处理GIC的所有中断
	do {
		//读取GIC的ack寄存器,获取硬件中断号
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		irqnr = irqstat & GICC_IAR_INT_ID_MASK;

		//GIC的16-1020号中断是irq
		if (likely(irqnr > 15 && irqnr < 1021)) {
			//_handle_domain_irq 调用_handle_domain_irq(domain, hwirq, true, regs); 进入下一级的中断处理
			handle_domain_irq(gic->domain, irqnr, regs);
			//处理完当前中断后,再次检查GIC中断
			continue;
		}
		//todo
		if (irqnr < 16) {
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
			continue;
		}
		break;
	} while (1);
}

__handle_domain_irq会先保存寄存器,然后进入arm cpu模式,并通过irq_find_mapping()将硬件中断号hwirq转换成虚拟中断号irq,最后将irq传递给generic_handle_irq继续处理。

//调用domain的中断处理函数
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
	//因为要进入中断上下文,所以保存当前的irq寄存器,并设置新的寄存器值
	struct pt_regs *old_regs = set_irq_regs(regs);
	unsigned int irq = hwirq;
	int ret = 0;
	//进入中断上下文,禁止中断和抢占
	irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
	if (lookup)
		//通过irqdomain来找到irq
		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_exit();
	//恢复寄存器
	set_irq_regs(old_regs);
	return ret;
}
#endif

generic_handle_irq非常简单,首先根据irq找到一个irqdesc的结构体,然后调用该结构体中的函数指针进行下一步的中断处理。那么在按键中断这个例子中,这个函数指针指向哪个函数?以IMX6UL为例,irq对应的irqdesc描述是GPIO中断控制器,而GPIO控制器的中断处理函数是mx3_gpio_irq_handler

 //处理irq中断
int generic_handle_irq(unsigned int irq)
{
	//根据中断号找到irqdesc
	struct irq_desc *desc = irq_to_desc(irq);

	if (!desc)
		return -EINVAL;
	//desc->handle_irq(irq, desc);
	generic_handle_irq_desc(irq, desc);
	return 0;
}

mx3_gpio_irq_handler 先读取GPIO端口的寄存器,得到GPIO1_2的硬件中断号,然后调用mxc_gpio_irq_handler
mxc_gpio_irq_handler会找到GPIO端口对应的domain,并使用irq_find_mapping将硬件中断号转换成虚拟中断号irq,最后同样调用generic_handle_irq。同理,irq对应的irqdesc的中断处理函数会执行。

//gpio端口中断处理函数
static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc)
{
	u32 irq_stat;
	struct mxc_gpio_port *port = irq_get_handler_data(irq);
	struct irq_chip *chip = irq_get_chip(irq);

	chained_irq_enter(chip, desc);
	//读取端口的中断状态
	irq_stat = readl(port->base + GPIO_ISR) & readl(port->base + GPIO_IMR);
	//处理GPIO 端口上的中断
	mxc_gpio_irq_handler(port, irq_stat);

	chained_irq_exit(chip, desc);
}

//处理端口上的PIN中断
//irq_stat 保存的是中断状态寄存器的值
static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
{
	//处理端口上所有PIN脚的中断
	while (irq_stat != 0) {
		int irqoffset = fls(irq_stat) - 1;

		if (port->both_edges & (1 << irqoffset))
			mxc_flip_edge(port, irqoffset);

		//先使用domain将hw_irq转换成irq_number,再传递irq给generic_handle_irq
		generic_handle_irq(irq_find_mapping(port->domain, irqoffset));

		irq_stat &= ~(1 << irqoffset);
	}
}

那么这个irq对应的中断处理函数是什么呢?直接给出答案:handle_level_irq

handle_level_irq是linux中处理电平触发类中断的通用函数,类似的函数还有:handle_edge_irq,handle_fasteoi_irq等。imx6ul在初始化gpio时会将他注册给irqdesc->handle。

handle_level_irq 会锁住desc,做一些中断多核处理的保护,然后调用handle_irq_event
handle_irq_event 会对desc->irq_data做保护,然后调用handle_irq_event_percpu 去执行用户设置的回调函数。

void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
	//锁住desc
	raw_spin_lock(&desc->lock);
	//ack中断信号
	mask_ack_irq(desc);

	if (!irq_may_run(desc))
		goto out_unlock;

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
	kstat_incr_irqs_this_cpu(irq, desc);

	/*
	 * If its disabled or no action available
	 * keep it masked and get out of here
	 */
	if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
		desc->istate |= IRQS_PENDING;
		goto out_unlock;
	}
	//处理中断
	handle_irq_event(desc);

	cond_unmask_irq(desc);

out_unlock:
	raw_spin_unlock(&desc->lock);
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	struct irqaction *action = desc->action;
	irqreturn_t ret;

	desc->istate &= ~IRQS_PENDING;
	//设置desc->irq_data状态为in process
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	//锁定desc
	raw_spin_unlock(&desc->lock);
	//处理中断action和中断线程化
	ret = handle_irq_event_percpu(desc, action);

	raw_spin_lock(&desc->lock);
	//清除in process
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}

handle_irq_event_percpu 会先执行中断上半部,也就是irqaction函数,也就是按键处理函数。irqaction的返回值表示是否需要中断线程,以及中断是否正常处理。

//处理中断action
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
	irqreturn_t retval = IRQ_NONE;
	unsigned int flags = 0, irq = desc->irq_data.irq;

	do {
		irqreturn_t res;
		//执行用户注册的中断action
		res = action->handler(irq, action->dev_id);

		//根据action->handler()返回值,进入中断下半部or处理下一个action
		switch (res) {
		//中断线程化处理
		case IRQ_WAKE_THREAD:
			//唤醒中断线程
			__irq_wake_thread(desc, action);
			
		//中断处理完成
		case IRQ_HANDLED:
			flags |= action->flags;
			break;

		default:
			break;
		}
		//处理下一个action
		retval |= res;
		action = action->next;
	} while (action);

	add_interrupt_randomness(irq, flags);

	if (!noirqdebug)
		note_interrupt(irq, desc, retval);
	return retval;
}

//TODO中断线程

二、linux中断子系统

通过上述linux中断处理过程的简单介绍,我们可以来更加深入的学习linux的中断子系统。

思考上述的过程,发现可以将中断处理流程分为两个阶段:

  1. 中断流处理:指从cpu接收到中断信号到用户设置的中断回调函数执行前的过程。这个过程通过中断控制器,逐步地确定是哪个具体的外设产生的中断。对于每个中断,这个过程都是类似的。
  2. 中断处理:指具体的外设中断处理业务。对每个中断都不一样。

中断流处理大部分函数都是由linux内核实现的,这些东西就是linux的中断子系统,驱动开发者需要按照这个子系统的规则实现。

结构体

从中断处理流程中,可以发现一些结构体反复的出现,这些结构体是 irq_descirq_domain,,

irqdesc

linux使用irqdesc(定义在include/linux/irq)来抽象描述一个具体的中断信号。irqdesc定义如下(简单浏览)

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:		per irq and chip data passed down to chip functions
 * @kstat_irqs:		irq stats per cpu
 * @handle_irq:		highlevel irq-events handler
 * @preflow_handler:	handler called before the flow handler (currently used by sparc)
 * @action:		the irq action chain
 * @status:		status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:		stats field to detect stalled irqs
 * @last_unhandled:	aging timer for unhandled count
 * @irqs_unhandled:	stats field for spurious unhandled interrupts
 * @threads_handled:	stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:		locking for SMP
 * @affinity_hint:	hint to user space for preferred irq affinity
 * @affinity_notify:	context for notification of affinity changes
 * @pending_mask:	pending rebalanced interrupts
 * @threads_oneshot:	bitfield to handle shared oneshot threads
 * @threads_active:	number of irqaction threads currently running
 * @wait_for_threads:	wait queue for sync_irq to wait for threaded handlers
 * @nr_actions:		number of installed actions on this descriptor
 * @no_suspend_depth:	number of irqactions on a irq descriptor with
 *			IRQF_NO_SUSPEND set
 * @force_resume_depth:	number of irqactions on a irq descriptor with
 *			IRQF_FORCE_RESUME set
 * @dir:		/proc/irq/ procfs entry
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
	struct irq_data		irq_data;  //作为irq_chip函数指针的参数
	unsigned int __percpu	*kstat_irqs;	//irq状态
	irq_flow_handler_t	handle_irq;	//中断流处理函数
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	struct irqaction	*action;	/* IRQ action list 是开发者编写的特定中断的实际处理函数*/
	unsigned int		status_use_accessors;  //irqdesc的状态
	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		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;
#ifdef CONFIG_SMP
	const struct cpumask	*affinity_hint;
	struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
	cpumask_var_t		pending_mask;
#endif
#endif
	unsigned long		threads_oneshot;
	atomic_t		threads_active;
	wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
	unsigned int		nr_actions;
	unsigned int		no_suspend_depth;
	unsigned int		cond_suspend_depth;
	unsigned int		force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
	int			parent_irq;
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;

需要关注的成员:

  • struct irq_data irq_data:中断处理时传递参数用到
  • irq_flow_handler_t handle_irq:中断流处理函数
  • struct irqaction * action:用户注册的中断回调函数

irqaction 是外设具体的中断处理逻辑,可以从最常用的函数request_threaded_irq来理解:

struct irqaction {
	irq_handler_t		handler;	//中断处理函数
	void			*dev_id;	//用于共享中断中,判断设备驱动
	void __percpu		*percpu_dev_id;
	struct irqaction	*next;	//当是共享中断时,irqaction是一个链表,对应多个驱动中断
	irq_handler_t		thread_fn;	//中断线程化的线程入口函数
	struct task_struct	*thread;	//中断线程化的线程
	unsigned int		irq;	//虚拟中断号
	unsigned int		flags;	//request_irq 传入的flag
	unsigned long		thread_flags;
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;


//注册中断回调函数
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			 irq_handler_t thread_fn, unsigned long irqflags,
			 const char *devname, void *dev_id)
{
	struct irqaction *action;
	struct irq_desc *desc;
	int retval;
	//检查标志
	if (((irqflags & IRQF_SHARED) && !dev_id) ||
	    (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
	    ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
		return -EINVAL;
	//获取对应irqdesc
	desc = irq_to_desc(irq);
	//检查desc
	if (!irq_settings_can_request(desc) ||
	    WARN_ON(irq_settings_is_per_cpu_devid(desc)))
		return -EINVAL;
	//设置默认handler
	if (!handler) {
		if (!thread_fn)
			return -EINVAL;
		handler = irq_default_primary_handler;
	}
	//申请action
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;
	//设置action成员
	action->handler = handler;  //设置中断处理函数
	action->thread_fn = thread_fn;  //中断线程入口
	action->flags = irqflags;  //中断标志
	action->name = devname;  
	action->dev_id = dev_id;
	//锁住desc
	chip_bus_lock(desc);
	//注册一个irqaction,添加到irq_desc
	retval = __setup_irq(irq, desc, action);
	//释放锁
	chip_bus_sync_unlock(desc);
	return retval;
}
EXPORT_SYMBOL(request_threaded_irq);

需要关注的成员:

  • irq_handler_t handler:中断处理函数
  • void * dev_id:用于共享中断中,判断设备驱动
  • irq_handler_t thread_fn:中断线程化的线程入口函数
api

当linux初始化时,会为每一个中断信号初始化一个irqdesc对象,保存在内核的链表中。当CPU接收到中断时,会找到对应的irqdesc对象去处理中断。本节记录了irq_desc常用的几个API(irqdesc.c)

在irqdesc.c中提供函数 early_irq_init 来初始化irqdesc对象,先确定irq的数量,再创建对应的irq_desc,当然这些irq_desc还未初始化

//早期中断初始化,主要就是初始化irqdesc
int __init early_irq_init(void)
{
	//第一个设备节点
	int i, initcnt, node = first_online_node;
	struct irq_desc *desc;

	//todo 多核处理器相关
	init_irq_default_affinity();	

	/* Let arch update nr_irqs and return the nr of preallocated irqs */
	//返回中断的数量 machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;
	initcnt = arch_probe_nr_irqs();
	printk(KERN_INFO "NR_IRQS:%d nr_irqs:%d %d\n", NR_IRQS, nr_irqs, initcnt);

	//将最终的中断数量保存到全局变量nr_irqs
	if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
		nr_irqs = IRQ_BITMAP_BITS;

	if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
		initcnt = IRQ_BITMAP_BITS;

	if (initcnt > nr_irqs)
		nr_irqs = initcnt;

	//初始化irqdesc
	for (i = 0; i < initcnt; i++) {
		//为irq信号分配一个irqdesc并设置默认值
		desc = alloc_desc(i, node, NULL);
		//设置标志位
		set_bit(i, allocated_irqs);
		//将desc插入irq_desc_tree树或者数组
		irq_insert_desc(i, desc);
	}
	//arch_early_irq_init 是留给特定架构的中断初始化函数。在arm中没有用到
	return arch_early_irq_init();
}

需要注意的是irq_desc对象的存储有两种形式,一种是使用静态数组,一种是使用RADIX_TREE,动态分配。不同的存放方式导致不同的查找方法。
当宏 CONFIG_SPARSE_IRQ定义时,使用RADIX_TREE。

其他API:

//执行irq中断流处理
int generic_handle_irq(unsigned int irq);

//处理domain中的hwirq中断流
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,bool lookup, struct pt_regs *regs);

//申请irq_desc
int __ref __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,struct module *owner);

//释放
void irq_free_descs(unsigned int from, unsigned int cnt);


irq_domain

概念

中断控制器是负责输入多个中断信号,管理这些中断信号的优先级、开关、触发方式,并输出一个中断信号给下一级处理。
例如通用中断控制器GIC,在芯片中负责管理所有的外设中断,并将中断信号传递给CPU。按键按下,GPIO1控制器产生中断信号到GIC中断控制器,条件合理时,GIC将产生中断信号给CPU。CPU需要通过一层层的读取中断控制器,来确定最终的中断源,也就是GPIO1_2对应的按键中断。

中断信号一般由这样的过程构成:

  1. 中断源,比如按键
  2. 外设控制器,比如GPIO控制器
  3. GIC,通用中断控制器
  4. CPU

对应在软件上,中断的一般处理流程如下:

  1. CPU产生IRQ中断,跳转到IRQ_Handler
  2. IRQ_Handler中读取GIC状态寄存器,判断是哪个具体的外设产生的中断,跳转到GPIOx_handler
  3. GPIOx_handler:读取该GPIO控制器的寄存器,判断哪个IO产生的中断,拿到对应的irqdesc
  4. 执行irqdesc中驱动设置的中断回调函数irqaction

软件的中断处理就是硬件中断信号的逆过程。软件需要通过一层层地读取中断控制器的寄存器来确定具体的中断源。
这里有一个问题:在确定中断源是GPIO1_2后,如何找到我们之前注册的按键中断处理函数?

答案就是中断号。我们可以通过中断号来找到irqdesc,irqdesc对象中就有中断处理函数。

但是还有一个问题,就是在一个系统中,hw_irq(硬件中断号)并不是唯一的(因为GIC支持从0-1020的中断号,而GPIO1控制器支持0-31的中断号,GPIO2控制器也支持0-31的中断号),而在linux内核中,每一个irqdesc都必须由唯一的irq来区别。

linux的解决方法是:将一个中断控制器管理的所有中断的集合称为domain(领域、范围),一个中断控制器有一个domain,领域内的每个中断号唯一。domain的作用是将hw_irq转换成linux内核中唯一的irq,这个irq称为虚拟中断号。转换的方法由开发者实现。

这样,在同一个domain下,他们的hw_irq肯定不同;hw_irq相同时,所属的domain不同,转换的方法也不同,得到的irq也就不一样,这样就能保证irq是唯一的。

linux中使用irq_domain抽象上述的场景:

/**
 * struct irq_domain - Hardware interrupt number translation object
 * @link: Element in global irq_domain list.
 * @name: Name of interrupt domain
 * @ops: pointer to irq_domain methods
 * @host_data: private data pointer for use by owner.  Not touched by irq_domain
 *             core code.
 * @flags: host per irq_domain flags
 *
 * Optional elements
 * @of_node: Pointer to device tree nodes associated with the irq_domain. Used
 *           when decoding device tree interrupt specifiers.
 * @gc: Pointer to a list of generic chips. There is a helper function for
 *      setting up one or more generic chips for interrupt controllers
 *      drivers using the generic chip library which uses this pointer.
 * @parent: Pointer to parent irq_domain to support hierarchy irq_domains
 *
 * Revmap data, used internally by irq_domain
 * @revmap_direct_max_irq: The largest hwirq that can be set for controllers that
 *                         support direct mapping
 * @revmap_size: Size of the linear map table @linear_revmap[]
 * @revmap_tree: Radix map tree for hwirqs that don't fit in the linear map
 * @linear_revmap: Linear table of hwirq->virq reverse mappings
 */
struct irq_domain {
	struct list_head link;    //连接到全局domain表
	const char *name;
	const struct irq_domain_ops *ops;    //domain的操作接口
	void *host_data;	//私有数据
	unsigned int flags;

	/* Optional data */
	struct device_node *of_node;	//对应的设备树节点
	struct irq_domain_chip_generic *gc;	//对中断控制器的抽象
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain *parent;	//上一级的中断控制器
#endif

	/* reverse map data. The linear map gets appended to the irq_domain */
	irq_hw_number_t hwirq_max;			//domain下的中断信号数量
	unsigned int revmap_direct_max_irq;	//数组的最大中断号
	unsigned int revmap_size;	//linear_revmap[]的长度
	struct radix_tree_root revmap_tree;	//存放映射关系的树
	unsigned int linear_revmap[];	//存放映射关系的数组
};

需要关注的成员有:

  • const struct irq_domain_ops* ops:domain的操作接口
  • irq_hw_number_t hwirq_max:domain下的中断信号数量
  • struct radix_tree_root revmap_tree:存放映射关系的树
  • unsigned int linear_revmap[]:存放映射关系的数组

domain的主要功能是hw_irq到irq的转换,就需要有数据结构存放映射关系。有radix_tree 和数组两种存储方法。比较重点的是irq_domain_ops:


//irq_domain的所有功能在此实现,每个函数需要根据中断控制器具体实现
struct irq_domain_ops {
	int (*match)(struct irq_domain *d, struct device_node *node);  //匹配一个中断控制器到irq_domain
	int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);  //创建或更新硬件中断号和虚拟中断号的映射
	void (*unmap)(struct irq_domain *d, unsigned int virq);  //取消映射
	//解析出设备树中的硬件中断号和中断类型
	int (*xlate)(struct irq_domain *d, struct device_node *node,  
		     const u32 *intspec, unsigned int intsize,
		     unsigned long *out_hwirq, unsigned int *out_type);
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	/* 支持中断控制器级联 */
	//在当前irqdomain下分配一个新的子domain
	int (*alloc)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs, void *arg);
	void (*free)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs);
	//使能domain
	void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
	void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
#endif
};
api

irq_domain.c 中提供函数 __irq_domain_add 实现创建和添加irq_domain的功能:

/**
 * __irq_domain_add() - Allocate a new irq_domain data structure
 * @of_node: optional device-tree node of the interrupt controller
 * @size: Size of linear map; 0 for radix mapping only
 * @hwirq_max: Maximum number of interrupts supported by controller
 * @direct_max: Maximum value of direct maps; Use ~0 for no limit; 0 for no
 *              direct mapping
 * @ops: domain callbacks
 * @host_data: Controller private data pointer
 *
 * Allocates and initialize and irq_domain structure.
 * Returns pointer to IRQ domain, or NULL on failure.
 */
struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data)
{
	struct irq_domain *domain;

	//因为irq_domain的linear_revmap[]数组大小和size一样,所以需要额外的size 
	domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
			      GFP_KERNEL, of_node_to_nid(of_node));
	if (WARN_ON(!domain))
		return NULL;

	/* Fill structure 填充对象*/
	INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	domain->ops = ops;
	domain->host_data = host_data;
	domain->of_node = of_node_get(of_node);
	domain->hwirq_max = hwirq_max;  //该domain下的中断信号数量
	domain->revmap_size = size;  //数组的大小
	domain->revmap_direct_max_irq = direct_max;
	irq_domain_check_hierarchy(domain);

	//所住全局链表irq_domain_list
	mutex_lock(&irq_domain_mutex);
	//将domain插入链表
	list_add(&domain->link, &irq_domain_list);
	mutex_unlock(&irq_domain_mutex);

	pr_debug("Added domain %s\n", domain->name);
	return domain;
}
EXPORT_SYMBOL_GPL(__irq_domain_add);

irq_domain.c 中提供函数 irq_domain_associate 实现建立irq和hw_irq的映射关系:

//建立irq和hw_irq的映射关系
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
			 irq_hw_number_t hwirq)
{
	//由irq_data来代表irq进行映射
	struct irq_data *irq_data = irq_get_irq_data(virq);
	int ret;

	if (WARN(hwirq >= domain->hwirq_max,
		 "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
		return -EINVAL;
	if (WARN(!irq_data, "error: virq%i is not allocated", virq))
		return -EINVAL;
	if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
		return -EINVAL;

	mutex_lock(&irq_domain_mutex);
	//初始化irq_data
	irq_data->hwirq = hwirq;
	irq_data->domain = domain;
	
	if (domain->ops->map) {
		//建立映射关系
		ret = domain->ops->map(domain, virq, hwirq);
		if (ret != 0) {
			/*
			 * If map() returns -EPERM, this interrupt is protected
			 * by the firmware or some other service and shall not
			 * be mapped. Don't bother telling the user about it.
			 */
			if (ret != -EPERM) {
				pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
				       domain->name, hwirq, virq, ret);
			}
			irq_data->domain = NULL;
			irq_data->hwirq = 0;
			mutex_unlock(&irq_domain_mutex);
			return ret;
		}

		/* If not already assigned, give the domain the chip's name */
		if (!domain->name && irq_data->chip)
			domain->name = irq_data->chip->name;
	}
	//保存映射关系
	//线性映射时hwirq
	if (hwirq < domain->revmap_size) {
		//保存到linear_revmap数组
		domain->linear_revmap[hwirq] = virq;
	} else {
		//树映射时,revmap_size==0,保存到树
		mutex_lock(&revmap_trees_mutex);
		radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
		mutex_unlock(&revmap_trees_mutex);
	}
	mutex_unlock(&irq_domain_mutex);
	//设置irqdesc的状态
	irq_clear_status_flags(virq, IRQ_NOREQUEST);

	return 0;
}
EXPORT_SYMBOL_GPL(irq_domain_associate);

另一种方法是使用设备树中的信息来建立映射关系:

驱动程序使用 irq_of_parse_and_map 解析和映射中断时,该函数实际是调用irq_create_of_mapping,用于建立hw_irq和irq映射关系。

//使用设备树创建映射
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_domain *domain;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	int virq;
	//找到domain
	domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;
	if (!domain) {
		pr_warn("no irq domain found for %s !\n",
			of_node_full_name(irq_data->np));
		return 0;
	}

	/* If domain has no translation, then we assume interrupt line */
	if (domain->ops->xlate == NULL)
		hwirq = irq_data->args[0];
	else {
		//xlate负责从设备树中获取hw中断号
		if (domain->ops->xlate(domain, irq_data->np, irq_data->args,
					irq_data->args_count, &hwirq, &type))
			return 0;
	}
	//domain是否支持级联
	if (irq_domain_is_hierarchy(domain)) {
		/*
		 * If we've already configured this interrupt,
		 * don't do it again, or hell will break loose.
		 */
		 //根据domain和hw_irq找到irq
		virq = irq_find_mapping(domain, hwirq);
		//如果非0,说明映射关系已经建立,直接返回
		if (virq)
			return virq;
		//结果为0,说明映射关系未建立
		//分配一个新的irqdesc,并添加到domain下
		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);
		if (virq <= 0)
			return 0;
	} else {
		/* Create mapping */
		virq = irq_create_mapping(domain, hwirq);
		if (!virq)
			return virq;
	}

	/* Set type if specified and different than the current one */
	if (type != IRQ_TYPE_NONE &&
	    type != irq_get_trigger_type(virq))
		irq_set_irq_type(virq, type);
	return virq;
}
EXPORT_SYMBOL_GPL(irq_create_of_mapping);

其他API

//移除domain
void irq_domain_remove(struct irq_domain *domain);

 //给domain中的中断信号分配irqdesc
int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
			    unsigned int nr_irqs, int node, void *arg,
			    bool realloc);
			

irq_chip_generic

其实GPIO控制器、IIC控制器、UART控制器等,这些控制器也能实现中断的控制功能,都可以设置、使能、产生中断信号,这些也是中断控制器,但在linux中把他称为中断芯片貌似更合适。
对于这些中断芯片,linux内核使用irq_chip_generic来抽象描述他们。

struct irq_chip_generic {
	raw_spinlock_t		lock;
	void __iomem		*reg_base;  //中断芯片虚拟基地址
	u32			(*reg_readl)(void __iomem *addr);  //读写寄存器的函数
	void			(*reg_writel)(u32 val, void __iomem *addr);
	unsigned int		irq_base;  //中断起始号
	unsigned int		irq_cnt;  //中断数量
	u32			mask_cache;  //一些寄存器的缓存
	u32			type_cache;
	u32			polarity_cache;
	u32			wake_enabled;
	u32			wake_active;
	unsigned int		num_ct;  //chip_types的数量
	void			*private;
	unsigned long		installed;
	unsigned long		unused;
	struct irq_domain	*domain;
	struct list_head	list;
	struct irq_chip_type	chip_types[0];  //保存中断寄存器、中断处理函数等
};

使用中断芯片就是需要去读写其寄存器来配置中断,这可以用两个结构体来描述。
struct irq_chip_typestruct irq_chip

一般来说,一个irq_chip_generic 就有一个 irq_chip_type,描述了中断芯片的功能:

struct irq_chip_type {
	struct irq_chip		chip;  //控制器的回调函数
	struct irq_chip_regs	regs;  //寄存器的偏移地址
	irq_flow_handler_t	handler;  //中断流处理函数
	u32			type;
	u32			mask_cache_priv;
	u32			*mask_cache;  //指向cached mask register
};

当初始化一个通用中断控制器时,我们设置了寄存器信息,回调函数,中断流处理函数。当中断产生时,中断流处理函数会执行,获取具体外设的中断号,去执行对应的irqaction。并调用中断芯片提供的api接口,去设置寄存器的ack位,响应中断。

这里的芯片api接口,就是由irq_chip结构体来描述的,这个结构体定义了许多接口函数,驱动需要根据实际情况,按需实现函数。

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:		name for /proc/interrupts
 * @irq_startup:	start up the interrupt (defaults to ->enable if NULL)
 * @irq_shutdown:	shut down the interrupt (defaults to ->disable if NULL)
 * @irq_enable:		enable the interrupt (defaults to chip->unmask if NULL)
 * @irq_disable:	disable the interrupt
 * @irq_ack:		start of a new interrupt
 * @irq_mask:		mask an interrupt source
 * @irq_mask_ack:	ack and mask an interrupt source
 * @irq_unmask:		unmask an interrupt source
 * @irq_eoi:		end of interrupt
 * @irq_set_affinity:	set the CPU affinity on SMP machines
 * @irq_retrigger:	resend an IRQ to the CPU
 * @irq_set_type:	set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:	enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:	function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online:	configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:	un-configure an interrupt source for a secondary CPU
 * @irq_suspend:	function called from core code on suspend once per chip
 * @irq_resume:		function called from core code on resume once per chip
 * @irq_pm_shutdown:	function called from core code on shutdown once per chip
 * @irq_calc_mask:	Optional function to set irq_data.mask for special cases
 * @irq_print_chip:	optional to print special chip info in show_interrupts
 * @irq_request_resources:	optional to request resources before calling
 *				any other callback related to this irq
 * @irq_release_resources:	optional to release resources acquired with
 *				irq_request_resources
 * @irq_compose_msi_msg:	optional to compose message content for MSI
 * @irq_write_msi_msg:	optional to write message content for MSI
 * @irq_get_irqchip_state:	return the internal state of an interrupt
 * @irq_set_irqchip_state:	set the internal state of a interrupt
 * @flags:		chip specific flags
 */
struct irq_chip {
	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);

	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
	int		(*irq_retrigger)(struct irq_data *data);
	int		(*irq_set_type)(struct irq_data *data, unsigned int flow_type);
	int		(*irq_set_wake)(struct irq_data *data, unsigned int on);

	void		(*irq_bus_lock)(struct irq_data *data);
	void		(*irq_bus_sync_unlock)(struct irq_data *data);

	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);

	void		(*irq_suspend)(struct irq_data *data);
	void		(*irq_resume)(struct irq_data *data);
	void		(*irq_pm_shutdown)(struct irq_data *data);

	void		(*irq_calc_mask)(struct irq_data *data);

	void		(*irq_print_chip)(struct irq_data *data, struct seq_file *p);
	int		(*irq_request_resources)(struct irq_data *data);
	void		(*irq_release_resources)(struct irq_data *data);

	void		(*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
	void		(*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

	int		(*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
	int		(*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

	unsigned long	flags;
};

对于irq_chip这些接口函数,可以利用linux中提供的一些实现:
例如:

 {
 	struct irq_chip		chip
 	chip.irq_ack = irq_gc_ack_set_bit;
	chip.irq_mask = irq_gc_mask_clr_bit;
	chip.irq_unmask = irq_gc_mask_set_bit;
 }
 
 //ack指定中断
void irq_gc_ack_set_bit(struct irq_data *d)
{
	//通过irq_data获取中断芯片对象
	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
	struct irq_chip_type *ct = irq_data_get_chip_type(d);
	u32 mask = d->mask;

	irq_gc_lock(gc);
	//将mask的值写入寄存器regs.ack
	irq_reg_writel(gc, mask, ct->regs.ack);
	irq_gc_unlock(gc);
}
EXPORT_SYMBOL_GPL(irq_gc_ack_set_bit);

 //清除mask寄存器的某个位
void irq_gc_mask_clr_bit(struct irq_data *d)
{
	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
	struct irq_chip_type *ct = irq_data_get_chip_type(d);
	u32 mask = d->mask;

	irq_gc_lock(gc);
	*ct->mask_cache &= ~mask;
	irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask);
	irq_gc_unlock(gc);
}

 //设置mask寄存器的某个位
void irq_gc_mask_set_bit(struct irq_data *d)
{
	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
	struct irq_chip_type *ct = irq_data_get_chip_type(d);
	u32 mask = d->mask;

	irq_gc_lock(gc);
	*ct->mask_cache |= mask;
	irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask);
	irq_gc_unlock(gc);
}
EXPORT_SYMBOL_GPL(irq_gc_mask_set_bit);
api

linux提供 irq_alloc_generic_chipirq_setup_generic_chip 来初始化generic_chip:

irq_alloc_generic_chip,初始化需要提供参数:

  1. name:irq_chip的名称
  2. num_cnt:chip_type的数量,一般是1
  3. irq_base:中断号
  4. reg_base:寄存器基地址
  5. handler:中断流处理函数,即中断控制器响应自身中断的逻辑
    该函数主要实现了初始化 irq_chip_generic 对象,设置成员变量
struct irq_chip_generic *irq_alloc_generic_chip(const char *name, int num_ct, unsigned int irq_base,void __iomem *reg_base, irq_flow_handler_t handler)
{
	struct irq_chip_generic *gc;
	//计算需要的内存 irq_chip_type 的影响
	unsigned long sz = sizeof(*gc) + num_ct * sizeof(struct irq_chip_type);
	//申请内存
	gc = kzalloc(sz, GFP_KERNEL);
	if (gc) {
		//初始化结构体
		irq_init_generic_chip(gc, name, num_ct, irq_base, reg_base,
				      handler);
	}
	return gc;
}

//很简单,设置generic_chip结构体
static void irq_init_generic_chip(struct irq_chip_generic *gc, const char *name,
		      int num_ct, unsigned int irq_base,
		      void __iomem *reg_base, irq_flow_handler_t handler)
{
	raw_spin_lock_init(&gc->lock);
	gc->num_ct = num_ct;
	gc->irq_base = irq_base;
	gc->reg_base = reg_base;
	gc->chip_types->chip.name = name;
	gc->chip_types->handler = handler;
}

irq_setup_generic_chip用于建立/使能控制器中的中断信号。msk参数以位掩码的形式表示哪些中断被使能。例如0b1100,表示 irq为gc->irq_base+3,gc->irq_base+4 的中断信号被使能。


//根据配置设置中断芯片下所有的irqdesc
void irq_setup_generic_chip(struct irq_chip_generic *gc, u32 msk,
			    enum irq_gc_flags flags, unsigned int clr,
			    unsigned int set)
{
	struct irq_chip_type *ct = gc->chip_types;
	struct irq_chip *chip = &ct->chip;
	unsigned int i;
	//把gc添加到全局的gc链表
	raw_spin_lock(&gc_lock);
	list_add_tail(&gc->list, &gc_list);
	raw_spin_unlock(&gc_lock);

	//根据flags初始化mask_cache
	irq_gc_init_mask_cache(gc, flags);

	//遍历整个mask的bits,i表示的是irq number虚拟中断号
	for (i = gc->irq_base; msk; msk >>= 1, i++) {
		//若mask上的bit未使能,则跳过
		if (!(msk & 0x01))
			continue;

		if (flags & IRQ_GC_INIT_NESTED_LOCK)
			irq_set_lockdep_class(i, &irq_nested_lock_class);

		if (!(flags & IRQ_GC_NO_MASK)) {
			struct irq_data *d = irq_get_irq_data(i);

			if (chip->irq_calc_mask)
				chip->irq_calc_mask(d);
			else
				d->mask = 1 << (i - gc->irq_base);
		}
		//设置对应irqdesc的irq_chip和中断处理函数、chip_data
		irq_set_chip_and_handler(i, chip, ct->handler);
		irq_set_chip_data(i, gc);
		irq_modify_status(i, clr, set);
	}
	//计算实际使用的irq数量
	gc->irq_cnt = i - gc->irq_base;
}

其他API

 //设置mask寄存器的某个位
void irq_gc_mask_set_bit(struct irq_data *d);

 //清除mask寄存器的某个位
void irq_gc_mask_clr_bit(struct irq_data *d);

 //ack指定中断
void irq_gc_ack_set_bit(struct irq_data *d);

三、 ARM中断

以上是Linux对中断系统的抽象,具体的架构需要根据自身的特点,在linux中断系统中实现自己的中断处理.
以ARM为例,ARM的中断相关代码在 arch/arm/irq.c中.

对于如何处理中断,每一个体系都有细微差别,以ARM为例。
对于ARM处理器而言,当发生异常的时候,处理器会暂停当前指令的执行,保存现场,转而去执行对应的异常向量处的指令,当处理完该异常的时候,恢复现场,回到原来的那点去继续执行程序。

异常向量的定义在 entry-armv.S

irq_handler宏的作用就是跳转到handle_arch_irq函数执行,函数输入参数是sp

/*
 * Interrupt handling.
 */
	.macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq
	mov	r0, sp
	adr	lr, BSYM(9997f)
	ldr	pc, [r1]
#else
	arch_irq_handler_default
#endif

handle_arch_irq 是一个全局变量,他的值是由set_handle_irq()函数设置的.

//设置handle_arch_irq
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
	if (handle_arch_irq)
		return;

	handle_arch_irq = handle_irq;
}

我们要知道ARM的中断是如何处理,就需要知道是handle_arch_irq的内容.

就如前面所说,中断的处理离不开中断控制器,而ARM中断的处理则要依赖于GIC.

GIC

一般都是由GIC控制器来产生中断信号给ARM 的IRQ引脚,所以应该是由GIC相关的代码来调用set_handle_irq.

这就需要从GIC的驱动程序中来找到证据: GIC的驱动程序在 driver/irqchip/irq-gic.c 中,具体的代码可参考上述博客.

这里给出结论: gic_of_init()->gic_init_bases()->set_handle_irq(gic_handle_irq),可见,handle_arch_irq == gic_handle_irq

gic_handle_irq 非常简单,就是去读取GIC的寄存器,获取中断号,然后调用__handle_domain_irq,从此进入了linux的中断子系统了.

//GIC中断处理函数,此时的regs是SP
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	//获取GIC CPU Interface基地址
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	do {
		//读取GIC的中断应答寄存器,获取中断号
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		irqnr = irqstat & GICC_IAR_INT_ID_MASK;

		if (likely(irqnr > 15 && irqnr < 1021)) {
			//__handle_domain_irq(domain, hwirq, true, regs); 进入下一级的中断处理
			handle_domain_irq(gic->domain, irqnr, regs);
			continue;
		}
		if (irqnr < 16) {
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
			continue;
		}
		break;
	} while (1);
}

四、 中断系统初始化

以上我们了解了中断的处理流程以及使用的主要结构体,那么接下来就来了解这些对象是如何初始化的。

中断子系统涉及到的重要的结构体有:

  1. irqdesc
  2. irqdomain
  3. irqaction
  4. genneric_chip

合理的猜测,linux启动过程初始化中断时,势必会初始化这些对象,才能使用整个中断子系统.

我们从内核启动函数 start_kernel 开始追踪,可以发现其调用了两个中断相关的函数:

  1. early_irq_init
  2. init_IRQ

early_irq_init函数在前文irqdesc中已经分析过,他的作用就是初始化好irqdesc对象.

init_IRQ则与芯片架构相关,ARM架构下该函数定义在 arch/arm/kernel/irq.c

//ARM初始化IRQ
void __init init_IRQ(void)
{
	int ret;
	//是否使用设备树 
	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
		//初始化中断控制器
		irqchip_init();
	else
		machine_desc->init_irq();

	if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
	    (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
		if (!outer_cache.write_sec)
			outer_cache.write_sec = machine_desc->l2c_write_sec;
		ret = l2x0_of_init(machine_desc->l2c_aux_val,
				   machine_desc->l2c_aux_mask);
		if (ret)
			pr_err("L2C: failed to init: %d\n", ret);
	}
}

irqchip_init 函数的目的就是初始化中断控制器,而soc上有多少的中断控制器,可以从设备树中确定.

//初始化中断控制器
void __init irqchip_init(void)
{
	//初始化设备树中的中断控制器,__irqchip_of_table是一个程序段
	of_irq_init(__irqchip_of_table);

	acpi_irq_init(); //应该没有用到
}

那么问题是__irqchip_of_table 是什么东西呢?这里直接给出答案,在irq-gic.c中,有这样的定义:

IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);

IRQCHIP_DECLARE宏展开后,最终可以得到:

	static const struct of_device_id __of_table_cortex_a7_gic __used__section(__irqchip_of_table) = 		
	{ 
	    .compatible = "arm,cortex-a7-gic",		//匹配字符串名称,与设备树中compatible相同		
		.data = gic_of_init,    //gic初始化函数(设备树版)
	};

__irqchip_of_table 中存放的就是 of_device_id 数组

在GIC驱动程序中,使用了多个IRQCHIP_DECLARE,__irqchip_of_table中就有很多的of_device_id

有了这个了解,我们就可以继续学习 of_irq_init,这个函数逻辑有点乱,但我们只需要明白:

这个函数扫描设备树中的中断控制器节点,并从最上层的控制器开始调用注册的初始化函数

/**
 * of_irq_init - Scan and init matching interrupt controllers in DT
 * @matches: 0 terminated array of nodes to match and init function to call
 *
 * This function scans the device tree for matching interrupt controller nodes,
 * and calls their initialization functions in order with parents first.
 */
 //扫描设备树中的中断控制器节点,并从最上层的控制器开始初始化
 //match是一个数组,其成员包含了初始化函数
void __init of_irq_init(const struct of_device_id *matches)
{
	struct device_node *np, *parent = NULL;
	struct intc_desc *desc, *temp_desc;
	struct list_head intc_desc_list, intc_parent_list;
	//中断控制器链表
	INIT_LIST_HEAD(&intc_desc_list);
	INIT_LIST_HEAD(&intc_parent_list);

	//寻找设备树中定义的中断控制器,将他加入intc_desc_list
	for_each_matching_node(np, matches) {
		//寻找设备树节点中有"interrupt-controller"属性的节点,保存到np
		if (!of_find_property(np, "interrupt-controller", NULL) ||
				!of_device_is_available(np))
			continue;
		/*
		 * Here, we allocate and populate an intc_desc with the node
		 * pointer, interrupt-parent device_node etc.
		 */
		 //分配一个irqdesc给中断控制器,并绑定设备树节点
		desc = kzalloc(sizeof(*desc), GFP_KERNEL);
		if (WARN_ON(!desc))
			goto err;

		desc->dev = np;
		desc->interrupt_parent = of_irq_find_parent(np);
		if (desc->interrupt_parent == np)
			desc->interrupt_parent = NULL;
		//插入链表
		list_add_tail(&desc->list, &intc_desc_list);
	}

	/*
	 * The root irq controller is the one without an interrupt-parent.
	 * That one goes first, followed by the controllers that reference it,
	 * followed by the ones that reference the 2nd level controllers, etc.
	 */
	 //从root中断控制器开始初始化
	while (!list_empty(&intc_desc_list)) {
		/*
		 * Process all controllers with the current 'parent'.
		 * First pass will be looking for NULL as the parent.
		 * The assumption is that NULL parent means a root controller.
		 */
		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
			const struct of_device_id *match;		//当前控制器对应的of_device_id
			int ret;
			of_irq_init_cb_t irq_init_cb;	//中断控制器的初始化函数

			//desc是不是孤儿,直到找到一个孤儿,也就是root intr
			if (desc->interrupt_parent != parent)
				continue;
			//从intc_desc_list删除
			list_del(&desc->list);
			//匹配节点,获取对应的of_device_id
			match = of_match_node(matches, desc->dev);
			if (WARN(!match->data,
			    "of_irq_init: no init function for %s\n",
			    match->compatible)) {
				kfree(desc);
				continue;
			}

			pr_debug("of_irq_init: init %s @ %p, parent %p\n",
				 match->compatible,
				 desc->dev, desc->interrupt_parent);
			//获取初始化函数 gic_of_init
			irq_init_cb = (of_irq_init_cb_t)match->data;
			//调用中断控制器的初始化函数 gic_of_init(desc->dev, desc->interrupt_parent)
			ret = irq_init_cb(desc->dev, desc->interrupt_parent);
			if (ret) {
				kfree(desc);
				continue;
			}

			/*
			 * This one is now set up; add it to the parent list so
			 * its children can get processed in a subsequent pass.
			 */
			 //将root intr插入intc_parent_list
			list_add_tail(&desc->list, &intc_parent_list);
		}

		/* Get the next pending parent that might have children */
		desc = list_first_entry_or_null(&intc_parent_list,
						typeof(*desc), list);
		if (!desc) {
			pr_err("of_irq_init: children remain, but no parents\n");
			break;
		}
		list_del(&desc->list);
		parent = desc->dev;
		kfree(desc);
	}

	list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
		list_del(&desc->list);
		kfree(desc);
	}
err:
	list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
		list_del(&desc->list);
		kfree(desc);
	}
}

我使用的设备树如下,那么对应初始化函数就是gic_of_init

	intc: interrupt-controller@00a01000 {
		compatible = "arm,cortex-a7-gic";
		#interrupt-cells = <3>;
		interrupt-controller;
		reg = <0x00a01000 0x1000>,
		      <0x00a02000 0x100>;
	};

gic_of_init 会根据设备树的配置信息,去初始化GIC.这里需要对GIC的编程方法有一定了解.就是对GIC的地址进行映射,然后调用gic_init_bases,以及处理多核和父节点存在的情况.

//gic初始化函数
//node:中断控制器节点
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
	void __iomem *cpu_base;
	void __iomem *dist_base;
	u32 percpu_offset;
	int irq;

	if (WARN_ON(!node))
		return -ENODEV;
	//对distribute的地址进行映射
	dist_base = of_iomap(node, 0);
	WARN(!dist_base, "unable to map gic dist registers\n");
	//对cpu interface的地址进行映射
	cpu_base = of_iomap(node, 1);
	WARN(!cpu_base, "unable to map gic cpu registers\n");

	if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
		percpu_offset = 0;
	//初始化gic
	gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
	if (!gic_cnt)
		gic_init_physaddr(node);

	//有父节点的情况
	if (parent) {
		irq = irq_of_parse_and_map(node, 0);
		gic_cascade_irq(gic_cnt, irq);
	}
	//不考虑了
	if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
		gicv2m_of_init(node, gic_data[gic_cnt].domain);

	gic_cnt++;
	return 0;
}

gic_init_bases 实现了irq_domain的创建,这个domain对应GIC,是整个系统中的根domain。另外函数还使用向GIC CPU interface的寄存器写入初始值,使能GIC。

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
			   void __iomem *dist_base, void __iomem *cpu_base,
			   u32 percpu_offset, struct device_node *node)
{
	irq_hw_number_t hwirq_base;
	struct gic_chip_data *gic;
	int gic_irqs, irq_base, i;

	BUG_ON(gic_nr >= MAX_GIC_NR);

	gic = &gic_data[gic_nr];
#ifdef CONFIG_GIC_NON_BANKED
	if (percpu_offset) { /* Frankein-GIC without banked registers... */
		unsigned int cpu;

		gic->dist_base.percpu_base = alloc_percpu(void __iomem *);
		gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);
		if (WARN_ON(!gic->dist_base.percpu_base ||
			    !gic->cpu_base.percpu_base)) {
			free_percpu(gic->dist_base.percpu_base);
			free_percpu(gic->cpu_base.percpu_base);
			return;
		}

		for_each_possible_cpu(cpu) {
			u32 mpidr = cpu_logical_map(cpu);
			u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
			unsigned long offset = percpu_offset * core_id;
			*per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset;
			*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset;
		}

		gic_set_base_accessor(gic, gic_get_percpu_base);
	} else
#endif
	{			/* Normal, sane GIC... */
		WARN(percpu_offset,
		     "GIC_NON_BANKED not enabled, ignoring %08x offset!",
		     percpu_offset);
		//保存地址到gic对象
		gic->dist_base.common_base = dist_base;
		gic->cpu_base.common_base = cpu_base;
		//设置访问gic的方法
		gic_set_base_accessor(gic, gic_get_common_base);
	}

	/*
	 * Initialize the CPU interface map to all CPUs.
	 * It will be refined as each CPU probes its ID.
	 */
	 //多核相关的
	for (i = 0; i < NR_GIC_CPU_IF; i++)
		gic_cpu_map[i] = 0xff;

	/*
	 * Find out how many interrupts are supported.
	 * The GIC only supports up to 1020 interrupt sources.
	 */
	 //获取gic支持的中断数量
	gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
	gic_irqs = (gic_irqs + 1) * 32;
	if (gic_irqs > 1020)
		gic_irqs = 1020;
	gic->gic_irqs = gic_irqs;

	//初始化irqdesc
	if (node) {		
		//使用设备树时,只需要创建irq_domain
		//使用线性映射,重要的是gic_irq_domain_hierarchy_ops
		gic->domain = irq_domain_add_linear(node, gic_irqs,
						    &gic_irq_domain_hierarchy_ops,
						    gic);
	} else {		
		//不带设备树的方法,不分析了
		/*
		 * For primary GICs, skip over SGIs.
		 * For secondary GICs, skip over PPIs, too.
		 */
		if (gic_nr == 0 && (irq_start & 31) > 0) {
			hwirq_base = 16;
			if (irq_start != -1)
				irq_start = (irq_start & ~31) + 16;
		} else {
			hwirq_base = 32;
		}

		gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */

		irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
					   numa_node_id());
		if (IS_ERR_VALUE(irq_base)) {
			WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
			     irq_start);
			irq_base = irq_start;
		}

		gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
					hwirq_base, &gic_irq_domain_ops, gic);
	}

	if (WARN_ON(!gic->domain))
		return;

	if (gic_nr == 0) {
#ifdef CONFIG_SMP
		set_smp_cross_call(gic_raise_softirq);
		register_cpu_notifier(&gic_cpu_notifier);
#endif
		set_handle_irq(gic_handle_irq);
	}
	//对gic的distributor,cpu interface pm的初始化,写寄存器
	gic_dist_init(gic);
	gic_cpu_init(gic);
	gic_pm_init(gic);
}

domain分为使用设备树和不使用设备树的情况,当使用设备树时,domain的接口是gic_irq_domain_hierarchy_ops.目前他提供的以下三个接口:

//irq_domain的方法
static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
	.xlate = gic_irq_domain_xlate,  //获取设备树节点的中断信息
	.alloc = gic_irq_domain_alloc,	//设置domain下irq_desc的成员
	.free = irq_domain_free_irqs_top,  //清除domain下irqdesc的成员
};

首先是 gic_irq_domain_xlate可以获取domain节点下中断的hw_irq,例如:interrupts = ;是gic的子节点.

//intspec[]={GIC_SPI,86,IRQ_TYPE_LEVEL_HIGH};
static int gic_irq_domain_xlate(struct irq_domain *d,
				struct device_node *controller,
				const u32 *intspec, unsigned int intsize,
				unsigned long *out_hwirq, unsigned int *out_type)
{
	unsigned long ret = 0;

	if (d->of_node != controller)
		return -EINVAL;
	if (intsize < 3)
		return -EINVAL;

	/* Get the interrupt number and add 16 to skip over SGIs */
	//获取中断号,加16跳过sgis
	*out_hwirq = intspec[1] + 16;

	/* For SPIs, we need to add 16 more to get the GIC irq ID number */
	//gic_spi类型还要再跳过16
	if (!intspec[0])
		*out_hwirq += 16;
	//获取类型
	*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;

	return ret;
}

gic_irq_domain_alloc是初始化domain下的irqdesc,主要是设置中断流处理函数desc->handle_irq = handle_fasteoi_irq

//设置中断信息
static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
				unsigned int nr_irqs, void *arg)
{
	int i, ret;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	struct of_phandle_args *irq_data = arg;
	//获取硬件中断号
	ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args,
				   irq_data->args_count, &hwirq, &type);
	if (ret)
		return ret;
	//设置对应中断信息
	for (i = 0; i < nr_irqs; i++)
		gic_irq_domain_map(domain, virq + i, hwirq + i);

	return 0;
}

五、参考博客

Linux中斷子系統框架流程詳解(基於Kernel 3.16,arm,設備樹) - 台部落 (twblogs.net)

中断子系统 - 蜗窝科技 (wowotech.net)

你可能感兴趣的:(linux系统,linux,中断子系统,中断框架,IRQ,interrupt)