UP的APIC的建立过程
讲述UP的中断建立过程。内核版本2.6.9-78;ich8,cpu:intel-530。
许多内容不讲,一带而过。
内核config:
# CONFIG_SMP is not set
# CONFIG_PREEMPT is not set
CONFIG_PREEMPT_VOLUNTARY=y
CONFIG_X86_UP_APIC=y
CONFIG_X86_UP_IOAPIC=y
CONFIG_X86_LOCAL_APIC=y
CONFIG_X86_IO_APIC=y
内核流程
smp_init:main.c
#ifndef CONFIG_SMP
#ifdef CONFIG_X86_LOCAL_APIC
static void __init smp_init(void)
{
APIC_init_uniprocessor();
}
#else
#define smp_init() do { } while (0)
#endif
那么整个开始就是:APIC_init_uniprocessor().
APIC_init_uniprocessor
int __init APIC_init_uniprocessor (void)
{
//condition false
if (enable_local_apic < 0)
clear_bit(X86_FEATURE_APIC, boot_cpu_data.x86_capability);
//condition false
if (!smp_found_config && !cpu_has_apic)
return -1;
/*
* Complain if the BIOS pretends there is one.
*/
if (!cpu_has_apic && APIC_INTEGRATED(apic_version[boot_cpu_physical_apicid])) {
printk(KERN_ERR "BIOS bug, local APIC #%d not detected!.../n",
boot_cpu_physical_apicid);
return -1;
}
verify_local_APIC();
connect_bsp_APIC();
setup_local_APIC();
//如果nmi watchdog使用local apic。因为在setup_local_APIC中已经设置,所以这里需要检验
if (nmi_watchdog == NMI_LOCAL_APIC)
check_nmi_watchdog();
//所有条件都满足,即执行setup_IO_APIC()
#ifdef CONFIG_X86_IO_APIC
if (smp_found_config)
if (!skip_ioapic_setup && nr_ioapics)
setup_IO_APIC();
#endif
setup_boot_APIC_clock();
verify_local_APIC()
读取local APIC寄存器中的相关信息,判断是否是local apic。这部分没有什么难点。
connect_bsp_APIC
X86中断三种模式
1:pic_mode
2:virtual wire mode
3:APIC mode
具体看 intel mp spec。
系统处于virtual wire mode。
取决与:get_smp_config函数中的:
if (acpi_lapic && acpi_ioapic) {
printk(KERN_INFO "Using ACPI (MADT) for SMP configuration information/n");
return;
}
由于系统选用ACPI所以,这个条件都满足。即LAPCI、IOAPIC都存在,所以直接退出。则刚启动时系统处于virtual wire mode,而不是pic mode。
Virtual wire mode有分为两种情况:
我们这里是:
IOAPIC并没有使用,而是使用8259A中断控制器。LAPIC是使用的。
INTR直接链接LINIT0,配置位EXTINT即外部PIC中断源。
Code
系统重启时处于PIC模式,这里需要转换成APIC模式。但是我们并不处于PIC模式,所以condition false。
if (pic_mode) {
/*
* Do not trust the local APIC being empty at bootup.
*/
clear_local_APIC();
/* PIC mode, enable APIC mode in the IMCR, i.e.connect BSP's local APIC to INT and NMI lines.*/
apic_printk(APIC_VERBOSE, "leaving PIC mode, "
"enabling APIC mode./n");
outb(0x70, 0x22);
outb(0x01, 0x23);
}
//这个函数没用
enable_apic_mode();
setup_local_APIC
这个函数是配置LAPIC,使其起作用。LAPIC本身含有6个LVT即本地中断。一个定时器、performence counte、LINT0、LINIT1、error 。
我们仅仅关心LINT0、LINT1。Local APIC自己的中断,可以链接外部设备,不知道可以链接什么设备。
这里又要区别BSP、AP。Multi processor的环境下:BSP即第一个启动的cpu;ap:是其他cpu。
/* Set up LVT0, LVT1:
* set up through-local-APIC on the BP's LINT0. This is not strictly necessery in pure symmetric-IO mode, but sometimes we delegate interrupts to the 8259A. */
/* TODO: set up through-local-APIC from through-I/O-APIC? --macro */
value = apic_read(APIC_LVT0) & APIC_LVT_MASKED;
//ma_add:区分bsp和ap即cpu id,如果是bsp那么smp_processor_id=0。pic_mode不符合,但是value=0即LVTO在virtual wire mode下面是不能被屏蔽的。所以condition true
if (!smp_processor_id() && (pic_mode || !value)) {
value = APIC_DM_EXTINT;//中断是PIC
apic_printk(APIC_VERBOSE, "enabled ExtINT on CPU#%d/n",smp_processor_id());
} else {
//AP不能使用LINT0
value = APIC_DM_EXTINT | APIC_LVT_MASKED;
apic_printk(APIC_VERBOSE, "masked ExtINT on CPU#%d/n",smp_processor_id());
}
apic_write_around(APIC_LVT0, value);
/* only the BP should see the LINT1 NMI signal, obviously.*/
//LINT1:必须设置成NMI模式,因为硬件链接上LINT1与NMI相连。看上图
if (!smp_processor_id())
value = APIC_DM_NMI;
else
value = APIC_DM_NMI | APIC_LVT_MASKED;
//如果NMI watchdog的中断源是local apic。那么在这里设置,其实是使用了LOCAL APIC中的LVT中的performance counter作为中断源。这里注意:如果使用LAPIC作为nmi_watchdog那么,有几个cpu就有几个中断源。这里与IOAPIC是不同的。
if (nmi_watchdog == NMI_LOCAL_APIC)
setup_apic_nmi_watchdog();
setup_IO_APIC
这个函数是最复杂的函数,即启动IOAPIC,同时将8259A的中断源映射到IOAPIC,这样以后中断就不走8259A,而是通过IOAPIC。即系统进入APIC模式。
enable_IO_APIC
情况IOAPIC的相关寄存器信息。主要是24个PRT表。
系统有多少个IOAPIC,每一个IOAPIC中都多个pin即有多少个PRT表。每一个PRT表是64位。
具体可以看ich8的datasheet。
setup_IO_APIC_irqs
将8259A的irq与IOAPIC的pin联系起来。由于比较复杂,这里不详细讲述。
通过打印可以看出,系统在这里将8259A的15个irq都屏蔽掉了。
if (IO_APIC_IRQ(irq)) {
vector = assign_irq_vector(irq);
entry.vector = vector;
ioapic_register_intr(irq, vector, IOAPIC_AUTO);
if (!apic && (irq < 16))
disable_8259A_irq(irq);
}
此时中断不经过8259A,而是经过IOAPIC。即系统完成从virtual wire mode到APIC的转换。
init_IO_APIC_traps
没有看什么意思。
check_timer
这个函数很有意思,但是不知道为什么要检测timer的函数?即检测timer中断通过IOAPIC是否正常或者通过8259A或者vitual wire 模式正常。
由于我们的系统仅仅走一部分,所以我们讲相关部分。
//首先关中断:cli
local_irq_save(flags);
/*
* get/set the timer IRQ vector:
*/
disable_8259A_irq(0);
vector = assign_irq_vector(0);
set_intr_gate(vector, interrupt[0]);
/*
* Subtle, code in do_timer_interrupt() expects an AEOI
* mode for the 8259A whenever interrupts are routed
* through I/O APICs. Also IRQ0 has to be enabled in
* the 8259A which implies the virtual wire has to be
* disabled in the local APIC.
*/
//ma-add:禁用LVTO:前面LVTO与8259A的IRQ链接作为cpu的中断源
apic_write_around(APIC_LVT0, APIC_LVT_MASKED | APIC_DM_EXTINT);
//aeoi?为什么不懂:在do_IRQ中会执行disable_8259A_irq
init_8259A(1);
//function:do_timer_interrupt,需要ack
timer_ack = 1;
//ma_add:contidition is ture
if (timer_over_8254 > 0)
enable_8259A_irq(0);
//判断Timer IRQ作为mp_INT,是否连接至IO APIC
pin1 = find_isa_irq_pin(0, mp_INT);
//Timer作为mp_EXTINT,是否连接至IO APIC
pin2 = find_isa_irq_pin(0, mp_ExtINT);
if (pin1 == 0)
timer_uses_ioapic_pin_0 = 1;
//打印信息:TIMER: vector=0x31 pin1=2 pin2=-1
//timer中断的vector是0x31,链接到IOAPIC的pin2.关于8259A的irq与IOAPIC的pin的对应关系在后面的打印信息中会说明。
printk(KERN_INFO "..TIMER: vector=0x%02X pin1=%d pin2=%d/n", vector, pin1, pin2);
//ma-add:IOAPIC的pin1 mapped 8259A的irq0即timer irq
if (pin1 != -1) {
/*
* Ok, does IRQ0 through the IOAPIC work?
*/
//开启IOAPIC中断,重定向表已经在前面设定了:仔细看这个函数是将所有的IOAPIC都unmask,即此时中断就走IOAPIC,而由于前面8259A已经都关闭了,但是在这个函数中又将enable_8259A_irq(0),但是LINT0被mask,所以timer不能经过8259A走LINT0到cpu。但是timer中断有两条路径到APIO:直接和经过8259A。其实8259A的总的中断INTR是连到IOAPIC的pin0,但是IOAPIC是mask该pin。所以即使timer通过两条路径到APIO,其中一条是不通的。所以不会发生重复中断问题。
unmask_IO_APIC_irq(0);
if (timer_irq_works()) {
//ma_add:use NMI_NO_APIC,must unmask APIC_LVTO。this contdition:timer irq using two way:LINTO and IO_APIC。这里做过测试如果在check_nmi_watchdog()后将LVT0,masked。则NMI watchdog停止。所以可以说明:如果nmi_watchdog是IOAPIC,那么在virtual wire mode下面是timer定时器一个中断有两个作用:一个经过8259A和LINIT0,作为NMI的中断源通知cpu;而另一个作为一般的timer 中断走IOAPIC。通过打印信息发现:MP系统如果使用nmi_watchdog使用IOAPIC,那么也仅仅会有一个NMI中断源,这不同于nmi_watchdog=LAPIC.
if (nmi_watchdog == NMI_IO_APIC) {
disable_8259A_irq(0);
setup_nmi();
enable_8259A_irq(0);
check_nmi_watchdog();
}
if (disable_timer_pin_1 > 0)
clear_IO_APIC_pin(0, pin1);
goto out;
}
clear_IO_APIC_pin(0, pin1);
printk(KERN_ERR "..MP-BIOS bug: 8254 timer not connected to IO-APIC/n");
}
print_IO_APIC
通过打印相关信息,对于理解IOAPIC很有用。
下面是系统的相关打印:
IOAPIC的PRT表的打印信息
IOAPIC的pin0被mask,表明8259A的INRQ是不能被IOAPIC所接受,即不会发生重复中断现象.
在这里我们也能看到timer中断是pin2,vector是0x31.
8259A IRQ与IOAPIC pin的对应关系
从上面可以看出:整个8259A的INTR是连到IOAPIC pin0;由于8259A的IRQ2是级联,所以没有.
IRQ超过16应该是中断共享.
图示
用图表示vitual wire mode的情况.
1:LINE2、LINE4都能使用
2:外部中断是通过LINE1、8259A、LINE3与LINTIN0与LAPIC相连。这是是指LAPIC已经运行?如果在这之前怎么处理?
3:等待LINE2建立链接之后,LINE3和LINE1关闭。同时,8259A的INTR链接到IOAPIC 的pin0,但是在配置时将pin0 mask,所以即使8259A可以发出中断,IOAPIC也不会接收处理,不会发生中断重复问题。此时中断走的线路是LINE2、IOAPI、LINE4。
4:但是如果NMI watchdog是IOAPIC,那么timer interrupt分布从LINE1、8259A、LINE3、LINTIN0以NMI方式进入CPU;而已普通的中断方式从LINE2、IOAPIC、LINE4与LAPIC通信进入cpu。测试如下:如果启动NMI_watchdog测试成功后,将LINTIN0 mask后,发现NMI watchdog 中断不再发生,这说明NMI watchdog走的路线是LINTIN0.