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 119def is_phandle(prop): 120 """Check if a node contains phandles 121 122 We have no reliable way of detecting whether a node uses a phandle 123 or not. As an interim measure, use a list of known property names. 124 125 Args: 126 prop: Prop object to check 127 Return: 128 True if the object value contains phandles, else False 129 """ 130 if prop.name in ['clocks']: 131 return True 132 return False 133 134 135class DtbPlatdata(object): 136 """Provide a means to convert device tree binary data to platform data 137 138 The output of this process is C structures which can be used in space- 139 constrained encvironments where the ~3KB code overhead of device tree 140 code is not affordable. 141 142 Properties: 143 _fdt: Fdt object, referencing the device tree 144 _dtb_fname: Filename of the input device tree binary file 145 _valid_nodes: A list of Node object with compatible strings 146 _include_disabled: true to include nodes marked status = "disabled" 147 _phandle_nodes: A dict of nodes indexed by phandle number (1, 2...) 148 _outfile: The current output file (sys.stdout or a real file) 149 _lines: Stashed list of output lines for outputting in the future 150 _phandle_nodes: A dict of Nodes indexed by phandle (an integer) 151 """ 152 def __init__(self, dtb_fname, include_disabled): 153 self._fdt = None 154 self._dtb_fname = dtb_fname 155 self._valid_nodes = None 156 self._include_disabled = include_disabled 157 self._phandle_nodes = {} 158 self._outfile = None 159 self._lines = [] 160 self._aliases = {} 161 162 def setup_output(self, fname): 163 """Set up the output destination 164 165 Once this is done, future calls to self.out() will output to this 166 file. 167 168 Args: 169 fname: Filename to send output to, or '-' for stdout 170 """ 171 if fname == '-': 172 self._outfile = sys.stdout 173 else: 174 self._outfile = open(fname, 'w') 175 176 def out(self, line): 177 """Output a string to the output file 178 179 Args: 180 line: String to output 181 """ 182 self._outfile.write(line) 183 184 def buf(self, line): 185 """Buffer up a string to send later 186 187 Args: 188 line: String to add to our 'buffer' list 189 """ 190 self._lines.append(line) 191 192 def get_buf(self): 193 """Get the contents of the output buffer, and clear it 194 195 Returns: 196 The output buffer, which is then cleared for future use 197 """ 198 lines = self._lines 199 self._lines = [] 200 return lines 201 202 def scan_dtb(self): 203 """Scan the device tree to obtain a tree of nodes and properties 204 205 Once this is done, self._fdt.GetRoot() can be called to obtain the 206 device tree root node, and progress from there. 207 """ 208 self._fdt = fdt.FdtScan(self._dtb_fname) 209 210 def scan_node(self, root): 211 """Scan a node and subnodes to build a tree of node and phandle info 212 213 This adds each node to self._valid_nodes and each phandle to 214 self._phandle_nodes. 215 216 Args: 217 root: Root node for scan 218 """ 219 for node in root.subnodes: 220 if 'compatible' in node.props: 221 status = node.props.get('status') 222 if (not self._include_disabled and not status or 223 status.value != 'disabled'): 224 self._valid_nodes.append(node) 225 phandle_prop = node.props.get('phandle') 226 if phandle_prop: 227 phandle = phandle_prop.GetPhandle() 228 self._phandle_nodes[phandle] = node 229 230 # recurse to handle any subnodes 231 self.scan_node(node) 232 233 def scan_tree(self): 234 """Scan the device tree for useful information 235 236 This fills in the following properties: 237 _phandle_nodes: A dict of Nodes indexed by phandle (an integer) 238 _valid_nodes: A list of nodes we wish to consider include in the 239 platform data 240 """ 241 self._phandle_nodes = {} 242 self._valid_nodes = [] 243 return self.scan_node(self._fdt.GetRoot()) 244 245 def scan_structs(self): 246 """Scan the device tree building up the C structures we will use. 247 248 Build a dict keyed by C struct name containing a dict of Prop 249 object for each struct field (keyed by property name). Where the 250 same struct appears multiple times, try to use the 'widest' 251 property, i.e. the one with a type which can express all others. 252 253 Once the widest property is determined, all other properties are 254 updated to match that width. 255 """ 256 structs = {} 257 for node in self._valid_nodes: 258 node_name, _ = get_compat_name(node) 259 fields = {} 260 261 # Get a list of all the valid properties in this node. 262 for name, prop in node.props.items(): 263 if name not in PROP_IGNORE_LIST and name[0] != '#': 264 fields[name] = copy.deepcopy(prop) 265 266 # If we've seen this node_name before, update the existing struct. 267 if node_name in structs: 268 struct = structs[node_name] 269 for name, prop in fields.items(): 270 oldprop = struct.get(name) 271 if oldprop: 272 oldprop.Widen(prop) 273 else: 274 struct[name] = prop 275 276 # Otherwise store this as a new struct. 277 else: 278 structs[node_name] = fields 279 280 upto = 0 281 for node in self._valid_nodes: 282 node_name, _ = get_compat_name(node) 283 struct = structs[node_name] 284 for name, prop in node.props.items(): 285 if name not in PROP_IGNORE_LIST and name[0] != '#': 286 prop.Widen(struct[name]) 287 upto += 1 288 289 struct_name, aliases = get_compat_name(node) 290 for alias in aliases: 291 self._aliases[alias] = struct_name 292 293 return structs 294 295 def scan_phandles(self): 296 """Figure out what phandles each node uses 297 298 We need to be careful when outputing nodes that use phandles since 299 they must come after the declaration of the phandles in the C file. 300 Otherwise we get a compiler error since the phandle struct is not yet 301 declared. 302 303 This function adds to each node a list of phandle nodes that the node 304 depends on. This allows us to output things in the right order. 305 """ 306 for node in self._valid_nodes: 307 node.phandles = set() 308 for pname, prop in node.props.items(): 309 if pname in PROP_IGNORE_LIST or pname[0] == '#': 310 continue 311 if isinstance(prop.value, list): 312 if is_phandle(prop): 313 # Process the list as pairs of (phandle, id) 314 value_it = iter(prop.value) 315 for phandle_cell, _ in zip(value_it, value_it): 316 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 317 target_node = self._phandle_nodes[phandle] 318 node.phandles.add(target_node) 319 320 321 def generate_structs(self, structs): 322 """Generate struct defintions for the platform data 323 324 This writes out the body of a header file consisting of structure 325 definitions for node in self._valid_nodes. See the documentation in 326 README.of-plat for more information. 327 """ 328 self.out('#include <stdbool.h>\n') 329 self.out('#include <libfdt.h>\n') 330 331 # Output the struct definition 332 for name in sorted(structs): 333 self.out('struct %s%s {\n' % (STRUCT_PREFIX, name)) 334 for pname in sorted(structs[name]): 335 prop = structs[name][pname] 336 if is_phandle(prop): 337 # For phandles, include a reference to the target 338 self.out('\t%s%s[%d]' % (tab_to(2, 'struct phandle_2_cell'), 339 conv_name_to_c(prop.name), 340 len(prop.value) / 2)) 341 else: 342 ptype = TYPE_NAMES[prop.type] 343 self.out('\t%s%s' % (tab_to(2, ptype), 344 conv_name_to_c(prop.name))) 345 if isinstance(prop.value, list): 346 self.out('[%d]' % len(prop.value)) 347 self.out(';\n') 348 self.out('};\n') 349 350 for alias, struct_name in self._aliases.iteritems(): 351 self.out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias, 352 STRUCT_PREFIX, struct_name)) 353 354 def output_node(self, node): 355 """Output the C code for a node 356 357 Args: 358 node: node to output 359 """ 360 struct_name, _ = get_compat_name(node) 361 var_name = conv_name_to_c(node.name) 362 self.buf('static struct %s%s %s%s = {\n' % 363 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) 364 for pname, prop in node.props.items(): 365 if pname in PROP_IGNORE_LIST or pname[0] == '#': 366 continue 367 member_name = conv_name_to_c(prop.name) 368 self.buf('\t%s= ' % tab_to(3, '.' + member_name)) 369 370 # Special handling for lists 371 if isinstance(prop.value, list): 372 self.buf('{') 373 vals = [] 374 # For phandles, output a reference to the platform data 375 # of the target node. 376 if is_phandle(prop): 377 # Process the list as pairs of (phandle, id) 378 value_it = iter(prop.value) 379 for phandle_cell, id_cell in zip(value_it, value_it): 380 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 381 id_num = fdt_util.fdt32_to_cpu(id_cell) 382 target_node = self._phandle_nodes[phandle] 383 name = conv_name_to_c(target_node.name) 384 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id_num)) 385 else: 386 for val in prop.value: 387 vals.append(get_value(prop.type, val)) 388 389 # Put 8 values per line to avoid very long lines. 390 for i in xrange(0, len(vals), 8): 391 if i: 392 self.buf(',\n\t\t') 393 self.buf(', '.join(vals[i:i + 8])) 394 self.buf('}') 395 else: 396 self.buf(get_value(prop.type, prop.value)) 397 self.buf(',\n') 398 self.buf('};\n') 399 400 # Add a device declaration 401 self.buf('U_BOOT_DEVICE(%s) = {\n' % var_name) 402 self.buf('\t.name\t\t= "%s",\n' % struct_name) 403 self.buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name)) 404 self.buf('\t.platdata_size\t= sizeof(%s%s),\n' % (VAL_PREFIX, var_name)) 405 self.buf('};\n') 406 self.buf('\n') 407 408 self.out(''.join(self.get_buf())) 409 410 def generate_tables(self): 411 """Generate device defintions for the platform data 412 413 This writes out C platform data initialisation data and 414 U_BOOT_DEVICE() declarations for each valid node. Where a node has 415 multiple compatible strings, a #define is used to make them equivalent. 416 417 See the documentation in doc/driver-model/of-plat.txt for more 418 information. 419 """ 420 self.out('#include <common.h>\n') 421 self.out('#include <dm.h>\n') 422 self.out('#include <dt-structs.h>\n') 423 self.out('\n') 424 nodes_to_output = list(self._valid_nodes) 425 426 # Keep outputing nodes until there is none left 427 while nodes_to_output: 428 node = nodes_to_output[0] 429 # Output all the node's dependencies first 430 for req_node in node.phandles: 431 if req_node in nodes_to_output: 432 self.output_node(req_node) 433 nodes_to_output.remove(req_node) 434 self.output_node(node) 435 nodes_to_output.remove(node) 436 437 438def run_steps(args, dtb_file, include_disabled, output): 439 """Run all the steps of the dtoc tool 440 441 Args: 442 args: List of non-option arguments provided to the problem 443 dtb_file: Filename of dtb file to process 444 include_disabled: True to include disabled nodes 445 output: Name of output file 446 """ 447 if not args: 448 raise ValueError('Please specify a command: struct, platdata') 449 450 plat = DtbPlatdata(dtb_file, include_disabled) 451 plat.scan_dtb() 452 plat.scan_tree() 453 plat.setup_output(output) 454 structs = plat.scan_structs() 455 plat.scan_phandles() 456 457 for cmd in args[0].split(','): 458 if cmd == 'struct': 459 plat.generate_structs(structs) 460 elif cmd == 'platdata': 461 plat.generate_tables() 462 else: 463 raise ValueError("Unknown command '%s': (use: struct, platdata)" % 464 cmd) 465