2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2017 Google, Inc
5 # Written by Simon Glass <sjg@chromium.org>
8 """Device tree to platform data class
10 This supports converting device tree data to C structures definitions and
21 from dtoc import fdt_util
22 from patman import tools
24 # When we see these properties we ignore them - i.e. do not create a structure member
33 'u-boot,dm-pre-reloc',
38 # C type declarations for the tyues we support
40 fdt.TYPE_INT: 'fdt32_t',
41 fdt.TYPE_BYTE: 'unsigned char',
42 fdt.TYPE_STRING: 'const char *',
43 fdt.TYPE_BOOL: 'bool',
44 fdt.TYPE_INT64: 'fdt64_t',
47 STRUCT_PREFIX = 'dtd_'
50 # This holds information about a property which includes phandles.
52 # max_args: integer: Maximum number or arguments that any phandle uses (int).
53 # args: Number of args for each phandle in the property. The total number of
54 # phandles is len(args). This is a list of integers.
55 PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args'])
57 # Holds a single phandle link, allowing a C struct value to be assigned to point
60 # var_node: C variable to assign (e.g. 'dtv_mmc.clocks[0].node')
61 # dev_name: Name of device to assign to (e.g. 'clock')
62 PhandleLink = collections.namedtuple('PhandleLink', ['var_node', 'dev_name'])
65 def conv_name_to_c(name):
66 """Convert a device-tree name to a C identifier
68 This uses multiple replace() calls instead of re.sub() since it is faster
69 (400ms for 1m calls versus 1000ms for the 're' version).
74 String containing the C version of this name
76 new = name.replace('@', '_at_')
77 new = new.replace('-', '_')
78 new = new.replace(',', '_')
79 new = new.replace('.', '_')
82 def tab_to(num_tabs, line):
83 """Append tabs to a line of text to reach a tab stop.
86 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
87 line: Line of text to append to
90 line with the correct number of tabs appeneded. If the line already
91 extends past that tab stop then a single space is appended.
93 if len(line) >= num_tabs * 8:
95 return line + '\t' * (num_tabs - len(line) // 8)
97 def get_value(ftype, value):
98 """Get a value as a C expression
100 For integers this returns a byte-swapped (little-endian) hex string
101 For bytes this returns a hex string, e.g. 0x12
102 For strings this returns a literal string enclosed in quotes
103 For booleans this return 'true'
106 type: Data type (fdt_util)
107 value: Data value, as a string of bytes
109 if ftype == fdt.TYPE_INT:
110 return '%#x' % fdt_util.fdt32_to_cpu(value)
111 elif ftype == fdt.TYPE_BYTE:
112 return '%#x' % tools.ToByte(value[0])
113 elif ftype == fdt.TYPE_STRING:
114 # Handle evil ACPI backslashes by adding another backslash before them.
115 # So "\\_SB.GPO0" in the device tree effectively stays like that in C
116 return '"%s"' % value.replace('\\', '\\\\')
117 elif ftype == fdt.TYPE_BOOL:
119 elif ftype == fdt.TYPE_INT64:
122 def get_compat_name(node):
123 """Get the node's list of compatible string as a C identifiers
126 node: Node object to check
128 List of C identifiers for all the compatible strings
130 compat = node.props['compatible'].value
131 if not isinstance(compat, list):
133 return [conv_name_to_c(c) for c in compat]
136 class DtbPlatdata(object):
137 """Provide a means to convert device tree binary data to platform data
139 The output of this process is C structures which can be used in space-
140 constrained encvironments where the ~3KB code overhead of device tree
141 code is not affordable.
144 _fdt: Fdt object, referencing the device tree
145 _dtb_fname: Filename of the input device tree binary file
146 _valid_nodes: A list of Node object with compatible strings. The list
147 is ordered by conv_name_to_c(node.name)
148 _include_disabled: true to include nodes marked status = "disabled"
149 _outfile: The current output file (sys.stdout or a real file)
150 _warning_disabled: true to disable warnings about driver names not found
151 _lines: Stashed list of output lines for outputting in the future
152 _drivers: List of valid driver names found in drivers/
153 _driver_aliases: Dict that holds aliases for driver names
154 key: Driver alias declared with
155 U_BOOT_DRIVER_ALIAS(driver_alias, driver_name)
156 value: Driver name declared with U_BOOT_DRIVER(driver_name)
157 _drivers_additional: List of additional drivers to use during scanning
159 def __init__(self, dtb_fname, include_disabled, warning_disabled,
160 drivers_additional=[]):
162 self._dtb_fname = dtb_fname
163 self._valid_nodes = None
164 self._include_disabled = include_disabled
166 self._warning_disabled = warning_disabled
169 self._driver_aliases = {}
170 self._drivers_additional = drivers_additional
172 def get_normalized_compat_name(self, node):
173 """Get a node's normalized compat name
175 Returns a valid driver name by retrieving node's list of compatible
176 string as a C identifier and performing a check against _drivers
177 and a lookup in driver_aliases printing a warning in case of failure.
180 node: Node object to check
183 Driver name associated with the first compatible string
184 List of C identifiers for all the other compatible strings
186 In case of no match found, the return will be the same as
189 compat_list_c = get_compat_name(node)
191 for compat_c in compat_list_c:
192 if not compat_c in self._drivers:
193 compat_c = self._driver_aliases.get(compat_c)
197 aliases_c = compat_list_c
198 if compat_c in aliases_c:
199 aliases_c.remove(compat_c)
200 return compat_c, aliases_c
202 if not self._warning_disabled:
203 print('WARNING: the driver %s was not found in the driver list'
204 % (compat_list_c[0]))
206 return compat_list_c[0], compat_list_c[1:]
208 def setup_output(self, fname):
209 """Set up the output destination
211 Once this is done, future calls to self.out() will output to this
215 fname: Filename to send output to, or '-' for stdout
218 self._outfile = sys.stdout
220 self._outfile = open(fname, 'w')
223 """Output a string to the output file
226 line: String to output
228 self._outfile.write(line)
231 """Buffer up a string to send later
234 line: String to add to our 'buffer' list
236 self._lines.append(line)
239 """Get the contents of the output buffer, and clear it
242 The output buffer, which is then cleared for future use
248 def out_header(self):
249 """Output a message indicating that this is an auto-generated file"""
253 * This file was generated by dtoc from a .dtb (device tree binary) file.
258 def get_phandle_argc(self, prop, node_name):
259 """Check if a node contains phandles
261 We have no reliable way of detecting whether a node uses a phandle
262 or not. As an interim measure, use a list of known property names.
265 prop: Prop object to check
267 Number of argument cells is this is a phandle, else None
269 if prop.name in ['clocks', 'cd-gpios']:
270 if not isinstance(prop.value, list):
271 prop.value = [prop.value]
278 phandle = fdt_util.fdt32_to_cpu(val[i])
279 # If we get to the end of the list, stop. This can happen
280 # since some nodes have more phandles in the list than others,
281 # but we allocate enough space for the largest list. So those
282 # nodes with shorter lists end up with zeroes at the end.
285 target = self._fdt.phandle_to_node.get(phandle)
287 raise ValueError("Cannot parse '%s' in node '%s'" %
288 (prop.name, node_name))
290 for prop_name in ['#clock-cells', '#gpio-cells']:
291 cells = target.props.get(prop_name)
295 raise ValueError("Node '%s' has no cells property" %
297 num_args = fdt_util.fdt32_to_cpu(cells.value)
298 max_args = max(max_args, num_args)
299 args.append(num_args)
301 return PhandleInfo(max_args, args)
304 def scan_driver(self, fn):
305 """Scan a driver file to build a list of driver names and aliases
307 This procedure will populate self._drivers and self._driver_aliases
310 fn: Driver filename to scan
312 with open(fn, encoding='utf-8') as fd:
315 except UnicodeDecodeError:
316 # This seems to happen on older Python versions
317 print("Skipping file '%s' due to unicode error" % fn)
320 # The following re will search for driver names declared as
321 # U_BOOT_DRIVER(driver_name)
322 drivers = re.findall('U_BOOT_DRIVER\((.*)\)', buff)
324 for driver in drivers:
325 self._drivers.append(driver)
327 # The following re will search for driver aliases declared as
328 # U_BOOT_DRIVER_ALIAS(alias, driver_name)
329 driver_aliases = re.findall('U_BOOT_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
332 for alias in driver_aliases: # pragma: no cover
335 self._driver_aliases[alias[1]] = alias[0]
337 def scan_drivers(self):
338 """Scan the driver folders to build a list of driver names and aliases
340 This procedure will populate self._drivers and self._driver_aliases
343 basedir = sys.argv[0].replace('tools/dtoc/dtoc', '')
346 for (dirpath, dirnames, filenames) in os.walk(basedir):
348 if not fn.endswith('.c'):
350 self.scan_driver(dirpath + '/' + fn)
352 for fn in self._drivers_additional:
353 if not isinstance(fn, str) or len(fn) == 0:
358 self.scan_driver(basedir + '/' + fn)
361 """Scan the device tree to obtain a tree of nodes and properties
363 Once this is done, self._fdt.GetRoot() can be called to obtain the
364 device tree root node, and progress from there.
366 self._fdt = fdt.FdtScan(self._dtb_fname)
368 def scan_node(self, root, valid_nodes):
369 """Scan a node and subnodes to build a tree of node and phandle info
371 This adds each node to self._valid_nodes.
374 root: Root node for scan
375 valid_nodes: List of Node objects to add to
377 for node in root.subnodes:
378 if 'compatible' in node.props:
379 status = node.props.get('status')
380 if (not self._include_disabled and not status or
381 status.value != 'disabled'):
382 valid_nodes.append(node)
384 # recurse to handle any subnodes
385 self.scan_node(node, valid_nodes)
388 """Scan the device tree for useful information
390 This fills in the following properties:
391 _valid_nodes: A list of nodes we wish to consider include in the
395 self.scan_node(self._fdt.GetRoot(), valid_nodes)
396 self._valid_nodes = sorted(valid_nodes,
397 key=lambda x: conv_name_to_c(x.name))
398 for idx, node in enumerate(self._valid_nodes):
402 def get_num_cells(node):
403 """Get the number of cells in addresses and sizes for this node
410 Number of address cells for this node
411 Number of size cells for this node
416 na_prop = parent.props.get('#address-cells')
417 ns_prop = parent.props.get('#size-cells')
419 na = fdt_util.fdt32_to_cpu(na_prop.value)
421 ns = fdt_util.fdt32_to_cpu(ns_prop.value)
424 def scan_reg_sizes(self):
425 """Scan for 64-bit 'reg' properties and update the values
427 This finds 'reg' properties with 64-bit data and converts the value to
428 an array of 64-values. This allows it to be output in a way that the
431 for node in self._valid_nodes:
432 reg = node.props.get('reg')
435 na, ns = self.get_num_cells(node)
438 if reg.type != fdt.TYPE_INT:
439 raise ValueError("Node '%s' reg property is not an int" %
441 if len(reg.value) % total:
442 raise ValueError("Node '%s' reg property has %d cells "
443 'which is not a multiple of na + ns = %d + %d)' %
444 (node.name, len(reg.value), na, ns))
447 if na != 1 or ns != 1:
448 reg.type = fdt.TYPE_INT64
452 if not isinstance(val, list):
455 addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
457 size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
459 new_value += [addr, size]
460 reg.value = new_value
462 def scan_structs(self):
463 """Scan the device tree building up the C structures we will use.
465 Build a dict keyed by C struct name containing a dict of Prop
466 object for each struct field (keyed by property name). Where the
467 same struct appears multiple times, try to use the 'widest'
468 property, i.e. the one with a type which can express all others.
470 Once the widest property is determined, all other properties are
471 updated to match that width.
474 dict containing structures:
475 key (str): Node name, as a C identifier
476 value: dict containing structure fields:
477 key (str): Field name
478 value: Prop object with field information
480 structs = collections.OrderedDict()
481 for node in self._valid_nodes:
482 node_name, _ = self.get_normalized_compat_name(node)
485 # Get a list of all the valid properties in this node.
486 for name, prop in node.props.items():
487 if name not in PROP_IGNORE_LIST and name[0] != '#':
488 fields[name] = copy.deepcopy(prop)
490 # If we've seen this node_name before, update the existing struct.
491 if node_name in structs:
492 struct = structs[node_name]
493 for name, prop in fields.items():
494 oldprop = struct.get(name)
500 # Otherwise store this as a new struct.
502 structs[node_name] = fields
505 for node in self._valid_nodes:
506 node_name, _ = self.get_normalized_compat_name(node)
507 struct = structs[node_name]
508 for name, prop in node.props.items():
509 if name not in PROP_IGNORE_LIST and name[0] != '#':
510 prop.Widen(struct[name])
515 def scan_phandles(self):
516 """Figure out what phandles each node uses
518 We need to be careful when outputing nodes that use phandles since
519 they must come after the declaration of the phandles in the C file.
520 Otherwise we get a compiler error since the phandle struct is not yet
523 This function adds to each node a list of phandle nodes that the node
524 depends on. This allows us to output things in the right order.
526 for node in self._valid_nodes:
527 node.phandles = set()
528 for pname, prop in node.props.items():
529 if pname in PROP_IGNORE_LIST or pname[0] == '#':
531 info = self.get_phandle_argc(prop, node.name)
533 # Process the list as pairs of (phandle, id)
535 for args in info.args:
536 phandle_cell = prop.value[pos]
537 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
538 target_node = self._fdt.phandle_to_node[phandle]
539 node.phandles.add(target_node)
543 def generate_structs(self, structs):
544 """Generate struct defintions for the platform data
546 This writes out the body of a header file consisting of structure
547 definitions for node in self._valid_nodes. See the documentation in
548 doc/driver-model/of-plat.rst for more information.
551 structs: dict containing structures:
552 key (str): Node name, as a C identifier
553 value: dict containing structure fields:
554 key (str): Field name
555 value: Prop object with field information
559 self.out('#include <stdbool.h>\n')
560 self.out('#include <linux/libfdt.h>\n')
562 # Output the struct definition
563 for name in sorted(structs):
564 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
565 for pname in sorted(structs[name]):
566 prop = structs[name][pname]
567 info = self.get_phandle_argc(prop, structs[name])
569 # For phandles, include a reference to the target
570 struct_name = 'struct phandle_%d_arg' % info.max_args
571 self.out('\t%s%s[%d]' % (tab_to(2, struct_name),
572 conv_name_to_c(prop.name),
575 ptype = TYPE_NAMES[prop.type]
576 self.out('\t%s%s' % (tab_to(2, ptype),
577 conv_name_to_c(prop.name)))
578 if isinstance(prop.value, list):
579 self.out('[%d]' % len(prop.value))
583 def output_node(self, node):
584 """Output the C code for a node
589 def _output_list(node, prop):
590 """Output the C code for a devicetree property that holds a list
593 node (fdt.Node): Node to output
594 prop (fdt.Prop): Prop to output
598 # For phandles, output a reference to the platform data
599 # of the target node.
600 info = self.get_phandle_argc(prop, node.name)
602 # Process the list as pairs of (phandle, id)
605 for args in info.args:
606 phandle_cell = prop.value[pos]
607 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
608 target_node = self._fdt.phandle_to_node[phandle]
609 name = conv_name_to_c(target_node.name)
611 for i in range(args):
613 str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i])))
615 vals.append('\t{%d, {%s}}' % (target_node.idx,
616 ', '.join(arg_values)))
619 self.buf('\n\t\t%s,' % val)
621 for val in prop.value:
622 vals.append(get_value(prop.type, val))
624 # Put 8 values per line to avoid very long lines.
625 for i in range(0, len(vals), 8):
628 self.buf(', '.join(vals[i:i + 8]))
631 struct_name, _ = self.get_normalized_compat_name(node)
632 var_name = conv_name_to_c(node.name)
633 self.buf('/* Node %s index %d */\n' % (node.path, node.idx))
634 self.buf('static struct %s%s %s%s = {\n' %
635 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
636 for pname in sorted(node.props):
637 prop = node.props[pname]
638 if pname in PROP_IGNORE_LIST or pname[0] == '#':
640 member_name = conv_name_to_c(prop.name)
641 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
643 # Special handling for lists
644 if isinstance(prop.value, list):
645 _output_list(node, prop)
647 self.buf(get_value(prop.type, prop.value))
651 # Add a device declaration
652 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
653 self.buf('\t.name\t\t= "%s",\n' % struct_name)
654 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
655 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
657 if node.parent and node.parent in self._valid_nodes:
658 idx = node.parent.idx
659 self.buf('\t.parent_idx\t= %d,\n' % idx)
663 self.out(''.join(self.get_buf()))
665 def generate_tables(self):
666 """Generate device defintions for the platform data
668 This writes out C platform data initialisation data and
669 U_BOOT_DEVICE() declarations for each valid node. Where a node has
670 multiple compatible strings, a #define is used to make them equivalent.
672 See the documentation in doc/driver-model/of-plat.rst for more
676 self.out('/* Allow use of U_BOOT_DEVICE() in this file */\n')
677 self.out('#define DT_PLATDATA_C\n')
679 self.out('#include <common.h>\n')
680 self.out('#include <dm.h>\n')
681 self.out('#include <dt-structs.h>\n')
683 nodes_to_output = list(self._valid_nodes)
685 # Keep outputing nodes until there is none left
686 while nodes_to_output:
687 node = nodes_to_output[0]
688 # Output all the node's dependencies first
689 for req_node in node.phandles:
690 if req_node in nodes_to_output:
691 self.output_node(req_node)
692 nodes_to_output.remove(req_node)
693 self.output_node(node)
694 nodes_to_output.remove(node)
696 # Define dm_populate_phandle_data() which will add the linking between
697 # nodes using DM_GET_DEVICE
698 # dtv_dmc_at_xxx.clocks[0].node = DM_GET_DEVICE(clock_controller_at_xxx)
699 self.buf('void dm_populate_phandle_data(void) {\n')
702 self.out(''.join(self.get_buf()))
704 def run_steps(args, dtb_file, include_disabled, output, warning_disabled=False,
705 drivers_additional=[]):
706 """Run all the steps of the dtoc tool
709 args: List of non-option arguments provided to the problem
710 dtb_file: Filename of dtb file to process
711 include_disabled: True to include disabled nodes
712 output: Name of output file
715 raise ValueError('Please specify a command: struct, platdata')
717 plat = DtbPlatdata(dtb_file, include_disabled, warning_disabled, drivers_additional)
721 plat.scan_reg_sizes()
722 plat.setup_output(output)
723 structs = plat.scan_structs()
726 for cmd in args[0].split(','):
728 plat.generate_structs(structs)
729 elif cmd == 'platdata':
730 plat.generate_tables()
732 raise ValueError("Unknown command '%s': (use: struct, platdata)" %