/* SPDX-License-Identifier: GPL-2.0+ OR BSD-2-Clause */ /* * pylibfdt - Flat Device Tree manipulation in Python * Copyright (C) 2017 Google, Inc. * Written by Simon Glass */ %module libfdt %include %{ #define SWIG_FILE_WITH_INIT #include "libfdt.h" /* * We rename this function here to avoid problems with swig, since we also have * a struct called fdt_property. That struct causes swig to create a class in * libfdt.py called fdt_property(), which confuses things. */ static int fdt_property_stub(void *fdt, const char *name, const char *val, int len) { return fdt_property(fdt, name, val, len); } %} %pythoncode %{ import struct # Error codes, corresponding to FDT_ERR_... in libfdt.h (NOTFOUND, EXISTS, NOSPACE, BADOFFSET, BADPATH, BADPHANDLE, BADSTATE, TRUNCATED, BADMAGIC, BADVERSION, BADSTRUCTURE, BADLAYOUT, INTERNAL, BADNCELLS, BADVALUE, BADOVERLAY, NOPHANDLES) = QUIET_ALL = range(1, 18) # QUIET_ALL can be passed as the 'quiet' parameter to avoid exceptions # altogether. All # functions passed this value will return an error instead # of raising an exception. # Pass this as the 'quiet' parameter to return -ENOTFOUND on NOTFOUND errors, # instead of raising an exception. QUIET_NOTFOUND = (NOTFOUND,) QUIET_NOSPACE = (NOSPACE,) class FdtException(Exception): """An exception caused by an error such as one of the codes above""" def __init__(self, err): self.err = err def __str__(self): return 'pylibfdt error %d: %s' % (self.err, fdt_strerror(self.err)) def strerror(fdt_err): """Get the string for an error number Args: fdt_err: Error number (-ve) Returns: String containing the associated error """ return fdt_strerror(fdt_err) def check_err(val, quiet=()): """Raise an error if the return value is -ve This is used to check for errors returned by libfdt C functions. Args: val: Return value from a libfdt function quiet: Errors to ignore (empty to raise on all errors) Returns: val if val >= 0 Raises FdtException if val < 0 """ if val < 0: if -val not in quiet: raise FdtException(val) return val def check_err_null(val, quiet=()): """Raise an error if the return value is NULL This is used to check for a NULL return value from certain libfdt C functions Args: val: Return value from a libfdt function quiet: Errors to ignore (empty to raise on all errors) Returns: val if val is a list, None if not Raises FdtException if val indicates an error was reported and the error is not in @quiet. """ # Normally a list is returned which contains the data and its length. # If we get just an integer error code, it means the function failed. if not isinstance(val, list): if -val not in quiet: raise FdtException(val) return val class FdtRo(object): """Class for a read-only device-tree This is a base class used by FdtRw (read-write access) and FdtSw (sequential-write access). It implements read-only access to the device tree. Here are the three classes and when you should use them: FdtRo - read-only access to an existing FDT FdtRw - read-write access to an existing FDT (most common case) FdtSw - for creating a new FDT, as well as allowing read-only access """ def __init__(self, data): self._fdt = bytearray(data) check_err(fdt_check_header(self._fdt)); def as_bytearray(self): """Get the device tree contents as a bytearray This can be passed directly to libfdt functions that access a const void * for the device tree. Returns: bytearray containing the device tree """ return bytearray(self._fdt) def next_node(self, nodeoffset, depth, quiet=()): """Find the next subnode Args: nodeoffset: Node offset of previous node depth: The depth of the node at nodeoffset. This is used to calculate the depth of the returned node quiet: Errors to ignore (empty to raise on all errors) Returns: Typle: Offset of the next node, if any, else a -ve error Depth of the returned node, if any, else undefined Raises: FdtException if no more nodes found or other error occurs """ return check_err(fdt_next_node(self._fdt, nodeoffset, depth), quiet) def first_subnode(self, nodeoffset, quiet=()): """Find the first subnode of a parent node Args: nodeoffset: Node offset of parent node quiet: Errors to ignore (empty to raise on all errors) Returns: The offset of the first subnode, if any Raises: FdtException if no subnodes found or other error occurs """ return check_err(fdt_first_subnode(self._fdt, nodeoffset), quiet) def next_subnode(self, nodeoffset, quiet=()): """Find the next subnode Args: nodeoffset: Node offset of previous subnode quiet: Errors to ignore (empty to raise on all errors) Returns: The offset of the next subnode, if any Raises: FdtException if no more subnodes found or other error occurs """ return check_err(fdt_next_subnode(self._fdt, nodeoffset), quiet) def magic(self): """Return the magic word from the header Returns: Magic word """ return fdt_magic(self._fdt) def totalsize(self): """Return the total size of the device tree Returns: Total tree size in bytes """ return fdt_totalsize(self._fdt) def off_dt_struct(self): """Return the start of the device-tree struct area Returns: Start offset of struct area """ return fdt_off_dt_struct(self._fdt) def off_dt_strings(self): """Return the start of the device-tree string area Returns: Start offset of string area """ return fdt_off_dt_strings(self._fdt) def off_mem_rsvmap(self): """Return the start of the memory reserve map Returns: Start offset of memory reserve map """ return fdt_off_mem_rsvmap(self._fdt) def version(self): """Return the version of the device tree Returns: Version number of the device tree """ return fdt_version(self._fdt) def last_comp_version(self): """Return the last compatible version of the device tree Returns: Last compatible version number of the device tree """ return fdt_last_comp_version(self._fdt) def boot_cpuid_phys(self): """Return the physical boot CPU ID Returns: Physical boot CPU ID """ return fdt_boot_cpuid_phys(self._fdt) def size_dt_strings(self): """Return the start of the device-tree string area Returns: Start offset of string area """ return fdt_size_dt_strings(self._fdt) def size_dt_struct(self): """Return the start of the device-tree struct area Returns: Start offset of struct area """ return fdt_size_dt_struct(self._fdt) def num_mem_rsv(self, quiet=()): """Return the number of memory reserve-map records Returns: Number of memory reserve-map records """ return check_err(fdt_num_mem_rsv(self._fdt), quiet) def get_mem_rsv(self, index, quiet=()): """Return the indexed memory reserve-map record Args: index: Record to return (0=first) Returns: Number of memory reserve-map records """ return check_err(fdt_get_mem_rsv(self._fdt, index), quiet) def subnode_offset(self, parentoffset, name, quiet=()): """Get the offset of a named subnode Args: parentoffset: Offset of the parent node to check name: Name of the required subnode, e.g. 'subnode@1' quiet: Errors to ignore (empty to raise on all errors) Returns: The node offset of the found node, if any Raises FdtException if there is no node with that name, or other error """ return check_err(fdt_subnode_offset(self._fdt, parentoffset, name), quiet) def path_offset(self, path, quiet=()): """Get the offset for a given path Args: path: Path to the required node, e.g. '/node@3/subnode@1' quiet: Errors to ignore (empty to raise on all errors) Returns: Node offset Raises FdtException if the path is not valid or not found """ return check_err(fdt_path_offset(self._fdt, path), quiet) def get_name(self, nodeoffset): """Get the name of a node Args: nodeoffset: Offset of node to check Returns: Node name Raises: FdtException on error (e.g. nodeoffset is invalid) """ return check_err_null(fdt_get_name(self._fdt, nodeoffset))[0] def first_property_offset(self, nodeoffset, quiet=()): """Get the offset of the first property in a node offset Args: nodeoffset: Offset to the node to check quiet: Errors to ignore (empty to raise on all errors) Returns: Offset of the first property Raises FdtException if the associated node has no properties, or some other error occurred """ return check_err(fdt_first_property_offset(self._fdt, nodeoffset), quiet) def next_property_offset(self, prop_offset, quiet=()): """Get the next property in a node Args: prop_offset: Offset of the previous property quiet: Errors to ignore (empty to raise on all errors) Returns: Offset of the next property Raises: FdtException if the associated node has no more properties, or some other error occurred """ return check_err(fdt_next_property_offset(self._fdt, prop_offset), quiet) def get_property_by_offset(self, prop_offset, quiet=()): """Obtains a property that can be examined Args: prop_offset: Offset of property (e.g. from first_property_offset()) quiet: Errors to ignore (empty to raise on all errors) Returns: Property object, or None if not found Raises: FdtException on error (e.g. invalid prop_offset or device tree format) """ pdata = check_err_null( fdt_get_property_by_offset(self._fdt, prop_offset), quiet) if isinstance(pdata, (int)): return pdata return Property(pdata[0], pdata[1]) def getprop(self, nodeoffset, prop_name, quiet=()): """Get a property from a node Args: nodeoffset: Node offset containing property to get prop_name: Name of property to get quiet: Errors to ignore (empty to raise on all errors) Returns: Value of property as a Property object (which can be used as a bytearray/string), or -ve error number. On failure, returns an integer error Raises: FdtError if any error occurs (e.g. the property is not found) """ pdata = check_err_null(fdt_getprop(self._fdt, nodeoffset, prop_name), quiet) if isinstance(pdata, (int)): return pdata return Property(prop_name, bytearray(pdata[0])) def get_phandle(self, nodeoffset): """Get the phandle of a node Args: nodeoffset: Node offset to check Returns: phandle of node, or 0 if the node has no phandle or another error occurs """ return fdt_get_phandle(self._fdt, nodeoffset) def parent_offset(self, nodeoffset, quiet=()): """Get the offset of a node's parent Args: nodeoffset: Node offset to check quiet: Errors to ignore (empty to raise on all errors) Returns: The offset of the parent node, if any Raises: FdtException if no parent found or other error occurs """ return check_err(fdt_parent_offset(self._fdt, nodeoffset), quiet) def node_offset_by_phandle(self, phandle, quiet=()): """Get the offset of a node with the given phandle Args: phandle: Phandle to search for quiet: Errors to ignore (empty to raise on all errors) Returns: The offset of node with that phandle, if any Raises: FdtException if no node found or other error occurs """ return check_err(fdt_node_offset_by_phandle(self._fdt, phandle), quiet) class Fdt(FdtRo): """Device tree class, supporting all operations The Fdt object is created is created from a device tree binary file, e.g. with something like: fdt = Fdt(open("filename.dtb").read()) Operations can then be performed using the methods in this class. Each method xxx(args...) corresponds to a libfdt function fdt_xxx(fdt, args...). All methods raise an FdtException if an error occurs. To avoid this behaviour a 'quiet' parameter is provided for some functions. This defaults to empty, but you can pass a list of errors that you expect. If one of these errors occurs, the function will return an error number (e.g. -NOTFOUND). """ def __init__(self, data): FdtRo.__init__(self, data) @staticmethod def create_empty_tree(size, quiet=()): """Create an empty device tree ready for use Args: size: Size of device tree in bytes Returns: Fdt object containing the device tree """ data = bytearray(size) err = check_err(fdt_create_empty_tree(data, size), quiet) if err: return err return Fdt(data) def resize(self, size, quiet=()): """Move the device tree into a larger or smaller space This creates a new device tree of size @size and moves the existing device tree contents over to that. It can be used to create more space in a device tree. Note that the Fdt object remains the same, but it now has a new bytearray holding the contents. Args: size: Required new size of device tree in bytes """ fdt = bytearray(size) err = check_err(fdt_open_into(self._fdt, fdt, size), quiet) if err: return err self._fdt = fdt def pack(self, quiet=()): """Pack the device tree to remove unused space This adjusts the tree in place. Args: quiet: Errors to ignore (empty to raise on all errors) Returns: Error code, or 0 if OK Raises: FdtException if any error occurs """ err = check_err(fdt_pack(self._fdt), quiet) if err: return err del self._fdt[self.totalsize():] return err def set_name(self, nodeoffset, name, quiet=()): """Set the name of a node Args: nodeoffset: Node offset of node to update name: New node name (string without \0) Returns: Error code, or 0 if OK Raises: FdtException if no parent found or other error occurs """ if chr(0) in name: raise ValueError('Property contains embedded nul characters') return check_err(fdt_set_name(self._fdt, nodeoffset, name), quiet) def setprop(self, nodeoffset, prop_name, val, quiet=()): """Set the value of a property Args: nodeoffset: Node offset containing the property to create/update prop_name: Name of property val: Value to write (string or bytearray) quiet: Errors to ignore (empty to raise on all errors) Returns: Error code, or 0 if OK Raises: FdtException if no parent found or other error occurs """ return check_err(fdt_setprop(self._fdt, nodeoffset, prop_name, val, len(val)), quiet) def setprop_u32(self, nodeoffset, prop_name, val, quiet=()): """Set the value of a property Args: nodeoffset: Node offset containing the property to create/update prop_name: Name of property val: Value to write (integer) quiet: Errors to ignore (empty to raise on all errors) Returns: Error code, or 0 if OK Raises: FdtException if no parent found or other error occurs """ return check_err(fdt_setprop_u32(self._fdt, nodeoffset, prop_name, val), quiet) def setprop_u64(self, nodeoffset, prop_name, val, quiet=()): """Set the value of a property Args: nodeoffset: Node offset containing the property to create/update prop_name: Name of property val: Value to write (integer) quiet: Errors to ignore (empty to raise on all errors) Returns: Error code, or 0 if OK Raises: FdtException if no parent found or other error occurs """ return check_err(fdt_setprop_u64(self._fdt, nodeoffset, prop_name, val), quiet) def setprop_str(self, nodeoffset, prop_name, val, quiet=()): """Set the string value of a property The property is set to the string, with a nul terminator added Args: nodeoffset: Node offset containing the property to create/update prop_name: Name of property val: Value to write (string without nul terminator). Unicode is supposed by encoding to UTF-8 quiet: Errors to ignore (empty to raise on all errors) Returns: Error code, or 0 if OK Raises: FdtException if no parent found or other error occurs """ val = val.encode('utf-8') + '\0' return check_err(fdt_setprop(self._fdt, nodeoffset, prop_name, val, len(val)), quiet) def delprop(self, nodeoffset, prop_name, quiet=()): """Delete a property from a node Args: nodeoffset: Node offset containing property to delete prop_name: Name of property to delete quiet: Errors to ignore (empty to raise on all errors) Returns: Error code, or 0 if OK Raises: FdtError if the property does not exist, or another error occurs """ return check_err(fdt_delprop(self._fdt, nodeoffset, prop_name), quiet) def add_subnode(self, parentoffset, name, quiet=()): """Add a new subnode to a node Args: parentoffset: Parent offset to add the subnode to name: Name of node to add Returns: offset of the node created, or negative error code on failure Raises: FdtError if there is not enough space, or another error occurs """ return check_err(fdt_add_subnode(self._fdt, parentoffset, name), quiet) def del_node(self, nodeoffset, quiet=()): """Delete a node Args: nodeoffset: Offset of node to delete Returns: Error code, or 0 if OK Raises: FdtError if an error occurs """ return check_err(fdt_del_node(self._fdt, nodeoffset), quiet) class Property(bytearray): """Holds a device tree property name and value. This holds a copy of a property taken from the device tree. It does not reference the device tree, so if anything changes in the device tree, a Property object will remain valid. Properties: name: Property name value: Property value as a bytearray """ def __init__(self, name, value): bytearray.__init__(self, value) self.name = name def as_cell(self, fmt): return struct.unpack('>' + fmt, self)[0] def as_uint32(self): return self.as_cell('L') def as_int32(self): return self.as_cell('l') def as_uint64(self): return self.as_cell('Q') def as_int64(self): return self.as_cell('q') def as_str(self): """Unicode is supported by decoding from UTF-8""" if self[-1] != 0: raise ValueError('Property lacks nul termination') if 0 in self[:-1]: raise ValueError('Property contains embedded nul characters') return self[:-1].decode('utf-8') class FdtSw(FdtRo): """Software interface to create a device tree from scratch The methods in this class work by adding to an existing 'partial' device tree buffer of a fixed size created by instantiating this class. When the tree is complete, call as_fdt() to obtain a device tree ready to be used. Similarly with nodes, a new node is started with begin_node() and finished with end_node(). The context manager functions can be used to make this a bit easier: # First create the device tree with a node and property: sw = FdtSw() with sw.add_node('node'): sw.property_u32('reg', 2) fdt = sw.as_fdt() # Now we can use it as a real device tree fdt.setprop_u32(0, 'reg', 3) The size hint provides a starting size for the space to be used by the device tree. This will be increased automatically as needed as new items are added to the tree. """ INC_SIZE = 1024 # Expand size by this much when out of space def __init__(self, size_hint=None): """Create a new FdtSw object Args: size_hint: A hint as to the initial size to use Raises: ValueError if size_hint is negative Returns: FdtSw object on success, else integer error code (if not raising) """ if not size_hint: size_hint = self.INC_SIZE fdtsw = bytearray(size_hint) err = check_err(fdt_create(fdtsw, size_hint)) if err: return err self._fdt = fdtsw def as_fdt(self): """Convert a FdtSw into an Fdt so it can be accessed as normal Creates a new Fdt object from the work-in-progress device tree. This does not call fdt_finish() on the current object, so it is possible to add more nodes/properties and call as_fdt() again to get an updated tree. Returns: Fdt object allowing access to the newly created device tree """ fdtsw = bytearray(self._fdt) check_err(fdt_finish(fdtsw)) return Fdt(fdtsw) def check_space(self, val): """Check if we need to add more space to the FDT This should be called with the error code from an operation. If this is -NOSPACE then the FDT will be expanded to have more space, and True will be returned, indicating that the operation needs to be tried again. Args: val: Return value from the operation that was attempted Returns: True if the operation must be retried, else False """ if check_err(val, QUIET_NOSPACE) < 0: self.resize(len(self._fdt) + self.INC_SIZE) return True return False def resize(self, size): """Resize the buffer to accommodate a larger tree Args: size: New size of tree Raises: FdtException on any error """ fdt = bytearray(size) err = check_err(fdt_resize(self._fdt, fdt, size)) self._fdt = fdt def add_reservemap_entry(self, addr, size): """Add a new memory reserve map entry Once finished adding, you must call finish_reservemap(). Args: addr: 64-bit start address size: 64-bit size Raises: FdtException on any error """ while self.check_space(fdt_add_reservemap_entry(self._fdt, addr, size)): pass def finish_reservemap(self): """Indicate that there are no more reserve map entries to add Raises: FdtException on any error """ while self.check_space(fdt_finish_reservemap(self._fdt)): pass def begin_node(self, name): """Begin a new node Use this before adding properties to the node. Then call end_node() to finish it. You can also use the context manager as shown in the FdtSw class comment. Args: name: Name of node to begin Raises: FdtException on any error """ while self.check_space(fdt_begin_node(self._fdt, name)): pass def property_string(self, name, string): """Add a property with a string value The string will be nul-terminated when written to the device tree Args: name: Name of property to add string: String value of property Raises: FdtException on any error """ while self.check_space(fdt_property_string(self._fdt, name, string)): pass def property_u32(self, name, val): """Add a property with a 32-bit value Write a single-cell value to the device tree Args: name: Name of property to add val: Value of property Raises: FdtException on any error """ while self.check_space(fdt_property_u32(self._fdt, name, val)): pass def property_u64(self, name, val): """Add a property with a 64-bit value Write a double-cell value to the device tree in big-endian format Args: name: Name of property to add val: Value of property Raises: FdtException on any error """ while self.check_space(fdt_property_u64(self._fdt, name, val)): pass def property_cell(self, name, val): """Add a property with a single-cell value Write a single-cell value to the device tree Args: name: Name of property to add val: Value of property quiet: Errors to ignore (empty to raise on all errors) Raises: FdtException on any error """ while self.check_space(fdt_property_cell(self._fdt, name, val)): pass def property(self, name, val): """Add a property Write a new property with the given value to the device tree. The value is taken as is and is not nul-terminated Args: name: Name of property to add val: Value of property quiet: Errors to ignore (empty to raise on all errors) Raises: FdtException on any error """ while self.check_space(fdt_property_stub(self._fdt, name, val, len(val))): pass def end_node(self): """End a node Use this after adding properties to a node to close it off. You can also use the context manager as shown in the FdtSw class comment. Args: quiet: Errors to ignore (empty to raise on all errors) Raises: FdtException on any error """ while self.check_space(fdt_end_node(self._fdt)): pass def add_node(self, name): """Create a new context for adding a node When used in a 'with' clause this starts a new node and finishes it afterward. Args: name: Name of node to add """ return NodeAdder(self, name) class NodeAdder(): """Class to provide a node context This allows you to add nodes in a more natural way: with fdtsw.add_node('name'): fdtsw.property_string('test', 'value') The node is automatically completed with a call to end_node() when the context exits. """ def __init__(self, fdtsw, name): self._fdt = fdtsw self._name = name def __enter__(self): self._fdt.begin_node(self._name) def __exit__(self, type, value, traceback): self._fdt.end_node() %} %rename(fdt_property) fdt_property_func; /* * fdt32_t is a big-endian 32-bit value defined to uint32_t in libfdt_env.h * so use the same type here. */ typedef uint32_t fdt32_t; %include "libfdt/fdt.h" %include "typemaps.i" /* Most functions don't change the device tree, so use a const void * */ %typemap(in) (const void *)(const void *fdt) { if (!PyByteArray_Check($input)) { SWIG_exception_fail(SWIG_TypeError, "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = (void *)PyByteArray_AsString($input); fdt = $1; fdt = fdt; /* avoid unused variable warning */ } /* Some functions do change the device tree, so use void * */ %typemap(in) (void *)(const void *fdt) { if (!PyByteArray_Check($input)) { SWIG_exception_fail(SWIG_TypeError, "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = PyByteArray_AsString($input); fdt = $1; fdt = fdt; /* avoid unused variable warning */ } /* typemap used for fdt_get_property_by_offset() */ %typemap(out) (struct fdt_property *) { PyObject *buff; if ($1) { resultobj = PyString_FromString( fdt_string(fdt1, fdt32_to_cpu($1->nameoff))); buff = PyByteArray_FromStringAndSize( (const char *)($1 + 1), fdt32_to_cpu($1->len)); resultobj = SWIG_Python_AppendOutput(resultobj, buff); } } %apply int *OUTPUT { int *lenp }; /* typemap used for fdt_getprop() */ %typemap(out) (const void *) { if (!$1) $result = Py_None; else $result = Py_BuildValue("s#", $1, *arg4); } /* typemap used for fdt_setprop() */ %typemap(in) (const void *val) { $1 = PyString_AsString($input); /* char *str */ } /* typemap used for fdt_add_reservemap_entry() */ %typemap(in) uint64_t { $1 = PyLong_AsUnsignedLong($input); } /* typemaps used for fdt_next_node() */ %typemap(in, numinputs=1) int *depth (int depth) { depth = (int) PyInt_AsLong($input); $1 = &depth; } %typemap(argout) int *depth { PyObject *val = Py_BuildValue("i", *arg$argnum); resultobj = SWIG_Python_AppendOutput(resultobj, val); } %apply int *depth { int *depth }; /* typemaps for fdt_get_mem_rsv */ %typemap(in, numinputs=0) uint64_t * (uint64_t temp) { $1 = &temp; } %typemap(argout) uint64_t * { PyObject *val = PyLong_FromUnsignedLong(*arg$argnum); if (!result) { if (PyTuple_GET_SIZE(resultobj) == 0) resultobj = val; else resultobj = SWIG_Python_AppendOutput(resultobj, val); } } /* We have both struct fdt_property and a function fdt_property() */ %warnfilter(302) fdt_property; /* These are macros in the header so have to be redefined here */ uint32_t fdt_magic(const void *fdt); uint32_t fdt_totalsize(const void *fdt); uint32_t fdt_off_dt_struct(const void *fdt); uint32_t fdt_off_dt_strings(const void *fdt); uint32_t fdt_off_mem_rsvmap(const void *fdt); uint32_t fdt_version(const void *fdt); uint32_t fdt_last_comp_version(const void *fdt); uint32_t fdt_boot_cpuid_phys(const void *fdt); uint32_t fdt_size_dt_strings(const void *fdt); uint32_t fdt_size_dt_struct(const void *fdt); int fdt_property_string(void *fdt, const char *name, const char *val); int fdt_property_cell(void *fdt, const char *name, uint32_t val); /* * This function has a stub since the name fdt_property is used for both a * function and a struct, which confuses SWIG. */ int fdt_property_stub(void *fdt, const char *name, const char *val, int len); %include <../libfdt/libfdt.h>