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