关于APIC中断的问题

Questions about APIC interrupt

我正在按照一本书写一个linux类似的内核,但是,APIC章节遇到了问题。

首先,我将列出我的平台。我在 Windows 10,使用 Virtual Box 到 运行 Ubuntu 18.04,并 运行 在其中的 bochs 上测试代码。

目前我对APIC的理解如下:

1,每个核心上都有本地 APIC,主板上有 I/O APIC

2、可以使用内存映射或MSR引用访问本地APIC

3, I/O APIC由IOREGSEL, IOWIN, EOI这3个寄存器访问。基本思路是给IOREGSEL设置值,用IOWIN访问对应的寄存器。

4、有3种模式,感兴趣的是SymmetricI/O模式

5, I/O APIC 有 24 个引脚,引脚 1 链接到键盘

6、要启用APIC和I/OAPIC,还有一系列的工作要做:

a) 屏蔽8529A中断

b) 启用 xAPIC 和 2xAPIC,以便可以访问 MSR

c) 屏蔽所有 LVT(如果不需要本地中断)

d) 为 I/O APIC

设置 RTE 条目

e) 设置IMCR寄存器为0x01h,强制8529A中断传递信号给I/O APIC

f) 通过Root Complex Base Address Register(RCBA)找到Other Interrupt Control Register(OIC),设置OIC[8]=1b使能I/O APIC

现在我将提出我的问题:

1、在bochs和Virtual Box上,检测到Max LVT Entry number为6(根据Manual,有6+1=7个LVT entry),LVT_CMCI entry不能已访问(gp 故障)。

2、据说主板上不同的芯片会把RCBA映射到不同的端口,得去查手册了。但是有没有办法通过软件自己来检测,不然商业OS怎么适配不同的平台。

3、我在虚拟机上,如何检测RCBA的可访问性

感谢任何能为我的问题提供线索或帮助我进一步了解本章的人。


我将展示一些关于为简单的键盘中断设置 APIC 的代码。

首先是中断处理函数

void IRQ0x21_interrupt(Int_Info_No_Err STK)
{
    Ent_Int;
    color_printk(RED,BLACK,"do_IRQ: 0x21\t");

    unsigned char x;
    x = io_in8(0x60);
    color_printk(RED,BLACK,"key code:%#08x\n",x);
    wrmsr(0x80b, 0UL);
    //io_out8(0x20,0x20);
    Ret_Int;
}

Ret_Int&Ent_Int是定义处理中断栈的宏,wrmsr()函数write 0 to MSR address 0x80b(EOI)

接下来是 LAPIC 和 I/O APIC 的设置函数,假设物理地址 0xFEC00000 已经映射到页面 table

