Linux中断子系统3

Linux中断子系统3(基于Linux6.6)---IRQ number和中断描述符介绍

一、基本概念

1.1、通用中断的代码处理示意图

通用中断处理的示意图如下:

在Linux kernel中,对于每一个外设的IRQ都用struct irq_desc来描述,称之中断描述符(struct irq_desc)。linux kernel中会有一个数据结构保存了关于所有IRQ的中断描述符信息,称之中断描述符DB(上图中红色框图内)。当发生中断后,首先获取触发中断的HW interupt ID,然后通过irq domain翻译成IRQ number,然后通过IRQ number就可以获取对应的中断描述符。调用中断描述符中的highlevel irq-events handler来进行中断处理就OK了。

highlevel irq-events handler主要进行下面两个操作:

(1)调用中断描述符的底层irq chip driver进行mask,ack等callback函数,进行interrupt flow control。

(2)调用该中断描述符上的action list中的specific handler。这个步骤不一定会执行,这是和中断描述符的当前状态相关,实际上,interrupt flow control是软件(设定一些标志位,软件根据标志位进行处理)和硬件(mask或者unmask interrupt controller等)一起控制完成的。

1.2、中断的打开和关闭

整个通用中断处理过程中的开关中断情况,开关中断有两种:

(1)开关local CPU的中断。对于CPU,关闭CPU中断就关闭了一切,永远不会被抢占。对于SMP,实际上,没有关全局中断这一说,只能关闭local CPU。

(2)控制interrupt controller,关闭某个IRQ number对应的中断。更准确的术语是mask或者unmask一个 IRQ。

新的内核中在interrupt specific handler中是全程关闭CPU中断的。

1.3、IRQ number

从CPU的角度看,无论外部的Interrupt controller的结构是多么复杂,只关心发生了一个指定外设的中断,需要调用相应的外设中断的handler就好。更准确的说是通用中断处理模块不关心外部interrupt controller的组织细节。一言以蔽之,通用中断处理模块可以用一个线性的table来管理一个个的外部中断,这个表的每个元素就是一个irq描述符,在kernel/irq/irqdesc.c中定义如下:


struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};
 

系统中每一个连接外设的中断线(irq request line)用一个中断描述符来描述,每一个外设的interrupt request line分配一个中断号(irq number),系统中有多少个中断线(或者叫做中断源)就有多少个中断描述符(struct irq_desc)。NR_IRQS定义了该硬件平台IRQ的最大数目。

总之,一个静态定义的表格,irq number作为index,每个描述符都是紧密的排在一起,一切看起来很美好,但是现实很残酷的。有些系统可能会定义一个很大的NR_IRQS,但是只是想用其中的若干个,换句话说,这个静态定义的表格不是每个entry都是有效的,有空洞,如果使用静态定义的表格就会导致了内存很大的浪费。

为什么会有这种需求?各个interrupt controller硬件的interrupt ID映射到irq number的算法有关。在这种情况下,静态表格不适合了,用一个radix tree来保存中断描述符(HW interrupt作为索引)。这时候,每一个中断描述符都是动态分配,然后插入到radix tree中。如果采用这种策略,那么需要打开CONFIG_SPARSE_IRQ选项。如果打开CONFIG_SPARSE_IRQ选项,系统使用Radix tree来保存中断描述符DB,不过概念和静态表格是类似的。

此外,需要注意的是,在旧内核中,IRQ number和硬件的连接有一定的关系,但是,在引入irq domain后,IRQ number已经变成一个单纯的number,和硬件没有任何关系。

二、中断描述符数据结构

2.1、底层irq chip相关的数据结构

中断描述符中应该会包括底层irq chip相关的数据结构,linux kernel中把这些数据组织在一起,形成struct irq_data,具体代码如下:include/linux/irq.h

  
/**
 * struct irq_data - per irq chip data passed down to chip functions
 * @mask:		precomputed bitmask for accessing the chip registers
 * @irq:		interrupt number
 * @hwirq:		hardware interrupt number, local to the interrupt domain
 * @common:		point to data shared by all irqchips
 * @chip:		low level interrupt hardware access
 * @domain:		Interrupt translation domain; responsible for mapping
 *			between hwirq number and linux irq number.
 * @parent_data:	pointer to parent struct irq_data to support hierarchy
 *			irq_domain
 * @chip_data:		platform-specific per-chip private data for the chip
 *			methods, to allow shared chip implementations
 */
