linux3.10 中断处理过程(二)s3c2440中断控制器及处理函数的初始化

1中断寄存器map

在中断来临时,必然要读写相应的中断寄存器,来判断中断源,调用不同的中断处理函数,所以在系统初始化的时候,就给中断寄存器做了相应的静态映射。

start_kernel

       -------------->setup_arch

              -------------->paging_init

                     ---------------->devicemaps_init

                           ---------------- >mdesc->map_io

                                 ------------------>smdk2440_map_io

                                          ------------------>iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));

static struct map_desc s3c_iodesc[] __initdata = {
	IODESC_ENT(GPIO),
	IODESC_ENT(IRQ),
	IODESC_ENT(MEMCTRL),
	IODESC_ENT(UART)
};

IODESC_ENT(IRQ),是个宏定义,里面包含了中断寄存器的物理地址以及需要静态映射的虚拟地址,至此,中断寄存器页表建立完毕。

2 中断处理函数初始化

2.1 重要数据结构介绍

前面一篇文章介绍过,irq的中断处理总入口是handle_arch_irq,那么如何设置handle_arch_irq呢,以及在这个函数里面具体都做什么,在详细分析前,先看一下几个关键的数据结构。

Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h中定义:

struct irq_desc {
    unsigned int        irq;
    struct timer_rand_state *timer_rand_state;
    unsigned int *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
    struct irq_2_iommu *irq_2_iommu;
#endif
    irq_flow_handler_t    handle_irq; // 当前中断的处理函数入口

    struct irq_chip        *chip; //低层的硬件访问

    struct msi_desc        *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;    // 用户提供的中断处理函数链表

    unsigned int        status;        //IRQ状态
                ........

    const char        *name; //中断的名称

}

 handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数handle_arch_irq将根据中断号调用相应irq_desc数组项中handle_irqhandle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。

irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。

struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable
    void        (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disable"*/
    void        (*enable)(unsigned int irq);// 使用中断,如果不设置,缺省为"unmask"
    void        (*disable)(unsigned int irq);//禁止中断,如果不设置,缺省为“mask”
    void        (*ack)(unsigned int irq);/*响应中断,通常是清除当前中断使得可以接收下一个中断*/
    void        (*mask)(unsigned int irq); //屏蔽中断源

    void        (*mask_ack)(unsigned int irq);//屏蔽和响应中断

    void        (*unmask)(unsigned int irq);//开启中断源

    void        (*eoi)(unsigned int irq);
    ........
    const char    *typename;
};

irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结构链接成一个链表,以action为表头。irqation结构定义如下:

struct irqaction {
    irq_handler_t handler; //用户注册的中断处理函数
    unsigned long flags; //中断标志,比如是否共享中断,电平触发还是边沿触发
    const char *name; //用户注册的中断名字
    void *dev_id; //用户传给上面的handler的参数,还可以用来区分共享中断
    struct irqaction *next; //指向下一个用户注册函数的指针
    int irq; //中断号
    struct proc_dir_entry *dir;
    irq_handler_t thread_fn;
    struct task_struct *thread;
    unsigned long thread_flags;
};

irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图:

2.2 中断处理函数初始化

start_kernel

      ----------->early_irq_init

      ----------->init_IRQ

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], GFP_KERNEL, node);
		raw_spin_lock_init(&desc[i].lock);
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		desc_set_defaults(i, &desc[i], node, NULL);
	}
	return arch_early_irq_init();
}

接着初始化各个描述符的中断处理函数:

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

可以看到如果定义了device tree,但是没有定义machine_desc->init_irq,则利用device tree相关的方式来处理中断处理函数,我们这边定义了machine_desc->init_irq,该函数是s3c2440_init_irq:

void __init s3c2440_init_irq(void)
{
	pr_info("S3C2440: IRQ Support\n");

#ifdef CONFIG_FIQ
	init_FIQ(FIQ_START);
#endif
        设置主中断源相关的中断
	s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2440base[0], NULL,
					0x4a000000);
	if (IS_ERR(s3c_intc[0])) {
		pr_err("irq: could not create main interrupt controller\n");
		return;
	}
        设置扩展的外部中断源中断,外部中断4-7,8-13在主中断源中只用一位表示,用0x560000a4
        地址开始的外部中断寄存器进一步描述中断位
	s3c24xx_init_intc(NULL, &init_eint[0], s3c_intc[0], 0x560000a4);
        初始化子中断相关的中断
	s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2440subint[0],
					s3c_intc[0], 0x4a000018);
}

上面函数初始化每个中断描述符,并为其设置相应的处理中断处理函数。子中断和外部扩展中断,都依赖于主中断寄存器中的位,再看s3c24xx_init_intc:

