coreboot amdtools 工具集详解:AMD EFS SPI 频率修改与 K8 扩展内存设置对比实战

原创2026-10-14 22:03:441,740 阅读
文章标签:固件嵌入式操作系统驱动开发

coreboot amdtools 工具集详解:AMD EFS SPI 频率修改与 K8 扩展内存设置对比实战

本文围绕 coreboot 仓库 util/amdtools 目录下的 AMD 平台辅助工具展开,系统讲解两大工具组:一是用于修改固件镜像 EFS 表中 SPI 总线频率的 update_efs_spi_speed,二是面向 AMD K8 内存控制器的一组"读取—解析—对比"脚本工具链。读完本文,你将掌握如何对 AMD Stoney Ridge / Zen 级平台的固件镜像直接改 SPI 频率,以及如何借助 BKDG 文档与 PCI 配置空间读数定位内存设置差异。

工具集概览

util/amdtools 目录是 coreboot 仓库中专门面向 AMD 处理器的一站式辅助工具集,其清单见 description.md,更详细的用法说明见 README.md。目录内主要包含两类工具:

工具 语言 作用
update_efs_spi_speed Bash 直接修改固件二进制(ROM 镜像)中 EFS 表记录的 SPI 总线速度
k8-compare-pci-space.pl Perl 对比 PCI 配置空间中的值(lspci -vvxxx 转储)与默认值之间的差异
k8-interpret-extended-memory-settings.pl Perl 对比内存控制器读取值与默认值之间的差异,并给出位域级解释
k8-read-mem-settings.sh Bash 通过 setpci 读取 K8 内存控制器的扩展内存设置,生成上述脚本可理解的数据文件
parse-bkdg.pl Perl 将 K8 BKDG(BIOS and Kernel Developer's Guide)文本解析为 bkdg.data,供上述对比脚本使用
example_input/ 文本 上述脚本的示例输入数据

整个工具链的核心思路是:先用 parse-bkdg.pl 把厂商手册中的寄存器定义"数字化",再采集真实内存配置读数,最后用对比脚本在 BKDG 定义的位域语义下解释差异,从而帮助开发者排查内存初始化参数(例如超频、时序、驱动强度)与默认值或两组配置之间的具体差异。

一、update_efs_spi_speed:直接改写固件镜像中的 SPI 频率

1.1 适用平台与使用方式

根据 README.md,该工具面向 Stoney Ridge 与 Zen 级 AMD 处理器,其作用是在不重新编译 coreboot 的情况下,直接修改已经生成的固件 ROM 镜像里 EFS 表中记录的 SPI 频率。

工具本身是一个纯 Bash 脚本,用法为:

./update_efs_spi_speed <ROM> <Speed>

其中 <ROM> 是固件镜像文件路径,<Speed> 是目标速度编号,取值 0~5,对应关系如下(来自脚本内 usage() 与 showspeed() 的定义):

编号 对应频率
0 66.66 MHz
1 33.33 MHz
2 22.22 MHz
3 16.66 MHz
4 100 MHz
5 800 KHz

脚本启动时会先做参数校验:参数个数必须为 2、ROM 文件必须存在、速度编号必须在 0~5 之间,否则打印用法并退出(exit 1)。此外,脚本内部把 0xff 视为"未设置速度",把其他未定义的值视为"未知速度"并报错。

1.2 工作原理:从 EFS 签名到频率字节

EFS(Embedded Firmware Structure)是 AMD 固件中的一段关键结构,脚本通过特征签名 55aa55aa(EFS_SIG_DWORD)在镜像中定位它:

readonly EFS_SIG_DWORD="55aa55aa"
readonly FAST_SPEED_NEW_F15_MOD_60_6F_OFFSET=0x41
readonly SPI_FASTSPEED_F17_MOD_00_2F_OFFSET=0x44
readonly SPI_FASTSPEED_F17_MOD_30_3F_OFFSET=0x48

查找过程(main() 函数)按 for i in {0..5} 循环,用公式 location = 0xffffff - (0x80000 << i) + 0x20000 + 1 依次推算 6 个候选位置——对应 512KB、1MB、2MB、4MB、8MB、16MB 六种常见 ROM 镜像规格下 EFS 可能出现的位置——并在每个位置用 dd + hexdump 读取 4 字节与 55aa55aa 比对,命中即认为找到 EFS。

找到 EFS 后,update_efs() 会依次检查三个 SPI 速度字节偏移(按处理器家族区分):

  • 0x41:Family 15h(K10 时代)Model 60–6F 的 FAST_SPEED 偏移;
  • 0x44:Family 17h Model 00–2F 的 SPI_FASTSPEED 偏移;
  • 0x48:Family 17h Model 30–3F 的 SPI_FASTSPEED 偏移。

只要某个偏移处的当前字节不是 0xff,脚本就用 printf 生成 \x0N 形式的字节,通过 dd conv=notrunc 原位写回,并重新读取打印新旧值(例如 0: 66.66Mhz → 4: 100MHz)。写回时的 conv=notrunc 保证只覆盖该字节而不截断镜像文件。如果 6 个位置都找不到 EFS 签名,或三个偏移处都没有可更新的速度字节,脚本会报错退出,避免误改。

1.3 使用注意事项

  • 脚本直接改写二进制文件且不做备份,使用前请确认 ROM 镜像可恢复;
  • 该脚本读取/写入均为固定偏移,仅适用于上述 AMD 处理器家族,其他平台的 EFS 布局不同,不可照搬;
  • 镜像中速度字节为 0xff 时表示未设置,脚本会跳过该位置而不是强制写入。

二、K8 扩展内存设置对比工具链

第二组工具面向 AMD K8(Athlon 64 / Opteron)的内存控制器,用于把"厂商手册定义的寄存器语义"与"实际运行中的内存配置"对应起来,找出差异所在。整体流程为:

K8 BKDG 文档 ──pdftotext──> 文本文件 ──parse-bkdg.pl──> bkdg.data
                                                          │
k8-read-mem-settings.sh ──> 内存配置读数文件 ──────────────┼──> k8-interpret-extended-memory-settings.pl
lspci -vvxxx 转储 ──────> PCI 空间数据文件 ───────────────┼──> k8-compare-pci-space.pl

2.1 第一步:把 BKDG 加工成 bkdg.data

对比脚本之所以能"读懂"寄存器里的每个位域,依赖的是一份名为 bkdg.data 的 Perl 数据文件,它由 parse-bkdg.pl 从 AMD K8 BKDG 文档生成。README 给出了完整步骤:

  1. 从 AMD 官网获取一份 K8 BKDG 文档(README 中注明对应 Rev F 版本);
  2. 确保系统装有 pdftotext(Debian/Ubuntu 上属于 poppler-utils 软件包);
  3. 用 pdftotext 提取文本并保持版面:
    pdftotext -layout 32559.pdf 32559.txt
    
  4. 从文本中截取 4.5.15~4.5.19 小节,另存为 bkdg-raw.data;
  5. 通过 parse-bkdg.pl 生成数据文件:
    parse-bkdg.pl < bkdg-raw.data > bkdg.data
    

parse-bkdg.pl 本质上是一个文本解析器:它按章节标题(Chapter 4 等)过滤页眉,识别 Function N: Offset ..h 形式的寄存器标题行、Index ...h 形式的寄存器索引行,以及 Bits Mnemonic Function R/W Reset 表头,逐行把 BKDG 表格中的**位范围(bits)、助记符(mnemonic)、功能描述(function)、读写属性(R/W)、复位值(reset)**以及随后的 "Field Descriptions" 文本抽取出来,输出为一系列 Perl 赋值语句,例如 $info{'..h'}{'name'} = "...";、$info{'..h'}{'ranges'}{'..'}{'function'} = "...";,文件末尾以 1; 结尾,使其可以被其他脚本用 do 直接加载(见 k8-interpret-extended-memory-settings.pl 与 k8-compare-pci-space.pl 中的 load_datafile(),默认加载当前目录下的 bkdg.data)。

值得一提的实现细节:脚本内置了一张拼写纠错表(如把 BKDG 中的 CkeDreStrength 修正为 CkeDrvStrength),并在解析位范围时自动将 x-y 形式的连续位展开为逐位条目(sub multiply),从而支持从位位置反查所在位域。

如果没有 bkdg.data,两个对比脚本仍可运行,但只会报告"哪些位不同",无法给出对应的功能名与含义(脚本会打印 Warning: data file 'bkdg.data' not found 提示)。

2.2 第二步:用 k8-read-mem-settings.sh 采集内存控制器读数

k8-read-mem-settings.sh 是一个示例性质的 Bash 脚本,演示如何通过 PCI 配置空间读取 K8 内存控制器的扩展内存设置。K8 的 DRAM 控制器(以 0:18.2、0:19.2 为例,脚本注释说明这两个设备号适用于 Supermicro H8DME 这类双路 Opteron 主板)通过"索引寄存器 0x98 + 数据寄存器 0x9C"的方式访问扩展寄存器:

for OFFSET in 00, 01, 02, 03, 04, 05, 06, 07, 20, 21, 22, 23, 24, 25, 26, 27, 10, 13, 16, 19, 30, 33, 36, 39; do
  setpci -s 0:18.2 98.l=$OFFSET
  echo 0:18.2 98.l: `setpci -s 0:18.2 98.l`
  echo 0:18.2 9C.l: `setpci -s 0:18.2 9C.l`
done
  • setpci -s 0:18.2 98.l=$OFFSET:把扩展寄存器索引写入 0x98(读改写,98.l 表示 32 位长字);
  • 随后分别读取 0x98 与 0x9C,输出成两行一组的数据。

从 example_input 中的示例文件可以看到这类脚本产生的标准格式——每组是成对的两行:

0:18.2 98.l: 80000000
0:18.2 9C.l: 00111222

其中 98.l 行末两位十六进制数(如 00、01…)是刚才写入的索引,9C.l 行是索引对应寄存器的 32 位值。两份示例 coreboot-48G-667MHz-memsettings 与 coreboot-48G-667MHz-memsettings-20090909h 即是以 48GB 内存、667MHz 配置采集的两组(或两个版本)读数,覆盖了索引 00~07、10、13、16、19、20~27、30、33、36、39 等内存设置寄存器,既可用于验证脚本流程,也可作为对比的基准样本。

按主板不同,需要把脚本中的设备号(0:18.2、0:19.2)改成目标平台内存控制器对应的 PCI BDF,脚本头部注释对此有明确说明。

2.3 第三步:解释内存设置差异

k8-interpret-extended-memory-settings.pl 接受一个或多个 -f <文件> 参数(用法:k8-interpret-extended-memory-settings.pl -f <file1> -f <file2>),解析上一步产生的"98.l / 9C.l"成对格式,把同一个寄存器在两个文件中的值逐位 XOR:XOR 结果为 1 的位就是发生变化的位。随后借助 bkdg.data 中该寄存器的位域定义,反查每个变化位所属的字段,输出该字段的功能名、助记符、位范围、字段描述以及两个文件各自的取值(如 111b 并附 BKDG 中的值语义),最后以 HTML 形式输出报告。

k8-compare-pci-space.pl 的作用与之互补:它的输入不是 setpci 的成对行,而是 lspci -s 00:18.2 -vvxxx 产生的 PCI 配置空间十六进制转储(示例见 lspci-cb-48G-667MHz-18.2-20090909e 与 lspci-prop-48G-667MHz-18.2)。其解析逻辑会匹配 NN: xx xx xx ... 形式的字节行与设备地址行,将每个字节按寄存器偏移归档,再对同一设备同一寄存器在不同文件中的值做 XOR 对比;输出同样包含十六进制与二进制两种视图,以及差异位域的功能解释。usage_information() 中给出的输入生成方式即为 lspci -s 00:18.2 -vvxxx,这也正是 description.md 所述"对比 PCI 空间中的值与默认值差异"的数据来源——将两份转储(如厂商原厂固件与 coreboot 固件,或两组不同配置)分别以 -f 传入即可。

两个脚本还支持 -v 打印版本信息;参数不足时均会打印用法说明。从脚本结构(parse_file、interpret_differences、load_datafile)可以推断,k8-compare-pci-space.pl 与 k8-interpret-extended-memory-settings.pl 是同源实现,前者面向 lspci 转储、后者面向 setpci 成对读数,共享同一套 bkdg.data 位域解释逻辑。

三、快速体验:直接用示例数据跑通流程

不想先准备 BKDG 的话,可以直接使用仓库自带的 example_input 验证脚本:

# 对比两组内存控制器读数(从示例输入中截取、保持 98.l/9C.l 成对格式即可)
perl k8-interpret-extended-memory-settings.pl -f memsettings1 -f memsettings2

# 对比两份 lspci 配置空间转储
perl k8-compare-pci-space.pl -f lspci-cb.txt -f lspci-prop.txt

注意:k8-compare-pci-space.pl 的输入需为 lspci -vvxxx 的完整转储格式(示例文件中含设备描述行与配置空间字节行),k8-interpret-extended-memory-settings.pl 的输入需为 setpci 产生的成对行格式;两份示例 lspci-*.txt 与 coreboot-*.memsettings 文件恰好分别对应这两种格式,可直接作为对照素材。若当前目录没有 bkdg.data,脚本只会报告差异位而不解释含义——这正是第二步准备工作的价值所在。

四、同目录的延伸工具:ec_usb_pd_fw(USB Type-C PD 固件指针工具)

除上述两组工具外,util/amdtools 下还有一个子目录 ec_usb_pd_fw,其 description.md 说明它是一个"离线的 AMD 板级 USB Type-C PD 固件修改工具"(C 语言实现)。该工具在 flash 偏移 0x10 处生成指针列表,供 EC 固件读取:EC 依据 FMAP 与指定的 FMAP region 名称,在需要时把 USB PD 固件加载到对应芯片。使用要点如下:

  • 用法:ec_usb_pd_fw <rom-file> <command> [arguments],命令为 GEN_PDFW_PTR;
  • 选项:-f/--fmap_region_name 指定 FMAP region,-s/--slot [0-11] 指定指针槽位,-n/--next_free 自动寻找下一个空闲槽位(与 -s 互斥);
  • 每个指针占 8 字节:4 字节偏移 + 3 字节大小 + 1 字节校验(保证 8 字节总和为 0xff),槽位上限 12 个;
  • 该工具会无警告地覆写 flash 部分区域,README 与 description 都提示用户必须留意与 Intel IFD 等其他结构可能发生的冲突;
  • 构建方面,Makefile.mk 显示其复用了 util/cbfstool/flashmap/ 的 FMAP 解析代码与 src/commonlib/bsd 公共库,通过 coreboot 顶层构建系统以宿主机编译器(HOSTCC)编译为 ec_usb_pd_fw 工具。

结语

util/amdtools 展示了 coreboot 生态中一类典型的"平台辅助工具"形态:用少量脚本补齐固件开发与调试中的高频需求。update_efs_spi_speed 让 SPI 频率调整无需重新构建镜像即可完成,而 K8 工具链则以"BKDG 数字化 + 实机读数 + 位域级对比"的方式,把晦涩的寄存器手册变成可复用的排障手段。若要进一步深入,可直接阅读 README.md 与各脚本源码,并结合 example_input 中的真实采集数据亲自跑一遍对比流程。

登录后查看全文
coreboot