]> git.sur5r.net Git - u-boot/blob - tools/dtoc/dtb_platdata.py
dtoc: Use the Fdt's class's phandle map
[u-boot] / tools / dtoc / dtb_platdata.py
1 #!/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
9 """Device tree to platform data class
10
11 This supports converting device tree data to C structures definitions and
12 static data.
13 """
14
15 import copy
16 import sys
17
18 import fdt
19 import fdt_util
20
21 # When we see these properties we ignore them - i.e. do not create a structure member
22 PROP_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
36 TYPE_NAMES = {
37     fdt.TYPE_INT: 'fdt32_t',
38     fdt.TYPE_BYTE: 'unsigned char',
39     fdt.TYPE_STRING: 'const char *',
40     fdt.TYPE_BOOL: 'bool',
41     fdt.TYPE_INT64: 'fdt64_t',
42 }
43
44 STRUCT_PREFIX = 'dtd_'
45 VAL_PREFIX = 'dtv_'
46
47 def conv_name_to_c(name):
48     """Convert a device-tree name to a C identifier
49
50     This uses multiple replace() calls instead of re.sub() since it is faster
51     (400ms for 1m calls versus 1000ms for the 're' version).
52
53     Args:
54         name:   Name to convert
55     Return:
56         String containing the C version of this name
57     """
58     new = name.replace('@', '_at_')
59     new = new.replace('-', '_')
60     new = new.replace(',', '_')
61     new = new.replace('.', '_')
62     return new
63
64 def tab_to(num_tabs, line):
65     """Append tabs to a line of text to reach a tab stop.
66
67     Args:
68         num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
69         line: Line of text to append to
70
71     Returns:
72         line with the correct number of tabs appeneded. If the line already
73         extends past that tab stop then a single space is appended.
74     """
75     if len(line) >= num_tabs * 8:
76         return line + ' '
77     return line + '\t' * (num_tabs - len(line) // 8)
78
79 def get_value(ftype, value):
80     """Get a value as a C expression
81
82     For integers this returns a byte-swapped (little-endian) hex string
83     For bytes this returns a hex string, e.g. 0x12
84     For strings this returns a literal string enclosed in quotes
85     For booleans this return 'true'
86
87     Args:
88         type: Data type (fdt_util)
89         value: Data value, as a string of bytes
90     """
91     if ftype == fdt.TYPE_INT:
92         return '%#x' % fdt_util.fdt32_to_cpu(value)
93     elif ftype == fdt.TYPE_BYTE:
94         return '%#x' % ord(value[0])
95     elif ftype == fdt.TYPE_STRING:
96         return '"%s"' % value
97     elif ftype == fdt.TYPE_BOOL:
98         return 'true'
99     elif ftype == fdt.TYPE_INT64:
100         return '%#x' % value
101
102 def get_compat_name(node):
103     """Get a node's first compatible string as a C identifier
104
105     Args:
106         node: Node object to check
107     Return:
108         Tuple:
109             C identifier for the first compatible string
110             List of C identifiers for all the other compatible strings
111                 (possibly empty)
112     """
113     compat = node.props['compatible'].value
114     aliases = []
115     if isinstance(compat, list):
116         compat, aliases = compat[0], compat[1:]
117     return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
118
119 def is_phandle(prop):
120     """Check if a node contains phandles
121
122     We have no reliable way of detecting whether a node uses a phandle
123     or not. As an interim measure, use a list of known property names.
124
125     Args:
126         prop: Prop object to check
127     Return:
128         True if the object value contains phandles, else False
129     """
130     if prop.name in ['clocks']:
131         return True
132     return False
133
134
135 class DtbPlatdata(object):
136     """Provide a means to convert device tree binary data to platform data
137
138     The output of this process is C structures which can be used in space-
139     constrained encvironments where the ~3KB code overhead of device tree
140     code is not affordable.
141
142     Properties:
143         _fdt: Fdt object, referencing the device tree
144         _dtb_fname: Filename of the input device tree binary file
145         _valid_nodes: A list of Node object with compatible strings
146         _include_disabled: true to include nodes marked status = "disabled"
147         _outfile: The current output file (sys.stdout or a real file)
148         _lines: Stashed list of output lines for outputting in the future
149     """
150     def __init__(self, dtb_fname, include_disabled):
151         self._fdt = None
152         self._dtb_fname = dtb_fname
153         self._valid_nodes = None
154         self._include_disabled = include_disabled
155         self._outfile = None
156         self._lines = []
157         self._aliases = {}
158
159     def setup_output(self, fname):
160         """Set up the output destination
161
162         Once this is done, future calls to self.out() will output to this
163         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
173     def out(self, line):
174         """Output a string to the output file
175
176         Args:
177             line: String to output
178         """
179         self._outfile.write(line)
180
181     def buf(self, line):
182         """Buffer up a string to send later
183
184         Args:
185             line: String to add to our 'buffer' list
186         """
187         self._lines.append(line)
188
189     def get_buf(self):
190         """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
199     def scan_dtb(self):
200         """Scan the device tree to obtain a tree of nodes and properties
201
202         Once this is done, self._fdt.GetRoot() can be called to obtain the
203         device tree root node, and progress from there.
204         """
205         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
209
210         This adds each node to self._valid_nodes.
211
212         Args:
213             root: Root node for scan
214         """
215         for node in root.subnodes:
216             if 'compatible' in node.props:
217                 status = node.props.get('status')
218                 if (not self._include_disabled and not status or
219                         status.value != 'disabled'):
220                     self._valid_nodes.append(node)
221
222             # recurse to handle any subnodes
223             self.scan_node(node)
224
225     def scan_tree(self):
226         """Scan the device tree for useful information
227
228         This fills in the following properties:
229             _valid_nodes: A list of nodes we wish to consider include in the
230                 platform data
231         """
232         self._valid_nodes = []
233         return self.scan_node(self._fdt.GetRoot())
234
235     @staticmethod
236     def get_num_cells(node):
237         """Get the number of cells in addresses and sizes for this node
238
239         Args:
240             node: Node to check
241
242         Returns:
243             Tuple:
244                 Number of address cells for this node
245                 Number of size cells for this node
246         """
247         parent = node.parent
248         na, ns = 2, 2
249         if parent:
250             na_prop = parent.props.get('#address-cells')
251             ns_prop = parent.props.get('#size-cells')
252             if na_prop:
253                 na = fdt_util.fdt32_to_cpu(na_prop.value)
254             if ns_prop:
255                 ns = fdt_util.fdt32_to_cpu(ns_prop.value)
256         return na, ns
257
258     def scan_reg_sizes(self):
259         """Scan for 64-bit 'reg' properties and update the values
260
261         This finds 'reg' properties with 64-bit data and converts the value to
262         an array of 64-values. This allows it to be output in a way that the
263         C code can read.
264         """
265         for node in self._valid_nodes:
266             reg = node.props.get('reg')
267             if not reg:
268                 continue
269             na, ns = self.get_num_cells(node)
270             total = na + ns
271
272             if reg.type != fdt.TYPE_INT:
273                 raise ValueError("Node '%s' reg property is not an int")
274             if len(reg.value) % total:
275                 raise ValueError("Node '%s' reg property has %d cells "
276                         'which is not a multiple of na + ns = %d + %d)' %
277                         (node.name, len(reg.value), na, ns))
278             reg.na = na
279             reg.ns = ns
280             if na != 1 or ns != 1:
281                 reg.type = fdt.TYPE_INT64
282                 i = 0
283                 new_value = []
284                 val = reg.value
285                 if not isinstance(val, list):
286                     val = [val]
287                 while i < len(val):
288                     addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
289                     i += na
290                     size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
291                     i += ns
292                     new_value += [addr, size]
293                 reg.value = new_value
294
295     def scan_structs(self):
296         """Scan the device tree building up the C structures we will use.
297
298         Build a dict keyed by C struct name containing a dict of Prop
299         object for each struct field (keyed by property name). Where the
300         same struct appears multiple times, try to use the 'widest'
301         property, i.e. the one with a type which can express all others.
302
303         Once the widest property is determined, all other properties are
304         updated to match that width.
305         """
306         structs = {}
307         for node in self._valid_nodes:
308             node_name, _ = get_compat_name(node)
309             fields = {}
310
311             # Get a list of all the valid properties in this node.
312             for name, prop in node.props.items():
313                 if name not in PROP_IGNORE_LIST and name[0] != '#':
314                     fields[name] = copy.deepcopy(prop)
315
316             # If we've seen this node_name before, update the existing struct.
317             if node_name in structs:
318                 struct = structs[node_name]
319                 for name, prop in fields.items():
320                     oldprop = struct.get(name)
321                     if oldprop:
322                         oldprop.Widen(prop)
323                     else:
324                         struct[name] = prop
325
326             # Otherwise store this as a new struct.
327             else:
328                 structs[node_name] = fields
329
330         upto = 0
331         for node in self._valid_nodes:
332             node_name, _ = get_compat_name(node)
333             struct = structs[node_name]
334             for name, prop in node.props.items():
335                 if name not in PROP_IGNORE_LIST and name[0] != '#':
336                     prop.Widen(struct[name])
337             upto += 1
338
339             struct_name, aliases = get_compat_name(node)
340             for alias in aliases:
341                 self._aliases[alias] = struct_name
342
343         return structs
344
345     def scan_phandles(self):
346         """Figure out what phandles each node uses
347
348         We need to be careful when outputing nodes that use phandles since
349         they must come after the declaration of the phandles in the C file.
350         Otherwise we get a compiler error since the phandle struct is not yet
351         declared.
352
353         This function adds to each node a list of phandle nodes that the node
354         depends on. This allows us to output things in the right order.
355         """
356         for node in self._valid_nodes:
357             node.phandles = set()
358             for pname, prop in node.props.items():
359                 if pname in PROP_IGNORE_LIST or pname[0] == '#':
360                     continue
361                 if isinstance(prop.value, list):
362                     if is_phandle(prop):
363                         # Process the list as pairs of (phandle, id)
364                         value_it = iter(prop.value)
365                         for phandle_cell, _ in zip(value_it, value_it):
366                             phandle = fdt_util.fdt32_to_cpu(phandle_cell)
367                             target_node = self._fdt.phandle_to_node[phandle]
368                             node.phandles.add(target_node)
369
370
371     def generate_structs(self, structs):
372         """Generate struct defintions for the platform data
373
374         This writes out the body of a header file consisting of structure
375         definitions for node in self._valid_nodes. See the documentation in
376         README.of-plat for more information.
377         """
378         self.out('#include <stdbool.h>\n')
379         self.out('#include <libfdt.h>\n')
380
381         # Output the struct definition
382         for name in sorted(structs):
383             self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
384             for pname in sorted(structs[name]):
385                 prop = structs[name][pname]
386                 if is_phandle(prop):
387                     # For phandles, include a reference to the target
388                     self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
389                                              conv_name_to_c(prop.name),
390                                              len(prop.value) / 2))
391                 else:
392                     ptype = TYPE_NAMES[prop.type]
393                     self.out('\t%s%s' % (tab_to(2, ptype),
394                                          conv_name_to_c(prop.name)))
395                     if isinstance(prop.value, list):
396                         self.out('[%d]' % len(prop.value))
397                 self.out(';\n')
398             self.out('};\n')
399
400         for alias, struct_name in self._aliases.iteritems():
401             self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
402                                              STRUCT_PREFIX, struct_name))
403
404     def output_node(self, node):
405         """Output the C code for a node
406
407         Args:
408             node: node to output
409         """
410         struct_name, _ = get_compat_name(node)
411         var_name = conv_name_to_c(node.name)
412         self.buf('static struct %s%s %s%s = {\n' %
413                  (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
414         for pname, prop in node.props.items():
415             if pname in PROP_IGNORE_LIST or pname[0] == '#':
416                 continue
417             member_name = conv_name_to_c(prop.name)
418             self.buf('\t%s= ' % tab_to(3, '.' + member_name))
419
420             # Special handling for lists
421             if isinstance(prop.value, list):
422                 self.buf('{')
423                 vals = []
424                 # For phandles, output a reference to the platform data
425                 # of the target node.
426                 if is_phandle(prop):
427                     # Process the list as pairs of (phandle, id)
428                     value_it = iter(prop.value)
429                     for phandle_cell, id_cell in zip(value_it, value_it):
430                         phandle = fdt_util.fdt32_to_cpu(phandle_cell)
431                         id_num = fdt_util.fdt32_to_cpu(id_cell)
432                         target_node = self._fdt.phandle_to_node[phandle]
433                         name = conv_name_to_c(target_node.name)
434                         vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
435                 else:
436                     for val in prop.value:
437                         vals.append(get_value(prop.type, val))
438
439                 # Put 8 values per line to avoid very long lines.
440                 for i in xrange(0, len(vals), 8):
441                     if i:
442                         self.buf(',\n\t\t')
443                     self.buf(', '.join(vals[i:i + 8]))
444                 self.buf('}')
445             else:
446                 self.buf(get_value(prop.type, prop.value))
447             self.buf(',\n')
448         self.buf('};\n')
449
450         # Add a device declaration
451         self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
452         self.buf('\t.name\t\t= "%s",\n' % struct_name)
453         self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
454         self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
455         self.buf('};\n')
456         self.buf('\n')
457
458         self.out(''.join(self.get_buf()))
459
460     def generate_tables(self):
461         """Generate device defintions for the platform data
462
463         This writes out C platform data initialisation data and
464         U_BOOT_DEVICE() declarations for each valid node. Where a node has
465         multiple compatible strings, a #define is used to make them equivalent.
466
467         See the documentation in doc/driver-model/of-plat.txt for more
468         information.
469         """
470         self.out('#include <common.h>\n')
471         self.out('#include <dm.h>\n')
472         self.out('#include <dt-structs.h>\n')
473         self.out('\n')
474         nodes_to_output = list(self._valid_nodes)
475
476         # Keep outputing nodes until there is none left
477         while nodes_to_output:
478             node = nodes_to_output[0]
479             # Output all the node's dependencies first
480             for req_node in node.phandles:
481                 if req_node in nodes_to_output:
482                     self.output_node(req_node)
483                     nodes_to_output.remove(req_node)
484             self.output_node(node)
485             nodes_to_output.remove(node)
486
487
488 def run_steps(args, dtb_file, include_disabled, output):
489     """Run all the steps of the dtoc tool
490
491     Args:
492         args: List of non-option arguments provided to the problem
493         dtb_file: Filename of dtb file to process
494         include_disabled: True to include disabled nodes
495         output: Name of output file
496     """
497     if not args:
498         raise ValueError('Please specify a command: struct, platdata')
499
500     plat = DtbPlatdata(dtb_file, include_disabled)
501     plat.scan_dtb()
502     plat.scan_tree()
503     plat.scan_reg_sizes()
504     plat.setup_output(output)
505     structs = plat.scan_structs()
506     plat.scan_phandles()
507
508     for cmd in args[0].split(','):
509         if cmd == 'struct':
510             plat.generate_structs(structs)
511         elif cmd == 'platdata':
512             plat.generate_tables()
513         else:
514             raise ValueError("Unknown command '%s': (use: struct, platdata)" %
515                              cmd)