Boot flash access from Linux

Published on January 9, 2015

Archived Notice

This article has been archived and may contain broken links, photos and out-of-date information. If you have any questions, please Contact Us.

Every so often, we're asked about accessing the Boot ROM (flash) from Linux. Sometimes it's related to the question of upgrading U-Boot. Other times the question surrounds changing U-Boot environment variables or simply using the flash EEPROM to store some board-specific data.

In this post, we'll describe the basics of how this work and give some examples of how the pieces of software fit together.

Quick reference for the impatient

  • The MTD (Memory Technology Device) driver is used to map the serial EEPROM to devices
  • The devices involved are named /dev/mtd*: root@trusty-dev:~# ls -l /dev/mtd* crw------- 1 root root 90, 0 Jan 9 11:13 /dev/mtd0 crw------- 1 root root 90, 1 Jan 9 11:13 /dev/mtd0ro crw------- 1 root root 90, 2 Jan 9 11:13 /dev/mtd1 crw------- 1 root root 90, 3 Jan 9 11:13 /dev/mtd1ro crw------- 1 root root 90, 4 Jan 9 11:13 /dev/mtd2 crw------- 1 root root 90, 5 Jan 9 11:13 /dev/mtd2ro brw-rw---- 1 root disk 31, 0 Jan 9 11:13 /dev/mtdblock0 brw-rw---- 1 root disk 31, 1 Jan 9 11:13 /dev/mtdblock1 brw-rw---- 1 root disk 31, 2 Jan 9 11:13 /dev/mtdblock2
  • The primary utilities used to read and write to these devices are in the mtd-utils package and are maintained at https://www.linux-mtd.infradead.org/
  • The U-Boot utilities fw_printenv and fw_setenv as described by the Wiki at Denx Software Engineering
  • We store our environment variables in the second partition of the flash: root@trusty-dev:~# cat /etc/fw_env.config /dev/mtd1 0x00000 0x2000 0x1000 2
  • You can override our default partitioning by adding a clause mtdparts= to your kernel command line in your boot script. The full documentation is here in the Kconfig file
  • The name of the MTD partition will vary by kernel version, and is spi32766.0 in kernels 3.10.17 and 3.10.31.
  • This example will reserve 16k for a splash screen, 4k for a data partition, and leave the rest unidentified: mtdparts=spi32766.0:768k(U-Boot),8k(Env),16k(splash),4k(mydata),-(rest)
  • The file /proc/mtd can be used to see the current partitioning: root@trusty-dev:~# cat /proc/mtd dev: size erasesize name mtd0: 000c0000 00001000 "U-Boot" mtd1: 00002000 00001000 "Env" mtd2: 00001000 00001000 "mydata" mtd3: 0013d000 00001000 "rest"
  • Our standard serial EEPROM is 2MiBytes.
  • We reserve 768k for the U-Boot boot loader, and 8k for the U-Boot environment block, so there's a little over 1MiB available for other uses
  • U-Boot versions before 2014.07 didn't properly handle this. A patch from Dustin Byford may be needed if you're using an old version of fw_setenv.

Maybe that wasn't such a quick reference.

There are a lot of details to consider when describing things, but the basics are fairly simple. The mtd device driver(s) provide access to the serial EEPROM through a set of character devices, each of which corresponds to a region of the 2MiB of storage.

Background

We use a lot of jargon above, which may not be

  • EEPROM == Electrically Erasable Programmable Read Only Memory
  • NOR = Not OR - refers to the way that the memory is stored and this has implications on the characteristics of the memory. In particular, NOR flash is very reliable and doesn't require error correction. Refer to this Wikipedia entry for more details about flash memory
  • SPI NOR/serial EEPROM - refers to the fact that we use the Serial Peripheral Interface (SPI) bus to communicate with the ROM. This is a serial bus, with a clock, two data lines (MOSI and MISO) and a chip select.
  • Boot ROM - We program the fuses on our i.MX6-based boards to point at the serial EEPROM, so we refer to it as the Boot ROM

Simplest usage

You can access the devices using normal filesystem calls for reading:

