blob: 4a1162a9faea555693a781079361df5dc101f924 [file] [log] [blame]
Simon Glass7581c012017-06-18 22:08:58 -06001#!/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
Simon Glass2be282c2017-06-18 22:08:59 -06009"""Device tree to platform data class
10
11This supports converting device tree data to C structures definitions and
12static data.
13"""
14
Simon Glass7581c012017-06-18 22:08:58 -060015import copy
Simon Glass2be282c2017-06-18 22:08:59 -060016import sys
Simon Glass7581c012017-06-18 22:08:58 -060017
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',
Simon Glassfbdfd222017-08-29 14:15:48 -060041 fdt.TYPE_INT64: 'fdt64_t',
Simon Glass2be282c2017-06-18 22:08:59 -060042}
Simon Glass7581c012017-06-18 22:08:58 -060043
44STRUCT_PREFIX = 'dtd_'
45VAL_PREFIX = 'dtv_'
46
Simon Glass2be282c2017-06-18 22:08:59 -060047def conv_name_to_c(name):
Simon Glass7581c012017-06-18 22:08:58 -060048 """Convert a device-tree name to a C identifier
49
Simon Glass30107b02017-06-18 22:09:04 -060050 This uses multiple replace() calls instead of re.sub() since it is faster
51 (400ms for 1m calls versus 1000ms for the 're' version).
52
Simon Glass7581c012017-06-18 22:08:58 -060053 Args:
54 name: Name to convert
55 Return:
56 String containing the C version of this name
57 """
Simon Glass2be282c2017-06-18 22:08:59 -060058 new = name.replace('@', '_at_')
59 new = new.replace('-', '_')
60 new = new.replace(',', '_')
61 new = new.replace('.', '_')
Simon Glass2be282c2017-06-18 22:08:59 -060062 return new
Simon Glass7581c012017-06-18 22:08:58 -060063
Simon Glass2be282c2017-06-18 22:08:59 -060064def tab_to(num_tabs, line):
65 """Append tabs to a line of text to reach a tab stop.
Simon Glass7581c012017-06-18 22:08:58 -060066
Simon Glass2be282c2017-06-18 22:08:59 -060067 Args:
68 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
69 line: Line of text to append to
70
71 Returns:
72 line with the correct number of tabs appeneded. If the line already
73 extends past that tab stop then a single space is appended.
74 """
75 if len(line) >= num_tabs * 8:
76 return line + ' '
77 return line + '\t' * (num_tabs - len(line) // 8)
78
Simon Glass56e0bbe2017-06-18 22:09:02 -060079def get_value(ftype, value):
80 """Get a value as a C expression
81
82 For integers this returns a byte-swapped (little-endian) hex string
83 For bytes this returns a hex string, e.g. 0x12
84 For strings this returns a literal string enclosed in quotes
85 For booleans this return 'true'
86
87 Args:
88 type: Data type (fdt_util)
89 value: Data value, as a string of bytes
90 """
91 if ftype == fdt.TYPE_INT:
92 return '%#x' % fdt_util.fdt32_to_cpu(value)
93 elif ftype == fdt.TYPE_BYTE:
94 return '%#x' % ord(value[0])
95 elif ftype == fdt.TYPE_STRING:
96 return '"%s"' % value
97 elif ftype == fdt.TYPE_BOOL:
98 return 'true'
Simon Glassfbdfd222017-08-29 14:15:48 -060099 elif ftype == fdt.TYPE_INT64:
100 return '%#x' % value
Simon Glass56e0bbe2017-06-18 22:09:02 -0600101
102def get_compat_name(node):
103 """Get a node's first compatible string as a C identifier
104
105 Args:
106 node: Node object to check
107 Return:
108 Tuple:
109 C identifier for the first compatible string
110 List of C identifiers for all the other compatible strings
111 (possibly empty)
112 """
113 compat = node.props['compatible'].value
114 aliases = []
115 if isinstance(compat, list):
116 compat, aliases = compat[0], compat[1:]
117 return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
118
119def is_phandle(prop):
120 """Check if a node contains phandles
121
122 We have no reliable way of detecting whether a node uses a phandle
123 or not. As an interim measure, use a list of known property names.
124
125 Args:
126 prop: Prop object to check
127 Return:
128 True if the object value contains phandles, else False
129 """
130 if prop.name in ['clocks']:
131 return True
132 return False
133
134
Simon Glass2be282c2017-06-18 22:08:59 -0600135class DtbPlatdata(object):
Simon Glass7581c012017-06-18 22:08:58 -0600136 """Provide a means to convert device tree binary data to platform data
137
138 The output of this process is C structures which can be used in space-
139 constrained encvironments where the ~3KB code overhead of device tree
140 code is not affordable.
141
142 Properties:
Simon Glass2be282c2017-06-18 22:08:59 -0600143 _fdt: Fdt object, referencing the device tree
Simon Glass7581c012017-06-18 22:08:58 -0600144 _dtb_fname: Filename of the input device tree binary file
145 _valid_nodes: A list of Node object with compatible strings
Simon Glasse36024b2017-06-18 22:09:01 -0600146 _include_disabled: true to include nodes marked status = "disabled"
Simon Glass2be282c2017-06-18 22:08:59 -0600147 _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
Simon Glass7581c012017-06-18 22:08:58 -0600148 _outfile: The current output file (sys.stdout or a real file)
149 _lines: Stashed list of output lines for outputting in the future
Simon Glass2be282c2017-06-18 22:08:59 -0600150 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
Simon Glass7581c012017-06-18 22:08:58 -0600151 """
Simon Glasse36024b2017-06-18 22:09:01 -0600152 def __init__(self, dtb_fname, include_disabled):
Simon Glass2be282c2017-06-18 22:08:59 -0600153 self._fdt = None
Simon Glass7581c012017-06-18 22:08:58 -0600154 self._dtb_fname = dtb_fname
155 self._valid_nodes = None
Simon Glasse36024b2017-06-18 22:09:01 -0600156 self._include_disabled = include_disabled
Simon Glass2be282c2017-06-18 22:08:59 -0600157 self._phandle_nodes = {}
Simon Glass7581c012017-06-18 22:08:58 -0600158 self._outfile = None
159 self._lines = []
160 self._aliases = {}
161
Simon Glass2be282c2017-06-18 22:08:59 -0600162 def setup_output(self, fname):
Simon Glass7581c012017-06-18 22:08:58 -0600163 """Set up the output destination
164
Simon Glass2be282c2017-06-18 22:08:59 -0600165 Once this is done, future calls to self.out() will output to this
Simon Glass7581c012017-06-18 22:08:58 -0600166 file.
167
168 Args:
169 fname: Filename to send output to, or '-' for stdout
170 """
171 if fname == '-':
172 self._outfile = sys.stdout
173 else:
174 self._outfile = open(fname, 'w')
175
Simon Glass2be282c2017-06-18 22:08:59 -0600176 def out(self, line):
Simon Glass7581c012017-06-18 22:08:58 -0600177 """Output a string to the output file
178
179 Args:
Simon Glass2be282c2017-06-18 22:08:59 -0600180 line: String to output
Simon Glass7581c012017-06-18 22:08:58 -0600181 """
Simon Glass2be282c2017-06-18 22:08:59 -0600182 self._outfile.write(line)
Simon Glass7581c012017-06-18 22:08:58 -0600183
Simon Glass2be282c2017-06-18 22:08:59 -0600184 def buf(self, line):
Simon Glass7581c012017-06-18 22:08:58 -0600185 """Buffer up a string to send later
186
187 Args:
Simon Glass2be282c2017-06-18 22:08:59 -0600188 line: String to add to our 'buffer' list
Simon Glass7581c012017-06-18 22:08:58 -0600189 """
Simon Glass2be282c2017-06-18 22:08:59 -0600190 self._lines.append(line)
Simon Glass7581c012017-06-18 22:08:58 -0600191
Simon Glass2be282c2017-06-18 22:08:59 -0600192 def get_buf(self):
Simon Glass7581c012017-06-18 22:08:58 -0600193 """Get the contents of the output buffer, and clear it
194
195 Returns:
196 The output buffer, which is then cleared for future use
197 """
198 lines = self._lines
199 self._lines = []
200 return lines
201
Simon Glass2be282c2017-06-18 22:08:59 -0600202 def scan_dtb(self):
Anatolij Gustschinf1a7ba12017-08-18 17:58:51 +0200203 """Scan the device tree to obtain a tree of nodes and properties
Simon Glass7581c012017-06-18 22:08:58 -0600204
Simon Glass2be282c2017-06-18 22:08:59 -0600205 Once this is done, self._fdt.GetRoot() can be called to obtain the
Simon Glass7581c012017-06-18 22:08:58 -0600206 device tree root node, and progress from there.
207 """
Simon Glass2be282c2017-06-18 22:08:59 -0600208 self._fdt = fdt.FdtScan(self._dtb_fname)
Simon Glass7581c012017-06-18 22:08:58 -0600209
Simon Glass2be282c2017-06-18 22:08:59 -0600210 def scan_node(self, root):
211 """Scan a node and subnodes to build a tree of node and phandle info
212
213 This adds each node to self._valid_nodes and each phandle to
214 self._phandle_nodes.
215
216 Args:
217 root: Root node for scan
218 """
Simon Glass7581c012017-06-18 22:08:58 -0600219 for node in root.subnodes:
220 if 'compatible' in node.props:
221 status = node.props.get('status')
Simon Glasse36024b2017-06-18 22:09:01 -0600222 if (not self._include_disabled and not status or
Simon Glass2be282c2017-06-18 22:08:59 -0600223 status.value != 'disabled'):
Simon Glass7581c012017-06-18 22:08:58 -0600224 self._valid_nodes.append(node)
225 phandle_prop = node.props.get('phandle')
226 if phandle_prop:
227 phandle = phandle_prop.GetPhandle()
Simon Glass2be282c2017-06-18 22:08:59 -0600228 self._phandle_nodes[phandle] = node
Simon Glass7581c012017-06-18 22:08:58 -0600229
230 # recurse to handle any subnodes
Simon Glass2be282c2017-06-18 22:08:59 -0600231 self.scan_node(node)
Simon Glass7581c012017-06-18 22:08:58 -0600232
Simon Glass2be282c2017-06-18 22:08:59 -0600233 def scan_tree(self):
Simon Glass7581c012017-06-18 22:08:58 -0600234 """Scan the device tree for useful information
235
236 This fills in the following properties:
Simon Glass2be282c2017-06-18 22:08:59 -0600237 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
Simon Glass7581c012017-06-18 22:08:58 -0600238 _valid_nodes: A list of nodes we wish to consider include in the
239 platform data
240 """
Simon Glass2be282c2017-06-18 22:08:59 -0600241 self._phandle_nodes = {}
Simon Glass7581c012017-06-18 22:08:58 -0600242 self._valid_nodes = []
Simon Glass2be282c2017-06-18 22:08:59 -0600243 return self.scan_node(self._fdt.GetRoot())
Simon Glass7581c012017-06-18 22:08:58 -0600244
Simon Glass2be282c2017-06-18 22:08:59 -0600245 def scan_structs(self):
Simon Glass7581c012017-06-18 22:08:58 -0600246 """Scan the device tree building up the C structures we will use.
247
248 Build a dict keyed by C struct name containing a dict of Prop
249 object for each struct field (keyed by property name). Where the
250 same struct appears multiple times, try to use the 'widest'
251 property, i.e. the one with a type which can express all others.
252
253 Once the widest property is determined, all other properties are
254 updated to match that width.
255 """
256 structs = {}
257 for node in self._valid_nodes:
Simon Glass56e0bbe2017-06-18 22:09:02 -0600258 node_name, _ = get_compat_name(node)
Simon Glass7581c012017-06-18 22:08:58 -0600259 fields = {}
260
261 # Get a list of all the valid properties in this node.
262 for name, prop in node.props.items():
263 if name not in PROP_IGNORE_LIST and name[0] != '#':
264 fields[name] = copy.deepcopy(prop)
265
266 # If we've seen this node_name before, update the existing struct.
267 if node_name in structs:
268 struct = structs[node_name]
269 for name, prop in fields.items():
270 oldprop = struct.get(name)
271 if oldprop:
272 oldprop.Widen(prop)
273 else:
274 struct[name] = prop
275
276 # Otherwise store this as a new struct.
277 else:
278 structs[node_name] = fields
279
280 upto = 0
281 for node in self._valid_nodes:
Simon Glass56e0bbe2017-06-18 22:09:02 -0600282 node_name, _ = get_compat_name(node)
Simon Glass7581c012017-06-18 22:08:58 -0600283 struct = structs[node_name]
284 for name, prop in node.props.items():
285 if name not in PROP_IGNORE_LIST and name[0] != '#':
286 prop.Widen(struct[name])
287 upto += 1
288
Simon Glass56e0bbe2017-06-18 22:09:02 -0600289 struct_name, aliases = get_compat_name(node)
Simon Glass7581c012017-06-18 22:08:58 -0600290 for alias in aliases:
291 self._aliases[alias] = struct_name
292
293 return structs
294
Simon Glass2be282c2017-06-18 22:08:59 -0600295 def scan_phandles(self):
Simon Glass7581c012017-06-18 22:08:58 -0600296 """Figure out what phandles each node uses
297
298 We need to be careful when outputing nodes that use phandles since
299 they must come after the declaration of the phandles in the C file.
300 Otherwise we get a compiler error since the phandle struct is not yet
301 declared.
302
303 This function adds to each node a list of phandle nodes that the node
304 depends on. This allows us to output things in the right order.
305 """
306 for node in self._valid_nodes:
307 node.phandles = set()
308 for pname, prop in node.props.items():
309 if pname in PROP_IGNORE_LIST or pname[0] == '#':
310 continue
Simon Glass2be282c2017-06-18 22:08:59 -0600311 if isinstance(prop.value, list):
Simon Glass56e0bbe2017-06-18 22:09:02 -0600312 if is_phandle(prop):
Simon Glass7581c012017-06-18 22:08:58 -0600313 # Process the list as pairs of (phandle, id)
Simon Glass2be282c2017-06-18 22:08:59 -0600314 value_it = iter(prop.value)
315 for phandle_cell, _ in zip(value_it, value_it):
Simon Glass7581c012017-06-18 22:08:58 -0600316 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
Simon Glass2be282c2017-06-18 22:08:59 -0600317 target_node = self._phandle_nodes[phandle]
Simon Glass7581c012017-06-18 22:08:58 -0600318 node.phandles.add(target_node)
319
320
Simon Glass2be282c2017-06-18 22:08:59 -0600321 def generate_structs(self, structs):
Simon Glass7581c012017-06-18 22:08:58 -0600322 """Generate struct defintions for the platform data
323
324 This writes out the body of a header file consisting of structure
325 definitions for node in self._valid_nodes. See the documentation in
326 README.of-plat for more information.
327 """
Simon Glass2be282c2017-06-18 22:08:59 -0600328 self.out('#include <stdbool.h>\n')
329 self.out('#include <libfdt.h>\n')
Simon Glass7581c012017-06-18 22:08:58 -0600330
331 # Output the struct definition
332 for name in sorted(structs):
Simon Glass2be282c2017-06-18 22:08:59 -0600333 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
Simon Glass7581c012017-06-18 22:08:58 -0600334 for pname in sorted(structs[name]):
335 prop = structs[name][pname]
Simon Glass56e0bbe2017-06-18 22:09:02 -0600336 if is_phandle(prop):
Simon Glass7581c012017-06-18 22:08:58 -0600337 # For phandles, include a reference to the target
Simon Glass2be282c2017-06-18 22:08:59 -0600338 self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
339 conv_name_to_c(prop.name),
Simon Glass7581c012017-06-18 22:08:58 -0600340 len(prop.value) / 2))
341 else:
342 ptype = TYPE_NAMES[prop.type]
Simon Glass2be282c2017-06-18 22:08:59 -0600343 self.out('\t%s%s' % (tab_to(2, ptype),
344 conv_name_to_c(prop.name)))
345 if isinstance(prop.value, list):
346 self.out('[%d]' % len(prop.value))
347 self.out(';\n')
348 self.out('};\n')
Simon Glass7581c012017-06-18 22:08:58 -0600349
350 for alias, struct_name in self._aliases.iteritems():
Simon Glass2be282c2017-06-18 22:08:59 -0600351 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
Simon Glass7581c012017-06-18 22:08:58 -0600352 STRUCT_PREFIX, struct_name))
353
Simon Glass2be282c2017-06-18 22:08:59 -0600354 def output_node(self, node):
Simon Glass7581c012017-06-18 22:08:58 -0600355 """Output the C code for a node
356
357 Args:
358 node: node to output
359 """
Simon Glass56e0bbe2017-06-18 22:09:02 -0600360 struct_name, _ = get_compat_name(node)
Simon Glass2be282c2017-06-18 22:08:59 -0600361 var_name = conv_name_to_c(node.name)
362 self.buf('static struct %s%s %s%s = {\n' %
363 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
Simon Glass7581c012017-06-18 22:08:58 -0600364 for pname, prop in node.props.items():
365 if pname in PROP_IGNORE_LIST or pname[0] == '#':
366 continue
Simon Glass2be282c2017-06-18 22:08:59 -0600367 member_name = conv_name_to_c(prop.name)
368 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
Simon Glass7581c012017-06-18 22:08:58 -0600369
370 # Special handling for lists
Simon Glass2be282c2017-06-18 22:08:59 -0600371 if isinstance(prop.value, list):
372 self.buf('{')
Simon Glass7581c012017-06-18 22:08:58 -0600373 vals = []
374 # For phandles, output a reference to the platform data
375 # of the target node.
Simon Glass56e0bbe2017-06-18 22:09:02 -0600376 if is_phandle(prop):
Simon Glass7581c012017-06-18 22:08:58 -0600377 # Process the list as pairs of (phandle, id)
Simon Glass2be282c2017-06-18 22:08:59 -0600378 value_it = iter(prop.value)
379 for phandle_cell, id_cell in zip(value_it, value_it):
Simon Glass7581c012017-06-18 22:08:58 -0600380 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
Simon Glass2be282c2017-06-18 22:08:59 -0600381 id_num = fdt_util.fdt32_to_cpu(id_cell)
382 target_node = self._phandle_nodes[phandle]
383 name = conv_name_to_c(target_node.name)
384 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
Simon Glass7581c012017-06-18 22:08:58 -0600385 else:
386 for val in prop.value:
Simon Glass56e0bbe2017-06-18 22:09:02 -0600387 vals.append(get_value(prop.type, val))
Simon Glass2be282c2017-06-18 22:08:59 -0600388 self.buf(', '.join(vals))
389 self.buf('}')
Simon Glass7581c012017-06-18 22:08:58 -0600390 else:
Simon Glass56e0bbe2017-06-18 22:09:02 -0600391 self.buf(get_value(prop.type, prop.value))
Simon Glass2be282c2017-06-18 22:08:59 -0600392 self.buf(',\n')
393 self.buf('};\n')
Simon Glass7581c012017-06-18 22:08:58 -0600394
395 # Add a device declaration
Simon Glass2be282c2017-06-18 22:08:59 -0600396 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
397 self.buf('\t.name\t\t= "%s",\n' % struct_name)
398 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
399 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
400 self.buf('};\n')
401 self.buf('\n')
Simon Glass7581c012017-06-18 22:08:58 -0600402
Simon Glass2be282c2017-06-18 22:08:59 -0600403 self.out(''.join(self.get_buf()))
Simon Glass7581c012017-06-18 22:08:58 -0600404
Simon Glass2be282c2017-06-18 22:08:59 -0600405 def generate_tables(self):
Simon Glass7581c012017-06-18 22:08:58 -0600406 """Generate device defintions for the platform data
407
408 This writes out C platform data initialisation data and
409 U_BOOT_DEVICE() declarations for each valid node. Where a node has
410 multiple compatible strings, a #define is used to make them equivalent.
411
412 See the documentation in doc/driver-model/of-plat.txt for more
413 information.
414 """
Simon Glass2be282c2017-06-18 22:08:59 -0600415 self.out('#include <common.h>\n')
416 self.out('#include <dm.h>\n')
417 self.out('#include <dt-structs.h>\n')
418 self.out('\n')
Simon Glass7581c012017-06-18 22:08:58 -0600419 nodes_to_output = list(self._valid_nodes)
420
421 # Keep outputing nodes until there is none left
422 while nodes_to_output:
423 node = nodes_to_output[0]
424 # Output all the node's dependencies first
425 for req_node in node.phandles:
426 if req_node in nodes_to_output:
Simon Glass2be282c2017-06-18 22:08:59 -0600427 self.output_node(req_node)
Simon Glass7581c012017-06-18 22:08:58 -0600428 nodes_to_output.remove(req_node)
Simon Glass2be282c2017-06-18 22:08:59 -0600429 self.output_node(node)
Simon Glass7581c012017-06-18 22:08:58 -0600430 nodes_to_output.remove(node)
Simon Glassfa0ea5b2017-06-18 22:09:03 -0600431
432
433def run_steps(args, dtb_file, include_disabled, output):
434 """Run all the steps of the dtoc tool
435
436 Args:
437 args: List of non-option arguments provided to the problem
438 dtb_file: Filename of dtb file to process
439 include_disabled: True to include disabled nodes
440 output: Name of output file
441 """
442 if not args:
443 raise ValueError('Please specify a command: struct, platdata')
444
445 plat = DtbPlatdata(dtb_file, include_disabled)
446 plat.scan_dtb()
447 plat.scan_tree()
448 plat.setup_output(output)
449 structs = plat.scan_structs()
450 plat.scan_phandles()
451
452 for cmd in args[0].split(','):
453 if cmd == 'struct':
454 plat.generate_structs(structs)
455 elif cmd == 'platdata':
456 plat.generate_tables()
457 else:
458 raise ValueError("Unknown command '%s': (use: struct, platdata)" %
459 cmd)