]>
Commit | Line | Data |
---|---|---|
7581c01a | 1 | #!/usr/bin/python |
83d290c5 | 2 | # SPDX-License-Identifier: GPL-2.0+ |
7581c01a SG |
3 | # |
4 | # Copyright (C) 2017 Google, Inc | |
5 | # Written by Simon Glass <[email protected]> | |
6 | # | |
7581c01a | 7 | |
2be282ca SG |
8 | """Device tree to platform data class |
9 | ||
10 | This supports converting device tree data to C structures definitions and | |
11 | static data. | |
9b330382 SG |
12 | |
13 | See doc/driver-model/of-plat.rst for more informaiton | |
2be282ca SG |
14 | """ |
15 | ||
8fed2eb2 | 16 | import collections |
7581c01a | 17 | import copy |
be44f271 | 18 | from enum import IntEnum |
dac8228d WL |
19 | import os |
20 | import re | |
2be282ca | 21 | import sys |
7581c01a | 22 | |
bf776679 SG |
23 | from dtoc import fdt |
24 | from dtoc import fdt_util | |
7581c01a | 25 | |
9b330382 SG |
26 | # When we see these properties we ignore them - i.e. do not create a structure |
27 | # member | |
7581c01a SG |
28 | PROP_IGNORE_LIST = [ |
29 | '#address-cells', | |
30 | '#gpio-cells', | |
31 | '#size-cells', | |
32 | 'compatible', | |
33 | 'linux,phandle', | |
34 | "status", | |
35 | 'phandle', | |
36 | 'u-boot,dm-pre-reloc', | |
37 | 'u-boot,dm-tpl', | |
38 | 'u-boot,dm-spl', | |
39 | ] | |
40 | ||
5ea9dccf | 41 | # C type declarations for the types we support |
7581c01a | 42 | TYPE_NAMES = { |
5ea9dccf SG |
43 | fdt.Type.INT: 'fdt32_t', |
44 | fdt.Type.BYTE: 'unsigned char', | |
45 | fdt.Type.STRING: 'const char *', | |
46 | fdt.Type.BOOL: 'bool', | |
47 | fdt.Type.INT64: 'fdt64_t', | |
2be282ca | 48 | } |
7581c01a SG |
49 | |
50 | STRUCT_PREFIX = 'dtd_' | |
51 | VAL_PREFIX = 'dtv_' | |
52 | ||
be44f271 SG |
53 | class Ftype(IntEnum): |
54 | SOURCE, HEADER = range(2) | |
55 | ||
56 | ||
57 | # This holds information about each type of output file dtoc can create | |
58 | # type: Type of file (Ftype) | |
d1055d68 SG |
59 | # fname: Filename excluding directory, e.g. 'dt-plat.c' |
60 | # hdr_comment: Comment explaining the purpose of the file | |
61 | OutputFile = collections.namedtuple('OutputFile', | |
a7d5f96e | 62 | ['ftype', 'fname', 'method', 'hdr_comment']) |
be44f271 | 63 | |
8fed2eb2 SG |
64 | # This holds information about a property which includes phandles. |
65 | # | |
66 | # max_args: integer: Maximum number or arguments that any phandle uses (int). | |
67 | # args: Number of args for each phandle in the property. The total number of | |
68 | # phandles is len(args). This is a list of integers. | |
69 | PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args']) | |
70 | ||
97136eb5 SG |
71 | # Holds a single phandle link, allowing a C struct value to be assigned to point |
72 | # to a device | |
73 | # | |
74 | # var_node: C variable to assign (e.g. 'dtv_mmc.clocks[0].node') | |
75 | # dev_name: Name of device to assign to (e.g. 'clock') | |
76 | PhandleLink = collections.namedtuple('PhandleLink', ['var_node', 'dev_name']) | |
77 | ||
8fed2eb2 | 78 | |
7d637c12 SG |
79 | class Driver: |
80 | """Information about a driver in U-Boot | |
81 | ||
82 | Attributes: | |
83 | name: Name of driver. For U_BOOT_DRIVER(x) this is 'x' | |
84 | """ | |
85 | def __init__(self, name): | |
86 | self.name = name | |
87 | ||
88 | def __eq__(self, other): | |
89 | return self.name == other.name | |
90 | ||
91 | def __repr__(self): | |
92 | return "Driver(name='%s')" % self.name | |
93 | ||
94 | ||
2be282ca | 95 | def conv_name_to_c(name): |
7581c01a SG |
96 | """Convert a device-tree name to a C identifier |
97 | ||
30107b08 SG |
98 | This uses multiple replace() calls instead of re.sub() since it is faster |
99 | (400ms for 1m calls versus 1000ms for the 're' version). | |
100 | ||
7581c01a | 101 | Args: |
9b330382 | 102 | name (str): Name to convert |
7581c01a | 103 | Return: |
9b330382 | 104 | str: String containing the C version of this name |
7581c01a | 105 | """ |
2be282ca SG |
106 | new = name.replace('@', '_at_') |
107 | new = new.replace('-', '_') | |
108 | new = new.replace(',', '_') | |
109 | new = new.replace('.', '_') | |
2be282ca SG |
110 | return new |
111 | ||
112 | def tab_to(num_tabs, line): | |
113 | """Append tabs to a line of text to reach a tab stop. | |
114 | ||
115 | Args: | |
9b330382 SG |
116 | num_tabs (int): Tab stop to obtain (0 = column 0, 1 = column 8, etc.) |
117 | line (str): Line of text to append to | |
2be282ca SG |
118 | |
119 | Returns: | |
9b330382 | 120 | str: line with the correct number of tabs appeneded. If the line already |
2be282ca SG |
121 | extends past that tab stop then a single space is appended. |
122 | """ | |
123 | if len(line) >= num_tabs * 8: | |
124 | return line + ' ' | |
125 | return line + '\t' * (num_tabs - len(line) // 8) | |
126 | ||
56e0bbe0 SG |
127 | def get_value(ftype, value): |
128 | """Get a value as a C expression | |
129 | ||
130 | For integers this returns a byte-swapped (little-endian) hex string | |
131 | For bytes this returns a hex string, e.g. 0x12 | |
132 | For strings this returns a literal string enclosed in quotes | |
133 | For booleans this return 'true' | |
134 | ||
135 | Args: | |
9b330382 SG |
136 | ftype (fdt.Type): Data type (fdt_util) |
137 | value (bytes): Data value, as a string of bytes | |
138 | ||
139 | Returns: | |
140 | str: String representation of the value | |
56e0bbe0 | 141 | """ |
5ea9dccf | 142 | if ftype == fdt.Type.INT: |
ccc3da77 | 143 | val = '%#x' % fdt_util.fdt32_to_cpu(value) |
5ea9dccf | 144 | elif ftype == fdt.Type.BYTE: |
78128d52 | 145 | char = value[0] |
ccc3da77 | 146 | val = '%#x' % (ord(char) if isinstance(char, str) else char) |
5ea9dccf | 147 | elif ftype == fdt.Type.STRING: |
f02d0eb3 SG |
148 | # Handle evil ACPI backslashes by adding another backslash before them. |
149 | # So "\\_SB.GPO0" in the device tree effectively stays like that in C | |
ccc3da77 | 150 | val = '"%s"' % value.replace('\\', '\\\\') |
5ea9dccf | 151 | elif ftype == fdt.Type.BOOL: |
ccc3da77 | 152 | val = 'true' |
9b330382 | 153 | else: # ftype == fdt.Type.INT64: |
ccc3da77 SG |
154 | val = '%#x' % value |
155 | return val | |
56e0bbe0 SG |
156 | |
157 | def get_compat_name(node): | |
dcb3ed64 | 158 | """Get the node's list of compatible string as a C identifiers |
56e0bbe0 SG |
159 | |
160 | Args: | |
9b330382 | 161 | node (fdt.Node): Node object to check |
56e0bbe0 | 162 | Return: |
ccc3da77 | 163 | list of str: List of C identifiers for all the compatible strings |
56e0bbe0 SG |
164 | """ |
165 | compat = node.props['compatible'].value | |
dcb3ed64 WL |
166 | if not isinstance(compat, list): |
167 | compat = [compat] | |
168 | return [conv_name_to_c(c) for c in compat] | |
56e0bbe0 | 169 | |
56e0bbe0 | 170 | |
ccc3da77 | 171 | class DtbPlatdata(): |
7581c01a SG |
172 | """Provide a means to convert device tree binary data to platform data |
173 | ||
174 | The output of this process is C structures which can be used in space- | |
175 | constrained encvironments where the ~3KB code overhead of device tree | |
176 | code is not affordable. | |
177 | ||
178 | Properties: | |
2be282ca | 179 | _fdt: Fdt object, referencing the device tree |
7581c01a | 180 | _dtb_fname: Filename of the input device tree binary file |
1b27273e SG |
181 | _valid_nodes: A list of Node object with compatible strings. The list |
182 | is ordered by conv_name_to_c(node.name) | |
e36024b0 | 183 | _include_disabled: true to include nodes marked status = "disabled" |
7581c01a | 184 | _outfile: The current output file (sys.stdout or a real file) |
361e7335 | 185 | _warning_disabled: true to disable warnings about driver names not found |
7581c01a | 186 | _lines: Stashed list of output lines for outputting in the future |
7d637c12 SG |
187 | _drivers: Dict of valid driver names found in drivers/ |
188 | key: Driver name | |
189 | value: Driver for that driver | |
dac8228d WL |
190 | _driver_aliases: Dict that holds aliases for driver names |
191 | key: Driver alias declared with | |
bdf8fd76 | 192 | DM_DRIVER_ALIAS(driver_alias, driver_name) |
dac8228d | 193 | value: Driver name declared with U_BOOT_DRIVER(driver_name) |
6c74d1b8 | 194 | _drivers_additional: List of additional drivers to use during scanning |
be44f271 SG |
195 | _dirname: Directory to hold output files, or None for none (all files |
196 | go to stdout) | |
a7d5f96e SG |
197 | _struct_data (dict): OrderedDict of dtplat structures to output |
198 | key (str): Node name, as a C identifier | |
199 | value: dict containing structure fields: | |
200 | key (str): Field name | |
201 | value: Prop object with field information | |
1e0f3f46 | 202 | _basedir (str): Base directory of source tree |
7581c01a | 203 | """ |
6c74d1b8 | 204 | def __init__(self, dtb_fname, include_disabled, warning_disabled, |
78128d52 | 205 | drivers_additional=None): |
2be282ca | 206 | self._fdt = None |
7581c01a SG |
207 | self._dtb_fname = dtb_fname |
208 | self._valid_nodes = None | |
e36024b0 | 209 | self._include_disabled = include_disabled |
7581c01a | 210 | self._outfile = None |
361e7335 | 211 | self._warning_disabled = warning_disabled |
7581c01a | 212 | self._lines = [] |
7d637c12 | 213 | self._drivers = {} |
dac8228d | 214 | self._driver_aliases = {} |
78128d52 | 215 | self._drivers_additional = drivers_additional or [] |
be44f271 | 216 | self._dirnames = [None] * len(Ftype) |
a7d5f96e | 217 | self._struct_data = collections.OrderedDict() |
1e0f3f46 | 218 | self._basedir = None |
dac8228d WL |
219 | |
220 | def get_normalized_compat_name(self, node): | |
221 | """Get a node's normalized compat name | |
222 | ||
dcb3ed64 | 223 | Returns a valid driver name by retrieving node's list of compatible |
dac8228d WL |
224 | string as a C identifier and performing a check against _drivers |
225 | and a lookup in driver_aliases printing a warning in case of failure. | |
226 | ||
227 | Args: | |
ccc3da77 | 228 | node (Node): Node object to check |
dac8228d WL |
229 | Return: |
230 | Tuple: | |
231 | Driver name associated with the first compatible string | |
232 | List of C identifiers for all the other compatible strings | |
233 | (possibly empty) | |
234 | In case of no match found, the return will be the same as | |
235 | get_compat_name() | |
236 | """ | |
dcb3ed64 WL |
237 | compat_list_c = get_compat_name(node) |
238 | ||
239 | for compat_c in compat_list_c: | |
7d637c12 | 240 | if not compat_c in self._drivers.keys(): |
dcb3ed64 WL |
241 | compat_c = self._driver_aliases.get(compat_c) |
242 | if not compat_c: | |
243 | continue | |
244 | ||
245 | aliases_c = compat_list_c | |
246 | if compat_c in aliases_c: | |
247 | aliases_c.remove(compat_c) | |
248 | return compat_c, aliases_c | |
249 | ||
250 | if not self._warning_disabled: | |
251 | print('WARNING: the driver %s was not found in the driver list' | |
252 | % (compat_list_c[0])) | |
253 | ||
254 | return compat_list_c[0], compat_list_c[1:] | |
7581c01a | 255 | |
be44f271 SG |
256 | def setup_output_dirs(self, output_dirs): |
257 | """Set up the output directories | |
258 | ||
259 | This should be done before setup_output() is called | |
260 | ||
261 | Args: | |
262 | output_dirs (tuple of str): | |
263 | Directory to use for C output files. | |
264 | Use None to write files relative current directory | |
265 | Directory to use for H output files. | |
266 | Defaults to the C output dir | |
267 | """ | |
268 | def process_dir(ftype, dirname): | |
269 | if dirname: | |
270 | os.makedirs(dirname, exist_ok=True) | |
271 | self._dirnames[ftype] = dirname | |
272 | ||
273 | if output_dirs: | |
274 | c_dirname = output_dirs[0] | |
275 | h_dirname = output_dirs[1] if len(output_dirs) > 1 else c_dirname | |
276 | process_dir(Ftype.SOURCE, c_dirname) | |
277 | process_dir(Ftype.HEADER, h_dirname) | |
278 | ||
279 | def setup_output(self, ftype, fname): | |
7581c01a SG |
280 | """Set up the output destination |
281 | ||
2be282ca | 282 | Once this is done, future calls to self.out() will output to this |
be44f271 SG |
283 | file. The file used is as follows: |
284 | ||
285 | self._dirnames[ftype] is None: output to fname, or stdout if None | |
286 | self._dirnames[ftype] is not None: output to fname in that directory | |
287 | ||
288 | Calling this function multiple times will close the old file and open | |
289 | the new one. If they are the same file, nothing happens and output will | |
290 | continue to the same file. | |
7581c01a SG |
291 | |
292 | Args: | |
be44f271 SG |
293 | ftype (str): Type of file to create ('c' or 'h') |
294 | fname (str): Filename to send output to. If there is a directory in | |
295 | self._dirnames for this file type, it will be put in that | |
296 | directory | |
7581c01a | 297 | """ |
be44f271 SG |
298 | dirname = self._dirnames[ftype] |
299 | if dirname: | |
300 | pathname = os.path.join(dirname, fname) | |
301 | if self._outfile: | |
302 | self._outfile.close() | |
303 | self._outfile = open(pathname, 'w') | |
304 | elif fname: | |
305 | if not self._outfile: | |
306 | self._outfile = open(fname, 'w') | |
f62cea0e SG |
307 | else: |
308 | self._outfile = sys.stdout | |
7581c01a | 309 | |
be44f271 SG |
310 | def finish_output(self): |
311 | """Finish outputing to a file | |
312 | ||
313 | This closes the output file, if one is in use | |
314 | """ | |
315 | if self._outfile != sys.stdout: | |
316 | self._outfile.close() | |
317 | ||
2be282ca | 318 | def out(self, line): |
7581c01a SG |
319 | """Output a string to the output file |
320 | ||
321 | Args: | |
9b330382 | 322 | line (str): String to output |
7581c01a | 323 | """ |
2be282ca | 324 | self._outfile.write(line) |
7581c01a | 325 | |
2be282ca | 326 | def buf(self, line): |
7581c01a SG |
327 | """Buffer up a string to send later |
328 | ||
329 | Args: | |
9b330382 | 330 | line (str): String to add to our 'buffer' list |
7581c01a | 331 | """ |
2be282ca | 332 | self._lines.append(line) |
7581c01a | 333 | |
2be282ca | 334 | def get_buf(self): |
7581c01a SG |
335 | """Get the contents of the output buffer, and clear it |
336 | ||
337 | Returns: | |
9b330382 | 338 | list(str): The output buffer, which is then cleared for future use |
7581c01a SG |
339 | """ |
340 | lines = self._lines | |
341 | self._lines = [] | |
342 | return lines | |
343 | ||
d1055d68 SG |
344 | def out_header(self, outfile): |
345 | """Output a message indicating that this is an auto-generated file | |
346 | ||
347 | Args: | |
348 | outfile: OutputFile describing the file being generated | |
349 | """ | |
d503114c SG |
350 | self.out('''/* |
351 | * DO NOT MODIFY | |
352 | * | |
d1055d68 SG |
353 | * %s. |
354 | * This was generated by dtoc from a .dtb (device tree binary) file. | |
d503114c SG |
355 | */ |
356 | ||
d1055d68 | 357 | ''' % outfile.hdr_comment) |
d503114c | 358 | |
8fed2eb2 SG |
359 | def get_phandle_argc(self, prop, node_name): |
360 | """Check if a node contains phandles | |
2925c26b | 361 | |
8fed2eb2 SG |
362 | We have no reliable way of detecting whether a node uses a phandle |
363 | or not. As an interim measure, use a list of known property names. | |
2925c26b | 364 | |
8fed2eb2 | 365 | Args: |
9b330382 SG |
366 | prop (fdt.Prop): Prop object to check |
367 | node_name (str): Node name, only used for raising an error | |
368 | Returns: | |
369 | int or None: Number of argument cells is this is a phandle, | |
370 | else None | |
371 | Raises: | |
372 | ValueError: if the phandle cannot be parsed or the required property | |
373 | is not present | |
8fed2eb2 | 374 | """ |
ad34017c | 375 | if prop.name in ['clocks', 'cd-gpios']: |
760b7170 SG |
376 | if not isinstance(prop.value, list): |
377 | prop.value = [prop.value] | |
8fed2eb2 | 378 | val = prop.value |
8fed2eb2 SG |
379 | i = 0 |
380 | ||
381 | max_args = 0 | |
382 | args = [] | |
383 | while i < len(val): | |
384 | phandle = fdt_util.fdt32_to_cpu(val[i]) | |
760b7170 SG |
385 | # If we get to the end of the list, stop. This can happen |
386 | # since some nodes have more phandles in the list than others, | |
387 | # but we allocate enough space for the largest list. So those | |
388 | # nodes with shorter lists end up with zeroes at the end. | |
389 | if not phandle: | |
390 | break | |
8fed2eb2 SG |
391 | target = self._fdt.phandle_to_node.get(phandle) |
392 | if not target: | |
393 | raise ValueError("Cannot parse '%s' in node '%s'" % | |
394 | (prop.name, node_name)) | |
ad34017c WL |
395 | cells = None |
396 | for prop_name in ['#clock-cells', '#gpio-cells']: | |
397 | cells = target.props.get(prop_name) | |
398 | if cells: | |
399 | break | |
8fed2eb2 | 400 | if not cells: |
ad34017c | 401 | raise ValueError("Node '%s' has no cells property" % |
9b330382 | 402 | (target.name)) |
8fed2eb2 SG |
403 | num_args = fdt_util.fdt32_to_cpu(cells.value) |
404 | max_args = max(max_args, num_args) | |
405 | args.append(num_args) | |
406 | i += 1 + num_args | |
407 | return PhandleInfo(max_args, args) | |
408 | return None | |
2925c26b | 409 | |
78128d52 | 410 | def scan_driver(self, fname): |
dac8228d WL |
411 | """Scan a driver file to build a list of driver names and aliases |
412 | ||
413 | This procedure will populate self._drivers and self._driver_aliases | |
414 | ||
415 | Args | |
78128d52 | 416 | fname: Driver filename to scan |
dac8228d | 417 | """ |
78128d52 | 418 | with open(fname, encoding='utf-8') as inf: |
dac8228d | 419 | try: |
78128d52 | 420 | buff = inf.read() |
dac8228d WL |
421 | except UnicodeDecodeError: |
422 | # This seems to happen on older Python versions | |
78128d52 | 423 | print("Skipping file '%s' due to unicode error" % fname) |
dac8228d WL |
424 | return |
425 | ||
426 | # The following re will search for driver names declared as | |
427 | # U_BOOT_DRIVER(driver_name) | |
ccc3da77 | 428 | drivers = re.findall(r'U_BOOT_DRIVER\((.*)\)', buff) |
dac8228d WL |
429 | |
430 | for driver in drivers: | |
7d637c12 | 431 | self._drivers[driver] = Driver(driver) |
dac8228d WL |
432 | |
433 | # The following re will search for driver aliases declared as | |
bdf8fd76 | 434 | # DM_DRIVER_ALIAS(alias, driver_name) |
78128d52 | 435 | driver_aliases = re.findall( |
bdf8fd76 | 436 | r'DM_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)', |
78128d52 | 437 | buff) |
dac8228d WL |
438 | |
439 | for alias in driver_aliases: # pragma: no cover | |
440 | if len(alias) != 2: | |
441 | continue | |
442 | self._driver_aliases[alias[1]] = alias[0] | |
443 | ||
1e0f3f46 | 444 | def scan_drivers(self, basedir=None): |
dac8228d WL |
445 | """Scan the driver folders to build a list of driver names and aliases |
446 | ||
447 | This procedure will populate self._drivers and self._driver_aliases | |
448 | ||
449 | """ | |
1e0f3f46 SG |
450 | if not basedir: |
451 | basedir = sys.argv[0].replace('tools/dtoc/dtoc', '') | |
452 | if basedir == '': | |
453 | basedir = './' | |
454 | self._basedir = basedir | |
78128d52 SG |
455 | for (dirpath, _, filenames) in os.walk(basedir): |
456 | for fname in filenames: | |
457 | if not fname.endswith('.c'): | |
dac8228d | 458 | continue |
78128d52 | 459 | self.scan_driver(dirpath + '/' + fname) |
dac8228d | 460 | |
78128d52 SG |
461 | for fname in self._drivers_additional: |
462 | if not isinstance(fname, str) or len(fname) == 0: | |
6c74d1b8 | 463 | continue |
78128d52 SG |
464 | if fname[0] == '/': |
465 | self.scan_driver(fname) | |
6c74d1b8 | 466 | else: |
78128d52 | 467 | self.scan_driver(basedir + '/' + fname) |
6c74d1b8 | 468 | |
2be282ca | 469 | def scan_dtb(self): |
f1a7ba1d | 470 | """Scan the device tree to obtain a tree of nodes and properties |
7581c01a | 471 | |
2be282ca | 472 | Once this is done, self._fdt.GetRoot() can be called to obtain the |
7581c01a SG |
473 | device tree root node, and progress from there. |
474 | """ | |
2be282ca SG |
475 | self._fdt = fdt.FdtScan(self._dtb_fname) |
476 | ||
1b27273e | 477 | def scan_node(self, root, valid_nodes): |
2be282ca SG |
478 | """Scan a node and subnodes to build a tree of node and phandle info |
479 | ||
72ab7c5e | 480 | This adds each node to self._valid_nodes. |
7581c01a | 481 | |
2be282ca | 482 | Args: |
ccc3da77 SG |
483 | root (Node): Root node for scan |
484 | valid_nodes (list of Node): List of Node objects to add to | |
2be282ca | 485 | """ |
7581c01a SG |
486 | for node in root.subnodes: |
487 | if 'compatible' in node.props: | |
488 | status = node.props.get('status') | |
e36024b0 | 489 | if (not self._include_disabled and not status or |
2be282ca | 490 | status.value != 'disabled'): |
1b27273e | 491 | valid_nodes.append(node) |
7581c01a SG |
492 | |
493 | # recurse to handle any subnodes | |
1b27273e | 494 | self.scan_node(node, valid_nodes) |
7581c01a | 495 | |
2be282ca | 496 | def scan_tree(self): |
7581c01a SG |
497 | """Scan the device tree for useful information |
498 | ||
499 | This fills in the following properties: | |
7581c01a SG |
500 | _valid_nodes: A list of nodes we wish to consider include in the |
501 | platform data | |
502 | """ | |
1b27273e SG |
503 | valid_nodes = [] |
504 | self.scan_node(self._fdt.GetRoot(), valid_nodes) | |
505 | self._valid_nodes = sorted(valid_nodes, | |
506 | key=lambda x: conv_name_to_c(x.name)) | |
507 | for idx, node in enumerate(self._valid_nodes): | |
508 | node.idx = idx | |
7581c01a | 509 | |
c20ee0ed SG |
510 | @staticmethod |
511 | def get_num_cells(node): | |
512 | """Get the number of cells in addresses and sizes for this node | |
513 | ||
514 | Args: | |
9b330382 | 515 | node (fdt.None): Node to check |
c20ee0ed SG |
516 | |
517 | Returns: | |
518 | Tuple: | |
519 | Number of address cells for this node | |
520 | Number of size cells for this node | |
521 | """ | |
522 | parent = node.parent | |
78128d52 | 523 | num_addr, num_size = 2, 2 |
c20ee0ed | 524 | if parent: |
78128d52 SG |
525 | addr_prop = parent.props.get('#address-cells') |
526 | size_prop = parent.props.get('#size-cells') | |
527 | if addr_prop: | |
528 | num_addr = fdt_util.fdt32_to_cpu(addr_prop.value) | |
529 | if size_prop: | |
530 | num_size = fdt_util.fdt32_to_cpu(size_prop.value) | |
531 | return num_addr, num_size | |
c20ee0ed SG |
532 | |
533 | def scan_reg_sizes(self): | |
534 | """Scan for 64-bit 'reg' properties and update the values | |
535 | ||
536 | This finds 'reg' properties with 64-bit data and converts the value to | |
537 | an array of 64-values. This allows it to be output in a way that the | |
538 | C code can read. | |
539 | """ | |
540 | for node in self._valid_nodes: | |
541 | reg = node.props.get('reg') | |
542 | if not reg: | |
543 | continue | |
78128d52 SG |
544 | num_addr, num_size = self.get_num_cells(node) |
545 | total = num_addr + num_size | |
c20ee0ed | 546 | |
5ea9dccf | 547 | if reg.type != fdt.Type.INT: |
dfe5f5b9 SG |
548 | raise ValueError("Node '%s' reg property is not an int" % |
549 | node.name) | |
c20ee0ed | 550 | if len(reg.value) % total: |
9b330382 SG |
551 | raise ValueError( |
552 | "Node '%s' reg property has %d cells " | |
553 | 'which is not a multiple of na + ns = %d + %d)' % | |
78128d52 SG |
554 | (node.name, len(reg.value), num_addr, num_size)) |
555 | reg.num_addr = num_addr | |
556 | reg.num_size = num_size | |
557 | if num_addr != 1 or num_size != 1: | |
5ea9dccf | 558 | reg.type = fdt.Type.INT64 |
c20ee0ed SG |
559 | i = 0 |
560 | new_value = [] | |
561 | val = reg.value | |
562 | if not isinstance(val, list): | |
563 | val = [val] | |
564 | while i < len(val): | |
78128d52 SG |
565 | addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.num_addr) |
566 | i += num_addr | |
567 | size = fdt_util.fdt_cells_to_cpu(val[i:], reg.num_size) | |
568 | i += num_size | |
c20ee0ed SG |
569 | new_value += [addr, size] |
570 | reg.value = new_value | |
571 | ||
2be282ca | 572 | def scan_structs(self): |
7581c01a SG |
573 | """Scan the device tree building up the C structures we will use. |
574 | ||
575 | Build a dict keyed by C struct name containing a dict of Prop | |
576 | object for each struct field (keyed by property name). Where the | |
577 | same struct appears multiple times, try to use the 'widest' | |
578 | property, i.e. the one with a type which can express all others. | |
579 | ||
580 | Once the widest property is determined, all other properties are | |
581 | updated to match that width. | |
e4fb5faa | 582 | |
a7d5f96e | 583 | The results are written to self._struct_data |
7581c01a | 584 | """ |
a7d5f96e | 585 | structs = self._struct_data |
7581c01a | 586 | for node in self._valid_nodes: |
dac8228d | 587 | node_name, _ = self.get_normalized_compat_name(node) |
7581c01a SG |
588 | fields = {} |
589 | ||
590 | # Get a list of all the valid properties in this node. | |
591 | for name, prop in node.props.items(): | |
592 | if name not in PROP_IGNORE_LIST and name[0] != '#': | |
593 | fields[name] = copy.deepcopy(prop) | |
594 | ||
595 | # If we've seen this node_name before, update the existing struct. | |
596 | if node_name in structs: | |
597 | struct = structs[node_name] | |
598 | for name, prop in fields.items(): | |
599 | oldprop = struct.get(name) | |
600 | if oldprop: | |
601 | oldprop.Widen(prop) | |
602 | else: | |
603 | struct[name] = prop | |
604 | ||
605 | # Otherwise store this as a new struct. | |
606 | else: | |
607 | structs[node_name] = fields | |
608 | ||
7581c01a | 609 | for node in self._valid_nodes: |
dac8228d | 610 | node_name, _ = self.get_normalized_compat_name(node) |
7581c01a SG |
611 | struct = structs[node_name] |
612 | for name, prop in node.props.items(): | |
613 | if name not in PROP_IGNORE_LIST and name[0] != '#': | |
614 | prop.Widen(struct[name]) | |
7581c01a | 615 | |
2be282ca | 616 | def scan_phandles(self): |
7581c01a SG |
617 | """Figure out what phandles each node uses |
618 | ||
619 | We need to be careful when outputing nodes that use phandles since | |
620 | they must come after the declaration of the phandles in the C file. | |
621 | Otherwise we get a compiler error since the phandle struct is not yet | |
622 | declared. | |
623 | ||
624 | This function adds to each node a list of phandle nodes that the node | |
625 | depends on. This allows us to output things in the right order. | |
626 | """ | |
627 | for node in self._valid_nodes: | |
628 | node.phandles = set() | |
629 | for pname, prop in node.props.items(): | |
630 | if pname in PROP_IGNORE_LIST or pname[0] == '#': | |
631 | continue | |
8fed2eb2 SG |
632 | info = self.get_phandle_argc(prop, node.name) |
633 | if info: | |
8fed2eb2 | 634 | # Process the list as pairs of (phandle, id) |
634eba4b SG |
635 | pos = 0 |
636 | for args in info.args: | |
637 | phandle_cell = prop.value[pos] | |
8fed2eb2 SG |
638 | phandle = fdt_util.fdt32_to_cpu(phandle_cell) |
639 | target_node = self._fdt.phandle_to_node[phandle] | |
640 | node.phandles.add(target_node) | |
634eba4b | 641 | pos += 1 + args |
7581c01a SG |
642 | |
643 | ||
a7d5f96e | 644 | def generate_structs(self): |
7581c01a SG |
645 | """Generate struct defintions for the platform data |
646 | ||
647 | This writes out the body of a header file consisting of structure | |
648 | definitions for node in self._valid_nodes. See the documentation in | |
2799a69e | 649 | doc/driver-model/of-plat.rst for more information. |
7581c01a | 650 | """ |
a7d5f96e | 651 | structs = self._struct_data |
2be282ca | 652 | self.out('#include <stdbool.h>\n') |
b08c8c48 | 653 | self.out('#include <linux/libfdt.h>\n') |
7581c01a SG |
654 | |
655 | # Output the struct definition | |
656 | for name in sorted(structs): | |
2be282ca | 657 | self.out('struct %s%s {\n' % (STRUCT_PREFIX, name)) |
7581c01a SG |
658 | for pname in sorted(structs[name]): |
659 | prop = structs[name][pname] | |
8fed2eb2 SG |
660 | info = self.get_phandle_argc(prop, structs[name]) |
661 | if info: | |
7581c01a | 662 | # For phandles, include a reference to the target |
0d15463c SG |
663 | struct_name = 'struct phandle_%d_arg' % info.max_args |
664 | self.out('\t%s%s[%d]' % (tab_to(2, struct_name), | |
2be282ca | 665 | conv_name_to_c(prop.name), |
634eba4b | 666 | len(info.args))) |
7581c01a SG |
667 | else: |
668 | ptype = TYPE_NAMES[prop.type] | |
2be282ca SG |
669 | self.out('\t%s%s' % (tab_to(2, ptype), |
670 | conv_name_to_c(prop.name))) | |
671 | if isinstance(prop.value, list): | |
672 | self.out('[%d]' % len(prop.value)) | |
673 | self.out(';\n') | |
674 | self.out('};\n') | |
7581c01a | 675 | |
abf0c802 SG |
676 | def _output_list(self, node, prop): |
677 | """Output the C code for a devicetree property that holds a list | |
678 | ||
679 | Args: | |
680 | node (fdt.Node): Node to output | |
681 | prop (fdt.Prop): Prop to output | |
682 | """ | |
683 | self.buf('{') | |
684 | vals = [] | |
685 | # For phandles, output a reference to the platform data | |
686 | # of the target node. | |
687 | info = self.get_phandle_argc(prop, node.name) | |
688 | if info: | |
689 | # Process the list as pairs of (phandle, id) | |
690 | pos = 0 | |
691 | for args in info.args: | |
692 | phandle_cell = prop.value[pos] | |
693 | phandle = fdt_util.fdt32_to_cpu(phandle_cell) | |
694 | target_node = self._fdt.phandle_to_node[phandle] | |
695 | arg_values = [] | |
696 | for i in range(args): | |
697 | arg_values.append( | |
698 | str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i]))) | |
699 | pos += 1 + args | |
700 | vals.append('\t{%d, {%s}}' % (target_node.idx, | |
701 | ', '.join(arg_values))) | |
702 | for val in vals: | |
703 | self.buf('\n\t\t%s,' % val) | |
704 | else: | |
705 | for val in prop.value: | |
706 | vals.append(get_value(prop.type, val)) | |
707 | ||
708 | # Put 8 values per line to avoid very long lines. | |
709 | for i in range(0, len(vals), 8): | |
710 | if i: | |
711 | self.buf(',\n\t\t') | |
712 | self.buf(', '.join(vals[i:i + 8])) | |
713 | self.buf('}') | |
714 | ||
221ddc11 SG |
715 | def _declare_device(self, var_name, struct_name, node_parent): |
716 | """Add a device declaration to the output | |
717 | ||
20e442ab | 718 | This declares a U_BOOT_DRVINFO() for the device being processed |
221ddc11 SG |
719 | |
720 | Args: | |
721 | var_name (str): C name for the node | |
722 | struct_name (str): Name for the dt struct associated with the node | |
723 | node_parent (Node): Parent of the node (or None if none) | |
724 | """ | |
20e442ab | 725 | self.buf('U_BOOT_DRVINFO(%s) = {\n' % var_name) |
221ddc11 SG |
726 | self.buf('\t.name\t\t= "%s",\n' % struct_name) |
727 | self.buf('\t.plat\t= &%s%s,\n' % (VAL_PREFIX, var_name)) | |
728 | self.buf('\t.plat_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name)) | |
729 | idx = -1 | |
730 | if node_parent and node_parent in self._valid_nodes: | |
731 | idx = node_parent.idx | |
732 | self.buf('\t.parent_idx\t= %d,\n' % idx) | |
733 | self.buf('};\n') | |
734 | self.buf('\n') | |
735 | ||
161dac1d SG |
736 | def _output_prop(self, node, prop): |
737 | """Output a line containing the value of a struct member | |
738 | ||
739 | Args: | |
740 | node (Node): Node being output | |
741 | prop (Prop): Prop object to output | |
742 | """ | |
743 | if prop.name in PROP_IGNORE_LIST or prop.name[0] == '#': | |
744 | return | |
745 | member_name = conv_name_to_c(prop.name) | |
746 | self.buf('\t%s= ' % tab_to(3, '.' + member_name)) | |
747 | ||
748 | # Special handling for lists | |
749 | if isinstance(prop.value, list): | |
750 | self._output_list(node, prop) | |
751 | else: | |
752 | self.buf(get_value(prop.type, prop.value)) | |
753 | self.buf(',\n') | |
754 | ||
755 | def _output_values(self, var_name, struct_name, node): | |
756 | """Output the definition of a device's struct values | |
757 | ||
758 | Args: | |
759 | var_name (str): C name for the node | |
760 | struct_name (str): Name for the dt struct associated with the node | |
761 | node (Node): Node being output | |
762 | """ | |
763 | self.buf('static struct %s%s %s%s = {\n' % | |
764 | (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) | |
765 | for pname in sorted(node.props): | |
766 | self._output_prop(node, node.props[pname]) | |
767 | self.buf('};\n') | |
768 | ||
2be282ca | 769 | def output_node(self, node): |
7581c01a SG |
770 | """Output the C code for a node |
771 | ||
772 | Args: | |
9b330382 | 773 | node (fdt.Node): node to output |
7581c01a | 774 | """ |
dac8228d | 775 | struct_name, _ = self.get_normalized_compat_name(node) |
2be282ca | 776 | var_name = conv_name_to_c(node.name) |
1b27273e | 777 | self.buf('/* Node %s index %d */\n' % (node.path, node.idx)) |
7581c01a | 778 | |
161dac1d | 779 | self._output_values(var_name, struct_name, node) |
221ddc11 | 780 | self._declare_device(var_name, struct_name, node.parent) |
7581c01a | 781 | |
2be282ca | 782 | self.out(''.join(self.get_buf())) |
7581c01a | 783 | |
a7d5f96e | 784 | def generate_plat(self): |
7581c01a SG |
785 | """Generate device defintions for the platform data |
786 | ||
787 | This writes out C platform data initialisation data and | |
20e442ab | 788 | U_BOOT_DRVINFO() declarations for each valid node. Where a node has |
7581c01a SG |
789 | multiple compatible strings, a #define is used to make them equivalent. |
790 | ||
2799a69e | 791 | See the documentation in doc/driver-model/of-plat.rst for more |
7581c01a SG |
792 | information. |
793 | """ | |
20e442ab | 794 | self.out('/* Allow use of U_BOOT_DRVINFO() in this file */\n') |
f31fa99a | 795 | self.out('#define DT_PLAT_C\n') |
cb43ac18 | 796 | self.out('\n') |
2be282ca SG |
797 | self.out('#include <common.h>\n') |
798 | self.out('#include <dm.h>\n') | |
799 | self.out('#include <dt-structs.h>\n') | |
800 | self.out('\n') | |
7581c01a SG |
801 | nodes_to_output = list(self._valid_nodes) |
802 | ||
803 | # Keep outputing nodes until there is none left | |
804 | while nodes_to_output: | |
805 | node = nodes_to_output[0] | |
806 | # Output all the node's dependencies first | |
807 | for req_node in node.phandles: | |
808 | if req_node in nodes_to_output: | |
2be282ca | 809 | self.output_node(req_node) |
7581c01a | 810 | nodes_to_output.remove(req_node) |
2be282ca | 811 | self.output_node(node) |
7581c01a | 812 | nodes_to_output.remove(node) |
fa0ea5b0 | 813 | |
51f1263d | 814 | # Define dm_populate_phandle_data() which will add the linking between |
8629d30a SG |
815 | # nodes using DM_DRVINFO_GET |
816 | # dtv_dmc_at_xxx.clocks[0].node = DM_DRVINFO_GET(clock_controller_at_xxx) | |
51f1263d | 817 | self.buf('void dm_populate_phandle_data(void) {\n') |
51f1263d WL |
818 | self.buf('}\n') |
819 | ||
820 | self.out(''.join(self.get_buf())) | |
fa0ea5b0 | 821 | |
192c111c | 822 | |
be44f271 SG |
823 | # Types of output file we understand |
824 | # key: Command used to generate this file | |
825 | # value: OutputFile for this command | |
826 | OUTPUT_FILES = { | |
d1055d68 SG |
827 | 'struct': |
828 | OutputFile(Ftype.HEADER, 'dt-structs-gen.h', | |
a7d5f96e | 829 | DtbPlatdata.generate_structs, |
d1055d68 SG |
830 | 'Defines the structs used to hold devicetree data'), |
831 | 'platdata': | |
a7d5f96e | 832 | OutputFile(Ftype.SOURCE, 'dt-plat.c', DtbPlatdata.generate_plat, |
d1055d68 | 833 | 'Declares the U_BOOT_DRIVER() records and platform data'), |
be44f271 SG |
834 | } |
835 | ||
836 | ||
192c111c | 837 | def run_steps(args, dtb_file, include_disabled, output, output_dirs, |
1e0f3f46 | 838 | warning_disabled=False, drivers_additional=None, basedir=None): |
fa0ea5b0 SG |
839 | """Run all the steps of the dtoc tool |
840 | ||
841 | Args: | |
9b330382 SG |
842 | args (list): List of non-option arguments provided to the problem |
843 | dtb_file (str): Filename of dtb file to process | |
844 | include_disabled (bool): True to include disabled nodes | |
f62cea0e | 845 | output (str): Name of output file (None for stdout) |
192c111c SG |
846 | output_dirs (tuple of str): |
847 | Directory to put C output files | |
848 | Directory to put H output files | |
78128d52 SG |
849 | warning_disabled (bool): True to avoid showing warnings about missing |
850 | drivers | |
ccc3da77 | 851 | drivers_additional (list): List of additional drivers to use during |
78128d52 | 852 | scanning |
1e0f3f46 SG |
853 | basedir (str): Base directory of U-Boot source code. Defaults to the |
854 | grandparent of this file's directory | |
9b330382 SG |
855 | Raises: |
856 | ValueError: if args has no command, or an unknown command | |
fa0ea5b0 SG |
857 | """ |
858 | if not args: | |
be44f271 SG |
859 | raise ValueError('Please specify a command: struct, platdata, all') |
860 | if output and output_dirs and any(output_dirs): | |
861 | raise ValueError('Must specify either output or output_dirs, not both') | |
fa0ea5b0 | 862 | |
78128d52 SG |
863 | plat = DtbPlatdata(dtb_file, include_disabled, warning_disabled, |
864 | drivers_additional) | |
1e0f3f46 | 865 | plat.scan_drivers(basedir) |
fa0ea5b0 SG |
866 | plat.scan_dtb() |
867 | plat.scan_tree() | |
c20ee0ed | 868 | plat.scan_reg_sizes() |
be44f271 | 869 | plat.setup_output_dirs(output_dirs) |
a7d5f96e | 870 | plat.scan_structs() |
fa0ea5b0 SG |
871 | plat.scan_phandles() |
872 | ||
10cbd3b7 SG |
873 | cmds = args[0].split(',') |
874 | if 'all' in cmds: | |
875 | cmds = sorted(OUTPUT_FILES.keys()) | |
876 | for cmd in cmds: | |
be44f271 SG |
877 | outfile = OUTPUT_FILES.get(cmd) |
878 | if not outfile: | |
879 | raise ValueError("Unknown command '%s': (use: %s)" % | |
10cbd3b7 | 880 | (cmd, ', '.join(sorted(OUTPUT_FILES.keys())))) |
be44f271 SG |
881 | plat.setup_output(outfile.ftype, |
882 | outfile.fname if output_dirs else output) | |
d1055d68 | 883 | plat.out_header(outfile) |
a7d5f96e | 884 | outfile.method(plat) |
be44f271 | 885 | plat.finish_output() |