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