Re: [PATCH v3 1/4] Add an initial Python library for libfdt

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



Hi David,

On 4 December 2016 at 23:23, Simon Glass <sjg@xxxxxxxxxxxx> wrote:
> Hi David,
>
> On 4 December 2016 at 21:35, David Gibson <david@xxxxxxxxxxxxxxxxxxxxx> wrote:
>> On Sat, Dec 03, 2016 at 05:48:07PM -0700, Simon Glass wrote:
>>> Add Python bindings for a bare-bones set of libfdt functions. These allow
>>> navigating the tree and reading node names and properties.
>>>
>>> Signed-off-by: Simon Glass <sjg@xxxxxxxxxxxx>
>>> ---
>>>
>>> Changes in v3:
>>> - Make the library more pythonic
>>> - Add classes for Node and Prop along with methods
>>> - Add an exception class
>>> - Use Python to generate exeptions instead of SWIG
>>>
>>> Changes in v2:
>>> - Add exceptions when functions return an error
>>> - Correct Python naming to following PEP8
>>> - Use a class to encapsulate the various methods
>>> - Include fdt.h instead of redefining struct fdt_property
>>> - Use bytearray to avoid the SWIG warning 454
>>> - Add comments
>>>
>>>  pylibfdt/.gitignore        |   3 +
>>>  pylibfdt/Makefile.pylibfdt |  18 ++
>>>  pylibfdt/libfdt.swig       | 602 +++++++++++++++++++++++++++++++++++++++++++++
>>>  pylibfdt/setup.py          |  34 +++
>>>  4 files changed, 657 insertions(+)
>>>  create mode 100644 pylibfdt/.gitignore
>>>  create mode 100644 pylibfdt/Makefile.pylibfdt
>>>  create mode 100644 pylibfdt/libfdt.swig
>>>  create mode 100644 pylibfdt/setup.py

Could you please take a look at this email? I'd like to get your
further feedback before going any further.

Thanks,
Simon

