BIOS如何初始化DRAM?

How Does BIOS initialize DRAM?

一段时间以来,我一直在到处寻找有关 BIOS 究竟如何工作的解释。我设计了一个bootloader,并用它跳到32位模式,同时成功初始化了IDT和GDT,但这样做,我发现“操作系统”似乎很简单,感觉就像“ BIOS”是每台电脑的实际操作系统。

所以现在我接受了一个新的挑战,试图发现 BIOS 实际上是如何自我初始化的,发现有多少 RAM 是可用的,以及 how/where 附加卡 ROM 是如何导入到 RAM 中的。据我了解,处理器不是通过跳转,而是自动开始在 RAM 中 16 位 segment:offset 地址 0xFFFF:0x0000 处执行代码。这意味着,由于处理器的起始位置,从技术上讲,所有计算机最初都必须至少具有 1MB 的 RAM,以便它们能够启动,并且由于这些知识,我一直假设所有 BIOS 在启动之前自动将自己写入 RAM处理器获取其 RST 信号。我觉得这不是真的,因为我相信这正是可以通过 BIOS 禁用的“Shadow BIOS”。我一直在到处寻找“BIOS 设计者指南”,然而,我总是空手而归,似乎阅读了每一个规范。

作为一名程序员,我知道可能有很多方法可以实际完成我实际要求的事情,并且可能根本没有办法给出一个像样的直截了当的答案,如果我必须更具体,假设我正在使用 Dell Inspiron 518,或者至少是一台包含 G33 芯片组(G33 北桥和 ICH9 南桥)的计算机,我想编写初始 Pre-POST 程序,并构建我自己的 16 -bit IDT,具有所有标准中断和可能成功启动另一个操作系统(例如 Windows 10)所需的一切。 BIOS实际上如何知道有多少RAM?它是否只是在最高内存区域进行位写入位读取测试并从那里下降?附加卡 ROM 如何加载到 RAM 中?据我了解,BIOS 构建了一个非常基本的中断列表 and/or 附加卡 ROM 可以利用的“入口点”,并使它们能够“锁存”到其他 BIOS 的中断,例如“$PMM”? BIOS 制造商如何知道他们的 BIOS 中需要哪些确切的锚字符串才能启动像 Windows 这样的操作系统?

任何答案以及任何推荐的规格都会非常有帮助 and/or任何能够引导我了解我一直在寻找的知识的指南。例如,可能有一个指南说“在移交给 IPL 之前 BIOS 需要完成的最低要求流程?”甚至是 C 或汇编语言的源代码示例,其中包含可以向我展示附加卡的 ROM 映像实际是什么或看起来像什么的东西,这将非常有帮助。

简答...

BIOS 目前是一个被错误使用的术语。但是当你从 AMI 等购买 BIOS 时,它会启动 chip/system 并通过软件中断提供传统的基本 input/output 服务。

它是用高级语言编写的,因此需要使用堆栈和 ram 进行编译,因此芯片上有一些 sram 用于完成启动过程。代码本身存在于主板上的闪存中。 运行是像 mcu 一样直接从闪存中获取还是以某种方式复制到 ram,我不知道。

Dram 模块包含一个带有 SPD 数据(JEDEC 标准)的 eeprom,除其他外,它告诉引导加载程序有多少 dram。这就是引导加载程序 (BIOS) 知道有多少内存的方式。

就 UEFI,甚至 BIOS 而言,BIOS 供应商、主板供应商、操作系统供应商,推动这款非常独特的 PC 符合他们的最大利益兼容性标准,无论是否正式。 UEFI 改变的东西最初是由英特尔 (EFI) 驱动的,但后来进入了一个社区。我认为遗留 BIOS 模式可能是 Microsoft 和英特尔对 bios/motherboard 人的 maintained/enforced,因为如果他们激怒了 those 家公司。

TL;DR

了解到某块主板与上面的BIOS有着密切的关系。当您开发主板时,肯定会在 PC 世界或特定的 Intel chip/socket 世界或 AMD 中有很多共性。但是您仍然有理由制作另一块主板。由于历史和各种原因,BIOS 供应商的数量非常少,如果你想有一个很好的成功机会,你只需打电话给一个,支付你需要支付的费用,并获得一个 BIOS。这不像我买了一台电脑,没有和其他人讨论就把它 ose 改成 运行 windows 或 bsd 或 Linux 或其他。如果有成功的希望,processor/chip 供应商(英特尔或 AMD)、bios 供应商(ami、insyde、phoenix 等)和主板制造商您之间存在三向关系。主板供应商相对较少也是有原因的。

BIOS 的作用和提供的内容也有一段历史,我不一定要在这里详述。

