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
13 See doc/driver-model/of-plat.rst for more informaiton
23 from dtoc import fdt_util
25 # When we see these properties we ignore them - i.e. do not create a structure
35 'u-boot,dm-pre-reloc',
40 # C type declarations for the types we support
42 fdt.Type.INT: 'fdt32_t',
43 fdt.Type.BYTE: 'unsigned char',
44 fdt.Type.STRING: 'const char *',
45 fdt.Type.BOOL: 'bool',
46 fdt.Type.INT64: 'fdt64_t',
49 STRUCT_PREFIX = 'dtd_'
52 # This holds information about a property which includes phandles.
54 # max_args: integer: Maximum number or arguments that any phandle uses (int).
55 # args: Number of args for each phandle in the property. The total number of
56 # phandles is len(args). This is a list of integers.
57 PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args'])
59 # Holds a single phandle link, allowing a C struct value to be assigned to point
62 # var_node: C variable to assign (e.g. 'dtv_mmc.clocks[0].node')
63 # dev_name: Name of device to assign to (e.g. 'clock')
64 PhandleLink = collections.namedtuple('PhandleLink', ['var_node', 'dev_name'])
67 def conv_name_to_c(name):
68 """Convert a device-tree name to a C identifier
70 This uses multiple replace() calls instead of re.sub() since it is faster
71 (400ms for 1m calls versus 1000ms for the 're' version).
74 name (str): Name to convert
76 str: String containing the C version of this name
78 new = name.replace('@', '_at_')
79 new = new.replace('-', '_')
80 new = new.replace(',', '_')
81 new = new.replace('.', '_')
84 def tab_to(num_tabs, line):
85 """Append tabs to a line of text to reach a tab stop.
88 num_tabs (int): Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
89 line (str): Line of text to append to
92 str: line with the correct number of tabs appeneded. If the line already
93 extends past that tab stop then a single space is appended.
95 if len(line) >= num_tabs * 8:
97 return line + '\t' * (num_tabs - len(line) // 8)
99 def get_value(ftype, value):
100 """Get a value as a C expression
102 For integers this returns a byte-swapped (little-endian) hex string
103 For bytes this returns a hex string, e.g. 0x12
104 For strings this returns a literal string enclosed in quotes
105 For booleans this return 'true'
108 ftype (fdt.Type): Data type (fdt_util)
109 value (bytes): Data value, as a string of bytes
112 str: String representation of the value
114 if ftype == fdt.Type.INT:
115 return '%#x' % fdt_util.fdt32_to_cpu(value)
116 elif ftype == fdt.Type.BYTE:
118 return '%#x' % (ord(char) if isinstance(char, str) else char)
119 elif ftype == fdt.Type.STRING:
120 # Handle evil ACPI backslashes by adding another backslash before them.
121 # So "\\_SB.GPO0" in the device tree effectively stays like that in C
122 return '"%s"' % value.replace('\\', '\\\\')
123 elif ftype == fdt.Type.BOOL:
125 else: # ftype == fdt.Type.INT64:
128 def get_compat_name(node):
129 """Get the node's list of compatible string as a C identifiers
132 node (fdt.Node): Node object to check
134 List of C identifiers for all the compatible strings
136 compat = node.props['compatible'].value
137 if not isinstance(compat, list):
139 return [conv_name_to_c(c) for c in compat]
142 class DtbPlatdata(object):
143 """Provide a means to convert device tree binary data to platform data
145 The output of this process is C structures which can be used in space-
146 constrained encvironments where the ~3KB code overhead of device tree
147 code is not affordable.
150 _fdt: Fdt object, referencing the device tree
151 _dtb_fname: Filename of the input device tree binary file
152 _valid_nodes: A list of Node object with compatible strings. The list
153 is ordered by conv_name_to_c(node.name)
154 _include_disabled: true to include nodes marked status = "disabled"
155 _outfile: The current output file (sys.stdout or a real file)
156 _warning_disabled: true to disable warnings about driver names not found
157 _lines: Stashed list of output lines for outputting in the future
158 _drivers: List of valid driver names found in drivers/
159 _driver_aliases: Dict that holds aliases for driver names
160 key: Driver alias declared with
161 U_BOOT_DRIVER_ALIAS(driver_alias, driver_name)
162 value: Driver name declared with U_BOOT_DRIVER(driver_name)
163 _drivers_additional: List of additional drivers to use during scanning
165 def __init__(self, dtb_fname, include_disabled, warning_disabled,
166 drivers_additional=None):
168 self._dtb_fname = dtb_fname
169 self._valid_nodes = None
170 self._include_disabled = include_disabled
172 self._warning_disabled = warning_disabled
175 self._driver_aliases = {}
176 self._drivers_additional = drivers_additional or []
178 def get_normalized_compat_name(self, node):
179 """Get a node's normalized compat name
181 Returns a valid driver name by retrieving node's list of compatible
182 string as a C identifier and performing a check against _drivers
183 and a lookup in driver_aliases printing a warning in case of failure.
186 node: Node object to check
189 Driver name associated with the first compatible string
190 List of C identifiers for all the other compatible strings
192 In case of no match found, the return will be the same as
195 compat_list_c = get_compat_name(node)
197 for compat_c in compat_list_c:
198 if not compat_c in self._drivers:
199 compat_c = self._driver_aliases.get(compat_c)
203 aliases_c = compat_list_c
204 if compat_c in aliases_c:
205 aliases_c.remove(compat_c)
206 return compat_c, aliases_c
208 if not self._warning_disabled:
209 print('WARNING: the driver %s was not found in the driver list'
210 % (compat_list_c[0]))
212 return compat_list_c[0], compat_list_c[1:]
214 def setup_output(self, fname):
215 """Set up the output destination
217 Once this is done, future calls to self.out() will output to this
221 fname (str): Filename to send output to, or '-' for stdout
224 self._outfile = sys.stdout
226 self._outfile = open(fname, 'w')
229 """Output a string to the output file
232 line (str): String to output
234 self._outfile.write(line)
237 """Buffer up a string to send later
240 line (str): String to add to our 'buffer' list
242 self._lines.append(line)
245 """Get the contents of the output buffer, and clear it
248 list(str): The output buffer, which is then cleared for future use
254 def out_header(self):
255 """Output a message indicating that this is an auto-generated file"""
259 * This file was generated by dtoc from a .dtb (device tree binary) file.
264 def get_phandle_argc(self, prop, node_name):
265 """Check if a node contains phandles
267 We have no reliable way of detecting whether a node uses a phandle
268 or not. As an interim measure, use a list of known property names.
271 prop (fdt.Prop): Prop object to check
272 node_name (str): Node name, only used for raising an error
274 int or None: Number of argument cells is this is a phandle,
277 ValueError: if the phandle cannot be parsed or the required property
280 if prop.name in ['clocks', 'cd-gpios']:
281 if not isinstance(prop.value, list):
282 prop.value = [prop.value]
289 phandle = fdt_util.fdt32_to_cpu(val[i])
290 # If we get to the end of the list, stop. This can happen
291 # since some nodes have more phandles in the list than others,
292 # but we allocate enough space for the largest list. So those
293 # nodes with shorter lists end up with zeroes at the end.
296 target = self._fdt.phandle_to_node.get(phandle)
298 raise ValueError("Cannot parse '%s' in node '%s'" %
299 (prop.name, node_name))
301 for prop_name in ['#clock-cells', '#gpio-cells']:
302 cells = target.props.get(prop_name)
306 raise ValueError("Node '%s' has no cells property" %
308 num_args = fdt_util.fdt32_to_cpu(cells.value)
309 max_args = max(max_args, num_args)
310 args.append(num_args)
312 return PhandleInfo(max_args, args)
315 def scan_driver(self, fname):
316 """Scan a driver file to build a list of driver names and aliases
318 This procedure will populate self._drivers and self._driver_aliases
321 fname: Driver filename to scan
323 with open(fname, encoding='utf-8') as inf:
326 except UnicodeDecodeError:
327 # This seems to happen on older Python versions
328 print("Skipping file '%s' due to unicode error" % fname)
331 # The following re will search for driver names declared as
332 # U_BOOT_DRIVER(driver_name)
333 drivers = re.findall('U_BOOT_DRIVER\((.*)\)', buff)
335 for driver in drivers:
336 self._drivers.append(driver)
338 # The following re will search for driver aliases declared as
339 # U_BOOT_DRIVER_ALIAS(alias, driver_name)
340 driver_aliases = re.findall(
341 'U_BOOT_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
344 for alias in driver_aliases: # pragma: no cover
347 self._driver_aliases[alias[1]] = alias[0]
349 def scan_drivers(self):
350 """Scan the driver folders to build a list of driver names and aliases
352 This procedure will populate self._drivers and self._driver_aliases
355 basedir = sys.argv[0].replace('tools/dtoc/dtoc', '')
358 for (dirpath, _, filenames) in os.walk(basedir):
359 for fname in filenames:
360 if not fname.endswith('.c'):
362 self.scan_driver(dirpath + '/' + fname)
364 for fname in self._drivers_additional:
365 if not isinstance(fname, str) or len(fname) == 0:
368 self.scan_driver(fname)
370 self.scan_driver(basedir + '/' + fname)
373 """Scan the device tree to obtain a tree of nodes and properties
375 Once this is done, self._fdt.GetRoot() can be called to obtain the
376 device tree root node, and progress from there.
378 self._fdt = fdt.FdtScan(self._dtb_fname)
380 def scan_node(self, root, valid_nodes):
381 """Scan a node and subnodes to build a tree of node and phandle info
383 This adds each node to self._valid_nodes.
386 root: Root node for scan
387 valid_nodes: List of Node objects to add to
389 for node in root.subnodes:
390 if 'compatible' in node.props:
391 status = node.props.get('status')
392 if (not self._include_disabled and not status or
393 status.value != 'disabled'):
394 valid_nodes.append(node)
396 # recurse to handle any subnodes
397 self.scan_node(node, valid_nodes)
400 """Scan the device tree for useful information
402 This fills in the following properties:
403 _valid_nodes: A list of nodes we wish to consider include in the
407 self.scan_node(self._fdt.GetRoot(), valid_nodes)
408 self._valid_nodes = sorted(valid_nodes,
409 key=lambda x: conv_name_to_c(x.name))
410 for idx, node in enumerate(self._valid_nodes):
414 def get_num_cells(node):
415 """Get the number of cells in addresses and sizes for this node
418 node (fdt.None): Node to check
422 Number of address cells for this node
423 Number of size cells for this node
426 num_addr, num_size = 2, 2
428 addr_prop = parent.props.get('#address-cells')
429 size_prop = parent.props.get('#size-cells')
431 num_addr = fdt_util.fdt32_to_cpu(addr_prop.value)
433 num_size = fdt_util.fdt32_to_cpu(size_prop.value)
434 return num_addr, num_size
436 def scan_reg_sizes(self):
437 """Scan for 64-bit 'reg' properties and update the values
439 This finds 'reg' properties with 64-bit data and converts the value to
440 an array of 64-values. This allows it to be output in a way that the
443 for node in self._valid_nodes:
444 reg = node.props.get('reg')
447 num_addr, num_size = self.get_num_cells(node)
448 total = num_addr + num_size
450 if reg.type != fdt.Type.INT:
451 raise ValueError("Node '%s' reg property is not an int" %
453 if len(reg.value) % total:
455 "Node '%s' reg property has %d cells "
456 'which is not a multiple of na + ns = %d + %d)' %
457 (node.name, len(reg.value), num_addr, num_size))
458 reg.num_addr = num_addr
459 reg.num_size = num_size
460 if num_addr != 1 or num_size != 1:
461 reg.type = fdt.Type.INT64
465 if not isinstance(val, list):
468 addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.num_addr)
470 size = fdt_util.fdt_cells_to_cpu(val[i:], reg.num_size)
472 new_value += [addr, size]
473 reg.value = new_value
475 def scan_structs(self):
476 """Scan the device tree building up the C structures we will use.
478 Build a dict keyed by C struct name containing a dict of Prop
479 object for each struct field (keyed by property name). Where the
480 same struct appears multiple times, try to use the 'widest'
481 property, i.e. the one with a type which can express all others.
483 Once the widest property is determined, all other properties are
484 updated to match that width.
487 dict containing structures:
488 key (str): Node name, as a C identifier
489 value: dict containing structure fields:
490 key (str): Field name
491 value: Prop object with field information
493 structs = collections.OrderedDict()
494 for node in self._valid_nodes:
495 node_name, _ = self.get_normalized_compat_name(node)
498 # Get a list of all the valid properties in this node.
499 for name, prop in node.props.items():
500 if name not in PROP_IGNORE_LIST and name[0] != '#':
501 fields[name] = copy.deepcopy(prop)
503 # If we've seen this node_name before, update the existing struct.
504 if node_name in structs:
505 struct = structs[node_name]
506 for name, prop in fields.items():
507 oldprop = struct.get(name)
513 # Otherwise store this as a new struct.
515 structs[node_name] = fields
517 for node in self._valid_nodes:
518 node_name, _ = self.get_normalized_compat_name(node)
519 struct = structs[node_name]
520 for name, prop in node.props.items():
521 if name not in PROP_IGNORE_LIST and name[0] != '#':
522 prop.Widen(struct[name])
526 def scan_phandles(self):
527 """Figure out what phandles each node uses
529 We need to be careful when outputing nodes that use phandles since
530 they must come after the declaration of the phandles in the C file.
531 Otherwise we get a compiler error since the phandle struct is not yet
534 This function adds to each node a list of phandle nodes that the node
535 depends on. This allows us to output things in the right order.
537 for node in self._valid_nodes:
538 node.phandles = set()
539 for pname, prop in node.props.items():
540 if pname in PROP_IGNORE_LIST or pname[0] == '#':
542 info = self.get_phandle_argc(prop, node.name)
544 # Process the list as pairs of (phandle, id)
546 for args in info.args:
547 phandle_cell = prop.value[pos]
548 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
549 target_node = self._fdt.phandle_to_node[phandle]
550 node.phandles.add(target_node)
554 def generate_structs(self, structs):
555 """Generate struct defintions for the platform data
557 This writes out the body of a header file consisting of structure
558 definitions for node in self._valid_nodes. See the documentation in
559 doc/driver-model/of-plat.rst for more information.
562 structs: dict containing structures:
563 key (str): Node name, as a C identifier
564 value: dict containing structure fields:
565 key (str): Field name
566 value: Prop object with field information
570 self.out('#include <stdbool.h>\n')
571 self.out('#include <linux/libfdt.h>\n')
573 # Output the struct definition
574 for name in sorted(structs):
575 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
576 for pname in sorted(structs[name]):
577 prop = structs[name][pname]
578 info = self.get_phandle_argc(prop, structs[name])
580 # For phandles, include a reference to the target
581 struct_name = 'struct phandle_%d_arg' % info.max_args
582 self.out('\t%s%s[%d]' % (tab_to(2, struct_name),
583 conv_name_to_c(prop.name),
586 ptype = TYPE_NAMES[prop.type]
587 self.out('\t%s%s' % (tab_to(2, ptype),
588 conv_name_to_c(prop.name)))
589 if isinstance(prop.value, list):
590 self.out('[%d]' % len(prop.value))
594 def output_node(self, node):
595 """Output the C code for a node
598 node (fdt.Node): node to output
600 def _output_list(node, prop):
601 """Output the C code for a devicetree property that holds a list
604 node (fdt.Node): Node to output
605 prop (fdt.Prop): Prop to output
609 # For phandles, output a reference to the platform data
610 # of the target node.
611 info = self.get_phandle_argc(prop, node.name)
613 # Process the list as pairs of (phandle, id)
615 for args in info.args:
616 phandle_cell = prop.value[pos]
617 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
618 target_node = self._fdt.phandle_to_node[phandle]
620 for i in range(args):
622 str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i])))
624 vals.append('\t{%d, {%s}}' % (target_node.idx,
625 ', '.join(arg_values)))
627 self.buf('\n\t\t%s,' % val)
629 for val in prop.value:
630 vals.append(get_value(prop.type, val))
632 # Put 8 values per line to avoid very long lines.
633 for i in range(0, len(vals), 8):
636 self.buf(', '.join(vals[i:i + 8]))
639 struct_name, _ = self.get_normalized_compat_name(node)
640 var_name = conv_name_to_c(node.name)
641 self.buf('/* Node %s index %d */\n' % (node.path, node.idx))
642 self.buf('static struct %s%s %s%s = {\n' %
643 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
644 for pname in sorted(node.props):
645 prop = node.props[pname]
646 if pname in PROP_IGNORE_LIST or pname[0] == '#':
648 member_name = conv_name_to_c(prop.name)
649 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
651 # Special handling for lists
652 if isinstance(prop.value, list):
653 _output_list(node, prop)
655 self.buf(get_value(prop.type, prop.value))
659 # Add a device declaration
660 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
661 self.buf('\t.name\t\t= "%s",\n' % struct_name)
662 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
663 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
665 if node.parent and node.parent in self._valid_nodes:
666 idx = node.parent.idx
667 self.buf('\t.parent_idx\t= %d,\n' % idx)
671 self.out(''.join(self.get_buf()))
673 def generate_tables(self):
674 """Generate device defintions for the platform data
676 This writes out C platform data initialisation data and
677 U_BOOT_DEVICE() declarations for each valid node. Where a node has
678 multiple compatible strings, a #define is used to make them equivalent.
680 See the documentation in doc/driver-model/of-plat.rst for more
684 self.out('/* Allow use of U_BOOT_DEVICE() in this file */\n')
685 self.out('#define DT_PLATDATA_C\n')
687 self.out('#include <common.h>\n')
688 self.out('#include <dm.h>\n')
689 self.out('#include <dt-structs.h>\n')
691 nodes_to_output = list(self._valid_nodes)
693 # Keep outputing nodes until there is none left
694 while nodes_to_output:
695 node = nodes_to_output[0]
696 # Output all the node's dependencies first
697 for req_node in node.phandles:
698 if req_node in nodes_to_output:
699 self.output_node(req_node)
700 nodes_to_output.remove(req_node)
701 self.output_node(node)
702 nodes_to_output.remove(node)
704 # Define dm_populate_phandle_data() which will add the linking between
705 # nodes using DM_GET_DEVICE
706 # dtv_dmc_at_xxx.clocks[0].node = DM_GET_DEVICE(clock_controller_at_xxx)
707 self.buf('void dm_populate_phandle_data(void) {\n')
710 self.out(''.join(self.get_buf()))
712 def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False,
713 drivers_additional=None):
714 """Run all the steps of the dtoc tool
717 args (list): List of non-option arguments provided to the problem
718 dtb_file (str): Filename of dtb file to process
719 include_disabled (bool): True to include disabled nodes
720 output (str): Name of output file
721 warning_disabled (bool): True to avoid showing warnings about missing
723 _drivers_additional (list): List of additional drivers to use during
726 ValueError: if args has no command, or an unknown command
729 raise ValueError('Please specify a command: struct, platdata')
731 plat = DtbPlatdata(dtb_file, include_disabled, warning_disabled,
736 plat.scan_reg_sizes()
737 plat.setup_output(output)
738 structs = plat.scan_structs()
741 for cmd in args[0].split(','):
743 plat.generate_structs(structs)
744 elif cmd == 'platdata':
745 plat.generate_tables()
747 raise ValueError("Unknown command '%s': (use: struct, platdata)" %