浏览代码

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>
Simon Glass 6 年之前
父节点
当前提交
11e36ccea1

+ 20 - 0
tools/binman/README.entries

@@ -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
 Entry: intel-cmc: Entry containing an Intel Chipset Micro Code (CMC) file
 -------------------------------------------------------------------------
 -------------------------------------------------------------------------
 
 

+ 9 - 0
tools/binman/entry.py

@@ -361,6 +361,15 @@ class Entry(object):
         """
         """
         self.WriteMapLine(fd, indent, self.name, self.offset, self.size)
         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):
     def GetArg(self, name, datatype=str):
         """Get the value of an entry argument or device-tree-node property
         """Get the value of an entry argument or device-tree-node property
 
 

+ 61 - 0
tools/binman/etype/fmap.py

@@ -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())

+ 5 - 2
tools/binman/etype/section.py

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

+ 109 - 0
tools/binman/fmap_util.py

@@ -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

+ 32 - 0
tools/binman/ftest.py

@@ -21,6 +21,7 @@ import control
 import elf
 import elf
 import fdt
 import fdt
 import fdt_util
 import fdt_util
+import fmap_util
 import test_util
 import test_util
 import tools
 import tools
 import tout
 import tout
@@ -1192,6 +1193,37 @@ class TestFunctional(unittest.TestCase):
         self.assertIn('Documentation is missing for modules: u_boot',
         self.assertIn('Documentation is missing for modules: u_boot',
                       str(e.exception))
                       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__":
 if __name__ == "__main__":
     unittest.main()
     unittest.main()

+ 29 - 0
tools/binman/test/67_fmap.dts

@@ -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 {
+		};
+	};
+};