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 20import tools 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' % tools.ToByte(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 if not isinstance(prop.value, list): 216 prop.value = [prop.value] 217 val = prop.value 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 # If we get to the end of the list, stop. This can happen 225 # since some nodes have more phandles in the list than others, 226 # but we allocate enough space for the largest list. So those 227 # nodes with shorter lists end up with zeroes at the end. 228 if not phandle: 229 break 230 target = self._fdt.phandle_to_node.get(phandle) 231 if not target: 232 raise ValueError("Cannot parse '%s' in node '%s'" % 233 (prop.name, node_name)) 234 prop_name = '#clock-cells' 235 cells = target.props.get(prop_name) 236 if not cells: 237 raise ValueError("Node '%s' has no '%s' property" % 238 (target.name, prop_name)) 239 num_args = fdt_util.fdt32_to_cpu(cells.value) 240 max_args = max(max_args, num_args) 241 args.append(num_args) 242 i += 1 + num_args 243 return PhandleInfo(max_args, args) 244 return None 245 246 def scan_dtb(self): 247 """Scan the device tree to obtain a tree of nodes and properties 248 249 Once this is done, self._fdt.GetRoot() can be called to obtain the 250 device tree root node, and progress from there. 251 """ 252 self._fdt = fdt.FdtScan(self._dtb_fname) 253 254 def scan_node(self, root): 255 """Scan a node and subnodes to build a tree of node and phandle info 256 257 This adds each node to self._valid_nodes. 258 259 Args: 260 root: Root node for scan 261 """ 262 for node in root.subnodes: 263 if 'compatible' in node.props: 264 status = node.props.get('status') 265 if (not self._include_disabled and not status or 266 status.value != 'disabled'): 267 self._valid_nodes.append(node) 268 269 # recurse to handle any subnodes 270 self.scan_node(node) 271 272 def scan_tree(self): 273 """Scan the device tree for useful information 274 275 This fills in the following properties: 276 _valid_nodes: A list of nodes we wish to consider include in the 277 platform data 278 """ 279 self._valid_nodes = [] 280 return self.scan_node(self._fdt.GetRoot()) 281 282 @staticmethod 283 def get_num_cells(node): 284 """Get the number of cells in addresses and sizes for this node 285 286 Args: 287 node: Node to check 288 289 Returns: 290 Tuple: 291 Number of address cells for this node 292 Number of size cells for this node 293 """ 294 parent = node.parent 295 na, ns = 2, 2 296 if parent: 297 na_prop = parent.props.get('#address-cells') 298 ns_prop = parent.props.get('#size-cells') 299 if na_prop: 300 na = fdt_util.fdt32_to_cpu(na_prop.value) 301 if ns_prop: 302 ns = fdt_util.fdt32_to_cpu(ns_prop.value) 303 return na, ns 304 305 def scan_reg_sizes(self): 306 """Scan for 64-bit 'reg' properties and update the values 307 308 This finds 'reg' properties with 64-bit data and converts the value to 309 an array of 64-values. This allows it to be output in a way that the 310 C code can read. 311 """ 312 for node in self._valid_nodes: 313 reg = node.props.get('reg') 314 if not reg: 315 continue 316 na, ns = self.get_num_cells(node) 317 total = na + ns 318 319 if reg.type != fdt.TYPE_INT: 320 raise ValueError("Node '%s' reg property is not an int" % 321 node.name) 322 if len(reg.value) % total: 323 raise ValueError("Node '%s' reg property has %d cells " 324 'which is not a multiple of na + ns = %d + %d)' % 325 (node.name, len(reg.value), na, ns)) 326 reg.na = na 327 reg.ns = ns 328 if na != 1 or ns != 1: 329 reg.type = fdt.TYPE_INT64 330 i = 0 331 new_value = [] 332 val = reg.value 333 if not isinstance(val, list): 334 val = [val] 335 while i < len(val): 336 addr = fdt_util.fdt_cells_to_cpu(val[i:], reg.na) 337 i += na 338 size = fdt_util.fdt_cells_to_cpu(val[i:], reg.ns) 339 i += ns 340 new_value += [addr, size] 341 reg.value = new_value 342 343 def scan_structs(self): 344 """Scan the device tree building up the C structures we will use. 345 346 Build a dict keyed by C struct name containing a dict of Prop 347 object for each struct field (keyed by property name). Where the 348 same struct appears multiple times, try to use the 'widest' 349 property, i.e. the one with a type which can express all others. 350 351 Once the widest property is determined, all other properties are 352 updated to match that width. 353 """ 354 structs = {} 355 for node in self._valid_nodes: 356 node_name, _ = get_compat_name(node) 357 fields = {} 358 359 # Get a list of all the valid properties in this node. 360 for name, prop in node.props.items(): 361 if name not in PROP_IGNORE_LIST and name[0] != '#': 362 fields[name] = copy.deepcopy(prop) 363 364 # If we've seen this node_name before, update the existing struct. 365 if node_name in structs: 366 struct = structs[node_name] 367 for name, prop in fields.items(): 368 oldprop = struct.get(name) 369 if oldprop: 370 oldprop.Widen(prop) 371 else: 372 struct[name] = prop 373 374 # Otherwise store this as a new struct. 375 else: 376 structs[node_name] = fields 377 378 upto = 0 379 for node in self._valid_nodes: 380 node_name, _ = get_compat_name(node) 381 struct = structs[node_name] 382 for name, prop in node.props.items(): 383 if name not in PROP_IGNORE_LIST and name[0] != '#': 384 prop.Widen(struct[name]) 385 upto += 1 386 387 struct_name, aliases = get_compat_name(node) 388 for alias in aliases: 389 self._aliases[alias] = struct_name 390 391 return structs 392 393 def scan_phandles(self): 394 """Figure out what phandles each node uses 395 396 We need to be careful when outputing nodes that use phandles since 397 they must come after the declaration of the phandles in the C file. 398 Otherwise we get a compiler error since the phandle struct is not yet 399 declared. 400 401 This function adds to each node a list of phandle nodes that the node 402 depends on. This allows us to output things in the right order. 403 """ 404 for node in self._valid_nodes: 405 node.phandles = set() 406 for pname, prop in node.props.items(): 407 if pname in PROP_IGNORE_LIST or pname[0] == '#': 408 continue 409 info = self.get_phandle_argc(prop, node.name) 410 if info: 411 # Process the list as pairs of (phandle, id) 412 pos = 0 413 for args in info.args: 414 phandle_cell = prop.value[pos] 415 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 416 target_node = self._fdt.phandle_to_node[phandle] 417 node.phandles.add(target_node) 418 pos += 1 + args 419 420 421 def generate_structs(self, structs): 422 """Generate struct defintions for the platform data 423 424 This writes out the body of a header file consisting of structure 425 definitions for node in self._valid_nodes. See the documentation in 426 README.of-plat for more information. 427 """ 428 self.out_header() 429 self.out('#include <stdbool.h>\n') 430 self.out('#include <linux/libfdt.h>\n') 431 432 # Output the struct definition 433 for name in sorted(structs): 434 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name)) 435 for pname in sorted(structs[name]): 436 prop = structs[name][pname] 437 info = self.get_phandle_argc(prop, structs[name]) 438 if info: 439 # For phandles, include a reference to the target 440 struct_name = 'struct phandle_%d_arg' % info.max_args 441 self.out('\t%s%s[%d]' % (tab_to(2, struct_name), 442 conv_name_to_c(prop.name), 443 len(info.args))) 444 else: 445 ptype = TYPE_NAMES[prop.type] 446 self.out('\t%s%s' % (tab_to(2, ptype), 447 conv_name_to_c(prop.name))) 448 if isinstance(prop.value, list): 449 self.out('[%d]' % len(prop.value)) 450 self.out(';\n') 451 self.out('};\n') 452 453 for alias, struct_name in self._aliases.items(): 454 if alias not in sorted(structs): 455 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias, 456 STRUCT_PREFIX, struct_name)) 457 458 def output_node(self, node): 459 """Output the C code for a node 460 461 Args: 462 node: node to output 463 """ 464 struct_name, _ = get_compat_name(node) 465 var_name = conv_name_to_c(node.name) 466 self.buf('static const struct %s%s %s%s = {\n' % 467 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) 468 for pname in sorted(node.props): 469 prop = node.props[pname] 470 if pname in PROP_IGNORE_LIST or pname[0] == '#': 471 continue 472 member_name = conv_name_to_c(prop.name) 473 self.buf('\t%s= ' % tab_to(3, '.' + member_name)) 474 475 # Special handling for lists 476 if isinstance(prop.value, list): 477 self.buf('{') 478 vals = [] 479 # For phandles, output a reference to the platform data 480 # of the target node. 481 info = self.get_phandle_argc(prop, node.name) 482 if info: 483 # Process the list as pairs of (phandle, id) 484 pos = 0 485 for args in info.args: 486 phandle_cell = prop.value[pos] 487 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 488 target_node = self._fdt.phandle_to_node[phandle] 489 name = conv_name_to_c(target_node.name) 490 arg_values = [] 491 for i in range(args): 492 arg_values.append(str(fdt_util.fdt32_to_cpu(prop.value[pos + 1 + i]))) 493 pos += 1 + args 494 vals.append('\t{&%s%s, {%s}}' % (VAL_PREFIX, name, 495 ', '.join(arg_values))) 496 for val in vals: 497 self.buf('\n\t\t%s,' % val) 498 else: 499 for val in prop.value: 500 vals.append(get_value(prop.type, val)) 501 502 # Put 8 values per line to avoid very long lines. 503 for i in range(0, len(vals), 8): 504 if i: 505 self.buf(',\n\t\t') 506 self.buf(', '.join(vals[i:i + 8])) 507 self.buf('}') 508 else: 509 self.buf(get_value(prop.type, prop.value)) 510 self.buf(',\n') 511 self.buf('};\n') 512 513 # Add a device declaration 514 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name) 515 self.buf('\t.name\t\t= "%s",\n' % struct_name) 516 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name)) 517 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name)) 518 self.buf('};\n') 519 self.buf('\n') 520 521 self.out(''.join(self.get_buf())) 522 523 def generate_tables(self): 524 """Generate device defintions for the platform data 525 526 This writes out C platform data initialisation data and 527 U_BOOT_DEVICE() declarations for each valid node. Where a node has 528 multiple compatible strings, a #define is used to make them equivalent. 529 530 See the documentation in doc/driver-model/of-plat.txt for more 531 information. 532 """ 533 self.out_header() 534 self.out('#include <common.h>\n') 535 self.out('#include <dm.h>\n') 536 self.out('#include <dt-structs.h>\n') 537 self.out('\n') 538 nodes_to_output = list(self._valid_nodes) 539 540 # Keep outputing nodes until there is none left 541 while nodes_to_output: 542 node = nodes_to_output[0] 543 # Output all the node's dependencies first 544 for req_node in node.phandles: 545 if req_node in nodes_to_output: 546 self.output_node(req_node) 547 nodes_to_output.remove(req_node) 548 self.output_node(node) 549 nodes_to_output.remove(node) 550 551 552def run_steps(args, dtb_file, include_disabled, output): 553 """Run all the steps of the dtoc tool 554 555 Args: 556 args: List of non-option arguments provided to the problem 557 dtb_file: Filename of dtb file to process 558 include_disabled: True to include disabled nodes 559 output: Name of output file 560 """ 561 if not args: 562 raise ValueError('Please specify a command: struct, platdata') 563 564 plat = DtbPlatdata(dtb_file, include_disabled) 565 plat.scan_dtb() 566 plat.scan_tree() 567 plat.scan_reg_sizes() 568 plat.setup_output(output) 569 structs = plat.scan_structs() 570 plat.scan_phandles() 571 572 for cmd in args[0].split(','): 573 if cmd == 'struct': 574 plat.generate_structs(structs) 575 elif cmd == 'platdata': 576 plat.generate_tables() 577 else: 578 raise ValueError("Unknown command '%s': (use: struct, platdata)" % 579 cmd) 580