Linux中断子系统分析之(4):驱动程序申请中断

Linux中断子系统分析之(1):整体框架
Linux中断子系统分析之(2):通用的中断处理
Linux中断子系统分析之(3):irq domain

中断子系统中有一个重要的设计机制,那就是Top-half和Bottom-half,将紧急的工作放置在Top-half中来处理,而将耗时的工作放置在Bottom-half中来处理。

如果中断不分上下半部处理,那么意味着只有等上一个中断完成处理后才会打开中断,下一个中断才能得到响应。当某个中断处理处理时间较长时,很有可能就会造成其他中断丢失而无法响应,影响系统性能。

中断分成上下半部处理可以提高中断的响应能力,在上半部处理完成后便将中断打开,这样就可以响应其他中断了,等到中断退出的时候再进行下半部的处理。为了进一步提高的实时性,linux内核还提供了中断线程化的机制。

熟悉设备驱动的应该都清楚,经常会在驱动程序中调用request_irq接口或者request_threaded_irq接口来注册设备的中断处理函数。

在讲具体的注册流程前,先看一下主要的中断标志位:

//中断触发类型
#define IRQF_TRIGGER_RISING	0x00000001     //上升沿触发     
#define IRQF_TRIGGER_FALLING	0x00000002 //下降沿触发
#define IRQF_TRIGGER_HIGH	0x00000004     //高电平触发
#define IRQF_TRIGGER_LOW	0x00000008     //低电平


#define IRQF_ONESHOT		  0x00002000      //一次性触发的中断,不能嵌套
/* 为了确保:
	1. 在硬件中断处理完成后才能打开中断,中断上半部肯定是这样的,除非在上半部开中断
	2. 在中断线程化中保持关闭状态,直到该中断源上的所有thread_fn函数都执行完

   驱动程序可以设置IRQF_ONESHOT标志
*/
#define IRQF_SHARED		    0x00000080      //多个设备共享一个中断号,需要外设硬件支持
#define IRQF_PERCPU		    0x00000400      //属于特定CPU的中断
#define IRQF_NOBALANCING	0x00000800      //禁止在CPU之间进行中断均衡处理
#define IRQF_NO_THREAD		0x00010000      //禁止中断线程化

request_irq是老旧的接口了,这里就分析request_threaded_irq这个函数:

/* irq,软件中断号
   handler,primary handler,处理一些紧急的事情,上半部              
	 flags,一些标志位
	 thread_fn,threaded interrupt handler,系统会创建线程执行该函数
	 devname,设备的名字
	 dev_id,对于共享中断,需要传入dev_id,标识自己
  
 */
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 (irq == IRQ_NOTCONNECTED)
		return -ENOTCONN;

	//对于共享中断,要传入dev_id
	if (((irqflags & IRQF_SHARED) && !dev_id) ||
	    (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
	    ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
		return -EINVAL;

	//通过软件中断号获取irq_desc 
	desc = irq_to_desc(irq);
	if (!desc)
		return -EINVAL;

	......

	//handler、thread_fn不能同时为空
	if (!handler) {
		if (!thread_fn)
			return -EINVAL;
		handler = irq_default_primary_handler; 
	}

	//分配一个irqaction 
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;

	//根据传入的参数初始化irqaction 
	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;

	......

	chip_bus_lock(desc);
	//设置
	retval = __setup_irq(irq, desc, action);
	chip_bus_sync_unlock(desc);

	......

	return retval;
}

传入request_threaded_irq的primary handler和threaded handler参数有下面四种组合:
Linux中断子系统分析之(4):驱动程序申请中断
__setup_irq:

static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
	struct irqaction *old, **old_ptr;
	unsigned long flags, thread_mask = 0;
	int ret, nested, shared = 0;
	cpumask_var_t mask;

	......

	new->irq = irq;

	
	if (!(new->flags & IRQF_TRIGGER_MASK))
		new->flags |= irqd_get_trigger_type(&desc->irq_data);

	/* nested类型的中断,parent IRQ的threaded handler通过handle_nested_irq函数
     调用该中断的threaded interrupt handler */
	nested = irq_settings_is_nested_thread(desc);
	if (nested) {
		if (!new->thread_fn) {  //nested类型的中断只需提供thread_fn
			ret = -EINVAL;    
			goto out_mput;
		}
	
		new->handler = irq_nested_primary_handler;
	} else {
		if (irq_settings_can_thread(desc)) {
			ret = irq_setup_forced_threading(new);  //强制中断线程化
			if (ret)
				goto out_mput;
		}
	}

	
	//创建线程
	if (new->thread_fn && !nested) {
		ret = setup_irq_thread(new, irq, false);
		if (ret)
			goto out_mput;
		if (new->secondary) {
			ret = setup_irq_thread(new->secondary, irq, true);
			if (ret)
				goto out_thread;
		}
	}

	......

	raw_spin_lock_irqsave(&desc->lock, flags);
	old_ptr = &desc->action;
	old = *old_ptr;
	if (old) {   
	//old不为NULL,表示为共享中断,则每一个irqaction的触发方式要相同,相同的类型中断
		if (!((old->flags & new->flags) & IRQF_SHARED) ||
		    ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||
		    ((old->flags ^ new->flags) & IRQF_ONESHOT))
			goto mismatch;

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


		do {

			thread_mask |= old->thread_mask;
			old_ptr = &old->next;
			old = *old_ptr;  
		} while (old);
		shared = 1;  //shared为1,标志为共享中断
	}


	......

	if (!shared) { //非共享中断 
		
		......

		/* 设置中断触发类型 */
		if (new->flags & IRQF_TRIGGER_MASK) {
			ret = __irq_set_trigger(desc,
						new->flags & IRQF_TRIGGER_MASK);

			......

		}

		......

		//启动中断
		if (irq_settings_can_autoenable(desc))
			irq_startup(desc, true);
		else
			/* Undo nested disables: */
			desc->depth = 1;

		......

	} else if (new->flags & IRQF_TRIGGER_MASK) { 
		......

	}

	*old_ptr = new;  //把new的irqaction挂在irq_desc的action链表

	......

	if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
		desc->istate &= ~IRQS_SPURIOUS_DISABLED;
		__enable_irq(desc);    //使能中断
	}

	raw_spin_unlock_irqrestore(&desc->lock, flags);


	//唤醒中断线程
	if (new->thread)
		wake_up_process(new->thread); 
	if (new->secondary)
		wake_up_process(new->secondary->thread);


	......

}

