BIOS如何初始化DRAM?

10
我已经寻找了相当长时间,想要找到一个关于BIOS工作原理的解释。我已经设计了一个引导加载程序,并成功地将其跳转到32位模式,同时成功初始化了IDT和GDT。但是在这样做的过程中,我发现“操作系统”似乎很简单,而且感觉“BIOS”才是每台计算机的实际操作系统。
因此,我现在面临一个新的挑战,即尝试发现BIOS如何初始化自身,如何发现可用的RAM量,以及如何/在哪里将插卡ROM导入RAM。据我了解,处理器不是通过跳转,而是自动开始执行RAM内部的代码,在16位段:偏移地址0xFFFF:0x0000处。这意味着从技术上讲,所有计算机最初都必须至少有1MB的RAM,以便它们能够启动,由于处理器的起始位置,以及由于这种知识,我一直认为所有的BIOS在处理器获得其RST信号之前都会自动将自己写入RAM。我认为这并不正确,因为这正是可以通过BIOS禁用的“Shadow BIOS”。然而,我一直在寻找“BIOS设计指南”,但是无论我阅读什么规格说明,我总是找不到。
作为一名程序员,我理解实际上可能有许多方法来实现我所要求的内容,而且可能根本没有一种好的直接回答的方式。如果我必须更具体地说明,比如说我正在使用Dell Inspiron 518或者至少是一个包含G33芯片组(G33北桥和ICH9南桥)的计算机,并且我想编写初始的Pre-POST程序,并构建自己的16位IDT以及所有标准中断和其他需要的东西,这些东西可以成功地启动其他操作系统,比如Windows 10。那么BIOS如何知道有多少RAM?它只是在最高内存区域进行位写位读测试,然后从那里往下走吗?插卡ROM又是如何被加载到RAM中的呢?据我了解,BIOS会建立一个非常基本的中断列表和/或“入口点”,插卡ROM可以利用这些入口点,并赋予它们能够“锁定”其他BIOS中断(例如“$PMM”)的能力。那么BIOS制造商又是如何知道其BIOS中需要什么确切的锚定字符串才能启动像Windows这样的操作系统呢?
任何答案都将非常有帮助,以及任何推荐的规格说明和/或任何指导我进入我一直在寻找的知识的指南。比如说,“BIOS在交给IPL之前必须完成的最小要求流程的指南”或者一些用C或汇编语言编写的源代码示例,可以向我展示插卡ROM图像实际上是什么样子的,这将非常有帮助。

4
如果你编写的只使用BIOS调用而不是实际驱动程序来操作真正硬件的玩具内核,那么似乎"BIOS"就是每台计算机实际运行的操作系统。特别是如果它更像是一个独立的程序,而不是有进程管理功能的完整操作系统。的确,BIOS确实需要初始化一些超级重要的东西,比如DRAM定时,使用缓存作为RAM(无填充)模式,然后从磁盘加载UEFI或遗留的BIOS-MBR引导程序。但是一个合适的操作系统将接管所有内存,包括BIOS正在使用的任何内存;BIOS只是初始化硬件,不会保持活动状态。 - Peter Cordes
3
这实际上取决于芯片组。在旧机器上,RAM确实在启动时可用。在更现代的机器上,CPU实际上会在特殊的高速缓存作为RAM模式下启动,此时不使用外部RAM。此外,现代CPU实际上并不是在实模式下从ffff:0000开始启动。BIOS将隐藏很多这些信息不让你看到。 - fuz
2
当然,通过系统管理模式,SMI中断仍然可以在真正的操作系统背后执行一些操作,因此并不总是完全正确地认为BIOS在像Linux这样的操作系统启动后不会“保持运行”。 - Peter Cordes
1
在开机或重置时,Intel x86处理器将处于实模式(请参阅Intel 软件开发手册的第3卷“系统编程指南”中的第9章“处理器管理和初始化”),并将从F000:FFF0开始执行。如果我没记错,80286 CPU只能通过重置进入保护模式(然后不能再离开它),但所有必要的表等都将在重置之前设置好。 - 1201ProgramAlarm
1
我曾经发现了AMD手册,它们有每个CPU家族/型号的BIOS和内核开发者指南(BKDG),可以通过CPUID获取,我相信是eax内的第2个叶子节点或其他什么地方。我不知道。老实说,我觉得这个问题有点愚蠢,但它可能在ROM中有一个结构,在PCI中有一个设置,而PCI内部的ROM位默认情况下会被读取并写入RAM启动?谁知道呢?但还是谢谢你的提供! :) - GodDamn
显示剩余11条评论
2个回答

