主题:基于linux-2.6.22.6内核的中断系统分析与实现

编译系统   :ubuntu 16.04

内       核   :linux-2.6.22.6

硬件平台   :jz2240

交叉编译器:arm-linux-gcc 3.4.5

首先分析一下linux内核中断系统框架:

我们知道cpu在接收到外部中断之后,会自动跳转到异常向量表中找到中断向量再开始执行对应的中断。

_start:

	/* 建立异常向量表 */
	b reset
	ldr pc,_undefine_instruction
	ldr pc,_sofeware_interrupt
	ldr pc,_prefetch_abort
	ldr pc,_data_abort
	ldr pc,_reserved
	ldr pc,_irq
	ldr pc,_fiq
	
_undefine_instruction: .word undefine
_sofeware_interrupt: .word soft
_prefetch_abort: .word pre_abort
_data_abort: .word data_abort
_reserved: .word reserved
_irq: .word irq
_fiq: .word fiq


irq:
	ldr sp,=0x33400000
	sub lr,lr,#4
	stmdb sp!,{r0-r12,lr}
	/* 这里打印进入irq模式信息 */
	ldmia sp!,{r0-r12,pc}^

一般来说,我们所用的异常都是IRQ,其余的暂且不分析,假设现在有个按键被按下了,并且开启了IRQ中断,cpu会立即跳转到 ldr pc,_irq处,(不要问为什么会这样跳转,因为这里是arm内核固定好的。)然后会根据跳转指令调到标号irq处,(有的人可能会问,为什么要跳转,因为在 ldr pc,_irq 处只有四个字节,下个一个地址处固定存放fiq入口。)进入irq标号处,会设置irq模式的堆栈sp,然后保存现场,处理具体的中断,(我这里没有写,处理函数应该放在ldmia sp!,{r0-r12,pc}^前)。然后退出中断,回复现场。

linux系统与裸机中断不同的是,当外部中断到来时并不会转到如上所说的固定地址,而是会进行一系列的地址映射,最终找到中断入口。其过程繁琐复杂,这里不分析。当找到中断入口之后,会最先调用asm_do_IRQ函数(在arch/arm/kernel/irq.c文件中)

函数原型如下:

/*
 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not
 * come via this function.  Instead, they should provide their
 * own 'handler'
 */
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc *desc = irq_desc + irq;

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (irq >= NR_IRQS)
		desc = &bad_irq_desc;

	irq_enter();

	desc_handle_irq(irq, desc);

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	set_irq_regs(old_regs);
}

我们重点关注两个地方:

  1.  struct irq_desc *desc = irq_desc + irq;
  2.  desc_handle_irq(irq, desc);

1处的意思是会根据中断号irq 在全局数组irq_desc中找到对应的位置,赋值给desc。这个全局数组在kernel/irq.handle.c中申明定义,其意思是,每个中断都会存储在里面。

/*
 * Linux has a controller-independent interrupt architecture.
 * Every controller has a 'controller-template', that is used
 * by the main code to do the right thing. Each driver-visible
 * interrupt source is transparently wired to the appropriate
 * controller. Thus drivers need not be aware of the
 * interrupt-controller.
 *
 * The code is designed to be easily extended with new/different
 * interrupt controllers, without having to do assembly magic or
 * having to touch the generic code.
 *
 * Controller mappings for all interrupt sources:
 */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.status = IRQ_DISABLED,
		.chip = &no_irq_chip,
		.handle_irq = handle_bad_irq,
		.depth = 1,
		.lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
#ifdef CONFIG_SMP
		.affinity = CPU_MASK_ALL
#endif
	}
};

这个数组在哪进行初始化的呢?后面再说,刚刚分析的asm_do_IRQ中1处 根据irq在数组中找到对应的中断记录。然后调用2处desc_handle_irq(irq, desc);这个函数具体实现如下

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
	desc->handle_irq(irq, desc);
}

实际上就调用desc中的成员handle_irq。这个函数就是一个回调函数。在linux系统运行就初始化了。具体怎么初始化,后面再说。先看看desc的结构体有什么成员。重点关注三个,后面会用到。

irq_flow_handler_t	handle_irq;
struct irq_chip		*chip;
struct irqaction	*action;	/* IRQ action list */

