Linux中断子系统的初始化
注:以2.6.39内核源码讲解
Linux整个中断处理体系其实可以分为两个部分,一部分是系统完成的部分,另一部分是驱动工程师需要完成的部分(也就是我们用requst_irq注册的处理函数),本次我们主要讨论的是系统启动的过程中对中断子系统做了哪些事?
一、搬移异常向量表
内核在启动的时候就是先运行start_kernel() , 然后她就会调用体系结构相关的setup_arch(&command_line), 如arm体系结构的在arch/arm/kernel/setup.c中, 进一步, 她就要初始化板级相关的设备,内核会在这个工程中调用early_trap_init()把异常向量表搬移到0xFFFF0000的位置。
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine(machine_arch_type);
......
early_trap_init(); //把异常向量表搬移到0xFFFF0000的位置。
}
二、初始化中断管理子系统
我们先来看一下Linux系统中,中断管理系统的初始化。中断系统的初始化主要由两个函数来完成。在系统初始化的start_kernel()函数 (在文件init/main.c中定义)中可以看到:
asmlinkage void __init start_kernel(void)
{
……
setup_arch(&command_line);
trap_init();
……
early_irq_init();
init_IRQ();
……
}
Linux中断机制的核心数据结构 irq_desc, 它完整地描述了一条中断线 (或可简单理解为 “一个中断源” )。start_kernel()函数调用early_irq_init()和init_IRQ()两个函数来初始化中断管理系统。其实就是初始化irq_desc的结构。
1、early_irq_init()函数
注:这个函数在kernel/irq/irqdesc.c文件中定义。
在start_kernel()函数中调用了early_irq_init()函数,linux调用early_irq_init()初始化linux中断系统的核心数据。early_irq_init属于与硬件和平台无关的通用逻辑层,为irq_desc[]中个元素的某些成员填充默认值,完成后调用体系相关的arch_early_irq_init函数完成进一步的初始化工作,不过ARM体系没有实现arch_early_irq_init。
注:irq_desc[] 全局数组
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),
}
};
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].irq_data.irq = i;
desc[i].irq_data.chip = &no_irq_chip;
desc[i].kstat_irqs = alloc_percpu(unsigned int);
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
alloc_masks(desc + i, GFP_KERNEL, node);
desc_smp_init(desc + i, node);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
}
return arch_early_irq_init();
}
注: 在使用设备树的高版本内核中,管理中断描述符也可能是用基数树去管理了,避免空间的浪费。
2、init_IRQ()函数
init_IRQ(void)函数是一个特定与体系结构的函数,对于ARM体系结构,在文件arch/arm/kernel/irq.c
void __init init_IRQ(void)
{
machine_desc->init_irq();
}
mdesc结构的init_irq成员,这是一个struct machine_desc类型的结构,mach_desc里定义了一些关键的体系架构相关的信息。
以mini2440为例:
其machine_desc结构的init_irq成员在文件arch/arm/mach-s3c2440/mach-mini2440.c中被赋值为s3c24xx_init_irq函数
MACHINE_START(MINI2440, "MINI2440")
/* Maintainer: Michel Pollet <buserror@gmail.com> */
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = mini2440_map_io,
.init_machine = mini2440_init,
.init_irq = s3c24xx_init_irq,
.timer = &s3c24xx_timer,
MACHINE_END
注:MACHINE_START宏的作用是对mach_desc结构体进行初始化。OK,终于找到了init_IRQ() 真正内容,在arch/arm/plat-s3c24xx/irq.c中定义:
/* s3c24xx_init_irq
*
* Initialise S3C2410 IRQ system
*/
3、init_IRQ()实例
对应平台下面的代码,和硬件平台相关,为各个中断源设置默认的处理函数
void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}
.......
for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */
switch (irqno) {
/* deal with the special IRQs (cascaded) */
case IRQ_EINT4t7:
case IRQ_EINT8t23:
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
irq_set_chip_and_handler(irqno, &s3c_irq_level_chip, handle_level_irq);
break;
case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;
default:
//irqdbf("registering irq %d (s3c irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_chip, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}
/* 设置外部中断的控制器(操作方法集),默认前级处理函数handle_irq */
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irq_set_chip_and_handler(irqno, &s3c_irq_eint0t4, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irq_set_chip_and_handler(irqno, &s3c_irqext_chip, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
......
}
这个函数完成对于中断控制器的初始化,并且设置中断描述符的相应的函数指针的值,以在中断发生时发生时,调用这些函数来完成芯片级的处理。
irq_set_chip_and_handler()介绍:
1、可以设置中断号对应的中断描述符的chip,里面其实是一系列操作对于控制器的方法,以在中断发生或者管理中断时可以完成对于芯片的操作。
以IRQ_EINT4-IRQ_EINT23为例,它的chip是s3c_irqext_chip
static struct irq_chip s3c_irqext_chip = {
.name = "s3c-ext",
.irq_mask = s3c_irqext_mask,
.irq_unmask = s3c_irqext_unmask,
.irq_ack = s3c_irqext_ack,
.irq_set_type = s3c_irqext_type,
.irq_set_wake = s3c_irqext_wake
};
2、可以设置中断发生的第一级处理函数比如 handle_level_irq 或者 handle_edge_irq,本质上就是设置irq_desc[irqno].handle_irq 的值。
其实中断处理的时候一般分为两级,第一级是调用irq_desc[irqno].handle_irq。它主要面向GIC的莫一中断线IRQ line,跟当前中断触发电信号相关的一个函数,不同的中断触发方式,其中断线处理的函数是不同的,不过主要是电平处理和边沿处理两种。 第二级才是我们驱动开发者自己注册的中断函数,也就是irq_desc[irqno].action->handler的调用。
上面这个函数就是为特定的中断号设置好一个中断处理例程,这里的例程可不是我们request_irq注册的例程,Linux支持中断共享,共享同一个中断号的每一设备都可以有自己特定的中断处理程序,用来描述这些中断处理程序的结构会形成一个链表,这里设置的例程将会逐个调用特定中断号上的各设备的中断处理例程。