linux -- 中断管理 -- 中断管理以及PIC可编程中断控制器

文章详细介绍了计算机系统中的中断和异常机制,包括中断与异常的区别、中断处理流程、中断向量表、中断处理的层次结构、中断注册以及设备驱动如何注册中断处理函数。重点讨论了中断处理的HARDIRQ和SOFTIRQ阶段,以及中断共享和中断号探测机制。
摘要由CSDN通过智能技术生成

1 中断分为同步中断(中断)和异步中断(异常)

1.1 中断和异常的不同

  • 中断由IO设备和定时器产生,用户的一次按键会引起中断。异步。

  • 异常一般由程序错误产生或者由内核必须处理的异常条件产生。同步。缺页异常,断点int3

异常如果由程序错误产生,内核通过发送一个信号来处理异常

如果由内核必须处理的异常条件诱发,那么内核必须执行所需要的所有步骤。

(思考信号产生的动机)

1.2 中断和进程上下文切换的不同

中断切换的代码不是一个进程,而是一个内核控制路径,代表中断发生时正在执行的进程在内核中执行。

1.3 中断敏感性

  • 当内核正打算做一些事情,中断随时可能会到来,因此中断必须很快完成(以便内核处理它的事情),尽可能把多的事情放到后面执行。

  • 中断随时可能到来,在处理一个中断时必须允许其他中断到来。这样可以允许尽量多的IO设备处于忙碌状态。所以中断应该允许嵌套

  • 临界区中,中断必须被禁止。这个要求对于临界区来说是必要的,但是对于内核规则来说是不应当的。所以应该减少临界区。

1.4 中断向量表

非屏蔽中断和异常的向量是固定的,可屏蔽中断的向量可以通过编程来改变。

1.5 IRQ和中断

IRQ(Interrupt request)是设备用来向可编程中断控制器发送中断请求的,IRQ在硬件上和中断控制器相连接。一个设备可能有多条IRQ,如PCI卡有4条。

当设备产生中断信号时,会发送到IRQ线上。中断信号会被中断控制器一直监视,如果有条IRQ线上同时出现中断信号,那么会选取IRQn值最小的那一个优先处理。

中断控制器会将从IRQn上接受来的中断信号转换为对应的向量,将这个向量放到I/O端口上以便CPU需要的时候通过读取数据总线来读。中断控制器将接受到的中断信号转发到CPU的INTR引脚上产生一个中断

接下来就是等待CPU把这个中断信号写进可编程中断控制器的I/O端口来确认这个向量。如果确实是,则清INTR线。

IRQ编号从0开始,IRQn对应的intel的缺省向量为n+32。IRQ和向量的对应关系可以通过一些指令来重新编程。

选择性的禁止IRQ线相当于"服务台"(中断控制器)让客户的需求(设备中断)排队,当开启IRQ时还会来处理需求

通过cli sti来开启和屏蔽可屏蔽中断相当于服务台暂时不上班,客户的需求会被忽略。

1.6 多APIC系统和中断分发方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnHwwudK-1678462382637)(null)]

  • 静态分发

  • 动态分发 中断在CPU之间分发

CPU产生处理器之间的中断,这在SMP系统中很有用。

如果是单处理器,那么APIC可以弱化成8259A,LIN0 LIN1分别作为INTR和NMI引脚使用

也可以作为一个外部的I/O APIC使用(虽然它在处理器内部),本地APIC被激活。

1.7 中断框架 注册以及调用

