BIOS 如何初始化 DRAM?

God*_*amn 7 x86 assembly post bios

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

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

BIOS 如何真正知道有多少 RAM?它是否只是在最高内存区域进行位写入位读取测试并从那里下降?以及如何将附加卡 ROM 加载到 RAM 中?据我了解,BIOS 构建了一个非常基本的中断和/或“入口点”列表,附加卡 ROM 可以利用这些中断和/或“入口点”,并使它们能够“锁定”其他 BIOS 的中断,例如“$PMM”?BIOS 制造商如何知道他们的 BIOS 中需要哪些确切的锚字符串才能启动像 Windows 这样的操作系统?? BIOS 制造商如何知道他们的 BIOS 中需要哪些确切的锚字符串才能启动像 Windows 这样的操作系统?? BIOS 制造商如何知道他们的 BIOS 中需要哪些确切的锚字符串才能启动像 Windows 这样的操作系统?

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

Mar*_*oom 11

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


您(以及我)正在寻找的文档称为BIOS 编写者指南,不幸的是,它是机密文件,目前尚未泄露 (AFAIK)。

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

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

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

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

由于内存尚未初始化且闪存 ROM 是只读的(wrt 内存写入周期),因此无法使用这些功能:

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

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

缓存即 RAM

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

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

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

初始化内存

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

  1. 告诉内存控制器有关 DIMM 时序的信息(根本就是 CAS、RAS)。
  2. 告诉内存控制器有关 DIMM 大小和排名的信息。
  3. 设置路由。

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

MC 寄存器示例

类似地,您会发现 DIMM 大小和类型、通道大小等的寄存器:

另一个例子 完后还有

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

如前所述,解决方案是串行存在检测 (SPD),这是一种集成在 DIMM 本身上的小型 EEPROM,用于描述内存时序、拓扑结构和大小。

EEPROM 通过 I2C 兼容总线访问。
在 Intel 架构中,实际使用的总线是 SMBus(系统管理总线),它与 I2C 兼容,并且是适当创建的。
SMBus 主机位于 PCH 中,并记录在相关系列的数据表第 2 卷中。
例如PCH 系列 200 数据表第 2 卷

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

SPD 写禁用

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

这是FSP的FspMemoryInit()例程。

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

在 NUMA 系统中,需要引导其他应用处理器(每个插槽一个)来配置它们的内存控制器。
这是通过通过 LAPIC 发出的处理器间中断 (IPI) 完成的,LAPIC 是每个 CPU 中的一个 MMIO 组件。

概括

固件执行的粗略步骤是:

  1. 执行任何基本的环境初始化(例如切换到 32 位模式)。
  2. 初始化 Cache-As-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 的电源良好引脚,然后是复位/初始化引脚,这将导致 CPU 开始执行 POST,然后假设 CPU 支持 TXT,测量启动)和可选的 BIOS ACM(它将执行特定于供应商的任务,可能包括启动、跳过传统重置向量)。最终,BIOS ACM(或微码,如果在 FIT 中未找到 BIOS ACM)将跳转到重置向量。这是传统的引导流程。请注意,ACM 在采用缓存即 RAM(见上文)的特制环境中执行,遵循任何其他 TXT 启动的语义(请参阅英特尔 TXT 规范)。

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


old*_*mer 6

简短的回答...

\n

BIOS 目前是一个被错误滥用的术语。但是,当您从 AMI 等购买 BIOS 时,它会启动芯片/系统,并通过软件中断提供传统的基本输入/输出服务。

\n

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

\n

DRAM 模块包含一个带有 SPD 数据(JEDEC 标准)的 EEPROM,它可以告诉引导加载程序有多少 DRAM。这就是引导加载程序 (BIOS) 知道有多少 RAM 的方式。

\n

就 UEFI 而言,甚至是 BIOS 而言,BIOS 供应商、主板供应商、操作系统供应商,无论正式与否,推动这一非常独特的 PC 兼容性标准都符合他们的最佳利益。UEFI 的改变最初是由 Intel (EFI) 推动的,但后来进入了社区。我认为,传统 BIOS 模式可能是由 Microsoft 和 Intel 在 BIOS/主板人员上维护/强制执行的,因为如果激怒了这些公司,他们就无法开展业务。

\n

长话短说

\n

了解特定主板与其上的 BIOS 之间存在密切关系。当您开发主板时,在 PC 世界或特定的英特尔芯片/插槽世界或 AMD 中肯定存在很多共性。但您仍然有理由制作另一块主板。由于历史和各种原因,BIOS供应商的数量非常非常少,如果你想有很大的成功机会,你只需打电话给一个供应商并支付你需要支付的费用,然后就可以得到一个BIOS。这不像我买了一台电脑,没有与其他人讨论就选择在上面运行 Windows、BSD、Linux 或其他东西。如果有成功的希望,处理器/芯片供应商(Intel 或 AMD)、BIOS 供应商(AMI、Insyde Software、Phoenix Technologies 等)以及您(主板创建者)必须保持三向关系。主板供应商相对较少也是有原因的。

\n

BIOS 的作用和提供的功能也有其历史,我在此不必赘述。

\n

