xref: /rk3399_rockchip-uboot/tools/dtoc/dtoc.py (revision fd1c2d9b6a3a9b41ae070ca47361bd6cc6aaaf09)
1#!/usr/bin/python
2#
3# Copyright (C) 2016 Google, Inc
4# Written by Simon Glass <sjg@chromium.org>
5#
6# SPDX-License-Identifier:	GPL-2.0+
7#
8
9import copy
10from optparse import OptionError, OptionParser
11import os
12import sys
13
14import fdt_util
15
16# Bring in the patman libraries
17our_path = os.path.dirname(os.path.realpath(__file__))
18sys.path.append(os.path.join(our_path, '../patman'))
19
20# Bring in either the normal fdt library (which relies on libfdt) or the
21# fallback one (which uses fdtget and is slower). Both provide the same
22# interfface for this file to use.
23try:
24    from fdt import Fdt
25    import fdt
26    have_libfdt = True
27except ImportError:
28    have_libfdt = False
29    from fdt_fallback import Fdt
30    import fdt_fallback as fdt
31
32import struct
33
34# When we see these properties we ignore them - i.e. do not create a structure member
35PROP_IGNORE_LIST = [
36    '#address-cells',
37    '#gpio-cells',
38    '#size-cells',
39    'compatible',
40    'linux,phandle',
41    "status",
42    'phandle',
43]
44
45# C type declarations for the tyues we support
46TYPE_NAMES = {
47    fdt_util.TYPE_INT: 'fdt32_t',
48    fdt_util.TYPE_BYTE: 'unsigned char',
49    fdt_util.TYPE_STRING: 'const char *',
50    fdt_util.TYPE_BOOL: 'bool',
51};
52
53STRUCT_PREFIX = 'dtd_'
54VAL_PREFIX = 'dtv_'
55
56def Conv_name_to_c(name):
57    """Convert a device-tree name to a C identifier
58
59    Args:
60        name:   Name to convert
61    Return:
62        String containing the C version of this name
63    """
64    str = name.replace('@', '_at_')
65    str = str.replace('-', '_')
66    str = str.replace(',', '_')
67    str = str.replace('/', '__')
68    return str
69
70def TabTo(num_tabs, str):
71    if len(str) >= num_tabs * 8:
72        return str + ' '
73    return str + '\t' * (num_tabs - len(str) / 8)
74
75class DtbPlatdata:
76    """Provide a means to convert device tree binary data to platform data
77
78    The output of this process is C structures which can be used in space-
79    constrained encvironments where the ~3KB code overhead of device tree
80    code is not affordable.
81
82    Properties:
83        fdt: Fdt object, referencing the device tree
84        _dtb_fname: Filename of the input device tree binary file
85        _valid_nodes: A list of Node object with compatible strings
86        _options: Command-line options
87        _phandle_node: A dict of nodes indexed by phandle number (1, 2...)
88        _outfile: The current output file (sys.stdout or a real file)
89        _lines: Stashed list of output lines for outputting in the future
90        _phandle_node: A dict of Nodes indexed by phandle (an integer)
91    """
92    def __init__(self, dtb_fname, options):
93        self._dtb_fname = dtb_fname
94        self._valid_nodes = None
95        self._options = options
96        self._phandle_node = {}
97        self._outfile = None
98        self._lines = []
99
100    def SetupOutput(self, fname):
101        """Set up the output destination
102
103        Once this is done, future calls to self.Out() will output to this
104        file.
105
106        Args:
107            fname: Filename to send output to, or '-' for stdout
108        """
109        if fname == '-':
110            self._outfile = sys.stdout
111        else:
112            self._outfile = open(fname, 'w')
113
114    def Out(self, str):
115        """Output a string to the output file
116
117        Args:
118            str: String to output
119        """
120        self._outfile.write(str)
121
122    def Buf(self, str):
123        """Buffer up a string to send later
124
125        Args:
126            str: String to add to our 'buffer' list
127        """
128        self._lines.append(str)
129
130    def GetBuf(self):
131        """Get the contents of the output buffer, and clear it
132
133        Returns:
134            The output buffer, which is then cleared for future use
135        """
136        lines = self._lines
137        self._lines = []
138        return lines
139
140    def GetValue(self, type, value):
141        """Get a value as a C expression
142
143        For integers this returns a byte-swapped (little-endian) hex string
144        For bytes this returns a hex string, e.g. 0x12
145        For strings this returns a literal string enclosed in quotes
146        For booleans this return 'true'
147
148        Args:
149            type: Data type (fdt_util)
150            value: Data value, as a string of bytes
151        """
152        if type == fdt_util.TYPE_INT:
153            return '%#x' % fdt_util.fdt32_to_cpu(value)
154        elif type == fdt_util.TYPE_BYTE:
155            return '%#x' % ord(value[0])
156        elif type == fdt_util.TYPE_STRING:
157            return '"%s"' % value
158        elif type == fdt_util.TYPE_BOOL:
159            return 'true'
160
161    def GetCompatName(self, node):
162        """Get a node's first compatible string as a C identifier
163
164        Args:
165            node: Node object to check
166        Return:
167            C identifier for the first compatible string
168        """
169        compat = node.props['compatible'].value
170        if type(compat) == list:
171            compat = compat[0]
172        return Conv_name_to_c(compat)
173
174    def ScanDtb(self):
175        """Scan the device tree to obtain a tree of notes and properties
176
177        Once this is done, self.fdt.GetRoot() can be called to obtain the
178        device tree root node, and progress from there.
179        """
180        self.fdt = Fdt(self._dtb_fname)
181        self.fdt.Scan()
182
183    def ScanTree(self):
184        """Scan the device tree for useful information
185
186        This fills in the following properties:
187            _phandle_node: A dict of Nodes indexed by phandle (an integer)
188            _valid_nodes: A list of nodes we wish to consider include in the
189                platform data
190        """
191        node_list = []
192        self._phandle_node = {}
193        for node in self.fdt.GetRoot().subnodes:
194            if 'compatible' in node.props:
195                status = node.props.get('status')
196                if (not options.include_disabled and not status or
197                    status.value != 'disabled'):
198                    node_list.append(node)
199                    phandle_prop = node.props.get('phandle')
200                    if phandle_prop:
201                        phandle = phandle_prop.GetPhandle()
202                        self._phandle_node[phandle] = node
203
204        self._valid_nodes = node_list
205
206    def IsPhandle(self, prop):
207        """Check if a node contains phandles
208
209        We have no reliable way of detecting whether a node uses a phandle
210        or not. As an interim measure, use a list of known property names.
211
212        Args:
213            prop: Prop object to check
214        Return:
215            True if the object value contains phandles, else False
216        """
217        if prop.name in ['clocks']:
218            return True
219        return False
220
221    def ScanStructs(self):
222        """Scan the device tree building up the C structures we will use.
223
224        Build a dict keyed by C struct name containing a dict of Prop
225        object for each struct field (keyed by property name). Where the
226        same struct appears multiple times, try to use the 'widest'
227        property, i.e. the one with a type which can express all others.
228
229        Once the widest property is determined, all other properties are
230        updated to match that width.
231        """
232        structs = {}
233        for node in self._valid_nodes:
234            node_name = self.GetCompatName(node)
235            fields = {}
236
237            # Get a list of all the valid properties in this node.
238            for name, prop in node.props.iteritems():
239                if name not in PROP_IGNORE_LIST and name[0] != '#':
240                    fields[name] = copy.deepcopy(prop)
241
242            # If we've seen this node_name before, update the existing struct.
243            if node_name in structs:
244                struct = structs[node_name]
245                for name, prop in fields.iteritems():
246                    oldprop = struct.get(name)
247                    if oldprop:
248                        oldprop.Widen(prop)
249                    else:
250                        struct[name] = prop
251
252            # Otherwise store this as a new struct.
253            else:
254                structs[node_name] = fields
255
256        upto = 0
257        for node in self._valid_nodes:
258            node_name = self.GetCompatName(node)
259            struct = structs[node_name]
260            for name, prop in node.props.iteritems():
261                if name not in PROP_IGNORE_LIST and name[0] != '#':
262                    prop.Widen(struct[name])
263            upto += 1
264        return structs
265
266    def GenerateStructs(self, structs):
267        """Generate struct defintions for the platform data
268
269        This writes out the body of a header file consisting of structure
270        definitions for node in self._valid_nodes. See the documentation in
271        README.of-plat for more information.
272        """
273        self.Out('#include <stdbool.h>\n')
274        self.Out('#include <libfdt.h>\n')
275
276        # Output the struct definition
277        for name in sorted(structs):
278            self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name));
279            for pname in sorted(structs[name]):
280                prop = structs[name][pname]
281                if self.IsPhandle(prop):
282                    # For phandles, include a reference to the target
283                    self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'),
284                                             Conv_name_to_c(prop.name),
285                                             len(prop.value) / 2))
286                else:
287                    ptype = TYPE_NAMES[prop.type]
288                    self.Out('\t%s%s' % (TabTo(2, ptype),
289                                         Conv_name_to_c(prop.name)))
290                    if type(prop.value) == list:
291                        self.Out('[%d]' % len(prop.value))
292                self.Out(';\n')
293            self.Out('};\n')
294
295    def GenerateTables(self):
296        """Generate device defintions for the platform data
297
298        This writes out C platform data initialisation data and
299        U_BOOT_DEVICE() declarations for each valid node. See the
300        documentation in README.of-plat for more information.
301        """
302        self.Out('#include <common.h>\n')
303        self.Out('#include <dm.h>\n')
304        self.Out('#include <dt-structs.h>\n')
305        self.Out('\n')
306        node_txt_list = []
307        for node in self._valid_nodes:
308            struct_name = self.GetCompatName(node)
309            var_name = Conv_name_to_c(node.name)
310            self.Buf('static struct %s%s %s%s = {\n' %
311                (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
312            for pname, prop in node.props.iteritems():
313                if pname in PROP_IGNORE_LIST or pname[0] == '#':
314                    continue
315                ptype = TYPE_NAMES[prop.type]
316                member_name = Conv_name_to_c(prop.name)
317                self.Buf('\t%s= ' % TabTo(3, '.' + member_name))
318
319                # Special handling for lists
320                if type(prop.value) == list:
321                    self.Buf('{')
322                    vals = []
323                    # For phandles, output a reference to the platform data
324                    # of the target node.
325                    if self.IsPhandle(prop):
326                        # Process the list as pairs of (phandle, id)
327                        it = iter(prop.value)
328                        for phandle_cell, id_cell in zip(it, it):
329                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
330                            id = fdt_util.fdt32_to_cpu(id_cell)
331                            target_node = self._phandle_node[phandle]
332                            name = Conv_name_to_c(target_node.name)
333                            vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id))
334                    else:
335                        for val in prop.value:
336                            vals.append(self.GetValue(prop.type, val))
337                    self.Buf(', '.join(vals))
338                    self.Buf('}')
339                else:
340                    self.Buf(self.GetValue(prop.type, prop.value))
341                self.Buf(',\n')
342            self.Buf('};\n')
343
344            # Add a device declaration
345            self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
346            self.Buf('\t.name\t\t= "%s",\n' % struct_name)
347            self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
348            self.Buf('};\n')
349            self.Buf('\n')
350
351            # Output phandle target nodes first, since they may be referenced
352            # by others
353            if 'phandle' in node.props:
354                self.Out(''.join(self.GetBuf()))
355            else:
356                node_txt_list.append(self.GetBuf())
357
358        # Output all the nodes which are not phandle targets themselves, but
359        # may reference them. This avoids the need for forward declarations.
360        for node_txt in node_txt_list:
361            self.Out(''.join(node_txt))
362
363
364if __name__ != "__main__":
365    pass
366
367parser = OptionParser()
368parser.add_option('-d', '--dtb-file', action='store',
369                  help='Specify the .dtb input file')
370parser.add_option('--include-disabled', action='store_true',
371                  help='Include disabled nodes')
372parser.add_option('-o', '--output', action='store', default='-',
373                  help='Select output filename')
374(options, args) = parser.parse_args()
375
376if not args:
377    raise ValueError('Please specify a command: struct, platdata')
378
379plat = DtbPlatdata(options.dtb_file, options)
380plat.ScanDtb()
381plat.ScanTree()
382plat.SetupOutput(options.output)
383structs = plat.ScanStructs()
384
385for cmd in args[0].split(','):
386    if cmd == 'struct':
387        plat.GenerateStructs(structs)
388    elif cmd == 'platdata':
389        plat.GenerateTables()
390    else:
391        raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd)
392