FROMLIST: binman: Support marking FMAP areas as preserved

Add an entry flag called 'preserve' to indicate that an entry should be
preserved by firmware updates. Propagate this to FMAP too.

BUG=b:268750177
TEST=binman test

Change-Id: Iebc71f0bbadc0604c0cbd777151bd8bb29cbffbc
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/u-boot/+/4246314
Reviewed-by: Wai-Hong Tam <waihong@google.com>
Commit-Queue: Wai-Hong Tam <waihong@google.com>
diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst
index 03a99a1..31d4f98 100644
--- a/tools/binman/binman.rst
+++ b/tools/binman/binman.rst
@@ -838,6 +838,14 @@
     is the symbol to lookup (relative to elf-base-sym) and <offset> is an offset
     to add to that value.
 
+preserve:
+    Indicates that this entry should be preserved by any firmware updates. This
+    flag should be checked by the updater when it is deciding which entries to
+    update. This flag is normally attached to sections but can be attached to
+    a single entry in a section if the updater supports it. Note that binman
+    itself has no control over the updater's behaviour, so this is just a
+    signal. It is not enforced by binman.
+
 Examples of the above options can be found in the tests. See the
 tools/binman/test directory.
 
diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index 7a04a61..1965924 100644
--- a/tools/binman/entries.rst
+++ b/tools/binman/entries.rst
@@ -887,6 +887,11 @@
 from the FMAP by using the offset information. This convention does not
 seem to be documented, but is used in Chromium OS.
 
+To mark an area as preserved, use the normal 'preserved' flag in the entry.
+This will result in the corresponding FMAP area having the
+FMAP_AREA_PRESERVE flag. This flag does not automatically propagate down to
+child entries.
+
 CBFS entries appear as a single entry, i.e. the sub-entries are ignored.
 
 
diff --git a/tools/binman/entry.py b/tools/binman/entry.py
index 5eacc5f..fd617e4 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -100,6 +100,10 @@
             appear in the map
         optional (bool): True if this entry contains an optional external blob
         overlap (bool): True if this entry overlaps with others
+        preserve (bool): True if this entry should be preserved when updating
+            firmware. This means that it will not be changed by the update.
+            This is just a signal: enforcement of this is up to the updater.
+            This flag does not automatically propagate down to child entries.
     """
     fake_dir = None
 
@@ -148,6 +152,7 @@
         self.overlap = False
         self.elf_base_sym = None
         self.offset_from_elf = None
+        self.preserve = False
 
     @staticmethod
     def FindEntryClass(etype, expanded):
@@ -310,6 +315,8 @@
         self.offset_from_elf = fdt_util.GetPhandleNameOffset(self._node,
                                                              'offset-from-elf')
 
+        self.preserve = fdt_util.GetBool(self._node, 'preserve')
+
     def GetDefaultFilename(self):
         return None
 
diff --git a/tools/binman/etype/fmap.py b/tools/binman/etype/fmap.py
index 0c57620..b35450f 100644
--- a/tools/binman/etype/fmap.py
+++ b/tools/binman/etype/fmap.py
@@ -33,6 +33,11 @@
     from the FMAP by using the offset information. This convention does not
     seem to be documented, but is used in Chromium OS.
 
+    To mark an area as preserved, use the normal 'preserved' flag in the entry.
+    This will result in the corresponding FMAP area having the
+    FMAP_AREA_PRESERVE flag. This flag does not automatically propagate down to
+    child entries.
+
     CBFS entries appear as a single entry, i.e. the sub-entries are ignored.
     """
     def __init__(self, section, etype, node):
@@ -48,6 +53,12 @@
             entries = entry.GetEntries()
             tout.debug("fmap: Add entry '%s' type '%s' (%s subentries)" %
                        (entry.GetPath(), entry.etype, to_hex_size(entries)))
+
+            # Collect any flag (separate lines to ensure code coverage)
+            flags = 0
+            if entry.preserve:
+                flags = fmap_util.FMAP_AREA_PRESERVE
+
             if entries and entry.etype != 'cbfs':
                 # Create an area for the section, which encompasses all entries
                 # within it
@@ -59,7 +70,7 @@
                 # Drop @ symbols in name
                 name = entry.name.replace('@', '')
                 areas.append(
-                    fmap_util.FmapArea(pos, entry.size or 0, name, 0))
+                    fmap_util.FmapArea(pos, entry.size or 0, name, flags))
                 for subentry in entries.values():
                     _AddEntries(areas, subentry)
             else:
@@ -67,7 +78,7 @@
                 if pos is not None:
                     pos -= entry.section.GetRootSkipAtStart()
                 areas.append(fmap_util.FmapArea(pos or 0, entry.size or 0,
-                                                entry.name, 0))
+                                                entry.name, flags))
 
         entries = self.GetImage().GetEntries()
         areas = []
diff --git a/tools/binman/fmap_util.py b/tools/binman/fmap_util.py
index 1ce63d1..82e0f74 100644
--- a/tools/binman/fmap_util.py
+++ b/tools/binman/fmap_util.py
@@ -45,6 +45,9 @@
     'flags',
 )
 
+# Flags supported by areas (bits 2:0 are unused so not included here)
+FMAP_AREA_PRESERVE = 1 << 3  # Preserved by any firmware updates
+
 # 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)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 6b203df..8df2fb3 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -1702,7 +1702,7 @@
         self.assertEqual(b'SECTION0', fentry.name)
         self.assertEqual(0, fentry.offset)
         self.assertEqual(16, fentry.size)
-        self.assertEqual(0, fentry.flags)
+        self.assertEqual(fmap_util.FMAP_AREA_PRESERVE, fentry.flags)
 
         fentry = next(fiter)
         self.assertEqual(b'RO_U_BOOT', fentry.name)
diff --git a/tools/binman/test/067_fmap.dts b/tools/binman/test/067_fmap.dts
index 9c0e293..24fa635 100644
--- a/tools/binman/test/067_fmap.dts
+++ b/tools/binman/test/067_fmap.dts
@@ -11,6 +11,7 @@
 			name-prefix = "ro-";
 			size = <0x10>;
 			pad-byte = <0x21>;
+			preserve;
 
 			u-boot {
 			};