设置APIC中断服务

5.10 初始化中断处理系统

start_kernel接下来要做的事是初始化中断处理系统。整个内核的中断系统的核心就是我们在“初始化中断描述符表”里面设置的那个中断描述符表。而这个表的前19个表项我们已经在“初始化异常服务”中设置为了一些中断和异常的服务。内核接下来会如何设置其余的表项呢?

 

前面提到过高级可编程中断控制器APIC,这里简单地提一下它的体系结构,简单地说就是由两部分组成:本地高级中断控制器(Local APICLAPIC),位于每个CPU中,主要负责传递中断信号到指定的处理器中;I/O高级中断控制器(I/O APIC,)负责搜集来自I/O设备的中断信号并分发给LAPIC,形成一个多级APIC中断分发网络。

 

接下来的大部分初始化工作都将围绕着LAPICIOAPIC这两个东西进行。

 

5.10.1 设置APIC中断服务

回到start_kernel,由于我们没有配置CONFIG_SPARSE_IRQ,所以605行的early_irq_init()函数是个空函数。606行,调用init_IRQ函数,其本质上是调用x86_init.irqs.intr_init函数,也就是前面“拷贝可用内存区信息”的那个x86_init中看到的那个native_init_IRQ函数,用来初始化LAPIC

 

235void __init native_init_IRQ(void)

 236{

 237        int i;

 238

 239        /* Execute any quirks before the call gates are initialised: */

 240        x86_init.irqs.pre_vector_init();

 241

 242        apic_intr_init();

 243

 244        /*

 245         * Cover the whole vector space, no vector can escape

 246         * us. (some of these will be overridden and become

 247         * 'special' SMP interrupts)

 248         */

 249        for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {

 250                /* IA32_SYSCALL_VECTOR could be used in trap_init already. */

 251                if (!test_bit(i, used_vectors))

 252                        set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);

 253        }

 254

 255        if (!acpi_ioapic)

 256                setup_irq(2, &irq2);

 257

 258#ifdef CONFIG_X86_32

 259        /*

 260         * External FPU? Set up irq13 if so, for

 261         * original braindamaged IBM FERR coupling.

 262         */

 263        if (boot_cpu_data.hard_math && !cpu_has_fpu)

 264                setup_irq(FPU_IRQ, &fpu_irq);

 265

 266        irq_ctx_init(smp_processor_id());

 267#endif

 268}

 

该函数初始化中断向量表中前面一些我们没有设置的表项,首先是240,调用x86_init.irqs.pre_vector_init函数,也就是init_ISA_irqs函数:

 

101void __init init_ISA_irqs(void)

 102{

 103        int i;

 104

 105#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)

 106        init_bsp_APIC();

 107#endif

 108        legacy_pic->init(0);

 109

 110        /*

 111         * 16 old-style INTA-cycle interrupts:

 112         */

 113        for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) {

 114                struct irq_desc *desc = irq_to_desc(i);

 115

 116                desc->status = IRQ_DISABLED;

 117                desc->action = NULL;

 118                desc->depth = 1;

 119

 120                set_irq_chip_and_handler_name(i, &i8259A_chip,

 121                                              handle_level_irq, "XT");

 122        }

 123}

 

106行,由于我们配置了CONFIG_X86_LOCAL_APIC,所以首先调用init_bsp_APIC函数来配置本地LAPIC芯片。该函数调用apic_readapic_write调用全局变量apicreadwrite方法。先说说这个全局变量apic的数据结构,其代表一块LAPIC控制器芯片,于arch/x86/include/asm/apic.h文件中定义:

 

struct apic {

       char *name;

 

       int (*probe)(void);

       int (*acpi_madt_oem_check)(char *oem_id, char *oem_table_id);

       int (*apic_id_registered)(void);

 

……

 

       /* apic ops */

       u32 (*read)(u32 reg);

       void (*write)(u32 reg, u32 v);

       u64 (*icr_read)(void);

       void (*icr_write)(u32 low, u32 high);

       void (*wait_icr_idle)(void);

       u32 (*safe_wait_icr_idle)(void);

};

 

而全局变量apic在文件arch/x86/kernel/apic/probe_32.c文件中被定义成:

struct apic *apic = &apic_default;

于是乎,编译vmlinux的时候,apic_default就成为了该变量实际的值:

 

struct apic apic_default = {

 

       .name                           = "default",

       .probe                          = probe_default,

       .acpi_madt_oem_check         = NULL,

       .apic_id_registered        = default_apic_id_registered,

 

……

 

       .read                            = native_apic_mem_read,

       .write                           = native_apic_mem_write,

       .icr_read                = native_apic_icr_read,

       .icr_write                     = native_apic_icr_write,

