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