强制中断线程化:

static int irq_setup_forced_threading(struct irqaction *new)
{
	//如果打开了CONFIG_IRQ_FORCED_THREADING配置项,force_irqthreads总是为1
	if (!force_irqthreads)
		return 0;
	
	if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
		return 0;      //IRQF_NO_THREAD即不想中断线程化

	new->flags |= IRQF_ONESHOT;

	/* 如果调用request_threaded_irq都传入了primary handler和threaded handler参数
     则primary handler和threaded handler都会在进程上下文里执行
   */
	if (new->handler != irq_default_primary_handler && new->thread_fn) {
		new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
		if (!new->secondary)
			return -ENOMEM;
		new->secondary->handler = irq_forced_secondary_handler;
		new->secondary->thread_fn = new->thread_fn;
		new->secondary->dev_id = new->dev_id;
		new->secondary->irq = new->irq;
		new->secondary->name = new->name;
	}

	set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
	new->thread_fn = new->handler;
	new->handler = irq_default_primary_handler; 
	return 0;
}

创建中断线程:

static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
	struct task_struct *t;
	struct sched_param param = {
		.sched_priority = MAX_USER_RT_PRIO/2,
	};

	//创建线程,执行函数为irq_thread
	if (!secondary) {
		t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
				   new->name);
	} else {
		t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
				   new->name); 
		param.sched_priority -= 1;
	}

	......
	
	sched_setscheduler_nocheck(t, SCHED_FIFO, &param);


	get_task_struct(t);
	new->thread = t;

	set_bit(IRQTF_AFFINITY, &new->thread_flags);
	return 0;
}

来张图:
Linux中断子系统分析之(4):驱动程序申请中断

下面讲讲中断线程的唤醒流程,举个例子,如下场景:
某irq的irq_desc里的handle_irq设置为handle_simple_irq,用于简易流控处理(当然具体设置成什么函数,取决于中断控制器驱动程序,在映射阶段设置的),调用request_threaded_irq注册该irq的中断服务例程时,未设置primary handler,设置了threaded handler(xxx_threaded_handler),那么创建的irqaction里的primary handler会设置为irq_default_primary_handler,并会创建一个线程,执行函数为irq_thead。
Linux中断子系统分析之(4):驱动程序申请中断
看看irq_default_primary_handler:

static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
	return IRQ_WAKE_THREAD;
}

该irq到来时,会调用根据irq找到对应的irq_desc执行里面的handle_irq,即handle_simple_irq:
Linux中断子系统分析之(4):驱动程序申请中断
执行到__handle_irq_event_percpu函数:

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

	//遍历irq_desc的action链表
	for_each_action_of_desc(desc, action) {
		irqreturn_t res;

		......
		//执行irqaction里的handler,即irq_default_primary_handler,该函数直接返回IRQ_WAKE_THREAD
		res = action->handler(irq, action->dev_id); 
		
		......

		switch (res) {
		case IRQ_WAKE_THREAD:
			......
			__irq_wake_thread(desc, action); //handler函数返回IRQ_WAKE_THREAD,则唤醒进程

		case IRQ_HANDLED:
			*flags |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
	}

	return retval;
}

__irq_wake_thread:

void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
	......
	//设置thread_flags的IRQTF_RUNTHREAD位
	if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
		return;


	desc->threads_oneshot |= action->thread_mask;


	atomic_inc(&desc->threads_active);
	
	//唤醒中断线程
	wake_up_process(action->thread);
}

中断线程的执行函数为irq_thread:
Linux中断子系统分析之(4):驱动程序申请中断

static int irq_thread(void *data)
{
	struct callback_head on_exit_work;
	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;

	......

	//等待中断来临
	while (!irq_wait_for_interrupt(action)) {
		irqreturn_t action_ret;

		......
		
		action_ret = handler_fn(desc, action);
		if (action_ret == IRQ_HANDLED)
			atomic_inc(&desc->threads_handled);
		if (action_ret == IRQ_WAKE_THREAD)
			irq_wake_secondary(desc, action);

		wake_threads_waitq(desc);
	}

	......
}

等待中断来临:

static int irq_wait_for_interrupt(struct irqaction *action)
{
	set_current_state(TASK_INTERRUPTIBLE);

	while (!kthread_should_stop()) {

		//如果thread_flags的IRQTF_RUNTHREAD位置1表示中断来了
		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;
}

中断来临执行irq_forced_thread_fn或irq_thread_fn函数,取决于是否开启强制中断线程化,以irq_thread_fn为例:

static irqreturn_t irq_thread_fn(struct irq_desc *desc,
		struct irqaction *action)
{
	irqreturn_t ret;

	//执行irqaction里的thread_fn函数,即xxx_threaded_handler
	ret = action->thread_fn(action->irq, action->dev_id);
	......
	return ret;
}
上一篇:Django原生SQL语句查询返回字典


下一篇:浅读关于go语言的类属性大小写区别