1#!/usr/bin/python 2# SPDX-License-Identifier: GPL-2.0+ 3# 4# Copyright (C) 2016 Google, Inc 5# Written by Simon Glass <sjg@chromium.org> 6# 7 8import struct 9import sys 10 11import fdt_util 12import libfdt 13from libfdt import QUIET_NOTFOUND 14 15# This deals with a device tree, presenting it as an assortment of Node and 16# Prop objects, representing nodes and properties, respectively. This file 17# contains the base classes and defines the high-level API. You can use 18# FdtScan() as a convenience function to create and scan an Fdt. 19 20# This implementation uses a libfdt Python library to access the device tree, 21# so it is fairly efficient. 22 23# A list of types we support 24(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5) 25 26def CheckErr(errnum, msg): 27 if errnum: 28 raise ValueError('Error %d: %s: %s' % 29 (errnum, libfdt.fdt_strerror(errnum), msg)) 30 31class Prop: 32 """A device tree property 33 34 Properties: 35 name: Property name (as per the device tree) 36 value: Property value as a string of bytes, or a list of strings of 37 bytes 38 type: Value type 39 """ 40 def __init__(self, node, offset, name, bytes): 41 self._node = node 42 self._offset = offset 43 self.name = name 44 self.value = None 45 self.bytes = str(bytes) 46 if not bytes: 47 self.type = TYPE_BOOL 48 self.value = True 49 return 50 self.type, self.value = self.BytesToValue(bytes) 51 52 def RefreshOffset(self, poffset): 53 self._offset = poffset 54 55 def Widen(self, newprop): 56 """Figure out which property type is more general 57 58 Given a current property and a new property, this function returns the 59 one that is less specific as to type. The less specific property will 60 be ble to represent the data in the more specific property. This is 61 used for things like: 62 63 node1 { 64 compatible = "fred"; 65 value = <1>; 66 }; 67 node1 { 68 compatible = "fred"; 69 value = <1 2>; 70 }; 71 72 He we want to use an int array for 'value'. The first property 73 suggests that a single int is enough, but the second one shows that 74 it is not. Calling this function with these two propertes would 75 update the current property to be like the second, since it is less 76 specific. 77 """ 78 if newprop.type < self.type: 79 self.type = newprop.type 80 81 if type(newprop.value) == list and type(self.value) != list: 82 self.value = [self.value] 83 84 if type(self.value) == list and len(newprop.value) > len(self.value): 85 val = self.GetEmpty(self.type) 86 while len(self.value) < len(newprop.value): 87 self.value.append(val) 88 89 def BytesToValue(self, bytes): 90 """Converts a string of bytes into a type and value 91 92 Args: 93 A string containing bytes 94 95 Return: 96 A tuple: 97 Type of data 98 Data, either a single element or a list of elements. Each element 99 is one of: 100 TYPE_STRING: string value from the property 101 TYPE_INT: a byte-swapped integer stored as a 4-byte string 102 TYPE_BYTE: a byte stored as a single-byte string 103 """ 104 bytes = str(bytes) 105 size = len(bytes) 106 strings = bytes.split('\0') 107 is_string = True 108 count = len(strings) - 1 109 if count > 0 and not strings[-1]: 110 for string in strings[:-1]: 111 if not string: 112 is_string = False 113 break 114 for ch in string: 115 if ch < ' ' or ch > '~': 116 is_string = False 117 break 118 else: 119 is_string = False 120 if is_string: 121 if count == 1: 122 return TYPE_STRING, strings[0] 123 else: 124 return TYPE_STRING, strings[:-1] 125 if size % 4: 126 if size == 1: 127 return TYPE_BYTE, bytes[0] 128 else: 129 return TYPE_BYTE, list(bytes) 130 val = [] 131 for i in range(0, size, 4): 132 val.append(bytes[i:i + 4]) 133 if size == 4: 134 return TYPE_INT, val[0] 135 else: 136 return TYPE_INT, val 137 138 @classmethod 139 def GetEmpty(self, type): 140 """Get an empty / zero value of the given type 141 142 Returns: 143 A single value of the given type 144 """ 145 if type == TYPE_BYTE: 146 return chr(0) 147 elif type == TYPE_INT: 148 return struct.pack('<I', 0); 149 elif type == TYPE_STRING: 150 return '' 151 else: 152 return True 153 154 def GetOffset(self): 155 """Get the offset of a property 156 157 Returns: 158 The offset of the property (struct fdt_property) within the file 159 """ 160 self._node._fdt.CheckCache() 161 return self._node._fdt.GetStructOffset(self._offset) 162 163class Node: 164 """A device tree node 165 166 Properties: 167 offset: Integer offset in the device tree 168 name: Device tree node tname 169 path: Full path to node, along with the node name itself 170 _fdt: Device tree object 171 subnodes: A list of subnodes for this node, each a Node object 172 props: A dict of properties for this node, each a Prop object. 173 Keyed by property name 174 """ 175 def __init__(self, fdt, parent, offset, name, path): 176 self._fdt = fdt 177 self.parent = parent 178 self._offset = offset 179 self.name = name 180 self.path = path 181 self.subnodes = [] 182 self.props = {} 183 184 def GetFdt(self): 185 """Get the Fdt object for this node 186 187 Returns: 188 Fdt object 189 """ 190 return self._fdt 191 192 def FindNode(self, name): 193 """Find a node given its name 194 195 Args: 196 name: Node name to look for 197 Returns: 198 Node object if found, else None 199 """ 200 for subnode in self.subnodes: 201 if subnode.name == name: 202 return subnode 203 return None 204 205 def Offset(self): 206 """Returns the offset of a node, after checking the cache 207 208 This should be used instead of self._offset directly, to ensure that 209 the cache does not contain invalid offsets. 210 """ 211 self._fdt.CheckCache() 212 return self._offset 213 214 def Scan(self): 215 """Scan a node's properties and subnodes 216 217 This fills in the props and subnodes properties, recursively 218 searching into subnodes so that the entire tree is built. 219 """ 220 fdt_obj = self._fdt._fdt_obj 221 self.props = self._fdt.GetProps(self) 222 phandle = fdt_obj.get_phandle(self.Offset()) 223 if phandle: 224 self._fdt.phandle_to_node[phandle] = self 225 226 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND) 227 while offset >= 0: 228 sep = '' if self.path[-1] == '/' else '/' 229 name = fdt_obj.get_name(offset) 230 path = self.path + sep + name 231 node = Node(self._fdt, self, offset, name, path) 232 self.subnodes.append(node) 233 234 node.Scan() 235 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 236 237 def Refresh(self, my_offset): 238 """Fix up the _offset for each node, recursively 239 240 Note: This does not take account of property offsets - these will not 241 be updated. 242 """ 243 fdt_obj = self._fdt._fdt_obj 244 if self._offset != my_offset: 245 self._offset = my_offset 246 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) 247 for subnode in self.subnodes: 248 if subnode.name != fdt_obj.get_name(offset): 249 raise ValueError('Internal error, node name mismatch %s != %s' % 250 (subnode.name, fdt_obj.get_name(offset))) 251 subnode.Refresh(offset) 252 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 253 if offset != -libfdt.FDT_ERR_NOTFOUND: 254 raise ValueError('Internal error, offset == %d' % offset) 255 256 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) 257 while poffset >= 0: 258 p = fdt_obj.get_property_by_offset(poffset) 259 prop = self.props.get(p.name) 260 if not prop: 261 raise ValueError("Internal error, property '%s' missing, " 262 'offset %d' % (p.name, poffset)) 263 prop.RefreshOffset(poffset) 264 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) 265 266 def DeleteProp(self, prop_name): 267 """Delete a property of a node 268 269 The property is deleted and the offset cache is invalidated. 270 271 Args: 272 prop_name: Name of the property to delete 273 Raises: 274 ValueError if the property does not exist 275 """ 276 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), 277 "Node '%s': delete property: '%s'" % (self.path, prop_name)) 278 del self.props[prop_name] 279 self._fdt.Invalidate() 280 281 def AddZeroProp(self, prop_name): 282 """Add a new property to the device tree with an integer value of 0. 283 284 Args: 285 prop_name: Name of property 286 """ 287 fdt_obj = self._fdt._fdt_obj 288 if fdt_obj.setprop_u32(self.Offset(), prop_name, 0, 289 (libfdt.NOSPACE,)) == -libfdt.NOSPACE: 290 fdt_obj.resize(fdt_obj.totalsize() + 1024) 291 fdt_obj.setprop_u32(self.Offset(), prop_name, 0) 292 self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4) 293 self._fdt.Invalidate() 294 295 def SetInt(self, prop_name, val): 296 """Update an integer property int the device tree. 297 298 This is not allowed to change the size of the FDT. 299 300 Args: 301 prop_name: Name of property 302 val: Value to set 303 """ 304 fdt_obj = self._fdt._fdt_obj 305 fdt_obj.setprop_u32(self.Offset(), prop_name, val) 306 307 308class Fdt: 309 """Provides simple access to a flat device tree blob using libfdts. 310 311 Properties: 312 fname: Filename of fdt 313 _root: Root of device tree (a Node object) 314 """ 315 def __init__(self, fname): 316 self._fname = fname 317 self._cached_offsets = False 318 self.phandle_to_node = {} 319 if self._fname: 320 self._fname = fdt_util.EnsureCompiled(self._fname) 321 322 with open(self._fname) as fd: 323 self._fdt_obj = libfdt.Fdt(fd.read()) 324 325 def LookupPhandle(self, phandle): 326 """Look up a phandle 327 328 Args: 329 phandle: Phandle to look up (int) 330 331 Returns: 332 Node object the phandle points to 333 """ 334 return self.phandle_to_node.get(phandle) 335 336 def Scan(self, root='/'): 337 """Scan a device tree, building up a tree of Node objects 338 339 This fills in the self._root property 340 341 Args: 342 root: Ignored 343 344 TODO(sjg@chromium.org): Implement the 'root' parameter 345 """ 346 self._cached_offsets = True 347 self._root = self.Node(self, None, 0, '/', '/') 348 self._root.Scan() 349 350 def GetRoot(self): 351 """Get the root Node of the device tree 352 353 Returns: 354 The root Node object 355 """ 356 return self._root 357 358 def GetNode(self, path): 359 """Look up a node from its path 360 361 Args: 362 path: Path to look up, e.g. '/microcode/update@0' 363 Returns: 364 Node object, or None if not found 365 """ 366 node = self._root 367 parts = path.split('/') 368 if len(parts) < 2: 369 return None 370 for part in parts[1:]: 371 node = node.FindNode(part) 372 if not node: 373 return None 374 return node 375 376 def Flush(self): 377 """Flush device tree changes back to the file 378 379 If the device tree has changed in memory, write it back to the file. 380 """ 381 with open(self._fname, 'wb') as fd: 382 fd.write(self._fdt_obj.as_bytearray()) 383 384 def Pack(self): 385 """Pack the device tree down to its minimum size 386 387 When nodes and properties shrink or are deleted, wasted space can 388 build up in the device tree binary. 389 """ 390 CheckErr(self._fdt_obj.pack(), 'pack') 391 self.Invalidate() 392 393 def GetContents(self): 394 """Get the contents of the FDT 395 396 Returns: 397 The FDT contents as a string of bytes 398 """ 399 return self._fdt_obj.as_bytearray() 400 401 def GetFdtObj(self): 402 """Get the contents of the FDT 403 404 Returns: 405 The FDT contents as a libfdt.Fdt object 406 """ 407 return self._fdt_obj 408 409 def GetProps(self, node): 410 """Get all properties from a node. 411 412 Args: 413 node: Full path to node name to look in. 414 415 Returns: 416 A dictionary containing all the properties, indexed by node name. 417 The entries are Prop objects. 418 419 Raises: 420 ValueError: if the node does not exist. 421 """ 422 props_dict = {} 423 poffset = self._fdt_obj.first_property_offset(node._offset, 424 QUIET_NOTFOUND) 425 while poffset >= 0: 426 p = self._fdt_obj.get_property_by_offset(poffset) 427 prop = Prop(node, poffset, p.name, p) 428 props_dict[prop.name] = prop 429 430 poffset = self._fdt_obj.next_property_offset(poffset, 431 QUIET_NOTFOUND) 432 return props_dict 433 434 def Invalidate(self): 435 """Mark our offset cache as invalid""" 436 self._cached_offsets = False 437 438 def CheckCache(self): 439 """Refresh the offset cache if needed""" 440 if self._cached_offsets: 441 return 442 self.Refresh() 443 self._cached_offsets = True 444 445 def Refresh(self): 446 """Refresh the offset cache""" 447 self._root.Refresh(0) 448 449 def GetStructOffset(self, offset): 450 """Get the file offset of a given struct offset 451 452 Args: 453 offset: Offset within the 'struct' region of the device tree 454 Returns: 455 Position of @offset within the device tree binary 456 """ 457 return self._fdt_obj.off_dt_struct() + offset 458 459 @classmethod 460 def Node(self, fdt, parent, offset, name, path): 461 """Create a new node 462 463 This is used by Fdt.Scan() to create a new node using the correct 464 class. 465 466 Args: 467 fdt: Fdt object 468 parent: Parent node, or None if this is the root node 469 offset: Offset of node 470 name: Node name 471 path: Full path to node 472 """ 473 node = Node(fdt, parent, offset, name, path) 474 return node 475 476def FdtScan(fname): 477 """Returns a new Fdt object""" 478 dtb = Fdt(fname) 479 dtb.Scan() 480 return dtb 481