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