Flashing libreboot on a thinkpad X230

Created 2024-04-22

Table of contents

This post is notes I took while (and after) flashing my X230 with libreboot. I might edit this later properly.

Identifying SPI flash chips

I'll be flashing the bootloader externally because it's the method to unbrick the laptop if something goes wrong, and while doing this sort of think it's safe to assume something will go wrong so I might as well use the external method to begin with.

First step is to remove the keyboard and palmrest, it's pretty obvious what needs to be done but you can never go wrong by reading the manual first1 (pages 60 to 65). I've also removed the main battery, unplugged the backup coin cell battery under the palmrest and removed my SSD.

Unsticking the black plastic cover from the main board a surprise is that there are two SPI flash chips, not one.

Close up of the bottom-left corner of the main board with a corner the black
plastic cover folded away. There are two SOP-8 packages next to each other, one
marked SPI1 and another marked SPI2 on the silkscreen.

SPI2 chip label is clearly visible as MXIC MX25L3206E, the one marked SPI1 is
harder to discern but with some searching it's most likely cFeon QH64-104HIP.

SPI1 chip is most likely EON QH64-104HIP and SPI2 chips is MXIC MX25L3206E. Checking their datasheets2 they both are 3.3V logic and power compatible and they have identical pin assignments except for pin 7 where the MXIC part has HOLD but the EON part is not connected but that's ok as everything we'll is matching.

Chip pinFunction
1CS
2DO
4GND
5DI
6CLK
83V3

I also double checked that there is no voltage on the chips and that there is significant resistance to ground on all the pins so I won't blow anything up. Not sure if that's necessary but it probably doesn't hurt to check.

SPI flasher

For interfacing with the SPI I'll be using the pico-serprog (the libreboot fork) on a Raspberry Pi Pico3 and flashprog (a libreboot fork of flashrom)4.

I have pico-sdk installed from AUR from before, so the build proces was very easy just as documented in the readme.

cd pico-serprog
cmake .
make

And then I've flashed the pico_serprog.uf2 file using the Pico USB bootloader, and it appeared in dmesg a few seconds later.

usb 2-3: new full-speed USB device number 7 using xhci_hcd
usb 2-3: New USB device found, idVendor=cafe, idProduct=4001, bcdDevice= 1.00
usb 2-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 2-3: Product: pico-serprog (pico)
usb 2-3: Manufacturer: libreboot.org
usb 2-3: SerialNumber: E66118604B4A2C27
cdc_acm 2-3:1.0: ttyACM0: USB ACM device

The pinout is again nicely documented in the readme including a picture.

Pico pinFunction
4CLK
5DI
6DO
7CS

Reading out factory ROMs

For each chip I'll be first probing it with no operation, then I'll do 5 reads and verify they all match (for paranoia reasons).

For SPI1 (the EON chip) it looks like:

> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64
flashprog v1.0.1 on Linux 6.8.2-arch2-1 (x86_64)
flashprog is free software, get the source code at https://flashprog.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
serprog: Programmer name is "pico-serprog"
Found Eon flash chip "EN25QH64" (8192 kB, SPI) on serprog.
No operations were specified.
> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64 -r SPI1_read0.bin
flashprog v1.0.1 on Linux 6.8.2-arch2-1 (x86_64)
flashprog is free software, get the source code at https://flashprog.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
serprog: Programmer name is "pico-serprog"
Found Eon flash chip "EN25QH64" (8192 kB, SPI) on serprog.
Reading flash... done.
> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64 -r SPI1_read1.bin
[...]
> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64 -r SPI1_read2.bin
[...]
> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64 -r SPI1_read3.bin
[...]
> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64 -r SPI1_read4.bin
[...]
> b3sum SPI1_read?.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read0.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read1.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read2.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read3.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read4.bin

And they match!

Now disconnect the Pico, move the clip to the MXIC chip and do the same thing again, just with a different chip.

> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c MX25L3206E/MX25L3208E
flashprog v1.0.1 on Linux 6.8.2-arch2-1 (x86_64)
flashprog is free software, get the source code at https://flashprog.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
serprog: Programmer name is "pico-serprog"
No EEPROM/flash device found.
Note: flashprog can never write if the flash chip isn't found automatically.

