In a pinch: Flashing a PC BIOS with an ESP32

published 13.04.2022

ESP32 wiring for SPI flash ICs

This project started (as so often happens) with a simple, 30min task. We wanted to replace @faultierkatze's desktop PC mainboard (ASRock H81M-VG4) with a different one. The old mainboard only had a single VGA output (and a slightly odd behaving BIOS), so @faultierkatze was forced to use a Nvidia GPU (under Linux, with noveau), which isn't ideal at all.

I had an unused ASUS H81M-PLUS in my spare parts bin, collecting dust because it has been replaced by a full-size ATX board (with 4 DIMM slots, 6 SATA ports, etc.). The unused board used the exact same chipset, had the same form-factor and feature set, except for more video outputs.

After swapping the boards (and re-applying fresh thermal compound), we got... nothing. No POST, no beep codes, absolutely nothing. Troubleshooting ensued, the system was stripped to the minimum, power cables checked, RAM re-seated, CPU re-seated. Still, nothing.

A look into the ASUS H81M's manual revealed the real issue, the BIOS version was probably too old for the CPU. Without another (older) Haswell CPU there was no way to get the mainboard to POST and update the BIOS. The small ITX board had no advanced BIOS flashback features and only supported in-UEFI ("EZ Flash") or MS-DOS updates.

The perfect solution

Use a Raspberry Pi and flashrom. (or a CH341-based programmer)

Any Raspberry Pi (even Zero, 1, etc.) will work fine. Strip away the first 0x800 bytes from the .cap file, which ASUS provides, to get a nice 8MB flash image. Flash to the chip (make a backup first!), you're golden.

Only problem: We had no Raspberry Pi available. At the time of writing, the big chip shortage is happening and Raspberry Pi's are totally unavailable. Getting one would've involved a 2h car trip.

What didn't work

With the computer not POST'ing, another solution was required. Both computers used (socketed!) DIP-8 SPI flash chips to store their BIOS images. ASUS H81M-PLUS used a Winbond 25Q64 (8MiB), while the ASRock H81-VG4 used a Winbond 25Q32 (4MiB).

Amazingly, the BIOS images were actually similar enough to work in the wrong machine!

ASRock UEFI setup running on an ASUS board ASUS board in question

Here's a rare sight: ASUS mainboard showing an ASRock UEFI setup.

As expected, this didn't behave properly (of course):
— PS/2 ports didn't work
— Mainboard temperature was reported as +155°C
— +12V rail was reported as 6.3V
— ... probably lots of other issues

but the computer booted and actually started Linux perfectly well.

FreeDOS

First idea: Get the computer running with FreeDOS from a USB flash drive, hot-swap the SPI flash chip and flash a clean BIOS image.

Creating a bootable USB drive on Linux with Unetbootin failed, the resulting USB flash drive wouldn't boot at all.
Using Rufus on Windows, the same flash drive booted immediately.
This revealed another major issue with the swapped BIOSes, though:
Legacy USB emulation didn't work!
PS/2 was broken anyway, but USB keyboard and mice worked fine in the UEFI setup.
Typing into the FreeDOS prompt did absolutely nothing.

After fiddling with different keyboards, 3 different USB emulation settings in the UEFI setup, etc., another solution was required.

Linux / flashrom

Next idea: Boot Linux, use flashrom with the --programmer internal flag.

Booting Manjaro Linux actually showed that the USB emulation was broken even in Grub. This was especially annoying, because we needed the iomem=relaxed kernel parameter to allow flashrom to access the chipset directly. So instead we installed Manjaro Linux to a spare SSD (pacman didn't work right in the Live-ISO anyway), changed the kernel parameters, rebooted and ...

Found chipset "Intel H81"

It actually found the chipset and it even found the 25Q32 chip. Soo, I hot-swapped the chip while the computer was running and ran flashrom again. To my great joy, it now reported to have found a Winbond 25Q64FVA.

That happiness didn't last very long, though, as the Intel Management Engine was getting in the way. Flashing a raw BIOS image required a forced read first.

This failed (even with all no-verify, force, etc. parameters):
Reading old flash chip contents... Transaction error

Linux / afulnx

AMI actually released an official tool called afulnx, which compiles a custom kernel driver in order to talk to the hardware directly. This tool wasn't really updated in the last couple of years, so it didn't compile against the 5.15 kernel. A helpful GitHub repo by cantona had a patched version of the kernel driver, which would actually compile against the kernel.

Upon running afulnx, it immediately crashed with a Segmentation fault. Brilliant.

Windows / afuwin

So, we bit into the sour apple and installed Windows 10 onto the SSD.

PowerShell, running afuwinx64.exe

afuwin (and the accompanying GUI) actually seemed to be doing something and happily started to erase and write the flash chip (after hot-swapping again). Unfortunately, it would also randomly lock-up the computer while trying to write, which resulted in a lot of hot-swapping of the SPI flash IC. I suspect that this might have something to do with the Intel ME. afuwin also uses it's own kernel driver on Windows again, which might not expect a different flash chip to appear suddenly :)

No matter what we did, the resulting state on the SPI flash IC wasn't POSTing.

I was about to call it a day at this point after a couple of hours of fiddling... but I also really didn't want to put the Nvidia GPU (and old mainboard) back into the machine.

What worked

Using an ESP32 as a SPI flash programmer via UART.

ESP32 wiring for SPI flash ICs ESP32 with SPI flash dangling from the computer

I reluctantly connected an ESP32 to a breadboard and wired the SPI flash IC to the ESP's hardware SPI peripheral.
Thankfully, the fantastic SPIMemory Arduino library by @Marzogh exists.

Using the SPIMemory library, I implemented a very simple Arduino program, which would erase the whole flash, read 4096 bytes from the UART, write them to a flash page and repeat this for the whole 8MiB.

ESP32-flasher Arduino sketch

Writing to the SPI flash (and UART) is slow, so you can't just use a terminal and send the BIOS file this way. The Arduino firmware replies with "W" (and a newline) whenever it is ready to accept a new flash page (4KiB each). This PHP script will connect to the serial port, listen for the "W" commands and send over chunks of 4KiB of the BIOS image.

After flashing for 10 minutes: ASUS UEFI setup utility

It actually worked! The computer was now running with a regular ASUS UEFI on an ASUS board. We flashed the BIOS once more using the regular EZ Flash mechanism (just to ensure all ROM content was correct, as the UART-based flasher had no verification whatsoever).