[U-Boot] [PATCH v4 0/2] fit: Image node compression

This patch series adds compression support for non-kernel FIT image nodes (e.g. FDTs). The first patch adds the compression support itself, the second adds a new feature to compatible string matching that allows it to be useful with compressed FDTs.
Sandbox-tested with FIT images with and without compressed FDTs, with and without overlays, in both compatible string matching and direct config selection modes. Also expanded the test_fit pytest to include a case with compressed kernel, FDT and ramdisk.
Julius Werner (2): fit: Support compression for non-kernel components (e.g. FDT) - Changes for v2: - Changed from only supporting compressed FDTs to supporting all non-kernel image node types. - Changes for v3: - Fixed up some debug output that was still written for v1. - Fixed a mistake with handling FIT_LOAD_OPTIONAL_NON_ZERO when 'load' was 0 (i.e. unset). - Added compression test case to the test_fit pytest. - No changes for v4 fit: Support compat string property in configuration node - No changes for v2 - No changes for v3 - Changes for v4: - Added documentation for compatible string in config node. - Added example .its file for compressed FDT with compat string in config node.
common/image-fit.c | 140 +++++++++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 57 deletions(-)

This patch adds support for compressing non-kernel image nodes in a FIT image (kernel nodes could already be compressed previously). This can reduce the size of FIT images and therefore improve boot times (especially when an image bundles many different kernel FDTs). The images will automatically be decompressed on load.
This patch does not support extracting compatible strings from compressed FDTs, so it's not very helpful in conjunction with CONFIG_FIT_BEST_MATCH yet, but it can already be used in environments that select the configuration to load explicitly.
Signed-off-by: Julius Werner jwerner@chromium.org --- - Changes for v2: - Changed from only supporting compressed FDTs to supporting all non-kernel image node types. - Changes for v3: - Fixed up some debug output that was still written for v1. - Fixed a mistake with handling FIT_LOAD_OPTIONAL_NON_ZERO when 'load' was 0 (i.e. unset). - Added compression test case to the test_fit pytest. - No changes for v4
common/image-fit.c | 86 +++++++++++++++++++++++---------------- test/py/tests/test_fit.py | 29 +++++++++++-- 2 files changed, 77 insertions(+), 38 deletions(-)
diff --git a/common/image-fit.c b/common/image-fit.c index ac901e131c..469c5c8f49 100644 --- a/common/image-fit.c +++ b/common/image-fit.c @@ -22,6 +22,7 @@ DECLARE_GLOBAL_DATA_PTR; #endif /* !USE_HOSTCC*/
+#include <bootm.h> #include <image.h> #include <bootstage.h> #include <u-boot/crc.h> @@ -1576,6 +1577,13 @@ int fit_conf_find_compat(const void *fit, const void *fdt) kfdt_name); continue; } + + if (!fit_image_check_comp(fit, kfdt_noffset, IH_COMP_NONE)) { + debug("Can't extract compat from "%s" (compressed)\n", + kfdt_name); + continue; + } + /* * Get a pointer to this configuration's fdt. */ @@ -1795,11 +1803,12 @@ int fit_image_load(bootm_headers_t *images, ulong addr, const char *fit_uname_config; const char *fit_base_uname_config; const void *fit; - const void *buf; + void *buf; + void *loadbuf; size_t size; int type_ok, os_ok; - ulong load, data, len; - uint8_t os; + ulong load, load_end, data, len; + uint8_t os, comp; #ifndef USE_HOSTCC uint8_t os_arch; #endif @@ -1895,12 +1904,6 @@ int fit_image_load(bootm_headers_t *images, ulong addr, images->os.arch = os_arch; #endif
- if (image_type == IH_TYPE_FLATDT && - !fit_image_check_comp(fit, noffset, IH_COMP_NONE)) { - puts("FDT image is compressed"); - return -EPROTONOSUPPORT; - } - bootstage_mark(bootstage_id + BOOTSTAGE_SUB_CHECK_ALL); type_ok = fit_image_check_type(fit, noffset, image_type) || fit_image_check_type(fit, noffset, IH_TYPE_FIRMWARE) || @@ -1931,7 +1934,8 @@ int fit_image_load(bootm_headers_t *images, ulong addr, bootstage_mark(bootstage_id + BOOTSTAGE_SUB_CHECK_ALL_OK);
/* get image data address and length */ - if (fit_image_get_data_and_size(fit, noffset, &buf, &size)) { + if (fit_image_get_data_and_size(fit, noffset, + (const void **)&buf, &size)) { printf("Could not find %s subimage data!\n", prop_name); bootstage_error(bootstage_id + BOOTSTAGE_SUB_GET_DATA); return -ENOENT; @@ -1939,30 +1943,15 @@ int fit_image_load(bootm_headers_t *images, ulong addr,
#if !defined(USE_HOSTCC) && defined(CONFIG_FIT_IMAGE_POST_PROCESS) /* perform any post-processing on the image data */ - board_fit_image_post_process((void **)&buf, &size); + board_fit_image_post_process(&buf, &size); #endif
len = (ulong)size;
- /* verify that image data is a proper FDT blob */ - if (image_type == IH_TYPE_FLATDT && fdt_check_header(buf)) { - puts("Subimage data is not a FDT"); - return -ENOEXEC; - } - bootstage_mark(bootstage_id + BOOTSTAGE_SUB_GET_DATA_OK);
- /* - * Work-around for eldk-4.2 which gives this warning if we try to - * cast in the unmap_sysmem() call: - * warning: initialization discards qualifiers from pointer target type - */ - { - void *vbuf = (void *)buf; - - data = map_to_sysmem(vbuf); - } - + data = map_to_sysmem(buf); + load = data; if (load_op == FIT_LOAD_IGNORED) { /* Don't load */ } else if (fit_image_get_load(fit, noffset, &load)) { @@ -1974,8 +1963,6 @@ int fit_image_load(bootm_headers_t *images, ulong addr, } } else if (load_op != FIT_LOAD_OPTIONAL_NON_ZERO || load) { ulong image_start, image_end; - ulong load_end; - void *dst;
/* * move image data to the load address, @@ -1993,14 +1980,45 @@ int fit_image_load(bootm_headers_t *images, ulong addr,
printf(" Loading %s from 0x%08lx to 0x%08lx\n", prop_name, data, load); + } else { + load = data; /* No load address specified */ + } + + comp = IH_COMP_NONE; + loadbuf = buf; + /* Kernel images get decompressed later in bootm_load_os(). */ + if (!(image_type == IH_TYPE_KERNEL || + image_type == IH_TYPE_KERNEL_NOLOAD) && + !fit_image_get_comp(fit, noffset, &comp) && + comp != IH_COMP_NONE) { + ulong max_decomp_len = len * 20; + if (load == data) { + loadbuf = malloc(max_decomp_len); + load = map_to_sysmem(loadbuf); + } else { + loadbuf = map_sysmem(load, max_decomp_len); + } + if (bootm_decomp_image(comp, load, data, image_type, + loadbuf, buf, len, max_decomp_len, &load_end)) { + printf("Error decompressing %s\n", prop_name);
- dst = map_sysmem(load, len); - memmove(dst, buf, len); - data = load; + return -ENOEXEC; + } + len = load_end - load; + } else if (load != data) { + loadbuf = map_sysmem(load, len); + memcpy(loadbuf, buf, len); } + + /* verify that image data is a proper FDT blob */ + if (image_type == IH_TYPE_FLATDT && fdt_check_header(loadbuf)) { + puts("Subimage data is not a FDT"); + return -ENOEXEC; + } + bootstage_mark(bootstage_id + BOOTSTAGE_SUB_LOAD);
- *datap = data; + *datap = load; *lenp = len; if (fit_unamep) *fit_unamep = (char *)fit_uname; diff --git a/test/py/tests/test_fit.py b/test/py/tests/test_fit.py index 49d6fea571..8009d2907b 100755 --- a/test/py/tests/test_fit.py +++ b/test/py/tests/test_fit.py @@ -24,7 +24,7 @@ base_its = ''' type = "kernel"; arch = "sandbox"; os = "linux"; - compression = "none"; + compression = "%(compression)s"; load = <0x40000>; entry = <0x8>; }; @@ -39,11 +39,11 @@ base_its = ''' }; fdt@1 { description = "snow"; - data = /incbin/("u-boot.dtb"); + data = /incbin/("%(fdt)s"); type = "flat_dt"; arch = "sandbox"; %(fdt_load)s - compression = "none"; + compression = "%(compression)s"; signature@1 { algo = "sha1,rsa2048"; key-name-hint = "dev"; @@ -56,7 +56,7 @@ base_its = ''' arch = "sandbox"; os = "linux"; %(ramdisk_load)s - compression = "none"; + compression = "%(compression)s"; }; ramdisk@2 { description = "snow"; @@ -221,6 +221,10 @@ def test_fit(u_boot_console): print(data, file=fd) return fname
+ def make_compressed(filename): + util.run_and_log(cons, ['gzip', '-f', '-k', filename]) + return filename + '.gz' + def find_matching(text, match): """Find a match in a line of text, and return the unmatched line portion
@@ -312,6 +316,7 @@ def test_fit(u_boot_console): loadables1 = make_kernel('test-loadables1.bin', 'lenrek') loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar') kernel_out = make_fname('kernel-out.bin') + fdt = make_fname('u-boot.dtb') fdt_out = make_fname('fdt-out.dtb') ramdisk_out = make_fname('ramdisk-out.bin') loadables1_out = make_fname('loadables1-out.bin') @@ -326,6 +331,7 @@ def test_fit(u_boot_console): 'kernel_addr' : 0x40000, 'kernel_size' : filesize(kernel),
+ 'fdt' : fdt, 'fdt_out' : fdt_out, 'fdt_addr' : 0x80000, 'fdt_size' : filesize(control_dtb), @@ -351,6 +357,7 @@ def test_fit(u_boot_console): 'loadables2_load' : '',
'loadables_config' : '', + 'compression' : 'none', }
# Make a basic FIT and a script to load it @@ -417,6 +424,20 @@ def test_fit(u_boot_console): check_equal(loadables2, loadables2_out, 'Loadables2 (ramdisk) not loaded')
+ # Kernel, FDT and Ramdisk all compressed + with cons.log.section('(Kernel + FDT + Ramdisk) compressed'): + params['compression'] = 'gzip' + params['kernel'] = make_compressed(kernel) + params['fdt'] = make_compressed(fdt) + params['ramdisk'] = make_compressed(ramdisk) + fit = make_fit(mkimage, params) + cons.restart_uboot() + output = cons.run_command_list(cmd.splitlines()) + check_equal(kernel, kernel_out, 'Kernel not loaded') + check_equal(control_dtb, fdt_out, 'FDT not loaded') + check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded') + + cons = u_boot_console try: # We need to use our own device tree file. Remember to restore it

On Wed, 15 May 2019 at 16:30, Julius Werner jwerner@chromium.org wrote:
This patch adds support for compressing non-kernel image nodes in a FIT image (kernel nodes could already be compressed previously). This can reduce the size of FIT images and therefore improve boot times (especially when an image bundles many different kernel FDTs). The images will automatically be decompressed on load.
This patch does not support extracting compatible strings from compressed FDTs, so it's not very helpful in conjunction with CONFIG_FIT_BEST_MATCH yet, but it can already be used in environments that select the configuration to load explicitly.
Signed-off-by: Julius Werner jwerner@chromium.org
- Changes for v2:
- Changed from only supporting compressed FDTs to supporting all non-kernel image node types.
- Changes for v3:
- Fixed up some debug output that was still written for v1.
- Fixed a mistake with handling FIT_LOAD_OPTIONAL_NON_ZERO when 'load' was 0 (i.e. unset).
- Added compression test case to the test_fit pytest.
- No changes for v4
common/image-fit.c | 86 +++++++++++++++++++++++---------------- test/py/tests/test_fit.py | 29 +++++++++++-- 2 files changed, 77 insertions(+), 38 deletions(-)
Reviewed-by: Simon Glass sjg@chromium.org

Hi Simon,
I forgot about these for a while, but I believe I addressed all the feedback you still had in the latest version (v4). Looks like they didn't get picked up anywhere though. Can you help?
On Thu, May 16, 2019 at 7:34 PM Simon Glass sjg@chromium.org wrote:
On Wed, 15 May 2019 at 16:30, Julius Werner jwerner@chromium.org wrote:
This patch adds support for compressing non-kernel image nodes in a FIT image (kernel nodes could already be compressed previously). This can reduce the size of FIT images and therefore improve boot times (especially when an image bundles many different kernel FDTs). The images will automatically be decompressed on load.
This patch does not support extracting compatible strings from compressed FDTs, so it's not very helpful in conjunction with CONFIG_FIT_BEST_MATCH yet, but it can already be used in environments that select the configuration to load explicitly.
Signed-off-by: Julius Werner jwerner@chromium.org
- Changes for v2:
- Changed from only supporting compressed FDTs to supporting all non-kernel image node types.
- Changes for v3:
- Fixed up some debug output that was still written for v1.
- Fixed a mistake with handling FIT_LOAD_OPTIONAL_NON_ZERO when 'load' was 0 (i.e. unset).
- Added compression test case to the test_fit pytest.
- No changes for v4
common/image-fit.c | 86 +++++++++++++++++++++++---------------- test/py/tests/test_fit.py | 29 +++++++++++-- 2 files changed, 77 insertions(+), 38 deletions(-)
Reviewed-by: Simon Glass sjg@chromium.org

On Wed, May 15, 2019 at 03:29:57PM -0700, Julius Werner wrote:
This patch adds support for compressing non-kernel image nodes in a FIT image (kernel nodes could already be compressed previously). This can reduce the size of FIT images and therefore improve boot times (especially when an image bundles many different kernel FDTs). The images will automatically be decompressed on load.
This patch does not support extracting compatible strings from compressed FDTs, so it's not very helpful in conjunction with CONFIG_FIT_BEST_MATCH yet, but it can already be used in environments that select the configuration to load explicitly.
Signed-off-by: Julius Werner jwerner@chromium.org Reviewed-by: Simon Glass sjg@chromium.org
This breaks tools-only_defconfig in non-trivial ways due to calling bootm_decomp_image() which is not normally available to it. Can you please update things (it may just end up being common/Makefile that needs tweaking) so that tools-only builds? I'll then take a look and make sure we don't also see unexpected size growth elsewhere. Thanks!

Hi Tom,
This breaks tools-only_defconfig in non-trivial ways due to calling bootm_decomp_image() which is not normally available to it. Can you please update things (it may just end up being common/Makefile that needs tweaking) so that tools-only builds? I'll then take a look and make sure we don't also see unexpected size growth elsewhere. Thanks!
Thanks for pointing that out! I think the best solution here would be to move bootm_decomp_image() out of bootm.c, if we now want to have it available in configurations that don't compile that file. I think image.c would be a better home for it. Added another patch to move the function and sent out v5 of the series, let me know what you think!

This patch adds support for an optional optimization to compatible string matching where the compatible string property from the root node of the kernel FDT can be copied into the configuration node of the FIT image. This is most useful when using compressed FDTs or when using FDT overlays, where the traditional extraction of the compatible string from the kernel FDT itself is not easily possible.
Signed-off-by: Julius Werner jwerner@chromium.org --- - No changes for v2 - No changes for v3 - Changes for v4: - Added documentation for compatible string in config node. - Added example .its file for compressed FDT with compat string in config node.
common/image-fit.c | 67 ++++++++++++--------- doc/uImage.FIT/kernel_fdts_compressed.its | 73 +++++++++++++++++++++++ doc/uImage.FIT/source_file_format.txt | 7 +++ 3 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 doc/uImage.FIT/kernel_fdts_compressed.its
diff --git a/common/image-fit.c b/common/image-fit.c index 469c5c8f49..d32add6419 100644 --- a/common/image-fit.c +++ b/common/image-fit.c @@ -1522,6 +1522,10 @@ int fit_check_format(const void *fit) * compatible list, "foo,bar", matches a compatible string in the root of fdt1. * "bim,bam" in fdt2 matches the second string which isn't as good as fdt1. * + * As an optimization, the compatible property from the FDT's root node can be + * copied into the configuration node in the FIT image. This is required to + * match configurations with compressed FDTs. + * * returns: * offset to the configuration to use if one was found * -1 otherwise @@ -1554,55 +1558,62 @@ int fit_conf_find_compat(const void *fit, const void *fdt) for (noffset = fdt_next_node(fit, confs_noffset, &ndepth); (noffset >= 0) && (ndepth > 0); noffset = fdt_next_node(fit, noffset, &ndepth)) { - const void *kfdt; + const void *fdt; const char *kfdt_name; - int kfdt_noffset; + int kfdt_noffset, compat_noffset; const char *cur_fdt_compat; int len; - size_t size; + size_t sz; int i;
if (ndepth > 1) continue;
- kfdt_name = fdt_getprop(fit, noffset, "fdt", &len); - if (!kfdt_name) { - debug("No fdt property found.\n"); - continue; - } - kfdt_noffset = fdt_subnode_offset(fit, images_noffset, - kfdt_name); - if (kfdt_noffset < 0) { - debug("No image node named "%s" found.\n", - kfdt_name); - continue; - } + /* If there's a compat property in the config node, use that. */ + if (fdt_getprop(fit, noffset, "compatible", NULL)) { + fdt = fit; /* search in FIT image */ + compat_noffset = noffset; /* search under config node */ + } else { /* Otherwise extract it from the kernel FDT. */ + kfdt_name = fdt_getprop(fit, noffset, "fdt", &len); + if (!kfdt_name) { + debug("No fdt property found.\n"); + continue; + } + kfdt_noffset = fdt_subnode_offset(fit, images_noffset, + kfdt_name); + if (kfdt_noffset < 0) { + debug("No image node named "%s" found.\n", + kfdt_name); + continue; + }
- if (!fit_image_check_comp(fit, kfdt_noffset, IH_COMP_NONE)) { - debug("Can't extract compat from "%s" (compressed)\n", - kfdt_name); - continue; - } + if (!fit_image_check_comp(fit, kfdt_noffset, + IH_COMP_NONE)) { + debug("Can't extract compat from "%s" " + "(compressed)\n", kfdt_name); + continue; + }
- /* - * Get a pointer to this configuration's fdt. - */ - if (fit_image_get_data(fit, kfdt_noffset, &kfdt, &size)) { - debug("Failed to get fdt "%s".\n", kfdt_name); - continue; + /* search in this config's kernel FDT */ + if (fit_image_get_data(fit, kfdt_noffset, &fdt, &sz)) { + debug("Failed to get fdt "%s".\n", kfdt_name); + continue; + } + + compat_noffset = 0; /* search kFDT under root node */ }
len = fdt_compat_len; cur_fdt_compat = fdt_compat; /* * Look for a match for each U-Boot compatibility string in - * turn in this configuration's fdt. + * turn in the compat string property. */ for (i = 0; len > 0 && (!best_match_offset || best_match_pos > i); i++) { int cur_len = strlen(cur_fdt_compat) + 1;
- if (!fdt_node_check_compatible(kfdt, 0, + if (!fdt_node_check_compatible(fdt, compat_noffset, cur_fdt_compat)) { best_match_offset = noffset; best_match_pos = i; diff --git a/doc/uImage.FIT/kernel_fdts_compressed.its b/doc/uImage.FIT/kernel_fdts_compressed.its new file mode 100644 index 0000000000..8f81106efc --- /dev/null +++ b/doc/uImage.FIT/kernel_fdts_compressed.its @@ -0,0 +1,73 @@ +/* + * U-Boot uImage source file with a kernel and multiple compressed FDT blobs. + * Since the FDTs are compressed, configurations must provide a compatible + * string to match directly. + */ + +/dts-v1/; + +/ { + description = "Image with single Linux kernel and compressed FDT blobs"; + #address-cells = <1>; + + images { + kernel { + description = "Vanilla Linux kernel"; + data = /incbin/("./vmlinux.bin.gz"); + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + }; + fdt@1 { + description = "Flattened Device Tree blob 1"; + data = /incbin/("./myboard-var1.dtb"); + type = "flat_dt"; + arch = "ppc"; + compression = "gzip"; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + }; + fdt@2 { + description = "Flattened Device Tree blob 2"; + data = /incbin/("./myboard-var2.dtb"); + type = "flat_dt"; + arch = "ppc"; + compression = "lzma"; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + }; + }; + + configurations { + default = "conf@1"; + conf@1 { + description = "Boot Linux kernel with FDT blob 1"; + kernel = "kernel"; + fdt = "fdt@1"; + compatible = "myvendor,myboard-variant1"; + }; + conf@2 { + description = "Boot Linux kernel with FDT blob 2"; + kernel = "kernel"; + fdt = "fdt@2"; + compatible = "myvendor,myboard-variant2"; + }; + }; +}; diff --git a/doc/uImage.FIT/source_file_format.txt b/doc/uImage.FIT/source_file_format.txt index d701b9bb76..f8e27ed34e 100644 --- a/doc/uImage.FIT/source_file_format.txt +++ b/doc/uImage.FIT/source_file_format.txt @@ -240,6 +240,7 @@ o config-1 |- fdt = "fdt sub-node unit-name" [, "fdt overlay sub-node unit-name", ...] |- fpga = "fpga sub-node unit-name" |- loadables = "loadables sub-node unit-name" + |- compatible = "vendor,board-style device tree compatible string"
Mandatory properties: @@ -263,6 +264,12 @@ o config-1 of strings. U-Boot will load each binary at its given start-address and may optionaly invoke additional post-processing steps on this binary based on its component image node type. + - compatible : The root compatible string of the U-Boot device tree that + this configuration shall automatically match when CONFIG_FIT_BEST_MATCH is + enabled. If this property is not provided, the compatible string will be + extracted from the fdt blob instead. This is only possible if the fdt is + not compressed, so images with compressed fdts that want to use compatible + string matching must always provide this property.
The FDT blob is required to properly boot FDT based kernel, so the minimal configuration for 2.6 FDT kernel is (kernel, fdt) pair.

On Wed, 15 May 2019 at 16:30, Julius Werner jwerner@chromium.org wrote:
This patch adds support for an optional optimization to compatible string matching where the compatible string property from the root node of the kernel FDT can be copied into the configuration node of the FIT image. This is most useful when using compressed FDTs or when using FDT overlays, where the traditional extraction of the compatible string from the kernel FDT itself is not easily possible.
Signed-off-by: Julius Werner jwerner@chromium.org
- No changes for v2
- No changes for v3
- Changes for v4:
- Added documentation for compatible string in config node.
- Added example .its file for compressed FDT with compat string in config node.
common/image-fit.c | 67 ++++++++++++--------- doc/uImage.FIT/kernel_fdts_compressed.its | 73 +++++++++++++++++++++++ doc/uImage.FIT/source_file_format.txt | 7 +++ 3 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 doc/uImage.FIT/kernel_fdts_compressed.its
Reviewed-by: Simon Glass sjg@chromium.org
nit: We don't normally use a period in a single-line comment.
participants (3)
-
Julius Werner
-
Simon Glass
-
Tom Rini