xref: /openbmc/u-boot/tools/dtoc/dtb_platdata.py (revision fa0ea5b09ead2b0fa17754c0bf99249533fd36a3)
1#!/usr/bin/python
2#
3# Copyright (C) 2017 Google, Inc
4# Written by Simon Glass <sjg@chromium.org>
5#
6# SPDX-License-Identifier:	GPL-2.0+
7#
8
9"""Device tree to platform data class
10
11This supports converting device tree data to C structures definitions and
12static data.
13"""
14
15import copy
16import sys
17
18import fdt
19import fdt_util
20
21# When we see these properties we ignore them - i.e. do not create a structure member
22PROP_IGNORE_LIST = [
23    '#address-cells',
24    '#gpio-cells',
25    '#size-cells',
26    'compatible',
27    'linux,phandle',
28    "status",
29    'phandle',
30    'u-boot,dm-pre-reloc',
31    'u-boot,dm-tpl',
32    'u-boot,dm-spl',
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    new = name.replace('@', '_at_')
55    new = new.replace('-', '_')
56    new = new.replace(',', '_')
57    new = new.replace('.', '_')
58    return new
59
60def tab_to(num_tabs, line):
61    """Append tabs to a line of text to reach a tab stop.
62
63    Args:
64        num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
65        line: Line of text to append to
66
67    Returns:
68        line with the correct number of tabs appeneded. If the line already
69        extends past that tab stop then a single space is appended.
70    """
71    if len(line) >= num_tabs * 8:
72        return line + ' '
73    return line + '\t' * (num_tabs - len(line) // 8)
74
75def get_value(ftype, value):
76    """Get a value as a C expression
77
78    For integers this returns a byte-swapped (little-endian) hex string
79    For bytes this returns a hex string, e.g. 0x12
80    For strings this returns a literal string enclosed in quotes
81    For booleans this return 'true'
82
83    Args:
84        type: Data type (fdt_util)
85        value: Data value, as a string of bytes
86    """
87    if ftype == fdt.TYPE_INT:
88        return '%#x' % fdt_util.fdt32_to_cpu(value)
89    elif ftype == fdt.TYPE_BYTE:
90        return '%#x' % ord(value[0])
91    elif ftype == fdt.TYPE_STRING:
92        return '"%s"' % value
93    elif ftype == fdt.TYPE_BOOL:
94        return 'true'
95
96def get_compat_name(node):
97    """Get a node's first compatible string as a C identifier
98
99    Args:
100        node: Node object to check
101    Return:
102        Tuple:
103            C identifier for the first compatible string
104            List of C identifiers for all the other compatible strings
105                (possibly empty)
106    """
107    compat = node.props['compatible'].value
108    aliases = []
109    if isinstance(compat, list):
110        compat, aliases = compat[0], compat[1:]
111    return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
112
113def is_phandle(prop):
114    """Check if a node contains phandles
115
116    We have no reliable way of detecting whether a node uses a phandle
117    or not. As an interim measure, use a list of known property names.
118
119    Args:
120        prop: Prop object to check
121    Return:
122        True if the object value contains phandles, else False
123    """
124    if prop.name in ['clocks']:
125        return True
126    return False
127
128
129class DtbPlatdata(object):
130    """Provide a means to convert device tree binary data to platform data
131
132    The output of this process is C structures which can be used in space-
133    constrained encvironments where the ~3KB code overhead of device tree
134    code is not affordable.
135
136    Properties:
137        _fdt: Fdt object, referencing the device tree
138        _dtb_fname: Filename of the input device tree binary file
139        _valid_nodes: A list of Node object with compatible strings
140        _include_disabled: true to include nodes marked status = "disabled"
141        _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
142        _outfile: The current output file (sys.stdout or a real file)
143        _lines: Stashed list of output lines for outputting in the future
144        _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
145    """
146    def __init__(self, dtb_fname, include_disabled):
147        self._fdt = None
148        self._dtb_fname = dtb_fname
149        self._valid_nodes = None
150        self._include_disabled = include_disabled
151        self._phandle_nodes = {}
152        self._outfile = None
153        self._lines = []
154        self._aliases = {}
155
156    def setup_output(self, fname):
157        """Set up the output destination
158
159        Once this is done, future calls to self.out() will output to this
160        file.
161
162        Args:
163            fname: Filename to send output to, or '-' for stdout
164        """
165        if fname == '-':
166            self._outfile = sys.stdout
167        else:
168            self._outfile = open(fname, 'w')
169
170    def out(self, line):
171        """Output a string to the output file
172
173        Args:
174            line: String to output
175        """
176        self._outfile.write(line)
177
178    def buf(self, line):
179        """Buffer up a string to send later
180
181        Args:
182            line: String to add to our 'buffer' list
183        """
184        self._lines.append(line)
185
186    def get_buf(self):
187        """Get the contents of the output buffer, and clear it
188
189        Returns:
190            The output buffer, which is then cleared for future use
191        """
192        lines = self._lines
193        self._lines = []
194        return lines
195
196    def scan_dtb(self):
197        """Scan the device tree to obtain a tree of notes and properties
198
199        Once this is done, self._fdt.GetRoot() can be called to obtain the
200        device tree root node, and progress from there.
201        """
202        self._fdt = fdt.FdtScan(self._dtb_fname)
203
204    def scan_node(self, root):
205        """Scan a node and subnodes to build a tree of node and phandle info
206
207        This adds each node to self._valid_nodes and each phandle to
208        self._phandle_nodes.
209
210        Args:
211            root: Root node for scan
212        """
213        for node in root.subnodes:
214            if 'compatible' in node.props:
215                status = node.props.get('status')
216                if (not self._include_disabled and not status or
217                        status.value != 'disabled'):
218                    self._valid_nodes.append(node)
219                    phandle_prop = node.props.get('phandle')
220                    if phandle_prop:
221                        phandle = phandle_prop.GetPhandle()
222                        self._phandle_nodes[phandle] = node
223
224            # recurse to handle any subnodes
225            self.scan_node(node)
226
227    def scan_tree(self):
228        """Scan the device tree for useful information
229
230        This fills in the following properties:
231            _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
232            _valid_nodes: A list of nodes we wish to consider include in the
233                platform data
234        """
235        self._phandle_nodes = {}
236        self._valid_nodes = []
237        return self.scan_node(self._fdt.GetRoot())
238
239    def scan_structs(self):
240        """Scan the device tree building up the C structures we will use.
241
242        Build a dict keyed by C struct name containing a dict of Prop
243        object for each struct field (keyed by property name). Where the
244        same struct appears multiple times, try to use the 'widest'
245        property, i.e. the one with a type which can express all others.
246
247        Once the widest property is determined, all other properties are
248        updated to match that width.
249        """
250        structs = {}
251        for node in self._valid_nodes:
252            node_name, _ = get_compat_name(node)
253            fields = {}
254
255            # Get a list of all the valid properties in this node.
256            for name, prop in node.props.items():
257                if name not in PROP_IGNORE_LIST and name[0] != '#':
258                    fields[name] = copy.deepcopy(prop)
259
260            # If we've seen this node_name before, update the existing struct.
261            if node_name in structs:
262                struct = structs[node_name]
263                for name, prop in fields.items():
264                    oldprop = struct.get(name)
265                    if oldprop:
266                        oldprop.Widen(prop)
267                    else:
268                        struct[name] = prop
269
270            # Otherwise store this as a new struct.
271            else:
272                structs[node_name] = fields
273
274        upto = 0
275        for node in self._valid_nodes:
276            node_name, _ = get_compat_name(node)
277            struct = structs[node_name]
278            for name, prop in node.props.items():
279                if name not in PROP_IGNORE_LIST and name[0] != '#':
280                    prop.Widen(struct[name])
281            upto += 1
282
283            struct_name, aliases = get_compat_name(node)
284            for alias in aliases:
285                self._aliases[alias] = struct_name
286
287        return structs
288
289    def scan_phandles(self):
290        """Figure out what phandles each node uses
291
292        We need to be careful when outputing nodes that use phandles since
293        they must come after the declaration of the phandles in the C file.
294        Otherwise we get a compiler error since the phandle struct is not yet
295        declared.
296
297        This function adds to each node a list of phandle nodes that the node
298        depends on. This allows us to output things in the right order.
299        """
300        for node in self._valid_nodes:
301            node.phandles = set()
302            for pname, prop in node.props.items():
303                if pname in PROP_IGNORE_LIST or pname[0] == '#':
304                    continue
305                if isinstance(prop.value, list):
306                    if is_phandle(prop):
307                        # Process the list as pairs of (phandle, id)
308                        value_it = iter(prop.value)
309                        for phandle_cell, _ in zip(value_it, value_it):
310                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
311                            target_node = self._phandle_nodes[phandle]
312                            node.phandles.add(target_node)
313
314
315    def generate_structs(self, structs):
316        """Generate struct defintions for the platform data
317
318        This writes out the body of a header file consisting of structure
319        definitions for node in self._valid_nodes. See the documentation in
320        README.of-plat for more information.
321        """
322        self.out('#include <stdbool.h>\n')
323        self.out('#include <libfdt.h>\n')
324
325        # Output the struct definition
326        for name in sorted(structs):
327            self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
328            for pname in sorted(structs[name]):
329                prop = structs[name][pname]
330                if is_phandle(prop):
331                    # For phandles, include a reference to the target
332                    self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
333                                             conv_name_to_c(prop.name),
334                                             len(prop.value) / 2))
335                else:
336                    ptype = TYPE_NAMES[prop.type]
337                    self.out('\t%s%s' % (tab_to(2, ptype),
338                                         conv_name_to_c(prop.name)))
339                    if isinstance(prop.value, list):
340                        self.out('[%d]' % len(prop.value))
341                self.out(';\n')
342            self.out('};\n')
343
344        for alias, struct_name in self._aliases.iteritems():
345            self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
346                                             STRUCT_PREFIX, struct_name))
347
348    def output_node(self, node):
349        """Output the C code for a node
350
351        Args:
352            node: node to output
353        """
354        struct_name, _ = get_compat_name(node)
355        var_name = conv_name_to_c(node.name)
356        self.buf('static struct %s%s %s%s = {\n' %
357                 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
358        for pname, prop in node.props.items():
359            if pname in PROP_IGNORE_LIST or pname[0] == '#':
360                continue
361            member_name = conv_name_to_c(prop.name)
362            self.buf('\t%s= ' % tab_to(3, '.' + member_name))
363
364            # Special handling for lists
365            if isinstance(prop.value, list):
366                self.buf('{')
367                vals = []
368                # For phandles, output a reference to the platform data
369                # of the target node.
370                if is_phandle(prop):
371                    # Process the list as pairs of (phandle, id)
372                    value_it = iter(prop.value)
373                    for phandle_cell, id_cell in zip(value_it, value_it):
374                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
375                        id_num = fdt_util.fdt32_to_cpu(id_cell)
376                        target_node = self._phandle_nodes[phandle]
377                        name = conv_name_to_c(target_node.name)
378                        vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
379                else:
380                    for val in prop.value:
381                        vals.append(get_value(prop.type, val))
382                self.buf(', '.join(vals))
383                self.buf('}')
384            else:
385                self.buf(get_value(prop.type, prop.value))
386            self.buf(',\n')
387        self.buf('};\n')
388
389        # Add a device declaration
390        self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
391        self.buf('\t.name\t\t= "%s",\n' % struct_name)
392        self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
393        self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
394        self.buf('};\n')
395        self.buf('\n')
396
397        self.out(''.join(self.get_buf()))
398
399    def generate_tables(self):
400        """Generate device defintions for the platform data
401
402        This writes out C platform data initialisation data and
403        U_BOOT_DEVICE() declarations for each valid node. Where a node has
404        multiple compatible strings, a #define is used to make them equivalent.
405
406        See the documentation in doc/driver-model/of-plat.txt for more
407        information.
408        """
409        self.out('#include <common.h>\n')
410        self.out('#include <dm.h>\n')
411        self.out('#include <dt-structs.h>\n')
412        self.out('\n')
413        nodes_to_output = list(self._valid_nodes)
414
415        # Keep outputing nodes until there is none left
416        while nodes_to_output:
417            node = nodes_to_output[0]
418            # Output all the node's dependencies first
419            for req_node in node.phandles:
420                if req_node in nodes_to_output:
421                    self.output_node(req_node)
422                    nodes_to_output.remove(req_node)
423            self.output_node(node)
424            nodes_to_output.remove(node)
425
426
427def run_steps(args, dtb_file, include_disabled, output):
428    """Run all the steps of the dtoc tool
429
430    Args:
431        args: List of non-option arguments provided to the problem
432        dtb_file: Filename of dtb file to process
433        include_disabled: True to include disabled nodes
434        output: Name of output file
435    """
436    if not args:
437        raise ValueError('Please specify a command: struct, platdata')
438
439    plat = DtbPlatdata(dtb_file, include_disabled)
440    plat.scan_dtb()
441    plat.scan_tree()
442    plat.setup_output(output)
443    structs = plat.scan_structs()
444    plat.scan_phandles()
445
446    for cmd in args[0].split(','):
447        if cmd == 'struct':
448            plat.generate_structs(structs)
449        elif cmd == 'platdata':
450            plat.generate_tables()
451        else:
452            raise ValueError("Unknown command '%s': (use: struct, platdata)" %
453                             cmd)
454