
Add a command to look at VBE methods and their status. Provide a test for all of this as well.
Signed-off-by: Simon Glass sjg@chromium.org ---
arch/sandbox/dts/sandbox.dtsi | 13 ++++ arch/sandbox/dts/test.dts | 15 +++++ cmd/Kconfig | 10 +++ cmd/Makefile | 1 + cmd/vbe.c | 87 +++++++++++++++++++++++++ test/boot/Makefile | 4 ++ test/boot/bootflow.c | 53 ++++++++++------ test/boot/bootmeth.c | 11 ++-- test/boot/vbe_simple.c | 115 ++++++++++++++++++++++++++++++++++ 9 files changed, 285 insertions(+), 24 deletions(-) create mode 100644 cmd/vbe.c create mode 100644 test/boot/vbe_simple.c
diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi index aa22b8765c8..56e6b38bfa7 100644 --- a/arch/sandbox/dts/sandbox.dtsi +++ b/arch/sandbox/dts/sandbox.dtsi @@ -12,6 +12,19 @@
chosen { stdout-path = "/serial"; + + fwupd { + compatible = "simple-bus"; + firmware { + compatible = "fwupd,vbe-simple"; + cur-version = "1.2.3"; + bootloader-version = "2022.01"; + storage = "mmc1"; + area-start = <0x0>; + area-size = <0x1000000>; + skip-offset = <0x8000>; + }; + }; };
audio: audio-codec { diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 0194b9b30ef..2cd5abbcfe2 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1378,6 +1378,21 @@ compatible = "denx,u-boot-fdt-test"; reg = <9 1>; }; + + fwupd { + compatible = "simple-bus"; + firmware0 { + compatible = "fwupd,vbe-simple"; + storage = "mmc1"; + area-start = <0x400>; + area-size = <0x1000>; + skip-offset = <0x200>; + state-offset = <0x400>; + state-size = <0x40>; + version-offset = <0x800>; + version-size = <0x100>; + }; + }; };
translation-test@8000 { diff --git a/cmd/Kconfig b/cmd/Kconfig index d5f842136cf..321a612c54c 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -328,6 +328,16 @@ config BOOTM_RTEMS help Support booting RTEMS images via the bootm command.
+config CMD_VBE + bool "vbe - Verified Boot for Embedded" + depends on BOOTMETH_VBE + default y + help + Provides various subcommands related to VBE, such as listing the + available methods, looking at the state and changing which method + is used to boot. Updating the parameters is not currently + supported. + config BOOTM_VXWORKS bool "Support booting VxWorks OS images" depends on CMD_BOOTM diff --git a/cmd/Makefile b/cmd/Makefile index 5e43a1e022e..6e87522b62e 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -179,6 +179,7 @@ obj-$(CONFIG_CMD_FS_UUID) += fs_uuid.o obj-$(CONFIG_CMD_USB_MASS_STORAGE) += usb_mass_storage.o obj-$(CONFIG_CMD_USB_SDP) += usb_gadget_sdp.o obj-$(CONFIG_CMD_THOR_DOWNLOAD) += thordown.o +obj-$(CONFIG_CMD_VBE) += vbe.o obj-$(CONFIG_CMD_XIMG) += ximg.o obj-$(CONFIG_CMD_YAFFS2) += yaffs2.o obj-$(CONFIG_CMD_SPL) += spl.o diff --git a/cmd/vbe.c b/cmd/vbe.c new file mode 100644 index 00000000000..a5737edc047 --- /dev/null +++ b/cmd/vbe.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Verified Boot for Embedded (VBE) command + * + * Copyright 2022 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#include <common.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <vbe.h> + +static int do_vbe_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + vbe_list(); + + return 0; +} + +static int do_vbe_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + if (argc < 2) { + std->vbe_bootmeth = NULL; + return 0; + } + if (vbe_find_by_any(argv[1], &dev)) + return CMD_RET_FAILURE; + + std->vbe_bootmeth = dev; + + return 0; +} + +static int do_vbe_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + char buf[256]; + int ret, len; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + if (!std->vbe_bootmeth) { + printf("No VBE bootmeth selected\n"); + return CMD_RET_FAILURE; + } + ret = bootmeth_get_state_desc(std->vbe_bootmeth, buf, sizeof(buf)); + if (ret) { + printf("Failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + len = strnlen(buf, sizeof(buf)); + if (len >= sizeof(buf)) { + printf("Buffer overflow\n"); + return CMD_RET_FAILURE; + } + + puts(buf); + if (buf[len] != '\n') + putc('\n'); + + return 0; +} + +#ifdef CONFIG_SYS_LONGHELP +static char vbe_help_text[] = + "list - list VBE bootmeths\n" + "vbe select - select a VBE bootmeth by sequence or name\n" + "vbe info - show information about a VBE bootmeth"; +#endif + +U_BOOT_CMD_WITH_SUBCMDS(vbe, "Verified Boot for Embedded", vbe_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_vbe_list), + U_BOOT_SUBCMD_MKENT(select, 2, 1, do_vbe_select), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_vbe_info)); diff --git a/test/boot/Makefile b/test/boot/Makefile index 1730792b5fa..9e9d5ae21f3 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -3,3 +3,7 @@ # Copyright 2021 Google LLC
obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o + +ifdef CONFIG_OF_LIVE +obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o +endif diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index 1ebb789e97b..ed4cf653d96 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -114,21 +114,23 @@ static int bootflow_cmd_scan_e(struct unit_test_state *uts) ut_assert_nextline(" ** No partition found, err=-93"); ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole <NULL>"); ut_assert_nextline(" ** No partition found, err=-93"); + ut_assert_nextline(" 2 firmware0 media mmc 0 mmc2.bootdev.whole <NULL>"); + ut_assert_nextline(" ** No partition found, err=-93");
ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); - ut_assert_nextline(" 2 syslinux media mmc 0 mmc1.bootdev.whole <NULL>"); + ut_assert_nextline(" 3 syslinux media mmc 0 mmc1.bootdev.whole <NULL>"); ut_assert_nextline(" ** No partition found, err=-2"); - ut_assert_nextline(" 3 efi media mmc 0 mmc1.bootdev.whole <NULL>"); + ut_assert_nextline(" 4 efi media mmc 0 mmc1.bootdev.whole <NULL>"); ut_assert_nextline(" ** No partition found, err=-2"); - ut_assert_nextline(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); - ut_assert_nextline(" 5 efi fs mmc 1 mmc1.bootdev.part_1 efi/boot/bootsbox.efi"); + ut_assert_nextline(" 5 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextline(" 6 efi fs mmc 1 mmc1.bootdev.part_1 efi/boot/bootsbox.efi");
ut_assert_skip_to_line("Scanning bootdev 'mmc0.bootdev':"); - ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole <NULL>"); + ut_assert_skip_to_line(" 5d firmware0 media mmc 0 mmc0.bootdev.whole <NULL>"); ut_assert_nextline(" ** No partition found, err=-93"); ut_assert_nextline("No more bootdevs"); ut_assert_nextlinen("---"); - ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_nextline("(94 bootflows, 1 valid)"); ut_assert_console_end();
ut_assertok(run_command("bootflow list", 0)); @@ -137,10 +139,10 @@ static int bootflow_cmd_scan_e(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole <NULL>"); ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole <NULL>"); - ut_assert_skip_to_line(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); - ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole <NULL>"); + ut_assert_skip_to_line(" 5 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_skip_to_line(" 5d firmware0 media mmc 0 mmc0.bootdev.whole <NULL>"); ut_assert_nextlinen("---"); - ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_nextline("(94 bootflows, 1 valid)"); ut_assert_console_end();
return 0; @@ -216,7 +218,7 @@ static int bootflow_iter(struct unit_test_state *uts) /* The first device is mmc2.bootdev which has no media */ ut_asserteq(-EPROTONOSUPPORT, bootflow_scan_first(&iter, BOOTFLOWF_ALL, &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); @@ -224,7 +226,7 @@ static int bootflow_iter(struct unit_test_state *uts) ut_asserteq(0, bflow.err);
/* - * This shows MEDIA even though there is none, since int + * This shows MEDIA even though there is none, since in * bootdev_find_in_blk() we call part_get_info() which returns * -EPROTONOSUPPORT. Ideally it would return -EEOPNOTSUPP and we would * know. @@ -232,7 +234,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); @@ -241,9 +243,20 @@ static int bootflow_iter(struct unit_test_state *uts) ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); bootflow_free(&bflow);
+ /* Now we have the VBE bootmeth */ + 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("firmware0", 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); @@ -253,7 +266,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); @@ -262,9 +275,9 @@ 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 */ + /* 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); @@ -274,7 +287,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); @@ -285,7 +298,7 @@ static int bootflow_iter(struct unit_test_state *uts)
/* Then more to partition 2 which doesn't exist */ 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(2, iter.part); ut_asserteq(0x1e, iter.max_part); @@ -356,7 +369,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(&iter, 0, &bflow)); - ut_asserteq(3, iter.num_methods); + ut_asserteq(4, iter.num_methods); ut_asserteq_str("sandbox", iter.method->name); ut_asserteq(-ENOTSUPP, bootflow_run_boot(&iter, &bflow));
@@ -364,7 +377,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 5d2e87b1c95..dd87ae0b2a0 100644 --- a/test/boot/bootmeth.c +++ b/test/boot/bootmeth.c @@ -23,8 +23,9 @@ static int bootmeth_cmd_list(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); ut_assert_nextline(" 1 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" 2 2 firmware0 VBE simple"); ut_assert_nextlinen("---"); - ut_assert_nextline("(2 bootmeths)"); + ut_assert_nextline("(3 bootmeths)"); ut_assert_console_end();
return 0; @@ -56,8 +57,9 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); ut_assert_nextline(" - 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" - 2 firmware0 VBE simple"); ut_assert_nextlinen("---"); - ut_assert_nextline("(2 bootmeths)"); + ut_assert_nextline("(3 bootmeths)"); ut_assert_console_end();
/* Check the -a flag with the reverse order */ @@ -68,8 +70,9 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_assert_nextlinen("---"); ut_assert_nextline(" 1 0 syslinux Syslinux boot from a block device"); ut_assert_nextline(" 0 1 efi EFI boot from an .efi file"); + ut_assert_nextline(" - 2 firmware0 VBE simple"); ut_assert_nextlinen("---"); - ut_assert_nextline("(2 bootmeths)"); + ut_assert_nextline("(3 bootmeths)"); ut_assert_console_end();
/* Now reset the order to empty, which should show all of them again */ @@ -77,7 +80,7 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_assert_console_end(); ut_assertnull(env_get("bootmeths")); ut_assertok(run_command("bootmeth list", 0)); - ut_assert_skip_to_line("(2 bootmeths)"); + ut_assert_skip_to_line("(3 bootmeths)");
/* Try reverse order */ ut_assertok(run_command("bootmeth order "efi syslinux"", 0)); diff --git a/test/boot/vbe_simple.c b/test/boot/vbe_simple.c new file mode 100644 index 00000000000..2f6979cafcf --- /dev/null +++ b/test/boot/vbe_simple.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for vbe-simple bootmeth. All start with 'vbe_simple' + * + * Copyright 2023 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#include <common.h> +#include <bootmeth.h> +#include <dm.h> +#include <image.h> +#include <memalign.h> +#include <mmc.h> +#include <of_live.h> +#include <vbe.h> +#include <version_string.h> +#include <linux/log2.h> +#include <test/suites.h> +#include <test/ut.h> +#include <u-boot/crc.h> +#include "bootstd_common.h" + +#define NVDATA_START_BLK ((0x400 + 0x400) / MMC_MAX_BLOCK_LEN) +#define VERSION_START_BLK ((0x400 + 0x800) / MMC_MAX_BLOCK_LEN) +#define TEST_VERSION "U-Boot v2022.04-local2" +#define TEST_VERNUM 0x00010002 + +/* Basic test of reading nvdata and updating a fwupd node in the device tree */ +static int vbe_simple_test_base(struct unit_test_state *uts) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); + const char *version, *bl_version; + struct event_ft_fixup fixup; + struct udevice *dev, *mmc; + struct device_node *np; + struct blk_desc *desc; + char fdt_buf[0x400]; + char info[100]; + int node_ofs; + ofnode node; + u32 vernum; + + /* Set up the version string */ + ut_assertok(uclass_get_device(UCLASS_MMC, 1, &mmc)); + desc = blk_get_by_device(mmc); + ut_assertnonnull(desc); + + memset(buf, '\0', MMC_MAX_BLOCK_LEN); + strcpy(buf, TEST_VERSION); + if (blk_dwrite(desc, VERSION_START_BLK, 1, buf) != 1) + return log_msg_ret("write", -EIO); + + /* Set up the nvdata */ + memset(buf, '\0', MMC_MAX_BLOCK_LEN); + buf[1] = ilog2(0x40) << 4 | 1; + *(u32 *)(buf + 4) = TEST_VERNUM; + buf[0] = crc8(0, buf + 1, 0x3f); + if (blk_dwrite(desc, NVDATA_START_BLK, 1, buf) != 1) + return log_msg_ret("write", -EIO); + + /* Read the version back */ + ut_assertok(vbe_find_by_any("firmware0", &dev)); + ut_assertok(bootmeth_get_state_desc(dev, info, sizeof(info))); + ut_asserteq_str("Version: " TEST_VERSION "\nVernum: 1/2", info); + + ut_assertok(fdt_create_empty_tree(fdt_buf, sizeof(fdt_buf))); + node_ofs = fdt_add_subnode(fdt_buf, 0, "chosen"); + ut_assert(node_ofs > 0); + + node_ofs = fdt_add_subnode(fdt_buf, node_ofs, "fwupd"); + ut_assert(node_ofs > 0); + + node_ofs = fdt_add_subnode(fdt_buf, node_ofs, "firmware0"); + ut_assert(node_ofs > 0); + + /* + * This can only work on the live tree, since the ofnode interface for + * flat tree assumes that ofnode points to the control FDT + */ + ut_assertok(unflatten_device_tree(fdt_buf, &np)); + + /* + * It would be better to call image_setup_libfdt() here, but that + * function does not allow passing an ofnode. We can pass fdt_buf but + * when it comes to send the evenr, it creates an ofnode that uses the + * control FDT, since it has no way of accessing the live tree created + * here. + * + * Two fix this we need: + * - image_setup_libfdt() is updated to use ofnode + * - ofnode updated to support access to an FDT other than the control + * FDT. This is partially implemented with live tree, but not with + * flat tree + */ + fixup.tree.np = np; + ut_assertok(event_notify(EVT_FT_FIXUP, &fixup, sizeof(fixup))); + + node = ofnode_path_root(fixup.tree, "/chosen/fwupd/firmware0"); + + version = ofnode_read_string(node, "cur-version"); + ut_assertnonnull(version); + ut_asserteq_str(TEST_VERSION, version); + + ut_assertok(ofnode_read_u32(node, "cur-vernum", &vernum)); + ut_asserteq(TEST_VERNUM, vernum); + + bl_version = ofnode_read_string(node, "bootloader-version"); + ut_assertnonnull(bl_version); + ut_asserteq_str(version_string, bl_version); + + return 0; +} +BOOTSTD_TEST(vbe_simple_test_base, UT_TESTF_DM | UT_TESTF_SCAN_FDT | + UT_TESTF_LIVE_TREE);