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 struct 10import sys 11 12import fdt_util 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. Most of the 17# implementation is in the FdtNormal subclass. See fdt_select.py for how to 18# create an Fdt object. 19 20# A list of types we support 21(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL) = range(4) 22 23def CheckErr(errnum, msg): 24 if errnum: 25 raise ValueError('Error %d: %s: %s' % 26 (errnum, libfdt.fdt_strerror(errnum), msg)) 27 28class PropBase: 29 """A device tree property 30 31 Properties: 32 name: Property name (as per the device tree) 33 value: Property value as a string of bytes, or a list of strings of 34 bytes 35 type: Value type 36 """ 37 def __init__(self, node, offset, name): 38 self._node = node 39 self._offset = offset 40 self.name = name 41 self.value = None 42 43 def GetPhandle(self): 44 """Get a (single) phandle value from a property 45 46 Gets the phandle valuie from a property and returns it as an integer 47 """ 48 return fdt_util.fdt32_to_cpu(self.value[:4]) 49 50 def Widen(self, newprop): 51 """Figure out which property type is more general 52 53 Given a current property and a new property, this function returns the 54 one that is less specific as to type. The less specific property will 55 be ble to represent the data in the more specific property. This is 56 used for things like: 57 58 node1 { 59 compatible = "fred"; 60 value = <1>; 61 }; 62 node1 { 63 compatible = "fred"; 64 value = <1 2>; 65 }; 66 67 He we want to use an int array for 'value'. The first property 68 suggests that a single int is enough, but the second one shows that 69 it is not. Calling this function with these two propertes would 70 update the current property to be like the second, since it is less 71 specific. 72 """ 73 if newprop.type < self.type: 74 self.type = newprop.type 75 76 if type(newprop.value) == list and type(self.value) != list: 77 self.value = [self.value] 78 79 if type(self.value) == list and len(newprop.value) > len(self.value): 80 val = self.GetEmpty(self.type) 81 while len(self.value) < len(newprop.value): 82 self.value.append(val) 83 84 def BytesToValue(self, bytes): 85 """Converts a string of bytes into a type and value 86 87 Args: 88 A string containing bytes 89 90 Return: 91 A tuple: 92 Type of data 93 Data, either a single element or a list of elements. Each element 94 is one of: 95 TYPE_STRING: string value from the property 96 TYPE_INT: a byte-swapped integer stored as a 4-byte string 97 TYPE_BYTE: a byte stored as a single-byte string 98 """ 99 bytes = str(bytes) 100 size = len(bytes) 101 strings = bytes.split('\0') 102 is_string = True 103 count = len(strings) - 1 104 if count > 0 and not strings[-1]: 105 for string in strings[:-1]: 106 if not string: 107 is_string = False 108 break 109 for ch in string: 110 if ch < ' ' or ch > '~': 111 is_string = False 112 break 113 else: 114 is_string = False 115 if is_string: 116 if count == 1: 117 return TYPE_STRING, strings[0] 118 else: 119 return TYPE_STRING, strings[:-1] 120 if size % 4: 121 if size == 1: 122 return TYPE_BYTE, bytes[0] 123 else: 124 return TYPE_BYTE, list(bytes) 125 val = [] 126 for i in range(0, size, 4): 127 val.append(bytes[i:i + 4]) 128 if size == 4: 129 return TYPE_INT, val[0] 130 else: 131 return TYPE_INT, val 132 133 def GetEmpty(self, type): 134 """Get an empty / zero value of the given type 135 136 Returns: 137 A single value of the given type 138 """ 139 if type == TYPE_BYTE: 140 return chr(0) 141 elif type == TYPE_INT: 142 return struct.pack('<I', 0); 143 elif type == TYPE_STRING: 144 return '' 145 else: 146 return True 147 148 def GetOffset(self): 149 """Get the offset of a property 150 151 This can be implemented by subclasses. 152 153 Returns: 154 The offset of the property (struct fdt_property) within the 155 file, or None if not known. 156 """ 157 return None 158 159class NodeBase: 160 """A device tree node 161 162 Properties: 163 offset: Integer offset in the device tree 164 name: Device tree node tname 165 path: Full path to node, along with the node name itself 166 _fdt: Device tree object 167 subnodes: A list of subnodes for this node, each a Node object 168 props: A dict of properties for this node, each a Prop object. 169 Keyed by property name 170 """ 171 def __init__(self, fdt, offset, name, path): 172 self._fdt = fdt 173 self._offset = offset 174 self.name = name 175 self.path = path 176 self.subnodes = [] 177 self.props = {} 178 179 def _FindNode(self, name): 180 """Find a node given its name 181 182 Args: 183 name: Node name to look for 184 Returns: 185 Node object if found, else None 186 """ 187 for subnode in self.subnodes: 188 if subnode.name == name: 189 return subnode 190 return None 191 192 def Scan(self): 193 """Scan the subnodes of a node 194 195 This should be implemented by subclasses 196 """ 197 raise NotImplementedError() 198 199 def DeleteProp(self, prop_name): 200 """Delete a property of a node 201 202 This should be implemented by subclasses 203 204 Args: 205 prop_name: Name of the property to delete 206 """ 207 raise NotImplementedError() 208 209class Fdt: 210 """Provides simple access to a flat device tree blob. 211 212 Properties: 213 fname: Filename of fdt 214 _root: Root of device tree (a Node object) 215 """ 216 def __init__(self, fname): 217 self._fname = fname 218 219 def Scan(self, root='/'): 220 """Scan a device tree, building up a tree of Node objects 221 222 This fills in the self._root property 223 224 Args: 225 root: Ignored 226 227 TODO(sjg@chromium.org): Implement the 'root' parameter 228 """ 229 self._root = self.Node(self, 0, '/', '/') 230 self._root.Scan() 231 232 def GetRoot(self): 233 """Get the root Node of the device tree 234 235 Returns: 236 The root Node object 237 """ 238 return self._root 239 240 def GetNode(self, path): 241 """Look up a node from its path 242 243 Args: 244 path: Path to look up, e.g. '/microcode/update@0' 245 Returns: 246 Node object, or None if not found 247 """ 248 node = self._root 249 for part in path.split('/')[1:]: 250 node = node._FindNode(part) 251 if not node: 252 return None 253 return node 254 255 def Flush(self): 256 """Flush device tree changes back to the file 257 258 If the device tree has changed in memory, write it back to the file. 259 Subclasses can implement this if needed. 260 """ 261 pass 262 263 def Pack(self): 264 """Pack the device tree down to its minimum size 265 266 When nodes and properties shrink or are deleted, wasted space can 267 build up in the device tree binary. Subclasses can implement this 268 to remove that spare space. 269 """ 270 pass 271