From 1cf6935dea364a193bc9f37c80a34046c311c9f7 Mon Sep 17 00:00:00 2001 From: Steffen Trumtrar Date: Wed, 23 Jun 2021 06:33:59 +0200 Subject: firmware: add support for compressed images At least bitstreams for FPGAs can consist of a lot of zeros depending on device utilization. These bitstreams can be compressed very effectively. Let the firmware code accept these images and decompress them before handing it to the firmware-manager in question. Signed-off-by: Steffen Trumtrar Link: https://lore.barebox.org/20210616063246.14900-10-s.trumtrar@pengutronix.de Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210623043359.18391-4-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/firmware.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) (limited to 'common') diff --git a/common/firmware.c b/common/firmware.c index 58509d5da6..e4b5025ab1 100644 --- a/common/firmware.c +++ b/common/firmware.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #define BUFSIZ 4096 @@ -211,12 +213,52 @@ out: */ int firmwaremgr_load_file(struct firmware_mgr *mgr, const char *firmware) { - int ret; - char *name = basprintf("/dev/%s", mgr->handler->id); + char *dst; + enum filetype type; + int ret = 0; + int firmwarefd = 0; + int devicefd = 0; + + if (!firmware) + return -EINVAL; + + if (!mgr->handler->id) { + pr_err("id not defined for handler\n"); + return -ENODEV; + } + + dst = basprintf("/dev/%s", mgr->handler->id); + + firmwarefd = open(firmware, O_RDONLY); + if (firmwarefd < 0) { + printf("could not open %s: %s\n", firmware, + errno_str()); + ret = firmwarefd; + goto out; + } - ret = copy_file(firmware, name, 0); + type = file_name_detect_type(firmware); + + devicefd = open(dst, O_WRONLY); + if (devicefd < 0) { + printf("could not open %s: %s\n", dst, errno_str()); + ret = devicefd; + goto out; + } + + if (file_is_compressed_file(type)) + ret = uncompress_fd_to_fd(firmwarefd, devicefd, + uncompress_err_stdout); + else + ret = copy_fd(firmwarefd, devicefd); + +out: + free(dst); - free(name); + if (firmwarefd > 0) + close(firmwarefd); + if (devicefd > 0) + close(devicefd); return ret; } -- cgit v1.2.3 From 3b93bcd8db01bbe49249b59b0581b3ba375cb96b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:07 +0200 Subject: of: Add function to duplicate a device tree This adds of_dup() to duplicate a device tree. Previously of_copy_node() was used for this, but of_copy_node() has issues with potentially duplicated phandle values when the new tree is inserted to an existing tree, that is when the parent argument of of_copy_node() is non NULL. All users of of_copy_node() with a NULL parent argument are converted to of_dup() which is safe to use leaving only the problematic users of of_copy_node(). Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-3-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- commands/of_diff.c | 4 ++-- common/oftree.c | 2 +- drivers/of/base.c | 5 +++++ include/of.h | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) (limited to 'common') diff --git a/commands/of_diff.c b/commands/of_diff.c index ec039776cf..d9d84fd4bf 100644 --- a/commands/of_diff.c +++ b/commands/of_diff.c @@ -25,7 +25,7 @@ static struct device_node *get_tree(const char *filename, struct device_node *ro if (!node) return ERR_PTR(-ENOENT); - return of_copy_node(NULL, node); + return of_dup(node); } if (!strcmp(filename, "+")) { @@ -33,7 +33,7 @@ static struct device_node *get_tree(const char *filename, struct device_node *ro if (!node) return ERR_PTR(-ENOENT); - node = of_copy_node(NULL, root); + node = of_dup(root); of_fix_tree(node); diff --git a/common/oftree.c b/common/oftree.c index 5eaa63ad7e..da8043809b 100644 --- a/common/oftree.c +++ b/common/oftree.c @@ -353,7 +353,7 @@ struct fdt_header *of_get_fixed_tree(struct device_node *node) if (!node) return NULL; - freenp = node = of_copy_node(NULL, node); + freenp = node = of_dup(node); if (!node) return NULL; } diff --git a/drivers/of/base.c b/drivers/of/base.c index 1434f39ad1..5d93750aec 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2420,6 +2420,11 @@ struct device_node *of_copy_node(struct device_node *parent, const struct device return np; } +struct device_node *of_dup(const struct device_node *root) +{ + return of_copy_node(NULL, root); +} + void of_delete_node(struct device_node *node) { struct device_node *n, *nt; diff --git a/include/of.h b/include/of.h index d67a40bd19..94eb71190d 100644 --- a/include/of.h +++ b/include/of.h @@ -162,6 +162,7 @@ extern struct device_node *of_create_node(struct device_node *root, const char *path); extern struct device_node *of_copy_node(struct device_node *parent, const struct device_node *other); +extern struct device_node *of_dup(const struct device_node *root); extern void of_delete_node(struct device_node *node); extern const char *of_get_machine_compatible(void); -- cgit v1.2.3 From 8d21690fa82bbc29cc34005103a2eda63eafabf3 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:08 +0200 Subject: fdt: Check blob size during unflattening of_unflatten_dtb() doesn't check the size of the device tree blob passed to it. Add a size argument end add checks for the size. Some callers have no idea of the buffer size themselves, INT_MAX is passed in these cases. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-4-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/boards/qemu-virt/board.c | 4 ++-- arch/arm/boards/raspberry-pi/rpi-common.c | 2 +- arch/arm/boards/webasto-ccbv2/board.c | 2 +- arch/arm/lib32/bootm.c | 2 +- commands/of_diff.c | 2 +- commands/of_display_timings.c | 2 +- commands/of_dump.c | 4 ++-- commands/of_overlay.c | 5 +++-- commands/oftree.c | 2 +- common/blspec.c | 7 ++++--- common/bootm.c | 4 ++-- common/efi/efi.c | 2 +- common/image-fit.c | 2 +- common/state/backend_format_dtb.c | 2 +- drivers/of/base.c | 2 +- drivers/of/fdt.c | 17 ++++++++++++----- include/of.h | 4 ++-- 17 files changed, 37 insertions(+), 28 deletions(-) (limited to 'common') diff --git a/arch/arm/boards/qemu-virt/board.c b/arch/arm/boards/qemu-virt/board.c index 5ce1ecfc24..b2a3cb29ab 100644 --- a/arch/arm/boards/qemu-virt/board.c +++ b/arch/arm/boards/qemu-virt/board.c @@ -31,14 +31,14 @@ static int replace_dtb(void) { return 0; } - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, INT_MAX); if (!of_device_is_compatible(root, "linux,dummy-virt")) { of_delete_node(root); return 0; } - overlay = of_unflatten_dtb(__dtb_overlay_of_flash_start); + overlay = of_unflatten_dtb(__dtb_overlay_of_flash_start, INT_MAX); of_overlay_apply_tree(root, overlay); return barebox_register_of(root); diff --git a/arch/arm/boards/raspberry-pi/rpi-common.c b/arch/arm/boards/raspberry-pi/rpi-common.c index e326732b3a..6c5df6fd69 100644 --- a/arch/arm/boards/raspberry-pi/rpi-common.c +++ b/arch/arm/boards/raspberry-pi/rpi-common.c @@ -430,7 +430,7 @@ static int rpi_vc_fdt_bootargs(void *fdt) struct device_node *root = NULL, *node; const char *cmdline; - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, INT_MAX); if (IS_ERR(root)) { ret = PTR_ERR(root); root = NULL; diff --git a/arch/arm/boards/webasto-ccbv2/board.c b/arch/arm/boards/webasto-ccbv2/board.c index a78258ea6a..477771309e 100644 --- a/arch/arm/boards/webasto-ccbv2/board.c +++ b/arch/arm/boards/webasto-ccbv2/board.c @@ -28,7 +28,7 @@ static int ccbv2_probe(struct device_d *dev) return 0; fdt = (void*)OPTEE_OVERLAY_LOCATION; - overlay = of_unflatten_dtb(fdt); + overlay = of_unflatten_dtb(fdt, INT_MAX); if (IS_ERR(overlay)) return PTR_ERR(overlay); diff --git a/arch/arm/lib32/bootm.c b/arch/arm/lib32/bootm.c index 28a645a9d0..0ffb374cf1 100644 --- a/arch/arm/lib32/bootm.c +++ b/arch/arm/lib32/bootm.c @@ -421,7 +421,7 @@ static int do_bootz_linux_fdt(int fd, struct image_data *data, void **outfdt) if (IS_BUILTIN(CONFIG_OFTREE)) { struct device_node *root; - root = of_unflatten_dtb(oftree); + root = of_unflatten_dtb(oftree, header->totalsize); if (IS_ERR(root)) { pr_err("unable to unflatten devicetree\n"); goto err_free; diff --git a/commands/of_diff.c b/commands/of_diff.c index d9d84fd4bf..fa99fcd641 100644 --- a/commands/of_diff.c +++ b/commands/of_diff.c @@ -44,7 +44,7 @@ static struct device_node *get_tree(const char *filename, struct device_node *ro if (ret) return ERR_PTR(ret); - node = of_unflatten_dtb(fdt); + node = of_unflatten_dtb(fdt, size); free(fdt); diff --git a/commands/of_display_timings.c b/commands/of_display_timings.c index 27b91f311a..4e5ec223b7 100644 --- a/commands/of_display_timings.c +++ b/commands/of_display_timings.c @@ -83,7 +83,7 @@ static int do_of_display_timings(int argc, char *argv[]) return -EINVAL; } - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, size); free(fdt); diff --git a/commands/of_dump.c b/commands/of_dump.c index 2089c07ef7..5223ba63ad 100644 --- a/commands/of_dump.c +++ b/commands/of_dump.c @@ -71,7 +71,7 @@ static int do_of_dump(int argc, char *argv[]) return -errno; } - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, size); free(fdt); @@ -88,7 +88,7 @@ static int do_of_dump(int argc, char *argv[]) /* create a copy of internal devicetree */ void *fdt; fdt = of_flatten_dtb(root); - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, fdt_totalsize(fdt)); free(fdt); diff --git a/commands/of_overlay.c b/commands/of_overlay.c index aa4b6484ca..9a4c008efc 100644 --- a/commands/of_overlay.c +++ b/commands/of_overlay.c @@ -17,6 +17,7 @@ static int do_of_overlay(int argc, char *argv[]) struct fdt_header *fdt; struct device_node *overlay; const char *search_path = NULL; + size_t size; while ((opt = getopt(argc, argv, "S:")) > 0) { switch (opt) { @@ -31,13 +32,13 @@ static int do_of_overlay(int argc, char *argv[]) if (argc != optind + 1) return COMMAND_ERROR_USAGE; - fdt = read_file(argv[optind], NULL); + fdt = read_file(argv[optind], &size); if (!fdt) { printf("cannot read %s\n", argv[optind]); return 1; } - overlay = of_unflatten_dtb(fdt); + overlay = of_unflatten_dtb(fdt, size); free(fdt); if (IS_ERR(overlay)) return PTR_ERR(overlay); diff --git a/commands/oftree.c b/commands/oftree.c index 1d902f2830..7d4b08c9d3 100644 --- a/commands/oftree.c +++ b/commands/oftree.c @@ -82,7 +82,7 @@ static int do_oftree(int argc, char *argv[]) return 1; } - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, size); free(fdt); diff --git a/common/blspec.c b/common/blspec.c index ad80d7a8cd..056c0dbf7f 100644 --- a/common/blspec.c +++ b/common/blspec.c @@ -40,17 +40,18 @@ static int blspec_apply_oftree_overlay(char *file, const char *abspath, struct device_node *overlay; char *path; char *firmware_path; + size_t size; path = basprintf("%s/%s", abspath, file); - fdt = read_file(path, NULL); + fdt = read_file(path, &size); if (!fdt) { pr_warn("unable to read \"%s\"\n", path); ret = -EINVAL; goto out; } - overlay = of_unflatten_dtb(fdt); + overlay = of_unflatten_dtb(fdt, size); free(fdt); if (IS_ERR(overlay)) { ret = PTR_ERR(overlay); @@ -490,7 +491,7 @@ static bool entry_is_of_compatible(struct blspec_entry *entry) goto out; } - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, size); if (IS_ERR(root)) { ret = false; root = NULL; diff --git a/common/bootm.c b/common/bootm.c index 644443a021..89e3e93f2c 100644 --- a/common/bootm.c +++ b/common/bootm.c @@ -361,7 +361,7 @@ void *bootm_get_devicetree(struct image_data *data) if (ret) return ERR_PTR(ret); - data->of_root_node = of_unflatten_dtb(of_tree); + data->of_root_node = of_unflatten_dtb(of_tree, of_size); } else if (data->oftree_file) { size_t size; @@ -389,7 +389,7 @@ void *bootm_get_devicetree(struct image_data *data) if (ret) return ERR_PTR(ret); - data->of_root_node = of_unflatten_dtb(oftree); + data->of_root_node = of_unflatten_dtb(oftree, size); free(oftree); diff --git a/common/efi/efi.c b/common/efi/efi.c index 01003dc00f..7f12342cf9 100644 --- a/common/efi/efi.c +++ b/common/efi/efi.c @@ -437,7 +437,7 @@ static int efi_late_init(void) return -EINVAL; } - root = of_unflatten_dtb(fdt); + root = of_unflatten_dtb(fdt, size); free(fdt); diff --git a/common/image-fit.c b/common/image-fit.c index 2c5ef7f687..c1a34a4405 100644 --- a/common/image-fit.c +++ b/common/image-fit.c @@ -754,7 +754,7 @@ static int fit_do_open(struct fit_handle *handle) const char *desc = "(no description)"; struct device_node *root; - root = of_unflatten_dtb_const(handle->fit); + root = of_unflatten_dtb_const(handle->fit, handle->size); if (IS_ERR(root)) return PTR_ERR(root); diff --git a/common/state/backend_format_dtb.c b/common/state/backend_format_dtb.c index 48f30db1f5..d0fc948859 100644 --- a/common/state/backend_format_dtb.c +++ b/common/state/backend_format_dtb.c @@ -59,7 +59,7 @@ static int state_backend_format_dtb_verify(struct state_backend_format *format, fdtb->root = NULL; } - root = of_unflatten_dtb(buf); + root = of_unflatten_dtb(buf, dtb_len); if (IS_ERR(root)) { dev_err(fdtb->dev, "Failed to unflatten dtb from buffer with length %zd, %ld\n", len, PTR_ERR(root)); diff --git a/drivers/of/base.c b/drivers/of/base.c index 5d93750aec..3e2517213d 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1720,7 +1720,7 @@ int barebox_register_fdt(const void *dtb) if (root_node) return -EBUSY; - root = of_unflatten_dtb(dtb); + root = of_unflatten_dtb(dtb, INT_MAX); if (IS_ERR(root)) { pr_err("Cannot unflatten dtb: %pe\n", root); return PTR_ERR(root); diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index d98913e54a..f72f5e3a30 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -114,7 +114,8 @@ static int of_unflatten_reservemap(struct device_node *root, * Parse a flat device tree binary blob and return a pointer to the * unflattened tree. */ -static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops) +static struct device_node *__of_unflatten_dtb(const void *infdt, int size, + bool constprops) { const void *nodep; /* property node pointer */ uint32_t tag; /* tag */ @@ -131,6 +132,9 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops unsigned int maxlen; const struct fdt_header *fdt = infdt; + if (size < sizeof(struct fdt_header)) + return ERR_PTR(-EINVAL); + if (fdt->magic != cpu_to_fdt32(FDT_MAGIC)) { pr_err("bad magic: 0x%08x\n", fdt32_to_cpu(fdt->magic)); return ERR_PTR(-EINVAL); @@ -147,6 +151,9 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops f.off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings); f.size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings); + if (f.totalsize > size) + return ERR_PTR(-EINVAL); + if (f.off_dt_struct + f.size_dt_struct > f.totalsize) { pr_err("unflatten: dt size exceeds total size\n"); return ERR_PTR(-ESPIPE); @@ -274,9 +281,9 @@ err: * Parse a flat device tree binary blob and return a pointer to the unflattened * tree. The tree must be freed after use with of_delete_node(). */ -struct device_node *of_unflatten_dtb(const void *infdt) +struct device_node *of_unflatten_dtb(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, false); + return __of_unflatten_dtb(infdt, size, false); } /** @@ -290,9 +297,9 @@ struct device_node *of_unflatten_dtb(const void *infdt) * whole lifetime of the returned tree. This is normally not what you want, so * use of_unflatten_dtb() instead. */ -struct device_node *of_unflatten_dtb_const(const void *infdt) +struct device_node *of_unflatten_dtb_const(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, true); + return __of_unflatten_dtb(infdt, size, true); } struct fdt { diff --git a/include/of.h b/include/of.h index 94eb71190d..b22996901f 100644 --- a/include/of.h +++ b/include/of.h @@ -109,8 +109,8 @@ void of_print_properties(struct device_node *node); void of_diff(struct device_node *a, struct device_node *b, int indent); int of_probe(void); int of_parse_dtb(struct fdt_header *fdt); -struct device_node *of_unflatten_dtb(const void *fdt); -struct device_node *of_unflatten_dtb_const(const void *infdt); +struct device_node *of_unflatten_dtb(const void *fdt, int size); +struct device_node *of_unflatten_dtb_const(const void *infdt, int size); struct cdev; -- cgit v1.2.3 From 303b5b0cc9cce0101fcbe05c7504a0ca5cda95ee Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:09 +0200 Subject: firmware: make device_node argument non const firmwaremgr_find_by_node() takes a const pointer to a device tree node. Most functions that take a device tree node take a non const pointer though, so we can't call them from there. It might be worth looking into making the pointers const for other functions, but we are not there yet. Make the pointer non const for now. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-5-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/firmware.c | 2 +- include/firmware.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'common') diff --git a/common/firmware.c b/common/firmware.c index e4b5025ab1..fa4f585693 100644 --- a/common/firmware.c +++ b/common/firmware.c @@ -63,7 +63,7 @@ struct firmware_mgr *firmwaremgr_find(const char *id) * handler. This allows to retrieve the firmware handler with a phandle from * the device tree. */ -struct firmware_mgr *firmwaremgr_find_by_node(const struct device_node *np) +struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np) { struct firmware_mgr *mgr; diff --git a/include/firmware.h b/include/firmware.h index 2fef97a48f..d37098688c 100644 --- a/include/firmware.h +++ b/include/firmware.h @@ -28,9 +28,9 @@ int firmwaremgr_register(struct firmware_handler *); struct firmware_mgr *firmwaremgr_find(const char *); #ifdef CONFIG_FIRMWARE -struct firmware_mgr *firmwaremgr_find_by_node(const struct device_node *np); +struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np); #else -static inline struct firmware_mgr *firmwaremgr_find_by_node(const struct device_node *np) +static inline struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np) { return NULL; } -- cgit v1.2.3 From 8a7dec93f98a26e944dcfffcc6a320922d5afa2f Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:12 +0200 Subject: firmware: Add search path This adds a colon separated search path for firmware files. When the firmware we are searching is not an absolute path then look in the search path first. This will be useful later when the bootloader spec implementation shall look for firmware files relative to the provided root. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-8-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/firmware.c | 41 +++++++++++++++++++++++++++++++++++++++++ include/firmware.h | 12 ++++++++++++ 2 files changed, 53 insertions(+) (limited to 'common') diff --git a/common/firmware.c b/common/firmware.c index fa4f585693..477dc1f13a 100644 --- a/common/firmware.c +++ b/common/firmware.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -208,6 +210,26 @@ out: return ret; } +static char *firmware_path; + +const char *firmware_get_searchpath(void) +{ + return firmware_path; +} + +void firmware_set_searchpath(const char *path) +{ + free(firmware_path); + firmware_path = strdup(path); +} + +static bool file_exists(const char *filename) +{ + struct stat s; + + return !stat(filename, &s); +} + /* * firmware_load_file - load a firmware to a device */ @@ -216,6 +238,7 @@ int firmwaremgr_load_file(struct firmware_mgr *mgr, const char *firmware) char *dst; enum filetype type; int ret = 0; + char *fw = NULL; int firmwarefd = 0; int devicefd = 0; @@ -229,6 +252,12 @@ int firmwaremgr_load_file(struct firmware_mgr *mgr, const char *firmware) dst = basprintf("/dev/%s", mgr->handler->id); + if (*firmware != '/') { + fw = find_path(firmware_path, firmware, file_exists); + if (fw) + firmware = fw; + } + firmwarefd = open(firmware, O_RDONLY); if (firmwarefd < 0) { printf("could not open %s: %s\n", firmware, @@ -254,6 +283,7 @@ int firmwaremgr_load_file(struct firmware_mgr *mgr, const char *firmware) out: free(dst); + free(fw); if (firmwarefd > 0) close(firmwarefd); @@ -262,3 +292,14 @@ out: return ret; } + +static int firmware_init(void) +{ + firmware_path = strdup("/env/firmware"); + globalvar_add_simple_string("firmware.path", &firmware_path); + + return 0; +} +device_initcall(firmware_init); + +BAREBOX_MAGICVAR(global.firmware.path, "Firmware search path"); diff --git a/include/firmware.h b/include/firmware.h index 39e84b9ac7..515bdcaf40 100644 --- a/include/firmware.h +++ b/include/firmware.h @@ -30,6 +30,8 @@ struct firmware_mgr *firmwaremgr_find(const char *); #ifdef CONFIG_FIRMWARE struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np); int firmwaremgr_load_file(struct firmware_mgr *, const char *path); +const char *firmware_get_searchpath(void); +void firmware_set_searchpath(const char *path); #else static inline struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np) { @@ -40,6 +42,16 @@ static inline int firmwaremgr_load_file(struct firmware_mgr *mgr, const char *pa { return -ENOSYS; } + +static inline const char *firmware_get_searchpath(void) +{ + return NULL; +} + +static inline void firmware_set_searchpath(const char *path) +{ +} + #endif void firmwaremgr_list_handlers(void); -- cgit v1.2.3 From 2bfa06b62e7c1f327a01f45f756136b7c7f802dd Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:13 +0200 Subject: firmware: Fix device_node matching firmwaremgr_find_by_node() matches against the device node of the parent of the device associated to the handler. This is correct for the socfpga and zyncmp driver, but not for the altera_serial driver. Add a device_node argument to the handler which is set by the drivers to the correct device node and match against that. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-9-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/firmware.c | 2 +- drivers/firmware/altera_serial.c | 1 + drivers/firmware/socfpga.c | 2 ++ drivers/firmware/zynqmp-fpga.c | 2 ++ include/firmware.h | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) (limited to 'common') diff --git a/common/firmware.c b/common/firmware.c index 477dc1f13a..bb76a16050 100644 --- a/common/firmware.c +++ b/common/firmware.c @@ -70,7 +70,7 @@ struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np) struct firmware_mgr *mgr; list_for_each_entry(mgr, &firmwaremgr_list, list) - if (mgr->handler->dev->parent->device_node == np) + if (mgr->handler->device_node == np) return mgr; return NULL; diff --git a/drivers/firmware/altera_serial.c b/drivers/firmware/altera_serial.c index 3a0175dd07..49460c6a70 100644 --- a/drivers/firmware/altera_serial.c +++ b/drivers/firmware/altera_serial.c @@ -371,6 +371,7 @@ static int altera_spi_probe(struct device_d *dev) if (model) fh->model = xstrdup(model); fh->dev = dev; + fh->device_node = dev->device_node; this->spi = (struct spi_device *)dev->type_data; this->data = data; diff --git a/drivers/firmware/socfpga.c b/drivers/firmware/socfpga.c index 04fdfdb3af..b17dacf683 100644 --- a/drivers/firmware/socfpga.c +++ b/drivers/firmware/socfpga.c @@ -435,6 +435,8 @@ static int socfpga_fpgamgr_probe(struct device_d *dev) } fh->dev = &mgr->dev; + fh->device_node = dev->device_node; + ret = firmwaremgr_register(fh); if (ret != 0) { free(mgr); diff --git a/drivers/firmware/zynqmp-fpga.c b/drivers/firmware/zynqmp-fpga.c index ab70d99933..0fc229bfd3 100644 --- a/drivers/firmware/zynqmp-fpga.c +++ b/drivers/firmware/zynqmp-fpga.c @@ -383,6 +383,8 @@ static int zynqmp_fpga_probe(struct device_d *dev) } fh->dev = &mgr->dev; + fh->device_node = dev->device_node; + ret = firmwaremgr_register(fh); if (ret != 0) { free(mgr); diff --git a/include/firmware.h b/include/firmware.h index 515bdcaf40..0ffea52840 100644 --- a/include/firmware.h +++ b/include/firmware.h @@ -14,6 +14,7 @@ struct firmware_handler { char *model; /* description for this device */ struct device_d *dev; void *priv; + struct device_node *device_node; /* called once to prepare the firmware's programming cycle */ int (*open)(struct firmware_handler*); /* called multiple times to program the firmware with the given data */ -- cgit v1.2.3 From 04a6e765f70aaf7614ec62a7955d9f07b4309a17 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:14 +0200 Subject: firmware: recognize by reproducible name firmwaremgr_find_by_node() matches the device node pointers to find the firmware manager associated to a node. This function is called by the of_overlay code when it finds a firmware-name property to find a firmware manager for this node. This works when the overlay is applied to the live tree, but not when it's applied to the tree we are going to load the kernel with. To overcome this limitation match by the nodes reproducible name instead of pointers. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-10-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/firmware.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'common') diff --git a/common/firmware.c b/common/firmware.c index bb76a16050..b33acff77f 100644 --- a/common/firmware.c +++ b/common/firmware.c @@ -68,10 +68,22 @@ struct firmware_mgr *firmwaremgr_find(const char *id) struct firmware_mgr *firmwaremgr_find_by_node(struct device_node *np) { struct firmware_mgr *mgr; + char *na, *nb; - list_for_each_entry(mgr, &firmwaremgr_list, list) - if (mgr->handler->device_node == np) + na = of_get_reproducible_name(np); + + list_for_each_entry(mgr, &firmwaremgr_list, list) { + nb = of_get_reproducible_name(mgr->handler->device_node); + if (!strcmp(na, nb)) { + free(na); + free(nb); return mgr; + } + + free(nb); + } + + free(na); return NULL; } -- cgit v1.2.3 From dfebbb0a594453a02078fe3d8370496c9a0a1349 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:15 +0200 Subject: blspec: Set firmware searchpath When bootloader spec triggers dt overlay loading then this might also trigger loading firmware. This firmware should be looked for relative to the filesystem providing the bootloader spec files, so add that to the firmware search path. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-11-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/blspec.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'common') diff --git a/common/blspec.c b/common/blspec.c index 056c0dbf7f..c39805cb0b 100644 --- a/common/blspec.c +++ b/common/blspec.c @@ -122,6 +122,8 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun) const char *abspath, *devicetree, *options, *initrd, *linuximage; const char *overlays; const char *appendroot; + const char *old_fws; + char *fws; struct bootm_data data = { .dryrun = dryrun, }; @@ -184,9 +186,20 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun) (entry->cdev && entry->cdev->dev) ? dev_name(entry->cdev->dev) : "none"); + old_fws = firmware_get_searchpath(); + if (old_fws && *old_fws) + fws = basprintf("%s/lib/firmware:%s", abspath, old_fws); + else + fws = basprintf("%s/lib/firmware", abspath); + firmware_set_searchpath(fws); + free(fws); + ret = bootm_boot(&data); if (ret) pr_err("Booting failed\n"); + + firmware_set_searchpath(old_fws); + err_out: free((char *)data.oftree_file); free((char *)data.initrd_file); -- cgit v1.2.3 From 04e3a8fe4ae82bacd532e4fe91ec7a79260043e1 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:17 +0200 Subject: firmware: Load from global search path We have a global firmware search path, so use it. This removes the path argument from of_firmware_load_overlay(). blspec already extends the global firmware search path, so the path is not needed there. The of_overlay command has an option for specifying the search path, this is removed here, the global search path has to be used instead. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-13-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- commands/of_overlay.c | 31 ++++++------------------------- common/blspec.c | 11 +---------- drivers/of/of_firmware.c | 25 ++++--------------------- include/of.h | 4 ++-- 4 files changed, 13 insertions(+), 58 deletions(-) (limited to 'common') diff --git a/commands/of_overlay.c b/commands/of_overlay.c index 9a4c008efc..71f41a3c04 100644 --- a/commands/of_overlay.c +++ b/commands/of_overlay.c @@ -13,23 +13,12 @@ static int do_of_overlay(int argc, char *argv[]) { - int opt, ret; + int ret; struct fdt_header *fdt; struct device_node *overlay; - const char *search_path = NULL; size_t size; - while ((opt = getopt(argc, argv, "S:")) > 0) { - switch (opt) { - case 'S': - search_path = optarg; - break; - default: - return COMMAND_ERROR_USAGE; - } - } - - if (argc != optind + 1) + if (argc != 2) return COMMAND_ERROR_USAGE; fdt = read_file(argv[optind], &size); @@ -43,11 +32,9 @@ static int do_of_overlay(int argc, char *argv[]) if (IS_ERR(overlay)) return PTR_ERR(overlay); - if (search_path) { - ret = of_firmware_load_overlay(overlay, search_path); - if (ret) - goto err; - } + ret = of_firmware_load_overlay(overlay); + if (ret) + goto err; ret = of_register_overlay(overlay); if (ret) { @@ -62,15 +49,9 @@ err: return ret; } -BAREBOX_CMD_HELP_START(of_overlay) -BAREBOX_CMD_HELP_TEXT("Options:") -BAREBOX_CMD_HELP_OPT("-S path", "load firmware using this search path") -BAREBOX_CMD_HELP_END - BAREBOX_CMD_START(of_overlay) .cmd = do_of_overlay, BAREBOX_CMD_DESC("register device tree overlay as fixup") - BAREBOX_CMD_OPTS("[-S path] FILE") + BAREBOX_CMD_OPTS("FILE") BAREBOX_CMD_GROUP(CMD_GRP_MISC) - BAREBOX_CMD_HELP(cmd_of_overlay_help) BAREBOX_CMD_END diff --git a/common/blspec.c b/common/blspec.c index c39805cb0b..148e52b038 100644 --- a/common/blspec.c +++ b/common/blspec.c @@ -39,7 +39,6 @@ static int blspec_apply_oftree_overlay(char *file, const char *abspath, struct fdt_header *fdt; struct device_node *overlay; char *path; - char *firmware_path; size_t size; path = basprintf("%s/%s", abspath, file); @@ -64,15 +63,7 @@ static int blspec_apply_oftree_overlay(char *file, const char *abspath, goto out; } - /* - * Unfortunately the device tree overlay contains only the filename of - * the firmware and relies on the firmware search paths to find the - * actual file. Use /lib/firmware in the Linux root directory and hope - * for the best. - */ - firmware_path = basprintf("%s/%s", abspath, "/lib/firmware"); - ret = of_firmware_load_overlay(overlay, firmware_path); - free(firmware_path); + ret = of_firmware_load_overlay(overlay); if (ret) { pr_warn("failed to load firmware: skip overlay \"%s\"\n", path); of_delete_node(overlay); diff --git a/drivers/of/of_firmware.c b/drivers/of/of_firmware.c index 096f84572e..12ce1d95d0 100644 --- a/drivers/of/of_firmware.c +++ b/drivers/of/of_firmware.c @@ -6,10 +6,6 @@ #include #include -struct overlay_info { - const char *firmware_path; -}; - static struct firmware_mgr *of_node_get_mgr(struct device_node *np) { struct device_node *mgr_node; @@ -26,12 +22,9 @@ static struct firmware_mgr *of_node_get_mgr(struct device_node *np) } static int load_firmware(struct device_node *target, - struct device_node *fragment, void *data) + struct device_node *fragment, void *unused) { - struct overlay_info *info = data; const char *firmware_name; - const char *firmware_path = info->firmware_path; - char *firmware; int err; struct firmware_mgr *mgr; @@ -50,22 +43,13 @@ static int load_firmware(struct device_node *target, if (!mgr) return -EINVAL; - firmware = basprintf("%s/%s", firmware_path, firmware_name); - if (!firmware) - return -ENOMEM; - - err = firmwaremgr_load_file(mgr, firmware); - - free(firmware); + err = firmwaremgr_load_file(mgr, firmware_name); return err; } -int of_firmware_load_overlay(struct device_node *overlay, const char *path) +int of_firmware_load_overlay(struct device_node *overlay) { - struct overlay_info info = { - .firmware_path = path, - }; int err; struct device_node *root; struct device_node *resolved; @@ -81,8 +65,7 @@ int of_firmware_load_overlay(struct device_node *overlay, const char *path) */ ovl = resolved ? resolved : overlay; - err = of_process_overlay(root, ovl, - load_firmware, &info); + err = of_process_overlay(root, ovl, load_firmware, NULL); if (resolved) of_delete_node(resolved); diff --git a/include/of.h b/include/of.h index 9213d4165b..e1752fb28f 100644 --- a/include/of.h +++ b/include/of.h @@ -1035,7 +1035,7 @@ int of_process_overlay(struct device_node *root, struct device_node *overlay, void *data), void *data); -int of_firmware_load_overlay(struct device_node *overlay, const char *path); +int of_firmware_load_overlay(struct device_node *overlay); #else static inline struct device_node *of_resolve_phandles(struct device_node *root, const struct device_node *overlay) @@ -1069,7 +1069,7 @@ static inline int of_process_overlay(struct device_node *root, return -ENOSYS; } -static inline int of_firmware_load_overlay(struct device_node *overlay, const char *path) +static inline int of_firmware_load_overlay(struct device_node *overlay) { return -ENOSYS; } -- cgit v1.2.3 From eb1d3c2f4e5408575b470e5b1bf963c1df43491b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:18 +0200 Subject: blspec: Rework firmware load Applying overlays in blspec currently works in two steps. First of_firmware_load_overlay() is called which doesn't load an overlay, but instead loads firmware when one is needed by the overlay. This is done on the live tree, because that was needed to find the firmware manager. The second step is to call of_register_overlay() to apply the overlay to the kernel device tree when the fixups are executed. Instead of using a separate step to load the firmware, load the firmware as part of the of_fixups. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-14-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- arch/arm/lib32/bootm.c | 4 ++ arch/arm/lib32/bootu.c | 5 +- arch/arm/lib32/bootz.c | 3 ++ arch/arm/lib64/armlinux.c | 5 ++ arch/kvx/lib/bootm.c | 6 +++ arch/mips/lib/bootm.c | 4 ++ arch/powerpc/lib/ppclinux.c | 4 ++ arch/riscv/lib/bootm.c | 5 ++ commands/of_overlay.c | 4 -- common/blspec.c | 73 ++++++++--------------------- common/oftree.c | 2 + drivers/of/of_firmware.c | 111 ++++++++++++++++++++++++++++++++++---------- drivers/of/overlay.c | 5 ++ include/of.h | 17 ++++++- 14 files changed, 163 insertions(+), 85 deletions(-) (limited to 'common') diff --git a/arch/arm/lib32/bootm.c b/arch/arm/lib32/bootm.c index 0ffb374cf1..2bba585e96 100644 --- a/arch/arm/lib32/bootm.c +++ b/arch/arm/lib32/bootm.c @@ -325,6 +325,10 @@ static int __do_bootm_linux(struct image_data *data, unsigned long free_mem, if (data->dryrun) return 0; + ret = of_overlay_load_firmware(); + if (ret) + return ret; + if (data->tee_res) tee = (void *)data->tee_res->start; else diff --git a/arch/arm/lib32/bootu.c b/arch/arm/lib32/bootu.c index 24c744da58..0540a16280 100644 --- a/arch/arm/lib32/bootu.c +++ b/arch/arm/lib32/bootu.c @@ -8,7 +8,7 @@ static int do_bootu(int argc, char *argv[]) { - int fd; + int fd, ret; void *kernel = NULL; void *oftree = NULL; @@ -25,6 +25,9 @@ static int do_bootu(int argc, char *argv[]) #ifdef CONFIG_OFTREE oftree = of_get_fixed_tree(NULL); #endif + ret = of_overlay_load_firmware(); + if (ret) + return ret; start_linux(kernel, 0, 0, 0, oftree, ARM_STATE_SECURE, NULL); diff --git a/arch/arm/lib32/bootz.c b/arch/arm/lib32/bootz.c index a2a26ac2f9..486e945a74 100644 --- a/arch/arm/lib32/bootz.c +++ b/arch/arm/lib32/bootz.c @@ -111,6 +111,9 @@ static int do_bootz(int argc, char *argv[]) #ifdef CONFIG_OFTREE oftree = of_get_fixed_tree(NULL); #endif + ret = of_overlay_load_firmware(); + if (ret) + return ret; start_linux(zimage, swap, 0, 0, oftree, ARM_STATE_SECURE, NULL); diff --git a/arch/arm/lib64/armlinux.c b/arch/arm/lib64/armlinux.c index 0ba4d30b8e..078841b1d4 100644 --- a/arch/arm/lib64/armlinux.c +++ b/arch/arm/lib64/armlinux.c @@ -11,11 +11,16 @@ static int do_bootm_linux(struct image_data *data) void (*fn)(unsigned long dtb, unsigned long x1, unsigned long x2, unsigned long x3); phys_addr_t devicetree; + int ret; fn = booti_load_image(data, &devicetree); if (IS_ERR(fn)) return PTR_ERR(fn); + ret = of_overlay_load_firmware(); + if (ret) + return ret; + shutdown_barebox(); fn(devicetree, 0, 0, 0); diff --git a/arch/kvx/lib/bootm.c b/arch/kvx/lib/bootm.c index 198eef7980..4d17e1846d 100644 --- a/arch/kvx/lib/bootm.c +++ b/arch/kvx/lib/bootm.c @@ -24,11 +24,17 @@ typedef void __noreturn (*boot_func_entry)(unsigned long, void *); static int do_boot_entry(struct image_data *data, boot_func_entry entry, void *fdt_load_addr) { + int ret; + printf("starting elf (entry at %p)\n", entry); if (data->dryrun) return 0; + ret = of_overlay_load_firmware(); + if (ret) + return ret; + shutdown_barebox(); /* Synchronize I-cache with D-cache */ diff --git a/arch/mips/lib/bootm.c b/arch/mips/lib/bootm.c index 6c56202ea9..b71b8d5c38 100644 --- a/arch/mips/lib/bootm.c +++ b/arch/mips/lib/bootm.c @@ -65,6 +65,10 @@ static int do_bootm_elf(struct image_data *data) if (data->dryrun) goto bootm_free_fdt; + ret = of_overlay_load_firmware(); + if (ret) + return ret; + shutdown_barebox(); entry = (void *) (unsigned long) data->os_address; diff --git a/arch/powerpc/lib/ppclinux.c b/arch/powerpc/lib/ppclinux.c index 05c29be7da..b4cb59a524 100644 --- a/arch/powerpc/lib/ppclinux.c +++ b/arch/powerpc/lib/ppclinux.c @@ -67,6 +67,10 @@ static int do_bootm_linux(struct image_data *data) if (data->dryrun) return 0; + ret = of_overlay_load_firmware(); + if (ret) + return ret; + /* Relocate the device tree if outside the initial * Linux mapped TLB. */ diff --git a/arch/riscv/lib/bootm.c b/arch/riscv/lib/bootm.c index b3e41de4a8..8504ee63a9 100644 --- a/arch/riscv/lib/bootm.c +++ b/arch/riscv/lib/bootm.c @@ -8,11 +8,16 @@ static int do_bootm_linux(struct image_data *data) { void (*fn)(unsigned long a0, unsigned long dtb, unsigned long a2); phys_addr_t devicetree; + int ret; fn = booti_load_image(data, &devicetree); if (IS_ERR(fn)) return PTR_ERR(fn); + ret = of_overlay_load_firmware(); + if (ret) + return ret; + shutdown_barebox(); fn(0, devicetree, 0); diff --git a/commands/of_overlay.c b/commands/of_overlay.c index 71f41a3c04..b3660b4bf1 100644 --- a/commands/of_overlay.c +++ b/commands/of_overlay.c @@ -32,10 +32,6 @@ static int do_of_overlay(int argc, char *argv[]) if (IS_ERR(overlay)) return PTR_ERR(overlay); - ret = of_firmware_load_overlay(overlay); - if (ret) - goto err; - ret = of_register_overlay(overlay); if (ret) { printf("cannot apply oftree overlay: %s\n", strerror(-ret)); diff --git a/common/blspec.c b/common/blspec.c index 148e52b038..ca96a45487 100644 --- a/common/blspec.c +++ b/common/blspec.c @@ -32,71 +32,33 @@ int blspec_entry_var_set(struct blspec_entry *entry, const char *name, val ? strlen(val) + 1 : 0, 1); } -static int blspec_apply_oftree_overlay(char *file, const char *abspath, - int dryrun) -{ - int ret = 0; - struct fdt_header *fdt; - struct device_node *overlay; - char *path; - size_t size; - - path = basprintf("%s/%s", abspath, file); - - fdt = read_file(path, &size); - if (!fdt) { - pr_warn("unable to read \"%s\"\n", path); - ret = -EINVAL; - goto out; - } - - overlay = of_unflatten_dtb(fdt, size); - free(fdt); - if (IS_ERR(overlay)) { - ret = PTR_ERR(overlay); - goto out; - } - - if (dryrun) { - pr_info("dry run: skip overlay %s\n", path); - of_delete_node(overlay); - goto out; - } - - ret = of_firmware_load_overlay(overlay); - if (ret) { - pr_warn("failed to load firmware: skip overlay \"%s\"\n", path); - of_delete_node(overlay); - goto out; - } - - ret = of_register_overlay(overlay); - if (ret) { - pr_warn("cannot register devicetree overlay \"%s\"\n", path); - of_delete_node(overlay); - } - -out: - free(path); - - return ret; -} - -static void blspec_apply_oftree_overlays(const char *overlays, - const char *abspath, int dryrun) +static int blspec_overlay_fixup(struct device_node *root, void *ctx) { + struct blspec_entry *entry = ctx; + const char *overlays; char *overlay; char *sep, *freep; + overlays = blspec_entry_var_get(entry, "devicetree-overlay"); + sep = freep = xstrdup(overlays); while ((overlay = strsep(&sep, " "))) { + char *path; + if (!*overlay) continue; - blspec_apply_oftree_overlay(overlay, abspath, dryrun); + + path = basprintf("%s/%s", entry->rootpath, overlay); + + of_overlay_apply_file(root, path); + + free(path); } free(freep); + + return 0; } /* @@ -153,7 +115,7 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun) } if (overlays) - blspec_apply_oftree_overlays(overlays, abspath, dryrun); + of_register_fixup(blspec_overlay_fixup, entry); if (initrd) data.initrd_file = basprintf("%s/%s", abspath, initrd); @@ -189,6 +151,9 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun) if (ret) pr_err("Booting failed\n"); + if (overlays) + of_unregister_fixup(blspec_overlay_fixup, entry); + firmware_set_searchpath(old_fws); err_out: diff --git a/common/oftree.c b/common/oftree.c index da8043809b..7028c49aca 100644 --- a/common/oftree.c +++ b/common/oftree.c @@ -326,6 +326,8 @@ int of_fix_tree(struct device_node *node) struct of_fixup *of_fixup; int ret; + of_overlay_load_firmware_clear(); + list_for_each_entry(of_fixup, &of_fixup_list, list) { ret = of_fixup->fixup(node, of_fixup->context); if (ret) diff --git a/drivers/of/of_firmware.c b/drivers/of/of_firmware.c index 12ce1d95d0..2d8245a02e 100644 --- a/drivers/of/of_firmware.c +++ b/drivers/of/of_firmware.c @@ -21,12 +21,21 @@ static struct firmware_mgr *of_node_get_mgr(struct device_node *np) return NULL; } +struct fw_load_entry { + struct firmware_mgr *mgr; + char *firmware; + struct list_head list; +}; + +static LIST_HEAD(fw_load_list); + static int load_firmware(struct device_node *target, struct device_node *fragment, void *unused) { const char *firmware_name; int err; struct firmware_mgr *mgr; + struct fw_load_entry *fle; err = of_property_read_string(fragment, "firmware-name", &firmware_name); @@ -43,32 +52,86 @@ static int load_firmware(struct device_node *target, if (!mgr) return -EINVAL; - err = firmwaremgr_load_file(mgr, firmware_name); + fle = xzalloc(sizeof(*fle)); + fle->mgr = mgr; + fle->firmware = xstrdup(firmware_name); - return err; + list_add_tail(&fle->list, &fw_load_list); + + return 0; } -int of_firmware_load_overlay(struct device_node *overlay) +/* + * The dt overlay API says that a "firmware-name" property found in an overlay + * node compatible to "fpga-region" triggers loading of the firmware with the + * name given in the "firmware-name" property. + * + * barebox applies overlays to the Kernel device tree as part of booting the + * Kernel. When a firmware is needed for an overlay then it shall be loaded, + * so that the Kernel already finds the firmware loaded. + * + * In barebox overlays are applied as a of_fixup to the desired tree. The fixups + * might be executed multiple times not only as part of booting the Kernel, but + * also during of_diff command execution and other actions. It's not desired + * that we (re-)load all firmwares each time this happens, so the process is + * splitted up. During application of an overlay the needed firmwares are only + * collected to a list, but not actually loaded. Only once it's clear we want to + * boot with that device tree the firmwares are loaded by explicitly calling + * of_overlay_load_firmware(). + */ + +/** + * of_overlay_pre_load_firmware() - check overlay node for firmware to load + * @root: The device tree to apply the overlay to + * @overlay: The overlay + * + * This function checks the given overlay for firmware to load. If a firmware + * is needed then it is not directly loaded, but instead added to a list of + * firmware to be loaded. The firmware files on this list can then be loaded + * with of_overlay_load_firmware(). + * + * Return: 0 for success or negative error code otherwise + */ +int of_overlay_pre_load_firmware(struct device_node *root, struct device_node *overlay) { - int err; - struct device_node *root; - struct device_node *resolved; - struct device_node *ovl; - - root = of_get_root_node(); - resolved = of_resolve_phandles(root, overlay); - /* - * If the overlay cannot be resolved, use the load_firmware callback - * with the unresolved overlay to verify that the overlay does not - * depend on a firmware to be loaded. If a required firmware cannot be - * loaded, the overlay must not be applied. - */ - ovl = resolved ? resolved : overlay; - - err = of_process_overlay(root, ovl, load_firmware, NULL); - - if (resolved) - of_delete_node(resolved); - - return err; + return of_process_overlay(root, overlay, load_firmware, NULL); +} + +/** + * of_overlay_load_firmware() - load all firmware files + * + * This function loads all firmware files previously collected in + * of_overlay_pre_load_firmware(). + * + * Return: 0 when all firmware files could be loaded, negative error code + * otherwise. + */ +int of_overlay_load_firmware(void) +{ + struct fw_load_entry *fle; + int ret; + + list_for_each_entry(fle, &fw_load_list, list) { + ret = firmwaremgr_load_file(fle->mgr, fle->firmware); + if (ret) + return ret; + } + + return 0; +} + +/** + * of_overlay_load_firmware_clear() - Clear list of firmware files + * + * This function clears the list of firmware files. + */ +void of_overlay_load_firmware_clear(void) +{ + struct fw_load_entry *fle, *tmp; + + list_for_each_entry_safe(fle, tmp, &fw_load_list, list) { + list_del(&fle->list); + free(fle->firmware); + free(fle); + } } diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index f238b39ff1..26b5fee775 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -181,6 +181,10 @@ int of_overlay_apply_tree(struct device_node *root, 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 */ of_overlay_apply_symbols(root, resolved); @@ -191,6 +195,7 @@ int of_overlay_apply_tree(struct device_node *root, pr_warn("failed to apply %s\n", fragment->name); } +out_err: of_delete_node(resolved); return err; diff --git a/include/of.h b/include/of.h index e1752fb28f..59c1250fb2 100644 --- a/include/of.h +++ b/include/of.h @@ -1035,7 +1035,9 @@ int of_process_overlay(struct device_node *root, struct device_node *overlay, void *data), void *data); -int of_firmware_load_overlay(struct device_node *overlay); +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); #else static inline struct device_node *of_resolve_phandles(struct device_node *root, const struct device_node *overlay) @@ -1069,10 +1071,21 @@ static inline int of_process_overlay(struct device_node *root, return -ENOSYS; } -static inline int of_firmware_load_overlay(struct device_node *overlay) +static inline int of_overlay_pre_load_firmware(struct device_node *root, + struct device_node *overlay) { return -ENOSYS; } + +static inline int of_overlay_load_firmware(void) +{ + return 0; +} + +static inline void of_overlay_load_firmware_clear(void) +{ +} + #endif #endif /* __OF_H */ -- cgit v1.2.3 From 124c64af09829bb4e099b91cae4019951516f3ed Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:19 +0200 Subject: overlay: Add filters to choose which overlays to apply This adds a filter mechanism to choose which overlays to apply. Filters can either match on the filename or on the content of an overlay. Two generic filters are registered, one matching filename patterns given in global.of.overlay.filepattern, the other matching device tree compatibles given in global.of.overlay.compatible. Other board or SoC specific filters can be registered and activated using the global.of.overlay.filter variable. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-15-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/blspec.c | 2 +- drivers/of/overlay.c | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++- include/of.h | 18 ++- 3 files changed, 330 insertions(+), 4 deletions(-) (limited to 'common') 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 #include #include +#include +#include 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 ?: "", + ovl, filter->name); + else + pr_debug("filename %s, overlay %p: no match\n", + filename ?: "", 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 */ -- cgit v1.2.3 From b631f60acf8050963daab1a95d854e4ead33b28f Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 24 Jun 2021 10:52:20 +0200 Subject: blspec: Apply overlays from rootfs This sets the overlay search path to $BOOT/overlays during starting an bootloader spec entry with the effect that overlays from there can be applied. Signed-off-by: Sascha Hauer Link: https://lore.barebox.org/20210624085223.14616-16-s.hauer@pengutronix.de Signed-off-by: Sascha Hauer --- common/blspec.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'common') diff --git a/common/blspec.c b/common/blspec.c index 4146915ffb..6cb1fea9e8 100644 --- a/common/blspec.c +++ b/common/blspec.c @@ -139,6 +139,8 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun) (entry->cdev && entry->cdev->dev) ? dev_name(entry->cdev->dev) : "none"); + of_overlay_set_basedir(abspath); + old_fws = firmware_get_searchpath(); if (old_fws && *old_fws) fws = basprintf("%s/lib/firmware:%s", abspath, old_fws); @@ -154,6 +156,7 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun) if (overlays) of_unregister_fixup(blspec_overlay_fixup, entry); + of_overlay_set_basedir("/"); firmware_set_searchpath(old_fws); err_out: -- cgit v1.2.3