static struct s3c_irq_intc * __init s3c24xx_init_intc(struct device_node *np,
				       struct s3c_irq_data *irq_data,
				       struct s3c_irq_intc *parent,
				       unsigned long address)
{
	struct s3c_irq_intc *intc;
	void __iomem *base = (void *)0xf6000000; /* static mapping */中断寄存器静态映射地址
	int irq_num;
	int irq_start;
	int ret;

	intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);
	if (!intc)
		return ERR_PTR(-ENOMEM);

	intc->irqs = irq_data;

	if (parent) 如果有父中断控制器,则设置父中断控制器
		intc->parent = parent;

	/* select the correct data for the controller.
	 * Need to hard code the irq num start and offset
	 * to preserve the static mapping for now
	 */
	switch (address) {
	case 0x4a000000:   主中断源
		pr_debug("irq: found main intc\n");
		intc->reg_pending = base;
		intc->reg_mask = base + 0x08;
		intc->reg_intpnd = base + 0x10;
		irq_num = 32;
		irq_start = S3C2410_IRQ(0);
		break;
	case 0x4a000018:   子中断源
		pr_debug("irq: found subintc\n");
		intc->reg_pending = base + 0x18;
		intc->reg_mask = base + 0x1c;
		irq_num = 29;
		irq_start = S3C2410_IRQSUB(0);
		break;
	case 0x4a000040:
		pr_debug("irq: found intc2\n");
		intc->reg_pending = base + 0x40;
		intc->reg_mask = base + 0x48;
		intc->reg_intpnd = base + 0x50;
		irq_num = 8;
		irq_start = S3C2416_IRQ(0);
		break;
	case 0x560000a4:     外部扩展中断源
		pr_debug("irq: found eintc\n");
		base = (void *)0xfd000000;

		intc->reg_mask = base + 0xa4;
		intc->reg_pending = base + 0xa8;
		irq_num = 24;
		irq_start = S3C2410_IRQ(32);
		break;
	default:
		pr_err("irq: unsupported controller address\n");
		ret = -EINVAL;
		goto err;
	}

	/* now that all the data is complete, init the irq-domain */
	s3c24xx_clear_intc(intc);清楚中断寄存器的相应位
	intc->domain = irq_domain_add_legacy(np, irq_num, irq_start,
					     0, &s3c24xx_irq_ops,
					     intc);
	if (!intc->domain) {
		pr_err("irq: could not create irq-domain\n");
		ret = -EINVAL;
		goto err;
	}

	set_handle_irq(s3c24xx_handle_irq);设置irq总中断入口程序为s3c24xx_handle_irq

	return intc;

err:
	kfree(intc);
	return ERR_PTR(ret);
}

主要看一下irq_domain_add_legacy函数:

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
					 unsigned int size,
					 unsigned int first_irq,
					 irq_hw_number_t first_hwirq,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	struct irq_domain *domain;
	unsigned int i;

	domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data);
	if (!domain)
		return NULL;

	domain->revmap_data.legacy.first_irq = first_irq;当前寄存器的中断号起始地址
	domain->revmap_data.legacy.first_hwirq = first_hwirq;在当前中断寄存器中的偏移
	domain->revmap_data.legacy.size = size; 当前中断寄存器中断的数量

	mutex_lock(&irq_domain_mutex);
	/* Verify that all the irqs are available */
	for (i = 0; i < size; i++) {
		int irq = first_irq + i;
		struct irq_data *irq_data = irq_get_irq_data(irq);

		if (WARN_ON(!irq_data || irq_data->domain)) {
			mutex_unlock(&irq_domain_mutex);
			irq_domain_free(domain);
			return NULL;
		}
	}

	/* Claim all of the irqs before registering a legacy domain */
	for (i = 0; i < size; i++) {
		struct irq_data *irq_data = irq_get_irq_data(first_irq + i);
		irq_data->hwirq = first_hwirq + i; 
		irq_data->domain = domain;
	}
	mutex_unlock(&irq_domain_mutex);

	for (i = 0; i < size; i++) {
		int irq = first_irq + i;
		int hwirq = first_hwirq + i;

		/* IRQ0 gets ignored */
		if (!irq)
			continue;

		/* Legacy flags are left to default at this point,
		 * one can then use irq_create_mapping() to
		 * explicitly change them
		 */
		if (ops->map) 调用map函数,为每个中断描述符设置中断处理函数,以及硬件相关的函数
			ops->map(domain, irq, hwirq);

		/* Clear norequest flags */
		irq_clear_status_flags(irq, IRQ_NOREQUEST);
	}
        把domain挂载到irq_domain_list链表上
	irq_domain_add(domain);
	return domain;
}