Oh... that's, not good. Is that not the right chip? The label was clearly legible, it should be correct. I tried a bunch of different options, I've reseated the clip multiple times, tripple checked the pinout was correct. I started thinking about the HOLD pin if it made any difference.

Then I remembered that I've seen an IRC channel mentioned on the flashprog website4, so I've tried describing the issue there and they were amazing <3

First showed up one person suggesting the CS not being deasserted on the chip I'm not dumping causing interference on the SPI bus.

<hell> flashing the X230 in-circuit is tricky, you have to make sure the chip
       you do *not* want to access is not enabled
<hell> de-asserting the chip select signal on the other chip should allow
       reading the chip
<hell> it's just that the board isn't designed for in-circuit flashing, so chip
       select may be driven by the southbridge
<hell> IIRC chip select is active low, so inactive high
<hell> but double-check datasheet just in case
<hell> I'd also re-do any dumps you made from the chip that works, in case there
       was interference

This reminded me of an option I've seen in the pico-serprog3 readme about flashing multiple chips at once. I've checked if the CLK, DI and DO lines are connected between the chips, and while it didn't trigger the continuity beeper the resistance between them was quite low. So I tried connecting a one pin clip onto the CS leg of the other chip and connected it to the next pin of the Pico.

Pico pinFunction
8CS2

Unfortunately something wasn't right, maybe the documentation is outdated there but flashprog wasn't happy with serprog's response and it didn't work.

> sudo flashrom -p serprog:dev=/dev/ttyACM0,cs=0 -c MX25L3206E/MX25L3208E
flashrom v1.3.0 on Linux 6.8.2-arch2-1 (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).
serprog: Programmer name is "pico-serprog"
Unhandled programmer parameters: cs=0
Aborting.
Error: Programmer initialization failed.

Soon after I got another response on IRC, offering a completely different explanation for the issue I was having.

<icon> IIRC, the actual X230 problem is that power is leaking. so one shouldn't
       power the flash chip(s) externally
<icon> there should be a way to power it through the regular AC adapter of the
       laptop. when the laptop is powered down (soft-off, S5), and an ethernet
       cable with an active device on the other end is connected, it should keep
       the flash chips powered (among other things)
<icon> in this state, one can usually flash, but should *not* connect VCC to the
       flash chip

So I've disconnected the VCC pin (and the second CS) from the Pico and then I had to try getting the laptop into this S5 state, where the SPI chip is powered (I should be able to measure 3.3V on it) but nothing besides the ethernet port looks on.

Because I was afraid of connecting the clip while the system was powered - to not short it out - I always disconnected power from both the laptop and the flasher, connected the clip and then plugged in the USB connectors.

First I tried connecting just ethernet and laptop charger, that resulted in three angry beeps and a "System CMOS Checksum bad" error message, followed by a prompt to "press ESC to continue or F1 to enter Setup" which was rather amusing since I don't have a keyboard on a dissasembled laptop. Then I tried connecting the clip first but not powering on the flasher. I had hoped this would result in floating pins and not affect the power up, but that was not the case, the laptop did not turn on. When the clip was on one of the chips the fan would spin briefly and then turn off and completely nothing on the other chip. Trying to power on the flasher didn't help either. There was only about 0.94V on the SPI VCC pins and they were not responding to the flasher correctly.

<icon>  you shouldn't turn it on with the clip connected
<icon>  I'm not sure about all valid states, the following order worked in the
        past: remove all power (all batteries too), plug the ethernet cable,
        plug the charger. then check the voltage. in theory the EC should power
        up the ME, it should sense the active ethernet cable and tell the EC to
        keep some power on
<p2502> ah, it just turns on after i plug in the charger
<icon>  you mean the laptop boots up / tries to boot?
<p2502> yes, it tries to boot after i connect the charger
<icon>  did you configure this on purpose? could be something in the BIOS setup
<p2502> i did not, the setup should be default since i've removed the cmos
        battery no?
<icon>  don't know for sure. it would store some things in flash, hard to tell
        if it clears that on a cmos reset too