21

我将此回答限制在Intel架构上,因为我对它们比较熟悉。


您(以及我)正在寻找的文档称为《BIOS编写指南》,不幸的是,该文档是机密的,目前尚未泄漏(据我所知)。
为了在开源社区推广其产品,英特尔发布了固件支持包。这可以视为固件编写者的库,其中包含用于初始化内存控制器、PCH(Peripheral Controller Hub,非正式称为“芯片组”)和CPU的(二进制)代码。
任何无法承担与英特尔签署保密协议的开发人员,包括开源开发人员,都可以使用FSP编写自己的固件。
可以反向使用FSP(我要做的许多TODO之一),但将其用作参考更快捷。
在CPU从复位向量2执行之前,会发生很多事情,但重要的是要记住芯片组(即PCH)已经允许CPU访问闪存ROM。
实际上,这就是第一条指令的执行方式,因为CPU只能从内存地址空间中获取指令。
因此,只要固件将执行流保持在映射到闪存ROM的内存区域内(该区域由闪存ROM本身中存在的Flash描述确定,PCH在其重置期间读取它并配置内存请求的路由),其代码就可以执行。
由于内存尚未初始化,且闪存ROM是只读的(相对于内存写入周期),因此无法使用这些功能:
  • 调用。因为它们需要一个可写堆栈。
  • 内存中的变量。因为它们会改变。
这两个都是很烦人的问题,在汇编语言中,您可以使用跳转和寄存器来解决这些问题,但在C中则不行。
因此,固件通常要做的第一件事是设置“临时RAM”。
这是FSP的TempRamInit()例程(顺便说一下,必须使用跳转调用它),实际上设置了Cache-as-RAM(CAR)。

Cache-as-RAM

这个想法是使用缓存作为临时RAM。
基本点是缓存行不会过期,只有当没有空间来存储来自内存的新请求线时才会被替换掉。
因此,只要小心避免访问超过缓存容量的变量,CPU就只会从缓存中读取和写入(当然,这需要Write-back缓存模式)。
然而,这需要仔细地定位变量,而且非常容易出错。更好的方法是启用缓存(通过清除CR0寄存器中的CD(Cache Disable)位),然后从一个与L1^3一样大的内存区域进行虚拟读取(甚至写入)。然后再次禁用缓存。此模式实际上被称为“no-fill mode”,其中不会将新行带入缓存(因此不会丢失现有行),但读取和写入仍然可以命中缓存。这允许几KB的“RAM”。存在适用于CAR环境的C编译器。
现在固件可以初始化RAM,为此必须完成三件事:
告诉内存控制器DIMM定时(所有的CAS、RAS等)。 告诉内存控制器DIMM大小和排名。 设置路由。
内存控制器通过PCI配置空间和MMIO进行配置,您可以在处理器数据手册第2卷中找到具体信息(假设MC在CPU芯片上)。例如,第8代和第9代Core数据手册第2卷中包含了内存控制器寄存器的描述。以下是固件可以设置tRAS参数的摘录:

Example of the MC registers

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

Another example Yet another

这些寄存器涵盖了点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 write disable

