我手上有一台ThinkPad X200,从闲鱼购入,宝刀不老,长期以来一直都是我写程序写论文的主力。不过,前主人给它设置了超级管理员密码,由于相隔已久,早已忘了密码。幸好超级密码只锁定BIOS设置,不影响进系统,倒也相安无事。

然而,有密码始终影响我对笔记本的掌控。我尝试像台式机那样,拔掉BIOS电池来清除密码。殊不知,X200是商务笔记本系列,联想早已经做足了防范。结果很惨:每次开机都得输入管理员密码!

最终,我决定,刷coreboot,一劳永逸解决。coreboot是一款自由、开源的系统固件,是BIOS和UEFI的替代。它早已对ThinkPad X200提供完备的支持。何不用它,绕过密码验证,直接引导系统?

准备工具

BIOS刷写需要准备以下工具:

  • 一台安装了Linux发行版的电脑
  • CH341A编程器
  • SOP-16夹具

既然电脑无法进系统,想要刷BIOS,当然离不开编程器了。我选用的是著名的CH341A“土豪金”编程器,接下来用到的刷写工具Flashrom为它提供了官方支持。

X200的BIOS芯片是25型拥有16根引脚,可以直接通过SOP-16夹具来连接编程器。夹具和编程器都可在淘宝购买到。我手上的X200为高配版本,芯片容量为8.0 MB。

系统环境方面,选用Linux发行版。通过它来运行Flashrom来刷写BIOS,以及编译coreboot。

值得注意的是,coreboot只提供源代码,不提供已经编译的固件,所以需要自己编译。你也许可以选用网上别人编译好的固件,但它们常常缺少正确的配置,导致功能缺失(例如,无线网卡和蓝牙不能使用)。

编程器连接

编程器的卖家通常会提供完备的资料。由于CH341A编程器可能有不同的外观,配件也可能有不同的组装方式,故应当以资料为准。

就以我的编程器为例。我手上这台CH341A“土豪金”编程器是这样子的:

左为CH341A编程器本体,右为配套的SOP-16夹具

1)卸下键盘与掌托

X200的BIOS隐藏在掌托下,需要卸下键盘与掌托才能看到BIOS芯片。

卸掉笔记本底部除硬盘仓外所有的螺丝(还有一颗螺丝藏在电池仓,需要先卸下电池),然后小心沿着边缘拆下掌托,即可同时将键盘和掌托卸下。注意不要弄坏键盘底部的排线。

有的X200是带有指纹传感器的(高配版),它与主板的接口是一块可拆卸的芯片,轻轻将它从主板取出。

2)准备编程器

编程器右侧有一个黑色的连接器。先把连接器右侧的手柄向上推,使手柄朝上:

按下图的方式,将夹具的8个针脚插入连接器左半边的8个孔位。注意区分方向,不要接反了。

最后,将手柄向下压,即可将夹具固定住,这样编程器与夹具就连接上了。

3)连接BIOS芯片

X200的BIOS芯片位于主板下沿、Intel南桥芯片左侧。先展示一下芯片的特写:

红圈中的芯片就是BIOS芯片。右侧为Intel南桥芯片。

张开夹子,先让连着红色导线的夹板立在芯片右侧(对着南桥芯片那一侧),仔细对准芯片的针脚。接着缓缓将夹子夹在芯片上,确保芯片两侧的针脚都被夹住。一旦夹牢靠,就不容易松动。

注意不要把夹子的方向弄反了,否则芯片会识别不出来。

接好之后,将编程器插在电脑的USB接口上,确保红灯亮起。连接完成的效果如下图所示。

夹具连接示意图。由于拍摄角度问题,一些细节没能准确呈现。你可以大致观察到BIOS芯片的位置,以及编程器的连接方式。

备份原机BIOS

连接编程器后,首先需要备份原机BIOS ROM。一方面是防止万一,另一方面是coreboot需要使用官方BIOS的组件。

1)检测芯片型号

 

首先运行以下命令,尝试读取BIOS芯片,检测型号,借此检查连接情况:

sudo flashrom -p ch341a_spi

若一切正常,执行结果如下。可以看到,编程器已经识别出了芯片:

flashrom v1.2 on Linux 5.15.65-1-MANJARO (x86_64)
flashrom is free software, get the source code at https://flashrom.org
Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
Found Macronix flash chip "MX25L6405" (8192 kB, SPI) on ch341a_spi.
Found Macronix flash chip "MX25L6405D" (8192 kB, SPI) on ch341a_spi.
Found Macronix flash chip "MX25L6406E/MX25L6408E" (8192 kB, SPI) on ch341a_spi.
Found Macronix flash chip "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" (8192 kB, SPI) on ch341a_spi.

