中断二 C实现

init/main.c
start_kernel()->early_irq_init()初始化中断描述符irq_desc,分为数组和基数树两种方式。

desc->irq_data.chip和desc->handle_irq

start_kernel()->init_IRQ()->
void __init init_IRQ(void)
{
	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
		irqchip_init();
	else
		machine_desc->init_irq();
}

假设执行irqchip_init(),执行machine_desc->init_irq()也是一样的动作。

static const struct of_device_id
irqchip_of_match_end __used __section(__irqchip_of_end);

extern struct of_device_id __irqchip_begin[];

void __init irqchip_init(void)
{
	of_irq_init(__irqchip_begin);
}
__irqchip_begin是什么内容?
#define IRQCHIP_OF_MATCH_TABLE()					\
	. = ALIGN(8);							\
	VMLINUX_SYMBOL(__irqchip_begin) = .;				\
	*(__irqchip_of_table)		  				\
	*(__irqchip_of_end)
__irqchip_of_table是什么?drivers/irqchip/irq-gic.c中:
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
#define IRQCHIP_DECLARE(name,compstr,fn)				\
	static const struct of_device_id irqchip_of_match_##name	\
	__used __section(__irqchip_of_table)				\
	= { .compatible = compstr, .data = fn }
也是一个 of_device_id类型的结构,上述定义等价于:
static const struct of_device_id
irqchip_of_match_cortex_a15_gic __used __section(__irqchip_of_table) = {
  .compatible = "arm,cortex-a15-gic",
  .data = gic_of_init
};
of_irq_init(__irqchip_begin)->
irq_init_cb = (of_irq_init_cb_t)match->data;
ret = irq_init_cb(desc->dev, desc->interrupt_parent);
gic_of_init()->gic_init_bases()->irq_domain_add_legacy()->irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq)
static struct irq_chip gic_chip = {
	.name			= "GIC",
	.irq_mask		= gic_mask_irq,
	.irq_unmask		= gic_unmask_irq,
	.irq_eoi		= gic_eoi_irq,
	.irq_set_type		= gic_set_type,
	.irq_retrigger		= gic_retrigger,
#ifdef CONFIG_SMP
	.irq_set_affinity	= gic_set_affinity,
#endif
	.irq_set_wake		= gic_set_wake,
};
其实我想看看flags是怎么设置的,但是这里没有设置。我曾遇到过这样的问题:
genirq: Threaded irq requested with handler=NULL and !ONESHOT for irq 96
err = request_threaded_irq(client->irq, NULL, ts_interrupt, IRQF_DISABLED, client->dev.driver->name, ts);
__setup_irq()->
if (new->handler == irq_default_primary_handler &&
!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
irq);
ret = -EINVAL;
goto out_mask;
}
IRQCHIP_ONESHOT_SAFE这个标志有什么作用?现在还看不出来。
irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq)->irq_set_chip_and_handler_name()->irq_set_chip()->(desc->irq_data.chip = chip)
irq_set_chip_and_handler_name()->__irq_set_handler()->(desc->handle_irq = handle)
handle_fasteoi_irq就是desc->handle_irq。

handle_arch_irq

gic_of_init()->gic_init_bases()->set_handle_irq(gic_handle_irq)
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
	if (handle_arch_irq)
		return;

	handle_arch_irq = handle_irq;
}
handle_arch_irq很熟悉了,这就是动态设置的汇编跳到C的入口了。

request_irq

