linux中断管理

Posted 古澜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux中断管理相关的知识,希望对你有一定的参考价值。

一、linux中断注册

1、request_irq函数

request_irq 函数就是驱动开发者向内核注册一个中断的接口。它有五个参数,分别是响:中断号,应中断时的中断处理函数,中断的触发方式,中断的名字,传给中断处理的参数。代码块如下:

/*********************************************************
 * irq        : 中断号
 * handler    : 中断处理函数
 * irqflags   : 中断的触发方式
 * devname    : 中断名称
 * dev_id     : 传给中断服务函数的参数
 *********************************************************/
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 节点,驱动开发者每注册一个中断,都回添加到action链表里面 */
	action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
	if (!action)
		return -ENOMEM;

        /* 填充action结构体 */
        /* 指向用户添加的中断处理函数,响应中断的时候就回通过这个指针调用用户注册的中断处理函数 */
	action->handler = handler;
        /* 中断的触发方式 */
	action->flags = irqflags;
        /* 清除屏蔽,即使能中断 */
	cpus_clear(action->mask);
        /* 中断名称 */
	action->name = devname;
        /* 指向下一个action节点的指针 */
	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
        /* 设置irq中断 */
	retval = setup_irq(irq, action);
	if (retval)
		kfree(action);

	return retval;
}

2、setup_irq函数

代码块如下:

int setup_irq(unsigned int irq, struct irqaction *new)
{
    /* 根据中断号找到对应的数组项 */
	struct irq_desc *desc = irq_desc + irq;
	struct irqaction *old, **p;
	const char *old_name = NULL;
	unsigned long flags;
	int shared = 0;

	if (irq >= NR_IRQS)
		return -EINVAL;

	if (desc->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);
	}

	/*
	 * The following block of code has to be executed atomically
	 */
	spin_lock_irqsave(&desc->lock, flags);

	p = &desc->action;
	old = *p;
    /* 判断链表头是否已经有一个节点了 */
	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.
		 */
                /* 这个中断是不是共享中断,如果不是共享中断则不能继续添加节点 */
		if (!((old->flags & new->flags) & IRQF_SHARED) ||
		    ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
			old_name = old->name;
			goto mismatch;
		}

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

		/* add new interrupt at end of irq queue */
                /* 如果是共享中断,这里是找到链表的末尾节点 */
		do {
			p = &old->next;
			old = *p;
		} while (old);
		shared = 1; //共享中断标志设置为1
	}
        /* 这里将新的节点添加到链表里面 */
	*p = new;

	/* Exclude IRQ from balancing */
	if (new->flags & IRQF_NOBALANCING)
		desc->status |= IRQ_NO_BALANCING;
        /* 如果不是共享中断 */
	if (!shared) {
                /* 将chip设置为默认的chip */
		irq_chip_set_defaults(desc->chip);

#if defined(CONFIG_IRQ_PER_CPU)
		if (new->flags & IRQF_PERCPU)
			desc->status |= IRQ_PER_CPU;
#endif

		/* Setup the type (level, edge polarity) if configured: */
		if (new->flags & IRQF_TRIGGER_MASK) {
			if (desc->chip && desc->chip->set_type)
                                /* set_type 这个函数就是操作底层的函数了,根据中断号将对应的引脚设置为中断引脚,
                                 * 并且根据 new->flags 设置上升沿触发还是高电平触发等等 
                                 */
				desc->chip->set_type(irq,
						new->flags & IRQF_TRIGGER_MASK);
			else
				/*
				 * IRQF_TRIGGER_* but the PIC does not support
				 * multiple flow-types?
				 */
				printk(KERN_WARNING "No IRQF_TRIGGER set_type "
				       "function for IRQ %d (%s)\\n", irq,
				       desc->chip ? desc->chip->name :
				       "unknown");
		} else
			compat_irq_chip_set_default_handler(desc);

		desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
				  IRQ_INPROGRESS);

		if (!(desc->status & IRQ_NOAUTOEN)) {
			desc->depth = 0;
			desc->status &= ~IRQ_DISABLED;


                        /* desc->chip->startup()/desc->chip->enable 就是使能中断 */

			if (desc->chip->startup)
				desc->chip->startup(irq);
			else
				desc->chip->enable(irq);
		} else
			/* Undo nested disables: */
			desc->depth = 1;
	}
	/* Reset broken irq detection when installing new handler */
	desc->irq_count = 0;
	desc->irqs_unhandled = 0;
	spin_unlock_irqrestore(&desc->lock, flags);

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

	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
	spin_unlock_irqrestore(&desc->lock, flags);
	return -EBUSY;
}

