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