request_irq()->request_threaded_irq()->
action->handler = handler;//这就是我们的中断处理函数
action->thread_fn = thread_fn;//如果handler为NULL,用中断线程也可以
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
retval = __setup_irq(irq, desc, action);
__setup_irq()->
1 irq_settings_is_nested_thread(desc)->if (desc->status_use_accessors & _IRQ_NESTED_THREAD)成立,表示当前中断要嵌套到其他的中断中,其他中断会唤醒thread_fn中断线程的执行,此时的中断处理函数handle用irq_nested_primary_handler代替。
2 否则,没有嵌套。irq_settings_is_nested_thread(desc)->if (!(desc->status_use_accessors & _IRQ_NOTHREAD))成立,表示需要中断线程,irq_setup_forced_threading(new)->force_irqthreads标志置位,就一定要设置thread_fn。
#ifdef CONFIG_IRQ_FORCED_THREADING
__read_mostly bool force_irqthreads;

static int __init setup_forced_irqthreads(char *arg)
{
	force_irqthreads = true;
	return 0;
}
early_param("threadirqs", setup_forced_irqthreads);
#endif
force_irqthreads是通过early_param宏的内核启动参数设置的。
3 thread_fn存在,又没有嵌套在其他中断中,则需要创建一个线程来运行thread_fn。
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
此时设置了一个标志,set_bit(IRQTF_AFFINITY, &new->thread_flags);用于调整中断线程的关系,对共享中断很重要。
4 if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
new->flags &= ~IRQF_ONESHOT;
IRQF_ONESHOT表示单次触发模式,直到线程函数执行完毕才会开启该中断。IRQCHIP_ONESHOT_SAFE,貌似两个标志一个意思。
IRQF_ONESHOT如何保证一次中断执行完成,再执行下一次中断呢?该标志会使desc->istate |= IRQS_ONESHOT执行,istate是个什么东东?desc结构中没有它啊。kernel/irq/internals.h中有#define istate core_internal_state__do_not_mess_with_it。handle_fasteoi_irq()或者电平、边沿触发函数都有相似的处理,如果desc->istate设置IRQS_ONESHOT,mask中断线mask_irq(desc);执行完处理再cond_unmask_irq(desc)。mask中断线就通过操作中断控制器实现的,例如gic_mask_irq()。
5 确定共享中断。
6 如果当前irqaction的thread mask设置了ONESHOT,!ONESHOT 就为0,我们避开irq_wake_thread()中的一个条件。就是else要求的条件。
IRQF_ONESHOT 与 IRQF_SHARED 不能同时使用。当多个设备共享中断时,由于IRQF_ONESHOT会关闭中断线程的中断,而线程一般执行时间会比较长,所以是不允许的。中断线可能被多个设备共享,cpu无法确定中断是哪个设备产生的。handle_irq_event中do while循环的作用就是执行中断线上所有设备的中断处理函数,由中断处理函数去判断设备是否产生中断。 如果IRQF_ONESHOT和IRQF_SHARED同时设置,使用了中断线程,根据使用中断线程的流程,发生中断时,会唤醒一组线程irq_thread,这组线程被挂在运行进程队列中,等待运行。第一个线程irq_thread执行完后,中断线就会被unmask,这样就有问题了。
当handler函数为NULL时,必须声明IRQF_ONESHOT,表示thread_fn线程中关闭该中断,在某些情况下,这个标志会非常有用。例如:设备是低电平产生中断,而硬中断函数为NULL,如果不使用IRQF_ONESHOT,就会一直产生中断执行NULL函数,中断线程得不到执行,声明IRQF_ONESHOT后,会执行完线程才使能该中断 。
thread_mask为desc->thread_active标识IRQF_ONESHOT thread handler被唤醒,但是还没完成,完成时该bit被清除。当共享中断线的所有线程都完成时,desc->thread_active被清0,并且中断线被unmask。在硬件中断流处理环境,如果没有thread被主要中断处理函数唤醒,desc->threads_active也会被检测,如果是0,就unmask该中断线。新的action会找到thread_mask (共享中断就是所有old->thread_mask按位或,否则是0)中第一个为0的bit分配给action>thread_mask,
如果是单次模式,就要求有是中断处理函数把它唤醒,所以thread_mask不能全置位。
7 如果 handler = NULL,handler == irq_default_primary_handler。
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
	return IRQ_WAKE_THREAD;
}
如果此时没设置单次触发flag,!(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE);对于电平触发这是致命的,因为default primary handler只是唤醒thread,那么中断线是可重入的,但是外设还是被判断为电平中断,又进入了 handler中唤醒线程,如此循环,反反复复。
当工作在边沿触发中断模式,我们假设安全并且无条件地丢掉了,因为我们不能肯定哪种类型的中断确实发生了。这些type flag是不可靠的,后来的中断会覆盖它们。所以此时要求设置 IRQCHIP_ONESHOT_SAFE。
handler和thread_fn同时出现时,处理thread_fn时该中断是打开的;但handler和thread_fn只有一个存在时,处理thread_fn时,中断是关闭的。
8 非共享中断,第一次设置,init_waitqueue_head(&desc->wait_for_threads);
根据action->flags设置中断触发方式IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING。
!(desc->status_use_accessors & _IRQ_NOAUTOEN)一般都会过走到irq_startup(desc, true);
也就是说request_irq()执行过后,中断时enable的。
一旦一切都准备好,设置默认的affinity(中断线程关联标志)掩码。
9 共享中断,检查中断触发方式是否一致,不一致给出警告。
10 为 struct irqaction *new找一个家。
new->irq = irq;
*old_ptr = new;
对于非共享中断*old_ptr = desc->action。
对于共享中断在desc->action链上找到为NULL的一个next:
old_ptr = &desc->action;
old = *old_ptr;
do {
/*
* Or all existing action->thread_mask bits,
* so we can find the next zero bit for this
* new action.
*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
11 共享中断,检查之前是否有通过spurious handlerdisable irq,有则,重新enable irq,并清除IRQS_SPURIOUS_DISABLED标志。
12 如果有new->thread_fn,又没有嵌套到别的中断中,前面创建了一个线程:
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
new->thread = t;
此时唤醒它:
if (new->thread)
wake_up_process(new->thread);
 * Interrupt handler thread
 */
static int irq_thread(void *data)
{
	struct callback_head on_exit_work;
	static const struct sched_param param = {
		.sched_priority = MAX_USER_RT_PRIO/2,
	};
	struct irqaction *action = data;
	struct irq_desc *desc = irq_to_desc(action->irq);
	irqreturn_t (*handler_fn)(struct irq_desc *desc,
			struct irqaction *action);

	if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
					&action->thread_flags))
		handler_fn = irq_forced_thread_fn;
	else
		handler_fn = irq_thread_fn;

	sched_setscheduler(current, SCHED_FIFO, ¶m);

	init_task_work(&on_exit_work, irq_thread_dtor);
	task_work_add(current, &on_exit_work, false);

	irq_thread_check_affinity(desc, action);

	while (!irq_wait_for_interrupt(action)) {
		irqreturn_t action_ret;

		irq_thread_check_affinity(desc, action);

		action_ret = handler_fn(desc, action);
		if (!noirqdebug)
			note_interrupt(action->irq, desc, action_ret);

		wake_threads_waitq(desc);
	}

	/*
	 * This is the regular exit path. __free_irq() is stopping the
	 * thread via kthread_stop() after calling
	 * synchronize_irq(). So neither IRQTF_RUNTHREAD nor the
	 * oneshot mask bit can be set. We cannot verify that as we
	 * cannot touch the oneshot mask at this point anymore as
	 * __setup_irq() might have given out currents thread_mask
	 * again.
	 */
	task_work_cancel(current, irq_thread_dtor);
	return 0;
}
struct callback_head on_exit_work,init_task_work()执行后,irq_thread_dtor()是on_exit_work的更新函数;task_work_add()执行后,on_exit_work加入到current->task_works。如果task_work_add()的最后一个参数是true,会调用set_notify_resume()把当前进程的thread_info(current->stack)的flag设置上TIF_NOTIFY_RESUME,TIF_NOTIFY_RESUME用来实现一个notification的机制,这个机制可以让内核在从内核态切换到和用户态的时候执行一些操作;如果设置了TIF_NOTIFY_RESUME,中断处理完,tracehook_notify_resume()函数会执行,会把task->task_works链表中的所有注册好的函数都会执行一遍(此时irq_thread_dtor()函数就会被调用到了),并且清除TIF_NOTIFY_RESUME标识位。但是这里并没有设置TIF_NOTIFY_RESUME标识。
arch/arm/kernel/entry-armv.S
__irq_usr:
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq
 UNWIND(.fnend		)
