【问题标题】:How Does BIOS initialize DRAM?BIOS 如何初始化 DRAM?
【发布时间】:2020-11-19 09:38:09
【问题描述】:

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

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

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

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

【问题讨论】:

  • 好像“BIOS”是每台计算机的实际操作系统。 - 仅当您编写一个只使用 BIOS 调用的玩具内核,而不是真正的实际驱动程序时计算机真正拥有的硬件。特别是如果它更像是一个独立的程序,而不是一个具有进程管理的完整操作系统。确实,BIOS 确实需要在从磁盘加载 UEFI 或 legacy-BIOS-MBR 引导加载程序之前使用缓存作为 RAM(无填充)模式来初始化 DRAM 时序等超级重要的东西。但是一个合适的操作系统会接管所有的内存,包括 BIOS 正在使用的任何内存; BIOS 只是初始化硬件,不会保持活动状态。
  • 这真的取决于芯片组。在旧机器上,RAM 在启动时确实可用。在更现代的机器上,CPU 实际上以一种特殊的缓存即 RAM 模式启动,其中不使用外部 RAM。此外,现代 CPU 在实模式下并不是真正从 ffff:0000 开始的。 BIOS 对你隐藏了很多这些东西。
  • (当然,在系统管理模式下,SMI 中断仍然可以在真正的操作系统背后做一些事情,因此在 Linux 等操作系统启动后 BIOS 不会“保持运行”并不总是完全正确的.)
  • @fuz 上电或复位后,Intel x86 处理器将处于实模式(参见 Intel @ 987654321@) 并将在 F000:FFF0 处开始执行。 IIRC,80286 CPU 只能通过复位进入保护模式(然后不能再离开它),但所有必要的表等都在复位之前设置好了。
  • @prl 最新的英特尔(服务器?)CPU 开始在处理器内部执行代码,以验证英特尔提供给主板制造商的固件中的特殊代码块。然后执行该特殊代码,形成英特尔可信执行技术的信任根。然后,该特殊代码开始在 CS.base = 0xFFFFFFF0 和 EIP = 0 的实模式下正常执行固件。我不确定它是否记录了 CPU 在此之前运行的模式。

标签: post assembly x86 bios


【解决方案1】:

简答...

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

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

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

对于 UEFI,甚至 BIOS,BIOS 供应商、主板供应商、操作系统供应商,无论是否正式,推动这个非常独特的 PC 兼容性标准都符合他们的最大利益。 UEFI 改变事物最初是由英特尔 (EFI) 驱动的,但后来变成了一个社区。我认为旧版 BIOS 模式可能是由微软和英特尔在 bios/主板人员上维护/强制执行的,因为如果他们激怒了这些公司,他们就不会开展业务。

TL;DR

了解特定主板与其上的 BIOS 之间存在密切关系。当您开发主板时,在个人电脑世界或特定的英特尔芯片/插座世界或 AMD 中肯定有很多共性。但是,您仍然有一个原因要制造另一块主板。由于历史和各种原因,BIOS 供应商的数量非常少,如果您想获得成功的机会,您只需致电一家并支付您需要支付的费用,然后获得一个 BIOS。这不像我买了一台电脑,没有与其他人讨论,我选择在上面运行 windows 或 bsd 或 Linux 或其他。如果有成功的希望,处理器/芯片供应商(Intel 或 AMD)、BIOS 供应商(ami、insyde、phoenix 等)和您的主板创建者具有三向关系。主板供应商相对较少也是有原因的。

BIOS 的功能和提供的功能也有一段历史,我不必在这里赘述。

处理器需要以某种形式从非易失性介质启动。主板上的闪光灯。因此,从引导的角度来看,想想你的代码从闪存运行的微控制器。现在这并不意味着您必须完全从中运行它可能是您运行一个小循环,将其复制到某个固定的 sram 某处,或者可能是硬件为您将闪存读取到 ram 中。我不了解当前的英特尔和 AMD 处理器如何从复位和片上资源启动(刚刚付钱给 bios 人员并遵循参考设计并使用 bios 人员的字节对闪存进行编程,然后它启动了)。

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

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

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

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

所以:

有一个用于保存引导加载程序的闪存,并且电路板设计符合处理器引导要求,以便在处理器引导之前将闪存内容输出。确实需要一些片上 sram 来协助启动过程,是的。您如何根据对 DRAM 芯片和控制器规格/参数的了解来确定多少 DRAM。对于我们习惯使用的插件模块,有一个小的 eeprom 或类似的东西,其中包含模块上模块/芯片的 SPD 数据,这样引导加载程序不仅知道有多少总 ram,而且还知道需要通话的许多时序参数正确地去那个 DRAM。

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

