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