1#!/usr/bin/python 2# 3# Copyright (C) 2016 Google, Inc 4# Written by Simon Glass <sjg@chromium.org> 5# 6# SPDX-License-Identifier: GPL-2.0+ 7# 8 9"""Device tree to C tool 10 11This tool converts a device tree binary file (.dtb) into two C files. The 12indent is to allow a C program to access data from the device tree without 13having to link against libfdt. By putting the data from the device tree into 14C structures, normal C code can be used. This helps to reduce the size of the 15compiled program. 16 17Dtoc produces two output files: 18 19 dt-structs.h - contains struct definitions 20 dt-platdata.c - contains data from the device tree using the struct 21 definitions, as well as U-Boot driver definitions. 22 23This tool is used in U-Boot to provide device tree data to SPL without 24increasing the code size of SPL. This supports the CONFIG_SPL_OF_PLATDATA 25options. For more information about the use of this options and tool please 26see doc/driver-model/of-plat.txt 27""" 28 29import copy 30from optparse import OptionError, OptionParser 31import os 32import struct 33import sys 34 35# Bring in the patman libraries 36our_path = os.path.dirname(os.path.realpath(__file__)) 37sys.path.append(os.path.join(our_path, '../patman')) 38 39import fdt 40import fdt_util 41 42# When we see these properties we ignore them - i.e. do not create a structure member 43PROP_IGNORE_LIST = [ 44 '#address-cells', 45 '#gpio-cells', 46 '#size-cells', 47 'compatible', 48 'linux,phandle', 49 "status", 50 'phandle', 51 'u-boot,dm-pre-reloc', 52 'u-boot,dm-tpl', 53 'u-boot,dm-spl', 54] 55 56# C type declarations for the tyues we support 57TYPE_NAMES = { 58 fdt.TYPE_INT: 'fdt32_t', 59 fdt.TYPE_BYTE: 'unsigned char', 60 fdt.TYPE_STRING: 'const char *', 61 fdt.TYPE_BOOL: 'bool', 62}; 63 64STRUCT_PREFIX = 'dtd_' 65VAL_PREFIX = 'dtv_' 66 67def Conv_name_to_c(name): 68 """Convert a device-tree name to a C identifier 69 70 Args: 71 name: Name to convert 72 Return: 73 String containing the C version of this name 74 """ 75 str = name.replace('@', '_at_') 76 str = str.replace('-', '_') 77 str = str.replace(',', '_') 78 str = str.replace('.', '_') 79 str = str.replace('/', '__') 80 return str 81 82def TabTo(num_tabs, str): 83 if len(str) >= num_tabs * 8: 84 return str + ' ' 85 return str + '\t' * (num_tabs - len(str) // 8) 86 87class DtbPlatdata: 88 """Provide a means to convert device tree binary data to platform data 89 90 The output of this process is C structures which can be used in space- 91 constrained encvironments where the ~3KB code overhead of device tree 92 code is not affordable. 93 94 Properties: 95 fdt: Fdt object, referencing the device tree 96 _dtb_fname: Filename of the input device tree binary file 97 _valid_nodes: A list of Node object with compatible strings 98 _options: Command-line options 99 _phandle_node: A dict of nodes indexed by phandle number (1, 2...) 100 _outfile: The current output file (sys.stdout or a real file) 101 _lines: Stashed list of output lines for outputting in the future 102 _phandle_node: A dict of Nodes indexed by phandle (an integer) 103 """ 104 def __init__(self, dtb_fname, options): 105 self._dtb_fname = dtb_fname 106 self._valid_nodes = None 107 self._options = options 108 self._phandle_node = {} 109 self._outfile = None 110 self._lines = [] 111 self._aliases = {} 112 113 def SetupOutput(self, fname): 114 """Set up the output destination 115 116 Once this is done, future calls to self.Out() will output to this 117 file. 118 119 Args: 120 fname: Filename to send output to, or '-' for stdout 121 """ 122 if fname == '-': 123 self._outfile = sys.stdout 124 else: 125 self._outfile = open(fname, 'w') 126 127 def Out(self, str): 128 """Output a string to the output file 129 130 Args: 131 str: String to output 132 """ 133 self._outfile.write(str) 134 135 def Buf(self, str): 136 """Buffer up a string to send later 137 138 Args: 139 str: String to add to our 'buffer' list 140 """ 141 self._lines.append(str) 142 143 def GetBuf(self): 144 """Get the contents of the output buffer, and clear it 145 146 Returns: 147 The output buffer, which is then cleared for future use 148 """ 149 lines = self._lines 150 self._lines = [] 151 return lines 152 153 def GetValue(self, type, value): 154 """Get a value as a C expression 155 156 For integers this returns a byte-swapped (little-endian) hex string 157 For bytes this returns a hex string, e.g. 0x12 158 For strings this returns a literal string enclosed in quotes 159 For booleans this return 'true' 160 161 Args: 162 type: Data type (fdt_util) 163 value: Data value, as a string of bytes 164 """ 165 if type == fdt.TYPE_INT: 166 return '%#x' % fdt_util.fdt32_to_cpu(value) 167 elif type == fdt.TYPE_BYTE: 168 return '%#x' % ord(value[0]) 169 elif type == fdt.TYPE_STRING: 170 return '"%s"' % value 171 elif type == fdt.TYPE_BOOL: 172 return 'true' 173 174 def GetCompatName(self, node): 175 """Get a node's first compatible string as a C identifier 176 177 Args: 178 node: Node object to check 179 Return: 180 C identifier for the first compatible string 181 """ 182 compat = node.props['compatible'].value 183 aliases = [] 184 if type(compat) == list: 185 compat, aliases = compat[0], compat[1:] 186 return Conv_name_to_c(compat), [Conv_name_to_c(a) for a in aliases] 187 188 def ScanDtb(self): 189 """Scan the device tree to obtain a tree of notes and properties 190 191 Once this is done, self.fdt.GetRoot() can be called to obtain the 192 device tree root node, and progress from there. 193 """ 194 self.fdt = fdt.FdtScan(self._dtb_fname) 195 196 def ScanNode(self, root): 197 for node in root.subnodes: 198 if 'compatible' in node.props: 199 status = node.props.get('status') 200 if (not self._options.include_disabled and not status or 201 status.value != 'disabled'): 202 self._valid_nodes.append(node) 203 phandle_prop = node.props.get('phandle') 204 if phandle_prop: 205 phandle = phandle_prop.GetPhandle() 206 self._phandle_node[phandle] = node 207 208 # recurse to handle any subnodes 209 self.ScanNode(node); 210 211 def ScanTree(self): 212 """Scan the device tree for useful information 213 214 This fills in the following properties: 215 _phandle_node: A dict of Nodes indexed by phandle (an integer) 216 _valid_nodes: A list of nodes we wish to consider include in the 217 platform data 218 """ 219 self._phandle_node = {} 220 self._valid_nodes = [] 221 return self.ScanNode(self.fdt.GetRoot()); 222 223 for node in self.fdt.GetRoot().subnodes: 224 if 'compatible' in node.props: 225 status = node.props.get('status') 226 if (not self._options.include_disabled and not status or 227 status.value != 'disabled'): 228 node_list.append(node) 229 phandle_prop = node.props.get('phandle') 230 if phandle_prop: 231 phandle = phandle_prop.GetPhandle() 232 self._phandle_node[phandle] = node 233 234 self._valid_nodes = node_list 235 236 def IsPhandle(self, prop): 237 """Check if a node contains phandles 238 239 We have no reliable way of detecting whether a node uses a phandle 240 or not. As an interim measure, use a list of known property names. 241 242 Args: 243 prop: Prop object to check 244 Return: 245 True if the object value contains phandles, else False 246 """ 247 if prop.name in ['clocks']: 248 return True 249 return False 250 251 def ScanStructs(self): 252 """Scan the device tree building up the C structures we will use. 253 254 Build a dict keyed by C struct name containing a dict of Prop 255 object for each struct field (keyed by property name). Where the 256 same struct appears multiple times, try to use the 'widest' 257 property, i.e. the one with a type which can express all others. 258 259 Once the widest property is determined, all other properties are 260 updated to match that width. 261 """ 262 structs = {} 263 for node in self._valid_nodes: 264 node_name, _ = self.GetCompatName(node) 265 fields = {} 266 267 # Get a list of all the valid properties in this node. 268 for name, prop in node.props.items(): 269 if name not in PROP_IGNORE_LIST and name[0] != '#': 270 fields[name] = copy.deepcopy(prop) 271 272 # If we've seen this node_name before, update the existing struct. 273 if node_name in structs: 274 struct = structs[node_name] 275 for name, prop in fields.items(): 276 oldprop = struct.get(name) 277 if oldprop: 278 oldprop.Widen(prop) 279 else: 280 struct[name] = prop 281 282 # Otherwise store this as a new struct. 283 else: 284 structs[node_name] = fields 285 286 upto = 0 287 for node in self._valid_nodes: 288 node_name, _ = self.GetCompatName(node) 289 struct = structs[node_name] 290 for name, prop in node.props.items(): 291 if name not in PROP_IGNORE_LIST and name[0] != '#': 292 prop.Widen(struct[name]) 293 upto += 1 294 295 struct_name, aliases = self.GetCompatName(node) 296 for alias in aliases: 297 self._aliases[alias] = struct_name 298 299 return structs 300 301 def ScanPhandles(self): 302 """Figure out what phandles each node uses 303 304 We need to be careful when outputing nodes that use phandles since 305 they must come after the declaration of the phandles in the C file. 306 Otherwise we get a compiler error since the phandle struct is not yet 307 declared. 308 309 This function adds to each node a list of phandle nodes that the node 310 depends on. This allows us to output things in the right order. 311 """ 312 for node in self._valid_nodes: 313 node.phandles = set() 314 for pname, prop in node.props.items(): 315 if pname in PROP_IGNORE_LIST or pname[0] == '#': 316 continue 317 if type(prop.value) == list: 318 if self.IsPhandle(prop): 319 # Process the list as pairs of (phandle, id) 320 it = iter(prop.value) 321 for phandle_cell, id_cell in zip(it, it): 322 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 323 id = fdt_util.fdt32_to_cpu(id_cell) 324 target_node = self._phandle_node[phandle] 325 node.phandles.add(target_node) 326 327 328 def GenerateStructs(self, structs): 329 """Generate struct defintions for the platform data 330 331 This writes out the body of a header file consisting of structure 332 definitions for node in self._valid_nodes. See the documentation in 333 README.of-plat for more information. 334 """ 335 self.Out('#include <stdbool.h>\n') 336 self.Out('#include <libfdt.h>\n') 337 338 # Output the struct definition 339 for name in sorted(structs): 340 self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name)); 341 for pname in sorted(structs[name]): 342 prop = structs[name][pname] 343 if self.IsPhandle(prop): 344 # For phandles, include a reference to the target 345 self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'), 346 Conv_name_to_c(prop.name), 347 len(prop.value) / 2)) 348 else: 349 ptype = TYPE_NAMES[prop.type] 350 self.Out('\t%s%s' % (TabTo(2, ptype), 351 Conv_name_to_c(prop.name))) 352 if type(prop.value) == list: 353 self.Out('[%d]' % len(prop.value)) 354 self.Out(';\n') 355 self.Out('};\n') 356 357 for alias, struct_name in self._aliases.iteritems(): 358 self.Out('#define %s%s %s%s\n'% (STRUCT_PREFIX, alias, 359 STRUCT_PREFIX, struct_name)) 360 361 def OutputNode(self, node): 362 """Output the C code for a node 363 364 Args: 365 node: node to output 366 """ 367 struct_name, _ = self.GetCompatName(node) 368 var_name = Conv_name_to_c(node.name) 369 self.Buf('static struct %s%s %s%s = {\n' % 370 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) 371 for pname, prop in node.props.items(): 372 if pname in PROP_IGNORE_LIST or pname[0] == '#': 373 continue 374 ptype = TYPE_NAMES[prop.type] 375 member_name = Conv_name_to_c(prop.name) 376 self.Buf('\t%s= ' % TabTo(3, '.' + member_name)) 377 378 # Special handling for lists 379 if type(prop.value) == list: 380 self.Buf('{') 381 vals = [] 382 # For phandles, output a reference to the platform data 383 # of the target node. 384 if self.IsPhandle(prop): 385 # Process the list as pairs of (phandle, id) 386 it = iter(prop.value) 387 for phandle_cell, id_cell in zip(it, it): 388 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 389 id = fdt_util.fdt32_to_cpu(id_cell) 390 target_node = self._phandle_node[phandle] 391 name = Conv_name_to_c(target_node.name) 392 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id)) 393 else: 394 for val in prop.value: 395 vals.append(self.GetValue(prop.type, val)) 396 self.Buf(', '.join(vals)) 397 self.Buf('}') 398 else: 399 self.Buf(self.GetValue(prop.type, prop.value)) 400 self.Buf(',\n') 401 self.Buf('};\n') 402 403 # Add a device declaration 404 self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name) 405 self.Buf('\t.name\t\t= "%s",\n' % struct_name) 406 self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name)) 407 self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' % 408 (VAL_PREFIX, var_name)) 409 self.Buf('};\n') 410 self.Buf('\n') 411 412 self.Out(''.join(self.GetBuf())) 413 414 def GenerateTables(self): 415 """Generate device defintions for the platform data 416 417 This writes out C platform data initialisation data and 418 U_BOOT_DEVICE() declarations for each valid node. Where a node has 419 multiple compatible strings, a #define is used to make them equivalent. 420 421 See the documentation in doc/driver-model/of-plat.txt for more 422 information. 423 """ 424 self.Out('#include <common.h>\n') 425 self.Out('#include <dm.h>\n') 426 self.Out('#include <dt-structs.h>\n') 427 self.Out('\n') 428 nodes_to_output = list(self._valid_nodes) 429 430 # Keep outputing nodes until there is none left 431 while nodes_to_output: 432 node = nodes_to_output[0] 433 # Output all the node's dependencies first 434 for req_node in node.phandles: 435 if req_node in nodes_to_output: 436 self.OutputNode(req_node) 437 nodes_to_output.remove(req_node) 438 self.OutputNode(node) 439 nodes_to_output.remove(node) 440 441 442if __name__ != "__main__": 443 pass 444 445parser = OptionParser() 446parser.add_option('-d', '--dtb-file', action='store', 447 help='Specify the .dtb input file') 448parser.add_option('--include-disabled', action='store_true', 449 help='Include disabled nodes') 450parser.add_option('-o', '--output', action='store', default='-', 451 help='Select output filename') 452(options, args) = parser.parse_args() 453 454if not args: 455 raise ValueError('Please specify a command: struct, platdata') 456 457plat = DtbPlatdata(options.dtb_file, options) 458plat.ScanDtb() 459plat.ScanTree() 460plat.SetupOutput(options.output) 461structs = plat.ScanStructs() 462plat.ScanPhandles() 463 464for cmd in args[0].split(','): 465 if cmd == 'struct': 466 plat.GenerateStructs(structs) 467 elif cmd == 'platdata': 468 plat.GenerateTables() 469 else: 470 raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd) 471