如果您真的想体验从 x86 开始的这种转变并使用基于 ARM 的开源引导加载程序,那么在一定程度上有关逻辑的文档(dram 控制器和 pcie 等都是从某些第三方购买的 IP NDA,因此该外围设备的细节只有一部分在 TI 或 Broadcom 或 Allwinner 等芯片中)。但至少开源了Linux的init代码和驱动,总比反汇编好。你可以选择一个 beagle bone black 或一个 raspberry pi(糟糕的示例 dram 是在 gpu 中完成的,尽管我认为现在它在某种程度上是开放的)或无数基于 Allwinner 的板。

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


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

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

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

【讨论】:

  • 另请注意,服务器通常会有 BMC、板管理计算机(您也可以从 AMI 购买该软件)、基于 arm 的处理器,您可以通过 IPMI 或其他方式使用它来检查运行状况和主板状态、电源关闭/打开、有时重新加载操作系统、从网络启动等。而且我们现在知道并且自然期望在英特尔 x86 芯片中还有其他处理器可以帮助管理芯片。这些处理器中的每一个都有固件和一种引导该固件的方法,该固件部分是特定于它们的(部分是系统特定的)。
  • 整个系统设计包括这些组件,而 x86 本身的引导,沿着通往 dram 和 pcie init 的道路,是这些不同组件结合到系统设计中的一部分,谁先引导谁加载谁,等等。
  • 请注意,如果您的内存已损坏,即不是焊接在可移动的棒上,那么您不一定必须拥有 SPD,这是您选择进行设计的方式,例如您的手机带有 DDR3L 或DDR4L。而且您不一定必须使用需要运行时数学的 JEDEC SPD,您可以根据为构建选择的部件和替代品在系统设计中以自己的方式解决它。或者您可以添加 eeprom 和使用和 SPD 的成本。
【解决方案2】:

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


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

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

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

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

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

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

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

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

缓存为 RAM

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

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

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

初始化内存

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

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

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

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

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

如前所述,解决方案是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() 例程。

最后一步是配置路由。
这包括在内存地址空间中设置 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 代码,但从技术上讲,它是启动、BUP、模块)并重置芯片组。一旦芯片组准备就绪,它将断言 CPU 的电源良好引脚,然后是复位/初始化引脚,这将导致 CPU 开始执行 POST,然后,假设一个支持 TXT 的 CPU,微码获取来自闪存 ROM 的固件接口表和 SINIT ACM(系统初始化认证控制模块,它将设置测量启动所需的安全性)和可选的 BIOS ACM(它将执行供应商-特定任务,可能包括引导、跳过传统重置向量)。最终,BIOS ACM(如果在 FIT 中没有找到 BIOS ACM,则为微代码)将跳转到复位向量。这是传统的引导流程。请注意,ACM 是在采用 Cache-as-RAM(见上文)的特制环境中执行的,遵循任何其他 TXT 启动的语义(请参阅 Intel TXT 规范)。

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

【讨论】:

  • 感谢您提供所有这些信息和基本的简化分步说明!非常,非常翔实!非常感谢!由于您共享的分步信息,我接受了这个作为 Old_Timers 的答案!不过,他也有正道的答案!如果您有任何与“BIOS 开发”主题相关的推荐规范、阅读资料或教程,如果您愿意,我绝对欢迎您分享您可能拥有的任何信息。再次感谢您分享的所有信息!
  • @GodDamn 数据表可能是最有用的文档以及标准。一个好的方法是拆开一台旧 PC,找到每个芯片的数据表,然后使用谷歌查看它们是如何组合在一起的。不幸的是,没有一个文档,OSDev 是一个很好的起点。
  • SKL-SP 处理器的完整内存初始化流程在this 文档的第 5 节中进行了概述。 OP 询问的影子 BIOS 是在初始化后通过将固件从 NVRAM 复制到内存来创建的。我不清楚的一件事是在进入 CAR 模式之前,必须访问内存以在缓存中分配行。尚未初始化的内存控制器如何处理这些访问?也许它只是返回垃圾值。
  • @HadiBrais 我没有证据,但我确信内存控制器会返回垃圾值(可能是“全一”值,例如读取不存在的内存时)。最后,设置CAR时返回什么都无所谓,重要的是把线设置成E或M状态。感谢您的文档,非常有趣!
  • @FryRon 非常感谢,但我认为它有点太旧了。今天几乎没有这种架构。但我很感激这个提议:)
猜你喜欢
  • 2021-01-12
  • 1970-01-01
  • 2012-11-13
  • 2019-05-17
  • 2017-11-02
  • 1970-01-01
  • 2014-01-22
  • 2012-12-18
  • 2018-11-15
相关资源
最近更新 更多