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