Multiple flash chip definitions match the detected chip(s): "MX25L6405", "MX25L6405D", "MX25L6406E/MX25L6408E", "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F"
Please specify which chip definition to use with the -c <chipname> option.

输出日志中,不止一个型号被检测出来。后续的操作,我们使用最长串的型号:MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F

提示: 检测出多型号是正常现象,因为BIOS芯片本身有多种型号定义,仅通过编程器是无法精确识别的。

2)开始备份

运行以下命令,将整个BIOS ROM备份到文件x200_bios.rom中:

sudo flashrom -p ch341a_spi -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -r x200_bios.rom

正常情况下,输出结果如下所示:

flashrom v1.2 on Linux 5.15.65-1-MANJARO (x86_64)
flashrom is free software, get the source code at https://flashrom.org
Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
Found Macronix flash chip "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" (8192 kB, SPI) on ch341a_spi.
Reading flash... done.

由于传输过程可能存在异常,为保险起见,多备份几次,然后比较各个备份文件(例如,SHA256校验和)。如果完全相同,则说明备份无误。

例如,先备份,然后使用OpenSSL来比较SHA256:

# 再备份3次
sudo flashrom -p ch341a_spi -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -r x200_bios_2.rom
sudo flashrom -p ch341a_spi -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -r x200_bios_3.rom
sudo flashrom -p ch341a_spi -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -r x200_bios_4.rom

# 然后,比较它们的SHA256校验和
openssl sha256 x200_bios_*.rom

如果传输无误,则上述文件的SHA256值应当相同,如下:

SHA256(x200_bios.rom)= 038481951bb5d3412392fc1fd02b3996a9ac5394e82aaa4593b84f90362afce4
SHA256(x200_bios_2.rom)= 038481951bb5d3412392fc1fd02b3996a9ac5394e82aaa4593b84f90362afce4
SHA256(x200_bios_3.rom)= 038481951bb5d3412392fc1fd02b3996a9ac5394e82aaa4593b84f90362afce4
SHA256(x200_bios_4.rom)= 038481951bb5d3412392fc1fd02b3996a9ac5394e82aaa4593b84f90362afce4

准备coreboot源代码

接下来的教程中,假定我们把coreboot的源码目录放在用户主目录下。

1)克隆源码

首先,克隆coreboot的代码库:

git clone --recursive https://github.com/coreboot/coreboot.git coreboot
cd coreboot

如果不小心漏掉了--recursive参数,则同步子模块:

cd coreboot
git submodule update --init --recursive

2)准备交叉编译器

coreboot涉及到更底层的系统环境。为了保证coreboot的正常编译,还需要准备交叉编译器。例如,Intel x86_64平台中,一律使用i386的GCC编译器。

首先,安装GCC Ada工具链。

coreboot包含一个显卡驱动:libgfxinit,它可以在Intel平台下初始化集成显卡或核芯显卡,使用Ada语言的子集SPARK编写。要给交叉编译器提供Ada语言的支持,必须先在当前主机上安装GCC Ada工具链。

不同的发行版有不同的包名。在Arch Linux中,使用以下命令安装:

sudo pacman -S gcc-ada

接下来,就可以让coreboot自动构建交叉编译器,只需运行以下命令:

# 使用单处理器(单个线程)进行构建。会很慢!
make crossgcc-i386

# 使用多个处理器(多个线程)进行构建。
make crossgcc-i386 CPUS=$(nproc)

构建过程是全自动的,一步到位。

准备BIOS组件

coreboot需要从原机的官方BIOS ROM中提取以下三个组件:

  • Flash descriptor,存放闪存芯片的布局信息(必选)
  • ME Firmware,即Intel Management Engine的固件(可选,提取备用)
  • GbE,有线网卡的配置文件(必选)

上述组件可以直接使用coreboot提供的ifdtool来自动提取。

1)编译ifdtool

进入coreboot源码目录,然后浏览到ifdtool的源码目录,并编译:

cd util/ifdtool
make

编译完成后,当前目录会多出一个可执行文件:ifdtool。为便于使用,可以把它安装到系统中:

sudo cp ifdtool /usr/bin/

2)提取组件

浏览到存放BIOS备份的目录,运行以下命令提取:

ifdtool -x x200_bios.rom

执行结果如下所示:

File x200_bios.rom is 8388608 bytes
 Flash Region 0 (Flash Descriptor): 00000000 - 00000fff
 Flash Region 1 (BIOS): 00600000 - 007fffff
 Flash Region 2 (Intel ME): 00001000 - 005f5fff
 Flash Region 3 (GbE): 005f6000 - 005f7fff
 Flash Region 4 (Platform Data): 005f8000 - 005fffff

