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 11This supports converting device tree data to C structures definitions and 12static data. 13""" 14 15import collections 16import copy 17import sys 18 19import fdt 20import fdt_util 21 22# 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', 31 'u-boot,dm-pre-reloc', 32 'u-boot,dm-tpl', 33 'u-boot,dm-spl', 34] 35 36# C type declarations for the tyues we support 37TYPE_NAMES = { 38 fdt.TYPE_INT: 'fdt32_t', 39 fdt.TYPE_BYTE: 'unsigned char', 40 fdt.TYPE_STRING: 'const char *', 41 fdt.TYPE_BOOL: 'bool', 42 fdt.TYPE_INT64: 'fdt64_t', 43} 44 45STRUCT_PREFIX = 'dtd_' 46VAL_PREFIX = 'dtv_' 47 48# This holds information about a property which includes phandles. 49# 50# max_args: integer: Maximum number or arguments that any phandle uses (int). 51# args: Number of args for each phandle in the property. The total number of 52# phandles is len(args). This is a list of integers. 53PhandleInfo = collections.namedtuple('PhandleInfo', ['max_args', 'args']) 54 55 56def conv_name_to_c(name): 57 """Convert a device-tree name to a C identifier 58 59 This uses multiple replace() calls instead of re.sub() since it is faster 60 (400ms for 1m calls versus 1000ms for the 're' version). 61 62 Args: 63 name: Name to convert 64 Return: 65 String containing the C version of this name 66 """ 67 new = name.replace('@', '_at_') 68 new = new.replace('-', '_') 69 new = new.replace(',', '_') 70 new = new.replace('.', '_') 71 return new 72 73def tab_to(num_tabs, line): 74 """Append tabs to a line of text to reach a tab stop. 75 76 Args: 77 num_tabs: Tab stop to obtain (0 = column 0, 1 = column 8, etc.) 78 line: Line of text to append to 79 80 Returns: 81 line with the correct number of tabs appeneded. If the line already 82 extends past that tab stop then a single space is appended. 83 """ 84 if len(line) >= num_tabs * 8: 85 return line + ' ' 86 return line + '\t' * (num_tabs - len(line) // 8) 87 88def get_value(ftype, value): 89 """Get a value as a C expression 90 91 For integers this returns a byte-swapped (little-endian) hex string 92 For bytes this returns a hex string, e.g. 0x12 93 For strings this returns a literal string enclosed in quotes 94 For booleans this return 'true' 95 96 Args: 97 type: Data type (fdt_util) 98 value: Data value, as a string of bytes 99 """ 100 if ftype == fdt.TYPE_INT: 101 return '%#x' % fdt_util.fdt32_to_cpu(value) 102 elif ftype == fdt.TYPE_BYTE: 103 return '%#x' % ord(value[0]) 104 elif ftype == fdt.TYPE_STRING: 105 return '"%s"' % value 106 elif ftype == fdt.TYPE_BOOL: 107 return 'true' 108 elif ftype == fdt.TYPE_INT64: 109 return '%#x' % value 110 111def get_compat_name(node): 112 """Get a node's first compatible string as a C identifier 113 114 Args: 115 node: Node object to check 116 Return: 117 Tuple: 118 C identifier for the first compatible string 119 List of C identifiers for all the other compatible strings 120 (possibly empty) 121 """ 122 compat = node.props['compatible'].value 123 aliases = [] 124 if isinstance(compat, list): 125 compat, aliases = compat[0], compat[1:] 126 return conv_name_to_c(compat), [conv_name_to_c(a) for a in aliases] 127 128 129class 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 _outfile: The current output file (sys.stdout or a real file) 142 _lines: Stashed list of output lines for outputting in the future 143 """ 144 def __init__(self, dtb_fname, include_disabled): 145 self._fdt = None 146 self._dtb_fname = dtb_fname 147 self._valid_nodes = None 148 self._include_disabled = include_disabled 149 self._outfile = None 150 self._lines = [] 151 self._aliases = {} 152 153 def setup_output(self, fname): 154 """Set up the output destination 155 156 Once this is done, future calls to self.out() will output to this 157 file. 158 159 Args: 160 fname: Filename to send output to, or '-' for stdout 161 """ 162 if fname == '-': 163 self._outfile = sys.stdout 164 else: 165 self._outfile = open(fname, 'w') 166 167 def out(self, line): 168 """Output a string to the output file 169 170 Args: 171 line: String to output 172 """ 173 self._outfile.write(line) 174 175 def buf(self, line): 176 """Buffer up a string to send later 177 178 Args: 179 line: String to add to our 'buffer' list 180 """ 181 self._lines.append(line) 182 183 def get_buf(self): 184 """Get the contents of the output buffer, and clear it 185 186 Returns: 187 The output buffer, which is then cleared for future use 188 """ 189 lines = self._lines 190 self._lines = [] 191 return lines 192 193 def out_header(self): 194 """Output a message indicating that this is an auto-generated file""" 195 self.out('''/* 196 * DO NOT MODIFY 197 * 198 * This file was generated by dtoc from a .dtb (device tree binary) file. 199 */ 200 201''') 202 203 def get_phandle_argc(self, prop, node_name): 204 """Check if a node contains phandles 205 206 We have no reliable way of detecting whether a node uses a phandle 207 or not. As an interim measure, use a list of known property names. 208 209 Args: 210 prop: Prop object to check 211 Return: 212 Number of argument cells is this is a phandle, else None 213 """ 214 if prop.name in ['clocks']: 215 val = prop.value 216 if not isinstance(val, list): 217 val = [val] 218 i = 0 219 220 max_args = 0 221 args = [] 222 while i < len(val): 223 phandle = fdt_util.fdt32_to_cpu(val[i]) 224 target = self._fdt.phandle_to_node.get(phandle) 225 if not target: 226 raise ValueError("Cannot parse '%s' in node '%s'" % 227 (prop.name, node_name)) 228 prop_name = '#clock-cells' 229 cells = target.props.get(prop_name) 230 if not cells: 231 raise ValueError("Node '%s' has no '%s' property" % 232 (target.name, prop_name)) 233 num_args = fdt_util.fdt32_to_cpu(cells.value) 234 max_args = max(max_args, num_args) 235 args.append(num_args) 236 i += 1 + num_args 237 return PhandleInfo(max_args, args) 238 return None 239 240 def scan_dtb(self): 241 """Scan the device tree to obtain a tree of nodes and properties 242 243 Once this is done, self._fdt.GetRoot() can be called to obtain the 244 device tree root node, and progress from there. 245 """ 246 self._fdt = fdt.FdtScan(self._dtb_fname) 247 248 def scan_node(self, root): 249 """Scan a node and subnodes to build a tree of node and phandle info 250 251 This adds each node to self._valid_nodes. 252 253 Args: 254 root: Root node for scan 255 """ 256 for node in root.subnodes: 257 if 'compatible' in node.props: 258 status = node.props.get('status') 259 if (not self._include_disabled and not status or 260 status.value != 'disabled'): 261 self._valid_nodes.append(node) 262 263 # recurse to handle any subnodes 264 self.scan_node(node) 265 266 def scan_tree(self): 267 """Scan the device tree for useful information 268 269 This fills in the following properties: 270 _valid_nodes: A list of nodes we wish to consider include in the 271 platform data 272 """ 273 self._valid_nodes = [] 274 return self.scan_node(self._fdt.GetRoot()) 275 276 @staticmethod 277 def get_num_cells(node): 278 """Get the number of cells in addresses and sizes for this node 279 280 Args: 281 node: Node to check 282 283 Returns: 284 Tuple: 285 Number of address cells for this node 286 Number of size cells for this node 287 """ 288 parent = node.parent 289 na, ns = 2, 2 290 if parent: 291 na_prop = parent.props.get('#address-cells') 292 ns_prop = parent.props.get('#size-cells') 293 if na_prop: 294 na = fdt_util.fdt32_to_cpu(na_prop.value) 295 if ns_prop: 296 ns = fdt_util.fdt32_to_cpu(ns_prop.value) 297 return na, ns 298 299 def scan_reg_sizes(self): 300 """Scan for 64-bit 'reg' properties and update the values 301 302 This finds 'reg' properties with 64-bit data and converts the value to 303 an array of 64-values. This allows it to be output in a way that the 304 C code can read. 305 """ 306 for node in self._valid_nodes: 307 reg = node.props.get('reg') 308 if not reg: 309 continue 310 na, ns = self.get_num_cells(node) 311 total = na + ns 312 313 if reg.type != fdt.TYPE_INT: 314 raise ValueError("Node '%s' reg property is not an int") 315 if len(reg.value) % total: 316 raise ValueError("Node '%s' reg property has %d cells " 317 'which is not a multiple of na + ns = %d + %d)' % 318 (node.name, len(reg.value), na, ns)) 319 reg.na = na 320 reg.ns = ns 321 if na != 1 or ns != 1: 322 reg.type = fdt.TYPE_INT64 323 i = 0 324 new_value = [] 325 val = reg.value 326 if not isinstance(val, list): 327 val = [val] 328 while i < len(val): 329 addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na) 330 i += na 331 size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns) 332 i += ns 333 new_value += [addr, size] 334 reg.value = new_value 335 336 def scan_structs(self): 337 """Scan the device tree building up the C structures we will use. 338 339 Build a dict keyed by C struct name containing a dict of Prop 340 object for each struct field (keyed by property name). Where the 341 same struct appears multiple times, try to use the 'widest' 342 property, i.e. the one with a type which can express all others. 343 344 Once the widest property is determined, all other properties are 345 updated to match that width. 346 """ 347 structs = {} 348 for node in self._valid_nodes: 349 node_name, _ = get_compat_name(node) 350 fields = {} 351 352 # Get a list of all the valid properties in this node. 353 for name, prop in node.props.items(): 354 if name not in PROP_IGNORE_LIST and name[0] != '#': 355 fields[name] = copy.deepcopy(prop) 356 357 # If we've seen this node_name before, update the existing struct. 358 if node_name in structs: 359 struct = structs[node_name] 360 for name, prop in fields.items(): 361 oldprop = struct.get(name) 362 if oldprop: 363 oldprop.Widen(prop) 364 else: 365 struct[name] = prop 366 367 # Otherwise store this as a new struct. 368 else: 369 structs[node_name] = fields 370 371 upto = 0 372 for node in self._valid_nodes: 373 node_name, _ = get_compat_name(node) 374 struct = structs[node_name] 375 for name, prop in node.props.items(): 376 if name not in PROP_IGNORE_LIST and name[0] != '#': 377 prop.Widen(struct[name]) 378 upto += 1 379 380 struct_name, aliases = get_compat_name(node) 381 for alias in aliases: 382 self._aliases[alias] = struct_name 383 384 return structs 385 386 def scan_phandles(self): 387 """Figure out what phandles each node uses 388 389 We need to be careful when outputing nodes that use phandles since 390 they must come after the declaration of the phandles in the C file. 391 Otherwise we get a compiler error since the phandle struct is not yet 392 declared. 393 394 This function adds to each node a list of phandle nodes that the node 395 depends on. This allows us to output things in the right order. 396 """ 397 for node in self._valid_nodes: 398 node.phandles = set() 399 for pname, prop in node.props.items(): 400 if pname in PROP_IGNORE_LIST or pname[0] == '#': 401 continue 402 info = self.get_phandle_argc(prop, node.name) 403 if info: 404 if not isinstance(prop.value, list): 405 prop.value = [prop.value] 406 # Process the list as pairs of (phandle, id) 407 pos = 0 408 for args in info.args: 409 phandle_cell = prop.value[pos] 410 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 411 target_node = self._fdt.phandle_to_node[phandle] 412 node.phandles.add(target_node) 413 pos += 1 + args 414 415 416 def generate_structs(self, structs): 417 """Generate struct defintions for the platform data 418 419 This writes out the body of a header file consisting of structure 420 definitions for node in self._valid_nodes. See the documentation in 421 README.of-plat for more information. 422 """ 423 self.out_header() 424 self.out('#include <stdbool.h>\n') 425 self.out('#include <libfdt.h>\n') 426 427 # Output the struct definition 428 for name in sorted(structs): 429 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name)) 430 for pname in sorted(structs[name]): 431 prop = structs[name][pname] 432 info = self.get_phandle_argc(prop, structs[name]) 433 if info: 434 # For phandles, include a reference to the target 435 struct_name = 'struct phandle_%d_arg' % info.max_args 436 self.out('\t%s%s[%d]' % (tab_to(2, struct_name), 437 conv_name_to_c(prop.name), 438 len(info.args))) 439 else: 440 ptype = TYPE_NAMES[prop.type] 441 self.out('\t%s%s' % (tab_to(2, ptype), 442 conv_name_to_c(prop.name))) 443 if isinstance(prop.value, list): 444 self.out('[%d]' % len(prop.value)) 445 self.out(';\n') 446 self.out('};\n') 447 448 for alias, struct_name in self._aliases.iteritems(): 449 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias, 450 STRUCT_PREFIX, struct_name)) 451 452 def output_node(self, node): 453 """Output the C code for a node 454 455 Args: 456 node: node to output 457 """ 458 struct_name, _ = get_compat_name(node) 459 var_name = conv_name_to_c(node.name) 460 self.buf('static struct %s%s %s%s = {\n' % 461 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) 462 for pname, prop in node.props.items(): 463 if pname in PROP_IGNORE_LIST or pname[0] == '#': 464 continue 465 member_name = conv_name_to_c(prop.name) 466 self.buf('\t%s= ' % tab_to(3, '.' + member_name)) 467 468 # Special handling for lists 469 if isinstance(prop.value, list): 470 self.buf('{') 471 vals = [] 472 # For phandles, output a reference to the platform data 473 # of the target node. 474 info = self.get_phandle_argc(prop, node.name) 475 if info: 476 # Process the list as pairs of (phandle, id) 477 pos = 0 478 for args in info.args: 479 phandle_cell = prop.value[pos] 480 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 481 target_node = self._fdt.phandle_to_node[phandle] 482 name = conv_name_to_c(target_node.name) 483 arg_values = [] 484 for i in range(args): 485 arg_values.append(str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i]))) 486 pos += 1 + args 487 vals.append('\t{&%s%s, {%s}}' % (VAL_PREFIX, name, 488 ', '.join(arg_values))) 489 for val in vals: 490 self.buf('\n\t\t%s,' % val) 491 else: 492 for val in prop.value: 493 vals.append(get_value(prop.type, val)) 494 495 # Put 8 values per line to avoid very long lines. 496 for i in xrange(0, len(vals), 8): 497 if i: 498 self.buf(',\n\t\t') 499 self.buf(', '.join(vals[i:i + 8])) 500 self.buf('}') 501 else: 502 self.buf(get_value(prop.type, prop.value)) 503 self.buf(',\n') 504 self.buf('};\n') 505 506 # Add a device declaration 507 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name) 508 self.buf('\t.name\t\t= "%s",\n' % struct_name) 509 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name)) 510 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name)) 511 self.buf('};\n') 512 self.buf('\n') 513 514 self.out(''.join(self.get_buf())) 515 516 def generate_tables(self): 517 """Generate device defintions for the platform data 518 519 This writes out C platform data initialisation data and 520 U_BOOT_DEVICE() declarations for each valid node. Where a node has 521 multiple compatible strings, a #define is used to make them equivalent. 522 523 See the documentation in doc/driver-model/of-plat.txt for more 524 information. 525 """ 526 self.out_header() 527 self.out('#include <common.h>\n') 528 self.out('#include <dm.h>\n') 529 self.out('#include <dt-structs.h>\n') 530 self.out('\n') 531 nodes_to_output = list(self._valid_nodes) 532 533 # Keep outputing nodes until there is none left 534 while nodes_to_output: 535 node = nodes_to_output[0] 536 # Output all the node's dependencies first 537 for req_node in node.phandles: 538 if req_node in nodes_to_output: 539 self.output_node(req_node) 540 nodes_to_output.remove(req_node) 541 self.output_node(node) 542 nodes_to_output.remove(node) 543 544 545def run_steps(args, dtb_file, include_disabled, output): 546 """Run all the steps of the dtoc tool 547 548 Args: 549 args: List of non-option arguments provided to the problem 550 dtb_file: Filename of dtb file to process 551 include_disabled: True to include disabled nodes 552 output: Name of output file 553 """ 554 if not args: 555 raise ValueError('Please specify a command: struct, platdata') 556 557 plat = DtbPlatdata(dtb_file, include_disabled) 558 plat.scan_dtb() 559 plat.scan_tree() 560 plat.scan_reg_sizes() 561 plat.setup_output(output) 562 structs = plat.scan_structs() 563 plat.scan_phandles() 564 565 for cmd in args[0].split(','): 566 if cmd == 'struct': 567 plat.generate_structs(structs) 568 elif cmd == 'platdata': 569 plat.generate_tables() 570 else: 571 raise ValueError("Unknown command '%s': (use: struct, platdata)" % 572 cmd) 573