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