/**
 * struct irq_desc - interrupt descriptor
 *
 * @handle_irq:		highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:		low level interrupt hardware access
 * @msi_desc:		MSI descriptor
 * @handler_data:	per-IRQ data for the irq_chip methods
 * @chip_data:		platform-specific per-chip private data for the chip
 *			methods, to allow shared chip implementations
 * @action:		the irq action chain
 * @status:		status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple set_irq_wake() callers
 * @irq_count:		stats field to detect stalled irqs
 * @irqs_unhandled:	stats field for spurious unhandled interrupts
 * @lock:		locking for SMP
 * @affinity:		IRQ affinity on SMP
 * @cpu:		cpu index useful for balancing
 * @pending_mask:	pending rebalanced interrupts
 * @dir:		/proc/irq/ procfs entry
 * @affinity_entry:	/proc/irq/smp_affinity procfs entry on SMP
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
	irq_flow_handler_t	handle_irq;
	struct irq_chip		*chip;
	struct msi_desc		*msi_desc;
	void			*handler_data;
	void			*chip_data;
	struct irqaction	*action;	/* IRQ action list */
	unsigned int		status;		/* IRQ status */

	unsigned int		depth;		/* nested irq disables */
	unsigned int		wake_depth;	/* nested wake enables */
	unsigned int		irq_count;	/* For detecting broken IRQs */
	unsigned int		irqs_unhandled;
	spinlock_t		lock;
#ifdef CONFIG_SMP
	cpumask_t		affinity;
	unsigned int		cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
	cpumask_t		pending_mask;
#endif
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
	const char		*name;
} 

小总结,到外部有中断时,系统会自动跳到入口处执行中断,最开始会调用asm_do_IRQ函数,然后根据irq在数组desc中找到记录信息。然后调用desc->handle_irq。进行真正的中断处理。

下面来分析一下上面提到的全局数组的初始化,以及desc->handle_irq回调函数在哪进行与实际函数进行绑定的。在arch/arm/plat-s3c24xx/irq.c中

/* s3c24xx_init_irq
 *
 * Initialise S3C2410 IRQ system
*/

void __init s3c24xx_init_irq(void)
{
            、
            、
            、
            、

	/* external interrupts */
	for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
		irqdbf("registering irq %d (ext int)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_eint0t4);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
    
            、
            、
            、
            、
}

这里仅仅找一个进行分析,其余都一样。我们先看看 set_irq_handler(irqno, handle_edge_irq);

其中irqno 就是中断号,handle_edeg_irq也是一个函数,下一步再分析

/*
 * Set a highlevel flow handler for a given IRQ:
 */
static inline void
set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__set_irq_handler(irq, handle, 0, NULL);
}

-----------------------------------------------------------------------------------
void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	struct irq_desc *desc;
	unsigned long flags;

	if (irq >= NR_IRQS) {
		printk(KERN_ERR
		       "Trying to install type control for IRQ%d\n", irq);
		return;
	}

	desc = irq_desc + irq;

	if (!handle)
		handle = handle_bad_irq;
	else if (desc->chip == &no_irq_chip) {
		printk(KERN_WARNING "Trying to install %sinterrupt handler "
		       "for IRQ%d\n", is_chained ? "chained " : "", irq);
		/*
		 * Some ARM implementations install a handler for really dumb
		 * interrupt hardware without setting an irq_chip. This worked
		 * with the ARM no_irq_chip but the check in setup_irq would
		 * prevent us to setup the interrupt at all. Switch it to
		 * dummy_irq_chip for easy transition.
		 */
		desc->chip = &dummy_irq_chip;
	}

	spin_lock_irqsave(&desc->lock, flags);

	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->chip != &no_irq_chip)
			mask_ack_irq(desc, irq);
		desc->status |= IRQ_DISABLED;
		desc->depth = 1;
	}

	desc->handle_irq = handle;
	desc->name = name;

	if (handle != handle_bad_irq && is_chained) {
		desc->status &= ~IRQ_DISABLED;
		desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
		desc->depth = 0;
		desc->chip->unmask(irq);
	}
	spin_unlock_irqrestore(&desc->lock, flags);
}

我们不难发现,set_irq_handler函数内部就是调用__set_irq_handler(irq,handle,0,NULL);这个函数内部就是根据irq找到位置,然后把参数handle赋值给desc->handle_irq,这里就分析到了,之前asm_do_IRQ函数中,调用desc_handle_irq(irq, desc);这个函数内desc->handle_irq(irq, desc); 调用的handle_irq就是在__set_irq_handler中进行初始化的,传入参数handle就是set_irq_handler(irqno, handle_edge_irq)中的handle_edge_irq函数。

我们再分析set_irq_chip(irqno, &s3c_irq_eint0t4);其中的s3c_irq_eint0t4就是一个被初始化的结构体,重点关注一下ack

