xref: /openbmc/u-boot/tools/dtoc/dtoc.py (revision 5e9b1503)
1#!/usr/bin/python
2#
3# Copyright (C) 2016 Google, Inc
4# Written by Simon Glass <sjg@chromium.org>
5#
6# SPDX-License-Identifier:	GPL-2.0+
7#
8
9import copy
10from optparse import OptionError, OptionParser
11import os
12import struct
13import sys
14
15# Bring in the patman libraries
16our_path = os.path.dirname(os.path.realpath(__file__))
17sys.path.append(os.path.join(our_path, '../patman'))
18
19import fdt
20import fdt_select
21import fdt_util
22
23# When we see these properties we ignore them - i.e. do not create a structure member
24PROP_IGNORE_LIST = [
25    '#address-cells',
26    '#gpio-cells',
27    '#size-cells',
28    'compatible',
29    'linux,phandle',
30    "status",
31    'phandle',
32    'u-boot,dm-pre-reloc',
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    str = name.replace('@', '_at_')
55    str = str.replace('-', '_')
56    str = str.replace(',', '_')
57    str = str.replace('/', '__')
58    return str
59
60def TabTo(num_tabs, str):
61    if len(str) >= num_tabs * 8:
62        return str + ' '
63    return str + '\t' * (num_tabs - len(str) // 8)
64
65class DtbPlatdata:
66    """Provide a means to convert device tree binary data to platform data
67
68    The output of this process is C structures which can be used in space-
69    constrained encvironments where the ~3KB code overhead of device tree
70    code is not affordable.
71
72    Properties:
73        fdt: Fdt object, referencing the device tree
74        _dtb_fname: Filename of the input device tree binary file
75        _valid_nodes: A list of Node object with compatible strings
76        _options: Command-line options
77        _phandle_node: A dict of nodes indexed by phandle number (1, 2...)
78        _outfile: The current output file (sys.stdout or a real file)
79        _lines: Stashed list of output lines for outputting in the future
80        _phandle_node: A dict of Nodes indexed by phandle (an integer)
81    """
82    def __init__(self, dtb_fname, options):
83        self._dtb_fname = dtb_fname
84        self._valid_nodes = None
85        self._options = options
86        self._phandle_node = {}
87        self._outfile = None
88        self._lines = []
89
90    def SetupOutput(self, fname):
91        """Set up the output destination
92
93        Once this is done, future calls to self.Out() will output to this
94        file.
95
96        Args:
97            fname: Filename to send output to, or '-' for stdout
98        """
99        if fname == '-':
100            self._outfile = sys.stdout
101        else:
102            self._outfile = open(fname, 'w')
103
104    def Out(self, str):
105        """Output a string to the output file
106
107        Args:
108            str: String to output
109        """
110        self._outfile.write(str)
111
112    def Buf(self, str):
113        """Buffer up a string to send later
114
115        Args:
116            str: String to add to our 'buffer' list
117        """
118        self._lines.append(str)
119
120    def GetBuf(self):
121        """Get the contents of the output buffer, and clear it
122
123        Returns:
124            The output buffer, which is then cleared for future use
125        """
126        lines = self._lines
127        self._lines = []
128        return lines
129
130    def GetValue(self, type, value):
131        """Get a value as a C expression
132
133        For integers this returns a byte-swapped (little-endian) hex string
134        For bytes this returns a hex string, e.g. 0x12
135        For strings this returns a literal string enclosed in quotes
136        For booleans this return 'true'
137
138        Args:
139            type: Data type (fdt_util)
140            value: Data value, as a string of bytes
141        """
142        if type == fdt.TYPE_INT:
143            return '%#x' % fdt_util.fdt32_to_cpu(value)
144        elif type == fdt.TYPE_BYTE:
145            return '%#x' % ord(value[0])
146        elif type == fdt.TYPE_STRING:
147            return '"%s"' % value
148        elif type == fdt.TYPE_BOOL:
149            return 'true'
150
151    def GetCompatName(self, node):
152        """Get a node's first compatible string as a C identifier
153
154        Args:
155            node: Node object to check
156        Return:
157            C identifier for the first compatible string
158        """
159        compat = node.props['compatible'].value
160        if type(compat) == list:
161            compat = compat[0]
162        return Conv_name_to_c(compat)
163
164    def ScanDtb(self):
165        """Scan the device tree to obtain a tree of notes and properties
166
167        Once this is done, self.fdt.GetRoot() can be called to obtain the
168        device tree root node, and progress from there.
169        """
170        self.fdt = fdt_select.FdtScan(self._dtb_fname)
171
172    def ScanTree(self):
173        """Scan the device tree for useful information
174
175        This fills in the following properties:
176            _phandle_node: A dict of Nodes indexed by phandle (an integer)
177            _valid_nodes: A list of nodes we wish to consider include in the
178                platform data
179        """
180        node_list = []
181        self._phandle_node = {}
182        for node in self.fdt.GetRoot().subnodes:
183            if 'compatible' in node.props:
184                status = node.props.get('status')
185                if (not options.include_disabled and not status or
186                    status.value != 'disabled'):
187                    node_list.append(node)
188                    phandle_prop = node.props.get('phandle')
189                    if phandle_prop:
190                        phandle = phandle_prop.GetPhandle()
191                        self._phandle_node[phandle] = node
192
193        self._valid_nodes = node_list
194
195    def IsPhandle(self, prop):
196        """Check if a node contains phandles
197
198        We have no reliable way of detecting whether a node uses a phandle
199        or not. As an interim measure, use a list of known property names.
200
201        Args:
202            prop: Prop object to check
203        Return:
204            True if the object value contains phandles, else False
205        """
206        if prop.name in ['clocks']:
207            return True
208        return False
209
210    def ScanStructs(self):
211        """Scan the device tree building up the C structures we will use.
212
213        Build a dict keyed by C struct name containing a dict of Prop
214        object for each struct field (keyed by property name). Where the
215        same struct appears multiple times, try to use the 'widest'
216        property, i.e. the one with a type which can express all others.
217
218        Once the widest property is determined, all other properties are
219        updated to match that width.
220        """
221        structs = {}
222        for node in self._valid_nodes:
223            node_name = self.GetCompatName(node)
224            fields = {}
225
226            # Get a list of all the valid properties in this node.
227            for name, prop in node.props.items():
228                if name not in PROP_IGNORE_LIST and name[0] != '#':
229                    fields[name] = copy.deepcopy(prop)
230
231            # If we've seen this node_name before, update the existing struct.
232            if node_name in structs:
233                struct = structs[node_name]
234                for name, prop in fields.items():
235                    oldprop = struct.get(name)
236                    if oldprop:
237                        oldprop.Widen(prop)
238                    else:
239                        struct[name] = prop
240
241            # Otherwise store this as a new struct.
242            else:
243                structs[node_name] = fields
244
245        upto = 0
246        for node in self._valid_nodes:
247            node_name = self.GetCompatName(node)
248            struct = structs[node_name]
249            for name, prop in node.props.items():
250                if name not in PROP_IGNORE_LIST and name[0] != '#':
251                    prop.Widen(struct[name])
252            upto += 1
253        return structs
254
255    def GenerateStructs(self, structs):
256        """Generate struct defintions for the platform data
257
258        This writes out the body of a header file consisting of structure
259        definitions for node in self._valid_nodes. See the documentation in
260        README.of-plat for more information.
261        """
262        self.Out('#include <stdbool.h>\n')
263        self.Out('#include <libfdt.h>\n')
264
265        # Output the struct definition
266        for name in sorted(structs):
267            self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name));
268            for pname in sorted(structs[name]):
269                prop = structs[name][pname]
270                if self.IsPhandle(prop):
271                    # For phandles, include a reference to the target
272                    self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'),
273                                             Conv_name_to_c(prop.name),
274                                             len(prop.value) / 2))
275                else:
276                    ptype = TYPE_NAMES[prop.type]
277                    self.Out('\t%s%s' % (TabTo(2, ptype),
278                                         Conv_name_to_c(prop.name)))
279                    if type(prop.value) == list:
280                        self.Out('[%d]' % len(prop.value))
281                self.Out(';\n')
282            self.Out('};\n')
283
284    def GenerateTables(self):
285        """Generate device defintions for the platform data
286
287        This writes out C platform data initialisation data and
288        U_BOOT_DEVICE() declarations for each valid node. See the
289        documentation in README.of-plat for more information.
290        """
291        self.Out('#include <common.h>\n')
292        self.Out('#include <dm.h>\n')
293        self.Out('#include <dt-structs.h>\n')
294        self.Out('\n')
295        node_txt_list = []
296        for node in self._valid_nodes:
297            struct_name = self.GetCompatName(node)
298            var_name = Conv_name_to_c(node.name)
299            self.Buf('static struct %s%s %s%s = {\n' %
300                (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
301            for pname, prop in node.props.items():
302                if pname in PROP_IGNORE_LIST or pname[0] == '#':
303                    continue
304                ptype = TYPE_NAMES[prop.type]
305                member_name = Conv_name_to_c(prop.name)
306                self.Buf('\t%s= ' % TabTo(3, '.' + member_name))
307
308                # Special handling for lists
309                if type(prop.value) == list:
310                    self.Buf('{')
311                    vals = []
312                    # For phandles, output a reference to the platform data
313                    # of the target node.
314                    if self.IsPhandle(prop):
315                        # Process the list as pairs of (phandle, id)
316                        it = iter(prop.value)
317                        for phandle_cell, id_cell in zip(it, it):
318                            phandle = fdt_util.fdt32_to_cpu(phandle_cell)
319                            id = fdt_util.fdt32_to_cpu(id_cell)
320                            target_node = self._phandle_node[phandle]
321                            name = Conv_name_to_c(target_node.name)
322                            vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id))
323                    else:
324                        for val in prop.value:
325                            vals.append(self.GetValue(prop.type, val))
326                    self.Buf(', '.join(vals))
327                    self.Buf('}')
328                else:
329                    self.Buf(self.GetValue(prop.type, prop.value))
330                self.Buf(',\n')
331            self.Buf('};\n')
332
333            # Add a device declaration
334            self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
335            self.Buf('\t.name\t\t= "%s",\n' % struct_name)
336            self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
337            self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' %
338                     (VAL_PREFIX, var_name))
339            self.Buf('};\n')
340            self.Buf('\n')
341
342            # Output phandle target nodes first, since they may be referenced
343            # by others
344            if 'phandle' in node.props:
345                self.Out(''.join(self.GetBuf()))
346            else:
347                node_txt_list.append(self.GetBuf())
348
349        # Output all the nodes which are not phandle targets themselves, but
350        # may reference them. This avoids the need for forward declarations.
351        for node_txt in node_txt_list:
352            self.Out(''.join(node_txt))
353
354
355if __name__ != "__main__":
356    pass
357
358parser = OptionParser()
359parser.add_option('-d', '--dtb-file', action='store',
360                  help='Specify the .dtb input file')
361parser.add_option('--include-disabled', action='store_true',
362                  help='Include disabled nodes')
363parser.add_option('-o', '--output', action='store', default='-',
364                  help='Select output filename')
365(options, args) = parser.parse_args()
366
367if not args:
368    raise ValueError('Please specify a command: struct, platdata')
369
370plat = DtbPlatdata(options.dtb_file, options)
371plat.ScanDtb()
372plat.ScanTree()
373plat.SetupOutput(options.output)
374structs = plat.ScanStructs()
375
376for cmd in args[0].split(','):
377    if cmd == 'struct':
378        plat.GenerateStructs(structs)
379    elif cmd == 'platdata':
380        plat.GenerateTables()
381    else:
382        raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd)
383