xref: /openbmc/u-boot/tools/dtoc/dtoc.py (revision 83f1c2ef)
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 GenerateStructs(self, structs):
276        """Generate struct defintions for the platform data
277
278        This writes out the body of a header file consisting of structure
279        definitions for node in self._valid_nodes. See the documentation in
280        README.of-plat for more information.
281        """
282        self.Out('#include <stdbool.h>\n')
283        self.Out('#include <libfdt.h>\n')
284
285        # Output the struct definition
286        for name in sorted(structs):
287            self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name));
288            for pname in sorted(structs[name]):
289                prop = structs[name][pname]
290                if self.IsPhandle(prop):
291                    # For phandles, include a reference to the target
292                    self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'),
293                                             Conv_name_to_c(prop.name),
294                                             len(prop.value) / 2))
295                else:
296                    ptype = TYPE_NAMES[prop.type]
297                    self.Out('\t%s%s' % (TabTo(2, ptype),
298                                         Conv_name_to_c(prop.name)))
299                    if type(prop.value) == list:
300                        self.Out('[%d]' % len(prop.value))
301                self.Out(';\n')
302            self.Out('};\n')
303
304    def GenerateTables(self):
305        """Generate device defintions for the platform data
306
307        This writes out C platform data initialisation data and
308        U_BOOT_DEVICE() declarations for each valid node. See the
309        documentation in README.of-plat for more information.
310        """
311        self.Out('#include <common.h>\n')
312        self.Out('#include <dm.h>\n')
313        self.Out('#include <dt-structs.h>\n')
314        self.Out('\n')
315        node_txt_list = []
316        for node in self._valid_nodes:
317            struct_name = self.GetCompatName(node)
318            var_name = Conv_name_to_c(node.name)
319            self.Buf('static struct %s%s %s%s = {\n' %
320                (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
321            for pname, prop in node.props.items():
322                if pname in PROP_IGNORE_LIST or pname[0] == '#':
323                    continue
324                ptype = TYPE_NAMES[prop.type]
325                member_name = Conv_name_to_c(prop.name)
326                self.Buf('\t%s= ' % TabTo(3, '.' + member_name))
327
328                # Special handling for lists
329                if type(prop.value) == list:
330                    self.Buf('{')
331                    vals = []
332                    # For phandles, output a reference to the platform data
333                    # of the target node.
334                    if self.IsPhandle(prop):
335                        # Process the list as pairs of (phandle, id)
336                        it = iter(prop.value)
337                        for phandle_cell, id_cell in zip(it, it):
338                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
339                            id = fdt_util.fdt32_to_cpu(id_cell)
340                            target_node = self._phandle_node[phandle]
341                            name = Conv_name_to_c(target_node.name)
342                            vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id))
343                    else:
344                        for val in prop.value:
345                            vals.append(self.GetValue(prop.type, val))
346                    self.Buf(', '.join(vals))
347                    self.Buf('}')
348                else:
349                    self.Buf(self.GetValue(prop.type, prop.value))
350                self.Buf(',\n')
351            self.Buf('};\n')
352
353            # Add a device declaration
354            self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
355            self.Buf('\t.name\t\t= "%s",\n' % struct_name)
356            self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
357            self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' %
358                     (VAL_PREFIX, var_name))
359            self.Buf('};\n')
360            self.Buf('\n')
361
362            # Output phandle target nodes first, since they may be referenced
363            # by others
364            if 'phandle' in node.props:
365                self.Out(''.join(self.GetBuf()))
366            else:
367                node_txt_list.append(self.GetBuf())
368
369        # Output all the nodes which are not phandle targets themselves, but
370        # may reference them. This avoids the need for forward declarations.
371        for node_txt in node_txt_list:
372            self.Out(''.join(node_txt))
373
374
375if __name__ != "__main__":
376    pass
377
378parser = OptionParser()
379parser.add_option('-d', '--dtb-file', action='store',
380                  help='Specify the .dtb input file')
381parser.add_option('--include-disabled', action='store_true',
382                  help='Include disabled nodes')
383parser.add_option('-o', '--output', action='store', default='-',
384                  help='Select output filename')
385(options, args) = parser.parse_args()
386
387if not args:
388    raise ValueError('Please specify a command: struct, platdata')
389
390plat = DtbPlatdata(options.dtb_file, options)
391plat.ScanDtb()
392plat.ScanTree()
393plat.SetupOutput(options.output)
394structs = plat.ScanStructs()
395
396for cmd in args[0].split(','):
397    if cmd == 'struct':
398        plat.GenerateStructs(structs)
399    elif cmd == 'platdata':
400        plat.GenerateTables()
401    else:
402        raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd)
403