xref: /openbmc/u-boot/tools/dtoc/dtb_platdata.py (revision 72719d2f)
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}
42
43STRUCT_PREFIX = 'dtd_'
44VAL_PREFIX = 'dtv_'
45
46def conv_name_to_c(name):
47    """Convert a device-tree name to a C identifier
48
49    This uses multiple replace() calls instead of re.sub() since it is faster
50    (400ms for 1m calls versus 1000ms for the 're' version).
51
52    Args:
53        name:   Name to convert
54    Return:
55        String containing the C version of this name
56    """
57    new = name.replace('@', '_at_')
58    new = new.replace('-', '_')
59    new = new.replace(',', '_')
60    new = new.replace('.', '_')
61    return new
62
63def tab_to(num_tabs, line):
64    """Append tabs to a line of text to reach a tab stop.
65
66    Args:
67        num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
68        line: Line of text to append to
69
70    Returns:
71        line with the correct number of tabs appeneded. If the line already
72        extends past that tab stop then a single space is appended.
73    """
74    if len(line) >= num_tabs * 8:
75        return line + ' '
76    return line + '\t' * (num_tabs - len(line) // 8)
77
78def get_value(ftype, value):
79    """Get a value as a C expression
80
81    For integers this returns a byte-swapped (little-endian) hex string
82    For bytes this returns a hex string, e.g. 0x12
83    For strings this returns a literal string enclosed in quotes
84    For booleans this return 'true'
85
86    Args:
87        type: Data type (fdt_util)
88        value: Data value, as a string of bytes
89    """
90    if ftype == fdt.TYPE_INT:
91        return '%#x' % fdt_util.fdt32_to_cpu(value)
92    elif ftype == fdt.TYPE_BYTE:
93        return '%#x' % ord(value[0])
94    elif ftype == fdt.TYPE_STRING:
95        return '"%s"' % value
96    elif ftype == fdt.TYPE_BOOL:
97        return 'true'
98
99def get_compat_name(node):
100    """Get a node's first compatible string as a C identifier
101
102    Args:
103        node: Node object to check
104    Return:
105        Tuple:
106            C identifier for the first compatible string
107            List of C identifiers for all the other compatible strings
108                (possibly empty)
109    """
110    compat = node.props['compatible'].value
111    aliases = []
112    if isinstance(compat, list):
113        compat, aliases = compat[0], compat[1:]
114    return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
115
116def is_phandle(prop):
117    """Check if a node contains phandles
118
119    We have no reliable way of detecting whether a node uses a phandle
120    or not. As an interim measure, use a list of known property names.
121
122    Args:
123        prop: Prop object to check
124    Return:
125        True if the object value contains phandles, else False
126    """
127    if prop.name in ['clocks']:
128        return True
129    return False
130
131
132class DtbPlatdata(object):
133    """Provide a means to convert device tree binary data to platform data
134
135    The output of this process is C structures which can be used in space-
136    constrained encvironments where the ~3KB code overhead of device tree
137    code is not affordable.
138
139    Properties:
140        _fdt: Fdt object, referencing the device tree
141        _dtb_fname: Filename of the input device tree binary file
142        _valid_nodes: A list of Node object with compatible strings
143        _include_disabled: true to include nodes marked status = "disabled"
144        _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
145        _outfile: The current output file (sys.stdout or a real file)
146        _lines: Stashed list of output lines for outputting in the future
147        _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
148    """
149    def __init__(self, dtb_fname, include_disabled):
150        self._fdt = None
151        self._dtb_fname = dtb_fname
152        self._valid_nodes = None
153        self._include_disabled = include_disabled
154        self._phandle_nodes = {}
155        self._outfile = None
156        self._lines = []
157        self._aliases = {}
158
159    def setup_output(self, fname):
160        """Set up the output destination
161
162        Once this is done, future calls to self.out() will output to this
163        file.
164
165        Args:
166            fname: Filename to send output to, or '-' for stdout
167        """
168        if fname == '-':
169            self._outfile = sys.stdout
170        else:
171            self._outfile = open(fname, 'w')
172
173    def out(self, line):
174        """Output a string to the output file
175
176        Args:
177            line: String to output
178        """
179        self._outfile.write(line)
180
181    def buf(self, line):
182        """Buffer up a string to send later
183
184        Args:
185            line: String to add to our 'buffer' list
186        """
187        self._lines.append(line)
188
189    def get_buf(self):
190        """Get the contents of the output buffer, and clear it
191
192        Returns:
193            The output buffer, which is then cleared for future use
194        """
195        lines = self._lines
196        self._lines = []
197        return lines
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 and each phandle to
211        self._phandle_nodes.
212
213        Args:
214            root: Root node for scan
215        """
216        for node in root.subnodes:
217            if 'compatible' in node.props:
218                status = node.props.get('status')
219                if (not self._include_disabled and not status or
220                        status.value != 'disabled'):
221                    self._valid_nodes.append(node)
222                    phandle_prop = node.props.get('phandle')
223                    if phandle_prop:
224                        phandle = phandle_prop.GetPhandle()
225                        self._phandle_nodes[phandle] = node
226
227            # recurse to handle any subnodes
228            self.scan_node(node)
229
230    def scan_tree(self):
231        """Scan the device tree for useful information
232
233        This fills in the following properties:
234            _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
235            _valid_nodes: A list of nodes we wish to consider include in the
236                platform data
237        """
238        self._phandle_nodes = {}
239        self._valid_nodes = []
240        return self.scan_node(self._fdt.GetRoot())
241
242    def scan_structs(self):
243        """Scan the device tree building up the C structures we will use.
244
245        Build a dict keyed by C struct name containing a dict of Prop
246        object for each struct field (keyed by property name). Where the
247        same struct appears multiple times, try to use the 'widest'
248        property, i.e. the one with a type which can express all others.
249
250        Once the widest property is determined, all other properties are
251        updated to match that width.
252        """
253        structs = {}
254        for node in self._valid_nodes:
255            node_name, _ = get_compat_name(node)
256            fields = {}
257
258            # Get a list of all the valid properties in this node.
259            for name, prop in node.props.items():
260                if name not in PROP_IGNORE_LIST and name[0] != '#':
261                    fields[name] = copy.deepcopy(prop)
262
263            # If we've seen this node_name before, update the existing struct.
264            if node_name in structs:
265                struct = structs[node_name]
266                for name, prop in fields.items():
267                    oldprop = struct.get(name)
268                    if oldprop:
269                        oldprop.Widen(prop)
270                    else:
271                        struct[name] = prop
272
273            # Otherwise store this as a new struct.
274            else:
275                structs[node_name] = fields
276
277        upto = 0
278        for node in self._valid_nodes:
279            node_name, _ = get_compat_name(node)
280            struct = structs[node_name]
281            for name, prop in node.props.items():
282                if name not in PROP_IGNORE_LIST and name[0] != '#':
283                    prop.Widen(struct[name])
284            upto += 1
285
286            struct_name, aliases = get_compat_name(node)
287            for alias in aliases:
288                self._aliases[alias] = struct_name
289
290        return structs
291
292    def scan_phandles(self):
293        """Figure out what phandles each node uses
294
295        We need to be careful when outputing nodes that use phandles since
296        they must come after the declaration of the phandles in the C file.
297        Otherwise we get a compiler error since the phandle struct is not yet
298        declared.
299
300        This function adds to each node a list of phandle nodes that the node
301        depends on. This allows us to output things in the right order.
302        """
303        for node in self._valid_nodes:
304            node.phandles = set()
305            for pname, prop in node.props.items():
306                if pname in PROP_IGNORE_LIST or pname[0] == '#':
307                    continue
308                if isinstance(prop.value, list):
309                    if is_phandle(prop):
310                        # Process the list as pairs of (phandle, id)
311                        value_it = iter(prop.value)
312                        for phandle_cell, _ in zip(value_it, value_it):
313                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
314                            target_node = self._phandle_nodes[phandle]
315                            node.phandles.add(target_node)
316
317
318    def generate_structs(self, structs):
319        """Generate struct defintions for the platform data
320
321        This writes out the body of a header file consisting of structure
322        definitions for node in self._valid_nodes. See the documentation in
323        README.of-plat for more information.
324        """
325        self.out('#include <stdbool.h>\n')
326        self.out('#include <libfdt.h>\n')
327
328        # Output the struct definition
329        for name in sorted(structs):
330            self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
331            for pname in sorted(structs[name]):
332                prop = structs[name][pname]
333                if is_phandle(prop):
334                    # For phandles, include a reference to the target
335                    self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
336                                             conv_name_to_c(prop.name),
337                                             len(prop.value) / 2))
338                else:
339                    ptype = TYPE_NAMES[prop.type]
340                    self.out('\t%s%s' % (tab_to(2, ptype),
341                                         conv_name_to_c(prop.name)))
342                    if isinstance(prop.value, list):
343                        self.out('[%d]' % len(prop.value))
344                self.out(';\n')
345            self.out('};\n')
346
347        for alias, struct_name in self._aliases.iteritems():
348            self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
349                                             STRUCT_PREFIX, struct_name))
350
351    def output_node(self, node):
352        """Output the C code for a node
353
354        Args:
355            node: node to output
356        """
357        struct_name, _ = get_compat_name(node)
358        var_name = conv_name_to_c(node.name)
359        self.buf('static struct %s%s %s%s = {\n' %
360                 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
361        for pname, prop in node.props.items():
362            if pname in PROP_IGNORE_LIST or pname[0] == '#':
363                continue
364            member_name = conv_name_to_c(prop.name)
365            self.buf('\t%s= ' % tab_to(3, '.' + member_name))
366
367            # Special handling for lists
368            if isinstance(prop.value, list):
369                self.buf('{')
370                vals = []
371                # For phandles, output a reference to the platform data
372                # of the target node.
373                if is_phandle(prop):
374                    # Process the list as pairs of (phandle, id)
375                    value_it = iter(prop.value)
376                    for phandle_cell, id_cell in zip(value_it, value_it):
377                        phandle = fdt_util.fdt32_to_cpu(phandle_cell)
378                        id_num = fdt_util.fdt32_to_cpu(id_cell)
379                        target_node = self._phandle_nodes[phandle]
380                        name = conv_name_to_c(target_node.name)
381                        vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
382                else:
383                    for val in prop.value:
384                        vals.append(get_value(prop.type, val))
385                self.buf(', '.join(vals))
386                self.buf('}')
387            else:
388                self.buf(get_value(prop.type, prop.value))
389            self.buf(',\n')
390        self.buf('};\n')
391
392        # Add a device declaration
393        self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
394        self.buf('\t.name\t\t= "%s",\n' % struct_name)
395        self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
396        self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
397        self.buf('};\n')
398        self.buf('\n')
399
400        self.out(''.join(self.get_buf()))
401
402    def generate_tables(self):
403        """Generate device defintions for the platform data
404
405        This writes out C platform data initialisation data and
406        U_BOOT_DEVICE() declarations for each valid node. Where a node has
407        multiple compatible strings, a #define is used to make them equivalent.
408
409        See the documentation in doc/driver-model/of-plat.txt for more
410        information.
411        """
412        self.out('#include <common.h>\n')
413        self.out('#include <dm.h>\n')
414        self.out('#include <dt-structs.h>\n')
415        self.out('\n')
416        nodes_to_output = list(self._valid_nodes)
417
418        # Keep outputing nodes until there is none left
419        while nodes_to_output:
420            node = nodes_to_output[0]
421            # Output all the node's dependencies first
422            for req_node in node.phandles:
423                if req_node in nodes_to_output:
424                    self.output_node(req_node)
425                    nodes_to_output.remove(req_node)
426            self.output_node(node)
427            nodes_to_output.remove(node)
428
429
430def run_steps(args, dtb_file, include_disabled, output):
431    """Run all the steps of the dtoc tool
432
433    Args:
434        args: List of non-option arguments provided to the problem
435        dtb_file: Filename of dtb file to process
436        include_disabled: True to include disabled nodes
437        output: Name of output file
438    """
439    if not args:
440        raise ValueError('Please specify a command: struct, platdata')
441
442    plat = DtbPlatdata(dtb_file, include_disabled)
443    plat.scan_dtb()
444    plat.scan_tree()
445    plat.setup_output(output)
446    structs = plat.scan_structs()
447    plat.scan_phandles()
448
449    for cmd in args[0].split(','):
450        if cmd == 'struct':
451            plat.generate_structs(structs)
452        elif cmd == 'platdata':
453            plat.generate_tables()
454        else:
455            raise ValueError("Unknown command '%s': (use: struct, platdata)" %
456                             cmd)
457