Unbrick your motherboard with Treehopper

While Treehopper is a great prototyping platform for computer-human interaction, it’s also a useful tool to keep on your desk for random hacking around the office. And, in at least one case, it’s helped save my skin from a time-consuming (thus costly) RMA process.

The back story

When the AMD Ryzen 7 came out, my lab rushed out to build a system around the promising new architecture. We like small-form-factor systems, so we picked the ASUS PRIME B350M-A — a beautiful little microATX motherboard with the overclock-ready B350 chipset.

ASUS released a deluge of BIOS updates for the motherboard when it first launched — solving stability and fan speed / temperature info issues.

Unfortunately, while flashing one of these BIOS updates from within Windows, the BIOS update hung. Retrying failed, so we decided to reboot the system — I’ve had good luck with BIOS recovery features in the past, so I didn’t anticipate any issues.

But upon reboot, there was nothing — no monitor, no beeps, no activity at all — nothing.

BIOS basics

For the uninitiated: when we say “BIOS” we’re referring to low-level firmware executed by the motherboard before an operating system loads.

Historically, the BIOS provided a unified I/O interface to the underlying PC hardware — for example, there’s a BIOS call for printing characters to the screen. Even without an operating system, you could write an x86 program — running in privileged mode — that can print “Hello, world!” without knowing anything about video card drivers, PCI Express, or anything of that sort.

However, all modern operating systems bypass BIOS access to hardware, and load drivers that allow directly accessing peripherals.

Instead, modern BIOSes are mostly used for booting an operating system, and exposing information about the system configuration to it. In our case, half of our BIOS image file was blank — and without a running system, there’s no way to reflash the BIOS.

 

Manual reflashing

At this point, the typical plan of attack is an RMA — but that can take weeks to process. We couldn’t afford to lose this system for that much downtime.

Modern-day EFI-based systems store their BIOS in a simple SPI flash chip. The CPU will load this image on boot, and then execute it.

Consequently, “updating a BIOS” is really no different from “flashing an SPI chip” — something that can easily be done from most microcontrollers.

Compared to the fine-pitch BGAs, QFNs, and DFNs that fill modern motherboards, the SPI flash chip is comparatively homely — it’s a simple 8-pin SOIC, with a near-universal pinout (always check the datasheet for the chip to make sure, though!).

Looking at the motherboard, I noticed a group of header pins next to the SPI flash chip just waiting to be used. With the motherboard unplugged from power, I used my multimeter in continuity mode to determine which pins belonged to which signals of the SPI flash chip.

My labmate found a Linux-based tool for reflashing BIOSes through an Arduino sketch, but after a couple of hours of trying to coax it into writing data to our SPI flash chip, we still couldn’t get it working. It complained about the wrong JEDEC ID response, unknown chip versions, and all sorts of other issues. It didn’t explicitly support the specific SPI flash chip installed on the motherboard, so we had to try other equivalent ones — all to no avail.

It also didn’t help that the Arduino board we were using was a 5V board, with hacked-together 3.3V logic-level conversion to avoid damaging the chip.

These tools are great if you use them regularly, but there can be a steep learning curve to configure and use them effectively.

Writing a BIOS flashing tool

Instead of learning a tool, sometimes it can actually be easier and faster to write your own from scratch — that certainly rang true in this case.

At its core, all we needed to do was copy bytes out of a file into an SPI chip. Is that really so hard?

I wired the header signals straight through to a prototype Treehopper board laying on my desk. Since Treehopper is natively a 3.3V device, I didn’t need to fuss with logic-level conversion. Instead of powering on the motherboard to supply power to the SPI flash chip, I back-fed 3.3V from Treehopper to the VCC terminal of the SPI flash chip.

Software

The C# Treehopper.Libraries package provides an SpiFlash driver that can be used to interact with memory like this.

First thing’s first: discover the board, connect to it, and instantiate the SpiFlash class with the CS pin (I used Pin 3):

var board = await ConnectionService.Instance.GetFirstDeviceAsync();
await board.ConnectAsync();
mem = new SpiFlash(board.Spi, board.Pins[3]);
var id = await mem.ReadJedecIdAsync(); // quick sanity check

Next, I started out by reading all the data to a backup file. This would let me sanity-check the updated BIOS I was planning on flashing.

List<byte> bytes = new List<byte>();

// read the entire 16 MB flash chip, 128 bytes at a time
for (int i = 0; i < 16 * 1024 * 1024; i += 128)
{
  if (i % 4096 == 0) // to keep things performant, don't print every time
   Console.WriteLine($"Reading address: 0x{i:X}...");
  var chunk = await mem.ReadArrayAsync(i, 128);
  bytes.AddRange(chunk);
}

File.WriteAllBytes("backup.bin", bytes.ToArray());

After pulling the data off the chip, I used a hex editor to compare it side-by-side to an ASUS BIOS update file.

While this file format is undocumented, it was clear to me that ASUS had simply appended metadata to a raw binary file. One thing to look for is that you have exactly 16 MB worth of bytes — even if the BIOS doesn’t use all this space, it is included with the file to keep the update process simple.

After stripping off the metadata, I simply wrote out the file’s contents to the SPI flash chip:

byte[] data = File.ReadAllBytes("image-to-write.bin");
byte[] chunk = new byte[128];
var start = DateTime.Now;
// read the entire 16 MB flash chip
for (int i = 0; i < data.Length; i += 128) 
{ 
  if (i % 4096 == 0)
    Console.Write(string.Format("Writing address: 0x{0:X}...", i));
  Array.Copy(data, i, chunk, 0, 128); 
  byte[] verifyData; 
  int retries = -1; 
  do { 
    retries++; 
    if (retries > 0)
    {
      Debug.WriteLine("Retrying...");
    }
    await mem.Write(chunk, i);
    verifyData = await mem.ReadArrayAsync(i, 128);
    for (int j = 0; j < chunk.Length; j++)
    {
      if (chunk[j] != verifyData[j])
      {
        Debug.WriteLine("Verify mismatch at index: " + j); 
        break;
      }
    }
  } while (!chunk.SequenceEqual(verifyData)); 
  if (retries > 3)
  {
    Console.WriteLine("COULD NOT WRITE ADDRESS!!");
  }
  else
  {
    if (i % 4096 == 0)
      Console.WriteLine($"Verified! ({i * 100 / data.Length}% Complete)");
  }
}

I used a slow clock rate, and re-verified every byte written. I wrote the entire 16 MB file to flash — even the dead space at the end. Consequently, it took almost 12 minutes to complete.

After flashing the new firmware image, I disconnected the board, plugged in the motherboard, crossed my fingers, and hit the power button.

The computer came on immediately without a hitch.

It only took about 30 minutes of programming to get the BIOS update working properly. I plan to spend some time in the future to write a small GUI utility in case others ever need to reflash a BIOS file — or any other SPI flash chip.

Leave a comment