Adding Chip Support to Flashrom

By Kevin Strotz | July 19, 2021

Flashrom: An Introduction

When working with embedded systems, various flash chips often need to be read or written for analysis. Flashrom is an open-source tool used for reading, writing, verifying, and erasing a wide assortment of flash chips. It currently has support for over 470 chips as well as large numbers of chipsets, mainboards, and various other devices. While this base is impressive and covers many common uses, it is possible to come across a chip that is not supported. Fortunately, due to the open-source nature of the Flashrom project, it is fairly straightforward to add the support ourselves with a bit of help from data sheets and documentation. The flashrom project is written in the C programming language, so you’ll want to be familiar with the basic syntax before diving in.

Getting the Code Base

The Flashrom code base can be downloaded from a few different sources, including the official repository, a Github mirror, or as a tarball from the official website. You’ll also need to make sure the necessary dependencies are installed, including

  • pciutils development package
  • zlib development package
  • libftdi development package
  • libusb development package
  • standard build utilities such as make and gcc
  • git (if checking out the source from a repository)

Once we have the source code and all of the dependencies ready to go, let’s build the standard binary first just to make sure we won’t run into any unexpected issues later. The source includes a Makefile, so all we need to do is navigate to the directory the source files are in and run the following command:

$ make

If you do get errors and you’re comfortable with C, try to correct them based on the compiler messages. When I first tried to build, I got a few errors that were relatively easy to fix. Another option is to try getting the code from a different source. I had to fix the code I downloaded as a tarball, but the correct code was already on Github.

Hopefully there is now a clean, standard build of flashrom. We can check by simply running the program:

$ ./flashrom
flashrom v1.2 on Linux 5.10.0-6parrot1-amd64 (x86_64)
flashrom is free software, get the source code at https://flashrom.org

Please select a programmer with the --programmer parameter.
To choose the mainboard of this computer use 'internal'. Valid choices are:
internal, dummy, nic3com, nicrealtek, gfxnvidia, drkaiser, satasii, atavia,
it8212, ft2232_spi, serprog, buspirate_spi, dediprog, developerbox, rayer_spi,
pony_spi, nicintel, nicintel_spi, nicintel_eeprom, ogp_spi, satamv, linux_mtd,
linux_spi, usbblaster_spi, pickit2_spi, ch341a_spi, digilent_spi, stlinkv3_spi.

Great! Let’s go ahead and remove all the files generated during our test compilation by running

$ make clean

Adding Chip Identification

The specifications for the many chips that flashrom supports are stored in two files: flashchips.c and flashchips.h. The data here allows flashrom to identify what chip it is interfacing with by reading specific memory addresses and comparing the values to this dataset. In order to add support for our chip, we need to create new entries in these files with the correct data. The chip I’ll be using for the demonstration is a Spansion S70FS01GS, with the data sheet found here. (Cypress and Spansion merged; this is the correct data sheet for the chip).

Let’s start with flashchips.h to build our entry. This file maps vendor and device ID values for a wide variety of chips. If we scroll down a bit, we can see that some Spansion chips are already supported and the vendor ID is already mapped:

#define SPANSION_ID     0x01    /* Spansion, same ID as AMD */

However, we still need to add the correct device ID for our specific chip. Section 12.2 of the data sheet (Device ID and Common Flash Interface (ID-CFI) Address Map) will give us the necessary information:

Byte Address Data Description
00h 01h Manufacturer ID for Cypress
01h 02h (1 Gb) Device ID Most Significant Byte - Memory Interface Type
02h 21h (1 Gb) Device ID Least Significant Byte - Density

The manufacturer ID matches the value already in flashchips.h, so no modification is needed there. However, we do need to add an additional line in the Spansion section in order to give flashrom the device ID. Since the numbers are followed by an ‘h’, they are hex values rather than decimal.

/* Added directly after existing Spansion chip IDs */
#define SPANSION_S70FS01GS  0x0221

Just to check our work, save flashchips.h and re-run

$ make

The code should still compile as long as we didn’t add a typo somewhere.

Locating Chip Parameters

Let’s move on to flashchips.c now. If we open the file, we see the following comment right near the top:

