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
andfw_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 offw_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.