处理器需要以某种形式从非易失性介质启动。主板上有闪光灯。因此,从启动的角度来看,请考虑从闪存运行代码的微控制器。现在,这并不意味着您必须完全从它运行,您可能会运行一个小循环,将其复制到某个固定的 SRAM 中,或者硬件可能会为您将闪存读取到 RAM 中。我不了解当前的 Intel 和 AMD 处理器在从重置启动和芯片资源方面如何工作的最新信息(只是向 BIOS 人员付费并遵循参考设计并使用 BIOS 人员的字节对闪存进行编程,然后它启动)。

\n

DRAM/DDR 是一团糟,可能需要几周到几个月的时间才能让它正常运行,这不一定是一项微不足道的任务(新 IP 等、现有/已知的 IP 和匹配的电路板布局设计,可能需要几个小时或几天)。无论哪种方式,由于成本和历史,我们习惯于将我们的 DRAM 插入模块,因此您如何知道那里有什么,也许这就是您的问题。如果您在 Wikipedia 上搜索串行存在检测,您将看到 JEDEC 规范,了解模块向控制器/主机提供的信息。已知总线上的模块上有一个 EEPROM 或等效器件(我相信是 I\xc2\xb2C),其中包含该模块的 SPD 信息。从这些信息中,您不仅可以发现内存量,还可以发现使 DRAM 为该模块上的特定 DRAM 芯片(以及 DRAM 技术/一代 DDR2、DDR3、DDR3L 等)工作所需的多个时序设置。等级/坡度、宽度等。它还将包括一到几种可能的速度。

\n

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

\n

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

\n

在我看来,自从亲眼目睹英特尔的出现以来,制造AMD主板已经变得越来越容易了。他们都将更多以前的多芯片解决方案整合到一个芯片/多芯片模块中。如果您了解主板行业的历史,就会发现有很多黑魔法。人们会期望 Intel 或 AMD 拥有理解这一点所需的详细信息,但是这些信息是否公开(有多少人在制造主板、有多少人在编写 BIOS、有多少人愿意为支持合同付费、有多少人在愿意购买开发板/参考设计)。不管怎样,如果有公开信息,你需要从英特尔或 AMD 开始。预计一定比例的答案在几代产品中是通用的,并且某些答案可能特定于某一特定产品。

\n

所以:

\n

有一个闪存可容纳引导加载程序,并且电路板设计符合处理器启动要求,可将闪存内容放在处理器启动之前。确实需要一些片上 sram 来协助启动过程,是的。如何根据 DRAM 芯片和控制器规格/参数的知识来确定多少 DRAM。对于我们习惯的插入模块,有一个小的 eeprom 或类似的东西,其中包含模块/模块上芯片的 SPD 数据,以便引导加载程序不仅知道有多少总 RAM,还知道需要通话的许多时序参数正确地连接到该 dram。

\n

据说有一些开源 BIOS,如果我没记错的话,它们有点过时了,并且可能仅限于它们支持的主板(如果有的话),它们可能只是一个实现 BIOS 调用的实际 BIOS,而不是一个完整的 x86 引导加载程序调出主板。通常,BIOS/引导加载程序是您从 AMI 或 Insyde 等购买的东西,您选择设计的处理器可能会决定选择哪个或哪些 BIOS 供应商。AMI BIOS 或其他的源代码价格昂贵,并且需要长期的法律协议。可能包括某种形式的“如果您丢失/泄漏代码,您同意让我们耗尽您的银行帐户以清理泄漏”。我怀疑闪存是可读的,您可以尝试对其进行逆向工程,但我也怀疑它是编译代码而不是手写汇编,因此它不会那么容易阅读。最好只是了解这个过程,然后就这样。

\n

如果您真的想体验从 x86 开始的这一转变,并选择基于 ARM 的架构,因为有开源引导加载程序、一定程度的逻辑文档(DRAM 控制器和 PCIe 等是通过 NDA 从第三方购买的 IP,因此该外设的细节只有一部分位于 TI 或 Broadcom 或 Allwinner 等芯片中)。但至少开源Linux的初始化代码和驱动程序,这比反汇编一些东西要好。您可以选择 BeagleBone Black 或 Raspberry Pi(不好的例子;DRAM 是在 GPU 中完成的,尽管我认为现在在某种程度上是开放的)或无数基于 Allwinner 的主板。

\n

从高层次来看,体验和过程是相同的,引导,初始化东西,加载操作系统,启动操作系统。DRAM 初始化、PCIe 初始化、USB 初始化、以太网在正确的时间发生,以便引导和启动。技术相同(DDR2、DDR3、DDR4、PCIe Gen 1、2、3、USB 1、2、3 等),在某些情况下购买的 IP 相同或相似等。

\n
\n

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

\n

术语 BIOS 和 CMOS 已经被过度使用,不仅包括 BIOS(可能还包括引导加载程序),原始源代码在原始英特尔 PC 手册中可用,我仍然在我的某个地方放着一份手册。原来的电脑(遗憾的是我没有了))。如今,操作系统依靠 BIOS 或 EFI 来获得这种通用的方法,我不需要知道方法,但是一旦操作系统运行,它就会加载控制器特定的驱动程序,而不再需要深入了解 BIOS 系统调用。这是基于 PC 历史的 PC 事物,非 PC 采取更多传统方法。

\n

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

\n