ENDPROC(__irq_usr)
假设从用户模式陷入中断模式,中断处理完后,系统有三种执行流向:
(a) 直接返回中断前的状态;(b) 系统重新进行调度;(c) 进行信号处理;
(d) 返回到user模式前的回调处理。
arch/arm/kernel/entry-common.S
ENTRY(ret_to_user_from_irq)
	ldr	r1, [tsk, #TI_FLAGS]
	tst	r1, #_TIF_WORK_MASK
/*
#define _TIF_WORK_MASK		(_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)
判断是否有信号需要处理,或者被抢占,或者返回到user模式前需要回调;也就是这些标识有没有设置,如果有就bne	work_pending。
*/
	bne	work_pending
no_work_pending:
	asm_trace_hardirqs_on

	/* perform architecture specific actions before user return */
	arch_ret_to_user r1, lr
	ct_user_enter save = 0

	restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)

work_pending:
	mov	r0, sp				@ 'regs'
	mov	r2, why				@ 'syscall'
	bl	do_work_pending
	cmp	r0, #0
	beq	no_work_pending
	movlt	scno, #(__NR_restart_syscall - __NR_SYSCALL_BASE)
	ldmia	sp, {r0 - r6}			@ have to reload r0 - r6
	b	local_restart			@ ... and off we go
