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