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