3 # Copyright (C) 2017 Google, Inc
4 # Written by Simon Glass <sjg@chromium.org>
6 # SPDX-License-Identifier: GPL-2.0+
9 """Device tree to platform data class
11 This supports converting device tree data to C structures definitions and
21 # When we see these properties we ignore them - i.e. do not create a structure member
30 'u-boot,dm-pre-reloc',
35 # C type declarations for the tyues we support
37 fdt.TYPE_INT: 'fdt32_t',
38 fdt.TYPE_BYTE: 'unsigned char',
39 fdt.TYPE_STRING: 'const char *',
40 fdt.TYPE_BOOL: 'bool',
41 fdt.TYPE_INT64: 'fdt64_t',
44 STRUCT_PREFIX = 'dtd_'
47 def conv_name_to_c(name):
48 """Convert a device-tree name to a C identifier
50 This uses multiple replace() calls instead of re.sub() since it is faster
51 (400ms for 1m calls versus 1000ms for the 're' version).
56 String containing the C version of this name
58 new = name.replace('@', '_at_')
59 new = new.replace('-', '_')
60 new = new.replace(',', '_')
61 new = new.replace('.', '_')
64 def tab_to(num_tabs, line):
65 """Append tabs to a line of text to reach a tab stop.
68 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
69 line: Line of text to append to
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.
75 if len(line) >= num_tabs * 8:
77 return line + '\t' * (num_tabs - len(line) // 8)
79 def get_value(ftype, value):
80 """Get a value as a C expression
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'
88 type: Data type (fdt_util)
89 value: Data value, as a string of bytes
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:
97 elif ftype == fdt.TYPE_BOOL:
99 elif ftype == fdt.TYPE_INT64:
102 def get_compat_name(node):
103 """Get a node's first compatible string as a C identifier
106 node: Node object to check
109 C identifier for the first compatible string
110 List of C identifiers for all the other compatible strings
113 compat = node.props['compatible'].value
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]
119 def is_phandle(prop):
120 """Check if a node contains phandles
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.
126 prop: Prop object to check
128 True if the object value contains phandles, else False
130 if prop.name in ['clocks']:
135 class DtbPlatdata(object):
136 """Provide a means to convert device tree binary data to platform data
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.
143 _fdt: Fdt object, referencing the device tree
144 _dtb_fname: Filename of the input device tree binary file
145 _valid_nodes: A list of Node object with compatible strings
146 _include_disabled: true to include nodes marked status = "disabled"
147 _outfile: The current output file (sys.stdout or a real file)
148 _lines: Stashed list of output lines for outputting in the future
150 def __init__(self, dtb_fname, include_disabled):
152 self._dtb_fname = dtb_fname
153 self._valid_nodes = None
154 self._include_disabled = include_disabled
159 def setup_output(self, fname):
160 """Set up the output destination
162 Once this is done, future calls to self.out() will output to this
166 fname: Filename to send output to, or '-' for stdout
169 self._outfile = sys.stdout
171 self._outfile = open(fname, 'w')
174 """Output a string to the output file
177 line: String to output
179 self._outfile.write(line)
182 """Buffer up a string to send later
185 line: String to add to our 'buffer' list
187 self._lines.append(line)
190 """Get the contents of the output buffer, and clear it
193 The output buffer, which is then cleared for future use
200 """Scan the device tree to obtain a tree of nodes and properties
202 Once this is done, self._fdt.GetRoot() can be called to obtain the
203 device tree root node, and progress from there.
205 self._fdt = fdt.FdtScan(self._dtb_fname)
207 def scan_node(self, root):
208 """Scan a node and subnodes to build a tree of node and phandle info
210 This adds each node to self._valid_nodes.
213 root: Root node for scan
215 for node in root.subnodes:
216 if 'compatible' in node.props:
217 status = node.props.get('status')
218 if (not self._include_disabled and not status or
219 status.value != 'disabled'):
220 self._valid_nodes.append(node)
222 # recurse to handle any subnodes
226 """Scan the device tree for useful information
228 This fills in the following properties:
229 _valid_nodes: A list of nodes we wish to consider include in the
232 self._valid_nodes = []
233 return self.scan_node(self._fdt.GetRoot())
236 def get_num_cells(node):
237 """Get the number of cells in addresses and sizes for this node
244 Number of address cells for this node
245 Number of size cells for this node
250 na_prop = parent.props.get('#address-cells')
251 ns_prop = parent.props.get('#size-cells')
253 na = fdt_util.fdt32_to_cpu(na_prop.value)
255 ns = fdt_util.fdt32_to_cpu(ns_prop.value)
258 def scan_reg_sizes(self):
259 """Scan for 64-bit 'reg' properties and update the values
261 This finds 'reg' properties with 64-bit data and converts the value to
262 an array of 64-values. This allows it to be output in a way that the
265 for node in self._valid_nodes:
266 reg = node.props.get('reg')
269 na, ns = self.get_num_cells(node)
272 if reg.type != fdt.TYPE_INT:
273 raise ValueError("Node '%s' reg property is not an int")
274 if len(reg.value) % total:
275 raise ValueError("Node '%s' reg property has %d cells "
276 'which is not a multiple of na + ns = %d + %d)' %
277 (node.name, len(reg.value), na, ns))
280 if na != 1 or ns != 1:
281 reg.type = fdt.TYPE_INT64
285 if not isinstance(val, list):
288 addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
290 size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
292 new_value += [addr, size]
293 reg.value = new_value
295 def scan_structs(self):
296 """Scan the device tree building up the C structures we will use.
298 Build a dict keyed by C struct name containing a dict of Prop
299 object for each struct field (keyed by property name). Where the
300 same struct appears multiple times, try to use the 'widest'
301 property, i.e. the one with a type which can express all others.
303 Once the widest property is determined, all other properties are
304 updated to match that width.
307 for node in self._valid_nodes:
308 node_name, _ = get_compat_name(node)
311 # Get a list of all the valid properties in this node.
312 for name, prop in node.props.items():
313 if name not in PROP_IGNORE_LIST and name[0] != '#':
314 fields[name] = copy.deepcopy(prop)
316 # If we've seen this node_name before, update the existing struct.
317 if node_name in structs:
318 struct = structs[node_name]
319 for name, prop in fields.items():
320 oldprop = struct.get(name)
326 # Otherwise store this as a new struct.
328 structs[node_name] = fields
331 for node in self._valid_nodes:
332 node_name, _ = get_compat_name(node)
333 struct = structs[node_name]
334 for name, prop in node.props.items():
335 if name not in PROP_IGNORE_LIST and name[0] != '#':
336 prop.Widen(struct[name])
339 struct_name, aliases = get_compat_name(node)
340 for alias in aliases:
341 self._aliases[alias] = struct_name
345 def scan_phandles(self):
346 """Figure out what phandles each node uses
348 We need to be careful when outputing nodes that use phandles since
349 they must come after the declaration of the phandles in the C file.
350 Otherwise we get a compiler error since the phandle struct is not yet
353 This function adds to each node a list of phandle nodes that the node
354 depends on. This allows us to output things in the right order.
356 for node in self._valid_nodes:
357 node.phandles = set()
358 for pname, prop in node.props.items():
359 if pname in PROP_IGNORE_LIST or pname[0] == '#':
361 if isinstance(prop.value, list):
363 # Process the list as pairs of (phandle, id)
364 value_it = iter(prop.value)
365 for phandle_cell, _ in zip(value_it, value_it):
366 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
367 target_node = self._fdt.phandle_to_node[phandle]
368 node.phandles.add(target_node)
371 def generate_structs(self, structs):
372 """Generate struct defintions for the platform data
374 This writes out the body of a header file consisting of structure
375 definitions for node in self._valid_nodes. See the documentation in
376 README.of-plat for more information.
378 self.out('#include <stdbool.h>\n')
379 self.out('#include <libfdt.h>\n')
381 # Output the struct definition
382 for name in sorted(structs):
383 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
384 for pname in sorted(structs[name]):
385 prop = structs[name][pname]
387 # For phandles, include a reference to the target
388 self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
389 conv_name_to_c(prop.name),
390 len(prop.value) / 2))
392 ptype = TYPE_NAMES[prop.type]
393 self.out('\t%s%s' % (tab_to(2, ptype),
394 conv_name_to_c(prop.name)))
395 if isinstance(prop.value, list):
396 self.out('[%d]' % len(prop.value))
400 for alias, struct_name in self._aliases.iteritems():
401 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
402 STRUCT_PREFIX, struct_name))
404 def output_node(self, node):
405 """Output the C code for a node
410 struct_name, _ = get_compat_name(node)
411 var_name = conv_name_to_c(node.name)
412 self.buf('static struct %s%s %s%s = {\n' %
413 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
414 for pname, prop in node.props.items():
415 if pname in PROP_IGNORE_LIST or pname[0] == '#':
417 member_name = conv_name_to_c(prop.name)
418 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
420 # Special handling for lists
421 if isinstance(prop.value, list):
424 # For phandles, output a reference to the platform data
425 # of the target node.
427 # Process the list as pairs of (phandle, id)
428 value_it = iter(prop.value)
429 for phandle_cell, id_cell in zip(value_it, value_it):
430 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
431 id_num = fdt_util.fdt32_to_cpu(id_cell)
432 target_node = self._fdt.phandle_to_node[phandle]
433 name = conv_name_to_c(target_node.name)
434 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
436 for val in prop.value:
437 vals.append(get_value(prop.type, val))
439 # Put 8 values per line to avoid very long lines.
440 for i in xrange(0, len(vals), 8):
443 self.buf(', '.join(vals[i:i + 8]))
446 self.buf(get_value(prop.type, prop.value))
450 # Add a device declaration
451 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
452 self.buf('\t.name\t\t= "%s",\n' % struct_name)
453 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
454 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
458 self.out(''.join(self.get_buf()))
460 def generate_tables(self):
461 """Generate device defintions for the platform data
463 This writes out C platform data initialisation data and
464 U_BOOT_DEVICE() declarations for each valid node. Where a node has
465 multiple compatible strings, a #define is used to make them equivalent.
467 See the documentation in doc/driver-model/of-plat.txt for more
470 self.out('#include <common.h>\n')
471 self.out('#include <dm.h>\n')
472 self.out('#include <dt-structs.h>\n')
474 nodes_to_output = list(self._valid_nodes)
476 # Keep outputing nodes until there is none left
477 while nodes_to_output:
478 node = nodes_to_output[0]
479 # Output all the node's dependencies first
480 for req_node in node.phandles:
481 if req_node in nodes_to_output:
482 self.output_node(req_node)
483 nodes_to_output.remove(req_node)
484 self.output_node(node)
485 nodes_to_output.remove(node)
488 def run_steps(args, dtb_file, include_disabled, output):
489 """Run all the steps of the dtoc tool
492 args: List of non-option arguments provided to the problem
493 dtb_file: Filename of dtb file to process
494 include_disabled: True to include disabled nodes
495 output: Name of output file
498 raise ValueError('Please specify a command: struct, platdata')
500 plat = DtbPlatdata(dtb_file, include_disabled)
503 plat.scan_reg_sizes()
504 plat.setup_output(output)
505 structs = plat.scan_structs()
508 for cmd in args[0].split(','):
510 plat.generate_structs(structs)
511 elif cmd == 'platdata':
512 plat.generate_tables()
514 raise ValueError("Unknown command '%s': (use: struct, platdata)" %