struct irq_data {
	u32			mask;                   //预先计算的位掩码,用于访问芯片寄存器 
	unsigned int		irq;            //IRQ number 
	unsigned long		hwirq;          //该domain上的物理中断号
	struct irq_common_data	*common;    //指向所有irqchips共享的数据
	struct irq_chip		*chip;          //该中断描述符对应的irq chip数据结构 
	struct irq_domain	*domain;        //该中断描述符对应的irq domain数据结构 
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data;   //对于级联的中断控制器,他的上一级的irq_data
#endif
	void			*chip_data;         //和中断控制器相关的私有数据 
};
 
 
 
 
/**
 * struct irq_common_data - per irq data shared by all irqchips
 * @state_use_accessors: status information for irq chip functions.
 *			Use accessor functions to deal with it
 * @node:		node index useful for balancing
 * @handler_data:	per-IRQ data for the irq_chip methods
 * @affinity:		IRQ affinity on SMP. If this is an IPI
 *			related irq, then this is the mask of the
 *			CPUs to which an IPI can be sent.
 * @effective_affinity:	The effective IRQ affinity on SMP as some irq
 *			chips do not allow multi CPU destinations.
 *			A subset of @affinity.
 * @msi_desc:		MSI descriptor
 * @ipi_offset:		Offset of first IPI target cpu in @affinity. Optional.
 */
struct irq_common_data {
	unsigned int		__private state_use_accessors;    //芯片功能的状态信息
#ifdef CONFIG_NUMA
	unsigned int		node;                 //节点索引有助于平衡
#endif
	void			*handler_data;            //和外设specific handler相关的私有数据 
	struct msi_desc		*msi_desc;
	cpumask_var_t		affinity;             //SMP上的IRQ亲和力
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
	cpumask_var_t		effective_affinity;   //对SMP的有效IRQ亲和力,因为一些irq芯片不允许多CPU目的地。@ affinity的子集。
#endif
#ifdef CONFIG_GENERIC_IRQ_IPI
	unsigned int		ipi_offset;
#endif
};

中断有两种形态:一种就是直接通过signal相连,用电平或者边缘触发。另外一种是基于消息的,被称为MSI (Message Signaled Interrupts)。msi_desc是MSI类型的中断相关,这里不再描述。

node成员用来保存中断描述符的内存位于哪一个memory node上。 对于支持NUMA(的系统,其内存空间并不是均一的,而是被划分成不同的node,对于不同的memory node,CPU其访问速度是不一样的。如果一个IRQ大部分(或者固定)由某一个CPU处理,那么在动态分配中断描述符的时候,应该考虑将内存分配在该CPU访问速度比较快的memory node上。

2.2、irq chip数据结构

Interrupt controller描述符(struct irq_chip)包括了若干和具体Interrupt controller相关的callback函数,include/linux/irq.h

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);

#ifdef CONFIG_DEPRECATED_IRQ_CPU_ONOFFLINE
	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);
#endif
	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);

	int		(*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

	void		(*ipi_send_single)(struct irq_data *data, unsigned int cpu);
	void		(*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

	int		(*irq_nmi_setup)(struct irq_data *data);
	void		(*irq_nmi_teardown)(struct irq_data *data);

	unsigned long	flags;
};

2.3、中断描述符

在linux kernel中,使用struct irq_desc来描述一个外设的中断,我们称之中断描述符,具体代码如下。include/linux/irqdesc.h

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;
#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
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
	struct dentry		*debugfs_file;
	const char		*dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
	struct rcu_head		rcu;
	struct kobject		kobj;
#endif
	struct mutex		request_mutex;
	int			parent_irq;
	struct module		*owner;
	const char		*name;
#ifdef CONFIG_HARDIRQS_SW_RESEND
	struct hlist_node	resend_node;
#endif
} ____cacheline_internodealigned_in_smp;

说明分析:

(1)handle_irq就是highlevel irq-events handler,何谓high level?站在高处自然看不到细节。我认为high level是和specific相对,specific handler处理具体的事务,例如处理一个按键中断、处理一个磁盘中断。而high level则是对处理各种中断交互过程的一个抽象,根据下列硬件的不同:

  • 中断控制器
  • IRQ trigger type

highlevel irq-events handler可以分成:

  • 处理电平触发类型的中断handler(handle_level_irq)
  • 处理边缘触发类型的中断handler(handle_edge_irq)
  • 处理简单类型的中断handler(handle_simple_irq)
  • 处理EOI类型的中断handler(handle_fasteoi_irq)