root@trusty-dev:~# hexdump -C /dev/mtd1ro | head 00000000 9d 6d 56 c6 62 61 75 64 72 61 74 65 3d 31 31 35 |.mV.baudrate=115| 00000010 32 30 30 00 62 6f 61 72 64 3d 6e 69 74 72 6f 67 |200.board=nitrog| 00000020 65 6e 36 78 00 62 6f 6f 74 63 6d 64 3d 66 6f 72 |en6x.bootcmd=for| 00000030 20 64 74 79 70 65 20 69 6e 20 24 7b 62 6f 6f 74 | dtype in ${boot| 00000040 64 65 76 73 7d 3b 20 64 6f 20 69 66 20 69 74 65 |devs}; do if ite| 00000050 73 74 2e 73 20 22 78 75 73 62 22 20 3d 3d 20 22 |st.s "xusb" == "| 00000060 78 24 7b 64 74 79 70 65 7d 22 20 3b 20 74 68 65 |x${dtype}" ; the| 00000070 6e 20 75 73 62 20 73 74 61 72 74 20 3b 66 69 3b |n usb start ;fi;| 00000080 20 66 6f 72 20 64 69 73 6b 20 69 6e 20 30 20 31 | for disk in 0 1| 00000090 20 3b 20 64 6f 20 24 7b 64 74 79 70 65 7d 20 64 | ; do ${dtype} d|

The simplest way to program the serial EEPROM is to use the flash_erase tool to erase a partition and flashcp to program it.

root@trusty-dev:~# flash_erase /dev/mtd1 0 0 Erasing 4 Kibyte @ 1000 -- 100 % complete root@trusty-dev:~# hexdump -C /dev/mtd1ro | head 00000000 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| * 00002000 root@trusty-dev:~# flashcp u-boot-env /dev/mtd1 root@trusty-dev:~# hexdump -C /dev/mtd1ro | head 00000000 fe 4c 3c e6 62 61 75 64 72 61 74 65 3d 31 31 35 |.L<.baudrate=115| 00000010 32 30 30 00 62 6f 61 72 64 3d 6e 69 74 72 6f 67 |200.board=nitrog| 00000020 65 6e 36 78 00 62 6f 6f 74 32 71 74 5f 75 70 64 |en6x.boot2qt_upd| 00000030 61 74 65 5f 73 74 61 74 65 3d 76 61 6c 69 64 00 |ate_state=valid.| 00000040 62 6f 6f 74 61 72 67 73 3d 65 6e 61 62 6c 65 5f |bootargs=enable_| 00000050 77 61 69 74 5f 6d 6f 64 65 3d 6f 66 66 20 63 6f |wait_mode=off co| 00000060 6e 73 6f 6c 65 3d 74 74 79 6d 78 63 31 2c 31 31 |nsole=ttymxc1,11| 00000070 35 32 30 30 20 76 6d 61 6c 6c 6f 63 3d 34 30 30 |5200 vmalloc=400| 00000080 4d 20 63 6f 6e 73 6f 6c 65 62 6c 61 6e 6b 3d 30 |M consoleblank=0| 00000090 20 76 69 64 65 6f 3d 6d 78 63 66 62 30 3a 64 65 |video=mxcfb0:de|

U-Boot environment access

The tools fw_printenv and fw_setenv can be used to get and set environment variables:

root@trusty-dev:~# fw_printenv | head -n 3 baudrate=115200 board=nitrogen6x boot2qt_update_state=valid root@trusty-dev:~# fw_printenv board board=nitrogen6x root@trusty-dev:~# fw_setenv board myboard root@trusty-dev:~# fw_printenv board board=myboard

To clear an environment variable, use a single parameter (the variable name) to fw_setenv:

root@trusty-dev:~# fw_setenv blah BLAH root@trusty-dev:~# fw_printenv blah blah=BLAH root@trusty-dev:~# fw_setenv blah root@trusty-dev:~# fw_printenv blah ## Error: "blah" not defined

These utilities require a configuration file in /etc/fw_env.config:

root@trusty-dev:~# fw_printenv Cannot parse config file: No such file or directory root@trusty-dev:~# cat > /etc/fw_env.config /dev/mtd1 0x00000 0x2000 0x1000 2 ^D root@trusty-dev:~# fw_printenv board board=nitrogen6x

As mentioned above, they also require a patch for versions that pre-date U-Boot 2014.07:

root@trusty-dev:~# fw_setenv board myboard End of range reached, aborting Error: can't write fw_env to flash

Other uses

The serial EEPROM that we use for boot are not large enough to store a typical Linux kernel or RAM disk, but they
can be used to store other things.

We mentioned splash screens, which are a natural fit, since they can provide a polished output even if no bootable SD card is present.

They can be used to store other things as well, as long as you keep a handful of things in mind:

  • Flash is quick to write, but slow to erase
  • Flash EEPROM wears out based on erase cycles
  • The parts we use are rated to 100,000 erase cycles
  • Erasing a sector sets all bits to 1's

This form of storage is especially suitable for things that are small and infrequently written such as serial numbers, machine configuration, maintenance logs and the like.

We hope this helps you understand how to get the most out of your Boundary Devices board.

As always, contact us if you have questions or concerns.