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