会另外有一份文档对high level handler进行更详细的描述。

(2)action指向一个struct irqaction的链表。如果一个interrupt request line允许共享,那么该链表中的成员可以是多个,否则,该链表只有一个节点。

(3)这个有着很长名字的符号core_internal_state__do_not_mess_with_it在具体使用的时候被被简化成istate,表示internal state。就像这个名字定义的那样,我们最好不要直接修改它。

#define istate core_internal_state__do_not_mess_with_it

(4)我们可以通过enable和disable一个指定的IRQ来控制内核的并发,从而保护临界区的数据。对一个IRQ进行enable和disable的操作可以嵌套(当然一定要成对使用),depth是描述嵌套深度的信息。

(5)wake_depth是和电源管理中的wake up source相关。通过irq_set_irq_wake接口可以enable或者disable一个IRQ中断是否可以把系统从suspend状态唤醒。同样的,对一个IRQ进行wakeup source的enable和disable的操作可以嵌套(当然一定要成对使用),wake_depth是描述嵌套深度的信息。

(6)irq_count、last_unhandled和irqs_unhandled用于处理broken IRQ 的处理。所谓broken IRQ就是由于种种原因(例如错误firmware),IRQ handler没有定向到指定的IRQ上,当一个IRQ没有被处理的时候,kernel可以为这个没有被处理的handler启动scan过程,让系统中所有的handler来认领该IRQ。

(7)保护该中断描述符的spin lock。

(8)一个中断描述符可能会有两种情况,一种是该IRQ是global,一旦disable了该irq,那么对于所有的CPU而言都是disable的。还有一种情况,就是该IRQ是per CPU的,也就是说,在某个CPU上disable了该irq只是disable了本CPU的IRQ而已,其他的CPU仍然是enable的。percpu_enabled是一个描述该IRQ在各个CPU上是否enable成员。

(9)threads_oneshot、threads_active和wait_for_threads是和IRQ thread相关,后续文档会专门描述。

(10)nr_actions(共享中断数量)、no_suspend_depth、cond_suspend_depth、force_resume_depth,都和电源管理有关。

(11)parent_irq,级联时,这个中断控制器,它的父中断号。

(12)dir,用于在proc文件系统中展示。

三、初始化中断描述符

3.1、静态定义的中断描述符初始化

  
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};
 

kernel/irq/irqdesc.c
int __init early_irq_init(void)
{
	int count, i, node = first_online_node;
	struct irq_desc *desc;
 
	init_irq_default_affinity();
 
	printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);
 
	desc = irq_desc;
	count = ARRAY_SIZE(irq_desc);
 
    //遍历整个lookup table,对每一个entry进行初始化 
	for (i = 0; i < count; i++) {        
		desc[i].kstat_irqs = alloc_percpu(unsigned int);   //分配per cpu的irq统计信息需要的内存 
		alloc_masks(&desc[i], node);            //分配中断描述符中需要的cpu mask内存
		raw_spin_lock_init(&desc[i].lock);      //初始化spin lock 
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		desc_set_defaults(i, &desc[i], node, NULL, NULL);    //给desc设定default值 
	}
	return arch_early_irq_init();        //调用arch相关的初始化函数 ,一般为空
}
 
 
kernel/irq/irqdesc.c
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
			      const struct cpumask *affinity, struct module *owner)
{
	int cpu;
 
	desc->irq_common_data.handler_data = NULL;
	desc->irq_common_data.msi_desc = NULL;
 
	desc->irq_data.common = &desc->irq_common_data;
	desc->irq_data.irq = irq;
	desc->irq_data.chip = &no_irq_chip;
	desc->irq_data.chip_data = NULL;
	irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
	irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
	irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
	desc->handle_irq = handle_bad_irq;
	desc->depth = 1;
	desc->irq_count = 0;
	desc->irqs_unhandled = 0;
	desc->name = NULL;
	desc->owner = owner;
	for_each_possible_cpu(cpu)
		*per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
	desc_smp_init(desc, node, affinity);
}

3.2、使用Radix tree的中断描述符初始化

