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 FdtFallback and FdtNormal subclasses. See 18# fdt_select.py for how to 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 size = len(bytes) 100 strings = bytes.split('\0') 101 is_string = True 102 count = len(strings) - 1 103 if count > 0 and not strings[-1]: 104 for string in strings[:-1]: 105 if not string: 106 is_string = False 107 break 108 for ch in string: 109 if ch < ' ' or ch > '~': 110 is_string = False 111 break 112 else: 113 is_string = False 114 if is_string: 115 if count == 1: 116 return TYPE_STRING, strings[0] 117 else: 118 return TYPE_STRING, strings[:-1] 119 if size % 4: 120 if size == 1: 121 return TYPE_BYTE, bytes[0] 122 else: 123 return TYPE_BYTE, list(bytes) 124 val = [] 125 for i in range(0, size, 4): 126 val.append(bytes[i:i + 4]) 127 if size == 4: 128 return TYPE_INT, val[0] 129 else: 130 return TYPE_INT, val 131 132 def GetEmpty(self, type): 133 """Get an empty / zero value of the given type 134 135 Returns: 136 A single value of the given type 137 """ 138 if type == TYPE_BYTE: 139 return chr(0) 140 elif type == TYPE_INT: 141 return struct.pack('<I', 0); 142 elif type == TYPE_STRING: 143 return '' 144 else: 145 return True 146 147 def GetOffset(self): 148 """Get the offset of a property 149 150 This can be implemented by subclasses. 151 152 Returns: 153 The offset of the property (struct fdt_property) within the 154 file, or None if not known. 155 """ 156 return None 157 158class NodeBase: 159 """A device tree node 160 161 Properties: 162 offset: Integer offset in the device tree 163 name: Device tree node tname 164 path: Full path to node, along with the node name itself 165 _fdt: Device tree object 166 subnodes: A list of subnodes for this node, each a Node object 167 props: A dict of properties for this node, each a Prop object. 168 Keyed by property name 169 """ 170 def __init__(self, fdt, offset, name, path): 171 self._fdt = fdt 172 self._offset = offset 173 self.name = name 174 self.path = path 175 self.subnodes = [] 176 self.props = {} 177 178 def _FindNode(self, name): 179 """Find a node given its name 180 181 Args: 182 name: Node name to look for 183 Returns: 184 Node object if found, else None 185 """ 186 for subnode in self.subnodes: 187 if subnode.name == name: 188 return subnode 189 return None 190 191 def Scan(self): 192 """Scan the subnodes of a node 193 194 This should be implemented by subclasses 195 """ 196 raise NotImplementedError() 197 198 def DeleteProp(self, prop_name): 199 """Delete a property of a node 200 201 This should be implemented by subclasses 202 203 Args: 204 prop_name: Name of the property to delete 205 """ 206 raise NotImplementedError() 207 208class Fdt: 209 """Provides simple access to a flat device tree blob. 210 211 Properties: 212 fname: Filename of fdt 213 _root: Root of device tree (a Node object) 214 """ 215 def __init__(self, fname): 216 self._fname = fname 217 218 def Scan(self, root='/'): 219 """Scan a device tree, building up a tree of Node objects 220 221 This fills in the self._root property 222 223 Args: 224 root: Ignored 225 226 TODO(sjg@chromium.org): Implement the 'root' parameter 227 """ 228 self._root = self.Node(self, 0, '/', '/') 229 self._root.Scan() 230 231 def GetRoot(self): 232 """Get the root Node of the device tree 233 234 Returns: 235 The root Node object 236 """ 237 return self._root 238 239 def GetNode(self, path): 240 """Look up a node from its path 241 242 Args: 243 path: Path to look up, e.g. '/microcode/update@0' 244 Returns: 245 Node object, or None if not found 246 """ 247 node = self._root 248 for part in path.split('/')[1:]: 249 node = node._FindNode(part) 250 if not node: 251 return None 252 return node 253 254 def Flush(self): 255 """Flush device tree changes back to the file 256 257 If the device tree has changed in memory, write it back to the file. 258 Subclasses can implement this if needed. 259 """ 260 pass 261 262 def Pack(self): 263 """Pack the device tree down to its minimum size 264 265 When nodes and properties shrink or are deleted, wasted space can 266 build up in the device tree binary. Subclasses can implement this 267 to remove that spare space. 268 """ 269 pass 270