一旦读取了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代码的其他部分捆绑在一起,位于相同的闪存ROM的ME区域内,其中还包含BIOS代码,但从技术上讲,它是Bring-Up,BUP,模块)并重置芯片组。一旦芯片组准备就绪,它将断言CPU的电源良好引脚,然后是复位/初始化引脚,这将导致CPU开始执行POST,然后假设TXT能力的CPU,微码将从闪存ROM中获取固件接口表,并从中获取SINIT ACM(系统初始化身份验证控制模块,它将设置所需的安全性以进行“测量启动”)和可选的BIOS ACM(它将执行供应商特定的任务,可能包括引导,跳过传统的重置向量)。最终,BIOS ACM(或如果在FIT中未找到BIOS ACM,则是微码)将跳转到复位向量。这是传统的引导流程。请注意,ACM在使用Cache-as-RAM(参见上文)的特殊环境中执行,遵循任何其他TXT启动的语义(请参阅Intel TXT规格)。

3 根据英特尔,当设置CD时,不进行行替换。我认为这也不会将行移回和移动到更高级别的缓存。


非常感谢您提供的所有信息和基本简化的逐步说明!非常非常有用!非常感谢!我选择了这个答案,而不是Old_Timers,因为您分享了逐步信息!然而,他也找到了正确的答案!如果您有任何关于“BIOS开发”主题的推荐规格、读物或教程,我一定欢迎您分享任何您可能拥有的信息,如果您愿意的话。再次感谢您分享的所有信息! - GodDamn
1
@GodDamn 数据手册可能是最有用的文档之一,连同标准一起。一个好的方法是拆解旧PC并找到每个芯片的数据手册,然后使用Google查看它们如何组合在一起。不幸的是,并没有单一的文档,但OSDev是一个很好的起点。 - Margaret Bloom
1
SKL-SP处理器的完整内存初始化流程在文档的第5节中进行了概述。 OP所询问的影子BIOS是在初始化后通过将固件从NVRAM复制到内存来创建的。对于我来说不清楚的一件事是,在进入CAR模式之前,必须访问内存以分配高速缓存中的行。尚未初始化的内存控制器如何处理这些访问?也许它只会返回垃圾值。 - Hadi Brais
1
@FryRon 非常感谢,但我认为它有点过时了。那种架构今天几乎已经不存在了。但我很感激你的提议 :) - Margaret Bloom
好的!顺便说一句!:DDDDD - user13212784
显示剩余2条评论

6

简短回答:

BIOS是一个被错误使用的术语。当从AMI等处购买BIOS时,它通过软件中断引导芯片/系统,并提供传统的基本输入/输出服务。它是用高级语言编写的,因此需要堆栈和RAM,因此芯片上有一些SRAM用于完成启动过程。代码本身存储在主板上的闪存中。DRAM模块包含EEPROM和SPD数据(JEDEC标准),其中告诉引导程序有多少DRAM。UEFI或者BIOS BIOS厂商、主板厂商、操作系统厂商,最好推动这个非常独特的PC兼容性标准,无论是正式还是非正式。遗留模式的BIOS可能是由微软和英特尔在BIOS/主板方面维护/强制实施的,因为如果他们惹毛了这些公司,他们就没有业务了。

简明概述

要理解特定主板和其上的BIOS之间的紧密关系。当你开发主板时,虽然在PC世界或特定的Intel芯片/插座世界中有很多共同点,但仍有理由制作另一个主板。由于历史和各种原因,BIOS供应商数量非常少,如果你想成功的机会很大,你只需简单地购买需要支付的内容并获取BIOS。这不像我买了一台电脑,而没有与其他人讨论我选择在其上运行Windows、BSD、Linux或其他操作系统。处理器/芯片供应商(Intel或AMD)、BIOS供应商(AMI、Insyde Software、Phoenix Technologies等)和主板创建者之间存在三方关系,如果有成功的希望的话。也有一个原因,为什么市场上相对较少主板厂商。

BIOS做了什么以及提供了什么也有一个历史,我不一定在这里讨论。

