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