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