diff options
Diffstat (limited to 'drivers/of/overlay.c')
-rw-r--r-- | drivers/of/overlay.c | 387 |
1 files changed, 374 insertions, 13 deletions
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index de79e05cbc..73c7a91db9 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Functions for working with device tree overlays * @@ -11,6 +11,13 @@ #include <common.h> #include <of.h> #include <errno.h> +#include <globalvar.h> +#include <magicvar.h> +#include <string.h> +#include <libfile.h> +#include <fs.h> +#include <libbb.h> +#include <fnmatch.h> static struct device_node *find_target(struct device_node *root, struct device_node *fragment) @@ -58,6 +65,9 @@ static int of_overlay_apply(struct device_node *target, if (of_prop_cmp(prop->name, "name") == 0) continue; + if (of_prop_cmp(prop->name, "phandle") == 0) + target->phandle = be32_to_cpup(prop->value); + err = of_set_property(target, prop->name, prop->value, prop->length, true); if (err) @@ -92,8 +102,10 @@ static char *of_overlay_fix_path(struct device_node *root, if (of_get_child_by_name(fragment, "__overlay__")) break; } - if (!fragment) + if (!fragment) { + pr_info("could not find __overlay__ node\n"); return NULL; + } target = find_target(root, fragment); if (!target) @@ -102,7 +114,7 @@ static char *of_overlay_fix_path(struct device_node *root, prefix = of_get_child_by_name(fragment, "__overlay__")->full_name; path_tail = path + strlen(prefix); - return basprintf("%s%s", target->full_name, path_tail); + return basprintf("%pOF%s", target, path_tail); } static int of_overlay_apply_symbols(struct device_node *root, @@ -115,12 +127,17 @@ static int of_overlay_apply_symbols(struct device_node *root, struct device_node *overlay_symbols; root_symbols = of_get_child_by_name(root, "__symbols__"); - if (!root_symbols) - return -EINVAL; - overlay_symbols = of_get_child_by_name(overlay, "__symbols__"); - if (!overlay_symbols) - return -EINVAL; + + if (!overlay_symbols) { + pr_debug("overlay doesn't have a __symbols__ node\n"); + return 0; + } + + if (!root_symbols) { + pr_info("root doesn't have a __symbols__ node\n"); + return 0; + } list_for_each_entry(prop, &overlay_symbols->properties, list) { if (of_prop_cmp(prop->name, "name") == 0) @@ -128,6 +145,8 @@ static int of_overlay_apply_symbols(struct device_node *root, old_path = of_property_get_value(prop); new_path = of_overlay_fix_path(root, overlay, old_path); + if (!new_path) + return -EINVAL; pr_debug("add symbol %s with new path %s\n", prop->name, new_path); @@ -154,6 +173,8 @@ static int of_overlay_apply_fragment(struct device_node *root, return of_overlay_apply(target, overlay); } +static char *of_overlay_compatible; + /** * Apply the overlay on the passed devicetree root * @root: the devicetree onto which the overlay will be applied @@ -164,29 +185,155 @@ int of_overlay_apply_tree(struct device_node *root, { struct device_node *resolved; struct device_node *fragment; - int err; + int err = 0; resolved = of_resolve_phandles(root, overlay); if (!resolved) return -EINVAL; + err = of_overlay_pre_load_firmware(root, resolved); + if (err) + goto out_err; + /* Copy symbols from resolved overlay to base device tree */ err = of_overlay_apply_symbols(root, resolved); if (err) - pr_warn("failed to copy symbols from overlay"); + goto out_err; /* Copy nodes and properties from resolved overlay to root */ for_each_child_of_node(resolved, fragment) { err = of_overlay_apply_fragment(root, fragment); if (err) - pr_warn("failed to apply %s", fragment->name); + pr_warn("failed to apply %s\n", fragment->name); } + /* We are patching the live tree, reload aliases */ + if (root == of_get_root_node()) + of_alias_scan(); + +out_err: of_delete_node(resolved); return err; } +static char *of_overlay_filter; + +static LIST_HEAD(of_overlay_filters); + +static struct of_overlay_filter *of_overlay_find_filter(const char *name) +{ + struct of_overlay_filter *f; + + list_for_each_entry(f, &of_overlay_filters, list) + if (!strcmp(f->name, name)) + return f; + return NULL; +} + +static bool of_overlay_matches_filter(const char *filename, struct device_node *ovl) +{ + struct of_overlay_filter *filter; + char *p, *path, *n; + bool apply = false; + bool have_filename_filter = false; + bool have_content_filter = false; + + p = path = strdup(of_overlay_filter); + + while ((n = strsep_unescaped(&p, " "))) { + int score = 0; + + if (!*n) + continue; + + filter = of_overlay_find_filter(n); + if (!filter) { + pr_err("Ignoring unknown filter %s\n", n); + continue; + } + + if (filter->filter_filename) + have_filename_filter = true; + if (filter->filter_content) + have_content_filter = true; + + if (filename) { + if (filter->filter_filename && + filter->filter_filename(filter, kbasename(filename))) + score++; + } else { + score++; + } + + if (ovl) { + if (filter->filter_content && + filter->filter_content(filter, ovl)) + score++; + } else { + score++; + } + + if (score == 2) { + apply = true; + break; + } + } + + free(path); + + /* No filter found at all, no match */ + if (!have_filename_filter && !have_content_filter) + return false; + + /* Want to match filename, but we do not have a filename_filter */ + if (filename && !have_filename_filter) + return true; + + /* Want to match content, but we do not have a content_filter */ + if (ovl && !have_content_filter) + return true; + + if (apply) + pr_debug("filename %s, overlay %p: match against filter %s\n", + filename ?: "<NONE>", + ovl, filter->name); + else + pr_debug("filename %s, overlay %p: no match\n", + filename ?: "<NONE>", ovl); + + return apply; +} + +int of_overlay_apply_file(struct device_node *root, const char *filename, + bool filter) +{ + struct device_node *ovl; + int ret; + + if (filter && !of_overlay_matches_filter(filename, NULL)) + return 0; + + ovl = of_read_file(filename); + if (IS_ERR(ovl)) + return PTR_ERR(ovl); + + if (filter && !of_overlay_matches_filter(NULL, ovl)) + return 0; + + ret = of_overlay_apply_tree(root, ovl); + if (ret == -ENODEV) + pr_debug("Not applied %s (not compatible)\n", filename); + else if (ret) + pr_err("Cannot apply %s: %s\n", filename, strerror(-ret)); + else + pr_info("Applied %s\n", filename); + + of_delete_node(ovl); + + return ret; +} + static int of_overlay_fixup(struct device_node *root, void *data) { struct device_node *overlay = data; @@ -219,12 +366,13 @@ int of_process_overlay(struct device_node *root, target = find_target(root, fragment); if (!target) - continue; + pr_debug("cannot find target for fragment %s\n", + fragment->name); err = process(target, ovl, data); if (err) { pr_warn("failed to process overlay for %s\n", - target->name); + target ? target->name : "unknown"); break; } } @@ -245,3 +393,216 @@ int of_register_overlay(struct device_node *overlay) { return of_register_fixup(of_overlay_fixup, overlay); } + +static char *of_overlay_filepattern; +static char *of_overlay_dir; +static char *of_overlay_basedir; + +/** + * of_overlay_set_basedir - set the overlay basedir + * @path: The new overlay basedir + * + * This specifies the base directory where overlay files are expected. By + * default this is the root directory, but it is overwritten by blspec to + * point to the rootfs of the about-to-be-booted system. + */ +void of_overlay_set_basedir(const char *path) +{ + free(of_overlay_basedir); + of_overlay_basedir = strdup(path); +} + +static int of_overlay_apply_dir(struct device_node *root, const char *dirname, + bool filter) +{ + int ret = 0; + DIR *dir; + + if (!dirname || !*dirname) + return 0; + + pr_debug("Applying overlays from %s\n", dirname); + + dir = opendir(dirname); + if (!dir) + return -errno; + + while (1) { + struct dirent *ent; + char *filename; + + ent = readdir(dir); + if (!ent) + break; + + if (!strcmp(dir->d.d_name, ".") || !strcmp(dir->d.d_name, "..")) + continue; + + filename = basprintf("%s/%s", dirname, dir->d.d_name); + + of_overlay_apply_file(root, filename, filter); + + free(filename); + } + + closedir(dir); + + return ret; +} + +static int of_overlay_global_fixup(struct device_node *root, void *data) +{ + char *dir; + int ret; + + if (*of_overlay_dir == '/') + return of_overlay_apply_dir(root, of_overlay_dir, true); + + if (*of_overlay_dir == '\0') + return 0; + + dir = concat_path_file(of_overlay_basedir, of_overlay_dir); + + ret = of_overlay_apply_dir(root, dir, true); + + free(dir); + + return ret; +} + +/** + * of_overlay_register_filter - register a new overlay filter + * @filter: The new filter + * + * Register a new overlay filter. A filter can either match on + * the filename or on the content of an overlay, but not on both. + * If that's desired two filters have to be registered. + * + * @return: 0 for success, negative error code otherwise + */ +int of_overlay_register_filter(struct of_overlay_filter *filter) +{ + if (filter->filter_filename && filter->filter_content) + return -EINVAL; + + list_add_tail(&filter->list, &of_overlay_filters); + + return 0; +} + +/** + * of_overlay_filter_filename - A filter that matches on the filename of + * an overlay + * @f: The filter + * @filename: The filename of the overlay + * + * This filter matches when the filename matches one of the patterns given + * in global.of.overlay.filepattern. global.of.overlay.filepattern shall + * contain a space separated list of wildcard patterns. + * + * @return: True when the overlay shall be applied, false otherwise. + */ +static bool of_overlay_filter_filename(struct of_overlay_filter *f, + const char *filename) +{ + char *p, *path, *n; + int ret; + bool apply; + + p = path = strdup(of_overlay_filepattern); + + while ((n = strsep_unescaped(&p, " "))) { + if (!*n) + continue; + + ret = fnmatch(n, filename, 0); + + if (!ret) { + apply = true; + goto out; + } + } + + apply = false; +out: + free(path); + + return apply; +} + +static struct of_overlay_filter of_overlay_filepattern_filter = { + .name = "filepattern", + .filter_filename = of_overlay_filter_filename, +}; + +/** + * of_overlay_filter_compatible - A filter that matches on the compatible of + * an overlay + * @f: The filter + * @ovl: The overlay + * + * This filter matches when the compatible of an overlay matches to one + * of the compatibles given in global.of.overlay.compatible. When the + * overlay doesn't contain a compatible entry it is considered matching. + * Also when no compatibles are given in global.of.overlay.compatible + * all overlays will match. + * + * @return: True when the overlay shall be applied, false otherwise. + */ +static bool of_overlay_filter_compatible(struct of_overlay_filter *f, + struct device_node *ovl) +{ + char *p, *n, *compatibles; + bool res = false; + + if (!of_overlay_compatible || !*of_overlay_compatible) + return true; + if (!of_find_property(ovl, "compatible", NULL)) + return true; + + p = compatibles = xstrdup(of_overlay_compatible); + + while ((n = strsep_unescaped(&p, " "))) { + if (!*n) + continue; + + if (of_device_is_compatible(ovl, n)) { + res = true; + break; + } + } + + free(compatibles); + + return res; +} + +static struct of_overlay_filter of_overlay_compatible_filter = { + .name = "compatible", + .filter_content = of_overlay_filter_compatible, +}; + +static int of_overlay_init(void) +{ + of_overlay_filepattern = strdup("*"); + of_overlay_filter = strdup("filepattern compatible"); + of_overlay_set_basedir("/"); + + globalvar_add_simple_string("of.overlay.compatible", &of_overlay_compatible); + globalvar_add_simple_string("of.overlay.filepattern", &of_overlay_filepattern); + globalvar_add_simple_string("of.overlay.filter", &of_overlay_filter); + globalvar_add_simple_string("of.overlay.dir", &of_overlay_dir); + + of_overlay_register_filter(&of_overlay_filepattern_filter); + of_overlay_register_filter(&of_overlay_compatible_filter); + + of_register_fixup(of_overlay_global_fixup, NULL); + + return 0; +} +device_initcall(of_overlay_init); + +BAREBOX_MAGICVAR(global.of.overlay.compatible, "space separated list of compatibles an overlay must match"); +BAREBOX_MAGICVAR(global.of.overlay.filepattern, "space separated list of filepatterns an overlay must match"); +BAREBOX_MAGICVAR(global.of.overlay.dir, "Directory to look for dt overlays"); +BAREBOX_MAGICVAR(global.of.overlay.filter, "space separated list of filters"); |