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