Re: [PATCH v4 1/1] pylibfdt: Support the sequential-write interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]



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


[Index of Archives]     [Device Tree]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux