diff options
-rw-r--r-- | common/blspec.c | 2 | ||||
-rw-r--r-- | drivers/of/overlay.c | 314 | ||||
-rw-r--r-- | include/of.h | 18 |
3 files changed, 330 insertions, 4 deletions
diff --git a/common/blspec.c b/common/blspec.c index ca96a45487..4146915ffb 100644 --- a/common/blspec.c +++ b/common/blspec.c @@ -51,7 +51,7 @@ static int blspec_overlay_fixup(struct device_node *root, void *ctx) path = basprintf("%s/%s", entry->rootpath, overlay); - of_overlay_apply_file(root, path); + of_overlay_apply_file(root, path, false); free(path); } diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 26b5fee775..42b309805f 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -16,6 +16,8 @@ #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) @@ -165,6 +167,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 @@ -201,13 +205,105 @@ out_err: return err; } -int of_overlay_apply_file(struct device_node *root, const char *filename) +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) { void *fdt; struct device_node *ovl; size_t size; int ret; + if (filter && !of_overlay_matches_filter(filename, NULL)) + return 0; + ret = read_file_2(filename, &size, &fdt, FILESIZE_MAX); if (ret) return ret; @@ -221,6 +317,9 @@ int of_overlay_apply_file(struct device_node *root, const char *filename) 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); @@ -293,3 +392,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) +{ + char *p, *path; + 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; + + p = path = strdup(of_overlay_filepattern); + + 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); + + 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"); diff --git a/include/of.h b/include/of.h index 59c1250fb2..b698220b1e 100644 --- a/include/of.h +++ b/include/of.h @@ -1022,12 +1022,20 @@ static inline struct device_node *of_find_root_node(struct device_node *node) return node; } +struct of_overlay_filter { + bool (*filter_filename)(struct of_overlay_filter *, const char *filename); + bool (*filter_content)(struct of_overlay_filter *, struct device_node *); + char *name; + struct list_head list; +}; + #ifdef CONFIG_OF_OVERLAY struct device_node *of_resolve_phandles(struct device_node *root, const struct device_node *overlay); int of_overlay_apply_tree(struct device_node *root, struct device_node *overlay); -int of_overlay_apply_file(struct device_node *root, const char *filename); +int of_overlay_apply_file(struct device_node *root, const char *filename, + bool filter); int of_register_overlay(struct device_node *overlay); int of_process_overlay(struct device_node *root, struct device_node *overlay, @@ -1038,6 +1046,8 @@ int of_process_overlay(struct device_node *root, int of_overlay_pre_load_firmware(struct device_node *root, struct device_node *overlay); int of_overlay_load_firmware(void); void of_overlay_load_firmware_clear(void); +void of_overlay_set_basedir(const char *path); +int of_overlay_register_filter(struct of_overlay_filter *); #else static inline struct device_node *of_resolve_phandles(struct device_node *root, const struct device_node *overlay) @@ -1052,7 +1062,7 @@ static inline int of_overlay_apply_tree(struct device_node *root, } static inline int of_overlay_apply_file(struct device_node *root, - const char *filename) + const char *filename, bool filter) { return -ENOSYS; } @@ -1086,6 +1096,10 @@ static inline void of_overlay_load_firmware_clear(void) { } +static inline void of_overlay_set_basedir(const char *path) +{ +} + #endif #endif /* __OF_H */ |