kernel/irq/irqdesc.c 

  
int __init early_irq_init(void)
{
	int i, initcnt, node = first_online_node;
	struct irq_desc *desc;
 
	init_irq_default_affinity();
 
	/* Let arch update nr_irqs and return the nr of preallocated irqs */
	initcnt = arch_probe_nr_irqs();    //体系结构相关的代码来决定预先分配的中断描述符的个数
	printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
	       NR_IRQS, nr_irqs, initcnt);
 
	if (WARN_ON(nr_irqs > MAX_SPARSE_IRQS))
		nr_irqs = MAX_SPARSE_IRQS;

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

 
	if (initcnt > nr_irqs)    //initcnt是需要在初始化的时候预分配的IRQ的个数 
		nr_irqs = initcnt;    //nr_irqs是当前系统中IRQ number的最大值 
 
    //预先分配initcnt个中断描述符 
	for (i = 0; i < initcnt; i++) {
		desc = alloc_desc(i, node, 0, NULL, NULL);    //分配中断描述符 
		set_bit(i, allocated_irqs);                   //设定已经alloc的flag 
		irq_insert_desc(i, desc);                     //插入radix tree 
	}
	return arch_early_irq_init();
}

即便是配置了CONFIG_SPARSE_IRQ选项,在中断描述符初始化的时候,也有机会预先分配一定数量的IRQ。这个数量由arch_probe_nr_irqs决定,对于ARM而言,其arch_probe_nr_irqs定义如下:arch/arm/kernel/irq.c

 #ifdef CONFIG_SPARSE_IRQ
int __init arch_probe_nr_irqs(void)
{
	nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;
	return nr_irqs;
}
#endif

3.3、分配和释放中断描述符

对于使用Radix tree来保存中断描述符DB的linux kernel,其中断描述符是动态分配的,可以使用irq_alloc_descs和irq_free_descs来分配和释放中断描述符。alloc_desc函数也会对中断描述符进行初始化,初始化的内容和静态定义的中断描述符初始化过程是一样的。最大可以分配的ID是MAX_SPARSE_IRQS,定义如下:kernel/irq/internals.h

#ifdef CONFIG_SPARSE_IRQ
# define MAX_SPARSE_IRQS	INT_MAX
#else
# define MAX_SPARSE_IRQS	NR_IRQS
#endif

四、中断描述符的接口

这部分的接口主要有两类,irq_desc_get_xxx和irq_set_xxx,由于get接口API非常简单,这里不再描述,主要描述set类别的接口API。此外,还有一些locked版本的set接口API,定义为__irq_set_xxx,这些API的调用者应该已经持有保护irq desc的spinlock,因此,这些locked版本的接口没有中断描述符的spin lock进行操作。这些接口有自己特定的使用场合,这里也不详细描述了。

4.1、接口调用时机

kernel提供了若干的接口API可以让内核其他模块可以操作指定IRQ number的描述符结构。中断描述符中有很多的成员是和底层的中断控制器相关,例如:

(1)该中断描述符对应的irq chip

(2)该中断描述符对应的irq trigger type

(3)high level handler

在过去,系统中各个IRQ number是固定分配的,各个IRQ对应的中断控制器、触发类型等也都是清楚的,因此,一般都是在machine driver初始化的时候一次性的进行设定。machine driver的初始化过程会包括中断系统的初始化,在machine driver的中断初始化函数中,会调用本文定义的这些接口对各个IRQ number对应的中断描述符进行irq chip、触发类型的设定。

在引入了设备树、动态分配IRQ number以及irq domain这些概念之后,这些接口的调用时机移到各个中断控制器的初始化以及各个具体硬件驱动初始化过程中,具体如下:

(1)各个中断控制器定义好自己的struct irq_domain_ops callback函数,主要是map和translate函数。

(2)在各个具体的硬件驱动初始化过程中,通过device tree系统可以知道自己的中断信息(连接到哪一个interrupt controler、使用该interrupt controller的那个HW interrupt ID,trigger type为何),调用对应的irq domain的translate进行翻译、解析。之后可以动态申请一个IRQ number并和该硬件外设的HW interrupt ID进行映射,调用irq domain对应的map函数。在map函数中,可以调用本节定义的接口进行中断描述符底层interrupt controller相关信息的设定。

4.2、irq_set_chip

这个接口函数用来设定中断描述符中desc->irq_data.chip成员,具体代码如下:

kernel/irq/chip.c

  
/**
 *	irq_set_chip - set the irq chip for an irq
 *	@irq:	irq number
 *	@chip:	pointer to irq chip description structure
 */
