Target: add read_tree_values and read_tree_values_flat

Add two new methods to target that allow querying values of all sysfs
nodes in a sub-directory structure all at once. The difference is that
read_tree_values_flat returns a flat dict of path->value mappings;
read_tree_values returns a nested dict structure that mimics the
scanned sub-directories tree.
diff --git a/devlib/bin/scripts/shutils.in b/devlib/bin/scripts/shutils.in
index 6d78be7..004030d 100755
--- a/devlib/bin/scripts/shutils.in
+++ b/devlib/bin/scripts/shutils.in
@@ -196,6 +196,22 @@
 }
 
 ################################################################################
+# Misc
+################################################################################
+
+read_tree_values() {
+    PATH=$1
+    MAXDEPTH=$2
+
+    PATHS=$($BUSYBOX find $PATH -follow -maxdepth $MAXDEPTH)
+    if [ ${#PATHS[@]} -eq 0 ]; then
+        echo "ERROR: '$1' does not exist"
+    else
+        $BUSYBOX grep -s '' $PATHS
+    fi
+}
+
+################################################################################
 # Main Function Dispatcher
 ################################################################################
 
@@ -236,6 +252,9 @@
 ftrace_get_function_stats)
     ftrace_get_function_stats
     ;;
+read_tree_values)
+	read_tree_values $*
+    ;;
 *)
     echo "Command [$CMD] not supported"
     exit -1
diff --git a/devlib/target.py b/devlib/target.py
index f84c6a1..8609fec 100644
--- a/devlib/target.py
+++ b/devlib/target.py
@@ -614,6 +614,20 @@
         timeout = duration + 10
         self.execute('sleep {}'.format(duration), timeout=timeout)
 
+    def read_tree_values_flat(self, path, depth=1, check_exit_code=True):
+        command = 'read_tree_values {} {}'.format(path, depth)
+        output = self._execute_util(command, as_root=self.is_rooted,
+                                    check_exit_code=check_exit_code)
+        result = {}
+        for entry in output.strip().split('\n'):
+            path, value = entry.strip().split(':', 1)
+            result[path] = value
+        return result
+
+    def read_tree_values(self, path, depth=1, dictcls=dict, check_exit_code=True):
+	value_map = self.read_tree_values_flat(path, depth, check_exit_code)
+	return _build_path_tree(value_map, path, self.path.sep, dictcls)
+
     # internal methods
 
     def _setup_shutils(self):
@@ -1558,3 +1572,32 @@
     if name is None:
         name = '{}/{}/{}'.format(implementer, part, variant)
     return name
+
+
+def _build_path_tree(path_map, basepath, sep=os.path.sep, dictcls=dict):
+    """
+    Convert a flat mapping of paths to values into a nested structure of
+    dict-line object (``dict``'s by default), mirroring the directory hierarchy
+    represented by the paths relative to ``basepath``.
+
+    """
+    def process_node(node, path, value):
+        parts = path.split(sep, 1)
+        if len(parts) == 1:   # leaf
+            node[parts[0]] = value
+        else:  # branch
+            if parts[0] not in node:
+                node[parts[0]] = dictcls()
+            process_node(node[parts[0]], parts[1], value)
+
+    relpath_map = {os.path.relpath(p, basepath): v
+                   for p, v in path_map.iteritems()}
+
+    if len(relpath_map) == 1 and relpath_map.keys()[0] == '.':
+        result = relpath_map.values()[0]
+    else:
+        result = dictcls()
+        for path, value in relpath_map.iteritems():
+            process_node(result, path, value)
+
+    return result
diff --git a/doc/target.rst b/doc/target.rst
index 08472e2..ae6ddb0 100644
--- a/doc/target.rst
+++ b/doc/target.rst
@@ -327,6 +327,32 @@
        some sysfs entries silently failing to set the written value without
        returning an error code.
 
+.. method:: Target.read_tree_values(path, depth=1, dictcls=dict):
+
+   Read values of all sysfs (or similar) file nodes under ``path``, traversing
+   up to the maximum depth ``depth``.
+
+   Returns a nested structure of dict-like objects (``dict``\ s by default) that
+   follows the structure of the scanned sub-directory tree. The top-level entry
+   has a single item who's key is ``path``. If ``path`` points to a single file,
+   the value of the entry is the value ready from that file node. Otherwise, the
+   value is a dict-line object  with a key for every entry under ``path``
+   mapping onto its value or further dict-like objects as appropriate.
+
+   :param path: sysfs path to scan
+   :param depth: maximum depth to descend
+   :param dictcls: a dict-like type to be used for each level of the hierarchy.
+
+.. method:: Target.read_tree_values_flat(path, depth=1):
+
+   Read values of all sysfs (or similar) file nodes under ``path``, traversing
+   up to the maximum depth ``depth``.
+
+   Returns a dict mapping paths of file nodes to corresponding values.
+
+   :param path: sysfs path to scan
+   :param depth: maximum depth to descend
+
 .. method:: Target.reset()
 
    Soft reset the target. Typically, this means executing ``reboot`` on the