9923892dc359bf8cd21426b295b197e2c4812bf6
[platform/kernel/u-boot.git] / 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 }
42
43 STRUCT_PREFIX = 'dtd_'
44 VAL_PREFIX = 'dtv_'
45
46 def conv_name_to_c(name):
47     """Convert a device-tree name to a C identifier
48
49     Args:
50         name:   Name to convert
51     Return:
52         String containing the C version of this name
53     """
54     new = name.replace('@', '_at_')
55     new = new.replace('-', '_')
56     new = new.replace(',', '_')
57     new = new.replace('.', '_')
58     return new
59
60 def tab_to(num_tabs, line):
61     """Append tabs to a line of text to reach a tab stop.
62
63     Args:
64         num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.)
65         line: Line of text to append to
66
67     Returns:
68         line with the correct number of tabs appeneded. If the line already
69         extends past that tab stop then a single space is appended.
70     """
71     if len(line) >= num_tabs * 8:
72         return line + ' '
73     return line + '\t' * (num_tabs - len(line) // 8)
74
75 def get_value(ftype, value):
76     """Get a value as a C expression
77
78     For integers this returns a byte-swapped (little-endian) hex string
79     For bytes this returns a hex string, e.g. 0x12
80     For strings this returns a literal string enclosed in quotes
81     For booleans this return 'true'
82
83     Args:
84         type: Data type (fdt_util)
85         value: Data value, as a string of bytes
86     """
87     if ftype == fdt.TYPE_INT:
88         return '%#x' % fdt_util.fdt32_to_cpu(value)
89     elif ftype == fdt.TYPE_BYTE:
90         return '%#x' % ord(value[0])
91     elif ftype == fdt.TYPE_STRING:
92         return '"%s"' % value
93     elif ftype == fdt.TYPE_BOOL:
94         return 'true'
95
96 def get_compat_name(node):
97     """Get a node's first compatible string as a C identifier
98
99     Args:
100         node: Node object to check
101     Return:
102         Tuple:
103             C identifier for the first compatible string
104             List of C identifiers for all the other compatible strings
105                 (possibly empty)
106     """
107     compat = node.props['compatible'].value
108     aliases = []
109     if isinstance(compat, list):
110         compat, aliases = compat[0], compat[1:]
111     return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases]
112
113 def is_phandle(prop):
114     """Check if a node contains phandles
115
116     We have no reliable way of detecting whether a node uses a phandle
117     or not. As an interim measure, use a list of known property names.
118
119     Args:
120         prop: Prop object to check
121     Return:
122         True if the object value contains phandles, else False
123     """
124     if prop.name in ['clocks']:
125         return True
126     return False
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         _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...)
142         _outfile: The current output file (sys.stdout or a real file)
143         _lines: Stashed list of output lines for outputting in the future
144         _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
145     """
146     def __init__(self, dtb_fname, include_disabled):
147         self._fdt = None
148         self._dtb_fname = dtb_fname
149         self._valid_nodes = None
150         self._include_disabled = include_disabled
151         self._phandle_nodes = {}
152         self._outfile = None
153         self._lines = []
154         self._aliases = {}
155
156     def setup_output(self, fname):
157         """Set up the output destination
158
159         Once this is done, future calls to self.out() will output to this
160         file.
161
162         Args:
163             fname: Filename to send output to, or '-' for stdout
164         """
165         if fname == '-':
166             self._outfile = sys.stdout
167         else:
168             self._outfile = open(fname, 'w')
169
170     def out(self, line):
171         """Output a string to the output file
172
173         Args:
174             line: String to output
175         """
176         self._outfile.write(line)
177
178     def buf(self, line):
179         """Buffer up a string to send later
180
181         Args:
182             line: String to add to our 'buffer' list
183         """
184         self._lines.append(line)
185
186     def get_buf(self):
187         """Get the contents of the output buffer, and clear it
188
189         Returns:
190             The output buffer, which is then cleared for future use
191         """
192         lines = self._lines
193         self._lines = []
194         return lines
195
196     def scan_dtb(self):
197         """Scan the device tree to obtain a tree of notes and properties
198
199         Once this is done, self._fdt.GetRoot() can be called to obtain the
200         device tree root node, and progress from there.
201         """
202         self._fdt = fdt.FdtScan(self._dtb_fname)
203
204     def scan_node(self, root):
205         """Scan a node and subnodes to build a tree of node and phandle info
206
207         This adds each node to self._valid_nodes and each phandle to
208         self._phandle_nodes.
209
210         Args:
211             root: Root node for scan
212         """
213         for node in root.subnodes:
214             if 'compatible' in node.props:
215                 status = node.props.get('status')
216                 if (not self._include_disabled and not status or
217                         status.value != 'disabled'):
218                     self._valid_nodes.append(node)
219                     phandle_prop = node.props.get('phandle')
220                     if phandle_prop:
221                         phandle = phandle_prop.GetPhandle()
222                         self._phandle_nodes[phandle] = node
223
224             # recurse to handle any subnodes
225             self.scan_node(node)
226
227     def scan_tree(self):
228         """Scan the device tree for useful information
229
230         This fills in the following properties:
231             _phandle_nodes: A dict of Nodes indexed by phandle (an integer)
232             _valid_nodes: A list of nodes we wish to consider include in the
233                 platform data
234         """
235         self._phandle_nodes = {}
236         self._valid_nodes = []
237         return self.scan_node(self._fdt.GetRoot())
238
239     def scan_structs(self):
240         """Scan the device tree building up the C structures we will use.
241
242         Build a dict keyed by C struct name containing a dict of Prop
243         object for each struct field (keyed by property name). Where the
244         same struct appears multiple times, try to use the 'widest'
245         property, i.e. the one with a type which can express all others.
246
247         Once the widest property is determined, all other properties are
248         updated to match that width.
249         """
250         structs = {}
251         for node in self._valid_nodes:
252             node_name, _ = get_compat_name(node)
253             fields = {}
254
255             # Get a list of all the valid properties in this node.
256             for name, prop in node.props.items():
257                 if name not in PROP_IGNORE_LIST and name[0] != '#':
258                     fields[name] = copy.deepcopy(prop)
259
260             # If we've seen this node_name before, update the existing struct.
261             if node_name in structs:
262                 struct = structs[node_name]
263                 for name, prop in fields.items():
264                     oldprop = struct.get(name)
265                     if oldprop:
266                         oldprop.Widen(prop)
267                     else:
268                         struct[name] = prop
269
270             # Otherwise store this as a new struct.
271             else:
272                 structs[node_name] = fields
273
274         upto = 0
275         for node in self._valid_nodes:
276             node_name, _ = get_compat_name(node)
277             struct = structs[node_name]
278             for name, prop in node.props.items():
279                 if name not in PROP_IGNORE_LIST and name[0] != '#':
280                     prop.Widen(struct[name])
281             upto += 1
282
283             struct_name, aliases = get_compat_name(node)
284             for alias in aliases:
285                 self._aliases[alias] = struct_name
286
287         return structs
288
289     def scan_phandles(self):
290         """Figure out what phandles each node uses
291
292         We need to be careful when outputing nodes that use phandles since
293         they must come after the declaration of the phandles in the C file.
294         Otherwise we get a compiler error since the phandle struct is not yet
295         declared.
296
297         This function adds to each node a list of phandle nodes that the node
298         depends on. This allows us to output things in the right order.
299         """
300         for node in self._valid_nodes:
301             node.phandles = set()
302             for pname, prop in node.props.items():
303                 if pname in PROP_IGNORE_LIST or pname[0] == '#':
304                     continue
305                 if isinstance(prop.value, list):
306                     if is_phandle(prop):
307                         # Process the list as pairs of (phandle, id)
308                         value_it = iter(prop.value)
309                         for phandle_cell, _ in zip(value_it, value_it):
310                             phandle = fdt_util.fdt32_to_cpu(phandle_cell)
311                             target_node = self._phandle_nodes[phandle]
312                             node.phandles.add(target_node)
313
314
315     def generate_structs(self, structs):
316         """Generate struct defintions for the platform data
317
318         This writes out the body of a header file consisting of structure
319         definitions for node in self._valid_nodes. See the documentation in
320         README.of-plat for more information.
321         """
322         self.out('#include <stdbool.h>\n')
323         self.out('#include <libfdt.h>\n')
324
325         # Output the struct definition
326         for name in sorted(structs):
327             self.out('struct %s%s {\n' % (STRUCT_PREFIX, name))
328             for pname in sorted(structs[name]):
329                 prop = structs[name][pname]
330                 if is_phandle(prop):
331                     # For phandles, include a reference to the target
332                     self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'),
333                                              conv_name_to_c(prop.name),
334                                              len(prop.value) / 2))
335                 else:
336                     ptype = TYPE_NAMES[prop.type]
337                     self.out('\t%s%s' % (tab_to(2, ptype),
338                                          conv_name_to_c(prop.name)))
339                     if isinstance(prop.value, list):
340                         self.out('[%d]' % len(prop.value))
341                 self.out(';\n')
342             self.out('};\n')
343
344         for alias, struct_name in self._aliases.iteritems():
345             self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias,
346                                              STRUCT_PREFIX, struct_name))
347
348     def output_node(self, node):
349         """Output the C code for a node
350
351         Args:
352             node: node to output
353         """
354         struct_name, _ = get_compat_name(node)
355         var_name = conv_name_to_c(node.name)
356         self.buf('static struct %s%s %s%s = {\n' %
357                  (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
358         for pname, prop in node.props.items():
359             if pname in PROP_IGNORE_LIST or pname[0] == '#':
360                 continue
361             member_name = conv_name_to_c(prop.name)
362             self.buf('\t%s= ' % tab_to(3, '.' + member_name))
363
364             # Special handling for lists
365             if isinstance(prop.value, list):
366                 self.buf('{')
367                 vals = []
368                 # For phandles, output a reference to the platform data
369                 # of the target node.
370                 if is_phandle(prop):
371                     # Process the list as pairs of (phandle, id)
372                     value_it = iter(prop.value)
373                     for phandle_cell, id_cell in zip(value_it, value_it):
374                         phandle = fdt_util.fdt32_to_cpu(phandle_cell)
375                         id_num = fdt_util.fdt32_to_cpu(id_cell)
376                         target_node = self._phandle_nodes[phandle]
377                         name = conv_name_to_c(target_node.name)
378                         vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num))
379                 else:
380                     for val in prop.value:
381                         vals.append(get_value(prop.type, val))
382                 self.buf(', '.join(vals))
383                 self.buf('}')
384             else:
385                 self.buf(get_value(prop.type, prop.value))
386             self.buf(',\n')
387         self.buf('};\n')
388
389         # Add a device declaration
390         self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
391         self.buf('\t.name\t\t= "%s",\n' % struct_name)
392         self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
393         self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name))
394         self.buf('};\n')
395         self.buf('\n')
396
397         self.out(''.join(self.get_buf()))
398
399     def generate_tables(self):
400         """Generate device defintions for the platform data
401
402         This writes out C platform data initialisation data and
403         U_BOOT_DEVICE() declarations for each valid node. Where a node has
404         multiple compatible strings, a #define is used to make them equivalent.
405
406         See the documentation in doc/driver-model/of-plat.txt for more
407         information.
408         """
409         self.out('#include <common.h>\n')
410         self.out('#include <dm.h>\n')
411         self.out('#include <dt-structs.h>\n')
412         self.out('\n')
413         nodes_to_output = list(self._valid_nodes)
414
415         # Keep outputing nodes until there is none left
416         while nodes_to_output:
417             node = nodes_to_output[0]
418             # Output all the node's dependencies first
419             for req_node in node.phandles:
420                 if req_node in nodes_to_output:
421                     self.output_node(req_node)
422                     nodes_to_output.remove(req_node)
423             self.output_node(node)
424             nodes_to_output.remove(node)
425
426
427 def run_steps(args, dtb_file, include_disabled, output):
428     """Run all the steps of the dtoc tool
429
430     Args:
431         args: List of non-option arguments provided to the problem
432         dtb_file: Filename of dtb file to process
433         include_disabled: True to include disabled nodes
434         output: Name of output file
435     """
436     if not args:
437         raise ValueError('Please specify a command: struct, platdata')
438
439     plat = DtbPlatdata(dtb_file, include_disabled)
440     plat.scan_dtb()
441     plat.scan_tree()
442     plat.setup_output(output)
443     structs = plat.scan_structs()
444     plat.scan_phandles()
445
446     for cmd in args[0].split(','):
447         if cmd == 'struct':
448             plat.generate_structs(structs)
449         elif cmd == 'platdata':
450             plat.generate_tables()
451         else:
452             raise ValueError("Unknown command '%s': (use: struct, platdata)" %
453                              cmd)