static struct irq_chip s3c_irq_eint0t4 = {
	.name		= "s3c-ext0",
	.ack		= s3c_irq_ack,
	.mask		= s3c_irq_mask,
	.unmask		= s3c_irq_unmask,
	.set_wake	= s3c_irq_wake,
	.set_type	= s3c_irqext_type,
};

-----------------------------------------------------------
//就是处理一些中断标志位
static inline void s3c_irq_ack(unsigned int irqno)
{
	unsigned long bitval = 1UL << (irqno - IRQ_EINT0);

	__raw_writel(bitval, S3C2410_SRCPND);
	__raw_writel(bitval, S3C2410_INTPND);
}
/**
 *	set_irq_chip - set the irq chip for an irq
 *	@irq:	irq number
 *	@chip:	pointer to irq chip description structure
 */
int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
	struct irq_desc *desc;
	unsigned long flags;

	if (irq >= NR_IRQS) {
		printk(KERN_ERR "Trying to install chip for IRQ%d\n", irq);
		WARN_ON(1);
		return -EINVAL;
	}

	if (!chip)
		chip = &no_irq_chip;

	desc = irq_desc + irq;
	spin_lock_irqsave(&desc->lock, flags);
	irq_chip_set_defaults(chip);
	desc->chip = chip;
	spin_unlock_irqrestore(&desc->lock, flags);

	return 0;
}

在set_irq_chip函数中,我们可以容易发现,desc= irq_desc+irq; desc->chip = chip;意思是根据中断号irq找到全局数组irq_desc位置,然后把chip填充进去。

接下来进行分析handle_edge_irq()函数,这个就是发生中断的实际调用的函数