在ops->map中,为每个中断描述符设置中断处理函数,其实就是s3c24xx_irq_map函数:

static int s3c24xx_irq_map(struct irq_domain *h, unsigned int virq,
							irq_hw_number_t hw)
{
	struct s3c_irq_intc *intc = h->host_data;
	struct s3c_irq_data *irq_data = &intc->irqs[hw];
	struct s3c_irq_intc *parent_intc;
	struct s3c_irq_data *parent_irq_data;
	unsigned int irqno;

	/* attach controller pointer to irq_data */
	irq_data->intc = intc;
	irq_data->offset = hw;

	parent_intc = intc->parent;

	/* set handler and flags */
	switch (irq_data->type) {
	case S3C_IRQTYPE_NONE:
		return 0;
	case S3C_IRQTYPE_EINT:
		/* On the S3C2412, the EINT0to3 have a parent irq
		 * but need the s3c_irq_eint0t4 chip
		 */
		if (parent_intc && (!soc_is_s3c2412() || hw >= 4))
			irq_set_chip_and_handler(virq, &s3c_irqext_chip,
						 handle_edge_irq);
		else
			irq_set_chip_and_handler(virq, &s3c_irq_eint0t4,
						 handle_edge_irq);
		break;
	case S3C_IRQTYPE_EDGE:
		if (parent_intc || intc->reg_pending == S3C2416_SRCPND2)
			irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
						 handle_edge_irq);
		else
			irq_set_chip_and_handler(virq, &s3c_irq_chip,
						 handle_edge_irq);
		break;
	case S3C_IRQTYPE_LEVEL:
		if (parent_intc)
			irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
						 handle_level_irq);
		else
			irq_set_chip_and_handler(virq, &s3c_irq_chip,
						 handle_level_irq);
		break;
	default:
		pr_err("irq-s3c24xx: unsupported irqtype %d\n", irq_data->type);
		return -EINVAL;
	}

	irq_set_chip_data(virq, irq_data);

	set_irq_flags(virq, IRQF_VALID);
        如果当前设置的中断具有主中断,则需要修改主中断的处理函数为s3c_irq_demux
	if (parent_intc && irq_data->type != S3C_IRQTYPE_NONE) {
		if (irq_data->parent_irq > 31) {
			pr_err("irq-s3c24xx: parent irq %lu is out of range\n",
			       irq_data->parent_irq);
			goto err;
		}

		parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];
		parent_irq_data->sub_intc = intc;
		parent_irq_data->sub_bits |= (1UL << hw);

		/* attach the demuxer to the parent irq */
                获取主中断的中断号
		irqno = irq_find_mapping(parent_intc->domain,
					 irq_data->parent_irq);
		if (!irqno) {
			pr_err("irq-s3c24xx: could not find mapping for parent irq %lu\n",
			       irq_data->parent_irq);
			goto err;
		}
                设置主中断的处理函数为s3c_irq_demux
		irq_set_chained_handler(irqno, s3c_irq_demux);
	}

	return 0;

err:
	set_irq_flags(virq, 0);

	/* the only error can result from bad mapping data*/
	return -EINVAL;
}

上面根据不同的中断出发类型,设置不同的处理函数,同时如果设置了主中断控制器的情况下parent_intc,则需要做一些不同的处理,在中断来临时,如果是主中断发生的中断,则可以直接调用相应的而函数处理,而如果是子中断,则需要去读取子中断,获得真正的中断号来处理,处理上会有一些区别。举个例子,如果只是设置外部中断0-3,则其就是主中断,中断处理设置函数为irq_set_chip_and_handler(virq, &s3c_irq_eint0t4,handle_edge_irq);中断描述符的handle为handle_edge_irq,但是如果外部中断号大于3,则需要先设置当前子中断描述符处理函数为handle_edge_irq,设置中断描述符的handle为s3c_irq_demux,在这边处理子中断。

2.3 中断处理函数调用

设置完中断处理函数以后,看一下中断如何调用。中断总入口函数:

asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
	do {
		if (likely(s3c_intc[0]))
			if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))
				continue;

		if (s3c_intc[2])
			if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
				continue;

		break;
	} while (1);
}

s3c_intc[2] 这边没有使用,传入的struct pt_regs *regs参数其实就是之前保存现场的那些寄存器,可以参考第一章。

static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc,
				      struct pt_regs *regs, int intc_offset)
{
	int pnd;
	int offset;
	int irq;