中断框架中对 IRQ Line 和 PIC 分别做了抽象。暂且不讨论SMP情形,将外设-中断控制器-处理器这条通路简化为下图:
(不同的硬件会将中断控制器放在不同的位置:如X86的8259放在CPU外,嵌入式领域的SoC一般在芯片内集成)
在这里插入图片描述
一个外部设备要想将中断发送给CPU ,首先要配置好PIC。系统中断处理框架已将提供了PIC配置的接口供设备驱动程序使用(设备可能不是一开始就连接在PIC某一中断引脚上的,所以需要在设备驱动程序中调用PIC配置相关接口)
PIC的配置包含以下工作:

  1. 设定外设的中断触发类型 水平or边沿
  2. 将外设的中断引脚编号映射到处理器可见的软件中断号irq
  3. 屏蔽掉某些外部设备的中断触发
    处理器提供的PIC配置接口最终也是要落实到配置PIC寄存器上的,比如ARM GIC-400提供了一系列控制寄存器来完成以上所有配置工作。
    软件中断号是处理器可见的irq号,用来标识中断到来时从PIC中读取到的中断号码。现代PIC模块多是级联的,也就是说一个SoC上有多个中断控制器,每个中断控制器上都有外部设备接入IRQ Line,一般情况下每个PIC上的IRQ Line编号是一致的,为了区分不同的设备的IRQ编码就需要带上PIC的编码,也就是IRQ Domin的概念,IRQ Line编号在多中断控制器级联的情况下被称为HW Interrupt ID。也就是说这种情况下HW Interrupt ID + IRQ Domin才能标识出一个外设的中断。不管是单PIC还是多PIC级联,最后到处理器面前的只有软件中断号irq。

Linux初始化阶段会准备好中断向量表,其中一箱就是外部设备的中断向量,这是一个通用的外部中断处理函数的入口地址。从该地址运行通用的中断处理函数。进入通用中断处理函之后系统必须知道正在处理的中断是哪一个设备产生的,而这正是由前面提到的中断号irq决定的。
中断向量表的初始化以及通用外部中断处理函数的编写都由操作系统负责

外部中断发生时预先设计好的处理器硬件逻辑往往会做一些特定的动作,为从软件层面发起中断处理做准备。
不同的处理器行为不同,抽象出一个一般路径:

  1. 保存当前任务的上下文寄存器在中断栈中。
  2. 屏蔽处理器响应外部中断的能力
  3. 根据中断向量表找到通用外部中断处理函数入口地址
  4. 跳转

不同的架构平台上,通用的软件处理函数也有不同,一般过程是:

  1. 设法从PIC中得到导致本次中断的外部设备对应的软件中断号irq(一般用汇编实现)
  2. 调用一个C函数(do_IRQ)来处理中断
  3. C函数返回时完成中断现场恢复工作
  4. 被中断的任务开始继续执行

为什么要关中断执行:因为各个设备的中断处理函数在驱动程序中实现,内核无法保证这些函数的执行时间,如果时间过长会导致系统措施很多中断,丢失数据或者操作系统响应过长时间。
为了解决这个问题,操作系统将中断处理过程分为两个部分:HARDIRQ和SOFTIRQ。
HARDIRQ部分在中断关闭的情况下执行,只做最关键的操作,尽可能短。irq_enter函数。
SOFTIRQ部分在中断打开的情况下执行,这个阶段系统仍可以响应外部设备的中断,耗时操作可以放在这个部分。irq_exit中完成

do_IRQ源码:

asmlinkage void do_IRQ(int irq, struct pt_regs *regs)
{
	struct pt_regs *oldregs = set_irq_regs(regs);

	irq_enter();	//HARDIRQ部分的开始  更新系统中的一些统计量  标识出HARDIRQ上下文
	generic_handle_irq(irq);	//核心
	irq_exit();		//SOFTIRQ部分
	
static inline void generic_handle_irq(unsigned int irq)
{
	//irq_desc 沟通从通用的中断处理函数到设备特定的中断处理例程ISR之间的桥梁作用
	struct irq_desc *desc = &irq_desc[irq];
	//取出对应的irq_desc对象 运行handle_irq函数
	desc->handl_irq(irq, desc);
}

下图是对irq_desc结构以及irq_desc数组的描述图
在这里插入图片描述
irq_desc数组定义:

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_desc数组的初始化在early_irq_init函数中进行:

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

	for (i = 0; i < count; i++) {
		desc[i].kstat_irqs = alloc_percpu(unsigned int);
		alloc_masks(&desc[i], node);
		raw_spin_lock_init(&desc[i].lock);
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		mutex_init(&desc[i].request_mutex);
		init_waitqueue_head(&desc[i].wait_for_threads);
		desc_set_defaults(i, &desc[i], node, NULL, NULL);
	}
	return arch_early_irq_init();
}

struct irq_desc结构包含的成员,这个结构体十分重要
比较关键的成员已经注释

