1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2018 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4
5"""# Entry-type module for a full map of the firmware image
6
7This handles putting an FDT into the image with just the information about the
8image.
9"""
10
11from binman.entry import Entry
12from u_boot_pylib import tools
13from u_boot_pylib import tout
14
15FDTMAP_MAGIC   = b'_FDTMAP_'
16FDTMAP_HDR_LEN = 16
17
18# These is imported if needed
19Fdt = None
20libfdt = None
21state = None
22
23def LocateFdtmap(data):
24    """Search an image for an fdt map
25
26    Args:
27        data: Data to search
28
29    Returns:
30        Position of fdt map in data, or None if not found. Note that the
31            position returned is of the FDT header, i.e. before the FDT data
32    """
33    hdr_pos = data.find(FDTMAP_MAGIC)
34    size = len(data)
35    if hdr_pos != -1:
36        hdr = data[hdr_pos:hdr_pos + FDTMAP_HDR_LEN]
37        if len(hdr) == FDTMAP_HDR_LEN:
38            return hdr_pos
39    return None
40
41class Entry_fdtmap(Entry):
42    """An entry which contains an FDT map
43
44    Properties / Entry arguments:
45        None
46
47    An FDT map is just a header followed by an FDT containing a list of all the
48    entries in the image. The root node corresponds to the image node in the
49    original FDT, and an image-name property indicates the image name in that
50    original tree.
51
52    The header is the string _FDTMAP_ followed by 8 unused bytes.
53
54    When used, this entry will be populated with an FDT map which reflects the
55    entries in the current image. Hierarchy is preserved, and all offsets and
56    sizes are included.
57
58    Note that the -u option must be provided to ensure that binman updates the
59    FDT with the position of each entry.
60
61    Example output for a simple image with U-Boot and an FDT map::
62
63        / {
64            image-name = "binman";
65            size = <0x00000112>;
66            image-pos = <0x00000000>;
67            offset = <0x00000000>;
68            u-boot {
69                size = <0x00000004>;
70                image-pos = <0x00000000>;
71                offset = <0x00000000>;
72            };
73            fdtmap {
74                size = <0x0000010e>;
75                image-pos = <0x00000004>;
76                offset = <0x00000004>;
77            };
78        };
79
80    If allow-repack is used then 'orig-offset' and 'orig-size' properties are
81    added as necessary. See the binman README.
82
83    When extracting files, an alternative 'fdt' format is available for fdtmaps.
84    Use `binman extract -F fdt ...` to use this. It will export a devicetree,
85    without the fdtmap header, so it can be viewed with `fdtdump`.
86    """
87    def __init__(self, section, etype, node):
88        # Put these here to allow entry-docs and help to work without libfdt
89        global libfdt
90        global state
91        global Fdt
92
93        import libfdt
94        from binman import state
95        from dtoc.fdt import Fdt
96
97        super().__init__(section, etype, node)
98        self.alt_formats = ['fdt']
99
100    def CheckAltFormats(self, alt_formats):
101        alt_formats['fdt'] = self, 'Extract the devicetree blob from the fdtmap'
102
103    def _GetFdtmap(self):
104        """Build an FDT map from the entries in the current image
105
106        Returns:
107            FDT map binary data
108        """
109        fsw = libfdt.FdtSw()
110        fsw.finish_reservemap()
111
112        def _AddNode(node):
113            """Add a node to the FDT map"""
114            for pname, prop in node.props.items():
115                fsw.property(pname, prop.bytes)
116            for subnode in node.subnodes:
117                with fsw.add_node(subnode.name):
118                    _AddNode(subnode)
119
120        data = state.GetFdtContents('fdtmap')[1]
121        # If we have an fdtmap it means that we are using this as the
122        # fdtmap for this image.
123        if data is None:
124            # Get the FDT data into an Fdt object
125            data = state.GetFdtContents()[1]
126            infdt = Fdt.FromData(data)
127            infdt.Scan()
128
129            # Find the node for the image containing the Fdt-map entry
130            path = self.section.GetPath()
131            self.Detail("Fdtmap: Using section '%s' (path '%s')" %
132                        (self.section.name, path))
133            node = infdt.GetNode(path)
134            if not node:
135                self.Raise("Internal error: Cannot locate node for path '%s'" %
136                           path)
137
138            # Build a new tree with all nodes and properties starting from that
139            # node
140            with fsw.add_node(''):
141                fsw.property_string('image-node', node.name)
142                _AddNode(node)
143            fdt = fsw.as_fdt()
144
145            # Pack this new FDT and return its contents
146            fdt.pack()
147            outfdt = Fdt.FromData(fdt.as_bytearray())
148            data = outfdt.GetContents()
149        data = FDTMAP_MAGIC + tools.get_bytes(0, 8) + data
150        return data
151
152    def ObtainContents(self):
153        """Obtain a placeholder for the fdt-map contents"""
154        self.SetContents(self._GetFdtmap())
155        return True
156
157    def ProcessContents(self):
158        """Write an updated version of the FDT map to this entry
159
160        This is necessary since new data may have been written back to it during
161        processing, e.g. the image-pos properties.
162        """
163        return self.ProcessContentsUpdate(self._GetFdtmap())
164
165    def GetAltFormat(self, data, alt_format):
166        if alt_format == 'fdt':
167            return data[FDTMAP_HDR_LEN:]
168