2

I'm following a book to write a linux like kernel, however, met problems with the APIC chapter.

Before everything, I'll list my platform. I'm on Windows 10, using Virtual Box to run Ubuntu 18.04, and run test codes on bochs within it.

Currently my understanding about APIC are as follow:

1, There are built on Local APIC on each core and I/O APIC on motherboard

2, Local APIC can be accessed using memory mapping or MSR referencing

3, I/O APIC are accessed by 3 registers IOREGSEL, IOWIN, EOI. The basic idea is to set the value for IOREGSEL and access the corresponding register with IOWIN.

4, There are 3 mode, the interested one is Symmetric I/O mode

5, I/O APIC have 24 pins, pin 1 is linked to keyboard

6, To enable APIC and I/O APIC, there are serials of works to do:

a) Mask 8529A interrupt

b) Enable xAPIC and 2xAPIC, so that MSR access are possible

c) Mask all LVT (if Local interrupt are not needed)

d) Setting RTE entries for I/O APIC

e) Setting IMCR register to 0x01h, force 8529A interrupt pass signal to I/O APIC

f) Find Other Interrupt Control Register(OIC) through Root Complex Base Address Register(RCBA), and set OIC[8]=1b to enable I/O APIC

Now I'll present my questions:

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).

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.

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

Thanks to anyone who can provide a clue to my questions or helping me understand more about this chapter.


I'll present some of my code on setting up APIC for a simple keyboard interrupt.

First would be interrupt handling function

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 are macros defined to handle the interrupt stack, wrmsr() function write 0 to MSR address 0x80b(EOI)

Next would be the setup function for LAPIC and I/O APIC, assuming that physical address 0xFEC00000 is already mapped in page 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
}

So according to the answers, the I/O APIC is set to open once I complete initialization for RTEs. If any one can be so kind to tell me if the above code would work or not(for a simple keyboard interrupt). Thank you so much.

4

1 回答 1

3

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

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

LVT 性能计数器寄存器及其相关中断是在 P6 处理器中引入的,并且也存在于 Pentium 4 和 Intel Xeon 处理器中。
Pentium 4 和 Intel Xeon 处理器中引入了 LVT 热监控寄存器及其相关中断。
LVT CMCI 寄存器及其相关中断是在 Intel Xeon 5500 处理器中引入的。

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

在 x2APIC 模式下访问无效的 LAPIC 寄存器(即 MSR 访问)会生成 #GP,因为访问不存在的 MSR 会这样做。
使用传统接口并停留在 LAPIC 回收区域内(最多偏移 0x3f0)将设置 LAPIC ESR 寄存器中的 bit7。

Boch 不处理LVT_CMCI寄存器,源代码中实际上不支持它
该回购可能与当前来源不同步,但我的 bochs 构建(相当新)仍然不支持它。
对寄存器偏移的切换早在 2007 年就出现了,在 Xeon 5500 之前,所以作者要么忘记更新它,要么认为它不值得支持 MCE。

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

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


2、据说主板上不同的芯片会将RCBA映射到不同的端口,我得通过说明书查。但是有没有办法通过软件本身来检测它,否则商业操作系统如何适应不同的平台。

IOAPIC 通过 ACPI 表报告给 OS,具体来说,ACPI 规范5.2.12 多 APIC 描述表 (MADT)部分包含 IO APIC 的 MMIO。 或者,如果存在,可以使用 英特尔 MP 表。

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

在当前的 x86 系统中,PCH(平台控制器集线器)中始终有一个 IO APIC,并且在一些多插槽服务器 CPU(E5 和 E7 系列,以及 Xeon 5500 具有它的非内核中也有一个 IO APIC - Xeon Scalable 可以/应该,但没有详细的数据表)。
最后,IO APIC 可以通过其他方式提供,例如在 PCI 集线器中(例如 Intel PXH)。

PCH 系列 7 中的 IO APIC,当时与 Ivy Bridge 处理器一起使用(大约 2012 年)遵循 RCBA 模式:

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

从 PCH 的 100 系列开始(与 Skylake 耦合),PCH 中的 IO APIC 由 P2SB(Primary to Sideband)控制器控制,这是设备1f.1(有效到 C620 系列,撰写本文时的最后一个) .
P2SB 可以通过在 PCI 配置空间中写入 0xE0 寄存器的 bit8 对软件隐藏,这使得所有 PCI 配置读取都返回一个。
写入,至少到 0xE0,仍然被接受;事实上,我已经在我的系统中“取消隐藏”了 P2SB 并检查了它的配置。
PCI 配置空间中的寄存器 0x​​64 的工作方式类似于 OIC 寄存器(尽管它称为 IOAC)。

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

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

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


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

这部分或全部在第 2 点的答案中得到解决(即,要么没有 RCBA,要么它位于 0xf0 的 PCI-to-LPC 配置空间中)。
如果您使用的是 VirtualBox,您可以选择PIIX3或 ICH9 芯片组。

对于 PIIX3,没有 RCBA(太旧),APIC base 的形式可以FEC0_xy00hxydevice 的配置空间的地址 0x80 进行配置00.0
我只浏览了数据表,但我似乎认为 IO APIC 是一个外部组件,并且该设置决定了何时断言 IO APIC 特定引脚。

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

请注意,这两个组件都来自PCH 之前的时代。

于 2019-08-06T14:15:17.347 回答