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