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