Squashed 'yocto-poky/' content from commit ea562de

git-subtree-dir: yocto-poky
git-subtree-split: ea562de57590c966cd5a75fda8defecd397e6436
diff --git a/scripts/lib/bsp/kernel.py b/scripts/lib/bsp/kernel.py
new file mode 100644
index 0000000..ba68b60
--- /dev/null
+++ b/scripts/lib/bsp/kernel.py
@@ -0,0 +1,1071 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (c) 2012, Intel Corporation.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# DESCRIPTION
+# This module implements the kernel-related functions used by
+# 'yocto-kernel' to manage kernel config items and patches for Yocto
+# BSPs.
+#
+# AUTHORS
+# Tom Zanussi <tom.zanussi (at] intel.com>
+#
+
+import sys
+import os
+import shutil
+from tags import *
+import glob
+import subprocess
+from engine import create_context
+
+
+def find_bblayers():
+    """
+    Find and return a sanitized list of the layers found in BBLAYERS.
+    """
+    try:
+        builddir = os.environ["BUILDDIR"]
+    except KeyError:
+        print "BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)"
+        sys.exit(1)
+    bblayers_conf = os.path.join(builddir, "conf/bblayers.conf")
+
+    layers = []
+
+    bitbake_env_cmd = "bitbake -e"
+    bitbake_env_lines = subprocess.Popen(bitbake_env_cmd, shell=True,
+                                         stdout=subprocess.PIPE).stdout.read()
+
+    if not bitbake_env_lines:
+        print "Couldn't get '%s' output, exiting." % bitbake_env_cmd
+        sys.exit(1)
+
+    for line in bitbake_env_lines.split('\n'):
+        bblayers = get_line_val(line, "BBLAYERS")
+        if (bblayers):
+            break
+
+    if not bblayers:
+        print "Couldn't find BBLAYERS in %s output, exiting." % \
+            bitbake_env_cmd
+        sys.exit(1)
+
+    raw_layers = bblayers.split()
+
+    for layer in raw_layers:
+        if layer == 'BBLAYERS' or '=' in layer:
+            continue
+        layers.append(layer)
+
+    return layers
+
+
+def get_line_val(line, key):
+    """
+    Extract the value from the VAR="val" string
+    """
+    if line.startswith(key + "="):
+        stripped_line = line.split('=')[1]
+        stripped_line = stripped_line.replace('\"', '')
+        return stripped_line
+    return None
+
+
+def find_meta_layer():
+    """
+    Find and return the meta layer in BBLAYERS.
+    """
+    layers = find_bblayers()
+
+    for layer in layers:
+        if layer.endswith("meta"):
+            return layer
+
+    return None
+
+
+def find_bsp_layer(machine):
+    """
+    Find and return a machine's BSP layer in BBLAYERS.
+    """
+    layers = find_bblayers()
+
+    for layer in layers:
+        if layer.endswith(machine):
+            return layer
+
+    print "Unable to find the BSP layer for machine %s." % machine
+    print "Please make sure it is listed in bblayers.conf"
+    sys.exit(1)
+
+
+def gen_choices_str(choices):
+    """
+    Generate a numbered list of choices from a list of choices for
+    display to the user.
+    """
+    choices_str = ""
+
+    for i, choice in enumerate(choices):
+        choices_str += "\t" + str(i + 1) + ") " + choice + "\n"
+
+    return choices_str
+
+
+def open_user_file(scripts_path, machine, userfile, mode):
+    """
+    Find one of the user files (user-config.cfg, user-patches.scc)
+    associated with the machine (could be in files/,
+    linux-yocto-custom/, etc).  Returns the open file if found, None
+    otherwise.
+
+    The caller is responsible for closing the file returned.
+    """
+    layer = find_bsp_layer(machine)
+    linuxdir = os.path.join(layer, "recipes-kernel/linux")
+    linuxdir_list = os.listdir(linuxdir)
+    for fileobj in linuxdir_list:
+        fileobj_path = os.path.join(linuxdir, fileobj)
+        if os.path.isdir(fileobj_path):
+            userfile_name = os.path.join(fileobj_path, userfile)
+            try:
+                f = open(userfile_name, mode)
+                return f
+            except IOError:
+                continue
+    return None
+
+
+def read_config_items(scripts_path, machine):
+    """
+    Find and return a list of config items (CONFIG_XXX) in a machine's
+    user-defined config fragment [${machine}-user-config.cfg].
+    """
+    config_items = []
+
+    f = open_user_file(scripts_path, machine, machine+"-user-config.cfg", "r")
+    lines = f.readlines()
+    for line in lines:
+        s = line.strip()
+        if s and not s.startswith("#"):
+            config_items.append(s)
+    f.close()
+
+    return config_items
+
+
+def write_config_items(scripts_path, machine, config_items):
+    """
+    Write (replace) the list of config items (CONFIG_XXX) in a
+    machine's user-defined config fragment [${machine}=user-config.cfg].
+    """
+    f = open_user_file(scripts_path, machine, machine+"-user-config.cfg", "w")
+    for item in config_items:
+        f.write(item + "\n")
+    f.close()
+
+    kernel_contents_changed(scripts_path, machine)
+
+
+def yocto_kernel_config_list(scripts_path, machine):
+    """
+    Display the list of config items (CONFIG_XXX) in a machine's
+    user-defined config fragment [${machine}-user-config.cfg].
+    """
+    config_items = read_config_items(scripts_path, machine)
+
+    print "The current set of machine-specific kernel config items for %s is:" % machine
+    print gen_choices_str(config_items)
+
+
+def yocto_kernel_config_rm(scripts_path, machine):
+    """
+    Display the list of config items (CONFIG_XXX) in a machine's
+    user-defined config fragment [${machine}-user-config.cfg], prompt the user
+    for one or more to remove, and remove them.
+    """
+    config_items = read_config_items(scripts_path, machine)
+
+    print "Specify the kernel config items to remove:"
+    input = raw_input(gen_choices_str(config_items))
+    rm_choices = input.split()
+    rm_choices.sort()
+
+    removed = []
+
+    for choice in reversed(rm_choices):
+        try:
+            idx = int(choice) - 1
+        except ValueError:
+            print "Invalid choice (%s), exiting" % choice
+            sys.exit(1)
+        if idx < 0 or idx >= len(config_items):
+            print "Invalid choice (%d), exiting" % (idx + 1)
+            sys.exit(1)
+        removed.append(config_items.pop(idx))
+
+    write_config_items(scripts_path, machine, config_items)
+
+    print "Removed items:"
+    for r in removed:
+        print "\t%s" % r
+
+
+def yocto_kernel_config_add(scripts_path, machine, config_items):
+    """
+    Add one or more config items (CONFIG_XXX) to a machine's
+    user-defined config fragment [${machine}-user-config.cfg].
+    """
+    new_items = []
+    dup_items = []
+
+    cur_items = read_config_items(scripts_path, machine)
+
+    for item in config_items:
+        if not item.startswith("CONFIG") or (not "=y" in item and not "=m" in item):
+            print "Invalid config item (%s), exiting" % item
+            sys.exit(1)
+        if item not in cur_items and item not in new_items:
+            new_items.append(item)
+        else:
+            dup_items.append(item)
+
+    if len(new_items) > 0:
+        cur_items.extend(new_items)
+        write_config_items(scripts_path, machine, cur_items)
+        print "Added item%s:" % ("" if len(new_items)==1 else "s")
+        for n in new_items:
+            print "\t%s" % n
+
+    if len(dup_items) > 0:
+        output="The following item%s already exist%s in the current configuration, ignoring %s:" % \
+            (("","s", "it") if len(dup_items)==1 else ("s", "", "them" ))
+        print output
+        for n in dup_items:
+            print "\t%s" % n
+
+def find_current_kernel(bsp_layer, machine):
+    """
+    Determine the kernel and version currently being used in the BSP.
+    """
+    machine_conf = os.path.join(bsp_layer, "conf/machine/" + machine + ".conf")
+
+    preferred_kernel = preferred_kernel_version = preferred_version_varname = None
+
+    f = open(machine_conf, "r")
+    lines = f.readlines()
+    for line in lines:
+        if line.strip().startswith("PREFERRED_PROVIDER_virtual/kernel"):
+            preferred_kernel = line.split()[-1]
+            preferred_kernel = preferred_kernel.replace('\"','')
+            preferred_version_varname = "PREFERRED_VERSION_" + preferred_kernel
+        if preferred_version_varname and line.strip().startswith(preferred_version_varname):
+            preferred_kernel_version = line.split()[-1]
+            preferred_kernel_version = preferred_kernel_version.replace('\"','')
+            preferred_kernel_version = preferred_kernel_version.replace('%','')
+
+    if preferred_kernel and preferred_kernel_version:
+        return preferred_kernel + "_" + preferred_kernel_version
+    elif preferred_kernel:
+        return preferred_kernel
+
+
+def find_filesdir(scripts_path, machine):
+    """
+    Find the name of the 'files' dir associated with the machine
+    (could be in files/, linux-yocto-custom/, etc).  Returns the name
+    of the files dir if found, None otherwise.
+    """
+    layer = find_bsp_layer(machine)
+    filesdir = None
+    linuxdir = os.path.join(layer, "recipes-kernel/linux")
+    linuxdir_list = os.listdir(linuxdir)
+    for fileobj in linuxdir_list:
+        fileobj_path = os.path.join(linuxdir, fileobj)
+        if os.path.isdir(fileobj_path):
+            # this could be files/ or linux-yocto-custom/, we have no way of distinguishing
+            # so we take the first (and normally only) dir we find as the 'filesdir'
+            filesdir = fileobj_path
+
+    return filesdir
+
+
+def read_patch_items(scripts_path, machine):
+    """
+    Find and return a list of patch items in a machine's user-defined
+    patch list [${machine}-user-patches.scc].
+    """
+    patch_items = []
+
+    f = open_user_file(scripts_path, machine, machine+"-user-patches.scc", "r")
+    lines = f.readlines()
+    for line in lines:
+        s = line.strip()
+        if s and not s.startswith("#"):
+            fields = s.split()
+            if not fields[0] == "patch":
+                continue
+            patch_items.append(fields[1])
+    f.close()
+
+    return patch_items
+
+
+def write_patch_items(scripts_path, machine, patch_items):
+    """
+    Write (replace) the list of patches in a machine's user-defined
+    patch list [${machine}-user-patches.scc].
+    """
+    f = open_user_file(scripts_path, machine, machine+"-user-patches.scc", "w")
+    for item in patch_items:
+        f.write("patch " + item + "\n")
+    f.close()
+
+    kernel_contents_changed(scripts_path, machine)
+
+
+def yocto_kernel_patch_list(scripts_path, machine):
+    """
+    Display the list of patches in a machine's user-defined patch list
+    [${machine}-user-patches.scc].
+    """
+    patches = read_patch_items(scripts_path, machine)
+
+    print "The current set of machine-specific patches for %s is:" % machine
+    print gen_choices_str(patches)
+
+
+def yocto_kernel_patch_rm(scripts_path, machine):
+    """
+    Remove one or more patches from a machine's user-defined patch
+    list [${machine}-user-patches.scc].
+    """
+    patches = read_patch_items(scripts_path, machine)
+
+    print "Specify the patches to remove:"
+    input = raw_input(gen_choices_str(patches))
+    rm_choices = input.split()
+    rm_choices.sort()
+
+    removed = []
+
+    filesdir = find_filesdir(scripts_path, machine)
+    if not filesdir:
+        print "Couldn't rm patch(es) since we couldn't find a 'files' dir"
+        sys.exit(1)
+
+    for choice in reversed(rm_choices):
+        try:
+            idx = int(choice) - 1
+        except ValueError:
+            print "Invalid choice (%s), exiting" % choice
+            sys.exit(1)
+        if idx < 0 or idx >= len(patches):
+            print "Invalid choice (%d), exiting" % (idx + 1)
+            sys.exit(1)
+        filesdir_patch = os.path.join(filesdir, patches[idx])
+        if os.path.isfile(filesdir_patch):
+            os.remove(filesdir_patch)
+        removed.append(patches[idx])
+        patches.pop(idx)
+
+    write_patch_items(scripts_path, machine, patches)
+
+    print "Removed patches:"
+    for r in removed:
+        print "\t%s" % r
+
+
+def yocto_kernel_patch_add(scripts_path, machine, patches):
+    """
+    Add one or more patches to a machine's user-defined patch list
+    [${machine}-user-patches.scc].
+    """
+    existing_patches = read_patch_items(scripts_path, machine)
+
+    for patch in patches:
+        if os.path.basename(patch) in existing_patches:
+            print "Couldn't add patch (%s) since it's already been added" % os.path.basename(patch)
+            sys.exit(1)
+
+    filesdir = find_filesdir(scripts_path, machine)
+    if not filesdir:
+        print "Couldn't add patch (%s) since we couldn't find a 'files' dir to add it to" % os.path.basename(patch)
+        sys.exit(1)
+
+    new_patches = []
+
+    for patch in patches:
+        if not os.path.isfile(patch):
+            print "Couldn't find patch (%s), exiting" % patch
+            sys.exit(1)
+        basename = os.path.basename(patch)
+        filesdir_patch = os.path.join(filesdir, basename)
+        shutil.copyfile(patch, filesdir_patch)
+        new_patches.append(basename)
+
+    cur_items = read_patch_items(scripts_path, machine)
+    cur_items.extend(new_patches)
+    write_patch_items(scripts_path, machine, cur_items)
+
+    print "Added patches:"
+    for n in new_patches:
+        print "\t%s" % n
+
+
+def inc_pr(line):
+    """
+    Add 1 to the PR value in the given bbappend PR line.  For the PR
+    lines in kernel .bbappends after modifications.  Handles PRs of
+    the form PR := "${PR}.1" as well as PR = "r0".
+    """
+    idx = line.find("\"")
+
+    pr_str = line[idx:]
+    pr_str = pr_str.replace('\"','')
+    fields = pr_str.split('.')
+    if len(fields) > 1:
+        fields[1] = str(int(fields[1]) + 1)
+        pr_str = "\"" + '.'.join(fields) + "\"\n"
+    else:
+        pr_val = pr_str[1:]
+        pr_str = "\"" + "r" + str(int(pr_val) + 1) + "\"\n"
+    idx2 = line.find("\"", idx + 1)
+    line = line[:idx] + pr_str
+    
+    return line
+
+
+def kernel_contents_changed(scripts_path, machine):
+    """
+    Do what we need to do to notify the system that the kernel
+    recipe's contents have changed.
+    """
+    layer = find_bsp_layer(machine)
+
+    kernel = find_current_kernel(layer, machine)
+    if not kernel:
+        print "Couldn't determine the kernel for this BSP, exiting."
+        sys.exit(1)
+
+    kernel_bbfile = os.path.join(layer, "recipes-kernel/linux/" + kernel + ".bbappend")
+    if not os.path.isfile(kernel_bbfile):
+        kernel_bbfile = os.path.join(layer, "recipes-kernel/linux/" + kernel + ".bb")
+        if not os.path.isfile(kernel_bbfile):
+            return
+    kernel_bbfile_prev = kernel_bbfile + ".prev"
+    shutil.copyfile(kernel_bbfile, kernel_bbfile_prev)
+
+    ifile = open(kernel_bbfile_prev, "r")
+    ofile = open(kernel_bbfile, "w")
+    ifile_lines = ifile.readlines()
+    for ifile_line in ifile_lines:
+        if ifile_line.strip().startswith("PR"):
+            ifile_line = inc_pr(ifile_line)
+        ofile.write(ifile_line)
+    ofile.close()
+    ifile.close()
+
+
+def kernels(context):
+    """
+    Return the list of available kernels in the BSP i.e. corresponding
+    to the kernel .bbappends found in the layer.
+    """
+    archdir = os.path.join(context["scripts_path"], "lib/bsp/substrate/target/arch/" + context["arch"])
+    kerndir = os.path.join(archdir, "recipes-kernel/linux")
+    bbglob = os.path.join(kerndir, "*.bbappend")
+
+    bbappends = glob.glob(bbglob)
+
+    kernels = []
+
+    for kernel in bbappends:
+        filename = os.path.splitext(os.path.basename(kernel))[0]
+        idx = filename.find(CLOSE_TAG)
+        if idx != -1:
+            filename = filename[idx + len(CLOSE_TAG):].strip()
+        kernels.append(filename)
+
+    kernels.append("custom")
+
+    return kernels
+
+
+def extract_giturl(file):
+    """
+    Extract the git url of the kernel repo from the kernel recipe's
+    SRC_URI.
+    """
+    url = None
+    f = open(file, "r")
+    lines = f.readlines()
+    for line in lines:
+        line = line.strip()
+        if line.startswith("SRC_URI"):
+            line = line[len("SRC_URI"):].strip()
+            if line.startswith("="):
+                line = line[1:].strip()
+                if line.startswith("\""):
+                    line = line[1:].strip()
+                    prot = "git"
+                    for s in line.split(";"):
+                        if s.startswith("git://"):
+                            url = s
+                        if s.startswith("protocol="):
+                            prot = s.split("=")[1]
+                    if url:
+                        url = prot + url[3:]
+    return url
+
+
+def find_giturl(context):
+    """
+    Find the git url of the kernel repo from the kernel recipe's
+    SRC_URI.
+    """
+    name = context["name"]
+    filebase = context["filename"]
+    scripts_path = context["scripts_path"]
+
+    meta_layer = find_meta_layer()
+
+    kerndir = os.path.join(meta_layer, "recipes-kernel/linux")
+    bbglob = os.path.join(kerndir, "*.bb")
+    bbs = glob.glob(bbglob)
+    for kernel in bbs:
+        filename = os.path.splitext(os.path.basename(kernel))[0]
+        if filename in filebase:
+            giturl = extract_giturl(kernel)
+            return giturl
+    
+    return None
+
+
+def read_features(scripts_path, machine):
+    """
+    Find and return a list of features in a machine's user-defined
+    features fragment [${machine}-user-features.scc].
+    """
+    features = []
+
+    f = open_user_file(scripts_path, machine, machine+"-user-features.scc", "r")
+    lines = f.readlines()
+    for line in lines:
+        s = line.strip()
+        if s and not s.startswith("#"):
+            feature_include = s.split()
+            features.append(feature_include[1].strip())
+    f.close()
+
+    return features
+
+
+def write_features(scripts_path, machine, features):
+    """
+    Write (replace) the list of feature items in a
+    machine's user-defined features fragment [${machine}=user-features.cfg].
+    """
+    f = open_user_file(scripts_path, machine, machine+"-user-features.scc", "w")
+    for item in features:
+        f.write("include " + item + "\n")
+    f.close()
+
+    kernel_contents_changed(scripts_path, machine)
+
+
+def yocto_kernel_feature_list(scripts_path, machine):
+    """
+    Display the list of features used in a machine's user-defined
+    features fragment [${machine}-user-features.scc].
+    """
+    features = read_features(scripts_path, machine)
+
+    print "The current set of machine-specific features for %s is:" % machine
+    print gen_choices_str(features)
+
+
+def yocto_kernel_feature_rm(scripts_path, machine):
+    """
+    Display the list of features used in a machine's user-defined
+    features fragment [${machine}-user-features.scc], prompt the user
+    for one or more to remove, and remove them.
+    """
+    features = read_features(scripts_path, machine)
+
+    print "Specify the features to remove:"
+    input = raw_input(gen_choices_str(features))
+    rm_choices = input.split()
+    rm_choices.sort()
+
+    removed = []
+
+    for choice in reversed(rm_choices):
+        try:
+            idx = int(choice) - 1
+        except ValueError:
+            print "Invalid choice (%s), exiting" % choice
+            sys.exit(1)
+        if idx < 0 or idx >= len(features):
+            print "Invalid choice (%d), exiting" % (idx + 1)
+            sys.exit(1)
+        removed.append(features.pop(idx))
+
+    write_features(scripts_path, machine, features)
+
+    print "Removed features:"
+    for r in removed:
+        print "\t%s" % r
+
+
+def yocto_kernel_feature_add(scripts_path, machine, features):
+    """
+    Add one or more features a machine's user-defined features
+    fragment [${machine}-user-features.scc].
+    """
+    new_items = []
+
+    for item in features:
+        if not item.endswith(".scc"):
+            print "Invalid feature (%s), exiting" % item
+            sys.exit(1)
+        new_items.append(item)
+
+    cur_items = read_features(scripts_path, machine)
+    cur_items.extend(new_items)
+
+    write_features(scripts_path, machine, cur_items)
+
+    print "Added features:"
+    for n in new_items:
+        print "\t%s" % n
+
+
+def find_feature_url(git_url):
+    """
+    Find the url of the kern-features.rc kernel for the kernel repo
+    specified from the BSP's kernel recipe SRC_URI.
+    """
+    feature_url = ""
+    if git_url.startswith("git://"):
+        git_url = git_url[len("git://"):].strip()
+        s = git_url.split("/")
+        if s[1].endswith(".git"):
+            s[1] = s[1][:len(s[1]) - len(".git")]
+        feature_url = "http://" + s[0] + "/cgit/cgit.cgi/" + s[1] + \
+            "/plain/meta/cfg/kern-features.rc?h=meta"
+
+    return feature_url
+
+
+def find_feature_desc(lines):
+    """
+    Find the feature description and compatibility in the passed-in
+    set of lines.  Returns a string string of the form 'desc
+    [compat]'.
+    """
+    desc = "no description available"
+    compat = "unknown"
+
+    for line in lines:
+        idx = line.find("KFEATURE_DESCRIPTION")
+        if idx != -1:
+            desc = line[idx + len("KFEATURE_DESCRIPTION"):].strip()
+            if desc.startswith("\""):
+                desc = desc[1:]
+                if desc.endswith("\""):
+                    desc = desc[:-1]
+        else:
+            idx = line.find("KFEATURE_COMPATIBILITY")
+            if idx != -1:
+                compat = line[idx + len("KFEATURE_COMPATIBILITY"):].strip()
+
+    return desc + " [" + compat + "]"
+
+
+def print_feature_descs(layer, feature_dir):
+    """
+    Print the feature descriptions for the features in feature_dir.
+    """
+    kernel_files_features = os.path.join(layer, "recipes-kernel/linux/files/" +
+                                         feature_dir)
+    for root, dirs, files in os.walk(kernel_files_features):
+        for file in files:
+            if file.endswith("~") or file.endswith("#"):
+                continue
+            if file.endswith(".scc"):
+                fullpath = os.path.join(layer, "recipes-kernel/linux/files/" +
+                                        feature_dir + "/" + file)
+                f = open(fullpath)
+                feature_desc = find_feature_desc(f.readlines())
+                print feature_dir + "/" + file + ": " + feature_desc
+
+
+def yocto_kernel_available_features_list(scripts_path, machine):
+    """
+    Display the list of all the kernel features available for use in
+    BSPs, as gathered from the set of feature sources.
+    """
+    layer = find_bsp_layer(machine)
+    kernel = find_current_kernel(layer, machine)
+    if not kernel:
+        print "Couldn't determine the kernel for this BSP, exiting."
+        sys.exit(1)
+
+    context = create_context(machine, "arch", scripts_path)
+    context["name"] = "name"
+    context["filename"] = kernel
+    giturl = find_giturl(context)
+    feature_url = find_feature_url(giturl)
+
+    feature_cmd = "wget -q -O - " + feature_url
+    tmp = subprocess.Popen(feature_cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
+
+    print "The current set of kernel features available to %s is:\n" % machine
+
+    if tmp:
+        tmpline = tmp.split("\n")
+        in_kernel_options = False
+        for line in tmpline:
+            if not "=" in line:
+                if in_kernel_options:
+                    break
+                if "kernel-options" in line:
+                    in_kernel_options = True
+                continue
+            if in_kernel_options:
+                feature_def = line.split("=")
+                feature_type = feature_def[0].strip()
+                feature = feature_def[1].strip()
+                desc = get_feature_desc(giturl, feature)
+                print "%s: %s" % (feature, desc)
+
+    print "[local]"
+
+    print_feature_descs(layer, "cfg")
+    print_feature_descs(layer, "features")
+
+
+def find_feature_desc_url(git_url, feature):
+    """
+    Find the url of the kernel feature in the kernel repo specified
+    from the BSP's kernel recipe SRC_URI.
+    """
+    feature_desc_url = ""
+    if git_url.startswith("git://"):
+        git_url = git_url[len("git://"):].strip()
+        s = git_url.split("/")
+        if s[1].endswith(".git"):
+            s[1] = s[1][:len(s[1]) - len(".git")]
+        feature_desc_url = "http://" + s[0] + "/cgit/cgit.cgi/" + s[1] + \
+            "/plain/meta/cfg/kernel-cache/" + feature + "?h=meta"
+
+    return feature_desc_url
+
+
+def get_feature_desc(git_url, feature):
+    """
+    Return a feature description of the form 'description [compatibility]
+    BSPs, as gathered from the set of feature sources.
+    """
+    feature_desc_url = find_feature_desc_url(git_url, feature)
+    feature_desc_cmd = "wget -q -O - " + feature_desc_url
+    tmp = subprocess.Popen(feature_desc_cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
+
+    return find_feature_desc(tmp.split("\n"))
+
+
+def yocto_kernel_feature_describe(scripts_path, machine, feature):
+    """
+    Display the description of a specific kernel feature available for
+    use in a BSP.
+    """
+    layer = find_bsp_layer(machine)
+
+    kernel = find_current_kernel(layer, machine)
+    if not kernel:
+        print "Couldn't determine the kernel for this BSP, exiting."
+        sys.exit(1)
+
+    context = create_context(machine, "arch", scripts_path)
+    context["name"] = "name"
+    context["filename"] = kernel
+    giturl = find_giturl(context)
+
+    desc = get_feature_desc(giturl, feature)
+
+    print desc
+
+
+def check_feature_name(feature_name):
+    """
+    Sanity-check the feature name for create/destroy.  Return False if not OK.
+    """
+    if not feature_name.endswith(".scc"):
+        print "Invalid feature name (must end with .scc) [%s], exiting" % feature_name
+        return False
+
+    if "/" in feature_name:
+        print "Invalid feature name (don't specify directory) [%s], exiting" % feature_name
+        return False
+
+    return True
+
+
+def check_create_input(feature_items):
+    """
+    Sanity-check the create input.  Return False if not OK.
+    """
+    if not check_feature_name(feature_items[0]):
+        return False
+
+    if feature_items[1].endswith(".patch") or feature_items[1].startswith("CONFIG_"):
+        print "Missing description and/or compatibilty [%s], exiting" % feature_items[1]
+        return False
+
+    if feature_items[2].endswith(".patch") or feature_items[2].startswith("CONFIG_"):
+        print "Missing description and/or compatibility [%s], exiting" % feature_items[1]
+        return False
+
+    return True
+
+
+def yocto_kernel_feature_create(scripts_path, machine, feature_items):
+    """
+    Create a recipe-space kernel feature in a BSP.
+    """
+    if not check_create_input(feature_items):
+        sys.exit(1)
+
+    feature = feature_items[0]
+    feature_basename = feature.split(".")[0]
+    feature_description = feature_items[1]
+    feature_compat = feature_items[2]
+
+    patches = []
+    cfg_items = []
+
+    for item in feature_items[3:]:
+        if item.endswith(".patch"):
+            patches.append(item)
+        elif item.startswith("CONFIG"):
+            if ("=y" in item or "=m" in item):
+                cfg_items.append(item)
+        else:
+            print "Invalid feature item (must be .patch or CONFIG_*) [%s], exiting" % item
+            sys.exit(1)
+
+    feature_dirname = "cfg"
+    if patches:
+        feature_dirname = "features"
+
+    filesdir = find_filesdir(scripts_path, machine)
+    if not filesdir:
+        print "Couldn't add feature (%s), no 'files' dir found" % feature
+        sys.exit(1)
+
+    featdir = os.path.join(filesdir, feature_dirname)
+    if not os.path.exists(featdir):
+        os.mkdir(featdir)
+
+    for patch in patches:
+        if not os.path.isfile(patch):
+            print "Couldn't find patch (%s), exiting" % patch
+            sys.exit(1)
+        basename = os.path.basename(patch)
+        featdir_patch = os.path.join(featdir, basename)
+        shutil.copyfile(patch, featdir_patch)
+
+    new_cfg_filename = os.path.join(featdir, feature_basename + ".cfg")
+    new_cfg_file = open(new_cfg_filename, "w")
+    for cfg_item in cfg_items:
+        new_cfg_file.write(cfg_item + "\n")
+    new_cfg_file.close()
+
+    new_feature_filename = os.path.join(featdir, feature_basename + ".scc")
+    new_feature_file = open(new_feature_filename, "w")
+    new_feature_file.write("define KFEATURE_DESCRIPTION \"" + feature_description + "\"\n")
+    new_feature_file.write("define KFEATURE_COMPATIBILITY " + feature_compat + "\n\n")
+
+    for patch in patches:
+        patch_dir, patch_file = os.path.split(patch)
+        new_feature_file.write("patch " + patch_file + "\n")
+
+    new_feature_file.write("kconf non-hardware " + feature_basename + ".cfg\n")
+    new_feature_file.close()
+
+    print "Added feature:"
+    print "\t%s" % feature_dirname + "/" + feature
+
+
+def feature_in_use(scripts_path, machine, feature):
+    """
+    Determine whether the specified feature is in use by the BSP.
+    Return True if so, False otherwise.
+    """
+    features = read_features(scripts_path, machine)
+    for f in features:
+        if f == feature:
+            return True
+    return False
+
+
+def feature_remove(scripts_path, machine, feature):
+    """
+    Remove the specified feature from the available recipe-space
+    features defined for the BSP.
+    """
+    features = read_features(scripts_path, machine)
+    new_features = []
+    for f in features:
+        if f == feature:
+            continue
+        new_features.append(f)
+    write_features(scripts_path, machine, new_features)
+
+
+def yocto_kernel_feature_destroy(scripts_path, machine, feature):
+    """
+    Remove a recipe-space kernel feature from a BSP.
+    """
+    if not check_feature_name(feature):
+        sys.exit(1)
+
+    if feature_in_use(scripts_path, machine, "features/" + feature) or \
+            feature_in_use(scripts_path, machine, "cfg/" + feature):
+        print "Feature %s is in use (use 'feature rm' to un-use it first), exiting" % feature
+        sys.exit(1)
+
+    filesdir = find_filesdir(scripts_path, machine)
+    if not filesdir:
+        print "Couldn't destroy feature (%s), no 'files' dir found" % feature
+        sys.exit(1)
+
+    feature_dirname = "features"
+    featdir = os.path.join(filesdir, feature_dirname)
+    if not os.path.exists(featdir):
+        print "Couldn't find feature directory (%s)" % feature_dirname
+        sys.exit(1)
+
+    feature_fqn = os.path.join(featdir, feature)
+    if not os.path.exists(feature_fqn):
+        feature_dirname = "cfg"
+        featdir = os.path.join(filesdir, feature_dirname)
+        if not os.path.exists(featdir):
+            print "Couldn't find feature directory (%s)" % feature_dirname
+            sys.exit(1)
+        feature_fqn = os.path.join(featdir, feature_filename)
+        if not os.path.exists(feature_fqn):
+            print "Couldn't find feature (%s)" % feature
+            sys.exit(1)
+
+    f = open(feature_fqn, "r")
+    lines = f.readlines()
+    for line in lines:
+        s = line.strip()
+        if s.startswith("patch ") or s.startswith("kconf "):
+            split_line = s.split()
+            filename = os.path.join(featdir, split_line[-1])
+            if os.path.exists(filename):
+                os.remove(filename)
+    f.close()
+    os.remove(feature_fqn)
+
+    feature_remove(scripts_path, machine, feature)
+
+    print "Removed feature:"
+    print "\t%s" % feature_dirname + "/" + feature
+
+
+def base_branches(context):
+    """
+    Return a list of the base branches found in the kernel git repo.
+    """
+    giturl = find_giturl(context)
+
+    print "Getting branches from remote repo %s..." % giturl
+
+    gitcmd = "git ls-remote %s *heads* 2>&1" % (giturl)
+    tmp = subprocess.Popen(gitcmd, shell=True, stdout=subprocess.PIPE).stdout.read()
+
+    branches = []
+
+    if tmp:
+        tmpline = tmp.split("\n")
+        for line in tmpline:
+            if len(line)==0:
+                break;
+            if not line.endswith("base"):
+                continue;
+            idx = line.find("refs/heads/")
+            kbranch = line[idx + len("refs/heads/"):]
+            if kbranch.find("/") == -1 and kbranch.find("base") == -1:
+                continue
+            idx = kbranch.find("base")
+            branches.append(kbranch[:idx - 1])
+
+    return branches
+
+
+def all_branches(context):
+    """
+    Return a list of all the branches found in the kernel git repo.
+    """
+    giturl = find_giturl(context)
+
+    print "Getting branches from remote repo %s..." % giturl
+
+    gitcmd = "git ls-remote %s *heads* 2>&1" % (giturl)
+    tmp = subprocess.Popen(gitcmd, shell=True, stdout=subprocess.PIPE).stdout.read()
+
+    branches = []
+
+    base_prefixes = None
+
+    try:
+        branches_base = context["branches_base"]
+        if branches_base:
+            base_prefixes = branches_base.split(":")
+    except KeyError:
+        pass
+
+    arch = context["arch"]
+
+    if tmp:
+        tmpline = tmp.split("\n")
+        for line in tmpline:
+            if len(line)==0:
+                break;
+            idx = line.find("refs/heads/")
+            kbranch = line[idx + len("refs/heads/"):]
+            kbranch_prefix = kbranch.rsplit("/", 1)[0]
+
+            if base_prefixes:
+                for base_prefix in base_prefixes:
+                    if kbranch_prefix == base_prefix:
+                        branches.append(kbranch)
+                continue
+
+            if (kbranch.find("/") != -1 and
+                (kbranch.find("standard") != -1 or kbranch.find("base") != -1) or
+                kbranch == "base"):
+                branches.append(kbranch)
+                continue
+
+    return branches