Linux中断子系统-中断初始化

      前面学习了一下linux中断子系统的中断处理过程,看到中断最终由 desc->handle_irq(irq, desc)处理。linux系统最早调用与中断相关的函数是early_trap_init(),他主要工作是把我们定义好的中断向量表拷贝到CPU的中断地址。然后linux调用early_irq_init()初始化linux中断系统的核心数据,下面我们就从中断模型的核心数据开始来继续学习linux的中断系统。

      linux中断机制的核心数据结构 irq_desc, 它完整地描述了一条中断线 (或称为 “中断通道” )。其中irq_desc 结构在 include/linux/irqdesc.h 中定义:

struct irq_desc {
	struct irq_data		irq_data;
	unsigned int __percpu	*kstat_irqs;
	irq_flow_handler_t	handle_irq;/* 高层次的中断事件处理函数 */
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	struct irqaction	*action;	/*  行为链表IRQ action list */
	unsigned int		status_use_accessors;
	unsigned int		core_internal_state__do_not_mess_with_it;
	unsigned int		depth;		/* 关中断次数nested irq disables */
	unsigned int		wake_depth;	/*  唤醒次数nested wake enables */
	unsigned int		irq_count;	/* 发生的中断次数For detecting broken IRQs */
	unsigned long		last_unhandled;	/* 滞留时间Aging timer for unhandled count */
	unsigned int		irqs_unhandled;
	raw_spinlock_t		lock;/*自旋锁*/
	struct cpumask		*percpu_enabled;
#ifdef CONFIG_SMP
	const struct cpumask	*affinity_hint;
	struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
	cpumask_var_t		pending_mask;
#endif
#endif
	unsigned long		threads_oneshot;
	atomic_t		threads_active;
	wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;


其中irq_data 结构在 include/linux/irq.h 中定义:

struct irq_data {
	unsigned int		irq;
	unsigned long		hwirq;
	unsigned int		node;
	unsigned int		state_use_accessors;
	struct irq_chip		*chip; /* 低层次的硬件操作 */
	struct irq_domain	*domain;
	void			*handler_data;/* chip 方法使用的数据*/
	void			*chip_data;    /* chip 私有数据 */
	struct msi_desc		*msi_desc;
	cpumask_var_t		affinity;
};


其中irq_desc 结构在 include/linux/irqdesc.h 中定义:

struct irqaction {
	irq_handler_t		handler;//中断处理函数,注册时提供
	void			*dev_id;//设备id
	void __percpu		*percpu_dev_id;
	struct irqaction	*next;//如果有中断共享,则继续执行,
	irq_handler_t		thread_fn;
	struct task_struct	*thread;
	unsigned int		irq;//中断号,注册时提供
	unsigned int		flags;
	unsigned long		thread_flags;//中断标志,注册时提供
	unsigned long		thread_mask;//中断掩码
	const char		*name;//中断名称
	struct proc_dir_entry	*dir;//指向IRQn相关的/proc/irq/n目录的描述符
} ____cacheline_internodealigned_in_smp;

    在注册中断号为irq的中断服务程序时,系统会根据注册参数封装相应的irqaction结构体。并把中断号为irqirqaction结构体写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。 它的定义在kernel/irq/irqdesc.c:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};

         start_kernel()函数调用early_irq_init()init_IRQ()两个函数来初始化中断管理系统,其实就是初始化irq_desc的结构。

         start_kernel()函数中调用了early_irq_init()函数,这个函数在kernel/irq/irqdesc.c文件中定义。这个函数将用于管理中断的irq_desc[NR_IRQS]数组的每个元素的部分字段设置为确定的状态,它设置每一个成员的中断号

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

     early_irq_init属于与硬件和平台无关的通用逻辑层,它完成irq_desc结构的内存申请,为它们其中某些字段填充默认值,完成后调用体系相关的arch_early_irq_init函数完成进一步的初始化工作,不过ARM体系没有实现arch_early_irq_init。

       接着,start_kernel调用定义在arch\arm\kernel\irq.c的init_IRQ()函数,它会直接调用所属板子machine_desc结构体中的init_irq回调。machine_desc通常在板子的特定代码中,使用MACHINE_START和MACHINE_END宏进行定义。

void __init init_IRQ(void)
{
	machine_desc->init_irq();
}

machine_desc在setup_arch()函数中被初始化

	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(machine_arch_type);
	machine_desc = mdesc;


