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 GenerateStructs(self, structs): 276 """Generate struct defintions for the platform data 277 278 This writes out the body of a header file consisting of structure 279 definitions for node in self._valid_nodes. See the documentation in 280 README.of-plat for more information. 281 """ 282 self.Out('#include <stdbool.h>\n') 283 self.Out('#include <libfdt.h>\n') 284 285 # Output the struct definition 286 for name in sorted(structs): 287 self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name)); 288 for pname in sorted(structs[name]): 289 prop = structs[name][pname] 290 if self.IsPhandle(prop): 291 # For phandles, include a reference to the target 292 self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'), 293 Conv_name_to_c(prop.name), 294 len(prop.value) / 2)) 295 else: 296 ptype = TYPE_NAMES[prop.type] 297 self.Out('\t%s%s' % (TabTo(2, ptype), 298 Conv_name_to_c(prop.name))) 299 if type(prop.value) == list: 300 self.Out('[%d]' % len(prop.value)) 301 self.Out(';\n') 302 self.Out('};\n') 303 304 def OutputNode(self, node): 305 """Output the C code for a node 306 307 Args: 308 node: node to output 309 """ 310 struct_name = self.GetCompatName(node) 311 var_name = Conv_name_to_c(node.name) 312 self.Buf('static struct %s%s %s%s = {\n' % 313 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) 314 for pname, prop in node.props.items(): 315 if pname in PROP_IGNORE_LIST or pname[0] == '#': 316 continue 317 ptype = TYPE_NAMES[prop.type] 318 member_name = Conv_name_to_c(prop.name) 319 self.Buf('\t%s= ' % TabTo(3, '.' + member_name)) 320 321 # Special handling for lists 322 if type(prop.value) == list: 323 self.Buf('{') 324 vals = [] 325 # For phandles, output a reference to the platform data 326 # of the target node. 327 if self.IsPhandle(prop): 328 # Process the list as pairs of (phandle, id) 329 it = iter(prop.value) 330 for phandle_cell, id_cell in zip(it, it): 331 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 332 id = fdt_util.fdt32_to_cpu(id_cell) 333 target_node = self._phandle_node[phandle] 334 name = Conv_name_to_c(target_node.name) 335 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id)) 336 else: 337 for val in prop.value: 338 vals.append(self.GetValue(prop.type, val)) 339 self.Buf(', '.join(vals)) 340 self.Buf('}') 341 else: 342 self.Buf(self.GetValue(prop.type, prop.value)) 343 self.Buf(',\n') 344 self.Buf('};\n') 345 346 # Add a device declaration 347 self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name) 348 self.Buf('\t.name\t\t= "%s",\n' % struct_name) 349 self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name)) 350 self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' % 351 (VAL_PREFIX, var_name)) 352 self.Buf('};\n') 353 self.Buf('\n') 354 355 def GenerateTables(self): 356 """Generate device defintions for the platform data 357 358 This writes out C platform data initialisation data and 359 U_BOOT_DEVICE() declarations for each valid node. See the 360 documentation in README.of-plat for more information. 361 """ 362 self.Out('#include <common.h>\n') 363 self.Out('#include <dm.h>\n') 364 self.Out('#include <dt-structs.h>\n') 365 self.Out('\n') 366 node_txt_list = [] 367 for node in self._valid_nodes: 368 self.OutputNode(node) 369 370 # Output phandle target nodes first, since they may be referenced 371 # by others 372 if 'phandle' in node.props: 373 self.Out(''.join(self.GetBuf())) 374 else: 375 node_txt_list.append(self.GetBuf()) 376 377 # Output all the nodes which are not phandle targets themselves, but 378 # may reference them. This avoids the need for forward declarations. 379 for node_txt in node_txt_list: 380 self.Out(''.join(node_txt)) 381 382 383if __name__ != "__main__": 384 pass 385 386parser = OptionParser() 387parser.add_option('-d', '--dtb-file', action='store', 388 help='Specify the .dtb input file') 389parser.add_option('--include-disabled', action='store_true', 390 help='Include disabled nodes') 391parser.add_option('-o', '--output', action='store', default='-', 392 help='Select output filename') 393(options, args) = parser.parse_args() 394 395if not args: 396 raise ValueError('Please specify a command: struct, platdata') 397 398plat = DtbPlatdata(options.dtb_file, options) 399plat.ScanDtb() 400plat.ScanTree() 401plat.SetupOutput(options.output) 402structs = plat.ScanStructs() 403 404for cmd in args[0].split(','): 405 if cmd == 'struct': 406 plat.GenerateStructs(structs) 407 elif cmd == 'platdata': 408 plat.GenerateTables() 409 else: 410 raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd) 411