       .wait_icr_idle                = native_apic_wait_icr_idle,

       .safe_wait_icr_idle        = native_safe_apic_wait_icr_idle,

};

 

我们看到,init_bsp_APIC函数实际上调用native_apic_mem_readnative_apic_mem_write等方法。回到init_ISA_irqs中,108行又是一个全局变量,legacy_pic

struct legacy_pic *legacy_pic = &default_legacy_pic;

其值被初始化成了default_legacy_pic

 

struct legacy_pic default_legacy_pic = {

       .nr_legacy_irqs = NR_IRQS_LEGACY,

       .chip  = &i8259A_chip,

       .mask_all  = mask_8259A,

       .restore_mask = unmask_8259A,

       .init = init_8259A,

       .irq_pending = i8259A_irq_pending,

       .make_irq = make_8259A_irq,

};

 

struct legacy_pic *legacy_pic = &default_legacy_pic;

 

我们熟悉的8259A中断控制器芯片就是这个default_legacy_pic,所以init_ISA_irqs108行是调用该芯片的初始化函数init_8259A, 初始化了8259A的一些硬件状态,保证8259A能够正常工作:

 

 

static void init_8259A(int auto_eoi)

{

       unsigned long flags;

 

       i8259A_auto_eoi = auto_eoi;

 

       raw_spin_lock_irqsave(&i8259A_lock, flags);

 

       outb(0xff, PIC_MASTER_IMR);  /* mask all of 8259A-1 */

       outb(0xff, PIC_SLAVE_IMR);     /* mask all of 8259A-2 */

 

       /*

        * outb_pic - this has to work on a wide range of PC hardware.

        */

       outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */

 

       /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64,

          to 0x20-0x27 on i386 */

       outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR);

 

       /* 8259A-1 (the master) has a slave on IR2 */

       outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR);

 

       if (auto_eoi)   /* master does Auto EOI */

              outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);

       else         /* master expects normal EOI */

              outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);

 

       outb_pic(0x11, PIC_SLAVE_CMD);    /* ICW1: select 8259A-2 init */

 

       /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */

       outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR);

       /* 8259A-2 is a slave on master's IR2 */

       outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);

       /* (slave's support for AEOI in flat mode is to be investigated) */

       outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);

 

       if (auto_eoi)

              /*

               * In AEOI mode we just have to mask the interrupt

               * when acking.

               */

              i8259A_chip.mask_ack = disable_8259A_irq;

       else

              i8259A_chip.mask_ack = mask_and_ack_8259A;

 

       udelay(100);          /* wait for 8259A to initialize */

 

       outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */

       outb(cached_slave_mask, PIC_SLAVE_IMR);       /* restore slave IRQ mask */

 

       raw_spin_unlock_irqrestore(&i8259A_lock, flags);

}

 

函数具体内容我就不详细分析了,无非是些outb或者outb_pic通过这个芯片所占用的总线端口对它进行初始化。回到init_ISA_irqs113行,刚才看见nr_legacy_irqs NR_IRQS_LEGACY,也就是16次循环,将全局irq_desc[irq]数组的16irq_desc元素进行初始化;然后16次通过set_irq_chip_and_handler_name函数将每个irq_descchip字段通过set_irq_chip设置成全局变量i8259A_chip,以及将每个irq_deschandle_irqname字段通过__set_irq_handler分别设置为handle_level_irq” XT”

 

回到native_init_IRQ中,随后242行调用apic_intr_init()函数:

 

202static void __init apic_intr_init(void)

 203{

 204        smp_intr_init();

 205

 206#ifdef CONFIG_X86_THERMAL_VECTOR

 207        alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);

 208#endif

 209#ifdef CONFIG_X86_MCE_THRESHOLD

 210        alloc_intr_gate(THRESHOLD_APIC_VECTOR, threshold_interrupt);

 211#endif

 212#if defined(CONFIG_X86_MCE) && defined(CONFIG_X86_LOCAL_APIC)

 213        alloc_intr_gate(MCE_SELF_VECTOR, mce_self_interrupt);

 214#endif

 215

 216#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)

 217        /* self generated IPI for local APIC timer */

 218        alloc_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);

 219

 220        /* IPI for X86 platform specific use */

 221        alloc_intr_gate(X86_PLATFORM_IPI_VECTOR, x86_platform_ipi);

 222

 223        /* IPI vectors for APIC spurious and error interrupts */

 224        alloc_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);

 225        alloc_intr_gate(ERROR_APIC_VECTOR, error_interrupt);

 226

 227        /* Performance monitoring interrupts: */

 228# ifdef CONFIG_PERF_EVENTS

 229        alloc_intr_gate(LOCAL_PENDING_VECTOR, perf_pending_interrupt);

 230# endif

 231

 232#endif

 233}

 