int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);        //1
 
	if (!desc)
		return -EINVAL;
 
	if (!chip)
		chip = &no_irq_chip;
 
	desc->irq_data.chip = chip;
	irq_put_desc_unlock(desc, flags);                                 //2
	/*
	 * For !CONFIG_SPARSE_IRQ make the irq show up in
	 * allocated_irqs.
	 */
	irq_mark_irq(irq);
	return 0;
}

(1)获取irq number对应的中断描述符。这里用关闭中断+spin lock来保护中断描述符,flag中就是保存的关闭中断之前的状态flag。

(2)中会恢复中断flag。

(3)irq number有静态表格定义的,也有radix tree类型的。对于静态定义的中断描述符,没有alloc的概念。但是,对于radix tree类型,需要首先irq_alloc_desc或者irq_alloc_descs来分配一个或者一组IRQ number,在这些alloc函数值,就会set那些那些已经分配的IRQ。对于静态表格而言,其IRQ没有分配,因此,这里通过irq_mark_irq函数标识该IRQ已经分配,对于CONFIG_SPARSE_IRQ(使用radix tree)的配置而言,这个操作是一个空函数。

kernel/irq/internals.h
#ifdef CONFIG_SPARSE_IRQ
static inline void irq_mark_irq(unsigned int irq) { }    //空函数
#else
extern void irq_mark_irq(unsigned int irq);
#endif
 
 
kernel/irq/irqdesc.c
void irq_mark_irq(unsigned int irq)
{
	mutex_lock(&sparse_irq_lock);
	bitmap_set(allocated_irqs, irq, 1);        //在allocated_irqs标记irq已经使用
	mutex_unlock(&sparse_irq_lock);
}

4.3、irq_set_irq_type

这个函数是用来设定该irq number的trigger type的。kernel/irq/chip.c

  
/**
 *	irq_set_type - set the irq trigger type for an irq
 *	@irq:	irq number
 *	@type:	IRQ_TYPE_{LEVEL,EDGE}_* value - see include/linux/irq.h
 */
int irq_set_irq_type(unsigned int irq, unsigned int type)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
	int ret = 0;
 
	if (!desc)
		return -EINVAL;
 
	ret = __irq_set_trigger(desc, type);
	irq_put_desc_busunlock(desc, flags);
	return ret;
}

来到这个接口函数,第一个问题就是:为何irq_set_chip接口函数使用irq_get_desc_lock来获取中断描述符,而irq_set_irq_type这个函数却需要irq_get_desc_buslock呢?

其实也很简单,irq_set_chip不需要访问底层的irq chip(也就是interrupt controller),但是irq_set_irq_type需要。设定一个IRQ的trigger type最终要调用desc->irq_data.chip->irq_set_type函数对底层的interrupt controller进行设定。这时候,问题来了,对于嵌入SOC内部的interrupt controller,当然没有问题,因为访问这些中断控制器的寄存器memory map到了CPU的地址空间,访问非常的快,因此,关闭中断+spin lock来保护中断描述符当然没有问题,但是,如果该interrupt controller是一个I2C接口的IO expander芯片(这类芯片是扩展的IO,也可以提供中断功能),这时,让其他CPU进行spin操作太浪费CPU时间了(bus操作太慢了,会spin很久的)。肿么办?当然只能是用其他方法lock住bus了(例如mutex,具体实现是和irq chip中的irq_bus_lock实现相关)。一旦lock住了slow bus,然后就可以关闭中断了(中断状态保存在flag中)。

解决了bus lock的疑问后,还有一个看起来奇奇怪怪的宏:IRQ_GET_DESC_CHECK_GLOBAL。为何在irq_set_chip函数中不设定检查(check的参数是0),而在irq_set_irq_type接口函数中要设定global的check,到底是什么意思呢?既然要检查,那么检查什么呢?和“global”对应的不是local而是“per CPU”,内核中的宏定义是:IRQ_GET_DESC_CHECK_PERCPU。SMP情况下,从系统角度看,中断有两种形态(或者叫mode):

(1)1-N mode。只有1个processor处理中断。

(2)N-N mode。所有的processor都是独立的收到中断,如果有N个processor收到中断,那么就有N个处理器来处理该中断。

