blob: 1f85343a9fd2de7532b5f032c7939e20ec5f5df2 [file] [log] [blame]
Simon Glassd570dec2017-06-18 22:08:58 -06001#!/usr/bin/python
2#
3# Copyright (C) 2017 Google, Inc
4# Written by Simon Glass <sjg@chromium.org>
5#
6# SPDX-License-Identifier: GPL-2.0+
7#
8
Simon Glass1f730c82017-06-18 22:08:59 -06009"""Device tree to platform data class
10
11This supports converting device tree data to C structures definitions and
12static data.
13"""
14
Simon Glassd570dec2017-06-18 22:08:58 -060015import copy
Simon Glass1f730c82017-06-18 22:08:59 -060016import sys
Simon Glassd570dec2017-06-18 22:08:58 -060017
18import fdt
19import fdt_util
20
21# When we see these properties we ignore them - i.e. do not create a structure member
22PROP_IGNORE_LIST = [
23 '#address-cells',
24 '#gpio-cells',
25 '#size-cells',
26 'compatible',
27 'linux,phandle',
28 "status",
29 'phandle',
30 'u-boot,dm-pre-reloc',
31 'u-boot,dm-tpl',
32 'u-boot,dm-spl',
33]
34
35# C type declarations for the tyues we support
36TYPE_NAMES = {
37 fdt.TYPE_INT: 'fdt32_t',
38 fdt.TYPE_BYTE: 'unsigned char',
39 fdt.TYPE_STRING: 'const char *',
40 fdt.TYPE_BOOL: 'bool',
Simon Glass1f730c82017-06-18 22:08:59 -060041}
Simon Glassd570dec2017-06-18 22:08:58 -060042
43STRUCT_PREFIX = 'dtd_'
44VAL_PREFIX = 'dtv_'
45
Simon Glass1f730c82017-06-18 22:08:59 -060046def conv_name_to_c(name):
Simon Glassd570dec2017-06-18 22:08:58 -060047 """Convert a device-tree name to a C identifier
48
Simon Glass2e0bf3f2017-06-18 22:09:04 -060049 This uses multiple replace() calls instead of re.sub() since it is faster
50 (400ms for 1m calls versus 1000ms for the 're' version).
51
Simon Glassd570dec2017-06-18 22:08:58 -060052 Args:
53 name: Name to convert
54 Return:
55 String containing the C version of this name
56 """
Simon Glass1f730c82017-06-18 22:08:59 -060057 new = name.replace('@', '_at_')
58 new = new.replace('-', '_')
59 new = new.replace(',', '_')
60 new = new.replace('.', '_')
Simon Glass1f730c82017-06-18 22:08:59 -060061 return new
Simon Glassd570dec2017-06-18 22:08:58 -060062
Simon Glass1f730c82017-06-18 22:08:59 -060063def tab_to(num_tabs, line):
64 """Append tabs to a line of text to reach a tab stop.
Simon Glassd570dec2017-06-18 22:08:58 -060065
Simon Glass1f730c82017-06-18 22:08:59 -060066 Args:
67 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
68 line: Line of text to append to
69
70 Returns:
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.
73 """
74 if len(line) >= num_tabs * 8:
75 return line + ' '
76 return line + '\t' * (num_tabs - len(line) // 8)
77
Simon Glass7a2add12017-06-18 22:09:02 -060078def get_value(ftype, value):
79 """Get a value as a C expression
80
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'
85
86 Args:
87 type: Data type (fdt_util)
88 value: Data value, as a string of bytes
89 """
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:
95 return '"%s"' % value
96 elif ftype == fdt.TYPE_BOOL:
97 return 'true'
98
99def get_compat_name(node):
100 """Get a node's first compatible string as a C identifier
101
102 Args:
103 node: Node object to check
104 Return:
105 Tuple:
106 C identifier for the first compatible string
107 List of C identifiers for all the other compatible strings
108 (possibly empty)
109 """
110 compat = node.props['compatible'].value
111 aliases = []
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]
115
116def is_phandle(prop):
117 """Check if a node contains phandles
118
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.
121
122 Args:
123 prop: Prop object to check
124 Return:
125 True if the object value contains phandles, else False
126 """
127 if prop.name in ['clocks']:
128 return True
129 return False
130
131
Simon Glass1f730c82017-06-18 22:08:59 -0600132class DtbPlatdata(object):
Simon Glassd570dec2017-06-18 22:08:58 -0600133 """Provide a means to convert device tree binary data to platform data
134
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.
138
139 Properties:
Simon Glass1f730c82017-06-18 22:08:59 -0600140 _fdt: Fdt object, referencing the device tree
Simon Glassd570dec2017-06-18 22:08:58 -0600141 _dtb_fname: Filename of the input device tree binary file
142 _valid_nodes: A list of Node object with compatible strings
Simon Glasseab3f622017-06-18 22:09:01 -0600143 _include_disabled: true to include nodes marked status = "disabled"
Simon Glass1f730c82017-06-18 22:08:59 -0600144 _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
Simon Glassd570dec2017-06-18 22:08:58 -0600145 _outfile: The current output file (sys.stdout or a real file)
146 _lines: Stashed list of output lines for outputting in the future
Simon Glass1f730c82017-06-18 22:08:59 -0600147 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
Simon Glassd570dec2017-06-18 22:08:58 -0600148 """
Simon Glasseab3f622017-06-18 22:09:01 -0600149 def __init__(self, dtb_fname, include_disabled):
Simon Glass1f730c82017-06-18 22:08:59 -0600150 self._fdt = None
Simon Glassd570dec2017-06-18 22:08:58 -0600151 self._dtb_fname = dtb_fname
152 self._valid_nodes = None
Simon Glasseab3f622017-06-18 22:09:01 -0600153 self._include_disabled = include_disabled
Simon Glass1f730c82017-06-18 22:08:59 -0600154 self._phandle_nodes = {}
Simon Glassd570dec2017-06-18 22:08:58 -0600155 self._outfile = None
156 self._lines = []
157 self._aliases = {}
158
Simon Glass1f730c82017-06-18 22:08:59 -0600159 def setup_output(self, fname):
Simon Glassd570dec2017-06-18 22:08:58 -0600160 """Set up the output destination
161
Simon Glass1f730c82017-06-18 22:08:59 -0600162 Once this is done, future calls to self.out() will output to this
Simon Glassd570dec2017-06-18 22:08:58 -0600163 file.
164
165 Args:
166 fname: Filename to send output to, or '-' for stdout
167 """
168 if fname == '-':
169 self._outfile = sys.stdout
170 else:
171 self._outfile = open(fname, 'w')
172
Simon Glass1f730c82017-06-18 22:08:59 -0600173 def out(self, line):
Simon Glassd570dec2017-06-18 22:08:58 -0600174 """Output a string to the output file
175
176 Args:
Simon Glass1f730c82017-06-18 22:08:59 -0600177 line: String to output
Simon Glassd570dec2017-06-18 22:08:58 -0600178 """
Simon Glass1f730c82017-06-18 22:08:59 -0600179 self._outfile.write(line)
Simon Glassd570dec2017-06-18 22:08:58 -0600180
Simon Glass1f730c82017-06-18 22:08:59 -0600181 def buf(self, line):
Simon Glassd570dec2017-06-18 22:08:58 -0600182 """Buffer up a string to send later
183
184 Args:
Simon Glass1f730c82017-06-18 22:08:59 -0600185 line: String to add to our 'buffer' list
Simon Glassd570dec2017-06-18 22:08:58 -0600186 """
Simon Glass1f730c82017-06-18 22:08:59 -0600187 self._lines.append(line)
Simon Glassd570dec2017-06-18 22:08:58 -0600188
Simon Glass1f730c82017-06-18 22:08:59 -0600189 def get_buf(self):
Simon Glassd570dec2017-06-18 22:08:58 -0600190 """Get the contents of the output buffer, and clear it
191
192 Returns:
193 The output buffer, which is then cleared for future use
194 """
195 lines = self._lines
196 self._lines = []
197 return lines
198
Simon Glass1f730c82017-06-18 22:08:59 -0600199 def scan_dtb(self):
Simon Glassd570dec2017-06-18 22:08:58 -0600200 """Scan the device tree to obtain a tree of notes and properties
201
Simon Glass1f730c82017-06-18 22:08:59 -0600202 Once this is done, self._fdt.GetRoot() can be called to obtain the
Simon Glassd570dec2017-06-18 22:08:58 -0600203 device tree root node, and progress from there.
204 """
Simon Glass1f730c82017-06-18 22:08:59 -0600205 self._fdt = fdt.FdtScan(self._dtb_fname)
206
207 def scan_node(self, root):
208 """Scan a node and subnodes to build a tree of node and phandle info
Simon Glassd570dec2017-06-18 22:08:58 -0600209
Simon Glass1f730c82017-06-18 22:08:59 -0600210 This adds each node to self._valid_nodes and each phandle to
211 self._phandle_nodes.
212
213 Args:
214 root: Root node for scan
215 """
Simon Glassd570dec2017-06-18 22:08:58 -0600216 for node in root.subnodes:
217 if 'compatible' in node.props:
218 status = node.props.get('status')
Simon Glasseab3f622017-06-18 22:09:01 -0600219 if (not self._include_disabled and not status or
Simon Glass1f730c82017-06-18 22:08:59 -0600220 status.value != 'disabled'):
Simon Glassd570dec2017-06-18 22:08:58 -0600221 self._valid_nodes.append(node)
222 phandle_prop = node.props.get('phandle')
223 if phandle_prop:
224 phandle = phandle_prop.GetPhandle()
Simon Glass1f730c82017-06-18 22:08:59 -0600225 self._phandle_nodes[phandle] = node
Simon Glassd570dec2017-06-18 22:08:58 -0600226
227 # recurse to handle any subnodes
Simon Glass1f730c82017-06-18 22:08:59 -0600228 self.scan_node(node)
Simon Glassd570dec2017-06-18 22:08:58 -0600229
Simon Glass1f730c82017-06-18 22:08:59 -0600230 def scan_tree(self):
Simon Glassd570dec2017-06-18 22:08:58 -0600231 """Scan the device tree for useful information
232
233 This fills in the following properties:
Simon Glass1f730c82017-06-18 22:08:59 -0600234 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
Simon Glassd570dec2017-06-18 22:08:58 -0600235 _valid_nodes: A list of nodes we wish to consider include in the
236 platform data
237 """
Simon Glass1f730c82017-06-18 22:08:59 -0600238 self._phandle_nodes = {}
Simon Glassd570dec2017-06-18 22:08:58 -0600239 self._valid_nodes = []
Simon Glass1f730c82017-06-18 22:08:59 -0600240 return self.scan_node(self._fdt.GetRoot())
Simon Glassd570dec2017-06-18 22:08:58 -0600241
Simon Glass1f730c82017-06-18 22:08:59 -0600242 def scan_structs(self):
Simon Glassd570dec2017-06-18 22:08:58 -0600243 """Scan the device tree building up the C structures we will use.
244
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.
249
250 Once the widest property is determined, all other properties are
251 updated to match that width.
252 """
253 structs = {}
254 for node in self._valid_nodes:
Simon Glass7a2add12017-06-18 22:09:02 -0600255 node_name, _ = get_compat_name(node)
Simon Glassd570dec2017-06-18 22:08:58 -0600256 fields = {}
257
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)
262
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)
268 if oldprop:
269 oldprop.Widen(prop)
270 else:
271 struct[name] = prop
272
273 # Otherwise store this as a new struct.
274 else:
275 structs[node_name] = fields
276
277 upto = 0
278 for node in self._valid_nodes:
Simon Glass7a2add12017-06-18 22:09:02 -0600279 node_name, _ = get_compat_name(node)
Simon Glassd570dec2017-06-18 22:08:58 -0600280 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])
284 upto += 1
285
Simon Glass7a2add12017-06-18 22:09:02 -0600286 struct_name, aliases = get_compat_name(node)
Simon Glassd570dec2017-06-18 22:08:58 -0600287 for alias in aliases:
288 self._aliases[alias] = struct_name
289
290 return structs
291
Simon Glass1f730c82017-06-18 22:08:59 -0600292 def scan_phandles(self):
Simon Glassd570dec2017-06-18 22:08:58 -0600293 """Figure out what phandles each node uses
294
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
298 declared.
299
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.
302 """
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] == '#':
307 continue
Simon Glass1f730c82017-06-18 22:08:59 -0600308 if isinstance(prop.value, list):
Simon Glass7a2add12017-06-18 22:09:02 -0600309 if is_phandle(prop):
Simon Glassd570dec2017-06-18 22:08:58 -0600310 # Process the list as pairs of (phandle, id)
Simon Glass1f730c82017-06-18 22:08:59 -0600311 value_it = iter(prop.value)
312 for phandle_cell, _ in zip(value_it, value_it):
Simon Glassd570dec2017-06-18 22:08:58 -0600313 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
Simon Glass1f730c82017-06-18 22:08:59 -0600314 target_node = self._phandle_nodes[phandle]
Simon Glassd570dec2017-06-18 22:08:58 -0600315 node.phandles.add(target_node)
316
317
Simon Glass1f730c82017-06-18 22:08:59 -0600318 def generate_structs(self, structs):
Simon Glassd570dec2017-06-18 22:08:58 -0600319 """Generate struct defintions for the platform data
320
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.
324 """
Simon Glass1f730c82017-06-18 22:08:59 -0600325 self.out('#include <stdbool.h>\n')
326 self.out('#include <libfdt.h>\n')
Simon Glassd570dec2017-06-18 22:08:58 -0600327
328 # Output the struct definition
329 for name in sorted(structs):
Simon Glass1f730c82017-06-18 22:08:59 -0600330 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
Simon Glassd570dec2017-06-18 22:08:58 -0600331 for pname in sorted(structs[name]):
332 prop = structs[name][pname]
Simon Glass7a2add12017-06-18 22:09:02 -0600333 if is_phandle(prop):
Simon Glassd570dec2017-06-18 22:08:58 -0600334 # For phandles, include a reference to the target
Simon Glass1f730c82017-06-18 22:08:59 -0600335 self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
336 conv_name_to_c(prop.name),
Simon Glassd570dec2017-06-18 22:08:58 -0600337 len(prop.value) / 2))
338 else:
339 ptype = TYPE_NAMES[prop.type]
Simon Glass1f730c82017-06-18 22:08:59 -0600340 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))
344 self.out(';\n')
345 self.out('};\n')
Simon Glassd570dec2017-06-18 22:08:58 -0600346
347 for alias, struct_name in self._aliases.iteritems():
Simon Glass1f730c82017-06-18 22:08:59 -0600348 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
Simon Glassd570dec2017-06-18 22:08:58 -0600349 STRUCT_PREFIX, struct_name))
350
Simon Glass1f730c82017-06-18 22:08:59 -0600351 def output_node(self, node):
Simon Glassd570dec2017-06-18 22:08:58 -0600352 """Output the C code for a node
353
354 Args:
355 node: node to output
356 """
Simon Glass7a2add12017-06-18 22:09:02 -0600357 struct_name, _ = get_compat_name(node)
Simon Glass1f730c82017-06-18 22:08:59 -0600358 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))
Simon Glassd570dec2017-06-18 22:08:58 -0600361 for pname, prop in node.props.items():
362 if pname in PROP_IGNORE_LIST or pname[0] == '#':
363 continue
Simon Glass1f730c82017-06-18 22:08:59 -0600364 member_name = conv_name_to_c(prop.name)
365 self.buf('\t%s= ' % tab_to(3, '.' + member_name))
Simon Glassd570dec2017-06-18 22:08:58 -0600366
367 # Special handling for lists
Simon Glass1f730c82017-06-18 22:08:59 -0600368 if isinstance(prop.value, list):
369 self.buf('{')
Simon Glassd570dec2017-06-18 22:08:58 -0600370 vals = []
371 # For phandles, output a reference to the platform data
372 # of the target node.
Simon Glass7a2add12017-06-18 22:09:02 -0600373 if is_phandle(prop):
Simon Glassd570dec2017-06-18 22:08:58 -0600374 # Process the list as pairs of (phandle, id)
Simon Glass1f730c82017-06-18 22:08:59 -0600375 value_it = iter(prop.value)
376 for phandle_cell, id_cell in zip(value_it, value_it):
Simon Glassd570dec2017-06-18 22:08:58 -0600377 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
Simon Glass1f730c82017-06-18 22:08:59 -0600378 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))
Simon Glassd570dec2017-06-18 22:08:58 -0600382 else:
383 for val in prop.value:
Simon Glass7a2add12017-06-18 22:09:02 -0600384 vals.append(get_value(prop.type, val))
Simon Glass1f730c82017-06-18 22:08:59 -0600385 self.buf(', '.join(vals))
386 self.buf('}')
Simon Glassd570dec2017-06-18 22:08:58 -0600387 else:
Simon Glass7a2add12017-06-18 22:09:02 -0600388 self.buf(get_value(prop.type, prop.value))
Simon Glass1f730c82017-06-18 22:08:59 -0600389 self.buf(',\n')
390 self.buf('};\n')
Simon Glassd570dec2017-06-18 22:08:58 -0600391
392 # Add a device declaration
Simon Glass1f730c82017-06-18 22:08:59 -0600393 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))
397 self.buf('};\n')
398 self.buf('\n')
Simon Glassd570dec2017-06-18 22:08:58 -0600399
Simon Glass1f730c82017-06-18 22:08:59 -0600400 self.out(''.join(self.get_buf()))
Simon Glassd570dec2017-06-18 22:08:58 -0600401
Simon Glass1f730c82017-06-18 22:08:59 -0600402 def generate_tables(self):
Simon Glassd570dec2017-06-18 22:08:58 -0600403 """Generate device defintions for the platform data
404
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.
408
409 See the documentation in doc/driver-model/of-plat.txt for more
410 information.
411 """
Simon Glass1f730c82017-06-18 22:08:59 -0600412 self.out('#include <common.h>\n')
413 self.out('#include <dm.h>\n')
414 self.out('#include <dt-structs.h>\n')
415 self.out('\n')
Simon Glassd570dec2017-06-18 22:08:58 -0600416 nodes_to_output = list(self._valid_nodes)
417
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:
Simon Glass1f730c82017-06-18 22:08:59 -0600424 self.output_node(req_node)
Simon Glassd570dec2017-06-18 22:08:58 -0600425 nodes_to_output.remove(req_node)
Simon Glass1f730c82017-06-18 22:08:59 -0600426 self.output_node(node)
Simon Glassd570dec2017-06-18 22:08:58 -0600427 nodes_to_output.remove(node)
Simon Glass3fa797a2017-06-18 22:09:03 -0600428
429
430def run_steps(args, dtb_file, include_disabled, output):
431 """Run all the steps of the dtoc tool
432
433 Args:
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
438 """
439 if not args:
440 raise ValueError('Please specify a command: struct, platdata')
441
442 plat = DtbPlatdata(dtb_file, include_disabled)
443 plat.scan_dtb()
444 plat.scan_tree()
445 plat.setup_output(output)
446 structs = plat.scan_structs()
447 plat.scan_phandles()
448
449 for cmd in args[0].split(','):
450 if cmd == 'struct':
451 plat.generate_structs(structs)
452 elif cmd == 'platdata':
453 plat.generate_tables()
454 else:
455 raise ValueError("Unknown command '%s': (use: struct, platdata)" %
456 cmd)