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