struct machine_desc类型的结构,mach_desc里定义了一些关键的体系架构相关的信息。比如我手上这个板子的源码:

DT_MACHINE_START(sama5_dt, "Atmel SAMA5 (Device Tree)")
	/* Maintainer: Atmel */
	.timer		= &at91sam926x_timer,
	.map_io		= at91_map_io,
	.handle_irq	= at91_aic5_handle_irq,
	.init_early	= at91_dt_initialize,
	.init_irq	= at91_dt_init_irq,
	.init_machine	= at91_dt_device_init,
	.dt_compat	= sama5_dt_board_compat,
MACHINE_END

static const char *at91_dt_board_compat[] __initdata = {
	"atmel,at91sam9",
	NULL
};


MACHINE_START或DT_MACHINE_START宏的作用是对mach_desc结构体进行初始化。然后setup_arch去为相应的芯片找到相应的结构体。我们跟踪代码看看:

static void __init at91_dt_init_irq(void)
{
	of_irq_init(irq_of_match);
}
of_irq_init 扫描 初始 匹配 中断 控制器,扫描设备树匹配中断控制器节点并调用初始化函数,
void __init of_irq_init(const struct of_device_id *matches)
{
	struct device_node *np, *parent = NULL;
	struct intc_desc *desc, *temp_desc;
	struct list_head intc_desc_list, intc_parent_list;

	INIT_LIST_HEAD(&intc_desc_list);
	INIT_LIST_HEAD(&intc_parent_list);

	for_each_matching_node(np, matches) {
		if (!of_find_property(np, "interrupt-controller", NULL))
			continue;
		/*
		 * Here, we allocate and populate an intc_desc with the node
		 * pointer, interrupt-parent device_node etc.
		 */
		desc = kzalloc(sizeof(*desc), GFP_KERNEL);
		if (WARN_ON(!desc))
			goto err;

		desc->dev = np;
		desc->interrupt_parent = of_irq_find_parent(np);
		if (desc->interrupt_parent == np)
			desc->interrupt_parent = NULL;
		list_add_tail(&desc->list, &intc_desc_list);
	}

	/*
	 * The root irq controller is the one without an interrupt-parent.
	 * That one goes first, followed by the controllers that reference it,
	 * followed by the ones that reference the 2nd level controllers, etc.
	 */
	while (!list_empty(&intc_desc_list)) {
		/*
		 * Process all controllers with the current 'parent'.
		 * First pass will be looking for NULL as the parent.
		 * The assumption is that NULL parent means a root controller.
		 */
		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
			const struct of_device_id *match;
			int ret;
			of_irq_init_cb_t irq_init_cb;

			if (desc->interrupt_parent != parent)
				continue;

			list_del(&desc->list);
			match = of_match_node(matches, desc->dev);
			if (WARN(!match->data,
			    "of_irq_init: no init function for %s\n",
			    match->compatible)) {
				kfree(desc);
				continue;
			}

			pr_debug("of_irq_init: init %s @ %p, parent %p\n",
				 match->compatible,
				 desc->dev, desc->interrupt_parent);
			irq_init_cb = match->data;
			ret = irq_init_cb(desc->dev, desc->interrupt_parent);
			if (ret) {
				kfree(desc);
				continue;
			}

			/*
			 * This one is now set up; add it to the parent list so
			 * its children can get processed in a subsequent pass.
			 */
			list_add_tail(&desc->list, &intc_parent_list);
		}

		/* Get the next pending parent that might have children */
		desc = list_first_entry(&intc_parent_list, typeof(*desc), list);
		if (list_empty(&intc_parent_list) || !desc) {
			pr_err("of_irq_init: children remain, but no parents\n");
			break;
		}
		list_del(&desc->list);
		parent = desc->dev;
		kfree(desc);
	}

	list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
		list_del(&desc->list);
		kfree(desc);
	}
err:
	list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
		list_del(&desc->list);
		kfree(desc);
	}
}


这个函数完成对于中断控制器的初始化,并且设置中断描述符的相应的函数指针的值,以在中断发生时发生时,调用这些函数来完成芯片级的处理。为每个irq_desc结构安装irq_chip指针,使他指向正确的中断控制器所对应的irq_chip结构的实例,同时,如果该平台中的中断线有多路复用(多个中断公用一个irq中断线)的情况,还应该初始化irq_desc中相应的字段和标志,以便实现中断控制器的级联。


 

 

 

 

 

 

 

 

你可能感兴趣的:(Linux中断子系统-中断初始化)