struct irq_desc {
	struct irq_common_data	irq_common_data;
	struct irq_data		irq_data;		//保存软件中断号irq和chip相关数据 包括irq_domain
	unsigned int __percpu	*kstat_irqs;	//per-cpu型成员  统计系统中中断计数
	irq_flow_handler_t	handle_irq;		//函数指针 指向和当前设备中断触发电信号类型相关的函数(指向通用处理函数)
										//边沿触发就对应一个边沿触发类的处理函数 
										//电平触发类的就对应一个电平触发类的处理函数
										//如果没有区分那就提供一个常规处理函数就行
										//handle_irq指向的函数内部才会调用设备特定的中断服务例程
										//不同平台的handle_irq的具体实现在系统初始化阶段提供,设备驱动程序员不必关心
										
	struct irqaction	*action;	/* IRQ action list */ 
									//针对某一个具体设备的中断处理的抽象,是一个链表
									//通过request_irq向其中挂载设备特定的中断处理函数
									//action中有irq_handler_t类型的handler成员,这个才是对应设备中断服务例程ISR
									//设备驱动中安装或卸载的中断处理服务历程ISR也就是装载这个成员里

	//irq_desc结构内部有一个对IRQ Line的操作成员handle_irq
	//还有一个对这条Line上的所有设备的处理函数的成员action
	//handle_irq和action成员负责中断处理的不同部分,handle_irq与irq中断号一一对应,代表了对IRQ Line上的处理动作
	//action代表具体的设备相关的处理函数
	//action是一个链表,多个设备可以共享一条IRQ Line (共享同一中断号irq)所以接到同一链表上,后面IRQ Line上有中断到来时处理器可以遍历这条链表以执行所有处理函数
	
	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;		//查/proc/interrupts文件显示的对应名称
} ____cacheline_internodealigned_in_smp;

来看看struct irq_data结构的irq_data成员保存了哪些

struct irq_data {
	u32			mask;
	unsigned int		irq;		//软件中断号
	unsigned long		hwirq;
	struct irq_common_data	*common;
	struct irq_chip		*chip;		//软件层面对PIC的一个抽象  屏蔽不同平台上PIC的差异  提供统一的PIC操作接口
	struct irq_domain	*domain;	//用于映射hw interrupt id和irq
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_data		*parent_data;
#endif
	void			*chip_data;
};

irq_chip对PIC的抽象,提供统一PIC操作接口
provider需要实现接口中的API以供上层软件操作PIC
示例:

static struct irq_chip irq_chip = {
	.irq_mask = combiner_irq_chip_mask_irq,
	.irq_unmask = combiner_irq_chip_unmask_irq,
	.name = "qcom-irq-combiner"
};

平台的初始化函数负责实现该平台使用的PIC的对象并将其安装到irq_desc数组中
从这个结构中各个函数的名字可以看出,PIC配置工作主要包括设定外部设备的中断触发类型,屏蔽或者启用某一个设备的终端信号,向发出信号的设备发送中断响应信号等。

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

handle_irq和action之间的层次关系

在这里插入图片描述

设备注册的中断处理函数调用结束后,中断流程进入SOFTIRQ部分,如果有等待的softirq需要处理,则处理之,否则返回通用中断处理函数:
在这里插入图片描述

irq_set_handler

设定irq_desc数组中某一项即设定irq_desc[irq]
irq_desc[irq]代表了某一个PIC的某一个IRQ Line上的所有中断处理函数,使处理器能够调用到中断处理函数前必须要注册。
因为调用分为两步,所以注册也分为两步:

  1. 注册irq_desc[irq].handle_irq
  2. 注册irq_desc[irq].action->hanlder

第一级注册在平台初始化期间被安装到irq_desc数组,会被do_IRQ调用,主要面向某一中断线IRQ Line
第二级注册就是设备驱动开发人员在驱动程序中用request_irq注册的,会被irq_desc[irq].handle_irq
调用,主要面向中断线上链接的具体设备

灵活性,设备软件中断号探测机制。

内核准备了两个函数来让平台的初始化代码通过handle_irq注册第一级中断处理函数:
4. irq_set_handler
5. irq_set_chained_handler
这两个注册函数区别仅在于对应IRQ Line上的设备是否支持中断共享

/*
* handler: 安装在irq_desc[irq].handle_irq上的第一级处理函数
* irq:对应的irq号
*/
static inline void
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__irq_set_handler(irq, handle, 0, NULL);
}