desc->chip->set_typedesc->chip->startup 这些指针都是在 arch\\arm\\plat-s3c24xx\\irq.c__init s3c24xx_init_irq 函数初始化,在linux 中断管理(一) 有简单的分析过程。执行完 setup_irq 函数,一个中断就被注册进内核了。

二、linux的中断的释放

1、free_irq函数

free_irq 函数就是将注册到内核的一中断释放。代码块如下:

void free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc;
	struct irqaction **p;
	unsigned long flags;
	irqreturn_t (*handler)(int, void *) = NULL;

	WARN_ON(in_interrupt());
	if (irq >= NR_IRQS)
		return;
        /* 根据 irq 找到对应的数组项 */
	desc = irq_desc + irq;
	spin_lock_irqsave(&desc->lock, flags);
        /* 找到 action 链表头 */
	p = &desc->action;
        /* 下面的for循环就是根据 dev_id(这个也就是注册中断的时候的传给中断处理函数的参数)这个参数找到要释放的action节点 */
	for (;;) {
		struct irqaction *action = *p;

		if (action) {
			struct irqaction **pp = p;

			p = &action->next;
                        /* 没有找到就继续往下找 */
			if (action->dev_id != dev_id)
				continue;

			/* Found it - now remove it from the list of entries */
                        /* 这里将 action 要释放的节点从链表里面移除 */
			*pp = action->next;

			/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
                        
			if (desc->chip->release)
				desc->chip->release(irq, dev_id);
#endif
                        /* 如果action链表里面已经没有了节点,则要关闭中断 */
			if (!desc->action) {
				desc->status |= IRQ_DISABLED;
				if (desc->chip->shutdown)
					desc->chip->shutdown(irq);
				else
					desc->chip->disable(irq);
			}
			spin_unlock_irqrestore(&desc->lock, flags);
			unregister_handler_proc(irq, action);

			/* Make sure it\'s not being used on another CPU */
			synchronize_irq(irq);
			if (action->flags & IRQF_SHARED)
				handler = action->handler;

                        /* 释放空间 */
			kfree(action);
			return;
		}
		printk(KERN_ERR "Trying to free already-free IRQ %d\\n", irq);
		spin_unlock_irqrestore(&desc->lock, flags);
		return;
	}
#ifdef CONFIG_DEBUG_SHIRQ
	if (handler) {
		/*
		 * It\'s a shared IRQ -- the driver ought to be prepared for it
		 * to happen even now it\'s being freed, so let\'s make sure....
		 * We do this after actually deregistering it, to make sure that
		 * a \'real\' IRQ doesn\'t run in parallel with our fake
		 */
		handler(irq, dev_id);
	}
#endif
}

三、总结

1、中断注册:

中断注册是使用 request_irq 函数,注册的过程中主要做了下面几件事:

  1. 分配一个 struct irqaction 结构(上面说的action节点)
  2. 填充这个节点
  3. 将这个节点放入到 irq_desc[irq]action 链表里面
  4. 将引脚设置为中断引脚
  5. 使能中断
  • 注:如果是共享中断并且不是第一次注册就不需要设置中断引脚和使能中断。

2、释放中断

中断的释放时使用 free_irq 这个函数,释放的过程主要做了下面几件事:

  1. 根据 dev_id 找到 action 节点,从链表里面移除
  2. 如果释放的是最后的一个节点,则要关闭中断,否则不用关闭中断
  3. 释放 action 节点的空间

以上是关于linux中断管理的主要内容,如果未能解决你的问题,请参考以下文章

linux 中断管理

linux 中断管理

Linux 内核Linux 内核体系架构 ( 进程调度 | 内存管理 | 中断管理 | 设备管理 | 文件系统 )

Linux内存管理 (10)缺页中断处理

Linux的中断

linux设备树-中断控制器驱动