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