[PATCH 00/24] boot: Enhance VBE to support a separate devicetree FIT

From: Simon Glass <sjg@chromium.org> It is sometimes desirable to have the devicetrees packaged with firware, or in a different FIT from the OS. This series adds support for this, including a test. The new bootmeth can automatically locate a state file and use that to decide what images to use when booting. The OS must update the file before rebooting, if a different selection is required. For now there is no logic to deal with boot failures. Simon Glass (24): Correct implementation of Spawn() in ConsoleBase test: Rename test_vboot() to test_vboot_base() sandbox: Increase the number of bootstage records boot: Tidy up fit_get_desc() boot: Use a common index in fit_conf_print boot: Show messages when using FIT best-match boot: Add a constant for the FIT-configuration compatible boot: Show compatible strings with FIT information sandbox: Enable FIT best-match mkimage: Remove an unused argument from fdt_property_file() mkimage: Add compatible strings to configuration node mkimage: Support a load-only FIT mkimage: Set the timestamp with -f auto test: Allow inspection of input filesystems test: Drop double removal of the FS image on failure boot: Move PXE-parsing logic out to a separate file boot: Remove blank lines in pxe_parse boot: pxe: Add a token for FIT boot: Use provided filename with bootmeth_alloc_other() boot: Support restarting a bootm sequence from PXE boot: Add comments for struct pxe_label boot: Add support for VBE for the OS boot: test: Add a test for the VBE OS flow boot: Add documentation for the VBE-OS bootmeth arch/sandbox/dts/test.dts | 11 + boot/Kconfig | 14 + boot/Makefile | 2 +- boot/bootflow.c | 2 + boot/bootmeth-uclass.c | 3 +- boot/bootmeth_extlinux.c | 3 +- boot/bootmeth_pxe.c | 2 +- boot/ext_pxe_common.c | 3 +- boot/image-board.c | 3 +- boot/image-fit.c | 75 ++- boot/pxe_parse.c | 659 +++++++++++++++++++++++ boot/pxe_utils.c | 711 +------------------------ boot/vbe_abrec.c | 2 +- boot/vbe_abrec.h | 5 + boot/vbe_abrec_fw.c | 1 + boot/vbe_abrec_os.c | 293 ++++++++++ configs/sandbox_defconfig | 1 + doc/develop/bootstd/index.rst | 1 + doc/develop/bootstd/vbe_os.rst | 61 +++ doc/develop/vbe.rst | 2 + doc/mkimage.1 | 13 + include/bootflow.h | 8 +- include/extlinux.h | 4 +- include/image.h | 15 +- include/pxe_utils.h | 63 ++- include/vbe.h | 13 + test/boot/Makefile | 1 + test/boot/bootflow.c | 75 ++- test/boot/bootmeth.c | 19 +- test/boot/vbe_abrec_os.c | 138 +++++ test/py/console_base.py | 15 +- test/py/console_board.py | 2 +- test/py/test.py | 4 + test/py/tests/fs_helper.py | 74 ++- test/py/tests/img/vbe.py | 167 ++++++ test/py/tests/test_ut.py | 3 +- test/py/tests/test_vbe.py | 22 + test/py/tests/test_vboot.py | 4 +- test/py/tests/vboot/sandbox-kernel.dts | 3 +- tools/fit_image.c | 114 ++-- tools/imagetool.h | 15 +- tools/mkimage.c | 12 +- 42 files changed, 1757 insertions(+), 881 deletions(-) create mode 100644 boot/pxe_parse.c create mode 100644 boot/vbe_abrec_os.c create mode 100644 doc/develop/bootstd/vbe_os.rst create mode 100644 test/boot/vbe_abrec_os.c create mode 100644 test/py/tests/img/vbe.py -- 2.43.0 base-commit: 05840f9593b476d7500e1641723a899c57b26233 branch: loadg

From: Simon Glass <sjg@chromium.org> Now that this base-class function is called, it can produce an error on test failure, since it passes an empty list for the arguments. Rename the reset() function to prepare_for_spawn() and use that instead. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/console_base.py | 15 ++++++--------- test/py/console_board.py | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/py/console_base.py b/test/py/console_base.py index 3fbde86996c..7443c567937 100644 --- a/test/py/console_base.py +++ b/test/py/console_base.py @@ -245,13 +245,13 @@ class ConsoleBase(): self.at_prompt_logevt = None self.lab_mode = False self.u_boot_version_string = None - self.reset() + self.prepare_for_spawn() # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escap... self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I) self.eval_patterns() - def reset(self): + def prepare_for_spawn(self): """Reset all settings as we are about to spawn a new connection""" self.buf = '' self.output = '' @@ -261,13 +261,10 @@ class ConsoleBase(): self.logfile_read = None def get_spawn(self): - """This must be called by subclasses, to reset the system - - Return a value to avoid: - console_base.py:348:12: E1128: Assigning result of a function - call, where the function returns None (assignment-from-none) - """ - self.reset() + # This is not called, ssubclass must define this. + # Return a value to avoid: + # console_base.py:348:12: E1128: Assigning result of a function + # call, where the function returns None (assignment-from-none) return spawn.Spawn([]) def eval_patterns(self): diff --git a/test/py/console_board.py b/test/py/console_board.py index 8223668a84c..4313415a332 100644 --- a/test/py/console_board.py +++ b/test/py/console_board.py @@ -55,7 +55,7 @@ class ConsoleExecAttach(ConsoleBase): Returns: A spawn.Spawn object that is attached to U-Boot. """ - super().get_spawn() + self.prepare_for_spawn() args = [self.config.board_type, self.config.board_identity] s = Spawn(['u-boot-test-console'] + args) -- 2.43.0

From: Simon Glass <sjg@chromium.org> At present it isn't easy to run just the base vboot tests. Rename it to make this easier. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/tests/test_vboot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py index 7a7f9c379de..493c90564ef 100644 --- a/test/py/tests/test_vboot.py +++ b/test/py/tests/test_vboot.py @@ -113,8 +113,8 @@ TESTDATA += [pytest.param(*v, marks=pytest.mark.slow) for v in TESTDATA_IN[1:]] @pytest.mark.requiredtool('openssl') @pytest.mark.parametrize("name,sha_algo,padding,sign_options,required,full_test,algo_arg,global_sign", TESTDATA) -def test_vboot(ubman, name, sha_algo, padding, sign_options, required, - full_test, algo_arg, global_sign): +def test_vboot_base(ubman, name, sha_algo, padding, sign_options, required, + full_test, algo_arg, global_sign): """Test verified boot signing with mkimage and verification with 'bootm'. This works using sandbox only as it needs to update the device tree used -- 2.43.0

From: Simon Glass <sjg@chromium.org> When running boot tests involving FIT, 30 is not enough. Increase the limit to 50. Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/sandbox_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 0f364c3f298..673afdf93ba 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -30,6 +30,7 @@ CONFIG_LEGACY_IMAGE_FORMAT=y CONFIG_MEASURED_BOOT=y CONFIG_BOOTSTAGE=y CONFIG_BOOTSTAGE_REPORT=y +CONFIG_BOOTSTAGE_RECORD_COUNT=50 CONFIG_BOOTSTAGE_FDT=y CONFIG_BOOTSTAGE_STASH=y CONFIG_BOOTSTAGE_STASH_SIZE=0x4096 -- 2.43.0

