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