处理器需要从某种形式的非易失性介质启动,例如主板上的闪存。因此,在启动方面,可以将其视为微控制器,代码从闪存中运行。现在,这并不意味着您必须完全从中运行,可能是运行复制到某个固定的SRAM位置的小循环,或者可能是硬件为您读取闪存到RAM中。我不熟悉当前英特尔和AMD处理器在重置和芯片资源方面的工作方式(只是支付了BIOS供应商并遵循参考设计,并使用来自BIOS供应商的字节编程了闪存,它就启动了)。

DRAM/DDR有很多问题,可能需要花费几周到几个月的时间才能正确运行。这不是一个微不足道的任务(新知识产权等),同时需要匹配已知的IP和板卡布局设计,或许需要数小时或数天。无论如何,由于成本和历史原因,我们习惯于使用DRAM插件模块,结果你可能会想知道有哪些选择。如果在维基百科中搜索串行预取,则会看到供模块提供给控制器/主机的JEDEC规范信息。模块上有一个EEPROM或等效设备,位于已知总线(我认为是I²C),其中包含该模块的SPD信息,从中可以发现除了内存容量外,还有几个定时设置,以使DRAM适用于该模块上的特定DRAM芯片(以及DRAM技术/世代DDR2,DDR3,DDR3L等)。等级/组,宽度等。它还将包括一到几个可能的速度。

主机端的软件被归类为BIOS(引导加载程序),它具有关于主板和处理器或有效处理器的详细信息。以及了解DRAM控制器的能力,并与DRAM模块广告的可能组合相匹配。例如,模块可能支持2133,但主机控制器可能仅支持高达1666的速度,如果模块支持该速度,则会尝试该组合。

很自然,BIOS不是某些手工编写的程序集,它不需要RAM并且仅依赖于通用寄存器。因此,必须在某个地方有一些SRAM,我不了解这些英特尔芯片的情况(再次强调非常非常非常少的人以这种方式使用这些芯片)。如果您查看Linux可用的ARM芯片,那么这些芯片上会有一些SRAM。我熟悉的一个(非x86)芯片,DRAM缓存可以用作直接访问这种工作的一部分,以及其他一些芯片上的SRAM,因此可以使用这些芯片上的SRAM引导芯片(用于堆栈和数据,从闪存运行代码或也可以运行代码,具体取决于情况),然后DRAM就可用了。引导加载程序随后继续完成其工作,然后查找包含操作系统的介质并将其加载并启动。

我认为情况越来越容易,例如制作AMD主板,个人很久没有看到过Intel。它们都将更多曾经的多芯片解决方案整合到一个芯片/多芯片模块中。有很多基于以往制作主板的黑魔法,可以预计Intel或AMD拥有了解此问题所需的详细信息,但此信息是否公开可用(有多少人在制作主板,有多少人在编写BIOS,有多少人愿意支付支持合同费用,有多少人愿意购买开发板/参考设计)。无论如何,如果有公开可用的信息,则需要从Intel或AMD开始。期望一定比例的答案是适用于这些产品几代的通用答案,而一些答案可能是针对特定产品的。

