]> git.sur5r.net Git - u-boot/blob - tools/dtoc/dtb_platdata.py
0c719310b102ee93428c2fcbbe76d8c1ce73ce03
[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         _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
148         _outfile: The current output file (sys.stdout or a real file)
149         _lines: Stashed list of output lines for outputting in the future
150         _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
151     """
152     def __init__(self, dtb_fname, include_disabled):
153         self._fdt = None
154         self._dtb_fname = dtb_fname
155         self._valid_nodes = None
156         self._include_disabled = include_disabled
157         self._phandle_nodes = {}
158         self._outfile = None
159         self._lines = []
160         self._aliases = {}
161
162     def setup_output(self, fname):
163         """Set up the output destination
164
165         Once this is done, future calls to self.out() will output to this
166         file.
167
168         Args:
169             fname: Filename to send output to, or '-' for stdout
170         """
171         if fname == '-':
172             self._outfile = sys.stdout
173         else:
174             self._outfile = open(fname, 'w')
175
176     def out(self, line):
177         """Output a string to the output file
178
179         Args:
180             line: String to output
181         """
182         self._outfile.write(line)
183
184     def buf(self, line):
185         """Buffer up a string to send later
186
187         Args:
188             line: String to add to our 'buffer' list
189         """
190         self._lines.append(line)
191
192     def get_buf(self):
193         """Get the contents of the output buffer, and clear it
194
195         Returns:
196             The output buffer, which is then cleared for future use
197         """
198         lines = self._lines
199         self._lines = []
200         return lines
201
202     def scan_dtb(self):
203         """Scan the device tree to obtain a tree of nodes and properties
204
205         Once this is done, self._fdt.GetRoot() can be called to obtain the
206         device tree root node, and progress from there.
207         """
208         self._fdt = fdt.FdtScan(self._dtb_fname)
209
210     def scan_node(self, root):
211         """Scan a node and subnodes to build a tree of node and phandle info
212
213         This adds each node to self._valid_nodes and each phandle to
214         self._phandle_nodes.
215
216         Args:
217             root: Root node for scan
218         """
219         for node in root.subnodes:
220             if 'compatible' in node.props:
221                 status = node.props.get('status')
222                 if (not self._include_disabled and not status or
223                         status.value != 'disabled'):
224                     self._valid_nodes.append(node)
225                     phandle_prop = node.props.get('phandle')
226                     if phandle_prop:
227                         phandle = phandle_prop.GetPhandle()
228                         self._phandle_nodes[phandle] = node
229
230             # recurse to handle any subnodes
231             self.scan_node(node)
232
233     def scan_tree(self):
234         """Scan the device tree for useful information
235
236         This fills in the following properties:
237             _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
238             _valid_nodes: A list of nodes we wish to consider include in the
239                 platform data
240         """
241         self._phandle_nodes = {}
242         self._valid_nodes = []
243         return self.scan_node(self._fdt.GetRoot())
244
245     @staticmethod
246     def get_num_cells(node):
247         """Get the number of cells in addresses and sizes for this node
248
249         Args:
250             node: Node to check
251
252         Returns:
253             Tuple:
254                 Number of address cells for this node
255                 Number of size cells for this node
256         """
257         parent = node.parent
258         na, ns = 2, 2
259         if parent:
260             na_prop = parent.props.get('#address-cells')
261             ns_prop = parent.props.get('#size-cells')
262             if na_prop:
263                 na = fdt_util.fdt32_to_cpu(na_prop.value)
264             if ns_prop:
265                 ns = fdt_util.fdt32_to_cpu(ns_prop.value)
266         return na, ns
267
268     def scan_reg_sizes(self):
269         """Scan for 64-bit 'reg' properties and update the values
270
271         This finds 'reg' properties with 64-bit data and converts the value to
272         an array of 64-values. This allows it to be output in a way that the
273         C code can read.
274         """
275         for node in self._valid_nodes:
276             reg = node.props.get('reg')
277             if not reg:
278                 continue
279             na, ns = self.get_num_cells(node)
280             total = na + ns
281
282             if reg.type != fdt.TYPE_INT:
283                 raise ValueError("Node '%s' reg property is not an int")
284             if len(reg.value) % total:
285                 raise ValueError("Node '%s' reg property has %d cells "
286                         'which is not a multiple of na + ns = %d + %d)' %
287                         (node.name, len(reg.value), na, ns))
288             reg.na = na
289             reg.ns = ns
290             if na != 1 or ns != 1:
291                 reg.type = fdt.TYPE_INT64
292                 i = 0
293                 new_value = []
294                 val = reg.value
295                 if not isinstance(val, list):
296                     val = [val]
297                 while i < len(val):
298                     addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na)
299                     i += na
300                     size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns)
301                     i += ns
302                     new_value += [addr, size]
303                 reg.value = new_value
304
305     def scan_structs(self):
306         """Scan the device tree building up the C structures we will use.
307
308         Build a dict keyed by C struct name containing a dict of Prop
309         object for each struct field (keyed by property name). Where the
310         same struct appears multiple times, try to use the 'widest'
311         property, i.e. the one with a type which can express all others.
312
313         Once the widest property is determined, all other properties are
314         updated to match that width.
315         """
316         structs = {}
317         for node in self._valid_nodes:
318             node_name, _ = get_compat_name(node)
319             fields = {}
320
321             # Get a list of all the valid properties in this node.
322             for name, prop in node.props.items():
323                 if name not in PROP_IGNORE_LIST and name[0] != '#':
324                     fields[name] = copy.deepcopy(prop)
325
326             # If we've seen this node_name before, update the existing struct.
327             if node_name in structs:
328                 struct = structs[node_name]
329                 for name, prop in fields.items():
330                     oldprop = struct.get(name)
331                     if oldprop:
332                         oldprop.Widen(prop)
333                     else:
334                         struct[name] = prop
335
336             # Otherwise store this as a new struct.
337             else:
338                 structs[node_name] = fields
339
340         upto = 0
341         for node in self._valid_nodes:
342             node_name, _ = get_compat_name(node)
343             struct = structs[node_name]
344             for name, prop in node.props.items():
345                 if name not in PROP_IGNORE_LIST and name[0] != '#':
346                     prop.Widen(struct[name])
347             upto += 1
348
349             struct_name, aliases = get_compat_name(node)
350             for alias in aliases:
351                 self._aliases[alias] = struct_name
352
353         return structs
354
355     def scan_phandles(self):
356         """Figure out what phandles each node uses
357
358         We need to be careful when outputing nodes that use phandles since
359         they must come after the declaration of the phandles in the C file.
360         Otherwise we get a compiler error since the phandle struct is not yet
361         declared.
362
363         This function adds to each node a list of phandle nodes that the node
364         depends on. This allows us to output things in the right order.
365         """
366         for node in self._valid_nodes:
367             node.phandles = set()
368             for pname, prop in node.props.items():
369                 if pname in PROP_IGNORE_LIST or pname[0] == '#':
370                     continue
371                 if isinstance(prop.value, list):
372                     if is_phandle(prop):
373                         # Process the list as pairs of (phandle, id)
374                         value_it = iter(prop.value)
375                         for phandle_cell, _ in zip(value_it, value_it):
376                             phandle = fdt_util.fdt32_to_cpu(phandle_cell)
377                             target_node = self._phandle_nodes[phandle]
378                             node.phandles.add(target_node)
379
380
381     def generate_structs(self, structs):
382         """Generate struct defintions for the platform data
383
384         This writes out the body of a header file consisting of structure
385         definitions for node in self._valid_nodes. See the documentation in
386         README.of-plat for more information.
387         """
388         self.out('#include <stdbool.h>\n')
389         self.out('#include <libfdt.h>\n')
390
391         # Output the struct definition
392         for name in sorted(structs):
393             self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
394             for pname in sorted(structs[name]):
395                 prop = structs[name][pname]
396                 if is_phandle(prop):
397                     # For phandles, include a reference to the target
398                     self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
399                                              conv_name_to_c(prop.name),
400                                              len(prop.value) / 2))
401                 else:
402                     ptype = TYPE_NAMES[prop.type]
403                     self.out('\t%s%s' % (tab_to(2, ptype),
404                                          conv_name_to_c(prop.name)))
405                     if isinstance(prop.value, list):
406                         self.out('[%d]' % len(prop.value))
407                 self.out(';\n')
408             self.out('};\n')
409
410         for alias, struct_name in self._aliases.iteritems():
411             self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
412                                              STRUCT_PREFIX, struct_name))
413
414     def output_node(self, node):
415         """Output the C code for a node
416
417         Args:
418             node: node to output
419         """
420         struct_name, _ = get_compat_name(node)
421         var_name = conv_name_to_c(node.name)
422         self.buf('static struct %s%s %s%s = {\n' %
423                  (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
424         for pname, prop in node.props.items():
425             if pname in PROP_IGNORE_LIST or pname[0] == '#':
426                 continue
427             member_name = conv_name_to_c(prop.name)
428             self.buf('\t%s= ' % tab_to(3, '.' + member_name))
429
430             # Special handling for lists
431             if isinstance(prop.value, list):
432                 self.buf('{')
433                 vals = []
434                 # For phandles, output a reference to the platform data
435                 # of the target node.
436                 if is_phandle(prop):
437                     # Process the list as pairs of (phandle, id)
438                     value_it = iter(prop.value)
439                     for phandle_cell, id_cell in zip(value_it, value_it):
440                         phandle = fdt_util.fdt32_to_cpu(phandle_cell)
441                         id_num = fdt_util.fdt32_to_cpu(id_cell)
442                         target_node = self._phandle_nodes[phandle]
443                         name = conv_name_to_c(target_node.name)
444                         vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
445                 else:
446                     for val in prop.value:
447                         vals.append(get_value(prop.type, val))
448
449                 # Put 8 values per line to avoid very long lines.
450                 for i in xrange(0, len(vals), 8):
451                     if i:
452                         self.buf(',\n\t\t')
453                     self.buf(', '.join(vals[i:i + 8]))
454                 self.buf('}')
455             else:
456                 self.buf(get_value(prop.type, prop.value))
457             self.buf(',\n')
458         self.buf('};\n')
459
460         # Add a device declaration
461         self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
462         self.buf('\t.name\t\t= "%s",\n' % struct_name)
463         self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
464         self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
465         self.buf('};\n')
466         self.buf('\n')
467
468         self.out(''.join(self.get_buf()))
469
470     def generate_tables(self):
471         """Generate device defintions for the platform data
472
473         This writes out C platform data initialisation data and
474         U_BOOT_DEVICE() declarations for each valid node. Where a node has
475         multiple compatible strings, a #define is used to make them equivalent.
476
477         See the documentation in doc/driver-model/of-plat.txt for more
478         information.
479         """
480         self.out('#include <common.h>\n')
481         self.out('#include <dm.h>\n')
482         self.out('#include <dt-structs.h>\n')
483         self.out('\n')
484         nodes_to_output = list(self._valid_nodes)
485
486         # Keep outputing nodes until there is none left
487         while nodes_to_output:
488             node = nodes_to_output[0]
489             # Output all the node's dependencies first
490             for req_node in node.phandles:
491                 if req_node in nodes_to_output:
492                     self.output_node(req_node)
493                     nodes_to_output.remove(req_node)
494             self.output_node(node)
495             nodes_to_output.remove(node)
496
497
498 def run_steps(args, dtb_file, include_disabled, output):
499     """Run all the steps of the dtoc tool
500
501     Args:
502         args: List of non-option arguments provided to the problem
503         dtb_file: Filename of dtb file to process
504         include_disabled: True to include disabled nodes
505         output: Name of output file
506     """
507     if not args:
508         raise ValueError('Please specify a command: struct, platdata')
509
510     plat = DtbPlatdata(dtb_file, include_disabled)
511     plat.scan_dtb()
512     plat.scan_tree()
513     plat.scan_reg_sizes()
514     plat.setup_output(output)
515     structs = plat.scan_structs()
516     plat.scan_phandles()
517
518     for cmd in args[0].split(','):
519         if cmd == 'struct':
520             plat.generate_structs(structs)
521         elif cmd == 'platdata':
522             plat.generate_tables()
523         else:
524             raise ValueError("Unknown command '%s': (use: struct, platdata)" %
525                              cmd)