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 ScanTree(self): 176 """Scan the device tree for useful information 177 178 This fills in the following properties: 179 _phandle_node: A dict of Nodes indexed by phandle (an integer) 180 _valid_nodes: A list of nodes we wish to consider include in the 181 platform data 182 """ 183 node_list = [] 184 self._phandle_node = {} 185 for node in self.fdt.GetRoot().subnodes: 186 if 'compatible' in node.props: 187 status = node.props.get('status') 188 if (not options.include_disabled and not status or 189 status.value != 'disabled'): 190 node_list.append(node) 191 phandle_prop = node.props.get('phandle') 192 if phandle_prop: 193 phandle = phandle_prop.GetPhandle() 194 self._phandle_node[phandle] = node 195 196 self._valid_nodes = node_list 197 198 def IsPhandle(self, prop): 199 """Check if a node contains phandles 200 201 We have no reliable way of detecting whether a node uses a phandle 202 or not. As an interim measure, use a list of known property names. 203 204 Args: 205 prop: Prop object to check 206 Return: 207 True if the object value contains phandles, else False 208 """ 209 if prop.name in ['clocks']: 210 return True 211 return False 212 213 def ScanStructs(self): 214 """Scan the device tree building up the C structures we will use. 215 216 Build a dict keyed by C struct name containing a dict of Prop 217 object for each struct field (keyed by property name). Where the 218 same struct appears multiple times, try to use the 'widest' 219 property, i.e. the one with a type which can express all others. 220 221 Once the widest property is determined, all other properties are 222 updated to match that width. 223 """ 224 structs = {} 225 for node in self._valid_nodes: 226 node_name = self.GetCompatName(node) 227 fields = {} 228 229 # Get a list of all the valid properties in this node. 230 for name, prop in node.props.items(): 231 if name not in PROP_IGNORE_LIST and name[0] != '#': 232 fields[name] = copy.deepcopy(prop) 233 234 # If we've seen this node_name before, update the existing struct. 235 if node_name in structs: 236 struct = structs[node_name] 237 for name, prop in fields.items(): 238 oldprop = struct.get(name) 239 if oldprop: 240 oldprop.Widen(prop) 241 else: 242 struct[name] = prop 243 244 # Otherwise store this as a new struct. 245 else: 246 structs[node_name] = fields 247 248 upto = 0 249 for node in self._valid_nodes: 250 node_name = self.GetCompatName(node) 251 struct = structs[node_name] 252 for name, prop in node.props.items(): 253 if name not in PROP_IGNORE_LIST and name[0] != '#': 254 prop.Widen(struct[name]) 255 upto += 1 256 return structs 257 258 def GenerateStructs(self, structs): 259 """Generate struct defintions for the platform data 260 261 This writes out the body of a header file consisting of structure 262 definitions for node in self._valid_nodes. See the documentation in 263 README.of-plat for more information. 264 """ 265 self.Out('#include <stdbool.h>\n') 266 self.Out('#include <libfdt.h>\n') 267 268 # Output the struct definition 269 for name in sorted(structs): 270 self.Out('struct %s%s {\n' % (STRUCT_PREFIX, name)); 271 for pname in sorted(structs[name]): 272 prop = structs[name][pname] 273 if self.IsPhandle(prop): 274 # For phandles, include a reference to the target 275 self.Out('\t%s%s[%d]' % (TabTo(2, 'struct phandle_2_cell'), 276 Conv_name_to_c(prop.name), 277 len(prop.value) / 2)) 278 else: 279 ptype = TYPE_NAMES[prop.type] 280 self.Out('\t%s%s' % (TabTo(2, ptype), 281 Conv_name_to_c(prop.name))) 282 if type(prop.value) == list: 283 self.Out('[%d]' % len(prop.value)) 284 self.Out(';\n') 285 self.Out('};\n') 286 287 def GenerateTables(self): 288 """Generate device defintions for the platform data 289 290 This writes out C platform data initialisation data and 291 U_BOOT_DEVICE() declarations for each valid node. See the 292 documentation in README.of-plat for more information. 293 """ 294 self.Out('#include <common.h>\n') 295 self.Out('#include <dm.h>\n') 296 self.Out('#include <dt-structs.h>\n') 297 self.Out('\n') 298 node_txt_list = [] 299 for node in self._valid_nodes: 300 struct_name = self.GetCompatName(node) 301 var_name = Conv_name_to_c(node.name) 302 self.Buf('static struct %s%s %s%s = {\n' % 303 (STRUCT_PREFIX, struct_name, VAL_PREFIX, var_name)) 304 for pname, prop in node.props.items(): 305 if pname in PROP_IGNORE_LIST or pname[0] == '#': 306 continue 307 ptype = TYPE_NAMES[prop.type] 308 member_name = Conv_name_to_c(prop.name) 309 self.Buf('\t%s= ' % TabTo(3, '.' + member_name)) 310 311 # Special handling for lists 312 if type(prop.value) == list: 313 self.Buf('{') 314 vals = [] 315 # For phandles, output a reference to the platform data 316 # of the target node. 317 if self.IsPhandle(prop): 318 # Process the list as pairs of (phandle, id) 319 it = iter(prop.value) 320 for phandle_cell, id_cell in zip(it, it): 321 phandle = fdt_util.fdt32_to_cpu(phandle_cell) 322 id = fdt_util.fdt32_to_cpu(id_cell) 323 target_node = self._phandle_node[phandle] 324 name = Conv_name_to_c(target_node.name) 325 vals.append('{&%s%s, %d}' % (VAL_PREFIX, name, id)) 326 else: 327 for val in prop.value: 328 vals.append(self.GetValue(prop.type, val)) 329 self.Buf(', '.join(vals)) 330 self.Buf('}') 331 else: 332 self.Buf(self.GetValue(prop.type, prop.value)) 333 self.Buf(',\n') 334 self.Buf('};\n') 335 336 # Add a device declaration 337 self.Buf('U_BOOT_DEVICE(%s) = {\n' % var_name) 338 self.Buf('\t.name\t\t= "%s",\n' % struct_name) 339 self.Buf('\t.platdata\t= &%s%s,\n' % (VAL_PREFIX, var_name)) 340 self.Buf('\t.platdata_size\t= sizeof(%s%s),\n' % 341 (VAL_PREFIX, var_name)) 342 self.Buf('};\n') 343 self.Buf('\n') 344 345 # Output phandle target nodes first, since they may be referenced 346 # by others 347 if 'phandle' in node.props: 348 self.Out(''.join(self.GetBuf())) 349 else: 350 node_txt_list.append(self.GetBuf()) 351 352 # Output all the nodes which are not phandle targets themselves, but 353 # may reference them. This avoids the need for forward declarations. 354 for node_txt in node_txt_list: 355 self.Out(''.join(node_txt)) 356 357 358if __name__ != "__main__": 359 pass 360 361parser = OptionParser() 362parser.add_option('-d', '--dtb-file', action='store', 363 help='Specify the .dtb input file') 364parser.add_option('--include-disabled', action='store_true', 365 help='Include disabled nodes') 366parser.add_option('-o', '--output', action='store', default='-', 367 help='Select output filename') 368(options, args) = parser.parse_args() 369 370if not args: 371 raise ValueError('Please specify a command: struct, platdata') 372 373plat = DtbPlatdata(options.dtb_file, options) 374plat.ScanDtb() 375plat.ScanTree() 376plat.SetupOutput(options.output) 377structs = plat.ScanStructs() 378 379for cmd in args[0].split(','): 380 if cmd == 'struct': 381 plat.GenerateStructs(structs) 382 elif cmd == 'platdata': 383 plat.GenerateTables() 384 else: 385 raise ValueError("Unknown command '%s': (use: struct, platdata)" % cmd) 386