xref: /openbmc/u-boot/tools/dtoc/dtoc.py (revision daa483de)
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 ScanTree(self):
176        """Scan the device tree for useful information
177
178        This fills in the following properties:
179            _phandle_node: A dict of Nodes indexed by phandle (an integer)
180            _valid_nodes: A list of nodes we wish to consider include in the
181                platform data
182        """
183        node_list = []
184        self._phandle_node = {}
185        for node in self.fdt.GetRoot().subnodes:
186            if 'compatible' in node.props:
187                status = node.props.get('status')
188                if (not options.include_disabled and not status or
189                    status.value != 'disabled'):
190                    node_list.append(node)
191                    phandle_prop = node.props.get('phandle')
192                    if phandle_prop:
193                        phandle = phandle_prop.GetPhandle()
194                        self._phandle_node[phandle] = node
195
196        self._valid_nodes = node_list
197
198    def IsPhandle(self, prop):
199        """Check if a node contains phandles
200
201        We have no reliable way of detecting whether a node uses a phandle
202        or not. As an interim measure, use a list of known property names.
203
204        Args:
205            prop: Prop object to check
206        Return:
207            True if the object value contains phandles, else False
208        """
209        if prop.name in ['clocks']:
210            return True
211        return False
212
213    def ScanStructs(self):
214        """Scan the device tree building up the C structures we will use.
215
216        Build a dict keyed by C struct name containing a dict of Prop
217        object for each struct field (keyed by property name). Where the
218        same struct appears multiple times, try to use the 'widest'
219        property, i.e. the one with a type which can express all others.
220
221        Once the widest property is determined, all other properties are
222        updated to match that width.
223        """
224        structs = {}
225        for node in self._valid_nodes:
226            node_name = self.GetCompatName(node)
227            fields = {}
228
229            # Get a list of all the valid properties in this node.
230            for name, prop in node.props.items():
231                if name not in PROP_IGNORE_LIST and name[0] != '#':
232                    fields[name] = copy.deepcopy(prop)
233
234            # If we've seen this node_name before, update the existing struct.
235            if node_name in structs:
236                struct = structs[node_name]
237                for name, prop in fields.items():
238                    oldprop = struct.get(name)
239                    if oldprop:
240                        oldprop.Widen(prop)
241                    else:
242                        struct[name] = prop
243
244            # Otherwise store this as a new struct.
245            else:
246                structs[node_name] = fields
247
248        upto = 0
249        for node in self._valid_nodes:
250            node_name = self.GetCompatName(node)
251            struct = structs[node_name]
252            for name, prop in node.props.items():
253                if name not in PROP_IGNORE_LIST and name[0] != '#':
254                    prop.Widen(struct[name])
255            upto += 1
256        return structs
257
258    def GenerateStructs(self, structs):
259        """Generate struct defintions for the platform data
260
261        This writes out the body of a header file consisting of structure
262        definitions for node in self._valid_nodes. See the documentation in
263        README.of-plat for more information.
264        """
265        self.Out('#include <stdbool.h>\n')
266        self.Out('#include <libfdt.h>\n')
267
268        # Output the struct definition
269        for name in sorted(structs):
270            self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name));
271            for pname in sorted(structs[name]):
272                prop = structs[name][pname]
273                if self.IsPhandle(prop):
274                    # For phandles, include a reference to the target
275                    self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'),
276                                             Conv_name_to_c(prop.name),
277                                             len(prop.value) / 2))
278                else:
279                    ptype = TYPE_NAMES[prop.type]
280                    self.Out('\t%s%s' % (TabTo(2, ptype),
281                                         Conv_name_to_c(prop.name)))
282                    if type(prop.value) == list:
283                        self.Out('[%d]' % len(prop.value))
284                self.Out(';\n')
285            self.Out('};\n')
286
287    def GenerateTables(self):
288        """Generate device defintions for the platform data
289
290        This writes out C platform data initialisation data and
291        U_BOOT_DEVICE() declarations for each valid node. See the
292        documentation in README.of-plat for more information.
293        """
294        self.Out('#include <common.h>\n')
295        self.Out('#include <dm.h>\n')
296        self.Out('#include <dt-structs.h>\n')
297        self.Out('\n')
298        node_txt_list = []
299        for node in self._valid_nodes:
300            struct_name = self.GetCompatName(node)
301            var_name = Conv_name_to_c(node.name)
302            self.Buf('static struct %s%s %s%s = {\n' %
303                (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
304            for pname, prop in node.props.items():
305                if pname in PROP_IGNORE_LIST or pname[0] == '#':
306                    continue
307                ptype = TYPE_NAMES[prop.type]
308                member_name = Conv_name_to_c(prop.name)
309                self.Buf('\t%s= ' % TabTo(3, '.' + member_name))
310
311                # Special handling for lists
312                if type(prop.value) == list:
313                    self.Buf('{')
314                    vals = []
315                    # For phandles, output a reference to the platform data
316                    # of the target node.
317                    if self.IsPhandle(prop):
318                        # Process the list as pairs of (phandle, id)
319                        it = iter(prop.value)
320                        for phandle_cell, id_cell in zip(it, it):
321                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
322                            id = fdt_util.fdt32_to_cpu(id_cell)
323                            target_node = self._phandle_node[phandle]
324                            name = Conv_name_to_c(target_node.name)
325                            vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id))
326                    else:
327                        for val in prop.value:
328                            vals.append(self.GetValue(prop.type, val))
329                    self.Buf(', '.join(vals))
330                    self.Buf('}')
331                else:
332                    self.Buf(self.GetValue(prop.type, prop.value))
333                self.Buf(',\n')
334            self.Buf('};\n')
335
336            # Add a device declaration
337            self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
338            self.Buf('\t.name\t\t= "%s",\n' % struct_name)
339            self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
340            self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' %
341                     (VAL_PREFIX, var_name))
342            self.Buf('};\n')
343            self.Buf('\n')
344
345            # Output phandle target nodes first, since they may be referenced
346            # by others
347            if 'phandle' in node.props:
348                self.Out(''.join(self.GetBuf()))
349            else:
350                node_txt_list.append(self.GetBuf())
351
352        # Output all the nodes which are not phandle targets themselves, but
353        # may reference them. This avoids the need for forward declarations.
354        for node_txt in node_txt_list:
355            self.Out(''.join(node_txt))
356
357
358if __name__ != "__main__":
359    pass
360
361parser = OptionParser()
362parser.add_option('-d', '--dtb-file', action='store',
363                  help='Specify the .dtb input file')
364parser.add_option('--include-disabled', action='store_true',
365                  help='Include disabled nodes')
366parser.add_option('-o', '--output', action='store', default='-',
367                  help='Select output filename')
368(options, args) = parser.parse_args()
369
370if not args:
371    raise ValueError('Please specify a command: struct, platdata')
372
373plat = DtbPlatdata(options.dtb_file, options)
374plat.ScanDtb()
375plat.ScanTree()
376plat.SetupOutput(options.output)
377structs = plat.ScanStructs()
378
379for cmd in args[0].split(','):
380    if cmd == 'struct':
381        plat.GenerateStructs(structs)
382    elif cmd == 'platdata':
383        plat.GenerateTables()
384    else:
385        raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd)
386