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