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