On Mon, Jun 25, 2018 at 09:43:08PM -0600, Simon Glass wrote: > 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 AsFdt() will > return the completed device-tree object. > > Signed-off-by: Simon Glass <sjg@xxxxxxxxxxxx> Looking pretty good, but I still have some comments. > --- > > Changes in v4: > - Drop patches previously indicated as applied (but not yet present > in master) Ah, sorry, I keep forgetting to push to the kernel.org master tree. Note that you can often get my "tentative" master from github: git://github.com/dgibson/dtc [Background: The reason this happens is that I usually push first to github to run the Travis CI build then, if that passes, push to kernel.org. However, if the Travis build is delayed - which often happens when I have a bunch of much slower qemu builds in my Travis queue - then I sometimes forgot to look back and push to kernel.org] > - Make resizing reactive (to -FDT_ERR_NOTFOUND) instead of proactive > > 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() > - 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 > - Use fdtsw instead of fdtrw as internal variable in FdtSw > > pylibfdt/libfdt.i | 296 ++++++++++++++++++++++++++++++++++++++++ > tests/pylibfdt_tests.py | 89 +++++++++++- > 2 files changed, 379 insertions(+), 6 deletions(-) > > diff --git a/pylibfdt/libfdt.i b/pylibfdt/libfdt.i > index aed5390..abd8e61 100644 > --- a/pylibfdt/libfdt.i > +++ b/pylibfdt/libfdt.i > @@ -57,6 +57,18 @@ > %{ > #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 %{ > @@ -88,6 +100,7 @@ import struct > # 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): > @@ -693,6 +706,275 @@ class Property(bytearray): > if 0 in self[:-1]: > raise ValueError('Property contains embedded nul characters') > return self[:-1].decode('utf-8') > + > + > +class FdtSw(object): So I realized one drawback of having a completely separate FdtSw object is that the read-only methods of class Fdt won't be usable on it, whereas the read only functions in libfdt do (by design) work on sw mode trees. > + """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 > + if size_hint < 0: > + raise ValueError('Cannot use a negative size hint') You could just let the bytearray() immediately below throw the exception. > + 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 I'm pretty sure this isn't what you meant. You initialize fdtsw to a new bytearray, then throw that away, replacing it with a reference to the existing one inside this object. > + 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._fdtsw) + 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) > + # This line should not be needed. Without it, we get BADSTRUCTURE > + fdt[:len(self._fdtsw)] = self._fdtsw This is weird, we really need to track down why it's not working with that line. It definitely shouldn't be necessary. > + err = check_err(fdt_resize(self._fdtsw, fdt, size)) > + if err: > + return err IIUC this can't ever happen - since you didn't pass any quiet values to check_err(), any error result should have already resulted in an exception. > + 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 > + """ > + while self.check_space(fdt_add_reservemap_entry(self._fdtsw, 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._fdtsw)): > + 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._fdtsw, 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._fdtsw, 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._fdtsw, 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._fdtsw, 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._fdtsw, 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._fdtsw, 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._fdtsw)): > + 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') Neat trick. > + The node is automatically completed with a call to end_node() when the > + context exits. > + """ > + def __init__(self, fdtsw, name): > + self._fdtsw = fdtsw > + self._name = name > + > + def __enter__(self): > + self._fdtsw.begin_node(self._name) > + > + def __exit__(self, type, value, traceback): > + self._fdtsw.end_node() > %} > > %rename(fdt_property) fdt_property_func; > @@ -757,6 +1039,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); > @@ -800,4 +1087,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 9f3e55a..0f04007 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 > @@ -481,5 +496,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() -- David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
Attachment:
signature.asc
Description: PGP signature