xref: /openbmc/u-boot/tools/dtoc/dtb_platdata.py (revision ae485b54)
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            val = prop.value
215            if not isinstance(val, list):
216                val = [val]
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                target = self._fdt.phandle_to_node.get(phandle)
224                if not target:
225                    raise ValueError("Cannot parse '%s' in node '%s'" %
226                                     (prop.name, node_name))
227                prop_name = '#clock-cells'
228                cells = target.props.get(prop_name)
229                if not cells:
230                    raise ValueError("Node '%s' has no '%s' property" %
231                            (target.name, prop_name))
232                num_args = fdt_util.fdt32_to_cpu(cells.value)
233                max_args = max(max_args, num_args)
234                args.append(num_args)
235                i += 1 + num_args
236            return PhandleInfo(max_args, args)
237        return None
238
239    def scan_dtb(self):
240        """Scan the device tree to obtain a tree of nodes and properties
241
242        Once this is done, self._fdt.GetRoot() can be called to obtain the
243        device tree root node, and progress from there.
244        """
245        self._fdt = fdt.FdtScan(self._dtb_fname)
246
247    def scan_node(self, root):
248        """Scan a node and subnodes to build a tree of node and phandle info
249
250        This adds each node to self._valid_nodes.
251
252        Args:
253            root: Root node for scan
254        """
255        for node in root.subnodes:
256            if 'compatible' in node.props:
257                status = node.props.get('status')
258                if (not self._include_disabled and not status or
259                        status.value != 'disabled'):
260                    self._valid_nodes.append(node)
261
262            # recurse to handle any subnodes
263            self.scan_node(node)
264
265    def scan_tree(self):
266        """Scan the device tree for useful information
267
268        This fills in the following properties:
269            _valid_nodes: A list of nodes we wish to consider include in the
270                platform data
271        """
272        self._valid_nodes = []
273        return self.scan_node(self._fdt.GetRoot())
274
275    @staticmethod
276    def get_num_cells(node):
277        """Get the number of cells in addresses and sizes for this node
278
279        Args:
280            node: Node to check
281
282        Returns:
283            Tuple:
284                Number of address cells for this node
285                Number of size cells for this node
286        """
287        parent = node.parent
288        na, ns = 2, 2
289        if parent:
290            na_prop = parent.props.get('#address-cells')
291            ns_prop = parent.props.get('#size-cells')
292            if na_prop:
293                na = fdt_util.fdt32_to_cpu(na_prop.value)
294            if ns_prop:
295                ns = fdt_util.fdt32_to_cpu(ns_prop.value)
296        return na, ns
297
298    def scan_reg_sizes(self):
299        """Scan for 64-bit 'reg' properties and update the values
300
301        This finds 'reg' properties with 64-bit data and converts the value to
302        an array of 64-values. This allows it to be output in a way that the
303        C code can read.
304        """
305        for node in self._valid_nodes:
306            reg = node.props.get('reg')
307            if not reg:
308                continue
309            na, ns = self.get_num_cells(node)
310            total = na + ns
311
312            if reg.type != fdt.TYPE_INT:
313                raise ValueError("Node '%s' reg property is not an int")
314            if len(reg.value) % total:
315                raise ValueError("Node '%s' reg property has %d cells "
316                        'which is not a multiple of na + ns = %d + %d)' %
317                        (node.name, len(reg.value), na, ns))
318            reg.na = na
319            reg.ns = ns
320            if na != 1 or ns != 1:
321                reg.type = fdt.TYPE_INT64
322                i = 0
323                new_value = []
324                val = reg.value
325                if not isinstance(val, list):
326                    val = [val]
327                while i < len(val):
328                    addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
329                    i += na
330                    size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
331                    i += ns
332                    new_value += [addr, size]
333                reg.value = new_value
334
335    def scan_structs(self):
336        """Scan the device tree building up the C structures we will use.
337
338        Build a dict keyed by C struct name containing a dict of Prop
339        object for each struct field (keyed by property name). Where the
340        same struct appears multiple times, try to use the 'widest'
341        property, i.e. the one with a type which can express all others.
342
343        Once the widest property is determined, all other properties are
344        updated to match that width.
345        """
346        structs = {}
347        for node in self._valid_nodes:
348            node_name, _ = get_compat_name(node)
349            fields = {}
350
351            # Get a list of all the valid properties in this node.
352            for name, prop in node.props.items():
353                if name not in PROP_IGNORE_LIST and name[0] != '#':
354                    fields[name] = copy.deepcopy(prop)
355
356            # If we've seen this node_name before, update the existing struct.
357            if node_name in structs:
358                struct = structs[node_name]
359                for name, prop in fields.items():
360                    oldprop = struct.get(name)
361                    if oldprop:
362                        oldprop.Widen(prop)
363                    else:
364                        struct[name] = prop
365
366            # Otherwise store this as a new struct.
367            else:
368                structs[node_name] = fields
369
370        upto = 0
371        for node in self._valid_nodes:
372            node_name, _ = get_compat_name(node)
373            struct = structs[node_name]
374            for name, prop in node.props.items():
375                if name not in PROP_IGNORE_LIST and name[0] != '#':
376                    prop.Widen(struct[name])
377            upto += 1
378
379            struct_name, aliases = get_compat_name(node)
380            for alias in aliases:
381                self._aliases[alias] = struct_name
382
383        return structs
384
385    def scan_phandles(self):
386        """Figure out what phandles each node uses
387
388        We need to be careful when outputing nodes that use phandles since
389        they must come after the declaration of the phandles in the C file.
390        Otherwise we get a compiler error since the phandle struct is not yet
391        declared.
392
393        This function adds to each node a list of phandle nodes that the node
394        depends on. This allows us to output things in the right order.
395        """
396        for node in self._valid_nodes:
397            node.phandles = set()
398            for pname, prop in node.props.items():
399                if pname in PROP_IGNORE_LIST or pname[0] == '#':
400                    continue
401                info = self.get_phandle_argc(prop, node.name)
402                if info:
403                    if not isinstance(prop.value, list):
404                        prop.value = [prop.value]
405                    # Process the list as pairs of (phandle, id)
406                    pos = 0
407                    for args in info.args:
408                        phandle_cell = prop.value[pos]
409                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
410                        target_node = self._fdt.phandle_to_node[phandle]
411                        node.phandles.add(target_node)
412                        pos += 1 + args
413
414
415    def generate_structs(self, structs):
416        """Generate struct defintions for the platform data
417
418        This writes out the body of a header file consisting of structure
419        definitions for node in self._valid_nodes. See the documentation in
420        README.of-plat for more information.
421        """
422        self.out_header()
423        self.out('#include <stdbool.h>\n')
424        self.out('#include <linux/libfdt.h>\n')
425
426        # Output the struct definition
427        for name in sorted(structs):
428            self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
429            for pname in sorted(structs[name]):
430                prop = structs[name][pname]
431                info = self.get_phandle_argc(prop, structs[name])
432                if info:
433                    # For phandles, include a reference to the target
434                    struct_name = 'struct phandle_%d_arg' % info.max_args
435                    self.out('\t%s%s[%d]' % (tab_to(2, struct_name),
436                                             conv_name_to_c(prop.name),
437                                             len(info.args)))
438                else:
439                    ptype = TYPE_NAMES[prop.type]
440                    self.out('\t%s%s' % (tab_to(2, ptype),
441                                         conv_name_to_c(prop.name)))
442                    if isinstance(prop.value, list):
443                        self.out('[%d]' % len(prop.value))
444                self.out(';\n')
445            self.out('};\n')
446
447        for alias, struct_name in self._aliases.iteritems():
448            self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
449                                             STRUCT_PREFIX, struct_name))
450
451    def output_node(self, node):
452        """Output the C code for a node
453
454        Args:
455            node: node to output
456        """
457        struct_name, _ = get_compat_name(node)
458        var_name = conv_name_to_c(node.name)
459        self.buf('static struct %s%s %s%s = {\n' %
460                 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
461        for pname, prop in node.props.items():
462            if pname in PROP_IGNORE_LIST or pname[0] == '#':
463                continue
464            member_name = conv_name_to_c(prop.name)
465            self.buf('\t%s= ' % tab_to(3, '.' + member_name))
466
467            # Special handling for lists
468            if isinstance(prop.value, list):
469                self.buf('{')
470                vals = []
471                # For phandles, output a reference to the platform data
472                # of the target node.
473                info = self.get_phandle_argc(prop, node.name)
474                if info:
475                    # Process the list as pairs of (phandle, id)
476                    pos = 0
477                    for args in info.args:
478                        phandle_cell = prop.value[pos]
479                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
480                        target_node = self._fdt.phandle_to_node[phandle]
481                        name = conv_name_to_c(target_node.name)
482                        arg_values = []
483                        for i in range(args):
484                            arg_values.append(str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i])))
485                        pos += 1 + args
486                        vals.append('\t{&%s%s, {%s}}' % (VAL_PREFIX, name,
487                                                     ', '.join(arg_values)))
488                    for val in vals:
489                        self.buf('\n\t\t%s,' % val)
490                else:
491                    for val in prop.value:
492                        vals.append(get_value(prop.type, val))
493
494                    # Put 8 values per line to avoid very long lines.
495                    for i in xrange(0, len(vals), 8):
496                        if i:
497                            self.buf(',\n\t\t')
498                        self.buf(', '.join(vals[i:i + 8]))
499                self.buf('}')
500            else:
501                self.buf(get_value(prop.type, prop.value))
502            self.buf(',\n')
503        self.buf('};\n')
504
505        # Add a device declaration
506        self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
507        self.buf('\t.name\t\t= "%s",\n' % struct_name)
508        self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
509        self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
510        self.buf('};\n')
511        self.buf('\n')
512
513        self.out(''.join(self.get_buf()))
514
515    def generate_tables(self):
516        """Generate device defintions for the platform data
517
518        This writes out C platform data initialisation data and
519        U_BOOT_DEVICE() declarations for each valid node. Where a node has
520        multiple compatible strings, a #define is used to make them equivalent.
521
522        See the documentation in doc/driver-model/of-plat.txt for more
523        information.
524        """
525        self.out_header()
526        self.out('#include <common.h>\n')
527        self.out('#include <dm.h>\n')
528        self.out('#include <dt-structs.h>\n')
529        self.out('\n')
530        nodes_to_output = list(self._valid_nodes)
531
532        # Keep outputing nodes until there is none left
533        while nodes_to_output:
534            node = nodes_to_output[0]
535            # Output all the node's dependencies first
536            for req_node in node.phandles:
537                if req_node in nodes_to_output:
538                    self.output_node(req_node)
539                    nodes_to_output.remove(req_node)
540            self.output_node(node)
541            nodes_to_output.remove(node)
542
543
544def run_steps(args, dtb_file, include_disabled, output):
545    """Run all the steps of the dtoc tool
546
547    Args:
548        args: List of non-option arguments provided to the problem
549        dtb_file: Filename of dtb file to process
550        include_disabled: True to include disabled nodes
551        output: Name of output file
552    """
553    if not args:
554        raise ValueError('Please specify a command: struct, platdata')
555
556    plat = DtbPlatdata(dtb_file, include_disabled)
557    plat.scan_dtb()
558    plat.scan_tree()
559    plat.scan_reg_sizes()
560    plat.setup_output(output)
561    structs = plat.scan_structs()
562    plat.scan_phandles()
563
564    for cmd in args[0].split(','):
565        if cmd == 'struct':
566            plat.generate_structs(structs)
567        elif cmd == 'platdata':
568            plat.generate_tables()
569        else:
570            raise ValueError("Unknown command '%s': (use: struct, platdata)" %
571                             cmd)
572