/*
 * .vendor      = Vendor name
 * .name        = Chip name
 * .bustype     = Supported flash bus types (Parallel, LPC...)
 * .manufacture_id  = Manufacturer chip ID
 * .model_id        = Model chip ID
 * .total_size      = Total size in (binary) kbytes
 * .page_size       = Page or eraseblock(?) size in bytes
 * .tested      = Test status
 * .probe       = Probe function
 * .probe_timing    = Probe function delay
 * .block_erasers[] = Array of erase layouts and erase functions
 * {
 *  .eraseblocks[]  = Array of { blocksize, blockcount }
 *  .block_erase    = Block erase function
 * }
 * .printlock       = Chip lock status function
 * .unlock      = Chip unlock function
 * .write       = Chip write function
 * .read        = Chip read function
 * .voltage     = Voltage range in millivolt
 */

This tells us exactly how to construct a new entry for our chip. We can look at some other entries to get the formatting, and fill in the information we already know. So far, we have the following:

{
    .vendor         = "Spansion",
    .name           = "S70FS01GS",
    .bustype        = ???
    .manufacture_id = SPANSION_ID,          // These are the values we just
    .model_id       = SPANSION_S70FS01GS,   // added to flashchips.h
    .total_size     = ???
    .page_size      = ???
    .feature_bits   = ???
    .tested         = ???
    .probe          = ???
    .probe_timing   = ???
    .block_erasers  =
    {
        ???
    }
    .printlock      = ???
    .unlock         = ???
    .read           = ???
    .write          = ???
    .voltage        = ???
  }

Clearly there’s still a bit of information we need to track down, but flashrom has a very helpful page that talks about what each field means if you’re not sure. It’s also beneficial to find an entry of a similar chip that is already supported, as the options set for that chip can help guide you to the best option for your new entry. If you don’t know what options are available for a certain parameter, some of them are defined in the flash.h file. Others are found in different files and can be located with a quick search.

  • Let’s start with bus type. In flash.h we find the following options:

    enum chipbustype {
    BUS_NONE    = 0,
    BUS_PARALLEL    = 1 << 0,
    BUS_LPC     = 1 << 1,
    BUS_FWH     = 1 << 2,
    BUS_SPI     = 1 << 3,
    BUS_PROG    = 1 << 4,
    BUS_NONSPI  = BUS_PARALLEL | BUS_LPC | BUS_FWH,
    };
  • If we check the data sheet, it is clear that this is a Serial Peripheral Interface (SPI) chip. Therefore, we’ll use BUS_SPI as the option for bus type.

  • Next, we need the total size. The data sheet says that this is a 128 MB chip. We need to be careful with our entry because the entry needs to be in kilobytes and working with powers of two is different that powers of ten. If we look around flashchips.c, we can find a couple of other chips that use the size 131072. A quick search confirms that these are also 128 MB chips, so this seems promising. We can double check by verifying that (128 * 1024) == 131072, so we’ll use that number.

  • Page size can be tricky. Even the flashrom development guidelines admit as much, saying either to read a long explanation or ignore it and use a size of 256. The data sheet says that the chip has a 256 or 512 byte page programming buffer, which seems good enough. We’ll set this to 256 and move on.

  • The feature bits field is a bit mask that encodes various possible chip features. There are many possibilities found in flash.h, which we will need to narrow down. Unfortunately, the only way to do this is to look at the features in the flash.h file and match them to the data sheet. Searching through the data sheet for some of the flags, we find the Write Enable feature (WREN), Secure Silicon Region (OTP), 4 Byte Address Read (4BA_READ), 4BA Fast Read (4BA_FAST_READ), and 4BA Write (4BA_WRITE). These features combine into the mask of

    FEATURE_WRSR_WREN | FEATURE_OTP | FEATURE_4BA_NATIVE
  • The tested field just indicates what features have been tested with flashrom on this chip. Since this is a new chip, we will set it to TEST_UNTESTED

  • The probe function tells flashrom how to fetch the manufacturer and chip ID. The development guidelines helpfully say that this is likely probe_spi_rdid for our chip if the data sheet mentions code 0x9f as an identification code. Page 77 of the data sheet tells us that 0x9f is the Read Identification (RDID) command on this chip, so we can use the probe_spi_rdid function.

  • The probe timing field is not applicable to SPI chips, so we will set it to TIMING_ZERO.

  • The block erasers field tells flashrom how to erase sectors of memory, with both a function and a memory layout. The full list can be found in chipdrivers.h, but we can look at similar chips to help narrow down our choices. A few potential functions found in other entries are spi_block_erase_dc, spi_block_erase_60, and spi_block_erase_c7. However, the data sheet tells us that the 0x60 and 0xc7 commands are not supported. The 0xdc command is supported and operates on 256 KB memory sectors, of which there are 512 sectors to make the full 128 MB. Therefore, our block eraser entry will look like this:

    .block_erasers =
    { // Outer braces are necessary in case of multiple block erasers
        {
            .eraseblocks = { { 256 * 1024, 512 } },
            .block_erase = spi_block_erase_dc,
        }
    }
  • The printlock and unlock fields are related, and the development guidelines recommend looking at an already supported chip and comparing the status register descriptions to determine if we can use the same function. A fairly similar chip is the Spansion S25FL512S, and the data sheet can be found here. If we compare the status registers of the two chips (found in Section 7 of the respective datasheets), we see that they are essentially identical. Therefore, it is likely we can reuse the printlock and unlock functions from that chip, which are spi_prettyprint_status_register_bp2_ep_srwd and spi_disable_blockprotect_bp2_srwd respectively.

  • The write and read functions are fairly self-explanatory. Looking at other chips, we can see the most common choices are spi_chip_write_256 and spi_chip_read. This seems to match our chip, so we can go ahead and use them as well.

  • Finally, we need to set a voltage parameter defining the upper and lower bounds of the chip supply voltage. On page 5 of the data sheet, we can find that the supply voltage of the FS-S family of chips is from 1.7V to 2.0V. Be careful to note that flashrom has the units as millivolts, so the values to enter are 1700 and 2000 respectively.