此时,当前目录下就会多出几个文件。它们都是提取出的组件,属于BIOS ROM的组成部分。

  • flashregion_0_flashdescriptor.bin
  • flashregion_1_bios.bin
  • flashregion_2_intel_me.bin
  • flashregion_3_gbe.bin
  • flashregion_4_platform_data.bin

3)把组件放入coreboot源码目录中

接下来,我们把提取出的组件放入指定目录中,使coreboot可以识别。

首先,进入coreboot源码目录,然后建立下面的子目录:

mkdir -p 3rdparty/blobs/mainboard/lenovo/x200

然后,把提取出的以下三个组件改名,放入上述目录中:

原名更改的名字
flashregion_0_flashdescriptor.bindescriptor.bin
flashregion_2_intel_me.binme.bin
flashregion_3_gbe.bingbe.bin

至此,BIOS组件准备完成,可以开始后续的配置了。

配置coreboot

coreboot使用KBuild来配置,编译过Linux内核的开发者应该不会陌生。接下来,我们将配置coreboot,打造出功能完备的体验,不应满足于点亮机器进系统。

基本操作

  • 上下移动光标:选择设置项
  • Enter:
    • 进入子菜单或“多选一(给你多个选项,一次只能选一个)”菜单
    • 确认当前选项
  • Y/N/空格:勾选(或取消勾选),适用于带有方括号的项目,方括号里出现星号,表示已选中
  • 连按两次ESC:返回上一页

1)进入配置菜单

进入coreboot源码目录,然后打开配置菜单:

make menuconfig

2)设置主板类型

首先,要将coreboot的主板类型设置为ThinkPad X200。

  1. 进入一级菜单 【Mainboard(主板)】,然后选择 【Mainboard vendor(主板厂商)】,按Enter进入。在列表中,光标定位到Lenovo,按空格选中。
  2. 然后,选择 【Mainboard model(主板型号)】,同样按Enter进入,在列表中,光标定位到ThinkPad X201 / X201i / X201s / X201t,按空格选中。

选好后,按两次ESC,退回主页面。接下来的步骤都从主页面开始。

3)设置BIOS组件

进入一级菜单 【Chipset(芯片组)】,然后勾选 【Add Intel descriptor.bin file(添加英特尔descriptor.bin 文件)】

此时,列表下方会多出下面两个选项:

  • 【Add Intel ME/TXE firmware(添加英特尔ME/TXE固件)】
  • 【Add gigabit ethernet configuration(添加千兆以太网配置数据)】

为了保证以太网能正常工作,你需要勾选【Add gigabit ethernet configuration】。至于Intel ME/TXE固件,经测试可能会导致BIOS不稳定,不建议勾选。具体我会在文末“后续问题”章节讨论。

其余选项保持默认值。固件的路径默认均指向3rdparty/blobs/mainboard/lenovo/x200这一目录的相应文件,无需修改。

4)启用无线网卡支持

一些型号的Intel PCIe无线网卡,如果在ACPI和SMBIOS中缺少数据,则操作系统将无法识别它们。为了保证笔记本上安装的Intel网卡可用,需要在coreboot中提供对它们的支持。

进入一级菜单 【Generic drivers(通用驱动)】 ,勾选最后一项 【Support Intel PCI-e WiFi adapters(英特尔PCIe无线网卡支持)】,即可。

5)启用二合一网卡蓝牙支持

有别于笔记本常见的二合一无线网卡,X200采用独立的博通蓝牙适配器,走的是USB通道。但部分用户可能更换了二合一网卡。要想让这类网卡的蓝牙工作,需要开启coreboot的相关支持选项。

进入一级菜单 【Chipset】,选择 【Support bluetooth on wifi cards(为搭载于无线网卡上的蓝牙提供支持)】,按Y勾选即可。

6)启用NVRAM存储

coreboot可以把BIOS的参数保存在NVRAM中,这些参数控制着笔记本的各种特性,比如显存大小、无线网卡开关、告警提示音开关等。

但是,coreboot默认并没有开启参数保存的功能,这将导致所有参数在每次开机时还原为默认。并且,还会导致蓝牙指示灯无法点亮。

因此,我们需要手工打开它。具体步骤如下:

  1. 进入一级菜单 【General setup(一般配置)】,然后选中 【Option backend to use(要使用的配置项存储后端)】
  2. 回车,然后在列表中,光标定位到 【Use CMOS for configuration values(用CMOS来保存设置值)】,按空格选中,搞定。

