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 _FindNode(self, name): 185 """Find a node given its name 186 187 Args: 188 name: Node name to look for 189 Returns: 190 Node object if found, else None 191 """ 192 for subnode in self.subnodes: 193 if subnode.name == name: 194 return subnode 195 return None 196 197 def Offset(self): 198 """Returns the offset of a node, after checking the cache 199 200 This should be used instead of self._offset directly, to ensure that 201 the cache does not contain invalid offsets. 202 """ 203 self._fdt.CheckCache() 204 return self._offset 205 206 def Scan(self): 207 """Scan a node's properties and subnodes 208 209 This fills in the props and subnodes properties, recursively 210 searching into subnodes so that the entire tree is built. 211 """ 212 fdt_obj = self._fdt._fdt_obj 213 self.props = self._fdt.GetProps(self) 214 phandle = fdt_obj.get_phandle(self.Offset()) 215 if phandle: 216 self._fdt.phandle_to_node[phandle] = self 217 218 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND) 219 while offset >= 0: 220 sep = '' if self.path[-1] == '/' else '/' 221 name = fdt_obj.get_name(offset) 222 path = self.path + sep + name 223 node = Node(self._fdt, self, offset, name, path) 224 self.subnodes.append(node) 225 226 node.Scan() 227 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 228 229 def Refresh(self, my_offset): 230 """Fix up the _offset for each node, recursively 231 232 Note: This does not take account of property offsets - these will not 233 be updated. 234 """ 235 fdt_obj = self._fdt._fdt_obj 236 if self._offset != my_offset: 237 self._offset = my_offset 238 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) 239 for subnode in self.subnodes: 240 if subnode.name != fdt_obj.get_name(offset): 241 raise ValueError('Internal error, node name mismatch %s != %s' % 242 (subnode.name, fdt_obj.get_name(offset))) 243 subnode.Refresh(offset) 244 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 245 if offset != -libfdt.FDT_ERR_NOTFOUND: 246 raise ValueError('Internal error, offset == %d' % offset) 247 248 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) 249 while poffset >= 0: 250 p = fdt_obj.get_property_by_offset(poffset) 251 prop = self.props.get(p.name) 252 if not prop: 253 raise ValueError("Internal error, property '%s' missing, " 254 'offset %d' % (p.name, poffset)) 255 prop.RefreshOffset(poffset) 256 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) 257 258 def DeleteProp(self, prop_name): 259 """Delete a property of a node 260 261 The property is deleted and the offset cache is invalidated. 262 263 Args: 264 prop_name: Name of the property to delete 265 Raises: 266 ValueError if the property does not exist 267 """ 268 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), 269 "Node '%s': delete property: '%s'" % (self.path, prop_name)) 270 del self.props[prop_name] 271 self._fdt.Invalidate() 272 273 def AddZeroProp(self, prop_name): 274 """Add a new property to the device tree with an integer value of 0. 275 276 Args: 277 prop_name: Name of property 278 """ 279 fdt_obj = self._fdt._fdt_obj 280 if fdt_obj.setprop_u32(self.Offset(), prop_name, 0, 281 (libfdt.NOSPACE,)) == -libfdt.NOSPACE: 282 fdt_obj.open_into(fdt_obj.totalsize() + 1024) 283 fdt_obj.setprop_u32(self.Offset(), prop_name, 0) 284 self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4) 285 self._fdt.Invalidate() 286 287 def SetInt(self, prop_name, val): 288 """Update an integer property int the device tree. 289 290 This is not allowed to change the size of the FDT. 291 292 Args: 293 prop_name: Name of property 294 val: Value to set 295 """ 296 fdt_obj = self._fdt._fdt_obj 297 fdt_obj.setprop_u32(self.Offset(), prop_name, val) 298 299 300class Fdt: 301 """Provides simple access to a flat device tree blob using libfdts. 302 303 Properties: 304 fname: Filename of fdt 305 _root: Root of device tree (a Node object) 306 """ 307 def __init__(self, fname): 308 self._fname = fname 309 self._cached_offsets = False 310 self.phandle_to_node = {} 311 if self._fname: 312 self._fname = fdt_util.EnsureCompiled(self._fname) 313 314 with open(self._fname) as fd: 315 self._fdt_obj = libfdt.Fdt(fd.read()) 316 317 def Scan(self, root='/'): 318 """Scan a device tree, building up a tree of Node objects 319 320 This fills in the self._root property 321 322 Args: 323 root: Ignored 324 325 TODO(sjg@chromium.org): Implement the 'root' parameter 326 """ 327 self._cached_offsets = True 328 self._root = self.Node(self, None, 0, '/', '/') 329 self._root.Scan() 330 331 def GetRoot(self): 332 """Get the root Node of the device tree 333 334 Returns: 335 The root Node object 336 """ 337 return self._root 338 339 def GetNode(self, path): 340 """Look up a node from its path 341 342 Args: 343 path: Path to look up, e.g. '/microcode/update@0' 344 Returns: 345 Node object, or None if not found 346 """ 347 node = self._root 348 parts = path.split('/') 349 if len(parts) < 2: 350 return None 351 for part in parts[1:]: 352 node = node._FindNode(part) 353 if not node: 354 return None 355 return node 356 357 def Flush(self): 358 """Flush device tree changes back to the file 359 360 If the device tree has changed in memory, write it back to the file. 361 """ 362 with open(self._fname, 'wb') as fd: 363 fd.write(self._fdt_obj.as_bytearray()) 364 365 def Pack(self): 366 """Pack the device tree down to its minimum size 367 368 When nodes and properties shrink or are deleted, wasted space can 369 build up in the device tree binary. 370 """ 371 CheckErr(self._fdt_obj.pack(), 'pack') 372 self.Invalidate() 373 374 def GetContents(self): 375 """Get the contents of the FDT 376 377 Returns: 378 The FDT contents as a string of bytes 379 """ 380 return self._fdt_obj.as_bytearray() 381 382 def GetFdtObj(self): 383 """Get the contents of the FDT 384 385 Returns: 386 The FDT contents as a libfdt.Fdt object 387 """ 388 return self._fdt_obj 389 390 def GetProps(self, node): 391 """Get all properties from a node. 392 393 Args: 394 node: Full path to node name to look in. 395 396 Returns: 397 A dictionary containing all the properties, indexed by node name. 398 The entries are Prop objects. 399 400 Raises: 401 ValueError: if the node does not exist. 402 """ 403 props_dict = {} 404 poffset = self._fdt_obj.first_property_offset(node._offset, 405 QUIET_NOTFOUND) 406 while poffset >= 0: 407 p = self._fdt_obj.get_property_by_offset(poffset) 408 prop = Prop(node, poffset, p.name, p) 409 props_dict[prop.name] = prop 410 411 poffset = self._fdt_obj.next_property_offset(poffset, 412 QUIET_NOTFOUND) 413 return props_dict 414 415 def Invalidate(self): 416 """Mark our offset cache as invalid""" 417 self._cached_offsets = False 418 419 def CheckCache(self): 420 """Refresh the offset cache if needed""" 421 if self._cached_offsets: 422 return 423 self.Refresh() 424 self._cached_offsets = True 425 426 def Refresh(self): 427 """Refresh the offset cache""" 428 self._root.Refresh(0) 429 430 def GetStructOffset(self, offset): 431 """Get the file offset of a given struct offset 432 433 Args: 434 offset: Offset within the 'struct' region of the device tree 435 Returns: 436 Position of @offset within the device tree binary 437 """ 438 return self._fdt_obj.off_dt_struct() + offset 439 440 @classmethod 441 def Node(self, fdt, parent, offset, name, path): 442 """Create a new node 443 444 This is used by Fdt.Scan() to create a new node using the correct 445 class. 446 447 Args: 448 fdt: Fdt object 449 parent: Parent node, or None if this is the root node 450 offset: Offset of node 451 name: Node name 452 path: Full path to node 453 """ 454 node = Node(fdt, parent, offset, name, path) 455 return node 456 457def FdtScan(fname): 458 """Returns a new Fdt object""" 459 dtb = Fdt(fname) 460 dtb.Scan() 461 return dtb 462