xref: /openbmc/u-boot/tools/dtoc/dtb_platdata.py (revision 77c07e7e)
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                                 node.name)
321            if len(reg.value) % total:
322                raise ValueError("Node '%s' reg property has %d cells "
323                        'which is not a multiple of na + ns = %d + %d)' %
324                        (node.name, len(reg.value), na, ns))
325            reg.na = na
326            reg.ns = ns
327            if na != 1 or ns != 1:
328                reg.type = fdt.TYPE_INT64
329                i = 0
330                new_value = []
331                val = reg.value
332                if not isinstance(val, list):
333                    val = [val]
334                while i < len(val):
335                    addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
336                    i += na
337                    size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
338                    i += ns
339                    new_value += [addr, size]
340                reg.value = new_value
341
342    def scan_structs(self):
343        """Scan the device tree building up the C structures we will use.
344
345        Build a dict keyed by C struct name containing a dict of Prop
346        object for each struct field (keyed by property name). Where the
347        same struct appears multiple times, try to use the 'widest'
348        property, i.e. the one with a type which can express all others.
349
350        Once the widest property is determined, all other properties are
351        updated to match that width.
352        """
353        structs = {}
354        for node in self._valid_nodes:
355            node_name, _ = get_compat_name(node)
356            fields = {}
357
358            # Get a list of all the valid properties in this node.
359            for name, prop in node.props.items():
360                if name not in PROP_IGNORE_LIST and name[0] != '#':
361                    fields[name] = copy.deepcopy(prop)
362
363            # If we've seen this node_name before, update the existing struct.
364            if node_name in structs:
365                struct = structs[node_name]
366                for name, prop in fields.items():
367                    oldprop = struct.get(name)
368                    if oldprop:
369                        oldprop.Widen(prop)
370                    else:
371                        struct[name] = prop
372
373            # Otherwise store this as a new struct.
374            else:
375                structs[node_name] = fields
376
377        upto = 0
378        for node in self._valid_nodes:
379            node_name, _ = get_compat_name(node)
380            struct = structs[node_name]
381            for name, prop in node.props.items():
382                if name not in PROP_IGNORE_LIST and name[0] != '#':
383                    prop.Widen(struct[name])
384            upto += 1
385
386            struct_name, aliases = get_compat_name(node)
387            for alias in aliases:
388                self._aliases[alias] = struct_name
389
390        return structs
391
392    def scan_phandles(self):
393        """Figure out what phandles each node uses
394
395        We need to be careful when outputing nodes that use phandles since
396        they must come after the declaration of the phandles in the C file.
397        Otherwise we get a compiler error since the phandle struct is not yet
398        declared.
399
400        This function adds to each node a list of phandle nodes that the node
401        depends on. This allows us to output things in the right order.
402        """
403        for node in self._valid_nodes:
404            node.phandles = set()
405            for pname, prop in node.props.items():
406                if pname in PROP_IGNORE_LIST or pname[0] == '#':
407                    continue
408                info = self.get_phandle_argc(prop, node.name)
409                if info:
410                    # Process the list as pairs of (phandle, id)
411                    pos = 0
412                    for args in info.args:
413                        phandle_cell = prop.value[pos]
414                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
415                        target_node = self._fdt.phandle_to_node[phandle]
416                        node.phandles.add(target_node)
417                        pos += 1 + args
418
419
420    def generate_structs(self, structs):
421        """Generate struct defintions for the platform data
422
423        This writes out the body of a header file consisting of structure
424        definitions for node in self._valid_nodes. See the documentation in
425        README.of-plat for more information.
426        """
427        self.out_header()
428        self.out('#include <stdbool.h>\n')
429        self.out('#include <linux/libfdt.h>\n')
430
431        # Output the struct definition
432        for name in sorted(structs):
433            self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
434            for pname in sorted(structs[name]):
435                prop = structs[name][pname]
436                info = self.get_phandle_argc(prop, structs[name])
437                if info:
438                    # For phandles, include a reference to the target
439                    struct_name = 'struct phandle_%d_arg' % info.max_args
440                    self.out('\t%s%s[%d]' % (tab_to(2, struct_name),
441                                             conv_name_to_c(prop.name),
442                                             len(info.args)))
443                else:
444                    ptype = TYPE_NAMES[prop.type]
445                    self.out('\t%s%s' % (tab_to(2, ptype),
446                                         conv_name_to_c(prop.name)))
447                    if isinstance(prop.value, list):
448                        self.out('[%d]' % len(prop.value))
449                self.out(';\n')
450            self.out('};\n')
451
452        for alias, struct_name in self._aliases.iteritems():
453            self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
454                                             STRUCT_PREFIX, struct_name))
455
456    def output_node(self, node):
457        """Output the C code for a node
458
459        Args:
460            node: node to output
461        """
462        struct_name, _ = get_compat_name(node)
463        var_name = conv_name_to_c(node.name)
464        self.buf('static const struct %s%s %s%s = {\n' %
465                 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
466        for pname, prop in node.props.items():
467            if pname in PROP_IGNORE_LIST or pname[0] == '#':
468                continue
469            member_name = conv_name_to_c(prop.name)
470            self.buf('\t%s= ' % tab_to(3, '.' + member_name))
471
472            # Special handling for lists
473            if isinstance(prop.value, list):
474                self.buf('{')
475                vals = []
476                # For phandles, output a reference to the platform data
477                # of the target node.
478                info = self.get_phandle_argc(prop, node.name)
479                if info:
480                    # Process the list as pairs of (phandle, id)
481                    pos = 0
482                    for args in info.args:
483                        phandle_cell = prop.value[pos]
484                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
485                        target_node = self._fdt.phandle_to_node[phandle]
486                        name = conv_name_to_c(target_node.name)
487                        arg_values = []
488                        for i in range(args):
489                            arg_values.append(str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i])))
490                        pos += 1 + args
491                        vals.append('\t{&%s%s, {%s}}' % (VAL_PREFIX, name,
492                                                     ', '.join(arg_values)))
493                    for val in vals:
494                        self.buf('\n\t\t%s,' % val)
495                else:
496                    for val in prop.value:
497                        vals.append(get_value(prop.type, val))
498
499                    # Put 8 values per line to avoid very long lines.
500                    for i in xrange(0, len(vals), 8):
501                        if i:
502                            self.buf(',\n\t\t')
503                        self.buf(', '.join(vals[i:i + 8]))
504                self.buf('}')
505            else:
506                self.buf(get_value(prop.type, prop.value))
507            self.buf(',\n')
508        self.buf('};\n')
509
510        # Add a device declaration
511        self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
512        self.buf('\t.name\t\t= "%s",\n' % struct_name)
513        self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
514        self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
515        self.buf('};\n')
516        self.buf('\n')
517
518        self.out(''.join(self.get_buf()))
519
520    def generate_tables(self):
521        """Generate device defintions for the platform data
522
523        This writes out C platform data initialisation data and
524        U_BOOT_DEVICE() declarations for each valid node. Where a node has
525        multiple compatible strings, a #define is used to make them equivalent.
526
527        See the documentation in doc/driver-model/of-plat.txt for more
528        information.
529        """
530        self.out_header()
531        self.out('#include <common.h>\n')
532        self.out('#include <dm.h>\n')
533        self.out('#include <dt-structs.h>\n')
534        self.out('\n')
535        nodes_to_output = list(self._valid_nodes)
536
537        # Keep outputing nodes until there is none left
538        while nodes_to_output:
539            node = nodes_to_output[0]
540            # Output all the node's dependencies first
541            for req_node in node.phandles:
542                if req_node in nodes_to_output:
543                    self.output_node(req_node)
544                    nodes_to_output.remove(req_node)
545            self.output_node(node)
546            nodes_to_output.remove(node)
547
548
549def run_steps(args, dtb_file, include_disabled, output):
550    """Run all the steps of the dtoc tool
551
552    Args:
553        args: List of non-option arguments provided to the problem
554        dtb_file: Filename of dtb file to process
555        include_disabled: True to include disabled nodes
556        output: Name of output file
557    """
558    if not args:
559        raise ValueError('Please specify a command: struct, platdata')
560
561    plat = DtbPlatdata(dtb_file, include_disabled)
562    plat.scan_dtb()
563    plat.scan_tree()
564    plat.scan_reg_sizes()
565    plat.setup_output(output)
566    structs = plat.scan_structs()
567    plat.scan_phandles()
568
569    for cmd in args[0].split(','):
570        if cmd == 'struct':
571            plat.generate_structs(structs)
572        elif cmd == 'platdata':
573            plat.generate_tables()
574        else:
575            raise ValueError("Unknown command '%s': (use: struct, platdata)" %
576                             cmd)
577