所以:
有一个闪存用于保存引导加载程序,并且板子的设计使其满足处理器启动的要求,从而使该闪存内容在处理器启动时被提前。的确需要一些片上静态随机存储器(SRAM)来协助启动过程。如何确定需要多少动态随机存储器(DRAM)取决于DRAM芯片和控制器规格/参数的了解。对于我们常用的插件模块,还有一个小型EEPROM或类似存储器,其中包含模块/模块上的芯片的SPD数据,以便引导加载程序不仅知道总共有多少RAM,还知道许多与正确通信所需的时间相关的参数。
据说有一些开源BIOS存在,如果我没记错的话,它们有点过时,可能只限于支持哪些主板,它们可能只是实现BIOS调用的实际BIOS,而不是旨在启动主板的完整x86引导加载程序。通常,BIOS/引导加载程序是您只需从AMI或Insyde等公司购买的东西,您选择设计周围的处理器可能决定选择哪个BIOS供应商或哪些BIOS供应商。 AMI BIOS或其他源代码价格昂贵,而且需要签署长期的法律协议。可能包括某种形式的“如果您失去/泄露代码,您同意让我们清空您的银行账户以清除泄漏”的条款。我怀疑闪存是可读取的,您可以尝试对其进行逆向工程,但我也怀疑它是编译代码而不是手写汇编语言,因此阅读起来不会那么容易。最好只是了解过程并将其保留在那里。
如果您真的想体验这一点,请从x86转向基于ARM的系统,因为有开源的引导加载程序以及某种程度上的逻辑文档(DRAM控制器和PCIe等是从某些第三方购买的IP,并且受到保密协议的限制,因此TI或Broadcom或Allwinner等芯片中仅包含该外设部分的某些说明)。但至少有Linux的开源初始化代码和驱动程序,这比反汇编某些东西要好。您可以使用BeagleBone Black或Raspberry Pi(不是很好的例子;DRAM是在GPU中完成的,尽管我认为现在已经对某些非零程度开放了)或无数基于Allwinner的板。
在高层次上,体验和过程是相同的,即启动、初始化、加载操作系统、启动操作系统。必须在正确的时间进行DRAM初始化、PCIe初始化、USB初始化、以太网初始化,以便启动和启动。技术是相同的(DDR2、DDR3、DDR4、PCIe Gen 1,2,3、USB 1、2、3等),在某些情况下,购买的IP是相同或类似的等等。
是的,BIOS代表基本输入/输出服务。从历史上看,它是一组基于软件中断处理程序,用于以通用方式与视频或硬盘等交互,因此当你购买一个视频卡时,它在物理上有一个包含该视频卡的视频BIOS的rom。有一个过程将该BIOS链接到系统中,以便在调用其中一个int system call时,它会使用该flash上的代码。硬盘控制器也是如此。其想法是读取一个扇区不需要你知道软驱控制器或硬盘控制器的细节,你可以使用这些高级系统调用。
BIOS和CMOS这些术语已经被超载,不仅包括BIOS本身(可能还包括引导加载程序),原始源代码在原始英特尔PC手册中可用(我还保留着一本我的原始PC,但现在不再拥有了)。今天,操作系统依赖于BIOS或EFI来实现这种通用的"我不需要了解"方法,但一旦操作系统运行,它就会加载特定于控制器的驱动程序,不再需要跨越到BIOS系统调用。这是基于PC历史的非常PC的事情,而非PC采取更传统的方法。
因此,是的,BIOS是/曾经是处理软件中断(系统调用)的一些代码,但我们也错误地或正确地将该术语应用于包括引导加载程序。当你从AMI等购买"BIOS"时,它确实会引导芯片。

还要注意,服务器通常会配备BMC(板管理计算机,您也可以从AMI购买该软件),这是一种基于ARM处理器的设备,您可以通过IPMI或其他方式使用它来检查主板的健康状况和状态,开关电源,有时重新加载操作系统,从网络引导等。此外,我们现在知道并自然地期望,在例如Intel x86芯片中还有其他处理器来帮助管理芯片。每个处理器都有固件和引导该固件的方法,这在某种程度上是特定于它们的(部分是系统特定的)。 - old_timer
整个系统设计包括这些组件,而x86本身的引导,以及DRAM和PCIe初始化所需的进程,则是将这些各种组件融合成系统设计的一部分。谁先启动,谁加载谁等等,都是这个系统设计中需要考虑的问题。 - old_timer
1
请注意,如果您的内存是焊接在主板上而不是可拆卸的,则不一定需要使用SPD。这取决于您的设计方式,例如您的手机采用DDR3L或DDR4L。并且您不一定需要使用需要运行时计算的JEDEC SPD,您可以根据所选部件和备选项自行解决系统设计中的问题。或者您可以增加一个EEPROM的成本并使用SPD。 - old_timer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接