That’s it! We now have all the needed information to add a complete new entry to flashchips.c, which should look like this:

{
    .vendor     = "Spansion",
    .name       = "S70FS01GS",
    .bustype    = BUS_SPI,
    .manufacture_id = SPANSION_ID,
    .model_id   = SPANSION_S70FS01GS,
    .total_size = 131072, /* 1024 Mb (=> 128 MB) */
    .page_size  = 256,
    /* OTP: 1024 bytes total, 32 bytes reserved; OTP read 0x4b, OTP write 0x42 */
    .feature_bits   = FEATURE_WRSR_WREN | FEATURE_OTP | FEATURE_4BA_NATIVE,
    .tested     = TEST_UNTESTED,
    .probe      = probe_spi_rdid,
    .probe_timing   = TIMING_ZERO,
    .block_erasers  =
    {
        {
            .eraseblocks = { { 256 * 1024, 512 } },
            .block_erase = spi_block_erase_dc,
        }
    },
    .printlock  = spi_prettyprint_status_register_bp2_ep_srwd,
    .unlock     = spi_disable_blockprotect_bp2_srwd,
    .write      = spi_chip_write_256,
    .read       = spi_chip_read,
    .voltage    = { 1700, 2000 },
},

This entry will go after the last existing Spansion chip to help keep things sorted. Now all we have to do is re-run our compilation:

$ make

and we should have a new binary that supports our chip!

Testing Our Addition

Now we can give it a test to make sure everything is functioning. The exact steps here will vary depending on what configuration you are using to read the chip. Wire your chip to the programmer that you are using and plug it into your computer. The specific options are dependent on your equipment, but the basic command to read the memory from your chip should look something like this:

$ ./flashrom -p ft2232_spi:type=232H -r flash.bin 

This will read from the flash memory attached to an FTDI 232H programmer and write it to a file named flash.bin in the current directory. It may take a few minutes to run, so don’t worry if it doesn’t finish instantly. If you need help or want to see more options, you can run ./flashrom --help to find out more. This same process can be used to add support for a wide variety of chips, and if you like you can always submit your addition back to the project as a patch! You can find their development guidelines here.

Conclusion

In this blog post, we demonstrated how to add support for a new chip in flashrom to be able to easily read and write to it. We are always happy to discuss hardware reverse engineering and security issues, so don’t hesitate to contact us at any time with questions or to discuss how we can help with your product security and engineering needs.