<icon>  you could also connect the keyboard, try what happens after you turn
        it off
<p2502> checking the bios setup now "power on with AC Attach" is "Disabled"
<p2502> holding the power button to turn it off, spi flash voltage is back to
        essentially 0V
<p2502> oh! but when i leave the ethernet attached it does keep the spi powered
        (and ethernet is blinking)

So the network management features of Intel ME are good for something after all :^) This worked great, however now follows a scary bit.

<p2502> should i try to connect the clip now? i'm worried about shoring the pins
        like this.. but this is the first time it was properly powered by the
        laptop while the laptop is off. and it doesn't turn on with the clip
        already attached
<icon>  that's the plan. you could practice it first without power ofc. if the
        space around the chip allow it, I usually do it this way: fully open the
        clip, place one side first, tightly against the flash chip, then slowly
        close the clip

So, after a few more practice rounds of clipping the clip I went for it. And it worked!

Towards the back of the picture is a Lenovo X230 with a missing palmrest,
uncovered protective tape and a chip clip clipped to the barely visible flash
chip, which is slightly to the left of where the touchpad normally would be. At
the right edge of the laptop is connected ethernet and both lights of the socket
are visible from the inside of the laptop and they're on. In the middle is a
Raspberry Pi Pico on my expansion board with rainbow cable going off screen to
the left where it's presumably connected to the ribbon cable of the chip clip.
It's connected via flat blue USB cable to a second thinkpad which is just right
off screen. The whole thing is surrounded in a mess of parts of other projects.

I don't have the logs for it, forgot to save them because I was too excited (might try to repeat it later and add them back). Interestingly the first chip had different contents than when I read it before.

> b3sum SPI?_read?.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read0.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read1.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read2.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read3.bin
64034a5cdb6f011cd167f05e4315ce213a25a1da7885b8d703e5c3fe2d22c5ab  SPI1_read4.bin
b8d4218d699b47469caf026f660e02ee1d5ee360da00fe90d2f2543cb0de8357  SPI1_read5.bin
b8d4218d699b47469caf026f660e02ee1d5ee360da00fe90d2f2543cb0de8357  SPI1_read6.bin
b8d4218d699b47469caf026f660e02ee1d5ee360da00fe90d2f2543cb0de8357  SPI1_read7.bin
b8d4218d699b47469caf026f660e02ee1d5ee360da00fe90d2f2543cb0de8357  SPI1_read8.bin
b8d4218d699b47469caf026f660e02ee1d5ee360da00fe90d2f2543cb0de8357  SPI1_read9.bin
fdc6a87ed276f8491700b4f18c869185326dd38d5d9e973e05944641fc959a58  SPI2_read0.bin
fdc6a87ed276f8491700b4f18c869185326dd38d5d9e973e05944641fc959a58  SPI2_read1.bin
fdc6a87ed276f8491700b4f18c869185326dd38d5d9e973e05944641fc959a58  SPI2_read2.bin
fdc6a87ed276f8491700b4f18c869185326dd38d5d9e973e05944641fc959a58  SPI2_read3.bin
fdc6a87ed276f8491700b4f18c869185326dd38d5d9e973e05944641fc959a58  SPI2_read4.bin

Strange, but apparently that's just how Intel does these things.

<p2502> ok, successfully redumped the first chip which worked before too. the
        hash is different so that one really didn't work! (i've dumped it 5
        times each and both were consistent)
<icon>  flash contents can change during boot. it feels wrong, but that's how
        things are for ~15 years now

Here are the factory ROM files for my Lenovo X230 with 12MB of flash (8MB + 4MB chips) as I was able to flash them.

I would've had to unsolder at least the 4MB chip to read its contents before boot. I'm not sure if flashing these back would restore the laptop to the factory state since they thanks to the changes that happened on boot. I might try it later and see what happens.

Obtaining libreboot

This was surprisingly harder than I remembered from my last time flashing an X200, maybe the guide for it was a bit more organized or I just don't remember it much (it's been years). The necessary information was all there, but it was scattered across several pages on libreboot.org.