7)启用显卡支持:libgfxinit与Linear Frame buffer

coreboot首选的显卡支持方案是VGA Option ROM,即VGA BIOS镜像。用户需要获取当前显卡的VGA BIOS,编译时将它集成在coreboot中,每次开机即可利用它来初始化显卡。

如果系统能正常启动,我可以直接在Linux中获取VGA Option ROM。然而,现在X200无法进入系统,再买一台X200也不现实,因此根本没办法获取。

所幸,我还可以使用libgfxinit,这个库可以用来初始化Intel平台的核显与集显,驱动X200的GM45集显不在话下。在它的基础上,再启用Linear Framebuffer功能——建立一个帧缓冲(frame buffer),允许程序直接在coreboot中调用显卡绘图,从而使显卡在系统引导阶段发挥应有效能。

具体步骤如下:

  1. 进入一级菜单 【Devices(设备)】,选择【Graphics initialization(图形初始化)】。在选项列表中,光标定位到 【Use libgfxinit(使用libgfxinit)】,按空格确认。
  2. 然后,依次进入子菜单 【Display(显示)】→【Framebuffer mode(帧缓冲模式)】。在选项列表中,光标定位到 【Linear "high-resolution" framebuffer(线性高分辨率帧缓冲)】
  3. 之后,在【Framebuffer mode】子菜单中,会多出以下两个选项,指定为X200显示器的最佳分辨率即可:
    1. 【Maximum width in pixels(最大像素宽度)】,指定为1280
    2. 【Maximum height in pixels(最大像素高度)】,指定为800

至此,显卡支持设置完成。

编译coreboot

配置完成后,就可以立刻着手编译:

# 使用单处理器(单个线程)进行构建。会很慢!
make

# 使用多个处理器(多个线程)进行构建。
make -j$(nproc)

最终编译好的coreboot ROM位于build子目录,文件名为coreboot.rom

刷入coreboot并开机测试

连接编程器,运行以下命令,将coreboot刷入BIOS芯片中:

sudo flashrom -p ch341a_spi -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -w build/coreboot.rom

若一切正常,输出结果将如下所示:

flashrom v1.2 on Linux 5.15.65-1-MANJARO (x86_64)
flashrom is free software, get the source code at https://flashrom.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
Found Macronix flash chip "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" (8192 kB, SPI) on ch341a_spi.
Reading old flash chip contents... done.
Erasing and writing flash chip... Erase/write done.
Verifying flash... VERIFIED.

刷好之后,松开夹具,断开编程器,扣上电池,开机!

上电后,coreboot将初始化硬件,随后启动SeaBIOS——一款开源的BIOS实现。SeaBIOS再引导硬盘上的操作系统。

在X200中,屏幕上出现SeaBIOS的版本号,以及本机的UUID。不一会,本地硬盘上的Grub也成功引导,出现了“Welcome to Grub!”的一样,随后熟悉的Grub菜单出现在眼前。

启动成功!

内部刷写

刷好coreboot之后,将键盘、掌托及指纹识别器的排线按原样装回去,然后就可以放心上螺丝了。

后续的BIOS更新,无需再使用编程器,仅使用Flashrom就可以完成。

1)卸载lpc_ich驱动

通常,获取BIOS芯片状态的命令是flashrom -p internal。但一般情况下,在Arch Linux中运行时,会报错:

flashrom v1.2 on Linux 5.15.72-1-lts (x86_64)
flashrom is free software, get the source code at https://flashrom.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
Found chipset "Intel ICH9M".
Enabling flash write... Error accessing ICH RCRB, 0x4000 bytes at 0x00000000fed1c000
/dev/mem mmap failed: Operation not permitted
FAILED!
FATAL ERROR!
Error: Programmer initialization failed.

网上的刷写教程,甚至是coreboot的官方文档,都是不会告诉你这个问题的。

所幸,Flashrom的官方文档提供了对策。只需卸载系统的lpc_ich驱动,即可释放对BIOS芯片的访问权限。运行以下命令:

sudo modprobe -r lpc_ich

2) 备份与刷写

卸载上述驱动后,直接运行Flashrom刷写即可。你既可以备份整颗芯片,也可以只备份BIOS区域而保持ME、GbE等其他部分不变。

例如,刷写刚刚编译好的coreboot:

# 刷写整个BIOS芯片
sudo flashrom -p internal -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -w build/coreboot.rom

# 只刷写BIOS区域,保持其他部分不变
sudo flashrom -p internal -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -w build/coreboot.rom --ifd -i bios

以及,备份当前版本的BIOS:

