binman: Support finding symbols in sub-sections

At present binman only supports resolving symbols in the same section as
the binary that uses it. This is quite limited because we often need to
group entries into different sections.

Enhance the algorithm to search the entire image for symbols.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/binman/elf.py b/tools/binman/elf.py
index 249074a..03b49d7 100644
--- a/tools/binman/elf.py
+++ b/tools/binman/elf.py
@@ -132,7 +132,8 @@
                                  (msg, sym.size))
 
             # Look up the symbol in our entry tables.
-            value = section.LookupSymbol(name, sym.weak, msg, base.address)
+            value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
+                                                         base.address)
             if value is None:
                 value = -1
                 pack_string = pack_string.lower()
diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py
index e3d218a..7a12801 100644
--- a/tools/binman/elf_test.py
+++ b/tools/binman/elf_test.py
@@ -45,10 +45,12 @@
     def GetPath(self):
         return 'section_path'
 
-    def LookupSymbol(self, name, weak, msg, base_addr):
+    def LookupImageSymbol(self, name, weak, msg, base_addr):
         """Fake implementation which returns the same value for all symbols"""
         return self.sym_value
 
+    def GetImage(self):
+        return self
 
 def BuildElfTestFiles(target_dir):
     """Build ELF files used for testing in binman
diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py
index 3dd5f58..9c6334c 100644
--- a/tools/binman/etype/section.py
+++ b/tools/binman/etype/section.py
@@ -385,7 +385,7 @@
                 return entry.GetData()
         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
 
-    def LookupSymbol(self, sym_name, optional, msg, base_addr):
+    def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None):
         """Look up a symbol in an ELF file
 
         Looks up a symbol in an ELF file. Only entry types which come from an
@@ -428,18 +428,20 @@
                              (msg, sym_name))
         entry_name, prop_name = m.groups()
         entry_name = entry_name.replace('_', '-')
-        entry = self._entries.get(entry_name)
+        if not entries:
+            entries = self._entries
+        entry = entries.get(entry_name)
         if not entry:
             if entry_name.endswith('-any'):
                 root = entry_name[:-4]
-                for name in self._entries:
+                for name in entries:
                     if name.startswith(root):
                         rest = name[len(root):]
                         if rest in ['', '-img', '-nodtb']:
-                            entry = self._entries[name]
+                            entry = entries[name]
         if not entry:
             err = ("%s: Entry '%s' not found in list (%s)" %
-                   (msg, entry_name, ','.join(self._entries.keys())))
+                   (msg, entry_name, ','.join(entries.keys())))
             if optional:
                 print('Warning: %s' % err, file=sys.stderr)
                 return None
@@ -648,3 +650,32 @@
         """
         for entry in self._entries.values():
             entry.CheckMissing(missing_list)
+
+    def _CollectEntries(self, entries, entries_by_name, add_entry):
+        """Collect all the entries in an section
+
+        This builds up a dict of entries in this section and all subsections.
+        Entries are indexed by path and by name.
+
+        Since all paths are unique, entries will not have any conflicts. However
+        entries_by_name make have conflicts if two entries have the same name
+        (e.g. with different parent sections). In this case, an entry at a
+        higher level in the hierarchy will win over a lower-level entry.
+
+        Args:
+            entries: dict to put entries:
+                key: entry path
+                value: Entry object
+            entries_by_name: dict to put entries
+                key: entry name
+                value: Entry object
+            add_entry: Entry to add
+        """
+        entries[add_entry.GetPath()] = add_entry
+        to_add = add_entry.GetEntries()
+        if to_add:
+            for entry in to_add.values():
+                entries[entry.GetPath()] = entry
+            for entry in to_add.values():
+                self._CollectEntries(entries, entries_by_name, entry)
+        entries_by_name[add_entry.name] = add_entry
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index e753a34..5016060 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -4139,6 +4139,21 @@
             }
         self.assertEqual(expected, props)
 
+    def testSymbolsSubsection(self):
+        """Test binman can assign symbols from a subsection"""
+        elf_fname = self.ElfTestFile('u_boot_binman_syms')
+        syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
+        addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
+        self.assertEqual(syms['_binman_u_boot_spl_prop_offset'].address, addr)
+
+        self._SetupSplElf('u_boot_binman_syms')
+        data = self._DoReadFile('187_symbols_sub.dts')
+        sym_values = struct.pack('<LQLL', 0x00, 0x1c, 0x28, 0x04)
+        expected = (sym_values + U_BOOT_SPL_DATA[20:] +
+                    tools.GetBytes(0xff, 1) + U_BOOT_DATA + sym_values +
+                    U_BOOT_SPL_DATA[20:])
+        self.assertEqual(expected, data)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/binman/image.py b/tools/binman/image.py
index d65ab88..3622efc 100644
--- a/tools/binman/image.py
+++ b/tools/binman/image.py
@@ -324,3 +324,48 @@
             _DoLine(lines, _EntryToStrings(entry))
             selected_entries.append(entry)
         return selected_entries, lines, widths
+
+    def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
+        """Look up a symbol in an ELF file
+
+        Looks up a symbol in an ELF file. Only entry types which come from an
+        ELF image can be used by this function.
+
+        This searches through this image including all of its subsections.
+
+        At present the only entry properties supported are:
+            offset
+            image_pos - 'base_addr' is added if this is not an end-at-4gb image
+            size
+
+        Args:
+            sym_name: Symbol name in the ELF file to look up in the format
+                _binman_<entry>_prop_<property> where <entry> is the name of
+                the entry and <property> is the property to find (e.g.
+                _binman_u_boot_prop_offset). As a special case, you can append
+                _any to <entry> to have it search for any matching entry. E.g.
+                _binman_u_boot_any_prop_offset will match entries called u-boot,
+                u-boot-img and u-boot-nodtb)
+            optional: True if the symbol is optional. If False this function
+                will raise if the symbol is not found
+            msg: Message to display if an error occurs
+            base_addr: Base address of image. This is added to the returned
+                image_pos in most cases so that the returned position indicates
+                where the targeted entry/binary has actually been loaded. But
+                if end-at-4gb is used, this is not done, since the binary is
+                already assumed to be linked to the ROM position and using
+                execute-in-place (XIP).
+
+        Returns:
+            Value that should be assigned to that symbol, or None if it was
+                optional and not found
+
+        Raises:
+            ValueError if the symbol is invalid or not found, or references a
+                property which is not supported
+        """
+        entries = OrderedDict()
+        entries_by_name = {}
+        self._CollectEntries(entries, entries_by_name, self)
+        return self.LookupSymbol(sym_name, optional, msg, base_addr,
+                                 entries_by_name)
diff --git a/tools/binman/test/187_symbols_sub.dts b/tools/binman/test/187_symbols_sub.dts
new file mode 100644
index 0000000..54511a7
--- /dev/null
+++ b/tools/binman/test/187_symbols_sub.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		section {
+			pad-byte = <0xff>;
+			u-boot-spl {
+			};
+
+			u-boot {
+				offset = <24>;
+			};
+		};
+
+		u-boot-spl2 {
+			type = "u-boot-spl";
+		};
+	};
+};