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