static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__irq_set_handler(irq, handle, 1, NULL);
}

/*
 * is_chained标志是否对应项支持中断共享
*/
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);

	if (!desc)
		return;

	if (!handle) {
		handle = handle_bad_irq;
	} else {
		if (WARN_ON(desc->irq_data.chip == &no_irq_chip))
			goto out;
	}

	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->irq_data.chip != &no_irq_chip)
			mask_ack_irq(desc);
		irq_state_set_disabled(desc);
		desc->depth = 1;
	}
	desc->handle_irq = handle;
	desc->name = name;

	if (handle != handle_bad_irq && is_chained) {
		irq_settings_set_noprobe(desc);
		irq_settings_set_norequest(desc);
		irq_startup(desc);
	}
out:
	irq_put_desc_busunlock(desc, flags);
}
EXPORT_SYMBOL_GPL(__irq_set_handler);

中断共享由is_chained字段决定

如果支持共享:

irq_settings_set_noprobe(desc);
irq_settings_set_norequest(desc);
//上面两个函数等同于下面的设置:
desc->status_use_accessors |= _IRQ_NOPROBE | _IRQ_NOREQUEST;

_IRQ_NOREQUEST是指irq_desc[irq]来说无法通过request_irq安装中断处理例程
_IRQ_NOPROBE是指无法对irq_desc[irq]执行中断号的探测机制,这也说明如果irq_desc[irq]对应项是支持中断共享的,那就不能支持自动探测中断号。

中断共享和中断号自动探测二选一

handle_irq具体可以对应不同的中断触发类型的处理函数,这里有个例子,内核中处理边沿中断触发信号的函数:

void handle_edge_irq(struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);

	//清除这两位,用来实现设备软件中断号自动探测机制
	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

	/*
	 * If we're currently running this IRQ, or its disabled,
	 * we shouldn't process the IRQ. Mark it pending, handle
	 * the necessary masking and go out
	 * 如果当前正在运行这个IRQ处理,亦或是它已经被关闭了,我们就不应该处理这个IRQ
	 * 标记它为pending状态,处理必要的mask工作,然后返回。
	 */
	if (unlikely(irqd_irq_disabled(&desc->irq_data) ||
		     irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
		if (!irq_check_poll(desc)) {
			desc->istate |= IRQS_PENDING;
			mask_ack_irq(desc);
			goto out_unlock;
		}
	}
	
	//更新与中断相关的一些统计量  比如某个CPU上的中断次数
	kstat_incr_irqs_this_cpu(desc);

	/* Start handling the irq */
	//利用PIC对象的irq_ack例程向设备发出一个中断响应信号
	//硬件上配置了相关寄存器后使得当前发出中断信号的设备中产生一个信号电平的转换
	//防止设备在它的中断已经在设备驱动程序中处理时依然不停地发出同一中断信号(已经在做了,别叫了)
	desc->irq_data.chip->irq_ack(&desc->irq_data);
	
	//核心部分
	do {
		//检查  是空指针说明目前还没有设备驱动程序注册ISR到该中断线上
		if (unlikely(!desc->action)) {
			//调用这个PIC的irq_mask例程屏蔽当前中断线在PIC中对应的中断位
			//将dssc->irq_data.state_use_accessors的IRQD_IRQ_MASKED置位
			//没有安装中断处理函数的外部中断应该屏蔽掉它直到有处理函数安装上去,否则该设备将会不同的中断处理器
			mask_irq(desc);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Reenable it, if it was not disabled in meantime.
		 */
		 //上面注释的意思是说,如果在处理一个中断的同时新的中断出现了,那么应该将state位置位pending悬而未决
		 //PIC中将对应的中断线屏蔽掉,这样while循环的条件才会在下一次进入时满足
		if (unlikely(desc->istate & IRQS_PENDING)) {
			if (!irqd_irq_disabled(&desc->irq_data) &&
			    irqd_irq_masked(&desc->irq_data))
				unmask_irq(desc);
		}
		//调用设备驱动注册的中断处理例程后做最后的工作
		handle_irq_event(desc);

	} while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));

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


irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	struct irqaction *action = desc->action;
	irqreturn_t ret;
	//清除悬而未决(马上就做完的事情,就不要在高高挂起了)
	desc->istate &= ~IRQS_PENDING;
	//设置当前中断线为正在处理状态
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	raw_spin_unlock(&desc->lock);
	//要执行设备驱动注册的函数了
	ret = handle_irq_event_percpu(desc, action);

	raw_spin_lock(&desc->lock);
	//清除正在处理状态,已经处理完了
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
	irqreturn_t retval = IRQ_NONE;
	unsigned int random = 0, irq = desc->irq_data.irq;

	//遍历action链表
	do {
		irqreturn_t res;

		trace_irq_handler_entry(irq, action);
		//调用驱动模块中注册的那些处理函数!!!
		res = action->handler(irq, action->dev_id);
		trace_irq_handler_exit(irq, action, res);

		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
			      irq, action->handler))
			local_irq_disable();
		//处理返回值的不同情况
		switch (res) {
		case IRQ_WAKE_THREAD:
			/*
			 * Set result to handled so the spurious check
			 * does not trigger.
			 */
			res = IRQ_HANDLED;

			/*
			 * 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;
			}
			//唤醒action->thread,此成员表示一个内核线程,详见中断安装
			irq_wake_thread(desc, action);

			/* Fall through to add to randomness */
		case IRQ_HANDLED:
			random |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
		action = action->next;
	} while (action);		//遍历action形成的链表

	if (random & IRQF_SAMPLE_RANDOM)
		add_interrupt_randomness(irq);

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

在这里插入图片描述

设备驱动注册中断

中断注册过程讲完了,驱动程序中安装一个设备中断服务例程通过调用request_irq函数完成

/*
* irq:软件中断号irq
* handler:ISR函数,驱动实现
* flags:标志变量,影响内核安装ISR时的一些行为
* name:proc文件系统中生成name的一个入口点
* dev:对于共享中断线的设备处理函数很关键,free_irq时通过dev找到对应的action函数才能删除,dev必须有唯一性
*/
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	//thread_fn指定用于中断处理的线程irq_thread有关  传入NULL表示不使用irq_thread机制
	//驱动程序可以直接调用这个函数,参数2不传入NULL,即可使用irq_thread机制
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}


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;

	/*
	 * Sanity-check: shared interrupts must pass in a real dev-ID,
	 * otherwise we'll have trouble later trying to figure out
	 * which interrupt is which (messes up the interrupt freeing
	 * logic etc).
	 * 如果中断共享,必须提供dev_id,否则错误。
	 */
	if ((irqflags & IRQF_SHARED) && !dev_id)
		return -EINVAL;


	desc = irq_to_desc(irq);
	if (!desc)
		return -EINVAL;

	if (!irq_settings_can_request(desc))
		return -EINVAL;

	if (!handler) {
		if (!thread_fn)
			return -EINVAL;
		handler = irq_default_primary_handler;
	}
	
	//从物理内存直接映射区域分配一块内存,内容清0。
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;

	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;

	chip_bus_lock(desc);
	//超级复杂  但是核心就是设置处理函数  desc->action = action;
	//分为两个情况设置  desc->action还是空的 and  desc->action已经有节点
	//在/proc/irq目录下创建新目录项 方便调试
	retval = __setup_irq(irq, desc, action);
	chip_bus_sync_unlock(desc);

	if (retval)
		kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ_FIXME
	if (!retval && (irqflags & IRQF_SHARED)) {
		/*
		 * It's a shared IRQ -- the driver ought to be prepared for it
		 * to happen immediately, so let's make sure....
		 * We disable the irq to make sure that a 'real' IRQ doesn't
		 * run in parallel with our fake.
		 */
		unsigned long flags;

		disable_irq(irq);
		local_irq_save(flags);

		handler(irq, dev_id);

		local_irq_restore(flags);
		enable_irq(irq);
	}
#endif
	return retval;
}

setup_irq函数

/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.

