xref: /openbmc/u-boot/tools/dtoc/dtb_platdata.py (revision 21d54ac353d76d46848cb7fae14a07775cc3bacf)
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    def scan_structs(self):
246        """Scan the device tree building up the C structures we will use.
247
248        Build a dict keyed by C struct name containing a dict of Prop
249        object for each struct field (keyed by property name). Where the
250        same struct appears multiple times, try to use the 'widest'
251        property, i.e. the one with a type which can express all others.
252
253        Once the widest property is determined, all other properties are
254        updated to match that width.
255        """
256        structs = {}
257        for node in self._valid_nodes:
258            node_name, _ = get_compat_name(node)
259            fields = {}
260
261            # Get a list of all the valid properties in this node.
262            for name, prop in node.props.items():
263                if name not in PROP_IGNORE_LIST and name[0] != '#':
264                    fields[name] = copy.deepcopy(prop)
265
266            # If we've seen this node_name before, update the existing struct.
267            if node_name in structs:
268                struct = structs[node_name]
269                for name, prop in fields.items():
270                    oldprop = struct.get(name)
271                    if oldprop:
272                        oldprop.Widen(prop)
273                    else:
274                        struct[name] = prop
275
276            # Otherwise store this as a new struct.
277            else:
278                structs[node_name] = fields
279
280        upto = 0
281        for node in self._valid_nodes:
282            node_name, _ = get_compat_name(node)
283            struct = structs[node_name]
284            for name, prop in node.props.items():
285                if name not in PROP_IGNORE_LIST and name[0] != '#':
286                    prop.Widen(struct[name])
287            upto += 1
288
289            struct_name, aliases = get_compat_name(node)
290            for alias in aliases:
291                self._aliases[alias] = struct_name
292
293        return structs
294
295    def scan_phandles(self):
296        """Figure out what phandles each node uses
297
298        We need to be careful when outputing nodes that use phandles since
299        they must come after the declaration of the phandles in the C file.
300        Otherwise we get a compiler error since the phandle struct is not yet
301        declared.
302
303        This function adds to each node a list of phandle nodes that the node
304        depends on. This allows us to output things in the right order.
305        """
306        for node in self._valid_nodes:
307            node.phandles = set()
308            for pname, prop in node.props.items():
309                if pname in PROP_IGNORE_LIST or pname[0] == '#':
310                    continue
311                if isinstance(prop.value, list):
312                    if is_phandle(prop):
313                        # Process the list as pairs of (phandle, id)
314                        value_it = iter(prop.value)
315                        for phandle_cell, _ in zip(value_it, value_it):
316                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
317                            target_node = self._phandle_nodes[phandle]
318                            node.phandles.add(target_node)
319
320
321    def generate_structs(self, structs):
322        """Generate struct defintions for the platform data
323
324        This writes out the body of a header file consisting of structure
325        definitions for node in self._valid_nodes. See the documentation in
326        README.of-plat for more information.
327        """
328        self.out('#include <stdbool.h>\n')
329        self.out('#include <libfdt.h>\n')
330
331        # Output the struct definition
332        for name in sorted(structs):
333            self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
334            for pname in sorted(structs[name]):
335                prop = structs[name][pname]
336                if is_phandle(prop):
337                    # For phandles, include a reference to the target
338                    self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
339                                             conv_name_to_c(prop.name),
340                                             len(prop.value) / 2))
341                else:
342                    ptype = TYPE_NAMES[prop.type]
343                    self.out('\t%s%s' % (tab_to(2, ptype),
344                                         conv_name_to_c(prop.name)))
345                    if isinstance(prop.value, list):
346                        self.out('[%d]' % len(prop.value))
347                self.out(';\n')
348            self.out('};\n')
349
350        for alias, struct_name in self._aliases.iteritems():
351            self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
352                                             STRUCT_PREFIX, struct_name))
353
354    def output_node(self, node):
355        """Output the C code for a node
356
357        Args:
358            node: node to output
359        """
360        struct_name, _ = get_compat_name(node)
361        var_name = conv_name_to_c(node.name)
362        self.buf('static struct %s%s %s%s = {\n' %
363                 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
364        for pname, prop in node.props.items():
365            if pname in PROP_IGNORE_LIST or pname[0] == '#':
366                continue
367            member_name = conv_name_to_c(prop.name)
368            self.buf('\t%s= ' % tab_to(3, '.' + member_name))
369
370            # Special handling for lists
371            if isinstance(prop.value, list):
372                self.buf('{')
373                vals = []
374                # For phandles, output a reference to the platform data
375                # of the target node.
376                if is_phandle(prop):
377                    # Process the list as pairs of (phandle, id)
378                    value_it = iter(prop.value)
379                    for phandle_cell, id_cell in zip(value_it, value_it):
380                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
381                        id_num = fdt_util.fdt32_to_cpu(id_cell)
382                        target_node = self._phandle_nodes[phandle]
383                        name = conv_name_to_c(target_node.name)
384                        vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
385                else:
386                    for val in prop.value:
387                        vals.append(get_value(prop.type, val))
388
389                # Put 8 values per line to avoid very long lines.
390                for i in xrange(0, len(vals), 8):
391                    if i:
392                        self.buf(',\n\t\t')
393                    self.buf(', '.join(vals[i:i + 8]))
394                self.buf('}')
395            else:
396                self.buf(get_value(prop.type, prop.value))
397            self.buf(',\n')
398        self.buf('};\n')
399
400        # Add a device declaration
401        self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
402        self.buf('\t.name\t\t= "%s",\n' % struct_name)
403        self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
404        self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
405        self.buf('};\n')
406        self.buf('\n')
407
408        self.out(''.join(self.get_buf()))
409
410    def generate_tables(self):
411        """Generate device defintions for the platform data
412
413        This writes out C platform data initialisation data and
414        U_BOOT_DEVICE() declarations for each valid node. Where a node has
415        multiple compatible strings, a #define is used to make them equivalent.
416
417        See the documentation in doc/driver-model/of-plat.txt for more
418        information.
419        """
420        self.out('#include <common.h>\n')
421        self.out('#include <dm.h>\n')
422        self.out('#include <dt-structs.h>\n')
423        self.out('\n')
424        nodes_to_output = list(self._valid_nodes)
425
426        # Keep outputing nodes until there is none left
427        while nodes_to_output:
428            node = nodes_to_output[0]
429            # Output all the node's dependencies first
430            for req_node in node.phandles:
431                if req_node in nodes_to_output:
432                    self.output_node(req_node)
433                    nodes_to_output.remove(req_node)
434            self.output_node(node)
435            nodes_to_output.remove(node)
436
437
438def run_steps(args, dtb_file, include_disabled, output):
439    """Run all the steps of the dtoc tool
440
441    Args:
442        args: List of non-option arguments provided to the problem
443        dtb_file: Filename of dtb file to process
444        include_disabled: True to include disabled nodes
445        output: Name of output file
446    """
447    if not args:
448        raise ValueError('Please specify a command: struct, platdata')
449
450    plat = DtbPlatdata(dtb_file, include_disabled)
451    plat.scan_dtb()
452    plat.scan_tree()
453    plat.setup_output(output)
454    structs = plat.scan_structs()
455    plat.scan_phandles()
456
457    for cmd in args[0].split(','):
458        if cmd == 'struct':
459            plat.generate_structs(structs)
460        elif cmd == 'platdata':
461            plat.generate_tables()
462        else:
463            raise ValueError("Unknown command '%s': (use: struct, platdata)" %
464                             cmd)
465