It is useful to be able to create a device tree from scratch using software. This is supported in libfdt but not currently available in the Python bindings. Add a new FdtSw class to handle this, with various methods corresponding to the libfdt functions. When the tree is complete, calling as_fdt() will return the completed device-tree object. The FdtSw class automatically expands the tree before each operation, to ensure that there is enough space. There is no need to resize the tree manually, although a size hint given to the constructor may improve performance slightly for large trees. Signed-off-by: Simon Glass <sjg@xxxxxxxxxxxx> --- Changes in v3: - Add a separate comment explaining the purpose of the FdtSw context manager - Adjust operation of as_fdt() to include calling fdt_finish() - Automatically resize the sw tree before it runs out of space - Drop patches already applied (or marked as such) - Drop unnecessary data copy in resize() an rely on fdt_resize() instead - Rename AddNode() to add_node() - Rename AsFdt() to as_fdt() - Rename _fdt_property() to fdt_property_stub() - Rename the size passed to the FdtSw() to a hint only - Update commit message to describe auto-expansion with FdtSw - Update cover letter - Use fdtsw instead of fdtrw as internal variable in FdtSw Changes in v2: None pylibfdt/libfdt.i | 302 ++++++++++++++++++++++++++++++++++++++++ tests/pylibfdt_tests.py | 89 +++++++++++- 2 files changed, 385 insertions(+), 6 deletions(-) diff --git a/pylibfdt/libfdt.i b/pylibfdt/libfdt.i index 93694da..618d15b 100644 --- a/pylibfdt/libfdt.i +++ b/pylibfdt/libfdt.i @@ -54,9 +54,25 @@ %include <stdint.i> +%constant int SIZEOF_fdt_reserve_entry = sizeof(struct fdt_reserve_entry); +%constant int SIZEOF_fdt_node_header = sizeof(struct fdt_node_header); +%constant int SIZEOF_fdt_property = sizeof(struct fdt_property); + %{ #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 %{ @@ -89,6 +105,8 @@ import struct # instead of raising an exception. QUIET_NOTFOUND = (NOTFOUND,) +# Similar for the -ENOSPACE error +QUIET_NOSPACE = (NOSPACE,) class FdtException(Exception): """An exception caused by an error such as one of the codes above""" @@ -686,6 +704,276 @@ class Property(bytearray): if 0 in self[:-1]: raise ValueError('Property contains embedded nul characters') return self[:-1].decode('utf-8') + + +class FdtSw(object): + """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 + quiet: Errors to ignore (empty to raise on all errors) + + 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 + if size_hint < 0: + raise ValueError('Cannot use a negative size hint') + fdtsw = bytearray(size_hint) + err = check_err(fdt_create(fdtsw, size_hint)) + if err: + return err + self._fdtsw = 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._fdtsw) + fdtsw = self._fdtsw + check_err(fdt_finish(fdtsw)) + return Fdt(fdtsw) + + def add_space(self, inc_size): + """Expand the device tree enough to hold new data + + This makes sure that the device tree is large enough to hold the data + size indicated. If not, it is expanded to suit. + """ + free_space = fdt_free_space(self._fdtsw) + # allow a bit of margin for two nul terminators a node name and its + # string property. The worst case is 4 bytes of nul for each if we are + # unlucky with the alignment. We also add 4 more bytes in case the + # previous property finished unaligned. + if inc_size + 12 > free_space: + new_size = ((len(self._fdtsw) + inc_size + + self.INC_SIZE - 1) & ~(self.INC_SIZE - 1)) + self.resize(new_size) + + 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) + # This line should not be needed. Without it, we get BADSTRUCTURE + fdt[:len(self._fdtsw)] = self._fdtsw + err = check_err(fdt_resize(self._fdtsw, fdt, size)) + if err: + return err + self._fdtsw = 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 + """ + self.add_space(SIZEOF_fdt_reserve_entry) + check_err(fdt_add_reservemap_entry(self._fdtsw, addr, size)) + + def finish_reservemap(self): + """Indicate that there are no more reserve map entries to add + + Raises: + FdtException on any error + """ + check_err(fdt_finish_reservemap(self._fdtsw), QUIET_NOSPACE) + + 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 + """ + self.add_space(SIZEOF_fdt_node_header + len(name)) + return check_err(fdt_begin_node(self._fdtsw, name), QUIET_NOSPACE) + + 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 + """ + self.add_space(SIZEOF_fdt_property + len(name) + len(string)) + return check_err(fdt_property_string(self._fdtsw, name, string), + QUIET_NOSPACE) + + 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 + """ + self.add_space(SIZEOF_fdt_property + len(name) + 4) + return check_err(fdt_property_u32(self._fdtsw, name, val), + QUIET_NOSPACE) + + 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 + """ + self.add_space(SIZEOF_fdt_property + len(name) + 8) + return check_err(fdt_property_u64(self._fdtsw, name, val), + QUIET_NOSPACE) + + 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 + """ + self.add_space(SIZEOF_fdt_property + len(name) + 4) + return check_err(fdt_property_cell(self._fdtsw, name, val), + QUIET_NOSPACE) + + 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 + """ + self.add_space(SIZEOF_fdt_property + len(name) + len(val)) + return check_err(fdt_property_stub(self._fdtsw, name, val, len(val)), + QUIET_NOSPACE) + + 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 + """ + self.add_space(4) + return check_err(fdt_end_node(self._fdtsw), QUIET_NOSPACE) + + 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._fdtsw, 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, fdt, name): + self._fdt = fdt + self._name = name + + def __enter__(self): + check_err(fdt_begin_node(self._fdt, self._name)) + + def __exit__(self, type, value, traceback): + check_err(fdt_end_node(self._fdt)) %} %rename(fdt_property) fdt_property_func; @@ -750,6 +1038,11 @@ typedef uint32_t fdt32_t; $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); @@ -793,4 +1086,13 @@ 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> diff --git a/tests/pylibfdt_tests.py b/tests/pylibfdt_tests.py index 833aaa7..45b2e95 100644 --- a/tests/pylibfdt_tests.py +++ b/tests/pylibfdt_tests.py @@ -56,17 +56,32 @@ import unittest sys.path.insert(0, '../pylibfdt') import libfdt -from libfdt import Fdt, FdtException, QUIET_NOTFOUND, QUIET_ALL - -small_size = 160 -full_size = 1024 +from libfdt import Fdt, FdtSw, FdtException, QUIET_NOTFOUND, QUIET_ALL + +TEST_ADDR_1H = 0xdeadbeef +TEST_ADDR_1L = 0x00000000 +TEST_ADDR_1 = (TEST_ADDR_1H << 32) | TEST_ADDR_1L +TEST_ADDR_1 = 0x8000000000000000 +TEST_SIZE_1H = 0x00000000 +TEST_SIZE_1L = 0x00100000 +TEST_SIZE_1 = (TEST_SIZE_1H << 32) | TEST_SIZE_1L +TEST_ADDR_2H = 0 +TEST_ADDR_2L = 123456789 +TEST_ADDR_2 = (TEST_ADDR_2H << 32) | TEST_ADDR_2L +TEST_SIZE_2H = 0 +TEST_SIZE_2L = 010000 +TEST_SIZE_2 = (TEST_SIZE_2H << 32) | TEST_SIZE_2L TEST_VALUE_1 = 0xdeadbeef +TEST_VALUE_2 = 123456789 TEST_VALUE64_1H = 0xdeadbeef TEST_VALUE64_1L = 0x01abcdef TEST_VALUE64_1 = (TEST_VALUE64_1H << 32) | TEST_VALUE64_1L +PHANDLE_1 = 0x2000 +PHANDLE_2 = 0x2001 + TEST_STRING_1 = 'hello world' TEST_STRING_2 = 'hi world' TEST_STRING_3 = u'unicode ' + unichr(467) @@ -94,8 +109,8 @@ def _ReadFdt(fname): """ return libfdt.Fdt(open(fname).read()) -class PyLibfdtTests(unittest.TestCase): - """Test class for pylibfdt +class PyLibfdtBasicTests(unittest.TestCase): + """Test class for basic pylibfdt access functions Properties: fdt: Device tree file used for testing @@ -480,5 +495,67 @@ class PyLibfdtTests(unittest.TestCase): self.assertIn('embedded nul', str(e.exception)) +class PyLibfdtSwTests(unittest.TestCase): + """Test class for pylibfdt sequential-write DT creation + """ + def assertOk(self, err_code): + self.assertEquals(0, err_code) + + def testCreate(self): + # First check the minimum size and also the FdtSw() constructor + with self.assertRaisesRegexp(FdtException, get_err(libfdt.NOSPACE)): + self.assertEquals(-libfdt.NOSPACE, FdtSw(3)) + + sw = FdtSw() + sw.add_reservemap_entry(TEST_ADDR_1, TEST_SIZE_1) + sw.add_reservemap_entry(TEST_ADDR_2, TEST_SIZE_2) + sw.finish_reservemap() + + sw.begin_node('') + sw.property_string('compatible', 'test_tree1') + sw.property_u32('prop-int', TEST_VALUE_1) + + sw.property_u32('prop-int', TEST_VALUE_1) + sw.property_u64('prop-int64', TEST_VALUE64_1) + sw.property_string('prop-str', TEST_STRING_1) + sw.property_u32('#address-cells', 1) + sw.property_u32('#size-cells', 0) + + sw.begin_node('subnode@1') + sw.property_string('compatible', 'subnode1') + sw.property_u32('reg', 1) + sw.property_cell('prop-int', TEST_VALUE_1) + sw.begin_node('subsubnode') + sw.property('compatible', 'subsubnode1\0subsubnode') + sw.property_cell('prop-int', TEST_VALUE_1) + sw.end_node() + sw.begin_node('ss1') + sw.end_node() + sw.end_node() + + for i in range(9): + with sw.add_node('subnode@%d' % i): + sw.property_u32('reg', 2) + sw.property_cell('linux,phandle', PHANDLE_1) + sw.property_cell('prop-int', TEST_VALUE_2) + sw.property_u32('#address-cells', 1) + sw.property_u32('#size-cells', 0) + with sw.add_node('subsubnode@0'): + sw.property_u32('reg', 0) + sw.property_cell('phandle', PHANDLE_2) + sw.property('compatible', 'subsubnode2\0subsubnode') + sw.property_cell('prop-int', TEST_VALUE_2) + with sw.add_node('ss2'): + pass + sw.end_node() + + fdt = sw.as_fdt() + self.assertEqual(2, fdt.num_mem_rsv()) + self.assertEqual([TEST_ADDR_1, TEST_SIZE_1], fdt.get_mem_rsv(0)) + + # Make sure we did at least two resizes + self.assertTrue(len(fdt.as_bytearray()) > FdtSw.INC_SIZE * 2) + + if __name__ == "__main__": unittest.main() -- 2.18.0.rc1.244.gcf134e6275-goog -- To unsubscribe from this list: send the line "unsubscribe devicetree-compiler" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html