注册一个irqaction实例的内部函数,常被用于分配一个架构部分的的特殊中断
 */
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
	struct irqaction *old, **old_ptr;
	const char *old_name = NULL;
	unsigned long flags, thread_mask = 0;
	int ret, nested, shared = 0;
	cpumask_var_t mask;

	if (!desc)
		return -EINVAL;

	if (desc->irq_data.chip == &no_irq_chip)
		return -ENOSYS;
	/*
	 * Some drivers like serial.c use request_irq() heavily,
	 * so we have to be careful not to interfere with a
	 * running system.
	 */
	if (new->flags & IRQF_SAMPLE_RANDOM) {
		/*
		 * This function might sleep, we want to call it first,
		 * outside of the atomic block.
		 * Yes, this might clear the entropy pool if the wrong
		 * driver is attempted to be loaded, without actually
		 * installing a new handler, but is this really a problem,
		 * only the sysadmin is able to do this.
		 */
		rand_initialize_irq(irq);
	}

	/*
	 * Check whether the interrupt nests into another interrupt
	 * thread.
	 * 中断嵌套
	 */
	nested = irq_settings_is_nested_thread(desc);
	if (nested) {
		if (!new->thread_fn)
			return -EINVAL;
		/*
		 * Replace the primary handler which was provided from
		 * the driver for non nested interrupt handling by the
		 * dummy function which warns when called.
		 */
		new->handler = irq_nested_primary_handler;
	} else {
		//强制设置为线程化
		irq_setup_forced_threading(new);
	}

	/*
	 * Create a handler thread when a thread function is supplied
	 * and the interrupt does not nest into another interrupt
	 * thread.
	 * TODO: thread 机制
	 */
	 //如果传入的irqaction实例的thread_fn非空,也即上层实现了次fn,则进入
	 //irq_thread的创建逻辑
	 
	if (new->thread_fn && !nested) {
		struct task_struct *t;
		//kthread_create为创建函数,TASK_INTERRUPTIBLE状态,睡眠等待中断被触
		//发。
		//如果被触发,那么handler_irq只会负责唤醒睡眠的irq_thread,后者将调用
		//action->thread_fn,它本质上是一个独立的进程,所以发生在进程上下文,而非
		//中断上下文
		
		t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
				   new->name);
		if (IS_ERR(t))
			return PTR_ERR(t);
		/*
		 * We keep the reference to the task struct even if
		 * the thread dies to avoid that the interrupt code
		 * references an already freed task_struct.
		 */
		get_task_struct(t);
		new->thread = t;
	}

	if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
		ret = -ENOMEM;
		goto out_thread;
	}

	/*
	 * The following block of code has to be executed atomically
	 */
	raw_spin_lock_irqsave(&desc->lock, flags);
	old_ptr = &desc->action;
	old = *old_ptr;
	if (old) {
		/*
		 * Can't share interrupts unless both agree to and are
		 * the same type (level, edge, polarity). So both flag
		 * fields must have IRQF_SHARED set and the bits which
		 * set the trigger type must match. Also all must
		 * agree on ONESHOT.
		 */
		if (!((old->flags & new->flags) & IRQF_SHARED) ||
		    ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
		    ((old->flags ^ new->flags) & IRQF_ONESHOT)) {
			old_name = old->name;
			goto mismatch;
		}

		/* All handlers must agree on per-cpuness */
		if ((old->flags & IRQF_PERCPU) !=
		    (new->flags & IRQF_PERCPU))
			goto mismatch;

		/* add new interrupt at end of irq queue */
		do {
			thread_mask |= old->thread_mask;
			old_ptr = &old->next;
			old = *old_ptr;
		} while (old);
		shared = 1;
	}

	/*
	 * Setup the thread mask for this irqaction. Unlikely to have
	 * 32 resp 64 irqs sharing one line, but who knows.
	 */
	if (new->flags & IRQF_ONESHOT && thread_mask == ~0UL) {
		ret = -EBUSY;
		goto out_mask;
	}
	new->thread_mask = 1 << ffz(thread_mask);

	//判断是否共享,如果不共享,比较好办
	if (!shared) {
		init_waitqueue_head(&desc->wait_for_threads);

		/* Setup the type (level, edge polarity) if configured: */
		//如果传入了触发方式的配置,那就需要设置触发方式,调用的是irq_chip里的irq_set_type(irq_chip结构是对PIC的抽象)
		//同时还得配置到desc->irq_data.state_use_accessors
		//比如,如果一个驱动要注册一个下降沿触发的irq,可以如此调用:
		//request_irq(irq,demo_handler, IRQF_TRIGGER_FALLING, NULL, NULL);
		if (new->flags & IRQF_TRIGGER_MASK) {
			ret = __irq_set_trigger(desc, irq,
					new->flags & IRQF_TRIGGER_MASK);

			if (ret)
				goto out_mask;
		}

		desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
				  IRQS_ONESHOT | IRQS_WAITING);
		irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

		if (new->flags & IRQF_PERCPU) {
			irqd_set(&desc->irq_data, IRQD_PER_CPU);
			irq_settings_set_per_cpu(desc);
		}

		if (new->flags & IRQF_ONESHOT)
			desc->istate |= IRQS_ONESHOT;

		if (irq_settings_can_autoenable(desc))
			irq_startup(desc);
		else
			/* Undo nested disables: */
			desc->depth = 1;

		/* Exclude IRQ from balancing if requested */
		if (new->flags & IRQF_NOBALANCING) {
			irq_settings_set_no_balancing(desc);
			irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
		}

		/* Set default affinity mask once everything is setup */
		setup_affinity(irq, desc, mask);

	} else if (new->flags & IRQF_TRIGGER_MASK) {
		unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
		unsigned int omsk = irq_settings_get_trigger_mask(desc);

		if (nmsk != omsk)
			/* hope the handler works with current  trigger mode */
			pr_warning("IRQ %d uses trigger mode %u; requested %u\n",
				   irq, nmsk, omsk);
	}

	new->irq = irq;
	*old_ptr = new;

	/* Reset broken irq detection when installing new handler */
	desc->irq_count = 0;
	desc->irqs_unhandled = 0;

	/*
	 * Check whether we disabled the irq via the spurious handler
	 * before. Reenable it and give it another chance.
	 */
	if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
		desc->istate &= ~IRQS_SPURIOUS_DISABLED;
		__enable_irq(desc, irq, false);
	}

	raw_spin_unlock_irqrestore(&desc->lock, flags);

	/*
	 * Strictly no need to wake it up, but hung_task complains
	 * when no hard interrupt wakes the thread up.
	 */
	if (new->thread)
		wake_up_process(new->thread);

	register_irq_proc(irq, desc);
	new->dir = NULL;
	register_handler_proc(irq, new);
	free_cpumask_var(mask);

	return 0;

