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 co vmalloc="400|" consoleblank="0|">

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.