void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
	const unsigned int cpu = smp_processor_id();

	spin_lock(&desc->lock);

	desc->status &= ~(IRQ_REPLAY | IRQ_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
	 */
	if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
		    !desc->action)) {
		desc->status |= (IRQ_PENDING | IRQ_MASKED);
		mask_ack_irq(desc, irq);
		goto out_unlock;
	}

	kstat_cpu(cpu).irqs[irq]++;

	/* Start handling the irq */
	desc->chip->ack(irq);

	/* Mark the IRQ currently in progress.*/
	desc->status |= IRQ_INPROGRESS;

	do {
		struct irqaction *action = desc->action;
		irqreturn_t action_ret;

		if (unlikely(!action)) {
			desc->chip->mask(irq);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		if (unlikely((desc->status &
			       (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
			      (IRQ_PENDING | IRQ_MASKED))) {
			desc->chip->unmask(irq);
			desc->status &= ~IRQ_MASKED;
		}

		desc->status &= ~IRQ_PENDING;
		spin_unlock(&desc->lock);
		action_ret = handle_IRQ_event(irq, action);
		if (!noirqdebug)
			note_interrupt(irq, desc, action_ret);
		spin_lock(&desc->lock);

	} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

	desc->status &= ~IRQ_INPROGRESS;
out_unlock:
	spin_unlock(&desc->lock);
}

   我们可以看到这一句

/* Start handling the irq */
    desc->chip->ack(irq);

这里的desc->chip已经在set_irq_chip(irqno, &s3c_irq_eint0t4);中进行初始化了。其中的ack 就是s3c_irq_ack(见上面)

因此,desc->chip->ack(irq)就是把中断号irq的相关中断标志清除。

然后可以看到一个do{} while();循环,重点关注

struct irqaction *action = desc->action;

action_ret = handle_IRQ_event(irq, action);

	do {
		struct irqaction *action = desc->action;
		irqreturn_t action_ret;

		if (unlikely(!action)) {
			desc->chip->mask(irq);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		if (unlikely((desc->status &
			       (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
			      (IRQ_PENDING | IRQ_MASKED))) {
			desc->chip->unmask(irq);
			desc->status &= ~IRQ_MASKED;
		}

		desc->status &= ~IRQ_PENDING;
		spin_unlock(&desc->lock);
		action_ret = handle_IRQ_event(irq, action);
		if (!noirqdebug)
			note_interrupt(irq, desc, action_ret);
		spin_lock(&desc->lock);

	} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

struct irqaction *action = desc->action;意思是把desc中的action取出来,作为参数,调用handle_IRQ_event()函数,函数实现如下:

重点关注: ret = action->handler(irq, action->dev_id);

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
	irqreturn_t ret, retval = IRQ_NONE;
	unsigned int status = 0;

	handle_dynamic_tick(action);

	if (!(action->flags & IRQF_DISABLED))
		local_irq_enable_in_hardirq();

	do {
		ret = action->handler(irq, action->dev_id);
		if (ret == IRQ_HANDLED)
			status |= action->flags;
		retval |= ret;
		action = action->next;
	} while (action);

	if (status & IRQF_SAMPLE_RANDOM)
		add_interrupt_randomness(irq);
	local_irq_disable();

	return retval;
}

看到这里你可能会很疑惑,怎么突然就来了一个action,然后一直处理action,甚至调用action内的handler。

这个就得从驱动中如何使用中断来说了,要想用中断,首先得预先申请中断,用

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)

解释一下这里的参数

 *    @irq: Interrupt line to allocate                                              申请的中断号
 *    @handler: Function to be called when the IRQ occurs       当中断发生时,我们希望中去处理哪些事情,比如从传感器获取数据,是     *                                                                                                                                                                                                       实际需求
 *    @irqflags: Interrupt type flags                                               中断触发类型,上升沿,下降沿,高电平,低电平等
 *    @devname: An ascii name for the claiming device              中断名字,没事用处,可以在/proc/interrupts中看到注册哪些中断
 *    @dev_id: A cookie passed back to the handler function      作为handler的参数

/**
 *	request_irq - allocate an interrupt line
 *	@irq: Interrupt line to allocate
 *	@handler: Function to be called when the IRQ occurs
 *	@irqflags: Interrupt type flags
 *	@devname: An ascii name for the claiming device
 *	@dev_id: A cookie passed back to the handler function
 *
 *	This call allocates interrupt resources and enables the
 *	interrupt line and IRQ handling. From the point this
 *	call is made your handler function may be invoked. Since
 *	your handler function must clear any interrupt the board
 *	raises, you must take care both to initialise your hardware
 *	and to set up the interrupt handler in the right order.
 *
 *	Dev_id must be globally unique. Normally the address of the
 *	device data structure is used as the cookie. Since the handler
 *	receives this value it makes sense to use it.
 *
 *	If your interrupt is shared you must pass a non NULL dev_id
 *	as this is required when freeing the interrupt.
 *
 *	Flags:
 *
 *	IRQF_SHARED		Interrupt is shared
 *	IRQF_DISABLED	Disable local interrupts while processing
 *	IRQF_SAMPLE_RANDOM	The interrupt can be used for entropy
 *
 */
int request_irq(unsigned int irq, irq_handler_t handler,
		unsigned long irqflags, const char *devname, void *dev_id)
{
	struct irqaction *action;
	int retval;

#ifdef CONFIG_LOCKDEP
	/*
	 * Lockdep wants atomic interrupt handlers:
	 */
	irqflags |= IRQF_DISABLED;
#endif
	/*
	 * 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).
	 */
	if ((irqflags & IRQF_SHARED) && !dev_id)
		return -EINVAL;
	if (irq >= NR_IRQS)
		return -EINVAL;
	if (irq_desc[irq].status & IRQ_NOREQUEST)
		return -EINVAL;
	if (!handler)
		return -EINVAL;

	action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
	if (!action)
		return -ENOMEM;

	action->handler = handler;
	action->flags = irqflags;
	cpus_clear(action->mask);
	action->name = devname;
	action->next = NULL;
	action->dev_id = dev_id;

	select_smp_affinity(irq);

#ifdef CONFIG_DEBUG_SHIRQ
	if (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 do this before actually registering it, to make sure that
		 * a 'real' IRQ doesn't run in parallel with our fake
		 */
		if (irqflags & IRQF_DISABLED) {
			unsigned long flags;

			local_irq_save(flags);
			handler(irq, dev_id);
			local_irq_restore(flags);
		} else
			handler(irq, dev_id);
	}
#endif

	retval = setup_irq(irq, action);
	if (retval)
		kfree(action);

	return retval;
}

我们重点看到如下:实际意义就是为中断号填充自己的触发条件以及执行函数,这里就可以解释action->handler的内容了。

	action->handler = handler;
	action->flags = irqflags;
	cpus_clear(action->mask);
	action->name = devname;
	action->next = NULL;
	action->dev_id = dev_id;

因此,当有中断发生之后,就是进行一层层调用,最终调用到request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)中的handler函数(需要开发者实现)。

如下是中断信息结构图,irq_desc[]数组中存储对应的每一个中断,根据中断号来进行访问。

猜你喜欢

转载自blog.csdn.net/weixin_41682169/article/details/85450062