binman: Add support for flashrom FMAP

Add an entry which can hold an FMAP region as used by flashrom, an
open-source flashing tool used on Linux x86 machines. This provides a
simplified non-hierarchical view of the entries in the image and has a
signature at the start to allow flashrom to find it in the image.

Signed-off-by: Simon Glass <sjg@chromium.org>
lime2-spi
Simon Glass 6 years ago
parent 5a5da7ce15
commit 11e36ccea1
  1. 20
      tools/binman/README.entries
  2. 9
      tools/binman/entry.py
  3. 61
      tools/binman/etype/fmap.py
  4. 7
      tools/binman/etype/section.py
  5. 109
      tools/binman/fmap_util.py
  6. 32
      tools/binman/ftest.py
  7. 29
      tools/binman/test/67_fmap.dts

@ -26,6 +26,26 @@ example the 'u_boot' entry which provides the filename 'u-boot.bin'.
Entry: fmap: An entry which contains an Fmap section
----------------------------------------------------
Properties / Entry arguments:
None
FMAP is a simple format used by flashrom, an open-source utility for
reading and writing the SPI flash, typically on x86 CPUs. The format
provides flashrom with a list of areas, so it knows what it in the flash.
It can then read or write just a single area, instead of the whole flash.
The format is defined by the flashrom project, in the file lib/fmap.h -
see www.flashrom.org/Flashrom for more information.
When used, this entry will be populated with an FMAP which reflects the
entries in the current image. Note that any hierarchy is squashed, since
FMAP does not support this.
Entry: intel-cmc: Entry containing an Intel Chipset Micro Code (CMC) file
-------------------------------------------------------------------------

@ -361,6 +361,15 @@ class Entry(object):
"""
self.WriteMapLine(fd, indent, self.name, self.offset, self.size)
def GetEntries(self):
"""Return a list of entries contained by this entry
Returns:
List of entries, or None if none. A normal entry has no entries
within it so will return None
"""
return None
def GetArg(self, name, datatype=str):
"""Get the value of an entry argument or device-tree-node property

@ -0,0 +1,61 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2018 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
# Entry-type module for a Flash map, as used by the flashrom SPI flash tool
#
from entry import Entry
import fmap_util
class Entry_fmap(Entry):
"""An entry which contains an Fmap section
Properties / Entry arguments:
None
FMAP is a simple format used by flashrom, an open-source utility for
reading and writing the SPI flash, typically on x86 CPUs. The format
provides flashrom with a list of areas, so it knows what it in the flash.
It can then read or write just a single area, instead of the whole flash.
The format is defined by the flashrom project, in the file lib/fmap.h -
see www.flashrom.org/Flashrom for more information.
When used, this entry will be populated with an FMAP which reflects the
entries in the current image. Note that any hierarchy is squashed, since
FMAP does not support this.
"""
def __init__(self, section, etype, node):
Entry.__init__(self, section, etype, node)
def _GetFmap(self):
"""Build an FMAP from the entries in the current image
Returns:
FMAP binary data
"""
def _AddEntries(areas, entry):
entries = entry.GetEntries()
if entries:
for subentry in entries.values():
_AddEntries(areas, subentry)
else:
areas.append(fmap_util.FmapArea(entry.image_pos or 0,
entry.size or 0, entry.name, 0))
entries = self.section.GetEntries()
areas = []
for entry in entries.values():
_AddEntries(areas, entry)
return fmap_util.EncodeFmap(self.section.GetSize() or 0, self.name,
areas)
def ObtainContents(self):
"""Obtain a placeholder for the fmap contents"""
self.SetContents(self._GetFmap())
return True
def ProcessContents(self):
self.SetContents(self._GetFmap())

@ -30,8 +30,8 @@ class Entry_section(Entry):
hierarchical images to be created. See 'Sections and hierarchical images'
in the binman README for more information.
"""
def __init__(self, image, etype, node):
Entry.__init__(self, image, etype, node)
def __init__(self, section, etype, node):
Entry.__init__(self, section, etype, node)
self._section = bsection.Section(node.name, node)
def ProcessFdt(self, fdt):
@ -89,3 +89,6 @@ class Entry_section(Entry):
fd: File to write the map to
"""
self._section.WriteMap(fd, indent)
def GetEntries(self):
return self._section.GetEntries()