处理器需要以某种形式从非易失性媒体启动。主板上的闪光灯。因此,从启动的角度来看,想想你从闪存中获得代码 运行ning 的微控制器。现在这并不意味着你必须 运行 完全从它那里它可能是你 运行 一个小循环将它复制到某个固定的 sram 某处,或者它可能是硬件读取闪存给你成ram。我不是最新的关于当前的英特尔和 AMD 处理器在从复位和芯片资源启动方面的工作方式(刚刚支付给 bios 人员并遵循参考设计并使用来自的字节对闪存进行编程bios 伙计们,它启动了)。

DRAM/DDR 是一团糟,可能需要数周到数月才能得到它 运行ning 正确,这不一定是一项微不足道的任务(新 IP 等,existing/known ip和匹配的电路板布局设计,可能需要数小时或数天)。无论哪种方式,由于 cost 和历史,我们习惯于将 dram 插入模块,因此您如何知道那里有什么,也许这就是您的问题。如果您在 Wikipedia 上搜索 serial presence detect,您将看到 JEDEC 规范,了解模块向 controller/host 提供的信息。在已知总线(我相信是 i2c)上的模块上有一个 eeprom 或等效物,其中包含该模块的 SPD 信息。从这些信息中,您不仅可以发现内存量,还可以发现使 dram 为该模块上的特定 dram 芯片(和 DRAM tech/generation DDR2、DDR3、DDR3L 等)工作所需的几个时序设置。 Rank/bank、宽度等。它还将包括一到几个 possible 速度。

host 端的软件,我们将其归入术语 BIOS(想想引导加载程序),它对主板和处理器或有效处理器有深入的了解工作。以及了解 dram 控制器的功能,并与 dram 模块宣传的 possible 组合之一相匹配。因此,例如模块可能支持 2133,但是 host 控制器最多只能支持 1666,前提是模块支持该速度或它将尝试的速度。

自然地,BIOS 不是一些不需要 ram 并且仅依赖于通用 purpose 寄存器的手工编码程序集。所以是的,某处必须有一些 SRAM,我不知道这个级别的这些英特尔芯片(再次非常非常少的人以这种方式使用这些芯片),如果你看看 Linux 有能力的 ARM 芯片有ose 芯片上的一些 sram。我熟悉的一个(非 x86)芯片,dram 缓存可以与其他一些片上 sram 一起用作这种工作的直接访问,因此可以使用这些片上 sram(用于堆栈和数据)启动芯片, 运行 来自 flash 的代码或者 运行 来自 depending 的代码),然后当 dram 启动时,缓存被重新配置为缓存并且 dram 现在可用。然后引导加载程序继续完成其工作,然后查找包含操作系统的介质并加载并启动它。

IMO 越来越容易制造 AMD 主板,自从看到 Intel 亲自出现以来已经有一段时间了。他们都将更多以前的多芯片解决方案整合到一个 chip/multi 芯片模块中。如果您根据从事主板业务的历史,就会发现很多黑魔法。人们会期望 Intel 或 AMD 拥有理解这一点所需的详细信息,但这是公开的吗(有多少人在制造主板,有多少人在编写 BIOSes,人们有多少人愿意为此买单?支持合同,有多少人愿意购买开发 boards/reference 设计)。无论哪种方式,如果有公开可用的信息,您都需要从英特尔或 AMD 开始。预计一部分答案是通用的 across 这些产品的几代以及一些答案可能特定于某一特定产品。

所以:

有一个闪存用于存放引导加载程序,电路板设计符合处理器的引导要求,可以在处理器引导之前将闪存内容放出。确实需要一些片上 sram 来协助引导过程,是的。您如何根据对 dram 芯片和控制器的了解来确定多少 dram spec/parameters。对于我们习惯的插入式模块,有一个小的 eeprom 或类似的东西,其中包含模块上 module/chips 的 SPD 数据,以便引导加载程序不仅知道总共有多少 ram,而且还知道需要的许多时序参数正确地与那个 dram 交谈。

那里有一些支持os的开源 BIOSes,如果我没记错的话,它们有点过时,并且可能仅限于它们支持的主板,它们可能只是一个实际的 BIOS 实现 BIOS 调用,而不是一个用于启动主板的完整的 x86 引导加载程序。通常 BIOS/bootloader 是您刚从 AMI 或 insyde 等处购买的东西,您要设计的处理器 chos 可能会决定选择哪个 bios 供应商或哪些供应商 os来自。 ami bios 或其他的源代码价格高昂,而且法律协议很长。可能包括某种形式的“如果您 lose/leak 您同意让代码耗尽您的银行帐户以清理漏洞”。我怀疑闪存是可读的,你可以尝试对它进行逆向工程,但我也怀疑它是编译代码而不是手写的 asm,所以它不会那么容易阅读。最好只了解流程并就此打住。