From: Simon Glass <sjg@chromium.org> This should return a constant pointer, since modifying the description within a devicetree node is not allowed. Also there is no need to set the return value unless there is actually a description. Update the function and move the docs to the header file while we are here. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/image-fit.c | 32 ++++++++++---------------------- include/image.h | 14 +++++++++++++- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/boot/image-fit.c b/boot/image-fit.c index 5ac6a0cf532..5b0700c4f51 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -294,8 +294,7 @@ static void fit_image_print_verification_data(const void *fit, int noffset, */ static void fit_conf_print(const void *fit, int noffset, const char *p) { - char *desc; - const char *uname; + const char *uname, *desc; int ret; int fdt_index, loadables_index; int ndepth; @@ -377,7 +376,7 @@ static void fit_conf_print(const void *fit, int noffset, const char *p) */ void fit_print_contents(const void *fit) { - char *desc; + const char *desc; char *uname; int images_noffset; int confs_noffset; @@ -484,7 +483,7 @@ void fit_print_contents(const void *fit) */ void fit_image_print(const void *fit, int image_noffset, const char *p) { - char *desc; + const char *desc; uint8_t type, arch, os, comp = IH_COMP_NONE; size_t size; ulong load, entry; @@ -590,28 +589,17 @@ void fit_image_print(const void *fit, int image_noffset, const char *p) } } -/** - * fit_get_desc - get node description property - * @fit: pointer to the FIT format image header - * @noffset: node offset - * @desc: double pointer to the char, will hold pointer to the description - * - * fit_get_desc() reads description property from a given node, if - * description is found pointer to it is returned in third call argument. - * - * returns: - * 0, on success - * -1, on failure - */ -int fit_get_desc(const void *fit, int noffset, char **desc) +int fit_get_desc(const void *fit, int noffset, const char **descp) { + const char *desc; int len; - *desc = (char *)fdt_getprop(fit, noffset, FIT_DESC_PROP, &len); - if (*desc == NULL) { + desc = (char *)fdt_getprop(fit, noffset, FIT_DESC_PROP, &len); + if (!desc) { fit_get_debug(fit, noffset, FIT_DESC_PROP, len); - return -1; + return -ENOENT; } + *descp = desc; return 0; } @@ -1938,7 +1926,7 @@ int fit_conf_get_prop_node(const void *fit, int noffset, const char *prop_name, static int fit_get_data_tail(const void *fit, int noffset, const void **data, size_t *size) { - char *desc; + const char *desc; if (noffset < 0) return noffset; diff --git a/include/image.h b/include/image.h index 7576ffa1048..cff1b8bf4cb 100644 --- a/include/image.h +++ b/include/image.h @@ -1234,7 +1234,19 @@ static inline const char *fit_get_name(const void *fit_hdr, return fdt_get_name(fit_hdr, noffset, len); } -int fit_get_desc(const void *fit, int noffset, char **desc); +/** + * fit_get_desc() - get a node's 'description' property + * + * @fit: pointer to the FIT format image header + * @noffset: node offset + * @desc: Returns pointer to description, on success + * Return: 0 on success, -1 on failure + * + * fit_get_desc() reads description property from a given node, if + * description is found pointer to it is returned in third call argument. + */ +int fit_get_desc(const void *fit, int noffset, const char **descp); + int fit_get_timestamp(const void *fit, int noffset, time_t *timestamp); int fit_image_get_node(const void *fit, const char *image_uname); -- 2.43.0

From: Simon Glass <sjg@chromium.org> Rather than using two variables for the stringlist index, use one. Simplify the i == 0 expression. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/image-fit.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/boot/image-fit.c b/boot/image-fit.c index 5b0700c4f51..e3e935850a0 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -295,9 +295,7 @@ static void fit_image_print_verification_data(const void *fit, int noffset, static void fit_conf_print(const void *fit, int noffset, const char *p) { const char *uname, *desc; - int ret; - int fdt_index, loadables_index; - int ndepth; + int ret, ndepth, i; /* Mandatory properties */ ret = fit_get_desc(fit, noffset, &desc); @@ -323,11 +321,11 @@ static void fit_conf_print(const void *fit, int noffset, const char *p) if (uname) printf("%s Firmware: %s\n", p, uname); - for (fdt_index = 0; + for (i = 0; uname = fdt_stringlist_get(fit, noffset, FIT_FDT_PROP, - fdt_index, NULL), uname; - fdt_index++) { - if (fdt_index == 0) + i, NULL), uname; + i++) { + if (!i) printf("%s FDT: ", p); else printf("%s ", p); @@ -339,15 +337,14 @@ static void fit_conf_print(const void *fit, int noffset, const char *p) printf("%s FPGA: %s\n", p, uname); /* Print out all of the specified loadables */ - for (loadables_index = 0; + for (i = 0; uname = fdt_stringlist_get(fit, noffset, FIT_LOADABLE_PROP, - loadables_index, NULL), uname; - loadables_index++) { - if (loadables_index == 0) { + i, NULL), uname; + i++) { + if (!i) printf("%s Loadables: ", p); - } else { + else printf("%s ", p); - } printf("%s\n", uname); } -- 2.43.0

From: Simon Glass <sjg@chromium.org> This matching happens silently at present, which can lead one to wonder if U-Boot has been built without the feature. Add a few basic messages. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/image-fit.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/boot/image-fit.c b/boot/image-fit.c index e3e935850a0..d0221c3c64b 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -1726,6 +1726,7 @@ int fit_conf_find_compat(const void *fit, const void *fdt) /* * Loop over the configurations in the FIT image. */ + printf("Looking for best match..."); for (noffset = fdt_next_node(fit, confs_noffset, &ndepth); (noffset >= 0) && (ndepth > 0); noffset = fdt_next_node(fit, noffset, &ndepth)) { @@ -1795,9 +1796,10 @@ int fit_conf_find_compat(const void *fit, const void *fdt) } } if (!best_match_offset) { - debug("No match found.\n"); + printf("no match found\n"); return -ENOENT; } + printf("found\n"); return best_match_offset; } -- 2.43.0

From: Simon Glass <sjg@chromium.org> This property is part of the configuration node, so add a constant for it, instead of open-coding the value. This allows easy searching for places in U-Boot where the configuration node's compatible string is used. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/image-board.c | 3 ++- boot/image-fit.c | 6 +++--- include/image.h | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/boot/image-board.c b/boot/image-board.c index a0d2a7405e1..4b285587cd9 100644 --- a/boot/image-board.c +++ b/boot/image-board.c @@ -676,7 +676,8 @@ int boot_get_fpga(struct bootm_headers *images) } conf_noffset = fit_image_get_node(buf, uname); - compatible = fdt_getprop(buf, conf_noffset, "compatible", NULL); + compatible = fdt_getprop(buf, conf_noffset, FIT_COMPATIBLE_PROP, + NULL); if (!compatible) { printf("'fpga' image without 'compatible' property\n"); } else { diff --git a/boot/image-fit.c b/boot/image-fit.c index d0221c3c64b..dfb26ba716b 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -1717,9 +1717,9 @@ int fit_conf_find_compat(const void *fit, const void *fdt) return -EINVAL; } - fdt_compat = fdt_getprop(fdt, 0, "compatible", &fdt_compat_len); + fdt_compat = fdt_getprop(fdt, 0, FIT_COMPATIBLE_PROP, &fdt_compat_len); if (!fdt_compat) { - debug("Fdt for comparison has no \"compatible\" property.\n"); + debug("Fdt for comparison has no 'compatible' property.\n"); return -ENXIO; } @@ -1742,7 +1742,7 @@ int fit_conf_find_compat(const void *fit, const void *fdt) continue; /* If there's a compat property in the config node, use that. */ - if (fdt_getprop(fit, noffset, "compatible", NULL)) { + if (fdt_getprop(fit, noffset, FIT_COMPATIBLE_PROP, NULL)) { fdt = fit; /* search in FIT image */ compat_noffset = noffset; /* search under config node */ } else { /* Otherwise extract it from the kernel FDT. */ diff --git a/include/image.h b/include/image.h index cff1b8bf4cb..6a4cb912bc4 100644 --- a/include/image.h +++ b/include/image.h @@ -1186,6 +1186,7 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size, #define FIT_STANDALONE_PROP "standalone" #define FIT_SCRIPT_PROP "script" #define FIT_LOAD_ONLY_PROP "load-only" +#define FIT_COMPATIBLE_PROP "compatible" #define FIT_MAX_HASH_LEN HASH_MAX_DIGEST_SIZE -- 2.43.0

From: Simon Glass <sjg@chromium.org> The compatible strings used by each configuration comprise useful information about how the system will boot. Show these after the current configuration-node information. Swap the order of the desc argument in its caller, fit_image_print() since it is easier to read. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/image-fit.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/boot/image-fit.c b/boot/image-fit.c index dfb26ba716b..0c1c07c8498 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -348,6 +348,16 @@ static void fit_conf_print(const void *fit, int noffset, const char *p) printf("%s\n", uname); } + /* Show the list of compatible strings */ + for (i = 0; uname = fdt_stringlist_get(fit, noffset, + FIT_COMPATIBLE_PROP, i, NULL), uname; i++) { + if (!i) + printf("%s Compatible: ", p); + else + printf("%s ", p); + printf("%s\n", uname); + } + /* Process all hash subnodes of the component configuration node */ for (ndepth = 0, noffset = fdt_next_node(fit, noffset, &ndepth); (noffset >= 0) && (ndepth > 0); @@ -480,8 +490,8 @@ void fit_print_contents(const void *fit) */ void fit_image_print(const void *fit, int image_noffset, const char *p) { - const char *desc; uint8_t type, arch, os, comp = IH_COMP_NONE; + const char *desc; size_t size; ulong load, entry; const void *data; -- 2.43.0

From: Simon Glass <sjg@chromium.org> Enable this feature so that it can be tested on sandbox. Update the vboot FDT to avoid having it match on conf-1 instead of conf-1 Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/Kconfig | 1 + test/py/tests/vboot/sandbox-kernel.dts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/boot/Kconfig b/boot/Kconfig index b99d880f303..45fe5a07696 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -130,6 +130,7 @@ config FIT_VERBOSE config FIT_BEST_MATCH bool "Select the best match for the kernel device tree" + default y if SANDBOX help When no configuration is explicitly selected, default to the one whose fdt's compatibility field best matches that of diff --git a/test/py/tests/vboot/sandbox-kernel.dts b/test/py/tests/vboot/sandbox-kernel.dts index a1e853c9caa..4012e27726d 100644 --- a/test/py/tests/vboot/sandbox-kernel.dts +++ b/test/py/tests/vboot/sandbox-kernel.dts @@ -2,6 +2,5 @@ / { model = "Sandbox Verified Boot Test"; - compatible = "sandbox"; - + compatible = "none"; }; -- 2.43.0

From: Simon Glass <sjg@chromium.org> This function does not actually use the 'name' argument. Drop it and use FIT_DATA_PROP instead, to avoid confusion. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/fit_image.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tools/fit_image.c b/tools/fit_image.c index e2840cf48b4..57962d50e04 100644 --- a/tools/fit_image.c +++ b/tools/fit_image.c @@ -129,8 +129,15 @@ static int fit_calc_size(struct imgtool *itl) return total_size; } -static int fdt_property_file(struct imgtool *itl, void *fdt, const char *name, - const char *fname) +/** + * fdt_property_file() - Create a property using the contents of a file + * + * @itl: Image-tool info + * @fdt: Devicetree to write to + * @fname: Filename of file whose contents are to be written into the'data' + * property + */ +static int fdt_property_file(struct imgtool *itl, void *fdt, const char *fname) { struct stat sbuf; void *ptr; @@ -150,7 +157,7 @@ static int fdt_property_file(struct imgtool *itl, void *fdt, const char *name, goto err; } - ret = fdt_property_placeholder(fdt, "data", sbuf.st_size, &ptr); + ret = fdt_property_placeholder(fdt, FIT_DATA_PROP, sbuf.st_size, &ptr); if (ret) goto err; ret = read(fd, ptr, sbuf.st_size); @@ -291,7 +298,7 @@ static int fit_write_images(struct imgtool *itl, char *fdt) * Put data last since it is large. SPL may only load the first part * of the DT, so this way it can access all the above fields. */ - ret = fdt_property_file(itl, fdt, FIT_DATA_PROP, itl->datafile); + ret = fdt_property_file(itl, fdt, itl->datafile); if (ret) return ret; fit_add_hash_or_sign(itl, fdt, true); @@ -308,8 +315,7 @@ static int fit_write_images(struct imgtool *itl, char *fdt) get_basename(str, sizeof(str), cont->fname); fdt_property_string(fdt, FIT_DESC_PROP, str); - ret = fdt_property_file(itl, fdt, FIT_DATA_PROP, - cont->fname); + ret = fdt_property_file(itl, fdt, cont->fname); if (ret) return ret; fdt_property_string(fdt, FIT_TYPE_PROP, typename); @@ -333,8 +339,7 @@ static int fit_write_images(struct imgtool *itl, char *fdt) fdt_property_string(fdt, FIT_ARCH_PROP, genimg_get_arch_short_name(itl->arch)); - ret = fdt_property_file(itl, fdt, FIT_DATA_PROP, - itl->fit_ramdisk); + ret = fdt_property_file(itl, fdt, itl->fit_ramdisk); if (ret) return ret; fit_add_hash_or_sign(itl, fdt, true); -- 2.43.0

From: Simon Glass <sjg@chromium.org> When using '-f auto', mkimage automatically creates a FIT given the images. For devicetree files, FIT expects that the compatible string from each is copied to its corresponding configuration node. Implement this in mkimage, so far only for uncompressed devicetrees. This requires a few more fields in struct content_info, so take this opportunity to comment it properly. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/fit_image.c | 25 +++++++++++++++++++++---- tools/imagetool.h | 14 ++++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/tools/fit_image.c b/tools/fit_image.c index 57962d50e04..c8a8f7e1437 100644 --- a/tools/fit_image.c +++ b/tools/fit_image.c @@ -136,8 +136,11 @@ static int fit_calc_size(struct imgtool *itl) * @fdt: Devicetree to write to * @fname: Filename of file whose contents are to be written into the'data' * property + * @bufp: If non-null, returns a pointer to the data on success + * Return: 0 if OK, -1 on error */ -static int fdt_property_file(struct imgtool *itl, void *fdt, const char *fname) +static int fdt_property_file(struct imgtool *itl, void *fdt, const char *fname, + const char **bufp) { struct stat sbuf; void *ptr; @@ -166,6 +169,8 @@ static int fdt_property_file(struct imgtool *itl, void *fdt, const char *fname) itl->cmdname, fname, strerror(errno)); goto err; } + if (bufp) + *bufp = ptr; close(fd); return 0; @@ -298,7 +303,7 @@ static int fit_write_images(struct imgtool *itl, char *fdt) * Put data last since it is large. SPL may only load the first part * of the DT, so this way it can access all the above fields. */ - ret = fdt_property_file(itl, fdt, itl->datafile); + ret = fdt_property_file(itl, fdt, itl->datafile, NULL); if (ret) return ret; fit_add_hash_or_sign(itl, fdt, true); @@ -307,6 +312,9 @@ static int fit_write_images(struct imgtool *itl, char *fdt) /* Now the device tree files if available */ upto = 0; for (cont = itl->content_head; cont; cont = cont->next) { + const char *buf, *compat; + int len; + if (cont->type != IH_TYPE_FLATDT) continue; typename = genimg_get_type_short_name(cont->type); @@ -315,7 +323,7 @@ static int fit_write_images(struct imgtool *itl, char *fdt) get_basename(str, sizeof(str), cont->fname); fdt_property_string(fdt, FIT_DESC_PROP, str); - ret = fdt_property_file(itl, fdt, cont->fname); + ret = fdt_property_file(itl, fdt, cont->fname, &buf); if (ret) return ret; fdt_property_string(fdt, FIT_TYPE_PROP, typename); @@ -323,6 +331,11 @@ static int fit_write_images(struct imgtool *itl, char *fdt) genimg_get_arch_short_name(itl->arch)); fdt_property_string(fdt, FIT_COMP_PROP, genimg_get_comp_short_name(IH_COMP_NONE)); + if (!fdt_check_header(buf)) { + compat = fdt_getprop(buf, 0, FIT_COMPATIBLE_PROP, &len); + cont->compat = compat; + cont->compat_len = len; + } fit_add_hash_or_sign(itl, fdt, true); if (ret) return ret; @@ -339,7 +352,7 @@ static int fit_write_images(struct imgtool *itl, char *fdt) fdt_property_string(fdt, FIT_ARCH_PROP, genimg_get_arch_short_name(itl->arch)); - ret = fdt_property_file(itl, fdt, itl->fit_ramdisk); + ret = fdt_property_file(itl, fdt, itl->fit_ramdisk, NULL); if (ret) return ret; fit_add_hash_or_sign(itl, fdt, true); @@ -393,6 +406,10 @@ static void fit_write_configs(struct imgtool *itl, char *fdt) snprintf(str, sizeof(str), FIT_FDT_PROP "-%d", upto); fdt_property_string(fdt, FIT_FDT_PROP, str); + if (cont->compat) + fdt_property(fdt, FIT_COMPATIBLE_PROP, cont->compat, + cont->compat_len); + fit_add_hash_or_sign(itl, fdt, false); fdt_end_node(fdt); } diff --git a/tools/imagetool.h b/tools/imagetool.h index c877d52c19a..32694e32963 100644 --- a/tools/imagetool.h +++ b/tools/imagetool.h @@ -32,11 +32,21 @@ #define IH_ARCH_DEFAULT IH_ARCH_INVALID -/* Information about a file that needs to be placed into the FIT */ +/* + * struct content_info - info about a file to be placed into the FIT + * + * @next: Pointer to next record, or NULL if this is the last + * @type: File type (IH_TYPE_...) + * @fname: Filename containing the data to add + * @compat: Pointer to compatible property, if an FDT + * @compat_len: Length of the compatible property + */ struct content_info { struct content_info *next; - int type; /* File type (IH_TYPE_...) */ + int type; const char *fname; + const char *compat; + int compat_len; }; /* FIT auto generation modes */ -- 2.43.0

From: Simon Glass <sjg@chromium.org> Support creation of a load-only FIT (where there is no OS), with a new --load-only option. Allow FITs to be created without an OS image. Update the auto-generated FIT description to make this clear. Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/mkimage.1 | 13 ++++++++ tools/fit_image.c | 78 +++++++++++++++++++++++++++++------------------ tools/imagetool.h | 1 + tools/mkimage.c | 10 +++++- 4 files changed, 71 insertions(+), 31 deletions(-) diff --git a/doc/mkimage.1 b/doc/mkimage.1 index d0a038a880a..b98d405f424 100644 --- a/doc/mkimage.1 +++ b/doc/mkimage.1 @@ -476,6 +476,19 @@ the timestamp is assumed to have been set previously. This section documents the formats of the primary and secondary configuration options for each image type which supports them. . +.TQ +.B \-\-load\-only +Permit creation of a load-only FIT. +.IP +Normally a FIT has an OS image included and mkimage expects this. With this +option, a special FIT can be created which only has devicetrees. This can be +loaded before the normal 'OS' devicetree. This option sets the load-only +property in each generated configuration and allows the OS image to be missing. +. +.SH CONFIGURATION +This section documents the formats of the primary and secondary configuration +options for each image type which supports them. +. .SS aisimage The primary configuration is a file containing a series of .I AIS diff --git a/tools/fit_image.c b/tools/fit_image.c index c8a8f7e1437..013242cd204 100644 --- a/tools/fit_image.c +++ b/tools/fit_image.c @@ -100,12 +100,14 @@ err_keydest: static int fit_calc_size(struct imgtool *itl) { struct content_info *cont; - int size, total_size; + int size, total_size = 0; - size = imagetool_get_filesize(itl, itl->datafile); - if (size < 0) - return -1; - total_size = size; + if (itl->datafile) { + size = imagetool_get_filesize(itl, itl->datafile); + if (size < 0) + return -1; + total_size += size; + } if (itl->fit_ramdisk) { size = imagetool_get_filesize(itl, itl->fit_ramdisk); @@ -285,29 +287,33 @@ static int fit_write_images(struct imgtool *itl, char *fdt) fdt_begin_node(fdt, "images"); /* First the main image */ - typename = genimg_get_type_short_name(itl->fit_image_type); - snprintf(str, sizeof(str), "%s-1", typename); - fdt_begin_node(fdt, str); - fdt_property_string(fdt, FIT_DESC_PROP, itl->imagename); - fdt_property_string(fdt, FIT_TYPE_PROP, typename); - fdt_property_string(fdt, FIT_ARCH_PROP, - genimg_get_arch_short_name(itl->arch)); - fdt_property_string(fdt, FIT_OS_PROP, - genimg_get_os_short_name(itl->os)); - fdt_property_string(fdt, FIT_COMP_PROP, - genimg_get_comp_short_name(itl->comp)); - fdt_property_u32(fdt, FIT_LOAD_PROP, itl->addr); - fdt_property_u32(fdt, FIT_ENTRY_PROP, itl->ep); + if (itl->datafile) { + typename = genimg_get_type_short_name(itl->fit_image_type); + snprintf(str, sizeof(str), "%s-1", typename); + fdt_begin_node(fdt, str); + fdt_property_string(fdt, FIT_DESC_PROP, itl->imagename); + fdt_property_string(fdt, FIT_TYPE_PROP, typename); + fdt_property_string(fdt, FIT_ARCH_PROP, + genimg_get_arch_short_name(itl->arch)); + fdt_property_string(fdt, FIT_OS_PROP, + genimg_get_os_short_name(itl->os)); + fdt_property_string(fdt, FIT_COMP_PROP, + genimg_get_comp_short_name(itl->comp)); + fdt_property_u32(fdt, FIT_LOAD_PROP, itl->addr); + fdt_property_u32(fdt, FIT_ENTRY_PROP, itl->ep); - /* - * Put data last since it is large. SPL may only load the first part - * of the DT, so this way it can access all the above fields. - */ - ret = fdt_property_file(itl, fdt, itl->datafile, NULL); - if (ret) - return ret; - fit_add_hash_or_sign(itl, fdt, true); - fdt_end_node(fdt); + /* + * Put data last since it is large. SPL may only load the first + * part of the DT, so this way it can access all the above + * fields. If the FIT is converted to use external data, then + * there is no benefit here, but also no loss. + */ + ret = fdt_property_file(itl, fdt, itl->datafile, NULL); + if (ret) + return ret; + fit_add_hash_or_sign(itl, fdt, true); + fdt_end_node(fdt); + } /* Now the device tree files if available */ upto = 0; @@ -406,6 +412,8 @@ static void fit_write_configs(struct imgtool *itl, char *fdt) snprintf(str, sizeof(str), FIT_FDT_PROP "-%d", upto); fdt_property_string(fdt, FIT_FDT_PROP, str); + if (itl->load_only) + fdt_property(fdt, FIT_LOAD_ONLY_PROP, NULL, 0); if (cont->compat) fdt_property(fdt, FIT_COMPATIBLE_PROP, cont->compat, cont->compat_len); @@ -433,16 +441,26 @@ static void fit_write_configs(struct imgtool *itl, char *fdt) static int fit_build_fdt(struct imgtool *itl, char *fdt, int size) { + const struct content_info *cont; + bool has_fdt; int ret; + has_fdt = false; + for (cont = itl->content_head; cont; cont = cont->next) { + if (cont->type == IH_TYPE_FLATDT) { + has_fdt = true; + break; + } + } + ret = fdt_create(fdt, size); if (ret) return ret; fdt_finish_reservemap(fdt); fdt_begin_node(fdt, ""); - fdt_property_strf(fdt, FIT_DESC_PROP, - "%s image with one or more FDT blobs", - genimg_get_type_name(itl->fit_image_type)); + fdt_property_strf(fdt, FIT_DESC_PROP, "%s image%s", + genimg_get_type_name(itl->fit_image_type), + has_fdt ? " with one or more FDT blobs" : ""); fdt_property_strf(fdt, "creator", "U-Boot mkimage %s", PLAIN_VERSION); fdt_property_u32(fdt, "#address-cells", 1); ret = fit_write_images(itl, fdt); diff --git a/tools/imagetool.h b/tools/imagetool.h index 32694e32963..83812ea0566 100644 --- a/tools/imagetool.h +++ b/tools/imagetool.h @@ -111,6 +111,7 @@ struct imgtool { const char *engine_id; /* Engine to use for signing */ bool reset_timestamp; /* Reset the timestamp on an existing image */ struct image_summary summary; /* results of signing process */ + bool load_only; /* true to create a load-only FIT */ }; /** struct imgtool_funcs - image-type-specific variables and callbacks */ diff --git a/tools/mkimage.c b/tools/mkimage.c index 684e895938b..da629f17972 100644 --- a/tools/mkimage.c +++ b/tools/mkimage.c @@ -155,6 +155,10 @@ static int add_content(struct imgtool *itl, int type, const char *fname) static const char optstring[] = "a:A:b:B:c:C:d:D:e:Ef:Fg:G:i:k:K:ln:N:o:O:p:qrR:stT:vVx"; +enum { + OPT_LOAD_ONLY = 1, +}; + static const struct option longopts[] = { { "load-address", required_argument, NULL, 'a' }, { "architecture", required_argument, NULL, 'A' }, @@ -189,6 +193,7 @@ static const struct option longopts[] = { { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, { "xip", no_argument, NULL, 'x' }, + { "load-only", no_argument, NULL, OPT_LOAD_ONLY }, { /* sentinel */ }, }; @@ -360,6 +365,9 @@ static int process_args(struct imgtool *itl, int argc, char **argv) case 'x': itl->xflag++; break; + case OPT_LOAD_ONLY: + itl->load_only = true; + break; default: return usage(itl, "Invalid option"); } @@ -390,7 +398,7 @@ static int process_args(struct imgtool *itl, int argc, char **argv) /* For auto-FIT, datafile has to be provided with -d */ if (!itl->auto_fit) itl->datafile = datafile; - else if (!itl->datafile) + else if (!itl->datafile && type != IH_TYPE_FLATDT) return usage(itl, "Missing data file for auto-FIT (use -d)"); } else if (itl->lflag || type != IH_TYPE_INVALID) { -- 2.43.0

From: Simon Glass <sjg@chromium.org> When creating a FIT, ensure that a timestamp is added, even if there is no OS image present. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/mkimage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/mkimage.c b/tools/mkimage.c index da629f17972..11139657158 100644 --- a/tools/mkimage.c +++ b/tools/mkimage.c @@ -387,6 +387,8 @@ static int process_args(struct imgtool *itl, int argc, char **argv) return usage(itl, "Missing algorithm for auto-FIT with signed images (use -g)"); } + if (itl->auto_fit) + itl->reset_timestamp = 1; /* * For auto-generated FIT images we need to know the image type to put -- 2.43.0

From: Simon Glass <sjg@chromium.org> At present a temporary directory is used to hold the files before placing them in the filesystem. Use a suitably named directory inside the persistent-data directory, so that it is easy to take a look at what was produced. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/tests/fs_helper.py | 40 ++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index 3647af8fd89..c9df7a028a9 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -7,6 +7,7 @@ import re import os +import shutil from subprocess import call, check_call, check_output, CalledProcessError from subprocess import DEVNULL import tempfile @@ -63,16 +64,21 @@ class FsHelper: # Use a default filename; the caller can adjust it leaf = f'{prefix}.{fs_type}.img' - self.fs_img = os.path.join(config.persistent_data_dir, leaf) + if config: + self.fs_img = os.path.join(config.persistent_data_dir, leaf) + if os.path.exists(self.fs_img): + os.remove(self.fs_img) + else: + self.fs_img = leaf # Some distributions do not add /sbin to the default PATH, where mkfs # lives if '/sbin' not in os.environ["PATH"].split(os.pathsep): os.environ["PATH"] += os.pathsep + '/sbin' - self.srcdir = None self.tmpdir = None - self._do_cleanup = True + self.srcdir = None + self._do_cleanup = False def _get_fs_args(self): """Get the mkfs options and program to use @@ -108,6 +114,7 @@ class FsHelper: fs_img = self.fs_img with open(fs_img, 'wb') as fsi: fsi.truncate(self.size_mb << 20) + self._do_cleanup = True try: mkfs_opt, fs_lnxtype = self._get_fs_args() @@ -132,14 +139,22 @@ class FsHelper: def setup(self): """Set up the srcdir ready to receive files""" if not self.srcdir: - self.tmpdir = tempfile.TemporaryDirectory('fs_helper') - self.srcdir = self.tmpdir.name + if self.config: + self.srcdir = os.path.join(self.config.persistent_data_dir, + f'{self.prefix}.{self.fs_type}.tmp') + if os.path.exists(self.srcdir): + shutil.rmtree(self.srcdir) + os.mkdir(self.srcdir) + else: + self.tmpdir = tempfile.TemporaryDirectory('fs_helper') + self.srcdir = self.tmpdir.name def cleanup(self): - """Remove created files""" + """Remove created image""" if self.tmpdir: self.tmpdir.cleanup() - os.remove(self.fs_img) + if self._do_cleanup: + os.remove(self.fs_img) def __enter__(self): self.setup() @@ -188,6 +203,8 @@ class DiskHelper: self.fs_list = [] self.fname = os.path.join('' if cur_dir else config.persistent_data_dir, f'{prefix}{devnum}.img') + self.remove_img = True + self._do_cleanup = False def add_fs(self, fs_img, part_type, bootable=False): """Add a new filesystem @@ -217,6 +234,7 @@ class DiskHelper: img_size = pos try: check_call(f'qemu-img create {self.fname} {img_size}M', shell=True) + self._do_cleanup = True check_call(f'printf "{spec}" | sfdisk {self.fname}', shell=True) except CalledProcessError: os.remove(self.fname) @@ -230,15 +248,17 @@ class DiskHelper: pos += fsi.size_mb return self.fname - def cleanup(self, remove_full_img=False): + def cleanup(self): """Remove created file""" - os.remove(self.fname) + if self._do_cleanup: + os.remove(self.fname) def __enter__(self): return self def __exit__(self, extype, value, traceback): - self.cleanup() + if self.remove_img: + self.cleanup() def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000, -- 2.43.0

From: Simon Glass <sjg@chromium.org> We already have a cleanup function which removes the filesystem, so drop the unnecessary try...except for this. It results in an error on the error path, as we try to remove the filesystem twice. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/tests/fs_helper.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index c9df7a028a9..baf61cbd232 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -116,25 +116,21 @@ class FsHelper: fsi.truncate(self.size_mb << 20) self._do_cleanup = True - try: - mkfs_opt, fs_lnxtype = self._get_fs_args() - check_call(f'mkfs.{fs_lnxtype} {mkfs_opt} {fs_img}', shell=True, - stdout=DEVNULL if self.quiet else None) - - if self.fs_type.startswith('ext'): - sb_content = check_output(f'tune2fs -l {fs_img}', - shell=True).decode() - if 'metadata_csum' in sb_content: - check_call(f'tune2fs -O ^metadata_csum {fs_img}', shell=True) - elif fs_lnxtype == 'exfat': - check_call(f'fattools cp {self.srcdir}/* {fs_img}', shell=True) - elif self.srcdir and os.listdir(self.srcdir): - flags = f"-smpQ{'' if self.quiet else 'v'}" - check_call(f'mcopy -i {fs_img} {flags} {self.srcdir}/* ::/', - shell=True) - except CalledProcessError: - os.remove(fs_img) - raise + mkfs_opt, fs_lnxtype = self._get_fs_args() + check_call(f'mkfs.{fs_lnxtype} {mkfs_opt} {fs_img}', shell=True, + stdout=DEVNULL if self.quiet else None) + + if self.fs_type.startswith('ext'): + sb_content = check_output(f'tune2fs -l {fs_img}', + shell=True).decode() + if 'metadata_csum' in sb_content: + check_call(f'tune2fs -O ^metadata_csum {fs_img}', shell=True) + elif fs_lnxtype == 'exfat': + check_call(f'fattools cp {self.srcdir}/* {fs_img}', shell=True) + elif self.srcdir and os.listdir(self.srcdir): + flags = f"-smpQ{'' if self.quiet else 'v'}" + check_call(f'mcopy -i {fs_img} {flags} {self.srcdir}/* ::/', + shell=True) def setup(self): """Set up the srcdir ready to receive files""" -- 2.43.0

From: Simon Glass <sjg@chromium.org> The pxe_utils.c file is quite large and has a mix of parsing and booting code. Split out the parsing into a new 'pxe_parse.c' file. Add function prototypes for parse_pxefile_top() and label_destroy(), the two functions used by pxe_utils.c Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/Makefile | 2 +- boot/pxe_parse.c | 692 +++++++++++++++++++++++++++++++++++++++++++ boot/pxe_utils.c | 704 -------------------------------------------- include/pxe_utils.h | 24 ++ 4 files changed, 717 insertions(+), 705 deletions(-) create mode 100644 boot/pxe_parse.c diff --git a/boot/Makefile b/boot/Makefile index f4968cdcf7e..7f4126ec4ea 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -8,7 +8,7 @@ ifndef CONFIG_XPL_BUILD obj-$(CONFIG_BOOT_RETRY) += bootretry.o obj-$(CONFIG_BOOT) += bootm.o bootm_os.o -obj-$(CONFIG_PXE_UTILS) += pxe_utils.o +obj-$(CONFIG_PXE_UTILS) += pxe_utils.o pxe_parse.o endif diff --git a/boot/pxe_parse.c b/boot/pxe_parse.c new file mode 100644 index 00000000000..2402889ecdf --- /dev/null +++ b/boot/pxe_parse.c @@ -0,0 +1,692 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2010-2011 Calxeda, Inc. + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include <ctype.h> +#include <malloc.h> +#include <mapmem.h> +#include "pxe_utils.h" + +/** enum token_type - Tokens for the pxe file parser */ +enum token_type { + T_EOL, + T_STRING, + T_EOF, + T_MENU, + T_TITLE, + T_TIMEOUT, + T_LABEL, + T_KERNEL, + T_LINUX, + T_APPEND, + T_INITRD, + T_LOCALBOOT, + T_DEFAULT, + T_PROMPT, + T_INCLUDE, + T_FDT, + T_FDTDIR, + T_FDTOVERLAYS, + T_ONTIMEOUT, + T_IPAPPEND, + T_BACKGROUND, + T_KASLRSEED, + T_FALLBACK, + T_SAY, + T_INVALID +}; + +/** struct token - token - given by a value and a type */ +struct token { + char *val; + enum token_type type; +}; + +/* Keywords recognized */ +static const struct token keywords[] = { + {"menu", T_MENU}, + {"title", T_TITLE}, + {"timeout", T_TIMEOUT}, + {"default", T_DEFAULT}, + {"prompt", T_PROMPT}, + {"label", T_LABEL}, + {"kernel", T_KERNEL}, + {"linux", T_LINUX}, + {"localboot", T_LOCALBOOT}, + {"append", T_APPEND}, + {"initrd", T_INITRD}, + {"include", T_INCLUDE}, + {"devicetree", T_FDT}, + {"fdt", T_FDT}, + {"devicetreedir", T_FDTDIR}, + {"fdtdir", T_FDTDIR}, + {"fdtoverlays", T_FDTOVERLAYS}, + {"devicetree-overlay", T_FDTOVERLAYS}, + {"ontimeout", T_ONTIMEOUT,}, + {"ipappend", T_IPAPPEND,}, + {"background", T_BACKGROUND,}, + {"kaslrseed", T_KASLRSEED,}, + {"fallback", T_FALLBACK,}, + {"say", T_SAY,}, + {NULL, T_INVALID} +}; + +/** + * enum lex_state - lexer state + * + * Since pxe(linux) files don't have a token to identify the start of a + * literal, we have to keep track of when we're in a state where a literal is + * expected vs when we're in a state a keyword is expected. + */ +enum lex_state { + L_NORMAL = 0, + L_KEYWORD, + L_SLITERAL +}; + +/** + * label_create() - crate a new PXE label + * + * Allocates memory for and initializes a pxe_label. This uses malloc, so the + * result must be free()'d to reclaim the memory. + * + * Returns a pointer to the label, or NULL if out of memory + */ +static struct pxe_label *label_create(void) +{ + struct pxe_label *label; + + label = malloc(sizeof(struct pxe_label)); + if (!label) + return NULL; + + memset(label, 0, sizeof(struct pxe_label)); + + return label; +} + +void label_destroy(struct pxe_label *label) +{ + free(label->name); + free(label->kernel_label); + free(label->kernel); + free(label->config); + free(label->append); + free(label->initrd); + free(label->fdt); + free(label->fdtdir); + free(label->fdtoverlays); + free(label); +} + +/** + * get_string() - retrieves a string from *p and stores it as a token in *t. + * + * This is used for scanning both string literals and keywords. + * + * Characters from *p are copied into t-val until a character equal to + * delim is found, or a NUL byte is reached. If delim has the special value of + * ' ', any whitespace character will be used as a delimiter. + * + * If lower is unequal to 0, uppercase characters will be converted to + * lowercase in the result. This is useful to make keywords case + * insensitive. + * + * The location of *p is updated to point to the first character after the end + * of the token - the ending delimiter. + * + * Memory for t->val is allocated using malloc and must be free()'d to reclaim + * it. + * + * @p: Points to a pointer to the current position in the input being processed. + * Updated to point at the first character after the current token + * @t: Pointers to a token to fill in + * @delim: Delimiter character to look for, either newline or space + * @lower: true to convert the string to lower case when storing + * Returns the new value of t->val, on success, NULL if out of memory + */ +static char *get_string(char **p, struct token *t, char delim, int lower) +{ + char *b, *e; + size_t len, i; + + /* + * b and e both start at the beginning of the input stream. + * + * e is incremented until we find the ending delimiter, or a NUL byte + * is reached. Then, we take e - b to find the length of the token. + */ + b = *p; + e = *p; + while (*e) { + if ((delim == ' ' && isspace(*e)) || delim == *e) + break; + e++; + } + + len = e - b; + + /* + * Allocate memory to hold the string, and copy it in, converting + * characters to lowercase if lower is != 0. + */ + t->val = malloc(len + 1); + if (!t->val) + return NULL; + + for (i = 0; i < len; i++, b++) { + if (lower) + t->val[i] = tolower(*b); + else + t->val[i] = *b; + } + + t->val[len] = '\0'; + + /* Update *p so the caller knows where to continue scanning */ + *p = e; + t->type = T_STRING; + + return t->val; +} + +/** + * get_keyword() - Populate a keyword token with a type and value + * + * Updates the ->type field based on the keyword string in @val + * @t: Token to populate + */ +static void get_keyword(struct token *t) +{ + int i; + + for (i = 0; keywords[i].val; i++) { + if (!strcmp(t->val, keywords[i].val)) { + t->type = keywords[i].type; + break; + } + } +} + +/** + * get_token() - Get the next token + * + * We have to keep track of which state we're in to know if we're looking to get + * a string literal or a keyword. + * + * @p: Points to a pointer to the current position in the input being processed. + * Updated to point at the first character after the current token + */ +static void get_token(char **p, struct token *t, enum lex_state state) +{ + char *c = *p; + + t->type = T_INVALID; + + /* eat non EOL whitespace */ + while (isblank(*c)) + c++; + + /* + * eat comments. note that string literals can't begin with #, but + * can contain a # after their first character. + */ + if (*c == '#') { + while (*c && *c != '\n') + c++; + } + + if (*c == '\n') { + t->type = T_EOL; + c++; + } else if (*c == '\0') { + t->type = T_EOF; + c++; + } else if (state == L_SLITERAL) { + get_string(&c, t, '\n', 0); + } else if (state == L_KEYWORD) { + /* + * when we expect a keyword, we first get the next string + * token delimited by whitespace, and then check if it + * matches a keyword in our keyword list. if it does, it's + * converted to a keyword token of the appropriate type, and + * if not, it remains a string token. + */ + get_string(&c, t, ' ', 1); + get_keyword(t); + } + + *p = c; +} + +/** + * eol_or_eof() - Find end of line + * + * Increment *c until we get to the end of the current line, or EOF + * + * @c: Points to a pointer to the current position in the input being processed. + * Updated to point at the first character after the current token + */ +static void eol_or_eof(char **c) +{ + while (**c && **c != '\n') + (*c)++; +} + +/* + * All of these parse_* functions share some common behavior. + * + * They finish with *c pointing after the token they parse, and return 1 on + * success, or < 0 on error. + */ + +/* + * Parse a string literal and store a pointer it at *dst. String literals + * terminate at the end of the line. + */ +static int parse_sliteral(char **c, char **dst) +{ + struct token t; + char *s = *c; + + get_token(c, &t, L_SLITERAL); + + if (t.type != T_STRING) { + printf("Expected string literal: %.*s\n", (int)(*c - s), s); + return -EINVAL; + } + + *dst = t.val; + + return 1; +} + +/* + * Parse a base 10 (unsigned) integer and store it at *dst. + */ +static int parse_integer(char **c, int *dst) +{ + struct token t; + char *s = *c; + + get_token(c, &t, L_SLITERAL); + if (t.type != T_STRING) { + printf("Expected string: %.*s\n", (int)(*c - s), s); + return -EINVAL; + } + + *dst = simple_strtol(t.val, NULL, 10); + + free(t.val); + + return 1; +} + +/* + * Parse an include statement, and retrieve and parse the file it mentions. + * + * base should point to a location where it's safe to store the file, and + * nest_level should indicate how many nested includes have occurred. For this + * include, nest_level has already been incremented and doesn't need to be + * incremented here. + */ +static int handle_include(struct pxe_context *ctx, char **c, unsigned long base, + struct pxe_menu *cfg, int nest_level) +{ + char *include_path; + char *s = *c; + int err; + char *buf; + int ret; + + err = parse_sliteral(c, &include_path); + if (err < 0) { + printf("Expected include path: %.*s\n", (int)(*c - s), s); + return err; + } + + err = get_pxe_file(ctx, include_path, base); + if (err < 0) { + printf("Couldn't retrieve %s\n", include_path); + return err; + } + + buf = map_sysmem(base, 0); + ret = parse_pxefile_top(ctx, buf, base, cfg, nest_level); + unmap_sysmem(buf); + + return ret; +} + +/* + * Parse lines that begin with 'menu'. + * + * base and nest are provided to handle the 'menu include' case. + * + * base should point to a location where it's safe to store the included file. + * + * nest_level should be 1 when parsing the top level pxe file, 2 when parsing + * a file it includes, 3 when parsing a file included by that file, and so on. + */ +static int parse_menu(struct pxe_context *ctx, char **c, struct pxe_menu *cfg, + unsigned long base, int nest_level) +{ + struct token t; + char *s = *c; + int err = 0; + + get_token(c, &t, L_KEYWORD); + + switch (t.type) { + case T_TITLE: + err = parse_sliteral(c, &cfg->title); + + break; + + case T_INCLUDE: + err = handle_include(ctx, c, base, cfg, nest_level + 1); + break; + + case T_BACKGROUND: + err = parse_sliteral(c, &cfg->bmp); + break; + + default: + printf("Ignoring malformed menu command: %.*s\n", + (int)(*c - s), s); + } + if (err < 0) + return err; + + eol_or_eof(c); + + return 1; +} + +/* + * Handles parsing a 'menu line' when we're parsing a label. + */ +static int parse_label_menu(char **c, struct pxe_menu *cfg, + struct pxe_label *label) +{ + struct token t; + char *s; + + s = *c; + + get_token(c, &t, L_KEYWORD); + + switch (t.type) { + case T_DEFAULT: + if (!cfg->default_label) + cfg->default_label = strdup(label->name); + + if (!cfg->default_label) + return -ENOMEM; + + break; + case T_LABEL: + parse_sliteral(c, &label->menu); + break; + default: + printf("Ignoring malformed menu command: %.*s\n", + (int)(*c - s), s); + } + + eol_or_eof(c); + + return 0; +} + +/* + * Handles parsing a 'kernel' label. + * expecting "filename" or "<fit_filename>#cfg" + */ +static int parse_label_kernel(char **c, struct pxe_label *label) +{ + char *s; + int err; + + err = parse_sliteral(c, &label->kernel); + if (err < 0) + return err; + + /* copy the kernel label to compare with FDT / INITRD when FIT is used */ + label->kernel_label = strdup(label->kernel); + if (!label->kernel_label) + return -ENOMEM; + + s = strstr(label->kernel, "#"); + if (!s) + return 1; + + label->config = strdup(s); + if (!label->config) + return -ENOMEM; + + *s = 0; + + return 1; +} + +/* + * Parses a label and adds it to the list of labels for a menu. + * + * A label ends when we either get to the end of a file, or + * get some input we otherwise don't have a handler defined + * for. + * + */ +static int parse_label(char **c, struct pxe_menu *cfg) +{ + struct token t; + int len; + char *s = *c; + struct pxe_label *label; + int err; + + label = label_create(); + if (!label) + return -ENOMEM; + + err = parse_sliteral(c, &label->name); + if (err < 0) { + printf("Expected label name: %.*s\n", (int)(*c - s), s); + label_destroy(label); + return -EINVAL; + } + + list_add_tail(&label->list, &cfg->labels); + + while (1) { + s = *c; + get_token(c, &t, L_KEYWORD); + + err = 0; + switch (t.type) { + case T_MENU: + err = parse_label_menu(c, cfg, label); + break; + + case T_KERNEL: + case T_LINUX: + err = parse_label_kernel(c, label); + break; + + case T_APPEND: + err = parse_sliteral(c, &label->append); + if (label->initrd) + break; + s = strstr(label->append, "initrd="); + if (!s) + break; + s += 7; + len = (int)(strchr(s, ' ') - s); + label->initrd = malloc(len + 1); + strlcpy(label->initrd, s, len); + label->initrd[len] = '\0'; + + break; + + case T_INITRD: + if (!label->initrd) + err = parse_sliteral(c, &label->initrd); + break; + + case T_FDT: + if (!label->fdt) + err = parse_sliteral(c, &label->fdt); + break; + + case T_FDTDIR: + if (!label->fdtdir) + err = parse_sliteral(c, &label->fdtdir); + break; + + case T_FDTOVERLAYS: + if (!label->fdtoverlays) + err = parse_sliteral(c, &label->fdtoverlays); + break; + + case T_LOCALBOOT: + label->localboot = 1; + err = parse_integer(c, &label->localboot_val); + break; + + case T_IPAPPEND: + err = parse_integer(c, &label->ipappend); + break; + + case T_KASLRSEED: + label->kaslrseed = 1; + break; + + case T_EOL: + break; + case T_SAY: { + char *p = strchr(s, '\n'); + + if (p) { + printf("%.*s\n", (int)(p - *c) - 1, *c + 1); + + *c = p; + } + break; + } + default: + /* + * put the token back! we don't want it - it's the end + * of a label and whatever token this is, it's + * something for the menu level context to handle. + */ + *c = s; + return 1; + } + + if (err < 0) + return err; + } +} + +/* + * This 16 comes from the limit pxelinux imposes on nested includes. + * + * There is no reason at all we couldn't do more, but some limit helps prevent + * infinite (until crash occurs) recursion if a file tries to include itself. + */ +#define MAX_NEST_LEVEL 16 + +int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base, + struct pxe_menu *cfg, int nest_level) +{ + struct token t; + char *s, *b, *label_name; + int err; + + b = p; + + if (nest_level > MAX_NEST_LEVEL) { + printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL); + return -EMLINK; + } + + while (1) { + s = p; + + get_token(&p, &t, L_KEYWORD); + + err = 0; + switch (t.type) { + case T_MENU: + cfg->prompt = 1; + err = parse_menu(ctx, &p, cfg, + base + ALIGN(strlen(b) + 1, 4), + nest_level); + break; + + case T_TIMEOUT: + err = parse_integer(&p, &cfg->timeout); + break; + + case T_LABEL: + err = parse_label(&p, cfg); + break; + + case T_DEFAULT: + case T_ONTIMEOUT: + err = parse_sliteral(&p, &label_name); + + if (label_name) { + if (cfg->default_label) + free(cfg->default_label); + + cfg->default_label = label_name; + } + + break; + + case T_FALLBACK: + err = parse_sliteral(&p, &label_name); + + if (label_name) { + if (cfg->fallback_label) + free(cfg->fallback_label); + + cfg->fallback_label = label_name; + } + + break; + + case T_INCLUDE: + err = handle_include(ctx, &p, + base + ALIGN(strlen(b), 4), cfg, + nest_level + 1); + break; + + case T_PROMPT: + err = parse_integer(&p, &cfg->prompt); + // Do not fail if prompt configuration is undefined + if (err < 0) + eol_or_eof(&p); + break; + + case T_EOL: + break; + + case T_EOF: + return 1; + + default: + printf("Ignoring unknown command: %.*s\n", + (int)(p - s), s); + eol_or_eof(&p); + } + + if (err < 0) + return err; + } +} diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 23c23038a92..8ff06ce6c52 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -216,54 +216,6 @@ static int get_relfile_envaddr(struct pxe_context *ctx, const char *file_path, return get_relfile(ctx, file_path, file_addr, type, filesizep); } -/** - * label_create() - crate a new PXE label - * - * Allocates memory for and initializes a pxe_label. This uses malloc, so the - * result must be free()'d to reclaim the memory. - * - * Returns a pointer to the label, or NULL if out of memory - */ -static struct pxe_label *label_create(void) -{ - struct pxe_label *label; - - label = malloc(sizeof(struct pxe_label)); - if (!label) - return NULL; - - memset(label, 0, sizeof(struct pxe_label)); - - return label; -} - -/** - * label_destroy() - free the memory used by a pxe_label - * - * This frees @label itself as well as memory used by its name, - * kernel, config, append, initrd, fdt, fdtdir and fdtoverlay members, if - * they're non-NULL. - * - * So - be sure to only use dynamically allocated memory for the members of - * the pxe_label struct, unless you want to clean it up first. These are - * currently only created by the pxe file parsing code. - * - * @label: Label to free - */ -static void label_destroy(struct pxe_label *label) -{ - free(label->name); - free(label->kernel_label); - free(label->kernel); - free(label->config); - free(label->append); - free(label->initrd); - free(label->fdt); - free(label->fdtdir); - free(label->fdtoverlays); - free(label); -} - /** * label_print() - Print a label and its string members if they're defined * @@ -866,662 +818,6 @@ cleanup: return 1; /* returning is always failure */ } -/** enum token_type - Tokens for the pxe file parser */ -enum token_type { - T_EOL, - T_STRING, - T_EOF, - T_MENU, - T_TITLE, - T_TIMEOUT, - T_LABEL, - T_KERNEL, - T_LINUX, - T_APPEND, - T_INITRD, - T_LOCALBOOT, - T_DEFAULT, - T_PROMPT, - T_INCLUDE, - T_FDT, - T_FDTDIR, - T_FDTOVERLAYS, - T_ONTIMEOUT, - T_IPAPPEND, - T_BACKGROUND, - T_KASLRSEED, - T_FALLBACK, - T_SAY, - T_INVALID -}; - -/** struct token - token - given by a value and a type */ -struct token { - char *val; - enum token_type type; -}; - -/* Keywords recognized */ -static const struct token keywords[] = { - {"menu", T_MENU}, - {"title", T_TITLE}, - {"timeout", T_TIMEOUT}, - {"default", T_DEFAULT}, - {"prompt", T_PROMPT}, - {"label", T_LABEL}, - {"kernel", T_KERNEL}, - {"linux", T_LINUX}, - {"localboot", T_LOCALBOOT}, - {"append", T_APPEND}, - {"initrd", T_INITRD}, - {"include", T_INCLUDE}, - {"devicetree", T_FDT}, - {"fdt", T_FDT}, - {"devicetreedir", T_FDTDIR}, - {"fdtdir", T_FDTDIR}, - {"fdtoverlays", T_FDTOVERLAYS}, - {"devicetree-overlay", T_FDTOVERLAYS}, - {"ontimeout", T_ONTIMEOUT,}, - {"ipappend", T_IPAPPEND,}, - {"background", T_BACKGROUND,}, - {"kaslrseed", T_KASLRSEED,}, - {"fallback", T_FALLBACK,}, - {"say", T_SAY,}, - {NULL, T_INVALID} -}; - -/** - * enum lex_state - lexer state - * - * Since pxe(linux) files don't have a token to identify the start of a - * literal, we have to keep track of when we're in a state where a literal is - * expected vs when we're in a state a keyword is expected. - */ -enum lex_state { - L_NORMAL = 0, - L_KEYWORD, - L_SLITERAL -}; - -/** - * get_string() - retrieves a string from *p and stores it as a token in *t. - * - * This is used for scanning both string literals and keywords. - * - * Characters from *p are copied into t-val until a character equal to - * delim is found, or a NUL byte is reached. If delim has the special value of - * ' ', any whitespace character will be used as a delimiter. - * - * If lower is unequal to 0, uppercase characters will be converted to - * lowercase in the result. This is useful to make keywords case - * insensitive. - * - * The location of *p is updated to point to the first character after the end - * of the token - the ending delimiter. - * - * Memory for t->val is allocated using malloc and must be free()'d to reclaim - * it. - * - * @p: Points to a pointer to the current position in the input being processed. - * Updated to point at the first character after the current token - * @t: Pointers to a token to fill in - * @delim: Delimiter character to look for, either newline or space - * @lower: true to convert the string to lower case when storing - * Returns the new value of t->val, on success, NULL if out of memory - */ -static char *get_string(char **p, struct token *t, char delim, int lower) -{ - char *b, *e; - size_t len, i; - - /* - * b and e both start at the beginning of the input stream. - * - * e is incremented until we find the ending delimiter, or a NUL byte - * is reached. Then, we take e - b to find the length of the token. - */ - b = *p; - e = *p; - while (*e) { - if ((delim == ' ' && isspace(*e)) || delim == *e) - break; - e++; - } - - len = e - b; - - /* - * Allocate memory to hold the string, and copy it in, converting - * characters to lowercase if lower is != 0. - */ - t->val = malloc(len + 1); - if (!t->val) - return NULL; - - for (i = 0; i < len; i++, b++) { - if (lower) - t->val[i] = tolower(*b); - else - t->val[i] = *b; - } - - t->val[len] = '\0'; - - /* Update *p so the caller knows where to continue scanning */ - *p = e; - t->type = T_STRING; - - return t->val; -} - -/** - * get_keyword() - Populate a keyword token with a type and value - * - * Updates the ->type field based on the keyword string in @val - * @t: Token to populate - */ -static void get_keyword(struct token *t) -{ - int i; - - for (i = 0; keywords[i].val; i++) { - if (!strcmp(t->val, keywords[i].val)) { - t->type = keywords[i].type; - break; - } - } -} - -/** - * get_token() - Get the next token - * - * We have to keep track of which state we're in to know if we're looking to get - * a string literal or a keyword. - * - * @p: Points to a pointer to the current position in the input being processed. - * Updated to point at the first character after the current token - */ -static void get_token(char **p, struct token *t, enum lex_state state) -{ - char *c = *p; - - t->type = T_INVALID; - - /* eat non EOL whitespace */ - while (isblank(*c)) - c++; - - /* - * eat comments. note that string literals can't begin with #, but - * can contain a # after their first character. - */ - if (*c == '#') { - while (*c && *c != '\n') - c++; - } - - if (*c == '\n') { - t->type = T_EOL; - c++; - } else if (*c == '\0') { - t->type = T_EOF; - c++; - } else if (state == L_SLITERAL) { - get_string(&c, t, '\n', 0); - } else if (state == L_KEYWORD) { - /* - * when we expect a keyword, we first get the next string - * token delimited by whitespace, and then check if it - * matches a keyword in our keyword list. if it does, it's - * converted to a keyword token of the appropriate type, and - * if not, it remains a string token. - */ - get_string(&c, t, ' ', 1); - get_keyword(t); - } - - *p = c; -} - -/** - * eol_or_eof() - Find end of line - * - * Increment *c until we get to the end of the current line, or EOF - * - * @c: Points to a pointer to the current position in the input being processed. - * Updated to point at the first character after the current token - */ -static void eol_or_eof(char **c) -{ - while (**c && **c != '\n') - (*c)++; -} - -/* - * All of these parse_* functions share some common behavior. - * - * They finish with *c pointing after the token they parse, and return 1 on - * success, or < 0 on error. - */ - -/* - * Parse a string literal and store a pointer it at *dst. String literals - * terminate at the end of the line. - */ -static int parse_sliteral(char **c, char **dst) -{ - struct token t; - char *s = *c; - - get_token(c, &t, L_SLITERAL); - - if (t.type != T_STRING) { - printf("Expected string literal: %.*s\n", (int)(*c - s), s); - return -EINVAL; - } - - *dst = t.val; - - return 1; -} - -/* - * Parse a base 10 (unsigned) integer and store it at *dst. - */ -static int parse_integer(char **c, int *dst) -{ - struct token t; - char *s = *c; - - get_token(c, &t, L_SLITERAL); - if (t.type != T_STRING) { - printf("Expected string: %.*s\n", (int)(*c - s), s); - return -EINVAL; - } - - *dst = simple_strtol(t.val, NULL, 10); - - free(t.val); - - return 1; -} - -static int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base, - struct pxe_menu *cfg, int nest_level); - -/* - * Parse an include statement, and retrieve and parse the file it mentions. - * - * base should point to a location where it's safe to store the file, and - * nest_level should indicate how many nested includes have occurred. For this - * include, nest_level has already been incremented and doesn't need to be - * incremented here. - */ -static int handle_include(struct pxe_context *ctx, char **c, unsigned long base, - struct pxe_menu *cfg, int nest_level) -{ - char *include_path; - char *s = *c; - int err; - char *buf; - int ret; - - err = parse_sliteral(c, &include_path); - if (err < 0) { - printf("Expected include path: %.*s\n", (int)(*c - s), s); - return err; - } - - err = get_pxe_file(ctx, include_path, base); - if (err < 0) { - printf("Couldn't retrieve %s\n", include_path); - return err; - } - - buf = map_sysmem(base, 0); - ret = parse_pxefile_top(ctx, buf, base, cfg, nest_level); - unmap_sysmem(buf); - - return ret; -} - -/* - * Parse lines that begin with 'menu'. - * - * base and nest are provided to handle the 'menu include' case. - * - * base should point to a location where it's safe to store the included file. - * - * nest_level should be 1 when parsing the top level pxe file, 2 when parsing - * a file it includes, 3 when parsing a file included by that file, and so on. - */ -static int parse_menu(struct pxe_context *ctx, char **c, struct pxe_menu *cfg, - unsigned long base, int nest_level) -{ - struct token t; - char *s = *c; - int err = 0; - - get_token(c, &t, L_KEYWORD); - - switch (t.type) { - case T_TITLE: - err = parse_sliteral(c, &cfg->title); - - break; - - case T_INCLUDE: - err = handle_include(ctx, c, base, cfg, nest_level + 1); - break; - - case T_BACKGROUND: - err = parse_sliteral(c, &cfg->bmp); - break; - - default: - printf("Ignoring malformed menu command: %.*s\n", - (int)(*c - s), s); - } - if (err < 0) - return err; - - eol_or_eof(c); - - return 1; -} - -/* - * Handles parsing a 'menu line' when we're parsing a label. - */ -static int parse_label_menu(char **c, struct pxe_menu *cfg, - struct pxe_label *label) -{ - struct token t; - char *s; - - s = *c; - - get_token(c, &t, L_KEYWORD); - - switch (t.type) { - case T_DEFAULT: - if (!cfg->default_label) - cfg->default_label = strdup(label->name); - - if (!cfg->default_label) - return -ENOMEM; - - break; - case T_LABEL: - parse_sliteral(c, &label->menu); - break; - default: - printf("Ignoring malformed menu command: %.*s\n", - (int)(*c - s), s); - } - - eol_or_eof(c); - - return 0; -} - -/* - * Handles parsing a 'kernel' label. - * expecting "filename" or "<fit_filename>#cfg" - */ -static int parse_label_kernel(char **c, struct pxe_label *label) -{ - char *s; - int err; - - err = parse_sliteral(c, &label->kernel); - if (err < 0) - return err; - - /* copy the kernel label to compare with FDT / INITRD when FIT is used */ - label->kernel_label = strdup(label->kernel); - if (!label->kernel_label) - return -ENOMEM; - - s = strstr(label->kernel, "#"); - if (!s) - return 1; - - label->config = strdup(s); - if (!label->config) - return -ENOMEM; - - *s = 0; - - return 1; -} - -/* - * Parses a label and adds it to the list of labels for a menu. - * - * A label ends when we either get to the end of a file, or - * get some input we otherwise don't have a handler defined - * for. - * - */ -static int parse_label(char **c, struct pxe_menu *cfg) -{ - struct token t; - int len; - char *s = *c; - struct pxe_label *label; - int err; - - label = label_create(); - if (!label) - return -ENOMEM; - - err = parse_sliteral(c, &label->name); - if (err < 0) { - printf("Expected label name: %.*s\n", (int)(*c - s), s); - label_destroy(label); - return -EINVAL; - } - - list_add_tail(&label->list, &cfg->labels); - - while (1) { - s = *c; - get_token(c, &t, L_KEYWORD); - - err = 0; - switch (t.type) { - case T_MENU: - err = parse_label_menu(c, cfg, label); - break; - - case T_KERNEL: - case T_LINUX: - err = parse_label_kernel(c, label); - break; - - case T_APPEND: - err = parse_sliteral(c, &label->append); - if (label->initrd) - break; - s = strstr(label->append, "initrd="); - if (!s) - break; - s += 7; - len = (int)(strchr(s, ' ') - s); - label->initrd = malloc(len + 1); - strncpy(label->initrd, s, len); - label->initrd[len] = '\0'; - - break; - - case T_INITRD: - if (!label->initrd) - err = parse_sliteral(c, &label->initrd); - break; - - case T_FDT: - if (!label->fdt) - err = parse_sliteral(c, &label->fdt); - break; - - case T_FDTDIR: - if (!label->fdtdir) - err = parse_sliteral(c, &label->fdtdir); - break; - - case T_FDTOVERLAYS: - if (!label->fdtoverlays) - err = parse_sliteral(c, &label->fdtoverlays); - break; - - case T_LOCALBOOT: - label->localboot = 1; - err = parse_integer(c, &label->localboot_val); - break; - - case T_IPAPPEND: - err = parse_integer(c, &label->ipappend); - break; - - case T_KASLRSEED: - label->kaslrseed = 1; - break; - - case T_EOL: - break; - - case T_SAY: { - char *p = strchr(s, '\n'); - - if (p) { - printf("%.*s\n", (int)(p - *c) - 1, *c + 1); - - *c = p; - } - break; - } - - default: - /* - * put the token back! we don't want it - it's the end - * of a label and whatever token this is, it's - * something for the menu level context to handle. - */ - *c = s; - return 1; - } - - if (err < 0) - return err; - } -} - -/* - * This 16 comes from the limit pxelinux imposes on nested includes. - * - * There is no reason at all we couldn't do more, but some limit helps prevent - * infinite (until crash occurs) recursion if a file tries to include itself. - */ -#define MAX_NEST_LEVEL 16 - -/* - * Entry point for parsing a menu file. nest_level indicates how many times - * we've nested in includes. It will be 1 for the top level menu file. - * - * Returns 1 on success, < 0 on error. - */ -static int parse_pxefile_top(struct pxe_context *ctx, char *p, unsigned long base, - struct pxe_menu *cfg, int nest_level) -{ - struct token t; - char *s, *b, *label_name; - int err; - - b = p; - - if (nest_level > MAX_NEST_LEVEL) { - printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL); - return -EMLINK; - } - - while (1) { - s = p; - - get_token(&p, &t, L_KEYWORD); - - err = 0; - switch (t.type) { - case T_MENU: - cfg->prompt = 1; - err = parse_menu(ctx, &p, cfg, - base + ALIGN(strlen(b) + 1, 4), - nest_level); - break; - - case T_TIMEOUT: - err = parse_integer(&p, &cfg->timeout); - break; - - case T_LABEL: - err = parse_label(&p, cfg); - break; - - case T_DEFAULT: - case T_ONTIMEOUT: - err = parse_sliteral(&p, &label_name); - - if (label_name) { - if (cfg->default_label) - free(cfg->default_label); - - cfg->default_label = label_name; - } - - break; - - case T_FALLBACK: - err = parse_sliteral(&p, &label_name); - - if (label_name) { - if (cfg->fallback_label) - free(cfg->fallback_label); - - cfg->fallback_label = label_name; - } - - break; - - case T_INCLUDE: - err = handle_include(ctx, &p, - base + ALIGN(strlen(b), 4), cfg, - nest_level + 1); - break; - - case T_PROMPT: - err = parse_integer(&p, &cfg->prompt); - // Do not fail if prompt configuration is undefined - if (err < 0) - eol_or_eof(&p); - break; - - case T_EOL: - break; - - case T_EOF: - return 1; - - default: - printf("Ignoring unknown command: %.*s\n", - (int)(p - s), s); - eol_or_eof(&p); - } - - if (err < 0) - return err; - } -} - /* */ void destroy_pxe_menu(struct pxe_menu *cfg) diff --git a/include/pxe_utils.h b/include/pxe_utils.h index 6425e349d19..a63d0123059 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -324,4 +324,28 @@ int pxe_probe(struct pxe_context *ctx, ulong pxefile_addr_r, bool prompt); */ int pxe_do_boot(struct pxe_context *ctx); +/* + * Entry point for parsing a menu file. nest_level indicates how many times + * we've nested in includes. It will be 1 for the top level menu file. + * + * Returns 1 on success, < 0 on error. + */ +int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base, + struct pxe_menu *cfg, int nest_level); + +/** + * label_destroy() - free the memory used by a pxe_label + * + * This frees @label itself as well as memory used by its name, + * kernel, config, append, initrd, fdt, fdtdir and fdtoverlay members, if + * they're non-NULL. + * + * So - be sure to only use dynamically allocated memory for the members of + * the pxe_label struct, unless you want to clean it up first. These are + * currently only created by the pxe file parsing code. + * + * @label: Label to free + */ +void label_destroy(struct pxe_label *label); + #endif /* __PXE_UTILS_H */ -- 2.43.0

From: Simon Glass <sjg@chromium.org> This file has quite a few more blank lines than is usual in the U-Boot code base. Remove them. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/pxe_parse.c | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/boot/pxe_parse.c b/boot/pxe_parse.c index 2402889ecdf..bc1a15238b1 100644 --- a/boot/pxe_parse.c +++ b/boot/pxe_parse.c @@ -103,7 +103,6 @@ static struct pxe_label *label_create(void) label = malloc(sizeof(struct pxe_label)); if (!label) return NULL; - memset(label, 0, sizeof(struct pxe_label)); return label; @@ -167,7 +166,6 @@ static char *get_string(char **p, struct token *t, char delim, int lower) break; e++; } - len = e - b; /* @@ -294,7 +292,6 @@ static int parse_sliteral(char **c, char **dst) char *s = *c; get_token(c, &t, L_SLITERAL); - if (t.type != T_STRING) { printf("Expected string literal: %.*s\n", (int)(*c - s), s); return -EINVAL; @@ -320,7 +317,6 @@ static int parse_integer(char **c, int *dst) } *dst = simple_strtol(t.val, NULL, 10); - free(t.val); return 1; @@ -384,17 +380,13 @@ static int parse_menu(struct pxe_context *ctx, char **c, struct pxe_menu *cfg, switch (t.type) { case T_TITLE: err = parse_sliteral(c, &cfg->title); - break; - case T_INCLUDE: err = handle_include(ctx, c, base, cfg, nest_level + 1); break; - case T_BACKGROUND: err = parse_sliteral(c, &cfg->bmp); break; - default: printf("Ignoring malformed menu command: %.*s\n", (int)(*c - s), s); @@ -417,7 +409,6 @@ static int parse_label_menu(char **c, struct pxe_menu *cfg, char *s; s = *c; - get_token(c, &t, L_KEYWORD); switch (t.type) { @@ -479,7 +470,6 @@ static int parse_label_kernel(char **c, struct pxe_label *label) * A label ends when we either get to the end of a file, or * get some input we otherwise don't have a handler defined * for. - * */ static int parse_label(char **c, struct pxe_menu *cfg) { @@ -499,7 +489,6 @@ static int parse_label(char **c, struct pxe_menu *cfg) label_destroy(label); return -EINVAL; } - list_add_tail(&label->list, &cfg->labels); while (1) { @@ -511,12 +500,10 @@ static int parse_label(char **c, struct pxe_menu *cfg) case T_MENU: err = parse_label_menu(c, cfg, label); break; - case T_KERNEL: case T_LINUX: err = parse_label_kernel(c, label); break; - case T_APPEND: err = parse_sliteral(c, &label->append); if (label->initrd) @@ -531,40 +518,32 @@ static int parse_label(char **c, struct pxe_menu *cfg) label->initrd[len] = '\0'; break; - case T_INITRD: if (!label->initrd) err = parse_sliteral(c, &label->initrd); break; - case T_FDT: if (!label->fdt) err = parse_sliteral(c, &label->fdt); break; - case T_FDTDIR: if (!label->fdtdir) err = parse_sliteral(c, &label->fdtdir); break; - case T_FDTOVERLAYS: if (!label->fdtoverlays) err = parse_sliteral(c, &label->fdtoverlays); break; - case T_LOCALBOOT: label->localboot = 1; err = parse_integer(c, &label->localboot_val); break; - case T_IPAPPEND: err = parse_integer(c, &label->ipappend); break; - case T_KASLRSEED: label->kaslrseed = 1; break; - case T_EOL: break; case T_SAY: { @@ -608,7 +587,6 @@ int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base, int err; b = p; - if (nest_level > MAX_NEST_LEVEL) { printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL); return -EMLINK; @@ -616,7 +594,6 @@ int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base, while (1) { s = p; - get_token(&p, &t, L_KEYWORD); err = 0; @@ -627,59 +604,46 @@ int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base, base + ALIGN(strlen(b) + 1, 4), nest_level); break; - case T_TIMEOUT: err = parse_integer(&p, &cfg->timeout); break; - case T_LABEL: err = parse_label(&p, cfg); break; - case T_DEFAULT: case T_ONTIMEOUT: err = parse_sliteral(&p, &label_name); - if (label_name) { if (cfg->default_label) free(cfg->default_label); cfg->default_label = label_name; } - break; - case T_FALLBACK: err = parse_sliteral(&p, &label_name); - if (label_name) { if (cfg->fallback_label) free(cfg->fallback_label); cfg->fallback_label = label_name; } - break; - case T_INCLUDE: err = handle_include(ctx, &p, base + ALIGN(strlen(b), 4), cfg, nest_level + 1); break; - case T_PROMPT: err = parse_integer(&p, &cfg->prompt); // Do not fail if prompt configuration is undefined if (err < 0) eol_or_eof(&p); break; - case T_EOL: break; - case T_EOF: return 1; - default: printf("Ignoring unknown command: %.*s\n", (int)(p - s), s); -- 2.43.0

From: Simon Glass <sjg@chromium.org> It is not obvious that PXE supports FIT, but it does, by detecting whether an image is a FIT or not. In many cases a FIT is more convenient than using separate files for the kernel, initrd and devicetree. Really we should promote FIT as an important format, rather than silentily dealing with it if detected. Add a new 'fit' token which indicates that a FIT is being used. When the 'fit' token is used, the expectation is that the devicetree is within the FIT, but this is not required, for now. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/pxe_parse.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/boot/pxe_parse.c b/boot/pxe_parse.c index bc1a15238b1..633b218a7cd 100644 --- a/boot/pxe_parse.c +++ b/boot/pxe_parse.c @@ -37,6 +37,7 @@ enum token_type { T_KASLRSEED, T_FALLBACK, T_SAY, + T_FIT, T_INVALID }; @@ -72,6 +73,7 @@ static const struct token keywords[] = { {"kaslrseed", T_KASLRSEED,}, {"fallback", T_FALLBACK,}, {"say", T_SAY,}, + {"fit", T_FIT,}, {NULL, T_INVALID} }; @@ -502,6 +504,7 @@ static int parse_label(char **c, struct pxe_menu *cfg) break; case T_KERNEL: case T_LINUX: + case T_FIT: err = parse_label_kernel(c, label); break; case T_APPEND: -- 2.43.0

From: Simon Glass <sjg@chromium.org> Rather than using bflow->fname, which assumes that it is the same as the passed-in filename, use the passed in filename. This can be different in some cases. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/bootmeth-uclass.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c index f0e541c0934..603ad98fc3a 100644 --- a/boot/bootmeth-uclass.c +++ b/boot/bootmeth-uclass.c @@ -388,8 +388,7 @@ int bootmeth_alloc_other(struct bootflow *bflow, const char *fname, if (ret) return log_msg_ret("all", ret); - if (!bootflow_img_add(bflow, bflow->fname, type, abuf_addr(buf), - size)) + if (!bootflow_img_add(bflow, fname, type, abuf_addr(buf), size)) return log_msg_ret("boi", -ENOMEM); return 0; -- 2.43.0

From: Simon Glass <sjg@chromium.org> If a load-only FIT has already provided a devicetree, PXE boot may need to restart the bootm sequence, rather than starting an entirely new one, so that the devicetree is preserved and used for booting. Add support for this by adding a new field in the context and updating extlinux_boot() to receive the value as a new parameter. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/bootmeth_extlinux.c | 3 ++- boot/bootmeth_pxe.c | 2 +- boot/ext_pxe_common.c | 3 ++- boot/pxe_utils.c | 7 ++++++- include/extlinux.h | 4 +++- include/pxe_utils.h | 3 +++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/boot/bootmeth_extlinux.c b/boot/bootmeth_extlinux.c index 782e5f7ec76..383f33e681c 100644 --- a/boot/bootmeth_extlinux.c +++ b/boot/bootmeth_extlinux.c @@ -166,7 +166,8 @@ static int extlinux_read_bootflow(struct udevice *dev, struct bootflow *bflow) static int extlinux_local_boot(struct udevice *dev, struct bootflow *bflow) { - return extlinux_boot(dev, bflow, extlinux_getfile, true, bflow->fname); + return extlinux_boot(dev, bflow, extlinux_getfile, true, bflow->fname, + false); } #if CONFIG_IS_ENABLED(BOOTSTD_FULL) diff --git a/boot/bootmeth_pxe.c b/boot/bootmeth_pxe.c index 18d8ab446e0..30caadc8f3e 100644 --- a/boot/bootmeth_pxe.c +++ b/boot/bootmeth_pxe.c @@ -140,7 +140,7 @@ static int extlinux_pxe_read_file(struct udevice *dev, struct bootflow *bflow, static int extlinux_pxe_boot(struct udevice *dev, struct bootflow *bflow) { return extlinux_boot(dev, bflow, extlinux_pxe_getfile, false, - bflow->subdir); + bflow->subdir, false); } #if CONFIG_IS_ENABLED(BOOTSTD_FULL) diff --git a/boot/ext_pxe_common.c b/boot/ext_pxe_common.c index 2037099708a..67d7b68d310 100644 --- a/boot/ext_pxe_common.c +++ b/boot/ext_pxe_common.c @@ -95,7 +95,7 @@ static int extlinux_setup(struct udevice *dev, struct bootflow *bflow, int extlinux_boot(struct udevice *dev, struct bootflow *bflow, pxe_getfile_func getfile, bool allow_abs_path, - const char *bootfile) + const char *bootfile, bool restart) { struct extlinux_plat *plat = dev_get_plat(dev); ulong addr; @@ -109,6 +109,7 @@ int extlinux_boot(struct udevice *dev, struct bootflow *bflow, bootfile, &plat->ctx); if (ret) return log_msg_ret("elb", ret); + plat->ctx.restart = restart; addr = map_to_sysmem(bflow->buf); ret = pxe_process(&plat->ctx, addr, false); } diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 8ff06ce6c52..f524b0a1dd3 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -570,8 +570,13 @@ static int label_run_boot(struct pxe_context *ctx, struct pxe_label *label, if (IS_ENABLED(CONFIG_CMD_BOOTM) && (fmt == IMAGE_FORMAT_FIT || fmt == IMAGE_FORMAT_LEGACY)) { + int states; + + states = ctx->restart ? BOOTM_STATE_RESTART : BOOTM_STATE_START; log_debug("using bootm\n"); - ret = bootm_run(&bmi); + ret = boot_run(&bmi, "ext", states | BOOTM_STATE_FINDOS | + BOOTM_STATE_PRE_LOAD | BOOTM_STATE_FINDOTHER | + BOOTM_STATE_LOADOS); /* Try booting an AArch64 Linux kernel image */ } else if (IS_ENABLED(CONFIG_CMD_BOOTI) && fmt == IMAGE_FORMAT_BOOTI) { log_debug("using booti\n"); diff --git a/include/extlinux.h b/include/extlinux.h index f64597ebdef..4b5a8f316a8 100644 --- a/include/extlinux.h +++ b/include/extlinux.h @@ -58,11 +58,13 @@ int extlinux_set_property(struct udevice *dev, const char *property, * @allow_abs_path: true to allow absolute paths * @bootfile: Bootfile whose directory loaded files are relative to, NULL if * none + * @restart: true to use BOOTM_STATE_RESTART instead of BOOTM_STATE_START (only + * supported with FIT / bootm) * Return: 0 if OK, -ve error code on failure */ int extlinux_boot(struct udevice *dev, struct bootflow *bflow, pxe_getfile_func getfile, bool allow_abs_path, - const char *bootfile); + const char *bootfile, bool restart); /** * extlinux_read_all() - read all files for a bootflow diff --git a/include/pxe_utils.h b/include/pxe_utils.h index a63d0123059..28f83228167 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -121,6 +121,8 @@ typedef int (*pxe_getfile_func)(struct pxe_context *ctx, const char *file_path, * @initrd_addr_str) * @initrd_str: initrd string to process (only used if @initrd_addr_str) * @conf_fdt: string containing the FDT address + * @restart: true to use BOOTM_STATE_RESTART instead of BOOTM_STATE_START (only + * supported with FIT / bootm) */ struct pxe_context { /** @@ -152,6 +154,7 @@ struct pxe_context { char *initrd_filesize; char *initrd_str; char *conf_fdt; + bool restart; }; /** -- 2.43.0

From: Simon Glass <sjg@chromium.org> Some members are not commented. Add the missing comments and tidy up the style a little. Signed-off-by: Simon Glass <sjg@chromium.org> --- include/pxe_utils.h | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/include/pxe_utils.h b/include/pxe_utils.h index 28f83228167..58301a78b03 100644 --- a/include/pxe_utils.h +++ b/include/pxe_utils.h @@ -23,20 +23,28 @@ * take a 'include file getter' function. */ -/* - * Describes a single label given in a pxe file. - * - * Create these with the 'label_create' function given below. - * - * name - the name of the menu as given on the 'menu label' line. - * kernel_label - the kernel label, including FIT config if present. - * kernel - the path to the kernel file to use for this label. - * append - kernel command line to use when booting this label - * initrd - path to the initrd to use for this label. - * attempted - 0 if we haven't tried to boot this label, 1 if we have. - * localboot - 1 if this label specified 'localboot', 0 otherwise. - * kaslrseed - 1 if generate kaslrseed from hw_rng - * list - lets these form a list, which a pxe_menu struct will hold. +/** + * struct pxe_label - describes a single label in a pxe file + * + * Create these with label_create() + * + * @num: String version of the ID number of the label, e.g. "1" + * @name: name of the 'label' line + * @menu: name of the menu as given on the 'menu label' line + * @kernel_label: the kernel label, including FIT config if present + * @kernel: the path to the kernel file to use for this label + * @config: FIT configuration to use (after '#'), or NULL if none + * @append: kernel command line to use when booting this label + * @initrd: path to the initrd to use for this label. + * @fdt: path to FDT to use + * @fdtdir: path to FDT directory to use + * @fdtoverlays: space-separated list of paths of FDT overlays to apply + * @ipappend: flags for appending IP address (0x1) and MAC address (0x3) + * @attempted: 0 if we haven't tried to boot this label, 1 if we have + * @localboot: 1 if this label specified 'localboot', 0 otherwise + * @localboot_val: value of the localboot parameter + * @kaslrseed: 1 to generate kaslrseed from hw_rng + * @list: lets these form a list, which a pxe_menu struct will hold. */ struct pxe_label { char num[4]; -- 2.43.0

From: Simon Glass <sjg@chromium.org> When booting an OS it is useful to be able to read devicetrees provided by the OEM, from a separate FIT to the OS. Add a new method which supports this, along with the usual A/B/recovery flows, using a state file on the boot device. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/Kconfig | 12 ++ boot/bootflow.c | 2 + boot/vbe_abrec.c | 2 +- boot/vbe_abrec.h | 5 + boot/vbe_abrec_fw.c | 1 + boot/vbe_abrec_os.c | 293 ++++++++++++++++++++++++++++++++++++++++++++ include/bootflow.h | 8 +- include/vbe.h | 13 ++ 8 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 boot/vbe_abrec_os.c diff --git a/boot/Kconfig b/boot/Kconfig index 45fe5a07696..6f42cd7ec23 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -818,6 +818,18 @@ config BOOTMETH_VBE_ABREC verification step, a recovery image is booted. This method will eventually provide rollback protection as well. +config BOOTMETH_VBE_ABREC_OS + bool "Bootdev support for VBE 'a/b/recovery' method for the OS" + imply SPL_CRC8 + imply VPL_CRC8 + help + Enables support for VBE 'abrec' boot. This allows updating one of an + A or B Operating System in boot media such as MMC. The new OS is + tried and if it boots, it is copied to the other image, so that both + A and B have the same version. If neither firmware image passes the + verification step, a recovery image is booted. This method will + eventually provide rollback protection as well. + if BOOTMETH_VBE_SIMPLE config BOOTMETH_VBE_SIMPLE_OS diff --git a/boot/bootflow.c b/boot/bootflow.c index 2e77875c52f..c088300ea96 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -28,6 +28,8 @@ static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = { "logo", "efi", "cmdline", + "vbe-state", + "vbe-oem-fit", }; /** diff --git a/boot/vbe_abrec.c b/boot/vbe_abrec.c index 3b5f75421ff..4736e6147d2 100644 --- a/boot/vbe_abrec.c +++ b/boot/vbe_abrec.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Verified Boot for Embedded (VBE) 'abrec' method + * Verified Boot for Embedded (VBE) 'abrec' method (for firmware) * * Copyright 2024 Google LLC * Written by Simon Glass <sjg@chromium.org> diff --git a/boot/vbe_abrec.h b/boot/vbe_abrec.h index 63c73297351..590ad3cfacb 100644 --- a/boot/vbe_abrec.h +++ b/boot/vbe_abrec.h @@ -17,6 +17,9 @@ struct bootflow; struct udevice; +#define VBE_STATE_FNAME "vbe-state" +#define VBE_OEM_FIT_FNAME "oem.fit" + /** * struct abrec_priv - information read from the device tree * @@ -30,6 +33,7 @@ struct udevice; * @version_offset: Offset from from area_start of the VBE version info * @version_size: Size of the version info * @storage: Storage device to use, in the form <uclass><devnum>, e.g. "mmc1" + * @oem_devicetree: true if we should read an OEM devicetree */ struct abrec_priv { u32 area_start; @@ -40,6 +44,7 @@ struct abrec_priv { u32 version_offset; u32 version_size; const char *storage; + bool oem_devicetree; }; /** struct abrec_state - state information read from media diff --git a/boot/vbe_abrec_fw.c b/boot/vbe_abrec_fw.c index d52bd9ddff0..2a11abc967a 100644 --- a/boot/vbe_abrec_fw.c +++ b/boot/vbe_abrec_fw.c @@ -150,6 +150,7 @@ static int abrec_run_vpl(struct udevice *blk, struct spl_image_info *image, ub_size = binman_sym(ulong, u_boot_b, size); break; case VBEP_RECOVERY: + case VBEP_COUNT: offset = binman_sym(ulong, spl_recovery, image_pos); size = binman_sym(ulong, spl_recovery, size); ub_offset = binman_sym(ulong, u_boot_recovery, image_pos); diff --git a/boot/vbe_abrec_os.c b/boot/vbe_abrec_os.c new file mode 100644 index 00000000000..53eb3d79aca --- /dev/null +++ b/boot/vbe_abrec_os.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Verified Boot for Embedded (VBE) 'abrec' method (for OS) + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include <bootm.h> +#include <bootmeth.h> +#include <dm.h> +#include <extlinux.h> +#include <fs_legacy.h> +#include <memalign.h> +#include <mmc.h> +#include <dm/ofnode.h> +#include "vbe_abrec.h" + +static const char *const pick_names[VBEP_COUNT] = {"a", "b", "recovery"}; + +static int vbe_abrec_read_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_check_blk(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static enum vbe_pick_t find_pick(const char *name) +{ + int i; + + for (i = 0; i < VBEP_COUNT; i++) + if (!strcmp(pick_names[i], name)) + return i; + + return -1; +} + +static int vbe_abrec_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, enum bootflow_img_t type, + ulong *sizep) +{ + struct extlinux_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + + /* Allow up to 1GB */ + *sizep = 1 << 30; + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + type, sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int decode_state(struct vbe_bflow_priv *priv, oftree tree) +{ + ofnode root, next, osn; + const char *slot; + int ret; + + if (!oftree_valid(tree)) + return log_msg_ret("vtr", -ENOENT); + + root = oftree_root(tree); + if (strcmp("vbe,abrec-state", ofnode_read_string(root, "compatible"))) + return log_msg_ret("vco", -ENOENT); + + osn = ofnode_find_subnode(root, "os"); + if (!oftree_valid(tree)) + return log_msg_ret("vos", -ENOENT); + + next = ofnode_find_subnode(osn, "next-boot"); + if (!oftree_valid(tree)) + return log_msg_ret("vnn", -ENOENT); + + slot = ofnode_read_string(next, "slot"); + if (!slot) + return log_msg_ret("vnn", -ENOENT); + + ret = find_pick(slot); + if (ret == -1) + return log_msg_ret("vsl", -EINVAL); + priv->pick_slot = ret; + + return 0; +} + +static int vbe_abrec_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct abrec_priv *priv = dev_get_priv(dev); + struct vbe_bflow_priv *bfpriv; + struct blk_desc *desc; + struct abuf buf, oem; + char subdir[20]; + oftree tree; + int ret; + + // log_debug("part %d\n", bflow->part); + /* we expect a boot partition; for now we assume it is partition 2 */ + if (bflow->part != 2) { + // log_debug("wrong partition\n"); + return -ENOENT; + } + bflow->state = BOOTFLOWST_FS; + + desc = dev_get_uclass_plat(bflow->blk); + + bflow->subdir = strdup(""); + if (!bflow->subdir) + return log_msg_ret("bss", -ENOMEM); + + ret = bootmeth_alloc_other(bflow, VBE_STATE_FNAME, BFI_VBE_STATE, &buf); + if (ret) + return log_msg_ret("bst", ret); + if (!buf.size) { + ret = log_msg_ret("bst", -ENOENT); + goto err_buf; + } + + bflow->state = BOOTFLOWST_FILE; + + bfpriv = calloc(1, sizeof(struct vbe_bflow_priv)); + if (!bfpriv) { + ret = log_msg_ret("val", -ENOMEM); + goto err_priv; + } + bflow->bootmeth_priv = bfpriv; + + tree = oftree_from_fdt(buf.data); + if (!oftree_valid(tree)) { + ret = log_msg_ret("vtr", -ENOENT); + goto err_tree; + } + + ret = decode_state(bfpriv, tree); + oftree_dispose(tree); + if (ret) { + ret = log_msg_ret("vds", ret); + goto err_decode; + } + + printf("VBE: Picked slot %s\n", pick_names[bfpriv->pick_slot]); + ret = bootmeth_setup_fs(bflow, desc); + if (ret) { + ret = log_msg_ret("vsf", ret); + goto err_fs; + } + + snprintf(subdir, sizeof(subdir), "%s/", pick_names[bfpriv->pick_slot]); + free(bflow->subdir); + bflow->subdir = strdup(subdir); + if (!bflow->subdir) { + ret = log_msg_ret("vsd", -ENOMEM); + if (ret) + goto err_fs; + } + + ret = bootmeth_try_file(bflow, desc, subdir, EXTLINUX_FNAME); + if (ret) { + log_debug("part %d: ret %d\n", bflow->part, ret); + ret = log_msg_ret("vtr", ret); + goto err_file; + } + + ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN, + BFI_EXTLINUX_CFG); + if (ret) { + ret = log_msg_ret("vaf", ret); + goto err_file; + } + + if (priv->oem_devicetree) { + /* Locate the OEM FIT in the same slot, if it exists */ + ret = bootmeth_alloc_other(bflow, VBE_OEM_FIT_FNAME, BFI_VBE_OEM_FIT, + &oem); + if (ret == -ENOMEM) { + ret = log_msg_ret("bst", ret); + goto err_file; + } + } + + bflow->state = BOOTFLOWST_READY; + + return 0; + +err_file: +err_fs: +err_decode: +err_tree: + free(priv); +err_priv: +err_buf: + abuf_uninit(&buf); + + return ret; +} + +static int vbe_abrec_boot(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootflow_img *img; + int ret; + + /* load the devicetree first */ + img = bootflow_img_find(bflow, BFI_VBE_OEM_FIT); + if (img) { + struct bootm_info bmi; + char addr_str[30]; + int states; + + printf("Loading OEM devicetree from FIT\n"); + bootm_init(&bmi); + snprintf(addr_str, sizeof(addr_str), "%lx", img->addr); + bmi.addr_img = addr_str; + bmi.cmd_name = "vbe_os"; + states = BOOTM_STATE_START | BOOTM_STATE_FINDOS | + BOOTM_STATE_PRE_LOAD | BOOTM_STATE_FINDOTHER | + BOOTM_STATE_LOADOS; + ret = bootm_run_states(&bmi, states); + + /* we should get -ENOPKG, indicating that there was no OS */ + if (ret != -ENOPKG) + return log_msg_ret("vab", ret ? ret : -EFAULT); + } + + printf("Loading OS FIT%s\n", img ? " keeping existing FDT" : ""); + + return extlinux_boot(dev, bflow, vbe_abrec_getfile, true, bflow->fname, + img); +} + +#if CONFIG_IS_ENABLED(BOOTSTD_FULL) +static int vbe_abrec_read_all(struct udevice *dev, struct bootflow *bflow) +{ + return extlinux_read_all(dev, bflow, vbe_abrec_getfile, true, + bflow->fname); +} +#endif + +static struct bootmeth_ops bootmeth_vbe_abrec_os_ops = { + .check = vbe_abrec_read_check, + .read_file = bootmeth_common_read_file, + .read_bootflow = vbe_abrec_read_bootflow, + .boot = vbe_abrec_boot, +#if CONFIG_IS_ENABLED(BOOTSTD_FULL) + .read_all = vbe_abrec_read_all, +#endif +}; + +static int bootmeth_vbe_abrec_os_probe(struct udevice *dev) +{ + struct abrec_priv *priv = dev_get_priv(dev); + + priv->oem_devicetree = true; + + return 0; +} + +static int bootmeth_vbe_abrec_os_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = "VBE A/B/recovery for OS"; + + return 0; +} + +#if CONFIG_IS_ENABLED(OF_REAL) +static const struct udevice_id vbe_abrec_os_ids[] = { + { .compatible = "vbe,abrec-os" }, + { } +}; +#endif + +U_BOOT_DRIVER(vbe_abrec_os) = { + .name = "vbe_abrec_os", + .id = UCLASS_BOOTMETH, + .of_match = of_match_ptr(vbe_abrec_os_ids), + .ops = &bootmeth_vbe_abrec_os_ops, + .bind = bootmeth_vbe_abrec_os_bind, + .probe = bootmeth_vbe_abrec_os_probe, + .priv_auto = sizeof(struct abrec_priv), + .plat_auto = sizeof(struct extlinux_plat) +}; diff --git a/include/bootflow.h b/include/bootflow.h index d2ffa3c5aad..ec5baff7e01 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -120,12 +120,16 @@ struct bootflow { /** * bootflow_img_t: Supported image types * - * This uses image_type_t for most types, but extends it + * This uses image_type_t for most types, but extends it. See the names in + * bootflow_img[] * * @BFI_EXTLINUX_CFG: extlinux configuration-file * @BFI_LOGO: logo image * @BFI_EFI: EFI PE image * @BFI_CMDLINE: OS command-line string + * @BFI_VBE_STATE: Verified Boot for Embedded (VBE) state + * @BFI_VBE_OEM_FIT: Verified Boot for Embedded (VBE) OEM FIT containing + * devicetrees */ enum bootflow_img_t { BFI_FIRST = IH_TYPE_COUNT, @@ -133,6 +137,8 @@ enum bootflow_img_t { BFI_LOGO, BFI_EFI, BFI_CMDLINE, + BFI_VBE_STATE, + BFI_VBE_OEM_FIT, BFI_COUNT, }; diff --git a/include/vbe.h b/include/vbe.h index 84a996cdf7f..2f5e8b78471 100644 --- a/include/vbe.h +++ b/include/vbe.h @@ -31,6 +31,8 @@ enum vbe_phase_t { /** * enum vbe_pick_t - indicates which firmware is picked * + * The names are in pick_names[] + * * @VBEFT_A: Firmware A * @VBEFT_B: Firmware B * @VBEFT_RECOVERY: Recovery firmware @@ -39,6 +41,8 @@ enum vbe_pick_t { VBEP_A, VBEP_B, VBEP_RECOVERY, + + VBEP_COUNT, }; /** @@ -71,6 +75,15 @@ static inline enum vbe_phase_t vbe_phase(void) return VBE_PHASE_OS; } +/** + * struct vbe_bflow_priv - Information attached to the VBE bootflow + * + * @pick: Boot path to pick for the current boot + */ +struct vbe_bflow_priv { + enum vbe_pick_t pick_slot; +}; + /** * vbe_list() - List the VBE bootmeths * -- 2.43.0

From: Simon Glass <sjg@chromium.org> Create two new images (vbe0 and vbe1) containing the two types of VBE-OS setups: with and without an OEM FIT. Put this in a new img/ subdirectory since the test_ut.py file is getting quite large. Most of the test is in C, with just the image-setup done in Python. Enable the test for non-SPL sandbox builds. Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/dts/test.dts | 11 +++ boot/Kconfig | 1 + test/boot/Makefile | 1 + test/boot/bootflow.c | 75 ++++++++++++----- test/boot/bootmeth.c | 19 +++-- test/boot/vbe_abrec_os.c | 138 +++++++++++++++++++++++++++++++ test/py/test.py | 4 + test/py/tests/img/vbe.py | 167 ++++++++++++++++++++++++++++++++++++++ test/py/tests/test_ut.py | 3 +- test/py/tests/test_vbe.py | 22 +++++ 10 files changed, 412 insertions(+), 29 deletions(-) create mode 100644 test/boot/vbe_abrec_os.c create mode 100644 test/py/tests/img/vbe.py diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index b62531e9166..1783a21894a 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -128,6 +128,10 @@ compatible = "u-boot,distro-efi"; }; + vbe { + compatible = "vbe,abrec-os"; + }; + theme { font-size = <30>; menu-inset = <3>; @@ -1176,6 +1180,13 @@ filename = "mmc9.img"; }; + /* This is used for VBE ABrec tests */ + mmc10 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc10.img"; + }; + pch { compatible = "sandbox,pch"; }; diff --git a/boot/Kconfig b/boot/Kconfig index 6f42cd7ec23..ddb5b26ec15 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -822,6 +822,7 @@ config BOOTMETH_VBE_ABREC_OS bool "Bootdev support for VBE 'a/b/recovery' method for the OS" imply SPL_CRC8 imply VPL_CRC8 + default y if SANDBOX && !SPL help Enables support for VBE 'abrec' boot. This allows updating one of an A or B Operating System in boot media such as MMC. The new OS is diff --git a/test/boot/Makefile b/test/boot/Makefile index 00223b44fe0..e8b423b0c43 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -19,5 +19,6 @@ ifdef CONFIG_OF_LIVE obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o endif obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o +obj-$(CONFIG_BOOTMETH_VBE_ABREC_OS) += vbe_abrec_os.o obj-$(CONFIG_UPL) += upl.o diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index 697037e7b69..620af312700 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -184,6 +184,7 @@ static int bootflow_cmd_scan_e(struct unit_test_state *uts) { ut_assertok(bootstd_test_drop_bootdev_order(uts)); + ut_assertok(run_command("bootmeth order ", 0)); ut_assertok(run_command("bootflow scan -aleGH", 0)); ut_assert_nextline("Scanning for bootflows in all bootdevs"); ut_assert_nextline("Seq Method State Uclass Part Name Filename"); @@ -193,24 +194,28 @@ static int bootflow_cmd_scan_e(struct unit_test_state *uts) ut_assert_nextline(" ** No partition found, err=-93: Protocol not supported"); ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole "); ut_assert_nextline(" ** No partition found, err=-93: Protocol not supported"); + ut_assert_nextline(" 2 vbe media mmc 0 mmc2.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-93: Protocol not supported"); ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); - ut_assert_nextline(" 2 extlinux media mmc 0 mmc1.bootdev.whole "); + ut_assert_nextline(" 3 extlinux media mmc 0 mmc1.bootdev.whole "); + ut_assert_nextline(" ** No partition found, err=-2: No such file or directory"); + ut_assert_nextline(" 4 efi media mmc 0 mmc1.bootdev.whole "); ut_assert_nextline(" ** No partition found, err=-2: No such file or directory"); - ut_assert_nextline(" 3 efi media mmc 0 mmc1.bootdev.whole "); + ut_assert_nextline(" 5 vbe media mmc 0 mmc1.bootdev.whole "); ut_assert_nextline(" ** No partition found, err=-2: No such file or directory"); - ut_assert_nextline(" 4 extlinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextline(" 6 extlinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); ut_assert_nextline( - " 5 efi fs mmc 1 mmc1.bootdev.part_1 /EFI/BOOT/%s", + " 7 efi fs mmc 1 mmc1.bootdev.part_1 /EFI/BOOT/%s", efi_get_basename()); ut_assert_skip_to_line("Scanning bootdev 'mmc0.bootdev':"); ut_assert_skip_to_line( - " 3f efi media mmc 0 mmc0.bootdev.whole "); + " 5f vbe media mmc 0 mmc0.bootdev.whole "); ut_assert_nextline(" ** No partition found, err=-93: Protocol not supported"); ut_assert_nextline("No more bootdevs"); ut_assert_nextlinen("---"); - ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_nextline("(96 bootflows, 1 valid)"); ut_assert_console_end(); ut_assertok(run_command("bootflow list", 0)); @@ -219,11 +224,9 @@ static int bootflow_cmd_scan_e(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 0 extlinux media mmc 0 mmc2.bootdev.whole "); ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole "); - ut_assert_skip_to_line( - " 4 extlinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); - ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole "); + ut_assert_skip_to_line(" 5f vbe media mmc 0 mmc0.bootdev.whole "); ut_assert_nextlinen("---"); - ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_nextline("(96 bootflows, 1 valid)"); ut_assert_console_end(); return 0; @@ -304,7 +307,7 @@ static int bootflow_iter(struct unit_test_state *uts) bootflow_scan_first(NULL, NULL, &iter, BOOTFLOWIF_ALL | BOOTFLOWIF_SKIP_GLOBAL | BOOTFLOWIF_ONLY_BOOTABLE, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(0, iter.cur_method); ut_asserteq(0, iter.part); ut_asserteq(0, iter.max_part); @@ -320,7 +323,7 @@ static int bootflow_iter(struct unit_test_state *uts) ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); ut_asserteq(-EPROTONOSUPPORT, bootflow_scan_next(&iter, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(1, iter.cur_method); ut_asserteq(0, iter.part); ut_asserteq(0, iter.max_part); @@ -329,9 +332,20 @@ static int bootflow_iter(struct unit_test_state *uts) ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); bootflow_free(&bflow); + /* now the VBE boothmeth */ + ut_asserteq(-EPROTONOSUPPORT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(3, iter.num_methods); + ut_asserteq(2, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0, iter.max_part); + ut_asserteq_str("vbe", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + /* The next device is mmc1.bootdev - at first we use the whole device */ ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(0, iter.cur_method); ut_asserteq(0, iter.part); ut_asserteq(0x1e, iter.max_part); @@ -341,7 +355,7 @@ static int bootflow_iter(struct unit_test_state *uts) bootflow_free(&bflow); ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(1, iter.cur_method); ut_asserteq(0, iter.part); ut_asserteq(0x1e, iter.max_part); @@ -350,9 +364,20 @@ static int bootflow_iter(struct unit_test_state *uts) ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); bootflow_free(&bflow); - /* Then more to partition 1 where we find something */ + /* now the VBE boothmeth */ + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(3, iter.num_methods); + ut_asserteq(2, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("vbe", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + /* Then move to partition 1 where we find something */ ut_assertok(bootflow_scan_next(&iter, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(0, iter.cur_method); ut_asserteq(1, iter.part); ut_asserteq(0x1e, iter.max_part); @@ -362,7 +387,7 @@ static int bootflow_iter(struct unit_test_state *uts) bootflow_free(&bflow); ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(1, iter.cur_method); ut_asserteq(1, iter.part); ut_asserteq(0x1e, iter.max_part); @@ -371,9 +396,19 @@ static int bootflow_iter(struct unit_test_state *uts) ut_asserteq(BOOTFLOWST_FS, bflow.state); bootflow_free(&bflow); + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(3, iter.num_methods); + ut_asserteq(2, iter.cur_method); + ut_asserteq(1, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("vbe", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_FS, bflow.state); + bootflow_free(&bflow); + /* Then more to partition 2 which exists but is not bootable */ ut_asserteq(-EINVAL, bootflow_scan_next(&iter, &bflow)); - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); ut_asserteq(0, iter.cur_method); ut_asserteq(2, iter.part); ut_asserteq(0x1e, iter.max_part); @@ -441,7 +476,7 @@ static int bootflow_iter_disable(struct unit_test_state *uts) /* Try to boot the bootmgr flow, which will fail */ console_record_reset_enable(); ut_assertok(bootflow_scan_first(NULL, NULL, &iter, 0, &bflow)); - ut_asserteq(3, iter.num_methods); + ut_asserteq(4, iter.num_methods); ut_asserteq_str("sandbox", iter.method->name); ut_assertok(inject_response(uts)); ut_asserteq(-ENOTSUPP, bootflow_run_boot(&iter, &bflow)); @@ -450,7 +485,7 @@ static int bootflow_iter_disable(struct unit_test_state *uts) ut_assert_console_end(); /* Check that the sandbox bootmeth has been removed */ - ut_asserteq(2, iter.num_methods); + ut_asserteq(3, iter.num_methods); for (i = 0; i < iter.num_methods; i++) ut_assert(strcmp("sandbox", iter.method_order[i]->name)); diff --git a/test/boot/bootmeth.c b/test/boot/bootmeth.c index 577f259fb37..88fa3200cbc 100644 --- a/test/boot/bootmeth.c +++ b/test/boot/bootmeth.c @@ -20,11 +20,12 @@ static int bootmeth_cmd_list(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 0 0 extlinux Extlinux boot from a block device"); ut_assert_nextline(" 1 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" 2 2 vbe VBE A/B/recovery for OS"); if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) - ut_assert_nextline(" glob 2 firmware0 VBE simple"); + ut_assert_nextline(" glob 3 firmware0 VBE simple"); ut_assert_nextlinen("---"); ut_assert_nextline(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? - "(3 bootmeths)" : "(2 bootmeths)"); + "(4 bootmeths)" : "(3 bootmeths)"); ut_assert_console_end(); return 0; @@ -55,11 +56,12 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 0 0 extlinux Extlinux boot from a block device"); ut_assert_nextline(" - 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" - 2 vbe VBE A/B/recovery for OS"); if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) - ut_assert_nextline(" glob 2 firmware0 VBE simple"); + ut_assert_nextline(" glob 3 firmware0 VBE simple"); ut_assert_nextlinen("---"); ut_assert_nextline(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? - "(3 bootmeths)" : "(2 bootmeths)"); + "(4 bootmeths)" : "(3 bootmeths)"); ut_assert_console_end(); /* Check the -a flag with the reverse order */ @@ -70,11 +72,12 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 1 0 extlinux Extlinux boot from a block device"); ut_assert_nextline(" 0 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" - 2 vbe VBE A/B/recovery for OS"); if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) - ut_assert_nextline(" glob 2 firmware0 VBE simple"); + ut_assert_nextline(" glob 3 firmware0 VBE simple"); ut_assert_nextlinen("---"); ut_assert_nextline(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? - "(3 bootmeths)" : "(2 bootmeths)"); + "(4 bootmeths)" : "(3 bootmeths)"); ut_assert_console_end(); /* Now reset the order to empty, which should show all of them again */ @@ -83,7 +86,7 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_assertnull(env_get("bootmeths")); ut_assertok(run_command("bootmeth list", 0)); ut_assert_skip_to_line(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? - "(3 bootmeths)" : "(2 bootmeths)"); + "(4 bootmeths)" : "(3 bootmeths)"); /* Try reverse order */ ut_assertok(run_command("bootmeth order \"efi extlinux\"", 0)); @@ -115,7 +118,7 @@ static int bootmeth_cmd_order_glob(struct unit_test_state *uts) ut_assert_nextline("Order Seq Name Description"); ut_assert_nextlinen("---"); ut_assert_nextline(" 0 1 efi EFI boot from an .efi file"); - ut_assert_nextline(" glob 2 firmware0 VBE simple"); + ut_assert_nextline(" glob 3 firmware0 VBE simple"); ut_assert_nextlinen("---"); ut_assert_nextline("(2 bootmeths)"); ut_assertnonnull(env_get("bootmeths")); diff --git a/test/boot/vbe_abrec_os.c b/test/boot/vbe_abrec_os.c new file mode 100644 index 00000000000..3ac95d7b4c3 --- /dev/null +++ b/test/boot/vbe_abrec_os.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for VBE A/B boot of OS + * + * Copyright 2025 Simon Glass <simon.glass@canonical.com> + */ + +#include <bootflow.h> +#include <bootstd.h> +#include <dm.h> +#include <mapmem.h> +#include <vbe.h> +#include <test/test.h> +#include <test/ut.h> +#include "bootstd_common.h" +#include "../boot/vbe_abrec.h" + +/** + * check_abrec_norun() - Check operation with or without an OEM FIT + * + * @uts: Unit-test state + * @use_oem: Use an OEM devicetree + */ +static int check_abrec_norun(struct unit_test_state *uts, bool use_oem, + enum vbe_pick_t expect_pick) +{ + static const char *order[] = {"host", NULL}; + const struct bootflow_img *img; + struct bootflow_iter iter; + struct bootstd_priv *std; + struct abrec_priv *abpriv; + struct udevice *bootstd; + const char **old_order; + struct bootflow bflow; + struct vbe_bflow_priv *priv; + struct udevice *dev; + ofnode root; + oftree tree; + int ret; + + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + std = dev_get_priv(bootstd); + old_order = std->bootdev_order; + std->bootdev_order = order; + ut_assertok(env_set("boot_targets", NULL)); + + ut_assertok(uclass_get_device_by_driver(UCLASS_BOOTMETH, + DM_DRIVER_GET(vbe_abrec_os), + &dev)); + abpriv = dev_get_priv(dev); + abpriv->oem_devicetree = use_oem; + + ret = bootflow_scan_first(NULL, NULL, &iter, BOOTFLOWIF_SHOW, &bflow); + std->bootdev_order = old_order; + ut_assertok(ret); + + ut_asserteq_str("host-0.bootdev.part_2", bflow.name); + + /* Check that we got the state OK */ + img = bootflow_img_find(&bflow, BFI_VBE_STATE); + ut_assertnonnull(img); + ut_assert(img->addr); + + tree = oftree_from_fdt(map_sysmem(img->addr, 0)); + ut_assert(oftree_valid(tree)); + + root = oftree_root(tree); + ut_asserteq_str("vbe,abrec-state", + ofnode_read_string(root, "compatible")); + + /* check the private data */ + priv = bflow.bootmeth_priv; + ut_assertnonnull(priv); + ut_asserteq(expect_pick, priv->pick_slot); + + if (use_oem) { + /* Check that we got the OEM FIT */ + img = bootflow_img_find(&bflow, BFI_VBE_OEM_FIT); + ut_assertnonnull(img); + ut_assert(img->addr); + ut_assert(img->size > 0); + ut_assertok(fit_check_format(map_sysmem(img->addr, img->size), + img->size)); + } + + /* Select the first kernel from the extlinux menu */ + ut_asserteq(2, console_in_puts("1\n")); + + /* + * We expect it to get through to boot although sandbox always returns + * -EFAULT as it cannot actually boot the kernel + */ + ut_asserteq(-EFAULT, bootflow_boot(&bflow)); + + ut_assert_skip_to_line("VBE: Picked slot %s", + priv->pick_slot == VBEP_A ? "a" : "b"); + + if (use_oem) + ut_assert_skip_to_line("Loading OEM devicetree from FIT"); + + if (use_oem) + ut_assert_skip_to_line("Loading OS FIT keeping existing FDT"); + else + ut_assert_skip_to_line("Loading OS FIT"); + + ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux"); + ut_assert_console_end(); + + /* + * check the FDT we booted with: we should have loaded conf-1 as the + * compatible string for sandbox does not match + */ + ut_assertnonnull(working_fdt); + tree = oftree_from_fdt(working_fdt); + root = oftree_root(tree); + + ut_asserteq_str("snow", ofnode_read_string(root, "compatible")); + + return 0; +} + +/* Test without an OEM FIT */ +static int vbe_test_abrec_no_oem_norun(struct unit_test_state *uts) +{ + ut_assertok(check_abrec_norun(uts, false, VBEP_A)); + + return 0; +} +BOOTSTD_TEST(vbe_test_abrec_no_oem_norun, UTF_MANUAL | UTF_CONSOLE); + +/* Test with an OEM FIT */ +static int vbe_test_abrec_oem_norun(struct unit_test_state *uts) +{ + ut_assertok(check_abrec_norun(uts, true, VBEP_B)); + + return 0; +} +BOOTSTD_TEST(vbe_test_abrec_oem_norun, UTF_MANUAL | UTF_CONSOLE); diff --git a/test/py/test.py b/test/py/test.py index 7c477903d6b..ab47282db35 100755 --- a/test/py/test.py +++ b/test/py/test.py @@ -12,6 +12,10 @@ import os.path import sys import pytest +# Bring in the U-Boot libraries +our_path = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(our_path, '../../tools')) + if __name__ == '__main__': # argv; py.test test_directory_name user-supplied-arguments args = [os.path.dirname(__file__) + '/tests'] diff --git a/test/py/tests/img/vbe.py b/test/py/tests/img/vbe.py new file mode 100644 index 00000000000..1e4c7df68aa --- /dev/null +++ b/test/py/tests/img/vbe.py @@ -0,0 +1,167 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2025 Simon Glass <sjg@chromium.org> +# Author: Simon Glass <sjg@chromium.org> + +"""Create test disk-images for VBE""" + +import gzip +import os +import shutil +import tempfile + +import utils +from fs_helper import DiskHelper, FsHelper +from dtoc import fdt_util +from u_boot_pylib import tools + + +def dtb_for_compatible(ubman, kernpath, version, model): + """Create a fake devicetree binary for a given model name + + Args: + ubman (ConsoleBase): U-Boot fixture + kernpath (str): Path to place the created DTB + version (str): Version string to use with the dtb + model (str): Model name + + Return: + str: Filename of devicetree file written + """ + dtb_file = os.path.join(kernpath, f'dtb-{version}-{model}') + data = f'/dts-v1/; / {{ compatible = "{model}"; version = "{version}"; }};' + utils.run_and_log(ubman, f'dtc -o {dtb_file}', stdin=data.encode('utf-8')) + return dtb_file + + +def create_extlinux(ubman, kernpath, dirpath, slot, version, has_oem): + """Create a fake extlinux image + + Args: + ubman (ConsoleBase): U-Boot fixture + kernpath (str): Directory path for the kernel + dirpath (str): Directory path to write to (created by this function) + slot (str): Slot name (A, B or recovery) + version (str): Linux version to use, e.g. '6.14.0-24' + has_oem (bool): true to create an OEM FIT + """ + ext_path = os.path.join(dirpath, 'extlinux') + if not os.path.exists(dirpath): + os.mkdir(dirpath) + if not os.path.exists(ext_path): + os.mkdir(ext_path) + dtb_info = ' (no-dtb)' if has_oem else '' + with open(os.path.join(ext_path, 'extlinux.conf'), 'w', + encoding='ascii') as outf: + print(f'''## /boot/extlinux/extlinux.conf +## +## IMPORTANT WARNING +## +## The configuration of this file is generated automatically. +## Do not edit this file manually, use: u-boot-update + +default l0 +menu title Ubuntu Menu ({slot}) +prompt 1 +timeout 50 + + +label l0 + menu label Ubuntu 24.04.2 LTS {version}{dtb_info} + + fit /ubuntu-{version}.fit + append root=/dev/disk/by-id/dm-uuid-LVM-lNvIY7Drx7RGUJpQZGPr2axs2ueSe57B7p7jtyJGIUnMJMHCSPVphCu9IFivkJhK ro earlycon +''', file=outf) + + # True to add devicetrees and multiple configurations + add_dts = not has_oem + + # Create a FIT containing kernel, initrd and devicetree + with tempfile.TemporaryDirectory(suffix='vbe') as tmp: + kern = os.path.join(tmp, 'kern') + tools.write_file(kern, gzip.compress(f'vmlinux-{slot}'.encode('utf-8'))) + mkimage = ubman.config.build_dir + '/tools/mkimage' + + initrd = os.path.join(tmp, f'initrd.img-{version}') + tools.write_file(initrd, f'initrd {version}', binary=False) + + snow = dtb_for_compatible(ubman, tmp, version, 'snow') + kevin = dtb_for_compatible(ubman, tmp, version, 'kevin') + + fit = os.path.join(kernpath, f'ubuntu-{version}.fit') + cmd = f'{mkimage} -f auto -T kernel -A sandbox -O linux ' + dts = f'-b {snow} -b {kevin}' if add_dts else '' + utils.run_and_log(ubman, cmd + f'-d {kern} -i {initrd} {dts} {fit}') + + # Create a FIT containing OEM devicetrees + if has_oem: + oem_snow = dtb_for_compatible(ubman, tmp, 'oem', 'snow') + oem_kevin = dtb_for_compatible(ubman, tmp, 'oem', 'kevin') + + fit = os.path.join(dirpath, 'oem.fit') + utils.run_and_log( + ubman, + f'{mkimage} -f auto -A sandbox -O linux -T flat_dt --load-only' + f' -b {oem_snow} -b {oem_kevin} {fit}') + +def setup_vbe_image(ubman): + """Create three partitions (fat, boot and root) for a VBE boot flow + + The intent is to load either one or two FITs. + + Two separate images are created: + + - vbe0 has no OEM FIT; OS FIT contains the devicetrees + - vbe1 has an OEM FIT containing the devicetrees; OS FIT does not + """ + for seq in range(2): + with (DiskHelper(ubman.config, seq, 'vbe') as img, + FsHelper(ubman.config, 'fat', 1, 'efi') as vfat, + FsHelper(ubman.config, 'ext4', 1, f'boot{seq}') as boot, + FsHelper(ubman.config, 'ext4', 17, 'root') as root): + with open(os.path.join(vfat.srcdir, 'u-boot.efi'), 'wb') as outf: + outf.write(b'fake binary\n') + vfat.mk_fs() + img.add_fs(vfat, DiskHelper.VFAT, bootable=True) + + has_oem = bool(seq) + + dir_a = os.path.join(boot.srcdir, 'a') + vera = '6.14.0-24-generic' + create_extlinux(ubman, boot.srcdir, dir_a, 'A', vera, has_oem) + + dir_b = os.path.join(boot.srcdir, 'b') + verb = '6.8.0-1028-intel' + create_extlinux(ubman, boot.srcdir, dir_b, 'B', verb, has_oem) + + boot_slot = 'b' if seq else 'a' + fname = os.path.join(ubman.config.result_dir, 'vbe-state.dts') + tools.write_file(fname, b'''// VBE state file + +/dts-v1/; + +/ { + compatible = "vbe,abrec-state"; + + os { + compatible = "vbe,os-state"; + + next-boot { + slot = "boot_slot"; + }; + }; +}; +'''.replace(b'boot_slot', boot_slot.encode('utf-8'))) + + dtb = fdt_util.EnsureCompiled(fname, ubman.config.result_dir) + print('dtb', dtb) + shutil.copy(dtb, f'{boot.srcdir}/vbe-state') + + boot.mk_fs() + img.add_fs(boot, DiskHelper.EXT4) + + root.mk_fs() + img.add_fs(root, DiskHelper.EXT4) + + img.create() + img.remove_img = False diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 87c4231a86d..afd7e29e404 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -15,9 +15,9 @@ import pytest import utils # pylint: disable=E0611 -from tests import fs_helper from fs_helper import DiskHelper, FsHelper from test_android import test_abootimg +from img.vbe import setup_vbe_image def mkdir_cond(dirname): """Create a directory if it doesn't already exist @@ -642,6 +642,7 @@ def test_ut_dm_init_bootstd(ubman): setup_efi_image(ubman) setup_ubuntu_image(ubman, 3, 'flash') setup_localboot_image(ubman) + setup_vbe_image(ubman) # Restart so that the new mmc1.img is picked up ubman.restart_uboot() diff --git a/test/py/tests/test_vbe.py b/test/py/tests/test_vbe.py index 7a9a3141f07..e5b489bc819 100644 --- a/test/py/tests/test_vbe.py +++ b/test/py/tests/test_vbe.py @@ -3,6 +3,8 @@ # # Test addition of VBE +import os + import pytest import fit_util @@ -118,3 +120,23 @@ def test_vbe_os_request(ubman): output = ubman.run_command_list(cmd.splitlines()) assert 'failures: 0' in output[-1] + +@pytest.mark.boardspec('sandbox') +def test_vbe_extlinux_fit_no_oem(ubman): + """Test reading a FIT from an extlinux.conf file""" + fname = os.path.join(ubman.config.persistent_data_dir, 'vbe0.img') + ubman.run_command(f'host bind 0 {fname}') + + ubman.run_command('ut -f bootstd vbe_test_abrec_no_oem_norun') + result = ubman.run_command('echo $?') + assert '0' == result + +@pytest.mark.boardspec('sandbox') +def test_vbe_extlinux_fit_oem(ubman): + """Test reading an OEM FIT, then an OS FIT from an extlinux.conf file""" + fname = os.path.join(ubman.config.persistent_data_dir, 'vbe1.img') + ubman.run_command(f'host bind 0 {fname}') + + ubman.run_command('ut -f bootstd vbe_test_abrec_oem_norun') + result = ubman.run_command('echo $?') + assert '0' == result -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add some information about this new bootmeth, including how to enable it and how it works. Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/develop/bootstd/index.rst | 1 + doc/develop/bootstd/vbe_os.rst | 61 ++++++++++++++++++++++++++++++++++ doc/develop/vbe.rst | 2 ++ 3 files changed, 64 insertions(+) create mode 100644 doc/develop/bootstd/vbe_os.rst diff --git a/doc/develop/bootstd/index.rst b/doc/develop/bootstd/index.rst index 4c4e26ccdb7..342228064e6 100644 --- a/doc/develop/bootstd/index.rst +++ b/doc/develop/bootstd/index.rst @@ -14,3 +14,4 @@ Standard Boot cros script sandbox + vbe_os diff --git a/doc/develop/bootstd/vbe_os.rst b/doc/develop/bootstd/vbe_os.rst new file mode 100644 index 00000000000..36c1539dcd8 --- /dev/null +++ b/doc/develop/bootstd/vbe_os.rst @@ -0,0 +1,61 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +VBE OS Bootmeth +=============== + +VBE supports a separate FIT for devicetrees and the OS. This bootmeth detects +the `vbe-state` file on the media and uses this to determine the bootflow to +use: there may be two or three separate FITs, for the A, B and recovery flows. + +In embedded systems it is common to package the devicetrees with the OS in +a FIT. Thus both can be updated together by writing a single file. + +For distros which want to provide broad hardware support, it is sometimes +desirable to package the devicetrees separately from the OS, so they can have +their own OEM-controlled lifecycle. This allows the OS package to be somewhat +smalller than if it had all the devicetrees included. + +In terms of lifecycle, the OEM may update the devicetree FIT as part of firmware +update, or a separate OS package. + +There are challenges with this approach and it is best suited for fairly stable +platforms. Particularly in the early days of Linux supporting a particular +SoC, the devicetree/kernel interface may not be fully stable. It is often more +desirable to transition to using an OEM FIT once stability is achieved. + +It is also possible to put small overlays in the OS FIT, e.g. to enable a +feature which was not working at launch. This results in smaller FITs tha +packaging the entire devicetree for each board. + +The compatible string "vbe,abrec-os" is used for the driver. It is present +if `CONFIG_BOOTMETH_VBE_ABREC_OS` is enabled. + +For more information on VBE, see :doc:`../vbe`. + +Partition format +---------------- + +Boot files should be stored on a separate boot partition, formatted as ext4. + +Each slot (A, B, recovery) has a subdirectory containing an +`extlinux/extlinux.conf` file. This provides information about the OS fit for +each OS version. + +The VBE bootmeth requires a file called `vbe-state` to be present in the root +directory of the boot partition, to indicate which slot to boot. + +The `vbe-state` file is a devicetree blob, with the following schema: + +root node + compatible: "vbe,abrec-state" + +/os node: + compatible: "vbe,os-state" + +/os/next-boot: + Indicates the slot that will be used on the next boot. It has a single + property: + + slot (string) + Indicates the slot that will be used on the next boot; valid values + are "a", "b", and "recovery" diff --git a/doc/develop/vbe.rst b/doc/develop/vbe.rst index cca193c8fd4..d951e4e7772 100644 --- a/doc/develop/vbe.rst +++ b/doc/develop/vbe.rst @@ -21,6 +21,8 @@ For a detailed overview of VBE, see vbe-intro_. A fuller description of bootflows is at vbe-bootflows_ and the firmware-update mechanism is described at vbe-fwupdate_. VBE OS requests are described at vbe-osrequests_. +See also :doc:`bootstd/vbe_os`. + .. _vbe-intro: https://docs.google.com/document/d/e/2PACX-1vQjXLPWMIyVktaTMf8edHZYDrEvMYD_i... .. _vbe-bootflows: https://docs.google.com/document/d/e/2PACX-1vR0OzhuyRJQ8kdeOibS3xB1rVFy3J4M_... .. _vbe-fwupdate: https://docs.google.com/document/d/e/2PACX-1vTnlIL17vVbl6TVoTHWYMED0bme7oHHN... -- 2.43.0
participants (1)
-
Simon Glass