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