void APIC_init(void)
{
    int i;
    int virtual_index_address;
    int virtual_data_address;
    int virtual_EOI_address;
    unsigned long tmp;

    //Set interrupt, note No.33 link to IRQ0x21_interrupt() function
    for(i = 32;i < 56;i++)
    {
        _Set_INT(IDT_PTR.Offset + i, ATTR_INTR_GATE, 2, interrupt[i - 32]);
    }
    //Mask 8529A
    io_out8(0x21,0xff);
    io_out8(0xa1,0xff);
    //enable IMCR
    io_out8(0x22,0x70);
    io_out8(0x23,0x01);

    #pragma region Init_LAPIC
    //Enabling xAPIC(IA32_APIC_BASE[10]) and 2xAPIC(IA32_APIC_BASE[11])
    tmp = rdmsr(0x1b);
    tmp |= ((1UL << 10) | (1UL << 11));
    wrmsr(0x1b,tmp);
    //Enabling LAPIC(SVR[8])
    tmp = rdmsr(0x80f);
    tmp |= (1UL << 8); //No support for EOI broadcast, no need to set bit SVR[12]
    wrmsr(0x80f,tmp);
    //Mask all LVT
    tmp = 0x10000;
    //wrmsr(0x82F, tmp); Virtual machine do not support
    wrmsr(0x832, tmp);
    wrmsr(0x833, tmp);
    wrmsr(0x834, tmp);
    wrmsr(0x835, tmp);
    wrmsr(0x836, tmp);
    wrmsr(0x837, tmp);
    #pragma endregion

    #pragma region Init_IOAPIC
    virtual_index_address = (unsigned char*)(0xFEC00000 + PAGE_OFFSET);
    virtual_data_address = (unsigned int*)(0xFEC00000 + PAGE_OFFSET + 0x10);
    virtual_EOI_address = (unsigned int*)(0xFEC00000 + PAGE_OFFSET + 0x40);
    //Setting RTEs, mask all but 0x01 RTE table for keyboard
    for(i = 0x10;i < 0x40;i += 2){
        *virtual_index_address = i;
        io_mfence;
        *IOAPIC_MAP.virtual_data_address = 0x10020 + ((i - 0x10) >> 1) & 0xffffffff;
        io_mfence;
        *IOAPIC_MAP.virtual_index_address = i + 1;
        io_mfence;
        *IOAPIC_MAP.virtual_data_address = ((0x10020 + ((i - 0x10) >> 1)) >> 32) & 0xffffffff;
        io_mfence;
    }

    *virtual_index_address = 0x12;
    io_mfence;
    *IOAPIC_MAP.virtual_data_address = 0x10020 + (2 >> 1) & 0xffffffff;
    io_mfence;
    *IOAPIC_MAP.virtual_index_address = i + 1;
    io_mfence;
    *IOAPIC_MAP.virtual_data_address = ((0x10020 + (2 >> 1)) >> 32) & 0xffffffff;
    io_mfence;
    #pragma endregion
}

所以根据答案,I/O APIC 设置为在我完成 RTE 初始化后打开。如果有人能告诉我上面的代码是否有效(对于一个简单的键盘中断)。非常感谢。

1, On both bochs and Virtual Box, the Max LVT Entry number is detected as 6 (according to Manual, there are 6+1=7 LVT entries), and the LVT_CMCI entry can not be accessed(gp fault).

英特尔在其软件开发人员手册(第 10.5.1 节)中记录了七个 LVT 条目,但这是硬件的当前状态。

The LVT performance counter register and its associated interrupt were introduced in the P6 processors and are also present in the Pentium 4 and Intel Xeon processors.
The LVT thermal monitor register and its associated interrupt were introduced in the Pentium 4 and Intel Xeon processors.
The LVT CMCI register and its associated interrupt were introduced in the Intel Xeon 5500 processors.

如果您认为 P6 和 Pentium 4 处理器已过时,您始终可以假设至少有六个 LVT 条目。
Xeon 5000 系列基于 Nehalem,它是现代 CPU 的始祖,可以追溯到 2008 年。

在 x2APIC 模式下访问无效的 LAPIC 寄存器(即 MSR 访问)会生成 #GP,因为访问不存在的 MSR 会产生这种情况。
使用旧版接口并留在 LAPIC 回收区域内(直到偏移量 0x3f0)将设置 LAPIC ESR 寄存器中的位 7。

Boch 不处理 LVT_CMCI 寄存器,字面意思是 no support for it in the source code.
该回购协议可能与当前源不同步,但我构建的 bochs(相当新)仍然不支持它。
针对寄存器偏移的开关出现在 2007 年,在 Xeon 5500 之前,所以要么作者忘记更新它,要么认为它不值得支持 MCE。

我没有检查过 VirtualBox,但考虑到 MCE 和更通用的 MCA 机制非常复杂,可能不支持它。

简单地说,LVT_CMCI是可选的。您可以使用普通 MMIO 接口和 ESR 寄存器检查它的存在。


2, It is said different chips on motherboard will map RCBA to different port, and I would have to look it up through manuals. But would there be a way to detect it by software itself, otherwise how did the commercial OS fit different platform.

