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