blob: 10f6a125780418c689212aeeeb6dea38d9c31c1d [file] [log] [blame]
Simon Glass69f2ed72016-07-04 11:58:09 -06001#!/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
9import copy
10from optparse import OptionError, OptionParser
11import os
Simon Glass58593112016-07-25 18:59:01 -060012import struct
Simon Glass69f2ed72016-07-04 11:58:09 -060013import sys
14
Simon Glass69f2ed72016-07-04 11:58:09 -060015# Bring in the patman libraries
16our_path = os.path.dirname(os.path.realpath(__file__))
17sys.path.append(os.path.join(our_path, '../patman'))
18
Simon Glassba482582016-07-25 18:59:02 -060019import fdt_select
20import fdt_util
Simon Glass69f2ed72016-07-04 11:58:09 -060021
Simon Glass69f2ed72016-07-04 11:58:09 -060022# When we see these properties we ignore them - i.e. do not create a structure member
23PROP_IGNORE_LIST = [
24 '#address-cells',
25 '#gpio-cells',
26 '#size-cells',
27 'compatible',
28 'linux,phandle',
29 "status",
30 'phandle',
Simon Glassefefe122016-07-04 11:58:16 -060031 'u-boot,dm-pre-reloc',
Simon Glass69f2ed72016-07-04 11:58:09 -060032]
33
34# C type declarations for the tyues we support
35TYPE_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
42STRUCT_PREFIX = 'dtd_'
43VAL_PREFIX = 'dtv_'
44
45def 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
59def TabTo(num_tabs, str):
60 if len(str) >= num_tabs * 8:
61 return str + ' '
62 return str + '\t' * (num_tabs - len(str) / 8)
63
64class 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 """
Simon Glassba482582016-07-25 18:59:02 -0600169 self.fdt = fdt_select.FdtScan(self._dtb_fname)
Simon Glass69f2ed72016-07-04 11:58:09 -0600170
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))
Simon Glass9fa28192016-07-04 11:58:18 -0600336 self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' %
337 (VAL_PREFIX, var_name))
Simon Glass69f2ed72016-07-04 11:58:09 -0600338 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
354if __name__ != "__main__":
355 pass
356
357parser = OptionParser()
358parser.add_option('-d', '--dtb-file', action='store',
359 help='Specify the .dtb input file')
360parser.add_option('--include-disabled', action='store_true',
361 help='Include disabled nodes')
362parser.add_option('-o', '--output', action='store', default='-',
363 help='Select output filename')
364(options, args) = parser.parse_args()
365
366if not args:
367 raise ValueError('Please specify a command: struct, platdata')
368
369plat = DtbPlatdata(options.dtb_file, options)
370plat.ScanDtb()
371plat.ScanTree()
372plat.SetupOutput(options.output)
373structs = plat.ScanStructs()
374
375for 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)