Merge branch 'master' of git://git.denx.de/u-boot-spi
[oweals/u-boot.git] / 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 struct
13 import sys
14
15 # Bring in the patman libraries
16 our_path = os.path.dirname(os.path.realpath(__file__))
17 sys.path.append(os.path.join(our_path, '../patman'))
18
19 import fdt
20 import fdt_select
21 import fdt_util
22
23 # When we see these properties we ignore them - i.e. do not create a structure member
24 PROP_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
36 TYPE_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
43 STRUCT_PREFIX = 'dtd_'
44 VAL_PREFIX = 'dtv_'
45
46 def 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
60 def TabTo(num_tabs, str):
61     if len(str) >= num_tabs * 8:
62         return str + ' '
63     return str + '\t' * (num_tabs - len(str) / 8)
64
65 class 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.iteritems():
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.iteritems():
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.iteritems():
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.iteritems():
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
355 if __name__ != "__main__":
356     pass
357
358 parser = OptionParser()
359 parser.add_option('-d', '--dtb-file', action='store',
360                   help='Specify the .dtb input file')
361 parser.add_option('--include-disabled', action='store_true',
362                   help='Include disabled nodes')
363 parser.add_option('-o', '--output', action='store', default='-',
364                   help='Select output filename')
365 (options, args) = parser.parse_args()
366
367 if not args:
368     raise ValueError('Please specify a command: struct, platdata')
369
370 plat = DtbPlatdata(options.dtb_file, options)
371 plat.ScanDtb()
372 plat.ScanTree()
373 plat.SetupOutput(options.output)
374 structs = plat.ScanStructs()
375
376 for 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)