]> git.sur5r.net Git - u-boot/blob - tools/dtoc/dtoc.py
dm: allow limiting pre-reloc markings to spl or tpl
[u-boot] / tools / dtoc / dtoc.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2016 Google, Inc
4 # Written by Simon Glass <sjg@chromium.org>
5 #
6 # SPDX-License-Identifier:      GPL-2.0+
7 #
8
9 import copy
10 from optparse import OptionError, OptionParser
11 import os
12 import struct
13 import sys
14
15 # Bring in the patman libraries
16 our_path = os.path.dirname(os.path.realpath(__file__))
17 sys.path.append(os.path.join(our_path, '../patman'))
18
19 import fdt
20 import fdt_select
21 import fdt_util
22
23 # When we see these properties we ignore them - i.e. do not create a structure member
24 PROP_IGNORE_LIST = [
25     '#address-cells',
26     '#gpio-cells',
27     '#size-cells',
28     'compatible',
29     'linux,phandle',
30     "status",
31     'phandle',
32     'u-boot,dm-pre-reloc',
33     'u-boot,dm-tpl',
34     'u-boot,dm-spl',
35 ]
36
37 # C type declarations for the tyues we support
38 TYPE_NAMES = {
39     fdt.TYPE_INT: 'fdt32_t',
40     fdt.TYPE_BYTE: 'unsigned char',
41     fdt.TYPE_STRING: 'const char *',
42     fdt.TYPE_BOOL: 'bool',
43 };
44
45 STRUCT_PREFIX = 'dtd_'
46 VAL_PREFIX = 'dtv_'
47
48 def Conv_name_to_c(name):
49     """Convert a device-tree name to a C identifier
50
51     Args:
52         name:   Name to convert
53     Return:
54         String containing the C version of this name
55     """
56     str = name.replace('@', '_at_')
57     str = str.replace('-', '_')
58     str = str.replace(',', '_')
59     str = str.replace('.', '_')
60     str = str.replace('/', '__')
61     return str
62
63 def TabTo(num_tabs, str):
64     if len(str) >= num_tabs * 8:
65         return str + ' '
66     return str + '\t' * (num_tabs - len(str) // 8)
67
68 class DtbPlatdata:
69     """Provide a means to convert device tree binary data to platform data
70
71     The output of this process is C structures which can be used in space-
72     constrained encvironments where the ~3KB code overhead of device tree
73     code is not affordable.
74
75     Properties:
76         fdt: Fdt object, referencing the device tree
77         _dtb_fname: Filename of the input device tree binary file
78         _valid_nodes: A list of Node object with compatible strings
79         _options: Command-line options
80         _phandle_node: A dict of nodes indexed by phandle number (1, 2...)
81         _outfile: The current output file (sys.stdout or a real file)
82         _lines: Stashed list of output lines for outputting in the future
83         _phandle_node: A dict of Nodes indexed by phandle (an integer)
84     """
85     def __init__(self, dtb_fname, options):
86         self._dtb_fname = dtb_fname
87         self._valid_nodes = None
88         self._options = options
89         self._phandle_node = {}
90         self._outfile = None
91         self._lines = []
92
93     def SetupOutput(self, fname):
94         """Set up the output destination
95
96         Once this is done, future calls to self.Out() will output to this
97         file.
98
99         Args:
100             fname: Filename to send output to, or '-' for stdout
101         """
102         if fname == '-':
103             self._outfile = sys.stdout
104         else:
105             self._outfile = open(fname, 'w')
106
107     def Out(self, str):
108         """Output a string to the output file
109
110         Args:
111             str: String to output
112         """
113         self._outfile.write(str)
114
115     def Buf(self, str):
116         """Buffer up a string to send later
117
118         Args:
119             str: String to add to our 'buffer' list
120         """
121         self._lines.append(str)
122
123     def GetBuf(self):
124         """Get the contents of the output buffer, and clear it
125
126         Returns:
127             The output buffer, which is then cleared for future use
128         """
129         lines = self._lines
130         self._lines = []
131         return lines
132
133     def GetValue(self, type, value):
134         """Get a value as a C expression
135
136         For integers this returns a byte-swapped (little-endian) hex string
137         For bytes this returns a hex string, e.g. 0x12
138         For strings this returns a literal string enclosed in quotes
139         For booleans this return 'true'
140
141         Args:
142             type: Data type (fdt_util)
143             value: Data value, as a string of bytes
144         """
145         if type == fdt.TYPE_INT:
146             return '%#x' % fdt_util.fdt32_to_cpu(value)
147         elif type == fdt.TYPE_BYTE:
148             return '%#x' % ord(value[0])
149         elif type == fdt.TYPE_STRING:
150             return '"%s"' % value
151         elif type == fdt.TYPE_BOOL:
152             return 'true'
153
154     def GetCompatName(self, node):
155         """Get a node's first compatible string as a C identifier
156
157         Args:
158             node: Node object to check
159         Return:
160             C identifier for the first compatible string
161         """
162         compat = node.props['compatible'].value
163         if type(compat) == list:
164             compat = compat[0]
165         return Conv_name_to_c(compat)
166
167     def ScanDtb(self):
168         """Scan the device tree to obtain a tree of notes and properties
169
170         Once this is done, self.fdt.GetRoot() can be called to obtain the
171         device tree root node, and progress from there.
172         """
173         self.fdt = fdt_select.FdtScan(self._dtb_fname)
174
175     def ScanTree(self):
176         """Scan the device tree for useful information
177
178         This fills in the following properties:
179             _phandle_node: A dict of Nodes indexed by phandle (an integer)
180             _valid_nodes: A list of nodes we wish to consider include in the
181                 platform data
182         """
183         node_list = []
184         self._phandle_node = {}
185         for node in self.fdt.GetRoot().subnodes:
186             if 'compatible' in node.props:
187                 status = node.props.get('status')
188                 if (not options.include_disabled and not status or
189                     status.value != 'disabled'):
190                     node_list.append(node)
191                     phandle_prop = node.props.get('phandle')
192                     if phandle_prop:
193                         phandle = phandle_prop.GetPhandle()
194                         self._phandle_node[phandle] = node
195
196         self._valid_nodes = node_list
197
198     def IsPhandle(self, prop):
199         """Check if a node contains phandles
200
201         We have no reliable way of detecting whether a node uses a phandle
202         or not. As an interim measure, use a list of known property names.
203
204         Args:
205             prop: Prop object to check
206         Return:
207             True if the object value contains phandles, else False
208         """
209         if prop.name in ['clocks']:
210             return True
211         return False
212
213     def ScanStructs(self):
214         """Scan the device tree building up the C structures we will use.
215
216         Build a dict keyed by C struct name containing a dict of Prop
217         object for each struct field (keyed by property name). Where the
218         same struct appears multiple times, try to use the 'widest'
219         property, i.e. the one with a type which can express all others.
220
221         Once the widest property is determined, all other properties are
222         updated to match that width.
223         """
224         structs = {}
225         for node in self._valid_nodes:
226             node_name = self.GetCompatName(node)
227             fields = {}
228
229             # Get a list of all the valid properties in this node.
230             for name, prop in node.props.items():
231                 if name not in PROP_IGNORE_LIST and name[0] != '#':
232                     fields[name] = copy.deepcopy(prop)
233
234             # If we've seen this node_name before, update the existing struct.
235             if node_name in structs:
236                 struct = structs[node_name]
237                 for name, prop in fields.items():
238                     oldprop = struct.get(name)
239                     if oldprop:
240                         oldprop.Widen(prop)
241                     else:
242                         struct[name] = prop
243
244             # Otherwise store this as a new struct.
245             else:
246                 structs[node_name] = fields
247
248         upto = 0
249         for node in self._valid_nodes:
250             node_name = self.GetCompatName(node)
251             struct = structs[node_name]
252             for name, prop in node.props.items():
253                 if name not in PROP_IGNORE_LIST and name[0] != '#':
254                     prop.Widen(struct[name])
255             upto += 1
256         return structs
257
258     def GenerateStructs(self, structs):
259         """Generate struct defintions for the platform data
260
261         This writes out the body of a header file consisting of structure
262         definitions for node in self._valid_nodes. See the documentation in
263         README.of-plat for more information.
264         """
265         self.Out('#include <stdbool.h>\n')
266         self.Out('#include <libfdt.h>\n')
267
268         # Output the struct definition
269         for name in sorted(structs):
270             self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name));
271             for pname in sorted(structs[name]):
272                 prop = structs[name][pname]
273                 if self.IsPhandle(prop):
274                     # For phandles, include a reference to the target
275                     self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'),
276                                              Conv_name_to_c(prop.name),
277                                              len(prop.value) / 2))
278                 else:
279                     ptype = TYPE_NAMES[prop.type]
280                     self.Out('\t%s%s' % (TabTo(2, ptype),
281                                          Conv_name_to_c(prop.name)))
282                     if type(prop.value) == list:
283                         self.Out('[%d]' % len(prop.value))
284                 self.Out(';\n')
285             self.Out('};\n')
286
287     def GenerateTables(self):
288         """Generate device defintions for the platform data
289
290         This writes out C platform data initialisation data and
291         U_BOOT_DEVICE() declarations for each valid node. See the
292         documentation in README.of-plat for more information.
293         """
294         self.Out('#include <common.h>\n')
295         self.Out('#include <dm.h>\n')
296         self.Out('#include <dt-structs.h>\n')
297         self.Out('\n')
298         node_txt_list = []
299         for node in self._valid_nodes:
300             struct_name = self.GetCompatName(node)
301             var_name = Conv_name_to_c(node.name)
302             self.Buf('static struct %s%s %s%s = {\n' %
303                 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name))
304             for pname, prop in node.props.items():
305                 if pname in PROP_IGNORE_LIST or pname[0] == '#':
306                     continue
307                 ptype = TYPE_NAMES[prop.type]
308                 member_name = Conv_name_to_c(prop.name)
309                 self.Buf('\t%s= ' % TabTo(3, '.' + member_name))
310
311                 # Special handling for lists
312                 if type(prop.value) == list:
313                     self.Buf('{')
314                     vals = []
315                     # For phandles, output a reference to the platform data
316                     # of the target node.
317                     if self.IsPhandle(prop):
318                         # Process the list as pairs of (phandle, id)
319                         it = iter(prop.value)
320                         for phandle_cell, id_cell in zip(it, it):
321                             phandle = fdt_util.fdt32_to_cpu(phandle_cell)
322                             id = fdt_util.fdt32_to_cpu(id_cell)
323                             target_node = self._phandle_node[phandle]
324                             name = Conv_name_to_c(target_node.name)
325                             vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id))
326                     else:
327                         for val in prop.value:
328                             vals.append(self.GetValue(prop.type, val))
329                     self.Buf(', '.join(vals))
330                     self.Buf('}')
331                 else:
332                     self.Buf(self.GetValue(prop.type, prop.value))
333                 self.Buf(',\n')
334             self.Buf('};\n')
335
336             # Add a device declaration
337             self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name)
338             self.Buf('\t.name\t\t= "%s",\n' % struct_name)
339             self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name))
340             self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' %
341                      (VAL_PREFIX, var_name))
342             self.Buf('};\n')
343             self.Buf('\n')
344
345             # Output phandle target nodes first, since they may be referenced
346             # by others
347             if 'phandle' in node.props:
348                 self.Out(''.join(self.GetBuf()))
349             else:
350                 node_txt_list.append(self.GetBuf())
351
352         # Output all the nodes which are not phandle targets themselves, but
353         # may reference them. This avoids the need for forward declarations.
354         for node_txt in node_txt_list:
355             self.Out(''.join(node_txt))
356
357
358 if __name__ != "__main__":
359     pass
360
361 parser = OptionParser()
362 parser.add_option('-d', '--dtb-file', action='store',
363                   help='Specify the .dtb input file')
364 parser.add_option('--include-disabled', action='store_true',
365                   help='Include disabled nodes')
366 parser.add_option('-o', '--output', action='store', default='-',
367                   help='Select output filename')
368 (options, args) = parser.parse_args()
369
370 if not args:
371     raise ValueError('Please specify a command: struct, platdata')
372
373 plat = DtbPlatdata(options.dtb_file, options)
374 plat.ScanDtb()
375 plat.ScanTree()
376 plat.SetupOutput(options.output)
377 structs = plat.ScanStructs()
378
379 for cmd in args[0].split(','):
380     if cmd == 'struct':
381         plat.GenerateStructs(structs)
382     elif cmd == 'platdata':
383         plat.GenerateTables()
384     else:
385         raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd)