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',
43 STRUCT_PREFIX = 'dtd_'
46 def conv_name_to_c(name):
47 """Convert a device-tree name to a C identifier
49 This uses multiple replace() calls instead of re.sub() since it is faster
50 (400ms for 1m calls versus 1000ms for the 're' version).
55 String containing the C version of this name
57 new = name.replace('@', '_at_')
58 new = new.replace('-', '_')
59 new = new.replace(',', '_')
60 new = new.replace('.', '_')
63 def tab_to(num_tabs, line):
64 """Append tabs to a line of text to reach a tab stop.
67 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
68 line: Line of text to append to
71 line with the correct number of tabs appeneded. If the line already
72 extends past that tab stop then a single space is appended.
74 if len(line) >= num_tabs * 8:
76 return line + '\t' * (num_tabs - len(line) // 8)
78 def get_value(ftype, value):
79 """Get a value as a C expression
81 For integers this returns a byte-swapped (little-endian) hex string
82 For bytes this returns a hex string, e.g. 0x12
83 For strings this returns a literal string enclosed in quotes
84 For booleans this return 'true'
87 type: Data type (fdt_util)
88 value: Data value, as a string of bytes
90 if ftype == fdt.TYPE_INT:
91 return '%#x' % fdt_util.fdt32_to_cpu(value)
92 elif ftype == fdt.TYPE_BYTE:
93 return '%#x' % ord(value[0])
94 elif ftype == fdt.TYPE_STRING:
96 elif ftype == fdt.TYPE_BOOL:
99 def get_compat_name(node):
100 """Get a node's first compatible string as a C identifier
103 node: Node object to check
106 C identifier for the first compatible string
107 List of C identifiers for all the other compatible strings
110 compat = node.props['compatible'].value
112 if isinstance(compat, list):
113 compat, aliases = compat[0], compat[1:]
114 return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
116 def is_phandle(prop):
117 """Check if a node contains phandles
119 We have no reliable way of detecting whether a node uses a phandle
120 or not. As an interim measure, use a list of known property names.
123 prop: Prop object to check
125 True if the object value contains phandles, else False
127 if prop.name in ['clocks']:
132 class DtbPlatdata(object):
133 """Provide a means to convert device tree binary data to platform data
135 The output of this process is C structures which can be used in space-
136 constrained encvironments where the ~3KB code overhead of device tree
137 code is not affordable.
140 _fdt: Fdt object, referencing the device tree
141 _dtb_fname: Filename of the input device tree binary file
142 _valid_nodes: A list of Node object with compatible strings
143 _include_disabled: true to include nodes marked status = "disabled"
144 _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
145 _outfile: The current output file (sys.stdout or a real file)
146 _lines: Stashed list of output lines for outputting in the future
147 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
149 def __init__(self, dtb_fname, include_disabled):
151 self._dtb_fname = dtb_fname
152 self._valid_nodes = None
153 self._include_disabled = include_disabled
154 self._phandle_nodes = {}
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 notes 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 and each phandle to
214 root: Root node for scan
216 for node in root.subnodes:
217 if 'compatible' in node.props:
218 status = node.props.get('status')
219 if (not self._include_disabled and not status or
220 status.value != 'disabled'):
221 self._valid_nodes.append(node)
222 phandle_prop = node.props.get('phandle')
224 phandle = phandle_prop.GetPhandle()
225 self._phandle_nodes[phandle] = node
227 # recurse to handle any subnodes
231 """Scan the device tree for useful information
233 This fills in the following properties:
234 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
235 _valid_nodes: A list of nodes we wish to consider include in the
238 self._phandle_nodes = {}
239 self._valid_nodes = []
240 return self.scan_node(self._fdt.GetRoot())
242 def scan_structs(self):
243 """Scan the device tree building up the C structures we will use.
245 Build a dict keyed by C struct name containing a dict of Prop
246 object for each struct field (keyed by property name). Where the
247 same struct appears multiple times, try to use the 'widest'
248 property, i.e. the one with a type which can express all others.
250 Once the widest property is determined, all other properties are
251 updated to match that width.
254 for node in self._valid_nodes:
255 node_name, _ = get_compat_name(node)
258 # Get a list of all the valid properties in this node.
259 for name, prop in node.props.items():
260 if name not in PROP_IGNORE_LIST and name[0] != '#':
261 fields[name] = copy.deepcopy(prop)
263 # If we've seen this node_name before, update the existing struct.
264 if node_name in structs:
265 struct = structs[node_name]
266 for name, prop in fields.items():
267 oldprop = struct.get(name)
273 # Otherwise store this as a new struct.
275 structs[node_name] = fields
278 for node in self._valid_nodes:
279 node_name, _ = get_compat_name(node)
280 struct = structs[node_name]
281 for name, prop in node.props.items():
282 if name not in PROP_IGNORE_LIST and name[0] != '#':
283 prop.Widen(struct[name])
286 struct_name, aliases = get_compat_name(node)
287 for alias in aliases:
288 self._aliases[alias] = struct_name
292 def scan_phandles(self):
293 """Figure out what phandles each node uses
295 We need to be careful when outputing nodes that use phandles since
296 they must come after the declaration of the phandles in the C file.
297 Otherwise we get a compiler error since the phandle struct is not yet
300 This function adds to each node a list of phandle nodes that the node
301 depends on. This allows us to output things in the right order.
303 for node in self._valid_nodes:
304 node.phandles = set()
305 for pname, prop in node.props.items():
306 if pname in PROP_IGNORE_LIST or pname[0] == '#':
308 if isinstance(prop.value, list):
310 # Process the list as pairs of (phandle, id)
311 value_it = iter(prop.value)
312 for phandle_cell, _ in zip(value_it, value_it):
313 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
314 target_node = self._phandle_nodes[phandle]
315 node.phandles.add(target_node)
318 def generate_structs(self, structs):
319 """Generate struct defintions for the platform data
321 This writes out the body of a header file consisting of structure
322 definitions for node in self._valid_nodes. See the documentation in
323 README.of-plat for more information.
325 self.out('#include <stdbool.h>\n')
326 self.out('#include <libfdt.h>\n')
328 # Output the struct definition
329 for name in sorted(structs):
330 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
331 for pname in sorted(structs[name]):
332 prop = structs[name][pname]
334 # For phandles, include a reference to the target
335 self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
336 conv_name_to_c(prop.name),
337 len(prop.value) / 2))
339 ptype = TYPE_NAMES[prop.type]
340 self.out('\t%s%s' % (tab_to(2, ptype),
341 conv_name_to_c(prop.name)))
342 if isinstance(prop.value, list):
343 self.out('[%d]' % len(prop.value))
347 for alias, struct_name in self._aliases.iteritems():
348 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
349 STRUCT_PREFIX, struct_name))
351 def output_node(self, node):
352 """Output the C code for a node
357 struct_name, _ = get_compat_name(node)
358 var_name = conv_name_to_c(node.name)
359 self.buf('static struct %s%s %s%s = {\n' %
360 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
361 for pname, prop in node.props.items():
362 if pname in PROP_IGNORE_LIST or pname[0] == '#':
364 member_name = conv_name_to_c(prop.name)
365 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
367 # Special handling for lists
368 if isinstance(prop.value, list):
371 # For phandles, output a reference to the platform data
372 # of the target node.
374 # Process the list as pairs of (phandle, id)
375 value_it = iter(prop.value)
376 for phandle_cell, id_cell in zip(value_it, value_it):
377 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
378 id_num = fdt_util.fdt32_to_cpu(id_cell)
379 target_node = self._phandle_nodes[phandle]
380 name = conv_name_to_c(target_node.name)
381 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
383 for val in prop.value:
384 vals.append(get_value(prop.type, val))
385 self.buf(', '.join(vals))
388 self.buf(get_value(prop.type, prop.value))
392 # Add a device declaration
393 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
394 self.buf('\t.name\t\t= "%s",\n' % struct_name)
395 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
396 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
400 self.out(''.join(self.get_buf()))
402 def generate_tables(self):
403 """Generate device defintions for the platform data
405 This writes out C platform data initialisation data and
406 U_BOOT_DEVICE() declarations for each valid node. Where a node has
407 multiple compatible strings, a #define is used to make them equivalent.
409 See the documentation in doc/driver-model/of-plat.txt for more
412 self.out('#include <common.h>\n')
413 self.out('#include <dm.h>\n')
414 self.out('#include <dt-structs.h>\n')
416 nodes_to_output = list(self._valid_nodes)
418 # Keep outputing nodes until there is none left
419 while nodes_to_output:
420 node = nodes_to_output[0]
421 # Output all the node's dependencies first
422 for req_node in node.phandles:
423 if req_node in nodes_to_output:
424 self.output_node(req_node)
425 nodes_to_output.remove(req_node)
426 self.output_node(node)
427 nodes_to_output.remove(node)
430 def run_steps(args, dtb_file, include_disabled, output):
431 """Run all the steps of the dtoc tool
434 args: List of non-option arguments provided to the problem
435 dtb_file: Filename of dtb file to process
436 include_disabled: True to include disabled nodes
437 output: Name of output file
440 raise ValueError('Please specify a command: struct, platdata')
442 plat = DtbPlatdata(dtb_file, include_disabled)
445 plat.setup_output(output)
446 structs = plat.scan_structs()
449 for cmd in args[0].split(','):
451 plat.generate_structs(structs)
452 elif cmd == 'platdata':
453 plat.generate_tables()
455 raise ValueError("Unknown command '%s': (use: struct, platdata)" %