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