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