arch/arm/kernel/signal.c
do_work_pending()就要分情况处理了:
(1) _TIF_NEED_RESCHED,需要调度的,调用schedule()。
(2) _TIF_SIGPENDING,需要进行信号处理,调用do_signal(regs, syscall)。
(3) TIF_NOTIFY_RESUME,返回user前的回调处理,tracehook_notify_resume(regs)。
handler_fn()会执行ret = action->thread_fn(action->irq, action->dev_id);thread_fn就是中断线程了。接下来的while循环中,会执行action_ret = handler_fn(desc, action),根据需要唤醒desc->wait_for_threads等待队列。这while什么时候停止?
static int irq_wait_for_interrupt(struct irqaction *action)
{
	set_current_state(TASK_INTERRUPTIBLE);

	while (!kthread_should_stop()) {

		if (test_and_clear_bit(IRQTF_RUNTHREAD,
				       &action->thread_flags)) {
			__set_current_state(TASK_RUNNING);
			return 0;
		}
		schedule();
		set_current_state(TASK_INTERRUPTIBLE);
	}
	__set_current_state(TASK_RUNNING);
	return -1;
}
(1) 如果当前task没有stop,如果action->thread_flags设置了IRQTF_RUNTHREAD运行线程标志,则设置当前task 为TASK_RUNNING状态,并return 0。
(2) 如果当前task没有stop,如果action->thread_flags没有设置IRQTF_RUNTHREAD标志,就执行schedule(),一直等到action->thread_flags设置了IRQTF_RUNTHREAD,去执行(1)的动作。IRQTF_RUNTHREAD什么时候设置?desc->handle_irq ()->handle_irq_event()->handle_irq_event_percpu()->irq_wake_thread()->test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags)。也就是说,最开始,IRQTF_RUNTHREAD没有设置,将切换执行别的进程。直到有wake_up_process(new->thread)唤醒它,这个唤醒动作,在__setup_irq()的最后有执行,还有handler()返回IRQ_WAKE_THREAD的时候有执行。
(3) 如果当前task已经stop,设置task状态TASK_RUNNING,return -1。
情况(1)irq_thread的while正常走,正常执行中断线程;情况(2)会自动等到(1);情况(3)就停止了。
总结request 中断的3种方式:
1 调用request_irq,handler为中断处理函数,handler的返回值是IRQ_HANDLED。
2 使用中断线程,调用request_threaded_irq,thread_fn为中断处理函数,handler为NULL,将会使handler指向irq_default_primary_handler,该函数返回IRQ_WAKE_THREAD。
3 使用中断线程序,调用request_threaded_irq,thread_fn和handler都不为NULL,分别完成部分处理,handler必须返回IRQ_WAKE_THREAD,否则不会调用thread_fn。