听起来有些抽象,用GIC作为例子来具体描述。在GIC中,SPI使用1-N模型,而PPI和SGI使用N-N模型。对于SPI,由于采用了1-N模型,系统(硬件加上软件)必须保证一个中断被一个CPU处理。对于GIC,一个SPI的中断可以trigger多个CPU的interrupt line(如果Distributor中的Interrupt Processor Targets Registers有多个bit被设定),但是,该interrupt source和CPU的接口寄存器(例如ack register)只有一套,也就是说,这些寄存器接口是全局的,是global的,一旦一个CPU ack(读Interrupt Acknowledge Register,获取interrupt ID)了该中断,那么其他的CPU看到的该interupt source的状态也是已经ack的状态。在这种情况下,如果第二个CPU ack该中断的时候,将获取一个spurious interrupt ID。

对于PPI或者SGI,使用N-N mode,其interrupt source的寄存器是per CPU的,也就是每个CPU都有自己的、针对该interrupt source的寄存器接口(这些寄存器叫做banked register)。一个CPU 清除了该interrupt source的pending状态,其他的CPU感知不到这个变化,它们仍然认为该中断是pending的。

对于irq_set_irq_type这个接口函数,它是for 1-N mode的interrupt source使用的。如果底层设定该interrupt是per CPU的,那么irq_set_irq_type要返回错误。

4.4、irq_set_chip_data

每个irq chip总有自己私有的数据,称之chip data。具体设定chip data的代码如下:

kernel/irq/chip.c

 /**
 *	irq_set_chip_data - set irq chip data for an irq
 *	@irq:	Interrupt number
 *	@data:	Pointer to chip specific data
 *
 *	Set the hardware irq chip data for an irq
 */
int irq_set_chip_data(unsigned int irq, void *data)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);
 
	if (!desc)
		return -EINVAL;
	desc->irq_data.chip_data = data;
	irq_put_desc_unlock(desc, flags);
	return 0;
}

4.5、设定high level handler

这是中断处理的核心内容,__irq_set_handler就是设定high level handler的接口函数,不过一般不会直接调用,而是通过irq_set_chip_and_handler_name或者irq_set_chip_and_handler来进行设定。具体代码如下:kernel/irq/chip.c

 void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
 
	if (!desc)
		return;
 
	__irq_do_set_handler(desc, handle, is_chained, name);
	irq_put_desc_busunlock(desc, flags);
}
 
 
 
static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
		     int is_chained, const char *name)
{
	if (!handle) {
		handle = handle_bad_irq;
	} else {
		struct irq_data *irq_data = &desc->irq_data;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
		/*
		 * With hierarchical domains we might run into a
		 * situation where the outermost chip is not yet set
		 * up, but the inner chips are there.  Instead of
		 * bailing we install the handler, but obviously we
		 * cannot enable/startup the interrupt at this point.
		 */
		while (irq_data) {
			if (irq_data->chip != &no_irq_chip)
				break;
			/*
			 * Bail out if the outer chip is not set up
			 * and the interrrupt supposed to be started
			 * right away.
			 */
			if (WARN_ON(is_chained))
				return;
			/* Try the parent */
			irq_data = irq_data->parent_data;
		}
#endif
		if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
			return;
	}
 
	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->irq_data.chip != &no_irq_chip)
			mask_ack_irq(desc);
		irq_state_set_disabled(desc);
		if (is_chained)
			desc->action = NULL;
		desc->depth = 1;
	}
	desc->handle_irq = handle;
	desc->name = name;
 
	if (handle != handle_bad_irq && is_chained) {
		unsigned int type = irqd_get_trigger_type(&desc->irq_data);
 
		/*
		 * We're about to start this interrupt immediately,
		 * hence the need to set the trigger configuration.
		 * But the .set_type callback may have overridden the
		 * flow handler, ignoring that we're dealing with a
		 * chained interrupt. Reset it immediately because we
		 * do know better.
		 */
		if (type != IRQ_TYPE_NONE) {
			__irq_set_trigger(desc, type);
			desc->handle_irq = handle;
		}
 
		irq_settings_set_noprobe(desc);
		irq_settings_set_norequest(desc);
		irq_settings_set_nothread(desc);
		desc->action = &chained_action;
		irq_activate_and_startup(desc, IRQ_RESEND);
	}
}

理解这个函数的关键是在is_chained这个参数。这个参数是用在interrupt级联的情况下。例如中断控制器B级联到中断控制器A的第x个interrupt source上。那么对于A上的x这个interrupt而言,在设定其IRQ handler参数的时候要设定is_chained参数等于1,由于这个interrupt source用于级联,因此不能probe、不能被request(已经被中断控制器B使用了),不能被threaded。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值