smp_intr_init()函数,32x86体系中是一个空函数。后面206229设置中断描述符表中APIC相关的中断服务层序,也就是把位于32~255之间,除去系统调用外其他中断向量的中断处理程序设置为interrupt[i]interrupt共有NR_VECTORS-FIRST_EXTERNAL_VECTOR个表项:

#define NR_VECTORS               256

#define FIRST_EXTERNAL_VECTOR              0x20  //32

并且interrupt数组在arch/x86/kernel/entry_32.S中定义并初始化:

 

823.section .init.rodata,"a"

 824ENTRY(interrupt)

 825.text

 826        .p2align 5

 827        .p2align CONFIG_X86_L1_CACHE_SHIFT

 828ENTRY(irq_entries_start)

 829        RING0_INT_FRAME

 830vector=FIRST_EXTERNAL_VECTOR

 831.rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7

 832        .balign 32

 833  .rept 7

 834    .if vector < NR_VECTORS

 835      .if vector <> FIRST_EXTERNAL_VECTOR

 836        CFI_ADJUST_CFA_OFFSET -4

 837      .endif

 8381:      pushl $(~vector+0x80)   /* Note: always in signed byte range */

 839        CFI_ADJUST_CFA_OFFSET 4

 840      .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6

 841        jmp 2f

 842      .endif

 843      .previous

 844        .long 1b

 845      .text

 846vector=vector+1

 847    .endif

 848  .endr

 8492:      jmp common_interrupt

 850.endr

 851END(irq_entries_start)

 852

 853.previous

 854END(interrupt)

 

看到上面的代码,从824854行都属于数据段中,看到828~851之间的代码,虽然表面上看上去它们是分隔开的,但是实际上编译之后会在数据段中一块连续的区域中。我们看到从831~850行,执行循环NR_VECTORS-FIRST_EXTERNAL_VECTOR+6次,也就是说是256-32+6=230次,那么代码展开后,这段数据的内容就类似于:

ENTRY(interrupt)

      .long 1b

      .long 1b

      .long 1b

……

即构成225个项的interrupt[]数组,每个元素占64位,也就是8个字节。我们看到每个元素都是1b,即上面代码1标号后的代码片段:

pushl $(~vector+0x80)

CFI_ADJUST_CFA_OFFSET 4

.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6

       jmp 2f

.endif

 

因此,都会执行代码2标号的那个common_interrupt,其参数为push进去的~vector+0x80。举个例子,比如中断向量32,即IRQ0,对应的中断处理程序interrupt[0]

pushl $(~0 + 0x80)

CFI_ADJUST_CFA_OFFSET 4

jmp common_interrupt

 

至于common_interrupt随后如何执行,具体的细节请查阅博客“中断处理(续)” http://blog.csdn.net/yunsongice/archive/2010/03/07/5353896.aspx

 

那么在native_init_IRQ设置好了中断描述符表的32~256项之后,也就是interrupt[]0224元素作为其处理函数之后,我们就可以设置它了。所以看到随后255256行,在没有配置CONFIG_ACPI,也就是说,没有启动ACPI控制器的情况下,调用老式的setup_irq函数把2IRQ设置成一个空函数。263264行,调用setup_irq函数把13IRQ设置成FPU容错处理函数:

int setup_irq(unsigned int irq, struct irqaction *act)

{

       struct irq_desc *desc = irq_to_desc(irq);

       return __setup_irq(irq, desc, act);

}

 

__setup_irq本质上就是通过irq号得到对应的irq_desc描述符。然后调用__setup_irq()函数初始化它和irqaction,并把这个描述符插入到合适的IRQ链表。具体的代码内容比较多,我就不一一分析了,对上述过程还想深入研究得可以去浏览一下博客“中断处理(续)” http://blog.csdn.net/yunsongice/archive/2010/03/07/5353896.aspx

 

最后,由于我们没有配置CONFIG_4KSTACKS编译选项,所以native_init_IRQ的最后一个函数irq_ctx_init是空函数,那么native_init_IRQ结束后,整个init_IRQ函数就结束了,APIC和各本地PIC的相关的中断服务就算初始化完毕了。此时,内核全局变量irq_desc[]数组的16个元素的chip字段都是i8259A_chiphandle_irq字段是handle_level_irqname字段为“XT”;同时,interrupt[]数组的每一个元素作为中断描述符表表项32~255的服务程序,且仅有interrupt[2]interrupt[13]分别被初始化成了一个空的处理函数no_action和一个FPU容错函数math_error_irq,其余服务程序都还没有被初始化。

你可能感兴趣的:(疯狂内核之系统初始化)