Table of contents
- Identifying SPI flash chips
- SPI flasher
- Reading out factory ROMs
- Obtaining libreboot
- Hackers are amazing
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.
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 pin | Function |
---|---|
1 | CS |
2 | DO |
4 | GND |
5 | DI |
6 | CLK |
8 | 3V3 |
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 pin | Function |
---|---|
4 | CLK |
5 | DI |
6 | DO |
7 | CS |
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 pin | Function |
---|---|
8 | CS2 |
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!
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.
- SPI1_factory_internal_power.bin 8MB
- SPI2_factory_internal_power.bin 4MB
- SPI1_factory_external_power.bin 8MB, if you're interested in the differences
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 :)
Lenovo X230 Hardware Maintenance Manual - x230_x230i_hmm_en_0b48666_01.pdf
Datasheets for SPI flash chips EN25Q64-104HIP.pdf and mx25l3206e.pdf.
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.
Installed flashprog version 1.0.1 flashprog.org using the AUR package flashprog which builds source code from review.sourcearcade.org.
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 ^^