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