@ -0,0 +1,109 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2018 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
# Support for flashrom's FMAP format. This supports a header followed by a
# number of 'areas', describing regions of a firmware storage device,
# generally SPI flash.
import collections
import struct
# constants imported from lib/fmap.h
FMAP_SIGNATURE = '__FMAP__'
FMAP_VER_MAJOR = 1
FMAP_VER_MINOR = 0
FMAP_STRLEN = 32
FMAP_AREA_STATIC = 1 << 0
FMAP_AREA_COMPRESSED = 1 << 1
FMAP_AREA_RO = 1 << 2
FMAP_HEADER_LEN = 56
FMAP_AREA_LEN = 42
FMAP_HEADER_FORMAT = '<8sBBQI%dsH'% (FMAP_STRLEN)
FMAP_AREA_FORMAT = '<II%dsH' % (FMAP_STRLEN)
FMAP_HEADER_NAMES = (
'signature',
'ver_major',
'ver_minor',
'base',
'image_size',
'name',
'nareas',
)
FMAP_AREA_NAMES = (
'offset',
'size',
'name',
'flags',
)
# These are the two data structures supported by flashrom, a header (which
# appears once at the start) and an area (which is repeated until the end of
# the list of areas)
FmapHeader = collections.namedtuple('FmapHeader', FMAP_HEADER_NAMES)
FmapArea = collections.namedtuple('FmapArea', FMAP_AREA_NAMES)
def ConvertName(field_names, fields):
"""Convert a name to something flashrom likes
Flashrom requires upper case, underscores instead of hyphens. We remove any
null characters as well. This updates the 'name' value in fields.
Args:
field_names: List of field names for this struct
fields: Dict:
key: Field name
value: value of that field (string for the ones we support)
"""
name_index = field_names.index('name')
fields[name_index] = fields[name_index].replace('\0', '').replace('-', '_').upper()
def DecodeFmap(data):
"""Decode a flashmap into a header and list of areas
Args:
data: Data block containing the FMAP
Returns:
Tuple:
header: FmapHeader object
List of FmapArea objects
"""
fields = list(struct.unpack(FMAP_HEADER_FORMAT, data[:FMAP_HEADER_LEN]))
ConvertName(FMAP_HEADER_NAMES, fields)
header = FmapHeader(*fields)
areas = []
data = data[FMAP_HEADER_LEN:]
for area in range(header.nareas):
fields = list(struct.unpack(FMAP_AREA_FORMAT, data[:FMAP_AREA_LEN]))
ConvertName(FMAP_AREA_NAMES, fields)
areas.append(FmapArea(*fields))
data = data[FMAP_AREA_LEN:]
return header, areas
def EncodeFmap(image_size, name, areas):
"""Create a new FMAP from a list of areas
Args:
image_size: Size of image, to put in the header
name: Name of image, to put in the header
areas: List of FmapArea objects
Returns:
String containing the FMAP created
"""
def _FormatBlob(fmt, names, obj):
params = [getattr(obj, name) for name in names]
return struct.pack(fmt, *params)
values = FmapHeader(FMAP_SIGNATURE, 1, 0, 0, image_size, name, len(areas))
blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, values)
for area in areas:
blob += _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, area)
return blob

@ -21,6 +21,7 @@ import control
import elf
import fdt
import fdt_util
import fmap_util
import test_util
import tools
import tout
@ -1192,6 +1193,37 @@ class TestFunctional(unittest.TestCase):
self.assertIn('Documentation is missing for modules: u_boot',
str(e.exception))
def testFmap(self):
"""Basic test of generation of a flashrom fmap"""
data = self._DoReadFile('67_fmap.dts')
fhdr, fentries = fmap_util.DecodeFmap(data[32:])
expected = U_BOOT_DATA + '!' * 12 + U_BOOT_DATA + 'a' * 12
self.assertEqual(expected, data[:32])
self.assertEqual('__FMAP__', fhdr.signature)
self.assertEqual(1, fhdr.ver_major)
self.assertEqual(0, fhdr.ver_minor)
self.assertEqual(0, fhdr.base)
self.assertEqual(16 + 16 +
fmap_util.FMAP_HEADER_LEN +
fmap_util.FMAP_AREA_LEN * 3, fhdr.image_size)
self.assertEqual('FMAP', fhdr.name)
self.assertEqual(3, fhdr.nareas)
for fentry in fentries:
self.assertEqual(0, fentry.flags)
self.assertEqual(0, fentries[0].offset)
self.assertEqual(4, fentries[0].size)
self.assertEqual('RO_U_BOOT', fentries[0].name)
self.assertEqual(16, fentries[1].offset)
self.assertEqual(4, fentries[1].size)
self.assertEqual('RW_U_BOOT', fentries[1].name)
self.assertEqual(32, fentries[2].offset)
self.assertEqual(fmap_util.FMAP_HEADER_LEN +
fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
self.assertEqual('FMAP', fentries[2].name)
if __name__ == "__main__":
unittest.main()

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
section@0 {
read-only;
name-prefix = "ro-";
size = <0x10>;
pad-byte = <0x21>;
u-boot {
};
};
section@1 {
name-prefix = "rw-";
size = <0x10>;
pad-byte = <0x61>;
u-boot {
};
};
fmap {
};
};
};
Loading…
Cancel
Save