中断处理

中断产生后,先调转到异常向量表, 跳来跳去跳到C。假设我们改写了中断的入口,ldr r1, =handle_arch_irq就走到了handle_arch_irq函数gic_handle_irq()。
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	do {
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);//读取中断状态寄存器
		irqnr = irqstat & ~0x1c00;//取中断号

		if (likely(irqnr > 15 && irqnr < 1021)) {//用户中断
			irqnr = irq_find_mapping(gic->domain, irqnr);
			handle_IRQ(irqnr, regs);
			continue;
		}
		if (irqnr < 16) {//系统用的中断
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);//清中断
#ifdef CONFIG_SMP
			handle_IPI(irqnr, regs);
#endif
			continue;
		}
		break;
	} while (1);
}
(1) handle_IRQ()->irq_enter()
要判断一下是否在中断上下文,防止raise_softirq不必要的ksoftirqd进程醒来这里,因此,softirq将从中断返回后被服务。
(2) handle_IRQ()->generic_handle_irq(irq)->generic_handle_irq_desc(irq, desc)->(desc->handle_irq(irq, desc))
handle_fasteoi_irq就是desc->handle_irq。
handle_fasteoi_irq()->handle_irq_event(desc)->handle_irq_event_percpu(desc, action)->(action->handler(irq, action->dev_id))
action->handler是request_irq()注册的中断处理函数,如果是共享中断,会将所有共享中断的handler都执行到。
(3) handle_IRQ()->irq_exit()
void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
	local_irq_disable();
#else
	WARN_ON_ONCE(!irqs_disabled());
#endif

	account_irq_exit_time(current);
	trace_hardirq_exit();
	sub_preempt_count(HARDIRQ_OFFSET);//hardirq计数已经减1了,如果还在中断上下文,就是softirq
	if (!in_interrupt() && local_softirq_pending())//位于中断上下文,且有softirq被挂起
		invoke_softirq();//处理softirq

	tick_irq_exit();//更新tick
	rcu_irq_exit();
}
(4) preempt_count字段。
位于任务描述符thread_info的preempt_count是用来跟踪内核抢占和内核控制路径嵌套关键数据。其各个位的含义如下:
位                描述
0——7 preemption counter,内核抢占计数器(最大值255);内核抢占被关闭的次数,0表示可以抢占。
8——15 softirq counter,软中断计数器(最大值255);推迟函数(下半部)被关闭的次数,0表示推迟函数打开。
16——27 hardirq counter,硬件中断计数器(最大值4096);本地CPU中断嵌套的层数,irq_enter()增加该值,irq_exit()减该值。
28 PREEMPT_ACTIVE标志。
宏in_interrupt()检查current_thread_info->preempt_count的hardirq和softirq来断定是否处于中断上下文。如果这两个计数器之一为正,则返回非零。
irq_enter()->__irq_enter()->add_preempt_count(HARDIRQ_OFFSET)//hardirq counter增加。
irq_exit()->sub_preempt_count(HARDIRQ_OFFSET)//hardirq counter减少。

你可能感兴趣的:(linux驱动)