blob: 1f85343a9fd2de7532b5f032c7939e20ec5f5df2 [file] [log] [blame]
Simon Glass7581c012017-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 Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -060015import copy
Simon Glass2be282c2017-06-18 22:08:59 -060016import sys
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -060041}
Simon Glass7581c012017-06-18 22:08:58 -060042
43STRUCT_PREFIX = 'dtd_'
44VAL_PREFIX = 'dtv_'
45
Simon Glass2be282c2017-06-18 22:08:59 -060046def conv_name_to_c(name):
Simon Glass7581c012017-06-18 22:08:58 -060047 """Convert a device-tree name to a C identifier
48
Simon Glass30107b02017-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 Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -060057 new = name.replace('@', '_at_')
58 new = new.replace('-', '_')
59 new = new.replace(',', '_')
60 new = new.replace('.', '_')
Simon Glass2be282c2017-06-18 22:08:59 -060061 return new
Simon Glass7581c012017-06-18 22:08:58 -060062
Simon Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -060065
Simon Glass2be282c2017-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 Glass56e0bbe2017-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 Glass2be282c2017-06-18 22:08:59 -0600132class DtbPlatdata(object):
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600140 _fdt: Fdt object, referencing the device tree
Simon Glass7581c012017-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 Glasse36024b2017-06-18 22:09:01 -0600143 _include_disabled: true to include nodes marked status = "disabled"
Simon Glass2be282c2017-06-18 22:08:59 -0600144 _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600147 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
Simon Glass7581c012017-06-18 22:08:58 -0600148 """
Simon Glasse36024b2017-06-18 22:09:01 -0600149 def __init__(self, dtb_fname, include_disabled):
Simon Glass2be282c2017-06-18 22:08:59 -0600150 self._fdt = None
Simon Glass7581c012017-06-18 22:08:58 -0600151 self._dtb_fname = dtb_fname
152 self._valid_nodes = None
Simon Glasse36024b2017-06-18 22:09:01 -0600153 self._include_disabled = include_disabled
Simon Glass2be282c2017-06-18 22:08:59 -0600154 self._phandle_nodes = {}
Simon Glass7581c012017-06-18 22:08:58 -0600155 self._outfile = None
156 self._lines = []
157 self._aliases = {}
158
Simon Glass2be282c2017-06-18 22:08:59 -0600159 def setup_output(self, fname):
Simon Glass7581c012017-06-18 22:08:58 -0600160 """Set up the output destination
161
Simon Glass2be282c2017-06-18 22:08:59 -0600162 Once this is done, future calls to self.out() will output to this
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600173 def out(self, line):
Simon Glass7581c012017-06-18 22:08:58 -0600174 """Output a string to the output file
175
176 Args:
Simon Glass2be282c2017-06-18 22:08:59 -0600177 line: String to output
Simon Glass7581c012017-06-18 22:08:58 -0600178 """
Simon Glass2be282c2017-06-18 22:08:59 -0600179 self._outfile.write(line)
Simon Glass7581c012017-06-18 22:08:58 -0600180
Simon Glass2be282c2017-06-18 22:08:59 -0600181 def buf(self, line):
Simon Glass7581c012017-06-18 22:08:58 -0600182 """Buffer up a string to send later
183
184 Args:
Simon Glass2be282c2017-06-18 22:08:59 -0600185 line: String to add to our 'buffer' list
Simon Glass7581c012017-06-18 22:08:58 -0600186 """
Simon Glass2be282c2017-06-18 22:08:59 -0600187 self._lines.append(line)
Simon Glass7581c012017-06-18 22:08:58 -0600188
Simon Glass2be282c2017-06-18 22:08:59 -0600189 def get_buf(self):
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600199 def scan_dtb(self):
Simon Glass7581c012017-06-18 22:08:58 -0600200 """Scan the device tree to obtain a tree of notes and properties
201
Simon Glass2be282c2017-06-18 22:08:59 -0600202 Once this is done, self._fdt.GetRoot() can be called to obtain the
Simon Glass7581c012017-06-18 22:08:58 -0600203 device tree root node, and progress from there.
204 """
Simon Glass2be282c2017-06-18 22:08:59 -0600205 self._fdt = fdt.FdtScan(self._dtb_fname)
Simon Glass7581c012017-06-18 22:08:58 -0600206
Simon Glass2be282c2017-06-18 22:08:59 -0600207 def scan_node(self, root):
208 """Scan a node and subnodes to build a tree of node and phandle info
209
210 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 Glass7581c012017-06-18 22:08:58 -0600216 for node in root.subnodes:
217 if 'compatible' in node.props:
218 status = node.props.get('status')
Simon Glasse36024b2017-06-18 22:09:01 -0600219 if (not self._include_disabled and not status or
Simon Glass2be282c2017-06-18 22:08:59 -0600220 status.value != 'disabled'):
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600225 self._phandle_nodes[phandle] = node
Simon Glass7581c012017-06-18 22:08:58 -0600226
227 # recurse to handle any subnodes
Simon Glass2be282c2017-06-18 22:08:59 -0600228 self.scan_node(node)
Simon Glass7581c012017-06-18 22:08:58 -0600229
Simon Glass2be282c2017-06-18 22:08:59 -0600230 def scan_tree(self):
Simon Glass7581c012017-06-18 22:08:58 -0600231 """Scan the device tree for useful information
232
233 This fills in the following properties:
Simon Glass2be282c2017-06-18 22:08:59 -0600234 _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
Simon Glass7581c012017-06-18 22:08:58 -0600235 _valid_nodes: A list of nodes we wish to consider include in the
236 platform data
237 """
Simon Glass2be282c2017-06-18 22:08:59 -0600238 self._phandle_nodes = {}
Simon Glass7581c012017-06-18 22:08:58 -0600239 self._valid_nodes = []
Simon Glass2be282c2017-06-18 22:08:59 -0600240 return self.scan_node(self._fdt.GetRoot())
Simon Glass7581c012017-06-18 22:08:58 -0600241
Simon Glass2be282c2017-06-18 22:08:59 -0600242 def scan_structs(self):
Simon Glass7581c012017-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 Glass56e0bbe2017-06-18 22:09:02 -0600255 node_name, _ = get_compat_name(node)
Simon Glass7581c012017-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 Glass56e0bbe2017-06-18 22:09:02 -0600279 node_name, _ = get_compat_name(node)
Simon Glass7581c012017-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 Glass56e0bbe2017-06-18 22:09:02 -0600286 struct_name, aliases = get_compat_name(node)
Simon Glass7581c012017-06-18 22:08:58 -0600287 for alias in aliases:
288 self._aliases[alias] = struct_name
289
290 return structs
291
Simon Glass2be282c2017-06-18 22:08:59 -0600292 def scan_phandles(self):
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600308 if isinstance(prop.value, list):
Simon Glass56e0bbe2017-06-18 22:09:02 -0600309 if is_phandle(prop):
Simon Glass7581c012017-06-18 22:08:58 -0600310 # Process the list as pairs of (phandle, id)
Simon Glass2be282c2017-06-18 22:08:59 -0600311 value_it = iter(prop.value)
312 for phandle_cell, _ in zip(value_it, value_it):
Simon Glass7581c012017-06-18 22:08:58 -0600313 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
Simon Glass2be282c2017-06-18 22:08:59 -0600314 target_node = self._phandle_nodes[phandle]
Simon Glass7581c012017-06-18 22:08:58 -0600315 node.phandles.add(target_node)
316
317
Simon Glass2be282c2017-06-18 22:08:59 -0600318 def generate_structs(self, structs):
Simon Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600325 self.out('#include <stdbool.h>\n')
326 self.out('#include <libfdt.h>\n')
Simon Glass7581c012017-06-18 22:08:58 -0600327
328 # Output the struct definition
329 for name in sorted(structs):
Simon Glass2be282c2017-06-18 22:08:59 -0600330 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
Simon Glass7581c012017-06-18 22:08:58 -0600331 for pname in sorted(structs[name]):
332 prop = structs[name][pname]
Simon Glass56e0bbe2017-06-18 22:09:02 -0600333 if is_phandle(prop):
Simon Glass7581c012017-06-18 22:08:58 -0600334 # For phandles, include a reference to the target
Simon Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -0600337 len(prop.value) / 2))
338 else:
339 ptype = TYPE_NAMES[prop.type]
Simon Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -0600346
347 for alias, struct_name in self._aliases.iteritems():
Simon Glass2be282c2017-06-18 22:08:59 -0600348 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
Simon Glass7581c012017-06-18 22:08:58 -0600349 STRUCT_PREFIX, struct_name))
350
Simon Glass2be282c2017-06-18 22:08:59 -0600351 def output_node(self, node):
Simon Glass7581c012017-06-18 22:08:58 -0600352 """Output the C code for a node
353
354 Args:
355 node: node to output
356 """
Simon Glass56e0bbe2017-06-18 22:09:02 -0600357 struct_name, _ = get_compat_name(node)
Simon Glass2be282c2017-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 Glass7581c012017-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 Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -0600366
367 # Special handling for lists
Simon Glass2be282c2017-06-18 22:08:59 -0600368 if isinstance(prop.value, list):
369 self.buf('{')
Simon Glass7581c012017-06-18 22:08:58 -0600370 vals = []
371 # For phandles, output a reference to the platform data
372 # of the target node.
Simon Glass56e0bbe2017-06-18 22:09:02 -0600373 if is_phandle(prop):
Simon Glass7581c012017-06-18 22:08:58 -0600374 # Process the list as pairs of (phandle, id)
Simon Glass2be282c2017-06-18 22:08:59 -0600375 value_it = iter(prop.value)
376 for phandle_cell, id_cell in zip(value_it, value_it):
Simon Glass7581c012017-06-18 22:08:58 -0600377 phandle = fdt_util.fdt32_to_cpu(phandle_cell)
Simon Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -0600382 else:
383 for val in prop.value:
Simon Glass56e0bbe2017-06-18 22:09:02 -0600384 vals.append(get_value(prop.type, val))
Simon Glass2be282c2017-06-18 22:08:59 -0600385 self.buf(', '.join(vals))
386 self.buf('}')
Simon Glass7581c012017-06-18 22:08:58 -0600387 else:
Simon Glass56e0bbe2017-06-18 22:09:02 -0600388 self.buf(get_value(prop.type, prop.value))
Simon Glass2be282c2017-06-18 22:08:59 -0600389 self.buf(',\n')
390 self.buf('};\n')
Simon Glass7581c012017-06-18 22:08:58 -0600391
392 # Add a device declaration
Simon Glass2be282c2017-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 Glass7581c012017-06-18 22:08:58 -0600399
Simon Glass2be282c2017-06-18 22:08:59 -0600400 self.out(''.join(self.get_buf()))
Simon Glass7581c012017-06-18 22:08:58 -0600401
Simon Glass2be282c2017-06-18 22:08:59 -0600402 def generate_tables(self):
Simon Glass7581c012017-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 Glass2be282c2017-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 Glass7581c012017-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 Glass2be282c2017-06-18 22:08:59 -0600424 self.output_node(req_node)
Simon Glass7581c012017-06-18 22:08:58 -0600425 nodes_to_output.remove(req_node)
Simon Glass2be282c2017-06-18 22:08:59 -0600426 self.output_node(node)
Simon Glass7581c012017-06-18 22:08:58 -0600427 nodes_to_output.remove(node)
Simon Glassfa0ea5b2017-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)