2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2017 Google, Inc
8 """Device tree to platform data class
10 This supports converting device tree data to C structures definitions and
19 from dtoc import fdt_util
20 from patman import tools
22 # When we see these properties we ignore them - i.e. do not create a structure member
31 'u-boot,dm-pre-reloc',
36 # C type declarations for the tyues we support
38 fdt.TYPE_INT: 'fdt32_t',
39 fdt.TYPE_BYTE: 'unsigned char',
40 fdt.TYPE_STRING: 'const char *',
41 fdt.TYPE_BOOL: 'bool',
42 fdt.TYPE_INT64: 'fdt64_t',
45 STRUCT_PREFIX = 'dtd_'
48 # This holds information about a property which includes phandles.
50 # max_args: integer: Maximum number or arguments that any phandle uses (int).
51 # args: Number of args for each phandle in the property. The total number of
52 # phandles is len(args). This is a list of integers.
53 PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args'])
56 def conv_name_to_c(name):
57 """Convert a device-tree name to a C identifier
59 This uses multiple replace() calls instead of re.sub() since it is faster
60 (400ms for 1m calls versus 1000ms for the 're' version).
65 String containing the C version of this name
67 new = name.replace('@', '_at_')
68 new = new.replace('-', '_')
69 new = new.replace(',', '_')
70 new = new.replace('.', '_')
73 def tab_to(num_tabs, line):
74 """Append tabs to a line of text to reach a tab stop.
77 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
78 line: Line of text to append to
81 line with the correct number of tabs appeneded. If the line already
82 extends past that tab stop then a single space is appended.
84 if len(line) >= num_tabs * 8:
86 return line + '\t' * (num_tabs - len(line) // 8)
88 def get_value(ftype, value):
89 """Get a value as a C expression
91 For integers this returns a byte-swapped (little-endian) hex string
92 For bytes this returns a hex string, e.g. 0x12
93 For strings this returns a literal string enclosed in quotes
94 For booleans this return 'true'
97 type: Data type (fdt_util)
98 value: Data value, as a string of bytes
100 if ftype == fdt.TYPE_INT:
101 return '%#x' % fdt_util.fdt32_to_cpu(value)
102 elif ftype == fdt.TYPE_BYTE:
103 return '%#x' % tools.ToByte(value[0])
104 elif ftype == fdt.TYPE_STRING:
105 return '"%s"' % value
106 elif ftype == fdt.TYPE_BOOL:
108 elif ftype == fdt.TYPE_INT64:
111 def get_compat_name(node):
112 """Get a node's first compatible string as a C identifier
115 node: Node object to check
118 C identifier for the first compatible string
119 List of C identifiers for all the other compatible strings
122 compat = node.props['compatible'].value
124 if isinstance(compat, list):
125 compat, aliases = compat[0], compat[1:]
126 return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
129 class DtbPlatdata(object):
130 """Provide a means to convert device tree binary data to platform data
132 The output of this process is C structures which can be used in space-
133 constrained encvironments where the ~3KB code overhead of device tree
134 code is not affordable.
137 _fdt: Fdt object, referencing the device tree
138 _dtb_fname: Filename of the input device tree binary file
139 _valid_nodes: A list of Node object with compatible strings
140 _include_disabled: true to include nodes marked status = "disabled"
141 _outfile: The current output file (sys.stdout or a real file)
142 _lines: Stashed list of output lines for outputting in the future
144 def __init__(self, dtb_fname, include_disabled):
146 self._dtb_fname = dtb_fname
147 self._valid_nodes = None
148 self._include_disabled = include_disabled
153 def setup_output(self, fname):
154 """Set up the output destination
156 Once this is done, future calls to self.out() will output to this
160 fname: Filename to send output to, or '-' for stdout
163 self._outfile = sys.stdout
165 self._outfile = open(fname, 'w')
168 """Output a string to the output file
171 line: String to output
173 self._outfile.write(line)
176 """Buffer up a string to send later
179 line: String to add to our 'buffer' list
181 self._lines.append(line)
184 """Get the contents of the output buffer, and clear it
187 The output buffer, which is then cleared for future use
193 def out_header(self):
194 """Output a message indicating that this is an auto-generated file"""
198 * This file was generated by dtoc from a .dtb (device tree binary) file.
203 def get_phandle_argc(self, prop, node_name):
204 """Check if a node contains phandles
206 We have no reliable way of detecting whether a node uses a phandle
207 or not. As an interim measure, use a list of known property names.
210 prop: Prop object to check
212 Number of argument cells is this is a phandle, else None
214 if prop.name in ['clocks']:
215 if not isinstance(prop.value, list):
216 prop.value = [prop.value]
223 phandle = fdt_util.fdt32_to_cpu(val[i])
224 # If we get to the end of the list, stop. This can happen
225 # since some nodes have more phandles in the list than others,
226 # but we allocate enough space for the largest list. So those
227 # nodes with shorter lists end up with zeroes at the end.
230 target = self._fdt.phandle_to_node.get(phandle)
232 raise ValueError("Cannot parse '%s' in node '%s'" %
233 (prop.name, node_name))
234 prop_name = '#clock-cells'
235 cells = target.props.get(prop_name)
237 raise ValueError("Node '%s' has no '%s' property" %
238 (target.name, prop_name))
239 num_args = fdt_util.fdt32_to_cpu(cells.value)
240 max_args = max(max_args, num_args)
241 args.append(num_args)
243 return PhandleInfo(max_args, args)
247 """Scan the device tree to obtain a tree of nodes and properties
249 Once this is done, self._fdt.GetRoot() can be called to obtain the
250 device tree root node, and progress from there.
252 self._fdt = fdt.FdtScan(self._dtb_fname)
254 def scan_node(self, root):
255 """Scan a node and subnodes to build a tree of node and phandle info
257 This adds each node to self._valid_nodes.
260 root: Root node for scan
262 for node in root.subnodes:
263 if 'compatible' in node.props:
264 status = node.props.get('status')
265 if (not self._include_disabled and not status or
266 status.value != 'disabled'):
267 self._valid_nodes.append(node)
269 # recurse to handle any subnodes
273 """Scan the device tree for useful information
275 This fills in the following properties:
276 _valid_nodes: A list of nodes we wish to consider include in the
279 self._valid_nodes = []
280 return self.scan_node(self._fdt.GetRoot())
283 def get_num_cells(node):
284 """Get the number of cells in addresses and sizes for this node
291 Number of address cells for this node
292 Number of size cells for this node
297 na_prop = parent.props.get('#address-cells')
298 ns_prop = parent.props.get('#size-cells')
300 na = fdt_util.fdt32_to_cpu(na_prop.value)
302 ns = fdt_util.fdt32_to_cpu(ns_prop.value)
305 def scan_reg_sizes(self):
306 """Scan for 64-bit 'reg' properties and update the values
308 This finds 'reg' properties with 64-bit data and converts the value to
309 an array of 64-values. This allows it to be output in a way that the
312 for node in self._valid_nodes:
313 reg = node.props.get('reg')
316 na, ns = self.get_num_cells(node)
319 if reg.type != fdt.TYPE_INT:
320 raise ValueError("Node '%s' reg property is not an int" %
322 if len(reg.value) % total:
323 raise ValueError("Node '%s' reg property has %d cells "
324 'which is not a multiple of na + ns = %d + %d)' %
325 (node.name, len(reg.value), na, ns))
328 if na != 1 or ns != 1:
329 reg.type = fdt.TYPE_INT64
333 if not isinstance(val, list):
336 addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
338 size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
340 new_value += [addr, size]
341 reg.value = new_value
343 def scan_structs(self):
344 """Scan the device tree building up the C structures we will use.
346 Build a dict keyed by C struct name containing a dict of Prop
347 object for each struct field (keyed by property name). Where the
348 same struct appears multiple times, try to use the 'widest'
349 property, i.e. the one with a type which can express all others.
351 Once the widest property is determined, all other properties are
352 updated to match that width.
355 for node in self._valid_nodes:
356 node_name, _ = get_compat_name(node)
359 # Get a list of all the valid properties in this node.
360 for name, prop in node.props.items():
361 if name not in PROP_IGNORE_LIST and name[0] != '#':
362 fields[name] = copy.deepcopy(prop)
364 # If we've seen this node_name before, update the existing struct.
365 if node_name in structs:
366 struct = structs[node_name]
367 for name, prop in fields.items():
368 oldprop = struct.get(name)
374 # Otherwise store this as a new struct.
376 structs[node_name] = fields
379 for node in self._valid_nodes:
380 node_name, _ = get_compat_name(node)
381 struct = structs[node_name]
382 for name, prop in node.props.items():
383 if name not in PROP_IGNORE_LIST and name[0] != '#':
384 prop.Widen(struct[name])
387 struct_name, aliases = get_compat_name(node)
388 for alias in aliases:
389 self._aliases[alias] = struct_name
393 def scan_phandles(self):
394 """Figure out what phandles each node uses
396 We need to be careful when outputing nodes that use phandles since
397 they must come after the declaration of the phandles in the C file.
398 Otherwise we get a compiler error since the phandle struct is not yet
401 This function adds to each node a list of phandle nodes that the node
402 depends on. This allows us to output things in the right order.
404 for node in self._valid_nodes:
405 node.phandles = set()
406 for pname, prop in node.props.items():
407 if pname in PROP_IGNORE_LIST or pname[0] == '#':
409 info = self.get_phandle_argc(prop, node.name)
411 # Process the list as pairs of (phandle, id)
413 for args in info.args:
414 phandle_cell = prop.value[pos]
415 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
416 target_node = self._fdt.phandle_to_node[phandle]
417 node.phandles.add(target_node)
421 def generate_structs(self, structs):
422 """Generate struct defintions for the platform data
424 This writes out the body of a header file consisting of structure
425 definitions for node in self._valid_nodes. See the documentation in
426 doc/driver-model/of-plat.rst for more information.
429 self.out('#include <stdbool.h>\n')
430 self.out('#include <linux/libfdt.h>\n')
432 # Output the struct definition
433 for name in sorted(structs):
434 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
435 for pname in sorted(structs[name]):
436 prop = structs[name][pname]
437 info = self.get_phandle_argc(prop, structs[name])
439 # For phandles, include a reference to the target
440 struct_name = 'struct phandle_%d_arg' % info.max_args
441 self.out('\t%s%s[%d]' % (tab_to(2, struct_name),
442 conv_name_to_c(prop.name),
445 ptype = TYPE_NAMES[prop.type]
446 self.out('\t%s%s' % (tab_to(2, ptype),
447 conv_name_to_c(prop.name)))
448 if isinstance(prop.value, list):
449 self.out('[%d]' % len(prop.value))
453 for alias, struct_name in self._aliases.items():
454 if alias not in sorted(structs):
455 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
456 STRUCT_PREFIX, struct_name))
458 def output_node(self, node):
459 """Output the C code for a node
464 struct_name, _ = get_compat_name(node)
465 var_name = conv_name_to_c(node.name)
466 self.buf('static const struct %s%s %s%s = {\n' %
467 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
468 for pname in sorted(node.props):
469 prop = node.props[pname]
470 if pname in PROP_IGNORE_LIST or pname[0] == '#':
472 member_name = conv_name_to_c(prop.name)
473 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
475 # Special handling for lists
476 if isinstance(prop.value, list):
479 # For phandles, output a reference to the platform data
480 # of the target node.
481 info = self.get_phandle_argc(prop, node.name)
483 # Process the list as pairs of (phandle, id)
485 for args in info.args:
486 phandle_cell = prop.value[pos]
487 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
488 target_node = self._fdt.phandle_to_node[phandle]
489 name = conv_name_to_c(target_node.name)
491 for i in range(args):
492 arg_values.append(str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i])))
494 vals.append('\t{&%s%s, {%s}}' % (VAL_PREFIX, name,
495 ', '.join(arg_values)))
497 self.buf('\n\t\t%s,' % val)
499 for val in prop.value:
500 vals.append(get_value(prop.type, val))
502 # Put 8 values per line to avoid very long lines.
503 for i in range(0, len(vals), 8):
506 self.buf(', '.join(vals[i:i + 8]))
509 self.buf(get_value(prop.type, prop.value))
513 # Add a device declaration
514 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
515 self.buf('\t.name\t\t= "%s",\n' % struct_name)
516 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
517 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
521 self.out(''.join(self.get_buf()))
523 def generate_tables(self):
524 """Generate device defintions for the platform data
526 This writes out C platform data initialisation data and
527 U_BOOT_DEVICE() declarations for each valid node. Where a node has
528 multiple compatible strings, a #define is used to make them equivalent.
530 See the documentation in doc/driver-model/of-plat.rst for more
534 self.out('#include <common.h>\n')
535 self.out('#include <dm.h>\n')
536 self.out('#include <dt-structs.h>\n')
538 nodes_to_output = list(self._valid_nodes)
540 # Keep outputing nodes until there is none left
541 while nodes_to_output:
542 node = nodes_to_output[0]
543 # Output all the node's dependencies first
544 for req_node in node.phandles:
545 if req_node in nodes_to_output:
546 self.output_node(req_node)
547 nodes_to_output.remove(req_node)
548 self.output_node(node)
549 nodes_to_output.remove(node)
552 def run_steps(args, dtb_file, include_disabled, output):
553 """Run all the steps of the dtoc tool
556 args: List of non-option arguments provided to the problem
557 dtb_file: Filename of dtb file to process
558 include_disabled: True to include disabled nodes
559 output: Name of output file
562 raise ValueError('Please specify a command: struct, platdata')
564 plat = DtbPlatdata(dtb_file, include_disabled)
567 plat.scan_reg_sizes()
568 plat.setup_output(output)
569 structs = plat.scan_structs()
572 for cmd in args[0].split(','):
574 plat.generate_structs(structs)
575 elif cmd == 'platdata':
576 plat.generate_tables()
578 raise ValueError("Unknown command '%s': (use: struct, platdata)" %