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