如果你真的想体验从 x86 开始的这种转变并使用基于 ARM 的,因为有开源引导加载程序,在某种程度上关于逻辑的文档(dram 控制器和 pcie 等是从第三方购买的 IP参与保密协议,因此只有部分外围设备的细节在 TI 或 Broadcom 或 Allwinner 等芯片中)。但至少要开源 Linux 的初始代码和驱动程序,这比拆解一些东西要好。你可以选择一个黑色的比格犬骨头或一个 raspberry pi(错误的例子 dram 是在 gpu 中完成的,尽管我认为它现在已经开放到某种非零程度)或无数基于 Allwinner 的板。

在高层次上,体验和过程是相同的,引导、初始化东西、加载 os、启动 os。 dram init、pcie init、usb init、以太网,在正确的时间发生,以便引导和启动。技术相同(ddr2、ddr3、ddr4、pcie gen 1、2、3、usb 1、2、3 等),在某些情况下购买的 IP 相同或相似等。


是的 BIOS 表示基本 I/O 服务,历史上是一堆基于软件中断的处理程序,以通用方式执行与视频通话或与硬盘通话等操作因此,当您购买视频卡时,它实际上有一个 ROM,其中包含该视频卡的视频 bios,有一个将 bios 链接到系统的过程,以便当您调用 those int 系统调用之一时,它会使用该闪存上的代码。硬盘控制器等也是如此。读取一个扇区的想法不需要您了解具有这些高级系统调用的软盘驱动器控制器或硬盘控制器的详细信息。