# 备份整个BIOS芯片
sudo flashrom -p internal -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -r coreboot_backup.rom

# 只备份BIOS区域
sudo flashrom -p internal -c "MX25L6436E/MX25L6445E/MX25L6465E/MX25L6473E/MX25L6473F" -r coreboot_backup.rom --ifd -i bios

排查后续问题

1)Grub的显示

我在测试coreboot的时候,启动过程中,会卡在“Welcome to Grub!”字样。但没过多久,Grub仍能正常引导Linux。

Grub默认会以图形方式显示菜单,这要求显卡必须初始化图形功能——有别于传统的VGA文字模式。

然而,我的X200只能用libgfxinit初始化。如果不启用Linear Framebuffer,则默认只能使用文字模式,而Grub无法对此作出自适应的处理,所以菜单无法显示。

对此,有两种解决方案:

方案一:将Grub切换到文字模式

进入Linux后,修改/etc/default/grub,取消注释下面这行:

#GRUB_TERMINAL=console

如果是新版本的GRUB,则取消注释这一行:

#GRUB_TERMINAL_OUTPUT=console

保存该文件,随后重新生成Grub配置文件:

sudo grub-mkconfig -o /boot/grub/grub.cfg

重启后,Grub就会切换到VGA字符模式,以简易的文字来绘制界面,可以顺利使用了。

方案二:启用Linear Framebuffer

使用Linear Framebuffer,就可以保证Grub的图形菜单能正常显示。

但是,Linear Framebuffer不支持缩放,只能点对点显示,这意味着如果输入分辨率小于Linear Framebuffer指定的分辨率,画面就只能靠左上角显示。

并且,Grub也无法获取Linear Framebuffer的最佳分辨率,它一律会用640x480的最低分辨率输出。最终的显示效果很“憋屈”。

所以,我们还需要设置Grub的输出分辨率。打开修改/etc/default/grub,修改参数GRUB_GFXMODE:

GRUB_GFXMODE=1280x800x32

X200的最佳分辨率是1280x800,32位色深。后面的x32可以省略。

修改后,更新Grub配置文件,重启生效,此时Grub能全屏显示了。

2)Intel ME造成的问题

Intel ME(Management Engine)是内置在英特尔处理器中的底层软件框架,用于远程管理、系统启动保护(Bootguard)等。它的固件集成在官方BIOS中,同时coreboot也提供了对ME的支持。

不过,据笔者实测,将ME集成到coreboot中,可能会导致一些潜在的问题,包括但不限于:

  • 电脑关机后,有很大几率无法一次点亮,需要反复重试;
  • 睡眠之后无法唤醒;
  • 关机后立即重启时,电脑会发出尖锐的蜂鸣声。

笔者在近期(2024年5月初)重新构建coreboot时,就把ME剔除在外。即,在make menuconfig配置过程中,不开启【Add Intel ME/TXE firmware】这个选项。刷入固件后,电脑无法一次点亮的问题就不再出现。

另一方面,高配版的X200采用8 MB的BIOS芯片,而ME固件会占用约4 MB的BIOS ROM空间。这就意味着你可能没有足够的空间来同时放置多款负载(payload,即coreboot搭载的启动固件),例如GRUB2、EDK2等。如果你是高级用户,需要使用第二款负载(secondary payload),则不建议将ME集成到coreboot中。

3)更新源码树后,记得重新编译工具链

你可以随时使用git pull命令来更新coreboot源码树。

但要注意的是,coreboot会始终使用最新版本的GCC来作为交叉编译工具。如果编译时出现了以下的错误,则说明工具链需要重新构建了:

The coreboot toolchain for 'x86_32' architecture was not found.

此时,请使用以下命令,先执行清理,然后再重新编译工具链:

make crossgcc-clean
make crossgcc-i386 CPUS=$(nproc)

注意:如果不运行make crossgcc-clean,那么编译GCC时将会发生一些莫名其妙的错误,光搜索错误信息是完全找不到任何答案的。

写在最后

忘记BIOS管理员密码,给我掌控手上这台ThinkPad X200带来了麻烦,无法进系统,近似于变砖。幸运的是,我有开源的力量——借助开源固件coreboot,顺利绕开了官方BIOS的安全保护。

coreboot的表现非常惊艳,按下电源键的一瞬间,就立即进入了SeaBIOS,随后迅速引导硬盘中的Arch Linux,启动速度远远快于官方BIOS(仅支持MBR启动),表现堪比UEFI固件。

如今,X200成功焕发新生,不再吃灰,继续作为我的第二主力。在这里衷心感谢coreboot团队的匠心和努力。


参考资料