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