术语 BIOS 和 CMOS 有点过载,不仅包括 BIOS 的内容(可能还包括引导加载程序,原始源代码 is/was 可以在原始的 Intel pc 手册中找到,我仍然有一个放在我原来的 pc 的某个地方(遗憾的是我没有了)。今天,操作系统依赖于 bios 或 efi 来获得这种通用的方法,我不需要知道方法,但是一旦 OS 是 运行ning,它就会加载控制器特定的驱动程序,而不是不再需要进入 BIOS 系统调用。这是基于 PC 历史的非常 PC 的东西,非 PC 采取更多的传统方法。

所以是的,BIOS is/was 一些处理软件中断(系统调用)的代码,但我们也错误或正确地应用了该术语以包括引导加载程序。当您从 AMI 等购买“BIOS”时,它会启动芯片。

我将这个答案限制在英特尔架构上,因为我最熟悉它们。


你(还有我)正在寻找的文件叫做 BIOS 编写指南,不幸的是,它是机密的,到目前为止还没有泄露 (AFAIK)。

为了在开源社区推广他们的产品,英特尔发布了Firmware Support Package。这被认为类似于固件编写器的库,包含(二进制)代码来初始化内存控制器、PCH(外设控制器集线器,非正式地称为“芯片组”)和 CPU1.
开源开发人员,或者一般来说,任何无力与英特尔签署 NDA 的开发人员,都可以使用 FSP 编写自己的固件。

可以反转 FSP(我的许多 TODO 之一),但将其用作参考会更快。

当电源打开时,在 CPU 从复位向量开始执行之前会发生很多事情 2 但重要的是要记住芯片组(即 PCH)已经允许 CPU 访问闪存 ROM。
事实上,这就是第一条指令的执行方式,因为 CPU 只能从内存地址 space.

中获取指令

因此只要固件将执行流程保持在映射到闪存 ROM 的内存区域内(该区域由闪存 ROM 本身中存在的闪存描述确定,PCH 在其重置和配置期间读取它相应的内存请求路由),其代码可以执行。

由于内存尚未初始化且闪存 ROM 是只读的(w.r.t。内存写入周期)无法使用这些功能:

  • 来电。因为他们需要一个可写的堆栈。
  • 内存中的变量。因为它们各不相同。

两者都是烦人的地方,在汇编中你可以使用跳转和寄存器来解决它们,但在 C 中你不能。
所以固件做的第一件事通常是设置一个“临时RAM”。
这是 FSP 的 TempRamInit() 例程(顺便说一句,必须通过跳转调用)并且在实践中,它设置了 Cache-as-RAM (CAR)。

作为 RAM 缓存

想法是将缓存用作临时 RAM。
基本点是缓存行不会过期,只有当没有更多space 来自内存的新请求行时它们才会被驱逐。
所以只要你足够小心,避免访问更多可以放入缓存的变量,CPU只会从缓存中读取和写入(当然,这需要Write-back缓存模式)。

但是,这需要仔细定位变量,而且它确实非常脆弱。
更好的方法是启用缓存(通过清除 CR0 寄存器中的 CD(缓存禁用)位),然后从与缓存一样大的内存区域进行虚拟读取(甚至写入) L13.
然后你再次禁用缓存,这种模式实际上被称为 no-fill 模式,其中没有新行被带入缓存(因此没有现有行可以“丢失”)但读取并且写入仍然可以命中缓存。

这允许几 KiB 的“RAM”。
存在用于 CAR 环境的 C 编译器。

正在初始化 RAM

现在固件可以初始化 RAM,为此必须完成三件事:

  1. 告诉内存控制器 DIMM 时序(CAS,RAS)。
  2. 告诉内存控制器有关 DIMM 的大小和等级。
  3. 设置路由。

内存控制器通过 PCI 配置 space 和 MMIO 配置,您可以在处理器数据表第 2 卷中找到详细信息(假设 MC 在 CPU 芯片中)。
例如,8th and 9th generation core datasheet vol 2 包含内存控制器寄存器的描述。这是固件可以设置 tRAS 参数的摘录:

类似地,您会找到有关 DIMM 大小和类型、通道大小等的寄存器:

这些寄存器涵盖了第 1 点和第 2 点(以及一点第 3 点,具体取决于定义)但是固件如何知道要使用哪些值?
毕竟内存条是可以更换的

如前所述,解决方案是 Serial Presence Detect (SPD),一个集成在 DIMM 本身上的小型 EEPROM,用于描述内存时序、拓扑和大小。

使用 I2C 兼容总线访问 EEPROM。
在Intel架构中,实际使用的总线是SMBus(System Management Bus),它是兼容I2C的,并且是恰当创建的。
SMBus 主机位于 PCH 中,并记录在相关系列的数据表第 2 卷中。
例如 PCH series 200 datasheet vol 2.

SMBus master在使用前必须进行配置,但是非常简单。一旦配置好,它就可以用来读取 SPD 数据。
这与访问任何其他 I2C 设备完全一样。
SPD EEPROM(可以有多个,当然,每个 DIMM 一个)保留地址从 0x50 到 0x57(在 200 PCH 系列上)。
可以写入 SPD 并且在 SMBus 主机中存在禁用此类行为的位:

读取SPD数据后,就可以配置MC,然后就可以使用RAM了。

这是 FSP 的 FspMemoryInit() 例程。

最后一步是配置路由。
这包括在内存地址 space 中设置 RAM 区域的末尾(完整图片请参阅 PCH 数据表),以及在 NUMA 系统中,源地址和目标地址解码器用于跨套接字路由内存请求通过 QPI/UPI 个链接。
所有这些都是通过PCH中集成设备的PCI配置space完成的。

在 NUMA 系统中,有必要启动其他应用程序处理器(每个插槽一个)来配置它们的内存控制器。
这是通过 LAPIC 发出的处理器间中断 (IPI) 完成的,LAPIC 是每个 CPU.

中的一个 MMIO 组件

总结

固件执行的粗略步骤是:

  1. 执行任何基本环境初始化(例如切换到 32 位模式)。
  2. 初始化缓存作为 RAM。
  3. 使用 PCI 枚举在 PCH 中初始化 SMBus 主机。
  4. 读取每个DIMM的SPD EEPROM。
  5. 使用 SPD 数据配置每个插槽的内存控制器。
  6. 配置 PCH 内存映射。
  7. 配置 NUMA 路由。

1 CPU不需要初始化,实际上很多代码在调用FSP初始化例程时已经执行完毕。它们可能意味着对某些或多或少记录在案的功能进行“微调”。

2 它们不会在这里讨论,但是,简要地说,一旦启动(使用其集成的ROM) 其固件将使用 GPIO 打开电路板必要的电源门。其中一个门为 PCH 供电,一旦 EC 固件断言正确的引脚,它将启动自己的固件(称为管理引擎固件,因为它与 ME 代码的其余部分捆绑在一起,位于同一闪存的 ME 区域内ROM 也包含 BIOS 代码,但从技术上讲它是 Bring-Up、BUP、模块)并重置芯片组。一旦芯片组准备就绪,它将声明 CPU 的电源良好引脚,然后是 reset/init 引脚,这将导致 CPU 开始执行 POST然后,假设一个 TXT 支持 CPU,从闪存 ROM 和 SINIT ACM(系统初始化验证控制模块,它将设置必要的安全性)获取固件接口 Table 的微码measured launch)和可选的 BIOS ACM(它将执行特定于供应商的任务,可能包括引导、跳过旧版重置向量)。最终,BIOS ACM(如果在 FIT 中没有找到 BIOS ACM,则为微代码)将跳转到重置向量。这是传统的引导流程。请注意,ACM 在采用缓存作为 RAM(见上文)的特制环境中执行,遵循任何其他 TXT 启动的语义(请参阅英特尔 TXT 规范)。

3 根据 Intel 的说法,当设置 CD 时,不会进行行替换。我认为这也不会在更高的缓存中来回移动行。