设置APIC中断服务

    技术2024-07-27  64

    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,其余服务程序都还没有被初始化。

    最新回复(0)