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