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