mismatch:
#ifdef CONFIG_DEBUG_SHIRQ
	if (!(new->flags & IRQF_PROBE_SHARED)) {
		printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);
		if (old_name)
			printk(KERN_ERR "current handler: %s\n", old_name);
		dump_stack();
	}
#endif
	ret = -EBUSY;

out_mask:
	raw_spin_unlock_irqrestore(&desc->lock, flags);
	free_cpumask_var(mask);

out_thread:
	if (new->thread) {
		struct task_struct *t = new->thread;

		new->thread = NULL;
		if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))
			kthread_stop(t);
		put_task_struct(t);
	}
	return ret;
}

/**
 *	setup_irq - setup an interrupt
 *	@irq: Interrupt line to setup
 *	@act: irqaction for the interrupt
 *
 * Used to statically setup interrupts in the early boot process.
 */
int setup_irq(unsigned int irq, struct irqaction *act)
{
	int retval;
	struct irq_desc *desc = irq_to_desc(irq);

	chip_bus_lock(desc);
	retval = __setup_irq(irq, desc, act);
	chip_bus_sync_unlock(desc);

	return retval;
}
EXPORT_SYMBOL_GPL(setup_irq);

中断处理的thread_fn机制

thread_fn机制将中断处理过程发生在进程上下文而非中断上下文。
用request_threaded_irq函数安装中断时需要在struct irqaction对象中实现他的thread_fn成员,函数内部会创建一个irq_thread的独立线程
这个线程创建出来以TASK_INTERRUPTIBLE状态存在,睡眠等待中断的发生。
中断发生时在action->handler中负责唤醒睡眠的irq_thread 该线程调用action->thread_fn处理中断实际工作。

	if (new->thread_fn && !nested) {
		struct task_struct *t;

		t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
				   new->name);
		if (IS_ERR(t))
			return PTR_ERR(t);
		/*
		 * We keep the reference to the task struct even if
		 * the thread dies to avoid that the interrupt code
		 * references an already freed task_struct.
		 */
		get_task_struct(t);
		new->thread = t;
	

读内核代码,品百味人生。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值