IOAPIC 通过 ACPI tables 报告给 OS,特别是第 5.2.12 节 Multiple APIC Description Table (MADT)[=89= ACPI specification 的 ] 包含 IO APIC 的 MMIO。
或者,如果存在,可以使用 Intel MP Table

软件无需了解硬件即可访问 IO APIC。事实上,RCBA 的东西在硬件层面是相当不一致的。

在当前的x86系统中,PCH(Platform Controller Hub)中总是有一个IO APIC,并且在一些多路服务器CPU(E5和E7系列以及Xeon 5500有)的uncore中也有一个IO APIC它 - Xeon Scalable could/should 但没有详细的数据表)。
最后,可以通过其他方式提供 IO APIC,例如 PCI 集线器(例如 Intel PXH)。

当时用于 Ivy Bridge 处理器(2012 年左右)的 PCH Series 7 中的 IO APIC 遵循 RCBA 模式:

OIC 位于 RCBA 中的偏移量 0x31FE,而 RCBA 位于 PCI-to-LPC 桥(设备 1f.0)的 PCI 配置 space 中的偏移量 0xF0。
RCBA和LPC接口之间没有特别的link,显然Intel出于内部原因使用了这个设备。
由于这些都已记录在案,因此 OS 可以获得 RCBA 和 OIC 地址;授予它识别芯片组。
系列8(Haswell)也是如此。

从PCH的100系列开始(与Skylake耦合)PCH中的IO APIC由P2SB(Primary to Sideband)控制器控制,这是设备1f.1(有效至C620系列,撰写本文时的最后一个)。
P2SB 可以通过写入 PCI 配置 space 中的 0xE0 寄存器的位 8 从软件中隐藏,这使得所有 PCI 配置读取 return。
仍然接受至少写入 0xE0 的写入;事实上,我的系统中已经 "de-hidden" P2SB 并检查了它的配置。
在其 PCI 配置中注册 0x64 space 就像 OIC 寄存器一样工作(尽管它称为 IOAC)。

服务器端,一些(大多数?)英特尔处理器在非核心中集成了一个 IO APIC。
这显示为 PCI 设备(与客户端 APIC 不同,也是 there's a PCI class for IOAPIC)。
它可以使用标准的 PCI BAR 机制(寄存器名为 MBAR),因此它可以映射到 4GiB 的任何地方,而不仅仅是 0xFECx xxxx.
它也有一个ABAR寄存器,作用类似于IOAC寄存器。
这种模式似乎适用于所有显示为 PCI 设备的 IO APIC(例如 PXH 集线器中的那些)。

在服务器中,PCH也有一个IO APIC,但是,需要更多配置才能让系统将请求正确路由到DMI后面的IO APIC。

所有这些细节都是为 BIOS 程序员而不是 OS 程序员揭示的,可靠的方法是使用 ACPI table 或 MP table(如果两者都不存在系统不是 SMP 并且不需要 IO APIC)。


3, Since I'm on virtual machine, how could I detect the accessibility of RCBA

这在第 2 点的答案中部分或全部解决了(即没有 RCBA 或它在 PCI-to-LPC 配置 space 的 0xf0)。
如果您使用的是 VirtualBox,则可以 select PIIX3 或 ICH9 芯片组。

对于 PIIX3,没有 RCBA(太旧)并且 APIC 基础具有 FEC0_xy00h 的形式,其中 xy 可以在设备 space 的配置 space 的地址 0x80 处进行配置 00.0.
我只浏览了数据表,但我发现 IO APIC 是一个外部组件,该设置决定了何时断言 IO APIC 特定引脚。

对于 ICH9,RCBA 位于 PCI-to-LPC 桥中。因此,在 Linux 下阅读它的一种简单方法是 sudo setpci -s 1f.0 F0.D(但要检查语法)。

请注意,这两个组件都来自 pre-PCH 时代。