What was absolutely impossible to miss however, was the safety notice5. Since my x230 is among the affected platforms with non-ridistributable bits I followed (not very attentively, which bit me a littel later) the guide how to Insert vendor files on Sandybridge/Ivybridge/Haswell.

I've cloned the repository for the libreboot build system https://codeberg.org/libreboot/lbmk, downloaded a release libreboot-20230625_x230_12mb.tar.xz into it and ran.

./vendor inject libreboot-20230625_x230_12mb.tar.xz

This failed because I forgot to install dependencies, but instead I wanted this done already so I just installed innoextract which seemed to be the one missing dependency, then ran it again. Then I've noticed that you can put your MAC address in so ran it again but with the MAC address this time. This again didn't work because the output was already built, then I tried moving a piece of the output and reran it again which seemed to have worked but now that I'm running on the laptop my MAC is 00:de:ad:c0:ff:ee so clearly I should've not done this late in the night and read the docs before doing not afterwards when I'm writing it down into a post. Here is the command that I probably should've run.

./vendor inject libreboot-20230625_x230_12mb.tar.xz -m 3c:97:0e:81:49:24

This produced many files in bin/release/x230_12mb/, after a bit of googling I decided to try grub_x230_12mb_libgfxinit_corebootfb_usqwerty.rom first, and it ended up working great (after I fixed my nixos bootloader to be GRUB and Legacy instead of systemd-boot and UEFI).

Since I have two SPI chips the ROM file needs to be split, this is described on the libreboot website on a page specific to the Lenovo x230.

> dd if=grub_x230_12mb_libgfxinit_corebootfb_usqwerty.rom of=SPI1.rom bs=1M count=8
8+0 records in
8+0 records out
8388608 bytes (8.4 MB, 8.0 MiB) copied, 0.0151833 s, 552 MB/s
> dd if=grub_x230_12mb_libgfxinit_corebootfb_usqwerty.rom of=SPI2.rom bs=1M skip=8
4+0 records in
4+0 records out
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.00365031 s, 1.1 GB/s

And then just flashed those two files to their respective flash chips and we're done^^ (unless you're me and had to spend another two hours fixing NixOS bootloader and wifi)

> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c EN25QH64 -w SPI1.rom
flashprog v1.0.1 on Linux 6.8.2-arch2-1 (x86_64)
flashprog is free software, get the source code at https://flashprog.org

Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
serprog: Programmer name is "pico-serprog"
Found Eon flash chip "EN25QH64" (8192 kB, SPI) on serprog.
Reading old flash chip contents... done.
Erasing and writing flash chip... Erase/write done.
Verifying flash... VERIFIED.
> sudo flashprog -p serprog:dev=/dev/ttyACM0 -c MX25L3206E/MX25L3208E -w SPI2.rom
[...]

Hackers are amazing

I want to again thank the kind hackers in the #flashprog IRC channel for their friendliness and for helping me debug the the one hard problem in this process. And thanks to all the awesome people of the libreboot project, making free firmware so accessible the rest of my problems was only my rushing to finish in one evening.

<p2502> and the second chip works now too!
<p2502> thank you so much! <3
<icon>  grats! you're welcome :)

    1

    Lenovo X230 Hardware Maintenance Manual - x230_x230i_hmm_en_0b48666_01.pdf

    2

    Datasheets for SPI flash chips EN25Q64-104HIP.pdf and mx25l3206e.pdf.

    3

    Source repository notabug.org/libreboot/pico-serprog. And on the libreboot website there is a whole guide on how to Read/write 25XX NOR flash via SPI protocol.

    4

    Installed flashprog version 1.0.1 flashprog.org using the AUR package flashprog which builds source code from review.sourcearcade.org.

    5

    The post libreboot.org/news/safety is explaining why some libreboot ROMs are incomplete and links to a guide on how to patch them. It's also explaining how they're trying to mitigate the risk, what other solutions they considered and why the ended up with the incomplete roms and links to how to patch them everywhere.

    This makes me a bit afraid some people were pretty nasty towards some libreboot developers for "bricking" their machines (it's totally fixable) and they have to do this to protect themselves.

    But if I forget toxic people exist online for a bit it sounds like Leah is momming the reader, like she's making sure they don't forget their lunch again ^^