【问题标题】:Questions about APIC interrupt关于APIC中断的问题
【发布时间】:2019-08-06 09:57:29
【问题描述】:

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

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

目前我对APIC的理解如下:

1、每个内核都内置Local APIC,主板上内置I/O APIC

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

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

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

5,I/O APIC有24个pin,pin 1连接到键盘

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

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)找到其他中断控制寄存器(OIC),并设置OIC[8]=1b使能I/O APIC

现在我将提出我的问题:

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

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

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() 函数将 0 写入 MSR 地址 0x80b(EOI)

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

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
}

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

【问题讨论】:

    标签: assembly operating-system x86-64 apic


    【解决方案1】:

    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 条目。
    至强 5000 系列基于 Nehalem,它是现代 CPU 的祖先,可以追溯到 2008 年。

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

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

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

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


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

    IOAPIC 通过 ACPI 表报告给操作系统,具体而言,ACPI specification5.2.12 多 APIC 描述表 (MADT) 部分包含 IO APIC 的 MMIO。
    或者,如果存在,可以使用Intel MP Table

    软件无需了解硬件即可访问 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 不同,也是 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 表或 MP 表(如果两者都不存在,则系统不是 SMP 并且不需要 IO APIC)。


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

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

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

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

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

    【讨论】:

    • 您好,非常感谢。我是初学者,所以没有知识理解你所有的答案。那么大多数操作系统程序员为启用 I/O APIC 所做的事情是使用 ACPI 表,这意味着我应该在整个 ACPI 表中启用 I/O APIC?在虚拟机中会不会一样?我应该如何检测它??
    • 顺便说一句,如果可以的话,请问一下启用I/O APIC的例程吗??
    • @Shore 启用 IO APIC 只是对相关 RTE 进行编程的问题。现代系统不支持 PIC 模式,有一个双 8259A PIC,但它总是转到 IOAPIC 的 IRQ0(LAPIC 集成在 CPU 中,即使在禁用时也使用 LINT0/1 引脚)。因此,只需在 PIC 级别屏蔽 IRQ 并对 IO APIC RTE 进行编程。这是因为前 15 个 IRQ 与 PIC 相同,除非有 Override,下一个 IRQ-16-23 映射到 PCI 链路 A-H。 MADT 表包含详细信息:IO APIC 基础、覆盖、需要使用旧端口...
    • ... 22h/23h 禁用 PIC(在 MP 规范中描述,它是 IMCR 寄存器)。启用 IO APIC 并不是难事,获得 IRQ 拓扑才是难事。事实上,IRQ 行实际上已被弃用,取而代之的是 MSI。如果我有时间看看是否可以发布示例,解析 ACPI 表是一个冗长的代码样板。但是如果你是初学者,你一定要了解如何唤醒其他 CPU 以及如何路由 IRQ,否则这一切都将毫无意义。请问您为什么要启用 IO APIC?
    • 嗨,我在 bochs 和 Virtualbox 上测试了它们,它们都有效!!!所以我真的不需要“启用”I/O APIC,一旦我设置了 RTE,它就准备好了。非常感谢!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多