>>>
> [..]
>
>>> +# 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) = range(1, 17)
>>
>> Pity we can't get swig to autogenerate those.
>
> Maybe we can if you want it to change to an enum?
>
>>
>>> +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 fdt32_to_cpu(val):
>>> +    """Convert a device-tree cell value into a native integer"""
>>> +    return struct.unpack("=I", struct.pack(">I", val))[0]
>>> +
>>> +def data(prop):
>>> +    """Extract the data from a property
>>> +
>>> +    Args:
>>> +        prop: Property structure, as returned by get_property_by_offset()
>>> +
>>> +    Returns:
>>> +        The property data as a bytearray
>>> +    """
>>> +    buf = bytearray(fdt32_to_cpu(prop.len))
>>
>> prop.len should be a native value, not requiring byteswap.
>>
>>> +    pylibfdt_copy_data(buf, prop)
>>> +    return buf
>>
>> Urgh.. all this fdt_property() wrangling stuff is really ugly.  Any
>> chance we could just exclude it from the wrapper, providing only
>> fdt_getprop() interfaces which simply return a Python bytestring
>> directly?
>
> I effectively have, by hiding it in here. Client code need never use this.
>
>>
>>> +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=False):
>>> +    """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: True to ignore the NOTFOUND error, False to raise on all errors
>>> +
>>> +    Returns:
>>> +        val if val >= 0
>>> +
>>> +    Raises
>>> +        FdtException if val is < 0
>>> +    """
>>> +    if val < 0:
>>> +        if not quiet or val != -NOTFOUND:
>>> +            raise FdtException(val)
>>> +    return val
>>> +
>>> +def check_err_null(val):
>>> +    """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: True to ignore the NOTFOUND error, False to raise on all errors
>>> +
>>> +    Returns:
>>> +        val if val is not NULL
>>> +
>>> +    Raises
>>> +        FdtException if val is NULL
>>> +    """
>>> +    # Normally a tuple is returned which contains the data and its length.
>>
>> Yeah.. since Python strings / bytestrings know their own length, why
>> do we need such a tuple?
>
> It's just what swig returns for functions like: const char *func(int *len)
>
> This is internal to the class so should not matter to users.
>
>>
>>> +    # If we get just an integer error code, it means the function failed.
>>> +    if not isinstance(val, list):
>>> +        raise FdtException(val)
>>> +    return val
>>> +
>>> +class Fdt:
>>> +    """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...).
>>> +
>>> +    Almost all methods raise a FdtException if an error occurs. The
>>> +    following does not:
>>> +
>>> +        string() - since it has no error checking
>>> +
>>> +    To avoid this behaviour a 'quiet' version is provided for some functions.
>>> +    This behaves as for the normal version except that it will not raise
>>> +    an exception in the case of an FDT_ERR_NOTFOUND error: it will simply
>>> +    return the -NOTFOUND error code or None.
>>> +
>>> +    Separate classes are provided for nodes (Node) and properties (Prop).
>>> +    """
>>> +    def __init__(self, data):
>>> +        self._fdt = bytearray(data)
>>> +
>>> +    def string(self, offset):
>>> +        """Get a string given its offset
>>> +
>>> +        Args:
>>> +            offset: FDT offset in big-endian format
>>> +
>>> +        Returns:
>>> +            string value at that offset
>>> +        """
>>> +        return fdt_string(self._fdt, fdt32_to_cpu(offset))
>>
>> ARGH, no.  The offset argument is an integer, it should be treated as
>> a native integer, and the byteswap should absolutely not be here.
>
> Ah yes, oops.
>
>>
>>> +    def path(self, path):
>>> +        """Get the Node for a given path
>>> +
>>> +        Args:
>>> +            path: Path to the required node, e.g. '/node@3/subnode@1'
>>> +
>>> +        Returns:
>>> +            Node object for this node
>>> +
>>> +        Raises
>>> +            FdtException if the path is not valid
>>> +        """
>>> +        return Node(self, check_err(fdt_path_offset(self._fdt, path)))
>>
>> This interface is a mistake - more details on class Node below.
>
> [..]
>
>>> +    def get_property(self, nodeoffset, name):
>>> +        """Get a property given a node offset and the property name
>>> +
>>> +        We cannot use fdt_get_property() here since it does not return the
>>> +        offset. We prefer to create Node objects using the offset.
>>> +
>>> +        Args:
>>> +            nodeoffset: Offset of the node
>>> +            name: Property name
>>> +
>>> +        Returns:
>>> +            Prop object found
>>> +
>>> +        Raises:
>>> +            FdtException on error such as no such property
>>> +        """
>>> +        poffset = self.first_property_offset(nodeoffset)
>>> +        while True:
>>> +            pdata = self.get_property_by_offset_internal(poffset)
>>> +            if self.string(pdata.nameoff) == name:
>>> +                return Prop(Node(self, nodeoffset), poffset)
>>> +            poffset = self.next_property_offset(poffset)
>>
>> Wait.. what!?  You're searching through the properties in Python?
>> Why not just using fdt_get_property() or fdt_getprop()?
>
> Because they do not tell me the offset-(see the comment in the
> function description above:
>
>         We cannot use fdt_get_property() here since it does not return the
>         offset. We prefer to create Node objects using the offset.
>
> If we want to have a Node object, then we probably want to store the
> offset of the node. Another option would be to add a function to
> libfdt which gets a property by name, but returns its offset, rather
> than a struct?
>
>>
>>> +    def get_property_quiet(self, nodeoffset, name):
>>> +        """Get a property given a node offset and the property name
>>> +
>>> +        We cannot use fdt_get_property() here since it does not return the
>>> +        offset. We prefer to create Node objects using the offset.
>>> +
>>> +        Args:
>>> +            nodeoffset: Offset of the node
>>> +            name: Property name
>>> +
>>> +        Returns:
>>> +            Prop object found or None if there is no such property
>>> +
>>> +        Raises:
>>> +            FdtException on error
>>> +        """
>>> +        poffset = self.first_property_offset_quiet(nodeoffset)
>>> +        while poffset >= 0:
>>> +            pdata = self.get_property_by_offset_internal(poffset)
>>> +            if self.string(pdata.nameoff) == name:
>>> +                return Prop(Node(self, nodeoffset), poffset)
>>> +            poffset = self.next_property_offset_quiet(poffset)
>>> +        return None
>>> +
>>> +    def first_subnode(self, nodeoffset):
>>> +        return check_err(fdt_first_subnode(self._fdt, nodeoffset))
>>> +
>>> +    def first_subnode_quiet(self, nodeoffset):
>>> +        return check_err(fdt_first_subnode(self._fdt, nodeoffset), True)
>>> +
>>> +    def next_subnode(self, nodeoffset):
>>> +        return check_err(fdt_next_subnode(self._fdt, nodeoffset))
>>> +
>>> +    def next_subnode_quiet(self, nodeoffset):
>>> +        return check_err(fdt_next_subnode(self._fdt, nodeoffset), True)
>>> +
>>> +    def totalsize(self):
>>> +        return check_err(fdt_totalsize(self._fdt))
>>> +
>>> +    def off_dt_struct(self):
>>> +        return check_err(fdt_off_dt_struct(self._fdt))
>>> +
>>> +    def pack(self):
>>> +        return check_err(fdt_pack(self._fdt))
>>> +
>>> +    def delprop(self, nodeoffset, prop_name):
>>> +        return check_err(fdt_delprop(self._fdt, nodeoffset, prop_name))
>>> +
>>> +
>>> +class Node:
>>
>> This can't work.  The reason that so many libfdt functions are
>> insistently called whatever_offset(), rather than just get_whatever()
>> is to constantly remind the user that this is a actual byte offset
>> into a buffer of data.  It's *not* a friendly stable node-handle, and
>> can become invalid when the fdt blob is altered.
>>
>> Attempting to put a class wrapper around it obscures that vital fact.
>> Treat node offsets as offsets everywhere, just encoded as plain old
>> python integers.
>
> My intent is to make the interface more pythonic, and I've been trying
> out various approaches. For read-only use this interface is nice.
> Consider this the high-water mark of this approach.
>
> But I can go back to something in between, where there is no Node or
> Prop. But please do check the code example in the cover letter and see
> if you are sure.
>
> With this approach we can do things like node.next() which is nice I think.
>
> Of course things can break if you start changing the tree while
> keeping objects around. So it isn't a perfect illusion. Hopefully it
> returns errors in that case..
>
> [..]
>
>>> +class Prop:
>>> +    """A device-tree property
>>> +
>>> +    This encapsulates a device-tree property and provides various operations
>>> +    which can be performed on the property.
>>> +
>>> +    Generally the standard constructor is used to create a Prop object. But
>>> +    in the case where only the property offset is known (and not the Node
>>> +    that holds the property), from_offset() can be used.
>>> +    """
>>
>> TBH, I'm not really sold on the value of this class either.  Is there
>> really any point dealing with these rather fragile property handles,
>> rather than simply returning bytestrings with the values directly?  I
>> can't see it.
>
> It provides the ability to iterate, get the name (as well as the
> data), delete the property, etc.
>
>>
>>> +    def __init__(self, node, offset, fdt=None):
>>> +        self._node = node
>>> +        self._offset = offset
>>> +        self._fdt = node._fdt if node else fdt
>>> +
>>> +    @classmethod
>>> +    def from_offset(cls, fdt, prop_offset):
>>> +        prop = cls(None, prop_offset, fdt)
>>> +        prop._get_name_data()
>>> +        return prop
>>> +
>>> +    def next(self):
>>> +        return Prop(self._node, check_err(fdt_next_property_offset(
>>> +                self._fdt._fdt, self._offset)))
>>> +
>>> +    def next_quiet(self):
>>> +        val = check_err(fdt_next_property_offset(self._fdt._fdt, self._offset),
>>> +                        True)
>>> +        if val < 0:
>>> +            return None
>>> +        return Prop(self._node, val)
>>> +
>>> +    def _get_name_data(self):
>>> +        pdata = fdt_get_property_by_offset(self._fdt._fdt, self._offset)
>>> +        if not isinstance(pdata, list):
>>> +            raise FdtException(pdata)
>>> +        return self._fdt.string(pdata[0].nameoff), data(pdata[0])
>>> +
>>> +    def name(self):
>>> +        return self._get_name_data()[0]
>>> +
>>> +    def data(self):
>>> +        return self._get_name_data()[1]
>>> +
>>> +    def delete(self):
>>> +        if not self._node:
>>> +            raise RuntimeError("Can't delete property offset %d of unknown node"
>>> +                               % self._offset)
>>> +        self._fdt.delprop(self._node._offset, self.name())
>>> +%}
>
> [..]
>
>>> +
>>> +/*
>>> + * From here are the function definitions from libfdt.h, along with their
>>> + * exception-handling code.
>>> + */
>>
>> Uh.. why do you need to duplicate these rather than including libfdt.h?
>
> Hopefully I can do that, even with fdt_totalsize(). Will check.
>
>>
>>> +int fdt_path_offset(const void *fdt, const char *path);
>>> +
>>> +int fdt_first_property_offset(const void *fdt, int nodeoffset);
>>> +
>>> +int fdt_next_property_offset(const void *fdt, int offset);
>>> +
>>> +const char *fdt_get_name(const void *fdt, int nodeoffset, int *OUTPUT);
>>> +
>>> +/* no exception handling, since this function has no error checking */
>>> +const char *fdt_string(const void *fdt, int stroffset);
>>> +
>>> +const struct fdt_property *fdt_get_property_by_offset(const void *fdt,
>>> +                                                      int offset, int *OUTPUT);
>>> +
>>> +/* no exception handling, this this function always returns a valid string */
>>> +const char *fdt_strerror(int errval);
>>> +
>>> +int fdt_first_subnode(const void *fdt, int offset);
>>> +
>>> +int fdt_next_subnode(const void *fdt, int offset);
>>> +
>>> +int fdt_delprop(void *fdt, int nodeoffset, const char *name);
>>> +
>>> +int fdt_pack(void *fdt);
>>> +
>>> +int fdt_totalsize(const void *fdt);
>>> +
>>> +int fdt_off_dt_struct(const void *fdt);
>>> diff --git a/pylibfdt/setup.py b/pylibfdt/setup.py
>>> new file mode 100644
>>> index 0000000..8f8618e
>>> --- /dev/null
>>> +++ b/pylibfdt/setup.py
>>> @@ -0,0 +1,34 @@
>>> +#!/usr/bin/env python
>>> +
>>> +"""
>>> +setup.py file for SWIG libfdt
>>> +"""
>>> +
>>> +from distutils.core import setup, Extension
>>> +import os
>>> +import sys
>>> +
>>> +progname = sys.argv[0]
>>> +cflags = sys.argv[1]
>>> +files = sys.argv[2:]
>>> +
>>> +if cflags:
>>> +    cflags = [flag for flag in cflags.split(' ') if flag]
>>> +else:
>>> +    cflags = None
>>> +
>>> +libfdt_module = Extension(
>>> +    '_libfdt',
>>> +    sources = files,
>>> +    extra_compile_args =  cflags
>>> +)
>>> +
>>> +sys.argv = [progname, '--quiet', 'build_ext', '--inplace']
>>> +
>>> +setup (name = 'libfdt',
>>> +       version = '0.1',
>>> +       author      = "SWIG Docs",
>>
>> Um.. that doesn't seem quite right.
>
> Nope, will fix.
>
>>
>>> +       description = """Simple swig libfdt from docs""",
>>> +       ext_modules = [libfdt_module],
>>> +       py_modules = ["libfdt"],
>>> +       )
>>
>> --
>> 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
>
> Regards,
> Simon
--
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



[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