将 LinuxBIOS 移植到 AMD SC520:后续报告
嗯,事情太容易了。在我们将 LinuxBIOS 移植到 我们的项目 中,一切进展顺利,直到我们尝试刷写闪存芯片。然后我们开始遇到主板、主板设计和 AMD SC520 的一些问题。
哪里出错了?简而言之,当我们尝试使用 flash_rom 程序刷写芯片时,它甚至无法识别我们主板上的芯片类型。从那时起,情况变得更糟。我们编写了一个小程序来转储闪存芯片,如下所示
#include <errno.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <stdio.h> #include <string.h> # include <stdlib.h> #include <ctype.h> main(int argc, char *argv[]) { int fd_mem; volatile char *bios; unsigned long size = 512 * 1024; int i; if ((fd_mem = open("/dev/mem", O_SYNC|O_RDWR)) < 0) { perror("Can not open /dev/mem"); exit(1); } bios = mmap(0, size, PROT_READ, MAP_SHARED, fd_mem, off_t) (0xffffffff - size + 1)); if (bios == MAP_FAILED) { perror("Error MMAP /dev/mem"); exit(1); } write(1, bios, 512*1024); }
当我们运行这个程序时,我们无法得到合理的结果。这个程序在我们拥有的其他所有设备上都运行良好——数千个 K8 节点、我们的笔记本电脑、1,500 个 Xeon 节点——所以问题不在于程序。到底发生了什么?
正如前面提到的,我们发现了 Advanced Digital Logic 的 MSM586SEG 和其他基于 SC520 的主板的设计问题。简单来说,问题是 CPU 无法访问完整的闪存芯片;只能访问芯片的顶部 128KB。这个限制要求我们修改我们支持的所有闪存访问工具,以便它们知道,尽管闪存的标称大小为 256 或 512KB,但只有 128KB 的空间可用。
然而,即使做出这样的更改,仍然没有帮助。当我们转储闪存芯片时,我们得到的不是垃圾数据,而是无意义的数据。我们看到字符串读作CCCCoooo等等。这种无意义的数据使我们认为闪存空间被某种方式缓存了。此外,我们认为硬件设计存在问题,即如果内存范围内启用了缓存,则从闪存芯片的突发读取将返回相同的字节四次,而不是四个连续的字节。
然后,我们又遇到了一些其他问题。我们有两块 MSM586SEG 主板,并且它们上面的 IDE 接口都停止工作了。事实证明,MSM586SEG 有一个 FPGA 控制着许多功能,我们怀疑这个 FPGA 有一些初期问题。我们决定尝试旧的设计,即没有 FPGA 的 MSM586SEV。
MSM586SEV 解决了我们所有的问题,只剩下一个:当我们尝试读取闪存时,我们仍然得到无意义的数据。现在是时候深入研究 SC520 架构了。我们了解到,需要管理一组 16 个寄存器,称为 PAR 寄存器,才能启用闪存刷写。
什么是 PAR 寄存器?它们用于引导 CPU 发出的内存和 I/O 访问。如今,几乎所有处理器在内存和 I/O 地址生成路径中都有一组特殊的寄存器,用于修改处理此类地址的方式。
为什么需要这种类型的寄存器?由于多个总线能够支持内存和 I/O 访问,除非告知处理器,否则处理器不知道将访问发送到哪里。这就是 PAR 寄存器的功能。请看下面所示的 SC520 框图。
给定的 I/O 访问可以进入 PCI 总线或右侧显示的 GP 设备。内存访问可以进入 SDRAM、闪存芯片或 PCI 总线。PAR 寄存器允许 BIOS 为给定的 I/O 或内存范围指定其进入哪个总线,是否可写或只读以及是否被缓存。
我们发现,对于 BIOS 内存范围 0xe0000-0xfffff,PAR 寄存器被设置为 SDRAM。此设置并不令人意外:为了性能,BIOS 通常将 BIOS 镜像复制到 SDRAM,然后确保所有 BIOS 代码提取都转到保存 BIOS 的 SDRAM。此操作通常称为“BIOS 阴影”。
由于 Linux 根本不使用 BIOS,我们可以忽略此设置。我们所做的是将 BIOS 区域的 PAR 寄存器,即 PAR 寄存器 15,设置回原始 BIOS。这只是一个简单的映射寄存器,然后设置寄存器的问题。以下是执行此操作的代码片段
if ((fd_mem = open("/dev/mem", O_SYNC|O_RDWR)) < 0) { perror("Can not open /dev/mem"); exit(1); } mmcr = mmap(0, 4096, PROT_WRITE|PROT_READ, MAP_SHARED, fd_mem, (off_t) 0xfffef000); if (mmcr == MAP_FAILED) { perror("Error MMAP /dev/mem"); exit(1); } p = mmcr + 15; l = *p; printf("l is 0x%lx\n", l); /* clear cache bits */ l |= (1<<27); /* enable writeable bit */ l &= ~(1<<26); /* set type to flash, not sdram */ l &= ~(7<<29); l |= (4<<29); /* 64k pages */ l |= (1<<25); /* blow away base and size stuff. */ l &= ~(0x1fff | (0x7ff<<14)); printf("l is now 0x%lx\n", l); l |= (8 << 14) | (0x2000000>>16); printf("l is now 0x%lx\n", l); *p = l;
完成此操作后,我们仍然遇到问题。更深层次的问题是 PAR 寄存器的设计。它们位于内存中的 0xfffef000;换句话说,它们被放置在 4GB 内存空间顶部 2MB 的正中间。按照惯例,此空间保留给 BIOS 闪存,但 SC520 打破了该惯例。因此,尽管我们已经解决了主板问题,但现在我们面临着一个架构问题。
这时,一个灵感闪现,这与我们在 AMD 的示例代码中看到的注释有关。AMD 代码始终小心地对 PAR 寄存器进行编程,以将闪存芯片放置在 DRAM 顶部之上,即 32MB 或十六进制 0x2000000。我们稍微修改了我们的 parbios 程序,瞧——现在所有 512KB 的闪存都可用了,从 0x2000000 开始。
此更改的影响深远。我们必须修改我们的 flash_rom 程序,以在 SC520 上启用闪存,并将闪存放置在内存中的一个奇怪的位置。尽管如此,至少我们现在可以对其进行编程了。此更改也影响了 LinuxBIOS 本身。如果我们想使用所有闪存芯片,或者仅仅是超过 64KB 的闪存,我们将不得不对 LinuxBIOS 如何寻址闪存进行大量更改。到目前为止,我们从未见过一台无法直接在内存顶部寻址闪存的机器。
闪存已刷写。串口工作正常。让我们插上电源。
我们还必须修改 SC520 启动代码,以模拟 PAR 寄存器的设置。进行这一系列更改后,我们得到了我们的第一个串行输出
LinuxBIOS-1.1.8.0Fallback Tue Jun 14 13:36:22 MDT 2005 starting... Copying LinuxBIOS to ram. Jumping to LinuxBIOS
嗯,这是一个开始。记录一下,这是版本
LinuxBIOS@LinuxBIOS.org--devel/freebios--devel--2.0--patch-45.
现在发生了什么?“跳转到 LinuxBIOS”是什么意思?
这一切意味着基于 ROMCC 的代码正在工作,但 SDRAM 没有工作。由于 SDRAM 没有工作,因此基于 GCC 编译的代码也无法工作。现在是时候添加一些打印输出了。现在也是时候仔细扫描 src/cpu/amd/sc520/raminit.c 代码以查找错误了。截至这个版本,这段代码仍然非常难看,因为它来自汇编代码。快速浏览确实显示了一些错误,但此时打印输出是最好的选择。有时很难说清楚到底发生了什么。
这是这个版本的输出
LinuxBIOS-1.1.8.0Fallback Tue Jun 14 16:29:46 MDT 2005 starting... HI THERE! sizemem NOP
然后它重置了。作为参考,我已经将此版本提交为 patch-46。请查看 raminit 代码以了解崩溃的位置。
此时,我们不得不进行更多的挖掘。我们注意到在 AMD 汇编代码中,尽管使用了许多字节寄存器来控制各种事物,但一些汇编代码似乎使用了字写入。即使对于一个字节宽的寄存器,其后紧跟着另一个寄存器,该代码也使用了字写入。
我们改为字写入,情况变得好多了。一旦一切正常工作,为了简洁起见,我们将尝试将这些改回字节写入。字写入毫无意义,除非存在硬件问题。
我们始终在某个代码序列上遇到重置,几乎就像我们为错误的处理器编译一样。好吧,事实证明,我们确实是这样做的。尽管我们在主板的 Config.lb 中设置了这一行
arch i386 end
我们在其中一个额外的编译规则中犯了一个错误。我们告诉 ROMCC,CPU 是 P3
makerule ./auto.inc depends "$(MAINBOARD)/auto.c option_table.h ./romcc" action "./romcc -mcpu=p3 -O -I$(TOP)/src -I. $(CPPFLAGS) $(MAINBOARD)/auto.c -o $@" end
这样做有什么问题?简而言之,当我们将 P3 指定为 ROMCC 的 CPU 时,ROMCC 会生成 MMX 指令来使用那些额外的寄存器。这种用法会导致问题,因为 486 上没有 MMX 寄存器。
我们按如下方式修改了该行
makerule ./auto.inc depends "$(MAINBOARD)/auto.c option_table.h ./romcc" action "./romcc -mcpu=i386 -O -I$(TOP)/src -I. $(CPPFLAGS) $(MAINBOARD)/auto.c -o $@" end
情况突然变得好多了。
但是我们如何知道呢?复制 LinuxBIOS RAM 部分的代码是汇编代码。它说跳转到 LinuxBIOS,但我们只看到 POST EE。我们将向您概述如何为新平台进行调试。我们将绕过 ROMCC 代码之外发生的大部分 LinuxBIOS。此代码主要执行的操作是解压缩 GCC 代码并将其复制到 SDRAM。但是,这段代码可能很难理解,因此我们将完全跳过它。
我们将使复制到 RAM 的代码成为未压缩的代码,而不是压缩的代码,这将占用更多空间。因此,我们需要尽可能多地使用闪存。我们需要使闪存映射到 0x2000000。在 auto.c 代码中,我们将把闪存复制到 RAM。最后,在代码中,我们将插入一些像这样的循环
1: jmp 1b
这样,如果机器挂起,我们就知道它进入了无限循环。
让我们一次处理一个部分。在我们的 src/cpu/amd/sc520/raminit.c 文件中,我们添加以下内容
*par++ = 0x8a020200; /*PAR15: BOOTCS:code:nocache:write:Base 0x2000000, size 0x80000:*/
您现在仍然可以在那里看到该代码。这会将闪存映射到 32MB 位置。接下来,我们设置 LinuxBIOS,以便 GCC 有效负载未压缩。我们该怎么做呢?首先,我们需要解释内存布局。许多变量控制着 LinuxBIOS 中的闪存布局,如图 2 所示。请注意,可以为每个有效负载更改每组变量。在我们的示例中,此时,我们仅使用一个有效负载,因此我们显示该情况的变量。
在 src/mainboard/digitallogic/msm586/Options.lb 中,我们将 CONFIG_COMPRESS 设置为零。我们将 ROM_SIZE 设置为 128K,并将 ROM_IMAGE_SIZE 设置得足够大以容纳未压缩的有效负载。如果您查看存储库中 LinuxBIOS 的各个补丁级别,您可以追踪我们在调试中的进展;此处空间不允许全部展示。我们已将 auto.c 中的相应代码保留在 ifdefs 之间,以便您可以查看其外观。一个警告:必须小心 volatile。Romcc 是一个很好的编译器。如果您不小心使用 volatile,它会很乐意优化掉复制赋值循环。
此外,在 crt0.s 中,我们进行了一些尝试。这是一个有用的汇编序列,用于告诉您您在哪里,并确保您看到它
_start: movb $0x12, %al ; outb %al, $0x80; jmp _start
我们确实验证了在汇编中,我们正在进入 hardware main()。因此在 hardwaremain() 中,我们放入对 post() 的调用,后跟 while(1),并且我们确实看到系统在该点挂起。
下一步是测试背靠背的 post()。换句话说,我们调用 post() 两次。为什么这很重要?它也验证了堆栈是否正常工作。到目前为止,我们所做的只是调用函数;我们还没有真正了解返回。调用始终可以工作,但是返回依赖于正常工作的堆栈。如果内存设置不正确,则返回将失败。在过去,我们遇到过一系列函数调用,这些调用工作正常,直到第一个函数退出,此时系统失败。内存确实可能如此棘手。但是,背靠背调用 post() 可以验证我们有一个正常工作的堆栈。
这里的关键思想是,仔细放置所谓的“停止并起火”指令,并带有一些输出,可以让您精确地指出您在代码中走了多远。
长话短说,我们被我们自己在配置文件中的错误困住了。我们忘记告诉 LinuxBIOS 我们有什么类型的控制台。这很容易修复。在 src/mainboard/digitallogic/msm586seg/Options.lb 中,我们添加
uses CONFIG_CONSOLE_SERIAL8250 default CONFIG_CONSOLE_SERIAL8250=1
现在,我们有控制台了吗?让我们看看。
我们现在得到这个输出
Copying LinuxBIOS to ram. Jumping to LinuxBIOS. LinuxBIOS-1.1.8.0Fallback Wed Jun 22 16:10:58 MDT 2005 booting... Enumerating buses... scan_static_bus for Root Device PCI_DOMAIN: 0000 enabled scan_static_bus for Root Device done done Allocating resources... Reading resources... Root Device compute_allocate_io: base: 00000400 size: 00000000 align: 0 gran: 0 Root Device read_resources bus 0 link: 0 PCI_DOMAIN: 0000 missing read_resources Root Device read_resources bus 0 link: 0 done Root Device compute_allocate_io: base: 00000400 size: 00000000 align: 0 gran: 0e Root Device compute_allocate_mem: base: 00000000 size: 00000000 align: 0 gran: 0 Root Device read_resources bus 0 link: 0 PCI_DOMAIN: 0000 missing read_resources Root Device read_resources bus 0 link: 0 done Root Device compute_allocate_mem: base: 00000000 size: 00000000 align: 0 gran: e Done reading resources. Setting resources... Root Device compute_allocate_io: base: 00001000 size: 00000000 align: 0 gran: 0 Root Device read_resources bus 0 link: 0 PCI_DOMAIN: 0000 missing read_resources Root Device read_resources bus 0 link: 0 done Root Device compute_allocate_io: base: 00001000 size: 00000000 align: 0 gran: 0e Root Device compute_allocate_mem: base: 100000000 size: 00000000 align: 0 gran:0 Root Device read_resources bus 0 link: 0 PCI_DOMAIN: 0000 missing read_resources Root Device read_resources bus 0 link: 0 done Root Device compute_allocate_mem: base: 100000000 size: 00000000 align: 0 gran:e Root Device assign_resources, bus 0 link: 0 Root Device assign_resources, bus 0 link: 0 Done setting resources. Done allocating resources. Enabling resources... PCI_DOMAIN: 0000 missing enable_resources done. Initializing devices... Root Device init Devices initialized Copying IRQ routing tables to 0xf0000...done. Verifing copy of IRQ routing tables at 0xf0000...done Checking IRQ routing table consistency... check_pirq_routing_table() - irq_routing_table located at: 0x000f0000 done. Wrote LinuxBIOS table at: 00000500 - 00000af0 checksum 934c Welcome to elfboot, the open sourced starter. January 2002, Eric Biederman. Version 1.3 23:stream_init() - rom_stream: 0xffff0000 - 0xffff7fff Found ELF candidate at offset 0 header_offset is 0 Try to load at offset 0x0 Could not find a bounce buffer... Cannot Load ELF Image
嗯,无论如何,这是一个开始。bounce buffer 是怎么回事?这不是真正的问题。真正的问题是 LinuxBIOS 认为没有内存。请记住,在开始时,我们设置 CPU 时没有要调用的函数?事实证明,我们确实需要调用一些函数,因为函数必须执行的部分操作是指示有多少内存。我们可以从另一个北桥芯片中寻找灵感。它与 SC520 非常接近,并且避免了 K8 北桥的复杂性,后者非常复杂。您可以在版本 50 的存储库中看到现在的样子。
我们得到了一个 FILO 横幅和一个立即重置,但至少我们得到了一些东西。现在是时候在 FILO 中增加调试,看看哪里出错了。
事实证明,该芯片没有硬件时间戳计数器 (TSC)。因此,当您尝试读取 TSC 时,芯片会做正确的事情;也就是说,它会发生一般保护故障并进入崩溃模式。FILO 从未在没有 TSC 的芯片上运行过。我们不得不修复 FILO。
我们已在 LinuxBIOS 树的 Subversion 版本中包含了一个 FILO,该 FILO 可以使用 SC520 毫秒定时器。这个定时器是一个很好的自由运行定时器,可以提供准确的时间戳计数。
完成此操作后,FILO 会给我们一个提示符,但表示没有 IDE。再次回到 MMCR 寄存器显示,我们需要启用内置的 IDE 芯片选择线,这些线默认情况下未启用。奇怪的是,许多带有内置控制器的嵌入式芯片往往会在禁用这些功能的情况下启动。手动设置更多 MMCR 寄存器即可解决问题。
内核启动并运行良好,只是并非所有中断都到达内核。这是中断寄存器配置中的问题。中断寄存器位于内存的 MMCR 区域中,我们之前已经处理过。我们的第一个想法是简单地转储寄存器并恢复它们,但是这样做只会使情况变得更糟!那时,甚至时钟中断都无法工作。显然,中断寄存器设置有一些技巧,我们需要解决。
嗯,还剩下什么要做?我们将添加 VGA 支持,以便我们可以拥有控制台。我们还将清理端口中的内容,现在已经有很多东西可以工作了。本文没有空间追踪整个过程;毕竟我们有字数限制。主板几乎完成了,一旦基本工作正常,我们将回去真正清理代码,同时保留我们希望对其他端口的调试示例有用的位。我们打算用这些主板构建大量计算机;它们非常棒,虽然有点慢。但是,它们非常适合构建便携式开发和测试集群。
我们还像往常一样发现,标准 BIOS 错误配置了芯片组中的部件。这种类型的错误配置在专有 BIOS 中很常见,因为没有人可以检查他们所做的一切的正确性。我们现在需要回去验证我们所有的设置,并确保没有从专有 BIOS 中复制任何进一步的错误。
LinuxBIOS 是一个 GPL 许可的系统,您可以使用它快速可靠地启动您的主板。它在全球超过一百万个系统中使用,应用范围从测试仪器到电视机不等。我们在 LANL(洛斯阿拉莫斯国家实验室)的近 5,000 个集群节点上使用它,到年底可能会达到 7,000 个。
与专有 BIOS 相比,LinuxBIOS 允许用户定制系统启动序列以满足他们的确切需求。我们在这两篇文章中展示的 Version 2 系统是模块化的,并具有面向对象的结构,实际上,这使我们能够构建紧凑的 BIOS 镜像。即使在复杂的系统(如具有 32 个 PCI 总线的 8 路 Opteron)上,小于 32KB 的镜像也是常规操作。LinuxBIOS 也具有可移植性,并且已在 64 位 Alpha 系统以及 PowerPC 系统上运行。到 PowerPC 970(一个 64 位系统)的移植正在进行中。
对于 x86 系统,LinuxBIOS 包括一个完整的 ANSI C 编译器 ROMCC,它使用寄存器而不是内存。
SC520 是一款出色的芯片,几乎没有任何错误。可能最大的问题是配置寄存器的位置,它位于内存顶部 2MB 的中间,而这部分内存应始终为 BIOS 闪存保留。尽管如此,我们越是使用这个部件,就越欣赏它的设计。
我们在 LinuxBIOS 之前在 Linux 中完成了许多 SC520 寄存器设置的测试。如果您开始进行自己的移植工作,请记住在 Linux 下进行这种类型的寄存器探测的价值——这要容易得多。要执行 I/O 操作,请使用 iopl() 系统调用;对于内存操作,请使用 mmap()。
未来一年 LinuxBIOS 的主要工作将是使那些每月只有几天时间来使用它的工程师更容易使用它。换句话说,我们正在从专职工程师转向将 LinuxBIOS 用作工具但不关心其内部运作的工程师。总体目标是降低 LinuxBIOS 的学习曲线。关注网页以查看这些更改的公告。
感谢 Advanced Digital Logic 的 Gary Karns 和 Martin Mayer 在回答问题、加快主板发货以及提供我们启动这一切所需的信息方面的帮助。
这项研究部分由美国能源部科学办公室的数学信息和计算机科学 (MICS) 计划以及洛斯阿拉莫斯计算机科学研究所 (ASCI Institutes) 资助。洛斯阿拉莫斯国家实验室由加利福尼亚大学为美国能源部国家核安全管理局运营,合同号为 W-7405-ENG-36。洛斯阿拉莫斯,NM 87545 LANL LA-UR-05-5272。
Ronald Minnich 是洛斯阿拉莫斯国家实验室集群研究团队的负责人。他从事集群计算的时间比他愿意想的要长。