	pnd = __raw_readl(intc->reg_intpnd);
	if (!pnd)
		return false;

	/* non-dt machines use individual domains */
	if (!intc->domain->of_node)
		intc_offset = 0;

	/* We have a problem that the INTOFFSET register does not always
	 * show one interrupt. Occasionally we get two interrupts through
	 * the prioritiser, and this causes the INTOFFSET register to show
	 * what looks like the logical-or of the two interrupt numbers.
	 *
	 * Thanks to Klaus, Shannon, et al for helping to debug this problem
	 */
	offset = __raw_readl(intc->reg_intpnd + 4);

	/* Find the bit manually, when the offset is wrong.
	 * The pending register only ever contains the one bit of the next
	 * interrupt to handle.
	 */
	if (!(pnd & (1 << offset)))
		offset =  __ffs(pnd);

	irq = irq_find_mapping(intc->domain, intc_offset + offset);
	handle_IRQ(irq, regs);
	return true;
}

 s3c24xx_handle_intc先处理主中断寄存器,根据寄存器请求位,找到主中断寄存器的中断号,然后调用handle_IRQ。

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);

	irq_enter();

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (unlikely(irq >= nr_irqs)) {
		if (printk_ratelimit())
			printk(KERN_WARNING "Bad IRQ%u\n", irq);
		ack_bad_irq(irq);
	} else {
		generic_handle_irq(irq); 主要的中断处理函数
	}

	irq_exit(); 退出的时候会检查是否有软中断,如果有,则调用软中断
	set_irq_regs(old_regs);
}

handle_IRQ

         ----------->generic_handle_irq

                 ------------>generic_handle_irq_desc

                            ------------>desc->handle_irq

这边分两种情况,如果该中断号下面不再包含子中断,则根据触发方式不同,中断处理函数类型应该如下:

(1)handle_edge_irq

(2)handle_level_irq

如果中断号下面包含子中断,例如主中断为EINT4_7,则还包含4,5,6,7 4个子中断,则其主中断处理函数为s3c_irq_demux,先看该函数:

static void s3c_irq_demux(unsigned int irq, struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc);
	struct s3c_irq_intc *intc = irq_data->intc;
	struct s3c_irq_intc *sub_intc = irq_data->sub_intc;
	unsigned long src;
	unsigned long msk;
	unsigned int n;
	unsigned int offset;

	/* we're using individual domains for the non-dt case
	 * and one big domain for the dt case where the subintc
	 * starts at hwirq number 32.
	 */
	offset = (intc->domain->of_node) ? 32 : 0;

	chained_irq_enter(chip, desc);调用chip操作函数集清除主中断位,并设置该中断屏蔽位

	src = __raw_readl(sub_intc->reg_pending);
	msk = __raw_readl(sub_intc->reg_mask);

	src &= ~msk;
	src &= irq_data->sub_bits;

	while (src) {
		n = __ffs(src);
		src &= ~(1 << n);  根据子中断寄存器,计算出子中断号,调用generic_handle_irq
		irq = irq_find_mapping(sub_intc->domain, offset + n);
		generic_handle_irq(irq);
	}

	chained_irq_exit(chip, desc);清除该中断屏蔽位
}

可以看到s3c_irq_demux函数会根据子中断寄存器计算出子中断号,然后再一次调用generic_handle_irq,即generic_handle_irq_desc,再看一下handle_edge_irq如何处理。

void
handle_edge_irq(unsigned int 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
	 */
	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;
		}
	}
	kstat_incr_irqs_this_cpu(irq, desc);

	/* Start handling the irq */
	desc->irq_data.chip->irq_ack(&desc->irq_data);

	do {
		if (unlikely(!desc->action)) {
			mask_irq(desc);
			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->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);
}

中断处理的核心函数是handle_irq_event

handle_irq_event

          ----------->handle_irq_event_percpu

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

	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:
			/*
			 * 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;
			}

			irq_wake_thread(desc, action);

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

		default:
			break;
		}

		retval |= res;
		action = action->next;
	} while (action);

	add_interrupt_randomness(irq, flags);

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

可以看到,最终的执行处理函数是action->handler,而action->handler是在驱动程序调用request_irq的时候注册的,当使用共享中断的时候,会有多个action。最后总结一下中断处理的调用过程:

(1)中断向量调用总入口函数s3c24xx_handle_irq

(2)根据中断寄存器,计算出中断号调用不同的中断处理函数,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq。而对于有子中断的中断号,则需要先调用s3c_irq_demux处理获取子中断的中断号

(3)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数网上找到一张不错的结构图:

引用博文:https://www.cnblogs.com/hoys/archive/2011/04/13/2015322.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值