diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi index aa22b8765c..56e6b38bfa 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 d1a8cc7bfb..2761588f0d 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1387,6 +1387,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/arch/sandbox/include/asm/test.h b/arch/sandbox/include/asm/test.h index 015e96d53f..53a036b3ab 100644 --- a/arch/sandbox/include/asm/test.h +++ b/arch/sandbox/include/asm/test.h @@ -304,4 +304,15 @@ int sandbox_cros_ec_get_pwm_duty(struct udevice *dev, uint index, uint *duty); */ int sandbox_sdl_set_bpp(struct udevice *dev, enum video_log2_bpp l2bpp); +/** + * sandbox_set_fake_efi_mgr_dev() - Control EFI bootmgr producing valid bootflow + * + * This is only used for testing. + * + * @dev: efi_mgr bootmeth device + * @fake_dev: true to produce a valid bootflow when requested, false to produce + * an error + */ +void sandbox_set_fake_efi_mgr_dev(struct udevice *dev, bool fake_dev); + #endif diff --git a/arch/x86/lib/bios.c b/arch/x86/lib/bios.c index 98cc05de2e..94349ba807 100644 --- a/arch/x86/lib/bios.c +++ b/arch/x86/lib/bios.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -190,7 +190,7 @@ static void setup_realmode_idt(void) } #ifdef CONFIG_FRAMEBUFFER_SET_VESA_MODE -static u8 vbe_get_mode_info(struct vbe_mode_info *mi) +static u8 vbe_get_mode_info(struct vesa_state *mi) { u16 buffer_seg; u16 buffer_adr; @@ -204,13 +204,13 @@ static u8 vbe_get_mode_info(struct vbe_mode_info *mi) realmode_interrupt(0x10, VESA_GET_MODE_INFO, 0x0000, mi->video_mode, 0x0000, buffer_seg, buffer_adr); - memcpy(mi->mode_info_block, buffer, sizeof(struct vbe_mode_info)); + memcpy(mi->mode_info_block, buffer, sizeof(struct vesa_state)); mi->valid = true; return 0; } -static u8 vbe_set_mode(struct vbe_mode_info *mi) +static u8 vbe_set_mode(struct vesa_state *mi) { int video_mode = mi->video_mode; @@ -225,7 +225,7 @@ static u8 vbe_set_mode(struct vbe_mode_info *mi) return 0; } -static void vbe_set_graphics(int vesa_mode, struct vbe_mode_info *mode_info) +static void vbe_set_graphics(int vesa_mode, struct vesa_state *mode_info) { unsigned char *framebuffer; @@ -249,7 +249,7 @@ static void vbe_set_graphics(int vesa_mode, struct vbe_mode_info *mode_info) #endif /* CONFIG_FRAMEBUFFER_SET_VESA_MODE */ void bios_run_on_x86(struct udevice *dev, unsigned long addr, int vesa_mode, - struct vbe_mode_info *mode_info) + struct vesa_state *mode_info) { pci_dev_t pcidev = dm_pci_get_bdf(dev); u32 num_dev; diff --git a/arch/x86/lib/coreboot_table.c b/arch/x86/lib/coreboot_table.c index 6eab0452fd..05519d851a 100644 --- a/arch/x86/lib/coreboot_table.c +++ b/arch/x86/lib/coreboot_table.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/x86/lib/fsp/fsp_graphics.c b/arch/x86/lib/fsp/fsp_graphics.c index 02fd05c9fa..b07c666caf 100644 --- a/arch/x86/lib/fsp/fsp_graphics.c +++ b/arch/x86/lib/fsp/fsp_graphics.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -106,7 +106,7 @@ static int fsp_video_probe(struct udevice *dev) vesa->phys_base_ptr = dm_pci_read_bar32(dev, 2); gd->fb_base = vesa->phys_base_ptr; - ret = vbe_setup_video_priv(vesa, uc_priv, plat); + ret = vesa_setup_video_priv(vesa, uc_priv, plat); if (ret) goto err; diff --git a/boot/Kconfig b/boot/Kconfig index a294ad769e..6b3b8f072c 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -349,6 +349,13 @@ config BOOTSTD_BOOTCOMMAND standard boot does not support all of the features of distro boot yet. +config BOOTMETH_GLOBAL + bool + help + Add support for global bootmeths. This feature is used by VBE and + EFI bootmgr, since they take full control over which bootdevs are + selected to boot. + config BOOTMETH_DISTRO bool "Bootdev support for distro boot" select PXE_UTILS @@ -391,6 +398,28 @@ config BOOTMETH_EFILOADER This provides a way to try out standard boot on an existing boot flow. +config BOOTMETH_VBE + bool "Bootdev support for Verified Boot for Embedded" + depends on FIT + default y + select BOOTMETH_GLOBAL + help + Enables support for VBE boot. This is a standard boot method which + supports selection of various firmware components, seleciton of an OS to + boot as well as updating these using fwupd. + +if BOOTMETH_VBE + +config BOOTMETH_VBE_SIMPLE + bool "Bootdev support for VBE 'simple' method" + default y + help + Enables support for VBE 'simple' boot. This allows updating a single + firmware image in boot media such as MMC. It does not support any sort + of rollback, recovery or A/B boot. + +endif # BOOTMETH_VBE + config BOOTMETH_SANDBOX def_bool y depends on SANDBOX diff --git a/boot/Makefile b/boot/Makefile index 124065a03f..67e335255f 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -18,7 +18,7 @@ obj-y += image.o image-board.o obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o -obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o system_bootdev.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o @@ -46,3 +46,6 @@ obj-$(CONFIG_CMD_ADTIMG) += image-android-dt.o ifdef CONFIG_SPL_BUILD obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o endif + +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c index 1ede933c2f..13ac69eb39 100644 --- a/boot/bootdev-uclass.c +++ b/boot/bootdev-uclass.c @@ -36,7 +36,6 @@ enum { int bootdev_add_bootflow(struct bootflow *bflow) { - struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); struct bootstd_priv *std; struct bootflow *new; int ret; @@ -52,7 +51,11 @@ int bootdev_add_bootflow(struct bootflow *bflow) memcpy(new, bflow, sizeof(*bflow)); list_add_tail(&new->glob_node, &std->glob_head); - list_add_tail(&new->bm_node, &ucp->bootflow_head); + if (bflow->dev) { + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); + + list_add_tail(&new->bm_node, &ucp->bootflow_head); + } return 0; } @@ -604,14 +607,14 @@ int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp) log_debug("Expected %d bootdevs, found %d using aliases\n", count, upto); - count = build_order(bootstd, order, upto); - if (count < 0) { + ret = build_order(bootstd, order, upto); + if (ret < 0) { free(order); - return log_msg_ret("build", count); + return log_msg_ret("build", ret); } + iter->num_devs = ret; iter->dev_order = order; - iter->num_devs = count; iter->cur_dev = 0; dev = *order; diff --git a/boot/bootflow.c b/boot/bootflow.c index 24ba3c3466..5d94a27ff8 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -86,6 +86,7 @@ int bootflow_next_glob(struct bootflow **bflowp) void bootflow_iter_init(struct bootflow_iter *iter, int flags) { memset(iter, '\0', sizeof(*iter)); + iter->first_glob_method = -1; iter->flags = flags; } @@ -115,11 +116,17 @@ int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, static void bootflow_iter_set_dev(struct bootflow_iter *iter, struct udevice *dev) { + struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(iter->method); + iter->dev = dev; if ((iter->flags & (BOOTFLOWF_SHOW | BOOTFLOWF_SINGLE_DEV)) == BOOTFLOWF_SHOW) { if (dev) printf("Scanning bootdev '%s':\n", dev->name); + else if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && + ucp->flags & BOOTMETHF_GLOBAL) + printf("Scanning global bootmeth '%s':\n", + iter->method->name); else printf("No more bootdevs\n"); } @@ -133,8 +140,12 @@ static void bootflow_iter_set_dev(struct bootflow_iter *iter, static int iter_incr(struct bootflow_iter *iter) { struct udevice *dev; + bool inc_dev = true; + bool global; int ret; + global = iter->doing_global; + if (iter->err == BF_NO_MORE_DEVICES) return BF_NO_MORE_DEVICES; @@ -144,6 +155,21 @@ static int iter_incr(struct bootflow_iter *iter) iter->method = iter->method_order[iter->cur_method]; return 0; } + + /* + * If we have finished scanning the global bootmeths, start the + * normal bootdev scan + */ + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && global) { + iter->num_methods = iter->first_glob_method; + iter->doing_global = false; + + /* + * Don't move to the next dev as we haven't tried this + * one yet! + */ + inc_dev = false; + } } /* No more bootmeths; start at the first one, and... */ @@ -169,14 +195,18 @@ static int iter_incr(struct bootflow_iter *iter) /* ...select next bootdev */ if (iter->flags & BOOTFLOWF_SINGLE_DEV) { ret = -ENOENT; - } else if (++iter->cur_dev == iter->num_devs) { - ret = -ENOENT; - bootflow_iter_set_dev(iter, NULL); } else { - dev = iter->dev_order[iter->cur_dev]; - ret = device_probe(dev); - if (!log_msg_ret("probe", ret)) - bootflow_iter_set_dev(iter, dev); + if (inc_dev) + iter->cur_dev++; + if (iter->cur_dev == iter->num_devs) { + ret = -ENOENT; + bootflow_iter_set_dev(iter, NULL); + } else { + dev = iter->dev_order[iter->cur_dev]; + ret = device_probe(dev); + if (!log_msg_ret("probe", ret)) + bootflow_iter_set_dev(iter, dev); + } } /* if there are no more bootdevs, give up */ @@ -199,6 +229,15 @@ static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) struct udevice *dev; int ret; + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->doing_global) { + bootflow_iter_set_dev(iter, NULL); + ret = bootmeth_get_bootflow(iter->method, bflow); + if (ret) + return log_msg_ret("glob", ret); + + return 0; + } + dev = iter->dev; ret = bootdev_get_bootflow(dev, iter, bflow); @@ -231,19 +270,22 @@ int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter, { int ret; + if (dev) + flags |= BOOTFLOWF_SKIP_GLOBAL; bootflow_iter_init(iter, flags); ret = bootdev_setup_iter_order(iter, &dev); if (ret) return log_msg_ret("obdev", -ENODEV); - bootflow_iter_set_dev(iter, dev); - ret = bootmeth_setup_iter_order(iter); + ret = bootmeth_setup_iter_order(iter, !(flags & BOOTFLOWF_SKIP_GLOBAL)); if (ret) return log_msg_ret("obmeth", -ENODEV); /* Find the first bootmeth (there must be at least one!) */ iter->method = iter->method_order[iter->cur_method]; + if (!IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) || !iter->doing_global) + bootflow_iter_set_dev(iter, dev); ret = bootflow_check(iter, bflow); if (ret) { @@ -307,7 +349,8 @@ void bootflow_free(struct bootflow *bflow) void bootflow_remove(struct bootflow *bflow) { - list_del(&bflow->bm_node); + if (bflow->dev) + list_del(&bflow->bm_node); list_del(&bflow->glob_node); bootflow_free(bflow); diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c index c040d5f92b..2d7652edea 100644 --- a/boot/bootmeth-uclass.c +++ b/boot/bootmeth-uclass.c @@ -20,6 +20,16 @@ DECLARE_GLOBAL_DATA_PTR; +int bootmeth_get_state_desc(struct udevice *dev, char *buf, int maxsize) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->get_state_desc) + return -ENOSYS; + + return ops->get_state_desc(dev, buf, maxsize); +} + int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter) { const struct bootmeth_ops *ops = bootmeth_get_ops(dev); @@ -61,18 +71,21 @@ int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, return ops->read_file(dev, bflow, file_path, addr, sizep); } -/** - * bootmeth_setup_iter_order() - Set up the ordering of bootmeths to scan - * - * This sets up the ordering information in @iter, based on the selected - * ordering of the bootmethds in bootstd_priv->bootmeth_order. If there is no - * ordering there, then all bootmethods are added - * - * @iter: Iterator to update with the order - * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve - * on other error - */ -int bootmeth_setup_iter_order(struct bootflow_iter *iter) +int bootmeth_get_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_bootflow) + return -ENOSYS; + memset(bflow, '\0', sizeof(*bflow)); + bflow->dev = NULL; + bflow->method = dev; + bflow->state = BOOTFLOWST_BASE; + + return ops->read_bootflow(dev, bflow); +} + +int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global) { struct bootstd_priv *std; struct udevice **order; @@ -95,29 +108,77 @@ int bootmeth_setup_iter_order(struct bootflow_iter *iter) /* If we have an ordering, copy it */ if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && std->bootmeth_count) { - memcpy(order, std->bootmeth_order, - count * sizeof(struct bootmeth *)); - } else { - struct udevice *dev; - int i, upto; + int i; /* - * Get a list of bootmethods, in seq order (i.e. using aliases). - * There may be gaps so try to count up high enough to find them - * all. + * We don't support skipping global bootmeths. Instead, the user + * should omit them from the ordering */ - for (i = 0, upto = 0; upto < count && i < 20 + count * 2; i++) { - ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, i, - &dev); - if (!ret) - order[upto++] = dev; + if (!include_global) + return log_msg_ret("glob", -EPERM); + memcpy(order, std->bootmeth_order, + count * sizeof(struct bootmeth *)); + + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) { + for (i = 0; i < count; i++) { + struct udevice *dev = order[i]; + struct bootmeth_uc_plat *ucp; + bool is_global; + + ucp = dev_get_uclass_plat(dev); + is_global = ucp->flags & + BOOTMETHF_GLOBAL; + if (is_global) { + iter->first_glob_method = i; + break; + } + } + } + } else { + struct udevice *dev; + int i, upto, pass; + + /* + * Do two passes, one to find the normal bootmeths and another + * to find the global ones, if required, The global ones go at + * the end. + */ + for (pass = 0, upto = 0; pass < 1 + include_global; pass++) { + if (pass) + iter->first_glob_method = upto; + /* + * Get a list of bootmethods, in seq order (i.e. using + * aliases). There may be gaps so try to count up high + * enough to find them all. + */ + for (i = 0; upto < count && i < 20 + count * 2; i++) { + struct bootmeth_uc_plat *ucp; + bool is_global; + + ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, + i, &dev); + if (ret) + continue; + ucp = dev_get_uclass_plat(dev); + is_global = + IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && + (ucp->flags & BOOTMETHF_GLOBAL); + if (pass ? is_global : !is_global) + order[upto++] = dev; + } } count = upto; } + if (!count) + return log_msg_ret("count2", -ENOENT); + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && include_global && + iter->first_glob_method != -1 && iter->first_glob_method != count) { + iter->cur_method = iter->first_glob_method; + iter->doing_global = true; + } iter->method_order = order; iter->num_methods = count; - iter->cur_method = 0; return 0; } diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c index 2b41e654ad..fea09b2c2f 100644 --- a/boot/bootmeth_distro.c +++ b/boot/bootmeth_distro.c @@ -22,6 +22,19 @@ #include #include +static int distro_get_state_desc(struct udevice *dev, char *buf, int maxsize) +{ + if (IS_ENABLED(CONFIG_SANDBOX)) { + int len; + + len = snprintf(buf, maxsize, "OK"); + + return len + 1 < maxsize ? 0 : -ENOSPC; + } + + return 0; +} + static int disto_getfile(struct pxe_context *ctx, const char *file_path, char *file_addr, ulong *sizep) { @@ -123,6 +136,7 @@ static int distro_bootmeth_bind(struct udevice *dev) } static struct bootmeth_ops distro_bootmeth_ops = { + .get_state_desc = distro_get_state_desc, .check = distro_check, .read_bootflow = distro_read_bootflow, .read_file = bootmeth_common_read_file, diff --git a/boot/bootmeth_efi_mgr.c b/boot/bootmeth_efi_mgr.c index a6914466db..2f327c1f8c 100644 --- a/boot/bootmeth_efi_mgr.c +++ b/boot/bootmeth_efi_mgr.c @@ -15,6 +15,22 @@ #include #include +/** + * struct efi_mgr_priv - private info for the efi-mgr driver + * + * @fake_bootflow: Fake a valid bootflow for testing + */ +struct efi_mgr_priv { + bool fake_dev; +}; + +void sandbox_set_fake_efi_mgr_dev(struct udevice *dev, bool fake_dev) +{ + struct efi_mgr_priv *priv = dev_get_priv(dev); + + priv->fake_dev = fake_dev; +} + static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter) { int ret; @@ -29,13 +45,16 @@ static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter) static int efi_mgr_read_bootflow(struct udevice *dev, struct bootflow *bflow) { - /* - * Just assume there is something to boot since we don't have any way - * of knowing in advance - */ - bflow->state = BOOTFLOWST_READY; + struct efi_mgr_priv *priv = dev_get_priv(dev); - return 0; + if (priv->fake_dev) { + bflow->state = BOOTFLOWST_READY; + return 0; + } + + /* To be implemented */ + + return -EINVAL; } static int efi_mgr_read_file(struct udevice *dev, struct bootflow *bflow, @@ -61,6 +80,7 @@ static int bootmeth_efi_mgr_bind(struct udevice *dev) struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); plat->desc = "EFI bootmgr flow"; + plat->flags = BOOTMETHF_GLOBAL; return 0; } @@ -77,10 +97,11 @@ static const struct udevice_id efi_mgr_bootmeth_ids[] = { { } }; -U_BOOT_DRIVER(bootmeth_zefi_mgr) = { +U_BOOT_DRIVER(bootmeth_efi_mgr) = { .name = "bootmeth_efi_mgr", .id = UCLASS_BOOTMETH, .of_match = efi_mgr_bootmeth_ids, .ops = &efi_mgr_bootmeth_ops, .bind = bootmeth_efi_mgr_bind, + .priv_auto = sizeof(struct efi_mgr_priv), }; diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c index 3c6c32ae60..565c22a36e 100644 --- a/boot/bootstd-uclass.c +++ b/boot/bootstd-uclass.c @@ -133,12 +133,7 @@ int dm_scan_other(bool pre_reloc_only) return 0; for (i = 0; i < n_ents; i++, drv++) { - /* - * Disable EFI Manager for now as no one uses it so it is - * confusing - */ - if (drv->id == UCLASS_BOOTMETH && - strcmp("efi_mgr_bootmeth", drv->name)) { + if (drv->id == UCLASS_BOOTMETH) { const char *name = drv->name; if (!strncmp("bootmeth_", name, 9)) @@ -150,12 +145,6 @@ int dm_scan_other(bool pre_reloc_only) } } - /* Create the system bootdev too */ - ret = device_bind_driver(bootstd, "system_bootdev", "system-bootdev", - &dev); - if (ret) - return log_msg_ret("sys", ret); - return 0; } diff --git a/boot/image-fdt.c b/boot/image-fdt.c index 9db2cee994..5e5b24674d 100644 --- a/boot/image-fdt.c +++ b/boot/image-fdt.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #ifndef CONFIG_SYS_FDT_PAD @@ -668,6 +669,16 @@ int image_setup_libfdt(bootm_headers_t *images, void *blob, goto err; } } + if (CONFIG_IS_ENABLED(EVENT)) { + struct event_ft_fixup fixup; + + fixup.tree = oftree_default(); + ret = event_notify(EVT_FT_FIXUP, &fixup, sizeof(fixup)); + if (ret) { + printf("ERROR: fdt fixup event failed: %d\n", ret); + goto err; + } + } /* Delete the old LMB reservation */ if (lmb) diff --git a/boot/system_bootdev.c b/boot/system_bootdev.c deleted file mode 100644 index 432d203478..0000000000 --- a/boot/system_bootdev.c +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Bootdevice for system, used for bootmeths not tied to any partition device - * - * Copyright 2021 Google LLC - * Written by Simon Glass - */ - -#define LOG_CATEGORY UCLASS_BOOTSTD - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int system_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, - struct bootflow *bflow) -{ - int ret; - - /* Must be an bootstd device */ - ret = bootflow_iter_uses_system(iter); - if (ret) - return log_msg_ret("net", ret); - - ret = bootmeth_check(bflow->method, iter); - if (ret) - return log_msg_ret("check", ret); - - ret = bootmeth_read_bootflow(bflow->method, bflow); - if (ret) - return log_msg_ret("method", ret); - - return 0; -} - -static int system_bootdev_bind(struct udevice *dev) -{ - struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); - - ucp->prio = BOOTDEVP_6_SYSTEM; - - return 0; -} - -struct bootdev_ops system_bootdev_ops = { - .get_bootflow = system_get_bootflow, -}; - -static const struct udevice_id system_bootdev_ids[] = { - { .compatible = "u-boot,bootdev-system" }, - { } -}; - -U_BOOT_DRIVER(system_bootdev) = { - .name = "system_bootdev", - .id = UCLASS_BOOTDEV, - .ops = &system_bootdev_ops, - .bind = system_bootdev_bind, - .of_match = system_bootdev_ids, -}; diff --git a/boot/vbe.c b/boot/vbe.c new file mode 100644 index 0000000000..e6ee087dc2 --- /dev/null +++ b/boot/vbe.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verified Boot for Embedded (VBE) access functions + * + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * is_vbe() - Check if a device is a VBE method + * + * @dev: Device to check + * @return true if this is a VBE bootmth device, else false + */ +static bool is_vbe(struct udevice *dev) +{ + return !strncmp("vbe", dev->driver->name, 3); +} + +int vbe_find_next_device(struct udevice **devp) +{ + for (uclass_find_next_device(devp); + *devp; + uclass_find_next_device(devp)) { + if (is_vbe(*devp)) + return 0; + } + + return 0; +} + +int vbe_find_first_device(struct udevice **devp) +{ + uclass_find_first_device(UCLASS_BOOTMETH, devp); + if (*devp && is_vbe(*devp)) + return 0; + + return vbe_find_next_device(devp); +} + +int vbe_list(void) +{ + struct bootstd_priv *std; + struct udevice *dev; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + printf("%3s %-3s %-15s %-15s %s\n", "#", "Sel", "Device", "Driver", + "Description"); + printf("%3s %-3s %-15s %-15s %s\n", "---", "---", "--------------", + "--------------", "-----------"); + for (ret = vbe_find_first_device(&dev); dev; + ret = vbe_find_next_device(&dev)) { + const struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + printf("%3d %-3s %-15s %-15s %s\n", dev_seq(dev), + std->vbe_bootmeth == dev ? "*" : "", dev->name, + dev->driver->name, plat->desc); + } + printf("%3s %-3s %-15s %-15s %s\n", "---", "---", "--------------", + "--------------", "-----------"); + + return 0; +} + +int vbe_select(struct udevice *dev) +{ + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + std->vbe_bootmeth = dev; + + return 0; +} + +int vbe_find_by_any(const char *name, struct udevice **devp) +{ + struct udevice *dev; + int ret, seq; + char *endp; + + seq = simple_strtol(name, &endp, 16); + + /* Select by name */ + if (*endp) { + ret = uclass_get_device_by_name(UCLASS_BOOTMETH, name, &dev); + if (ret) { + printf("Cannot probe VBE bootmeth '%s' (err=%d)\n", name, + ret); + return ret; + } + + /* select by number */ + } else { + ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, seq, &dev); + if (ret) { + printf("Cannot find '%s' (err=%d)\n", name, ret); + return ret; + } + } + + *devp = dev; + + return 0; +} diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c new file mode 100644 index 0000000000..a395bc20a6 --- /dev/null +++ b/boot/vbe_simple.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Verified Boot for Embedded (VBE) 'simple' method + * + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + MAX_VERSION_LEN = 256, + + NVD_HDR_VER_SHIFT = 0, + NVD_HDR_VER_MASK = 0xf, + NVD_HDR_SIZE_SHIFT = 4, + NVD_HDR_SIZE_MASK = 0xf << NVD_HDR_SIZE_SHIFT, + + /* Firmware key-version is in the top 16 bits of fw_ver */ + FWVER_KEY_SHIFT = 16, + FWVER_FW_MASK = 0xffff, + + NVD_HDR_VER_CUR = 1, /* current version */ +}; + +/** struct simple_priv - information read from the device tree */ +struct simple_priv { + u32 area_start; + u32 area_size; + u32 skip_offset; + u32 state_offset; + u32 state_size; + u32 version_offset; + u32 version_size; + const char *storage; +}; + +/** struct simple_state - state information read from media + * + * @fw_version: Firmware version string + * @fw_vernum: Firmware version number + */ +struct simple_state { + char fw_version[MAX_VERSION_LEN]; + u32 fw_vernum; +}; + +/** struct simple_nvdata - storage format for non-volatile data */ +struct simple_nvdata { + u8 crc8; + u8 hdr; + u16 spare1; + u32 fw_vernum; + u8 spare2[0x38]; +}; + +static int simple_read_version(struct udevice *dev, struct blk_desc *desc, + u8 *buf, struct simple_state *state) +{ + struct simple_priv *priv = dev_get_priv(dev); + int start; + + if (priv->version_size > MMC_MAX_BLOCK_LEN) + return log_msg_ret("ver", -E2BIG); + + start = priv->area_start + priv->version_offset; + if (start & (MMC_MAX_BLOCK_LEN - 1)) + return log_msg_ret("get", -EBADF); + start /= MMC_MAX_BLOCK_LEN; + + if (blk_dread(desc, start, 1, buf) != 1) + return log_msg_ret("read", -EIO); + strlcpy(state->fw_version, buf, MAX_VERSION_LEN); + log_debug("version=%s\n", state->fw_version); + + return 0; +} + +static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc, + u8 *buf, struct simple_state *state) +{ + struct simple_priv *priv = dev_get_priv(dev); + uint hdr_ver, hdr_size, size, crc; + const struct simple_nvdata *nvd; + int start; + + if (priv->state_size > MMC_MAX_BLOCK_LEN) + return log_msg_ret("state", -E2BIG); + + start = priv->area_start + priv->state_offset; + if (start & (MMC_MAX_BLOCK_LEN - 1)) + return log_msg_ret("get", -EBADF); + start /= MMC_MAX_BLOCK_LEN; + + if (blk_dread(desc, start, 1, buf) != 1) + return log_msg_ret("read", -EIO); + nvd = (struct simple_nvdata *)buf; + hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; + hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT; + if (hdr_ver != NVD_HDR_VER_CUR) + return log_msg_ret("hdr", -EPERM); + size = 1 << hdr_size; + if (size > sizeof(*nvd)) + return log_msg_ret("sz", -ENOEXEC); + + crc = crc8(0, buf + 1, size - 1); + if (crc != nvd->crc8) + return log_msg_ret("crc", -EPERM); + state->fw_vernum = nvd->fw_vernum; + + log_debug("version=%s\n", state->fw_version); + + return 0; +} + +static int simple_read_state(struct udevice *dev, struct simple_state *state) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); + struct simple_priv *priv = dev_get_priv(dev); + struct blk_desc *desc; + char devname[16]; + const char *end; + int devnum; + int ret; + + /* First figure out the block device */ + log_debug("storage=%s\n", priv->storage); + devnum = trailing_strtoln_end(priv->storage, NULL, &end); + if (devnum == -1) + return log_msg_ret("num", -ENODEV); + if (end - priv->storage >= sizeof(devname)) + return log_msg_ret("end", -E2BIG); + strlcpy(devname, priv->storage, end - priv->storage + 1); + log_debug("dev=%s, %x\n", devname, devnum); + + desc = blk_get_dev(devname, devnum); + if (!desc) + return log_msg_ret("get", -ENXIO); + + ret = simple_read_version(dev, desc, buf, state); + if (ret) + return log_msg_ret("ver", ret); + + ret = simple_read_nvdata(dev, desc, buf, state); + if (ret) + return log_msg_ret("nvd", ret); + + return 0; +} + +static int vbe_simple_get_state_desc(struct udevice *dev, char *buf, + int maxsize) +{ + struct simple_state state; + int ret; + + ret = simple_read_state(dev, &state); + if (ret) + return log_msg_ret("read", ret); + + if (maxsize < 30) + return -ENOSPC; + snprintf(buf, maxsize, "Version: %s\nVernum: %x/%x", state.fw_version, + state.fw_vernum >> FWVER_KEY_SHIFT, + state.fw_vernum & FWVER_FW_MASK); + + return 0; +} + +static int vbe_simple_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + /* To be implemented */ + + return -EINVAL; +} + +static struct bootmeth_ops bootmeth_vbe_simple_ops = { + .get_state_desc = vbe_simple_get_state_desc, + .read_bootflow = vbe_simple_read_bootflow, + .read_file = bootmeth_common_read_file, +}; + +int vbe_simple_fixup_node(ofnode node, struct simple_state *state) +{ + char *version; + int ret; + + version = strdup(state->fw_version); + if (!version) + return log_msg_ret("ver", -ENOMEM); + + ret = ofnode_write_string(node, "cur-version", version); + if (ret) + return log_msg_ret("ver", ret); + ret = ofnode_write_u32(node, "cur-vernum", state->fw_vernum); + if (ret) + return log_msg_ret("ver", ret); + ret = ofnode_write_string(node, "bootloader-version", version_string); + if (ret) + return log_msg_ret("fix", ret); + + return 0; +} + +/** + * bootmeth_vbe_simple_ft_fixup() - Write out all VBE simple data to the DT + * + * @ctx: Context for event + * @event: Event to process + * @return 0 if OK, -ve on error + */ +static int bootmeth_vbe_simple_ft_fixup(void *ctx, struct event *event) +{ + oftree tree = event->data.ft_fixup.tree; + struct udevice *dev; + ofnode node; + int ret; + + /* + * Ideally we would have driver model support for fixups, but that does + * not exist yet. It is a step too far to try to do this before VBE is + * in place. + */ + for (ret = vbe_find_first_device(&dev); dev; + ret = vbe_find_next_device(&dev)) { + struct simple_state state; + + if (strcmp("vbe_simple", dev->driver->name)) + continue; + + /* Check if there is a node to fix up */ + node = ofnode_path_root(tree, "/chosen/fwupd"); + if (!ofnode_valid(node)) + continue; + node = ofnode_find_subnode(node, dev->name); + if (!ofnode_valid(node)) + continue; + + log_debug("Fixing up: %s\n", dev->name); + ret = device_probe(dev); + if (ret) + return log_msg_ret("probe", ret); + ret = simple_read_state(dev, &state); + if (ret) + return log_msg_ret("read", ret); + + ret = vbe_simple_fixup_node(node, &state); + if (ret) + return log_msg_ret("fix", ret); + } + + return 0; +} +EVENT_SPY(EVT_FT_FIXUP, bootmeth_vbe_simple_ft_fixup); + +static int bootmeth_vbe_simple_probe(struct udevice *dev) +{ + struct simple_priv *priv = dev_get_priv(dev); + + memset(priv, '\0', sizeof(*priv)); + if (dev_read_u32(dev, "area-start", &priv->area_start) || + dev_read_u32(dev, "area-size", &priv->area_size) || + dev_read_u32(dev, "version-offset", &priv->version_offset) || + dev_read_u32(dev, "version-size", &priv->version_size) || + dev_read_u32(dev, "state-offset", &priv->state_offset) || + dev_read_u32(dev, "state-size", &priv->state_size)) + return log_msg_ret("read", -EINVAL); + dev_read_u32(dev, "skip-offset", &priv->skip_offset); + priv->storage = strdup(dev_read_string(dev, "storage")); + if (!priv->storage) + return log_msg_ret("str", -EINVAL); + + return 0; +} + +static int bootmeth_vbe_simple_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "VBE simple" : "vbe-simple"; + plat->flags = BOOTMETHF_GLOBAL; + + return 0; +} + +#if CONFIG_IS_ENABLED(OF_REAL) +static const struct udevice_id generic_simple_vbe_simple_ids[] = { + { .compatible = "fwupd,vbe-simple" }, + { } +}; +#endif + +U_BOOT_DRIVER(vbe_simple) = { + .name = "vbe_simple", + .id = UCLASS_BOOTMETH, + .of_match = of_match_ptr(generic_simple_vbe_simple_ids), + .ops = &bootmeth_vbe_simple_ops, + .bind = bootmeth_vbe_simple_bind, + .probe = bootmeth_vbe_simple_probe, + .flags = DM_FLAG_PRE_RELOC, + .priv_auto = sizeof(struct simple_priv), +}; diff --git a/cmd/Kconfig b/cmd/Kconfig index 7d19706a8e..211ebe9c87 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -330,6 +330,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 5e43a1e022..6e87522b62 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/bootflow.c b/cmd/bootflow.c index af4b9c3732..313103d277 100644 --- a/cmd/bootflow.c +++ b/cmd/bootflow.c @@ -69,8 +69,8 @@ static void show_bootflow(int index, struct bootflow *bflow, bool errors) { printf("%3x %-11s %-6s %-9.9s %4x %-25.25s %s\n", index, bflow->method->name, bootflow_state_get_name(bflow->state), - dev_get_uclass_name(dev_get_parent(bflow->dev)), bflow->part, - bflow->name, bflow->fname); + bflow->dev ? dev_get_uclass_name(dev_get_parent(bflow->dev)) : + "(none)", bflow->part, bflow->name, bflow->fname); if (errors) report_bootflow_err(bflow, bflow->err); } @@ -95,7 +95,8 @@ static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc, struct bootflow_iter iter; struct udevice *dev; struct bootflow bflow; - bool all = false, boot = false, errors = false, list = false; + bool all = false, boot = false, errors = false, no_global = false; + bool list = false; int num_valid = 0; bool has_args; int ret, i; @@ -112,6 +113,7 @@ static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc, all = strchr(argv[1], 'a'); boot = strchr(argv[1], 'b'); errors = strchr(argv[1], 'e'); + no_global = strchr(argv[1], 'G'); list = strchr(argv[1], 'l'); argc--; argv++; @@ -137,6 +139,8 @@ static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc, flags |= BOOTFLOWF_SHOW; if (all) flags |= BOOTFLOWF_ALL; + if (no_global) + flags |= BOOTFLOWF_SKIP_GLOBAL; /* * If we have a device, just scan for bootflows attached to that device @@ -383,7 +387,7 @@ static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc, #ifdef CONFIG_SYS_LONGHELP static char bootflow_help_text[] = #ifdef CONFIG_CMD_BOOTFLOW_FULL - "scan [-abel] [bdev] - scan for valid bootflows (-l list, -a all, -e errors, -b boot)\n" + "scan [-abeGl] [bdev] - scan for valid bootflows (-l list, -a all, -e errors, -b boot, -G no global)\n" "bootflow list [-e] - list scanned bootflows (-e errors)\n" "bootflow select [|] - select a bootflow\n" "bootflow info [-d] - show info on current bootflow (-d dump bootflow)\n" diff --git a/cmd/bootmeth.c b/cmd/bootmeth.c index c9a27fe8ac..9fbcccdba7 100644 --- a/cmd/bootmeth.c +++ b/cmd/bootmeth.c @@ -69,7 +69,9 @@ static int do_bootmeth_list(struct cmd_tbl *cmdtp, int flag, int argc, } } - if (order == -1) + if (ucp->flags & BOOTMETHF_GLOBAL) + printf("%5s", "glob"); + else if (order == -1) printf("%5s", "-"); else printf("%5x", order); diff --git a/cmd/elf.c b/cmd/elf.c index 2b33c50bd0..ce40d3f72a 100644 --- a/cmd/elf.c +++ b/cmd/elf.c @@ -14,7 +14,7 @@ #include #include #ifdef CONFIG_X86 -#include +#include #include #include #include diff --git a/cmd/read.c b/cmd/read.c index 99c7e3854e..fecfadaa1f 100644 --- a/cmd/read.c +++ b/cmd/read.c @@ -10,6 +10,7 @@ #include #include +#include #include int do_read(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) @@ -45,7 +46,7 @@ int do_read(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) return 1; } - addr = (void *)hextoul(argv[3], NULL); + addr = map_sysmem(hextoul(argv[3], NULL), 0); blk = hextoul(argv[4], NULL); cnt = hextoul(argv[5], NULL); diff --git a/cmd/vbe.c b/cmd/vbe.c new file mode 100644 index 0000000000..a5737edc04 --- /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 + */ + +#include +#include +#include +#include +#include + +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/common/event.c b/common/event.c index af1ed4121d..3e34550978 100644 --- a/common/event.c +++ b/common/event.c @@ -35,6 +35,9 @@ const char *const type_name[] = { /* init hooks */ "misc_init_f", + + /* fdt hooks */ + "ft_fixup", }; _Static_assert(ARRAY_SIZE(type_name) == EVT_COUNT, "event type_name size"); diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index a71ce77c40..6d62feeb08 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -11,6 +11,7 @@ CONFIG_DISTRO_DEFAULTS=y CONFIG_FIT=y CONFIG_FIT_SIGNATURE=y CONFIG_FIT_VERBOSE=y +# CONFIG_BOOTMETH_VBE is not set CONFIG_BOOTSTAGE=y CONFIG_BOOTSTAGE_REPORT=y CONFIG_BOOTSTAGE_FDT=y @@ -206,6 +207,7 @@ CONFIG_RSA_VERIFY_WITH_PKEY=y CONFIG_TPM=y CONFIG_LZ4=y CONFIG_ERRNO_STR=y +# CONFIG_CMD_BOOTEFI_BOOTMGR is not set CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y CONFIG_EFI_CAPSULE_ON_DISK=y CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst index 5e9c0d282b..b8773f8339 100644 --- a/doc/develop/bootstd.rst +++ b/doc/develop/bootstd.rst @@ -32,6 +32,7 @@ way to boot with U-Boot. The feature is extensible to different Operating Systems (such as Chromium OS) and devices (beyond just block and network devices). It supports EFI boot and EFI bootmgr too. +Finally, standard boot supports the operation of :doc:`vbe`. Bootflow -------- @@ -89,6 +90,12 @@ bootflows. Note: it is possible to have a bootmeth that uses a partition or a whole device directly, but it is more common to use a filesystem. +Note that some bootmeths are 'global', meaning that they select the bootdev +themselves. Examples include VBE and EFI boot manager. In this case, they +provide a `read_bootflow()` method which checks whatever bootdevs it likes, then +returns the bootflow, if found. Some of these bootmeths may be very slow, if +they scan a lot of devices. + Boot process ------------ @@ -112,6 +119,9 @@ the following command:: which scans for available bootflows, optionally listing each find it finds (-l) and trying to boot it (-b). +When global bootmeths are available, these are typically checked before the +above bootdev scanning. + Controlling ordering -------------------- @@ -269,18 +279,8 @@ Standard boot requires a single instance of the bootstd device to make things work. This includes global information about the state of standard boot. See `struct bootstd_priv` for this structure, accessed with `bootstd_get_priv()`. -Within the devicetree, if you add bootmeth devices or a system bootdev, they -should be children of the bootstd device. See `arch/sandbox/dts/test.dts` for -an example of this. - - -The system bootdev ------------------- - -Some bootmeths don't operate on individual bootdevs, but on the whole system. -For example, the EFI boot manager does its own device scanning and does not -make use of the bootdev devices. Such bootmeths can make use of the system -bootdev, typically considered last, after everything else has been tried. +Within the devicetree, if you add bootmeth devices, they should be children of +the bootstd device. See `arch/sandbox/dts/test.dts` for an example of this. .. _`Automatic Devices`: @@ -291,12 +291,11 @@ Automatic devices It is possible to define all the required devices in the devicetree manually, but it is not necessary. The bootstd uclass includes a `dm_scan_other()` function which creates the bootstd device if not found. If no bootmeth devices -are found at all, it creates one for each available bootmeth driver as well as a -system bootdev. +are found at all, it creates one for each available bootmeth driver. If your devicetree has any bootmeth device it must have all of them that you -want to use, as well as the system bootdev if needed, since no bootmeth devices -will be created automatically in that case. +want to use, since no bootmeth devices will be created automatically in that +case. Using devicetree @@ -347,6 +346,7 @@ Bootmeth drivers are provided for: - distro boot from a disk (syslinux) - distro boot from a network (PXE) - EFI boot using bootefi + - VBE - EFI boot using boot manager @@ -433,18 +433,23 @@ case, the iterator ends up with a `dev_order` array containing the bootdevs that are going to be used, with `num_devs` set to the number of bootdevs and `cur_dev` starting at 0. -Next, the ordering of bootdevs is determined, by `bootmeth_setup_iter_order()`. +Next, the ordering of bootmeths is determined, by `bootmeth_setup_iter_order()`. By default the ordering is again by sequence number, i.e. the `/aliases` node, or failing that the order in the devicetree. But the `bootmeth order` command or `bootmeths` environment variable can be used to set up an ordering. If that has been done, the ordering is in `struct bootstd_priv`, so that ordering is simply copied into the iterator. Either way, the `method_order` array it set up, -along with `num_methods`. Then `cur_method` is set to 0. +along with `num_methods`. + +Note that global bootmeths are always put at the end of the ordering. If any are +present, `cur_method` is set to the first one, so that global bootmeths are done +first. Once all have been used, these bootmeths are dropped from the iteration. +When there are no global bootmeths, `cur_method` is set to 0. At this point the iterator is ready to use, with the first bootdev and bootmeth -selected. All the other fields are 0. This means that the current partition is -0, which is taken to mean the whole device, since partition numbers start at 1. -It also means that `max_part` is 0, i.e. the maximum partition number we know +selected. Most of the other fields are 0. This means that the current partition +is 0, which is taken to mean the whole device, since partition numbers start at +1. It also means that `max_part` is 0, i.e. the maximum partition number we know about is 0, meaning that, as far as we know, there is no partition table on this bootdev. @@ -455,6 +460,10 @@ If the `BOOTFLOWF_ALL` iterator flag is set, even errors are returned as incomplete bootflows, but normally an error results in moving onto the next iteration. +Note that `bootflow_check()` handles global bootmeths explicitly, but calling +`bootmeth_get_bootflow()` on each one. The `doing_global` flag indicates when +the iterator is in that state. + The `bootflow_scan_next()` function handles moving onto the next iteration and checking it. In fact it sits in a loop doing that repeatedly until it finds something it wants to return. @@ -473,9 +482,10 @@ the least-sigificant digit on the right, counting like this: 0 0 2 0 1 0 0 1 1 - 0 1 1 + 0 1 2 1 0 0 1 0 1 + ... ======== ======= ======= The maximum value for `method` is `num_methods - 1` so when it exceeds that, it @@ -487,6 +497,31 @@ exceeds its maximum, then the next bootdev is used. In this way, iter_incr() works its way through all possibilities, moving forward one each time it is called. +Note that global bootmeths introduce a subtlety into the above description. +When `doing_global` is true, the iteration takes place only among the bootmeths, +i.e. the last column above. The global bootmeths are at the end of the list. +Assuming that they are entries 3 and 4 in the list, the iteration then looks +like this: + + ======== ======= ======= ======================================= + bootdev part method notes + ======== ======= ======= ======================================= + . . 3 doing_global = true, method_count = 5 + . . 4 + 0 0 0 doing_global = false, method_count = 3 + 0 0 1 + 0 0 2 + 0 1 0 + 0 1 1 + 0 1 2 + 1 0 0 + 1 0 1 + ... + ======== ======= ======= ======================================= + +The changeover of the value of `doing_global` from true to false is handled in +`iter_incr()` as well. + There is no expectation that iteration will actually finish. Quite often a valid bootflow is found early on. With `bootflow scan -b`, that causes the bootflow to be immediately booted. Assuming it is successful, the iteration never @@ -516,17 +551,19 @@ method `bootdev_get_bootflow()` to ask the bootdev to return a bootflow. It passes the iterator to the bootdev method, so that function knows what we are talking about. At first, the bootflow is set up in the state `BOOTFLOWST_BASE`, with just the `method` and `dev` intiialised. But the bootdev may fill in more, -e.g. updating the state, depending on what it finds. +e.g. updating the state, depending on what it finds. For global bootmeths the +`bootmeth_get_bootflow()` function is called instead of +`bootdev_get_bootflow()`. -Based on what the bootdev responds with, `bootflow_check()` either +Based on what the bootdev or bootmeth responds with, `bootflow_check()` either returns a valid bootflow, or a partial one with an error. A partial bootflow is one that has some fields set up, but did not reach the `BOOTFLOWST_READY` state. As noted before, if the `BOOTFLOWF_ALL` iterator flag is set, then all bootflows are returned, even partial ones. This can help with debugging. So at this point you can see that total control over whether a bootflow can -be generated from a particular iteration, or not, rests with the bootdev. -Each one can adopt its own approach. +be generated from a particular iteration, or not, rests with the bootdev (or +global bootmeth). Each one can adopt its own approach. Going down a level, what does the bootdev do in its `get_bootflow()` method? Let us consider the MMC bootdev. In that case the call to diff --git a/doc/develop/driver-model/livetree.rst b/doc/develop/driver-model/livetree.rst index 9f654f3b89..faf3eb5b5f 100644 --- a/doc/develop/driver-model/livetree.rst +++ b/doc/develop/driver-model/livetree.rst @@ -211,9 +211,61 @@ using it in new code. Modifying the livetree ---------------------- -This is not currently supported. Once implemented it should provide a much -more efficient implementation for modification of the device tree than using -the flat tree. +This is supported in a limited way, with ofnode_write_prop() and related +functions. + +The unflattening algorithm results in a single block of memory being +allocated for the whole tree. When writing new properties, these are +allocated new memory outside that block. When the block is freed, the +allocated properties remain. This can result in a memory leak. + +The solution to this leak would be to add a flag for properties (and nodes when +support is provided for adding those) that indicates that they should be +freed. Then the tree can be scanned for these 'separately allocated' nodes and +properties before freeing the memory block. + +The ofnode_write\_...() functions also support writing to the flat tree. Care +should be taken however, since this can change the position of node names and +properties in the flat tree, thus affecting the live tree. Generally this does +not matter, since when we fire up the live tree we don't ever use the flat tree +again. But in the case of tests, this can cause a problem. + +The sandbox tests typically run with OF_LIVE enabled but with the actual live +tree either present or absent. This is to make sure that the flat tree functions +work correctly even with OF_LIVE is enabled. But if a test modifies the flat +device tree, then the live tree can become invalid. Any live tree tests that run +after that point will use a corrupted tree, e.g. with an incorrect property name +or worse. To deal with this we use a flag UT_TESTF_LIVE_OR_FLAT then ensures +that tests which write to the flat tree are not run if OF_LIVE is enabled. Only +the live tree version of the test is run, when OF_LIVE is enabled, with +sandbox_flattree running the flat tree version. + +This is of course a work-around, even if a reasonable one. One solution to this +problem would be to make a copy of the flat tree before the test and restore it +afterwards, in the same memory location, so that the live tree pointers work +again. Another would be to regenerate the live tree if a test modified the flat +tree. + +Neither of these solutions is currently implemented, since the situation that +causes the problem can only occur in sandbox tests, is somewhat esoteric and +the UT_TESTF_LIVE_OR_FLAT flag deals with it in a reasonable way. + + +Multiple livetrees +------------------ + +The livetree implementation was originally designed for use with the control +FDT. This means that the FDT fix-ups (ft_board_setup() and the like, must use +a flat tree. + +It would be helpful to use livetree for fixups, since adding a lot of nodes and +properties would involve less memory copying and be more efficient. As a step +towards this, an `oftree` type has been introduced. It is normally set to +oftree_default() but can be set to other values. Eventually this should allow +the use of FDT fixups using the ofnode interface, instead of the low-level +libfdt one. + +See dm_test_ofnode_root() for some examples. Internal implementation @@ -281,6 +333,6 @@ Live tree support was introduced in U-Boot 2017.07. There is still quite a bit of work to do to flesh this out: - tests for all access functions -- support for livetree modification +- more support for livetree modification - addition of more access functions as needed - support for livetree in SPL and before relocation (if desired) diff --git a/doc/develop/index.rst b/doc/develop/index.rst index 7c41e3f1b6..c94c7fe0ef 100644 --- a/doc/develop/index.rst +++ b/doc/develop/index.rst @@ -39,6 +39,7 @@ Implementation smbios spl uefi/index + vbe version Debugging diff --git a/doc/develop/vbe.rst b/doc/develop/vbe.rst new file mode 100644 index 0000000000..8f147fd936 --- /dev/null +++ b/doc/develop/vbe.rst @@ -0,0 +1,26 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +Verified Boot for Embedded (VBE) +================================ + +Introduction +------------ + +VBE provides a standard boot mechanism for embedded systems. If defines +how firmware and Operating Systems are located, updated and verified. + +Within U-Boot, one or more VBE bootmeths implement the boot logic. For example, +the vbe-simple bootmeth handles finding the firmware (e.g. in MMC) and starting +it. Typically the bootmeth is started up in VPL and controls which SPL and +U-Boot binaries are loaded. + +A 'vbe' command provides access to various aspects of VBE's operation, including +listing methods and getting the status for a method. + +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-intro: https://docs.google.com/document/d/e/2PACX-1vQjXLPWMIyVktaTMf8edHZYDrEvMYD_iNzIj1FgPmKF37fpglAC47Tt5cvPBC5fvTdoK-GA5Zv1wifo/pub +.. _vbe-bootflows: https://docs.google.com/document/d/e/2PACX-1vR0OzhuyRJQ8kdeOibS3xB1rVFy3J4M_QKTM5-3vPIBNcdvR0W8EXu9ymG-yWfqthzWoM4JUNhqwydN/pub +.. _vbe-fwupdate: https://docs.google.com/document/d/e/2PACX-1vTnlIL17vVbl6TVoTHWYMED0bme7oHHNk-g5VGxblbPiKIdGDALE1HKId8Go5f0g1eziLsv4h9bocbk/pub diff --git a/doc/usage/cmd/bootmeth.rst b/doc/usage/cmd/bootmeth.rst index 9fc7ebf0ab..29d8215a0c 100644 --- a/doc/usage/cmd/bootmeth.rst +++ b/doc/usage/cmd/bootmeth.rst @@ -31,7 +31,9 @@ scanning bootdevs, each bootmeth is tried in turn to see if it can find a valid bootflow. You can use this command to adjust the order or even to omit some boomeths. -The argument is a quoted list of bootmeths to use, by name. +The argument is a quoted list of bootmeths to use, by name. If global bootmeths +are included, they must be at the end, otherwise the scanning mechanism will not +work correctly. bootmeth list @@ -47,14 +49,15 @@ Order Seq Name Description 1 1 efi EFI boot from an .efi file 2 2 pxe PXE boot from a network device 3 3 sandbox Sandbox boot for testing - 4 4 efi_mgr EFI bootmgr flow + glob 4 efi_mgr EFI bootmgr flow ===== === ================== ================================= The fields are as follows: Order: The order in which these bootmeths are invoked for each bootdev. If this - shows as a hyphen, then the bootmeth is not in the current ordering. + shows as a hyphen, then the bootmeth is not in the current ordering. If it + shows as 'glob', then this is a global bootmeth and should be at the end. Seq: The sequence number of the bootmeth, i.e. the normal ordering if none is set diff --git a/drivers/bios_emulator/atibios.c b/drivers/bios_emulator/atibios.c index cdc5ba6ad9..7ebead6bfa 100644 --- a/drivers/bios_emulator/atibios.c +++ b/drivers/bios_emulator/atibios.c @@ -51,7 +51,7 @@ #include #include #include -#include +#include #include #include "biosemui.h" @@ -83,13 +83,13 @@ static const void *bios_ptr(const void *buf, BE_VGAInfo *vga_info, } static int atibios_debug_mode(BE_VGAInfo *vga_info, RMREGS *regs, - int vesa_mode, struct vbe_mode_info *mode_info) + int vesa_mode, struct vesa_state *mode_info) { void *buffer = (void *)(M.mem_base + vbe_offset); u16 buffer_seg = (((unsigned long)vbe_offset) >> 4) & 0xff00; u16 buffer_adr = ((unsigned long)vbe_offset) & 0xffff; struct vesa_mode_info *vm; - struct vbe_info *info; + struct vesa_bios_ext_info *info; const u16 *modes_bios, *ptr; u16 *modes; int size; @@ -140,7 +140,7 @@ static int atibios_debug_mode(BE_VGAInfo *vga_info, RMREGS *regs, int attr; debug("Mode %x: ", mode); - memset(buffer, '\0', sizeof(struct vbe_mode_info)); + memset(buffer, '\0', sizeof(struct vesa_state)); regs->e.eax = VESA_GET_MODE_INFO; regs->e.ebx = 0; regs->e.ecx = mode; @@ -174,7 +174,7 @@ static int atibios_debug_mode(BE_VGAInfo *vga_info, RMREGS *regs, } static int atibios_set_vesa_mode(RMREGS *regs, int vesa_mode, - struct vbe_mode_info *mode_info) + struct vesa_state *mode_info) { void *buffer = (void *)(M.mem_base + vbe_offset); u16 buffer_seg = (((unsigned long)vbe_offset) >> 4) & 0xff00; @@ -192,7 +192,7 @@ static int atibios_set_vesa_mode(RMREGS *regs, int vesa_mode, return -ENOSYS; } - memset(buffer, '\0', sizeof(struct vbe_mode_info)); + memset(buffer, '\0', sizeof(struct vesa_state)); debug("VBE: Geting info for VESA mode %#04x\n", vesa_mode); regs->e.eax = VESA_GET_MODE_INFO; regs->e.ecx = vesa_mode; @@ -231,7 +231,7 @@ at this stage the controller has its I/O and memory space enabled and that all other controllers are in a disabled state. ****************************************************************************/ static void PCI_doBIOSPOST(struct udevice *pcidev, BE_VGAInfo *vga_info, - int vesa_mode, struct vbe_mode_info *mode_info) + int vesa_mode, struct vesa_state *mode_info) { RMREGS regs; RMSREGS sregs; @@ -416,7 +416,7 @@ image we can extract over the PCI bus. ****************************************************************************/ static int PCI_postController(struct udevice *pcidev, uchar *bios_rom, int bios_len, BE_VGAInfo *vga_info, - int vesa_mode, struct vbe_mode_info *mode_info) + int vesa_mode, struct vesa_state *mode_info) { u32 bios_image_len; uchar *mapped_bios; @@ -496,7 +496,7 @@ void biosemu_set_interrupt_handler(int intnum, int (*int_func)(void)) int biosemu_run(struct udevice *pcidev, uchar *bios_rom, int bios_len, BE_VGAInfo *vga_info, int clean_up, int vesa_mode, - struct vbe_mode_info *mode_info) + struct vesa_state *mode_info) { /*Post all the display controller BIOS'es*/ if (!PCI_postController(pcidev, bios_rom, bios_len, vga_info, diff --git a/drivers/core/of_access.c b/drivers/core/of_access.c index c20b19cb50..a52f5a6b18 100644 --- a/drivers/core/of_access.c +++ b/drivers/core/of_access.c @@ -343,24 +343,30 @@ static struct device_node *__of_find_node_by_path(struct device_node *parent, #define for_each_property_of_node(dn, pp) \ for (pp = dn->properties; pp != NULL; pp = pp->next) -struct device_node *of_find_node_opts_by_path(const char *path, +struct device_node *of_find_node_opts_by_path(struct device_node *root, + const char *path, const char **opts) { struct device_node *np = NULL; struct property *pp; const char *separator = strchr(path, ':'); + if (!root) + root = gd->of_root; if (opts) *opts = separator ? separator + 1 : NULL; if (strcmp(path, "/") == 0) - return of_node_get(gd->of_root); + return of_node_get(root); /* The path could begin with an alias */ if (*path != '/') { int len; const char *p = separator; + /* Only allow alias processing on the control FDT */ + if (root != gd->of_root) + return NULL; if (!p) p = strchrnul(path, '/'); len = p - path; @@ -383,7 +389,7 @@ struct device_node *of_find_node_opts_by_path(const char *path, /* Step down the tree matching path components */ if (!np) - np = of_node_get(gd->of_root); + np = of_node_get(root); while (np && *path == '/') { struct device_node *tmp = np; @@ -791,7 +797,7 @@ int of_alias_scan(void) name = of_get_property(of_chosen, "stdout-path", NULL); if (name) - of_stdout = of_find_node_opts_by_path(name, + of_stdout = of_find_node_opts_by_path(NULL, name, &of_stdout_options); } @@ -881,3 +887,46 @@ struct device_node *of_get_stdout(void) { return of_stdout; } + +int of_write_prop(struct device_node *np, const char *propname, int len, + const void *value) +{ + struct property *pp; + struct property *pp_last = NULL; + struct property *new; + + if (!np) + return -EINVAL; + + for (pp = np->properties; pp; pp = pp->next) { + if (strcmp(pp->name, propname) == 0) { + /* Property exists -> change value */ + pp->value = (void *)value; + pp->length = len; + return 0; + } + pp_last = pp; + } + + if (!pp_last) + return -ENOENT; + + /* Property does not exist -> append new property */ + new = malloc(sizeof(struct property)); + if (!new) + return -ENOMEM; + + new->name = strdup(propname); + if (!new->name) { + free(new); + return -ENOMEM; + } + + new->value = (void *)value; + new->length = len; + new->next = NULL; + + pp_last->next = new; + + return 0; +} diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index a59832ebbf..45ea84e9fb 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -552,6 +552,17 @@ ofnode ofnode_path(const char *path) return offset_to_ofnode(fdt_path_offset(gd->fdt_blob, path)); } +ofnode ofnode_path_root(oftree tree, const char *path) +{ + if (of_live_active()) + return np_to_ofnode(of_find_node_opts_by_path(tree.np, path, + NULL)); + else if (*path != '/' && tree.fdt != gd->fdt_blob) + return ofnode_null(); /* Aliases only on control FDT */ + else + return offset_to_ofnode(fdt_path_offset(tree.fdt, path)); +} + const void *ofnode_read_chosen_prop(const char *propname, int *sizep) { ofnode chosen_node; @@ -1094,70 +1105,44 @@ ofnode ofnode_by_prop_value(ofnode from, const char *propname, } } -int ofnode_write_prop(ofnode node, const char *propname, int len, - const void *value) +int ofnode_write_prop(ofnode node, const char *propname, const void *value, + int len) { - const struct device_node *np = ofnode_to_np(node); - struct property *pp; - struct property *pp_last = NULL; - struct property *new; - - if (!of_live_active()) - return -ENOSYS; - - if (!np) - return -EINVAL; - - for (pp = np->properties; pp; pp = pp->next) { - if (strcmp(pp->name, propname) == 0) { - /* Property exists -> change value */ - pp->value = (void *)value; - pp->length = len; - return 0; - } - pp_last = pp; - } - - if (!pp_last) - return -ENOENT; - - /* Property does not exist -> append new property */ - new = malloc(sizeof(struct property)); - if (!new) - return -ENOMEM; - - new->name = strdup(propname); - if (!new->name) { - free(new); - return -ENOMEM; - } - - new->value = (void *)value; - new->length = len; - new->next = NULL; - - pp_last->next = new; + if (of_live_active()) + return of_write_prop(ofnode_to_npw(node), propname, len, value); + else + return fdt_setprop((void *)gd->fdt_blob, ofnode_to_offset(node), + propname, value, len); return 0; } int ofnode_write_string(ofnode node, const char *propname, const char *value) { - if (!of_live_active()) - return -ENOSYS; - assert(ofnode_valid(node)); debug("%s: %s = %s", __func__, propname, value); - return ofnode_write_prop(node, propname, strlen(value) + 1, value); + return ofnode_write_prop(node, propname, value, strlen(value) + 1); +} + +int ofnode_write_u32(ofnode node, const char *propname, u32 value) +{ + fdt32_t *val; + + assert(ofnode_valid(node)); + + log_debug("%s = %x", propname, value); + val = malloc(sizeof(*val)); + if (!val) + return -ENOMEM; + *val = cpu_to_fdt32(value); + + return ofnode_write_prop(node, propname, val, sizeof(value)); } int ofnode_set_enabled(ofnode node, bool value) { - if (!of_live_active()) - return -ENOSYS; - assert(ofnode_valid(node)); if (value) diff --git a/drivers/pci/pci_rom.c b/drivers/pci/pci_rom.c index 73d15e797f..27a24daa12 100644 --- a/drivers/pci/pci_rom.c +++ b/drivers/pci/pci_rom.c @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include #include @@ -202,7 +202,7 @@ static int pci_rom_load(struct pci_rom_header *rom_header, return 0; } -struct vbe_mode_info mode_info; +struct vesa_state mode_info; void setup_video(struct screen_info *screen_info) { @@ -326,9 +326,9 @@ err: } #ifdef CONFIG_DM_VIDEO -int vbe_setup_video_priv(struct vesa_mode_info *vesa, - struct video_priv *uc_priv, - struct video_uc_plat *plat) +int vesa_setup_video_priv(struct vesa_mode_info *vesa, + struct video_priv *uc_priv, + struct video_uc_plat *plat) { if (!vesa->x_resolution) return log_msg_ret("No x resolution", -ENXIO); @@ -358,7 +358,7 @@ int vbe_setup_video_priv(struct vesa_mode_info *vesa, return 0; } -int vbe_setup_video(struct udevice *dev, int (*int15_handler)(void)) +int vesa_setup_video(struct udevice *dev, int (*int15_handler)(void)) { struct video_uc_plat *plat = dev_get_uclass_plat(dev); struct video_priv *uc_priv = dev_get_uclass_priv(dev); @@ -378,7 +378,7 @@ int vbe_setup_video(struct udevice *dev, int (*int15_handler)(void)) return ret; } - ret = vbe_setup_video_priv(&mode_info.vesa, uc_priv, plat); + ret = vesa_setup_video_priv(&mode_info.vesa, uc_priv, plat); if (ret) { if (ret == -ENFILE) { /* diff --git a/drivers/video/broadwell_igd.c b/drivers/video/broadwell_igd.c index 2551f162e8..6aa4e27071 100644 --- a/drivers/video/broadwell_igd.c +++ b/drivers/video/broadwell_igd.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -681,7 +681,7 @@ static int broadwell_igd_probe(struct udevice *dev) debug("%s: is_broadwell=%d\n", __func__, is_broadwell); ret = igd_pre_init(dev, is_broadwell); if (!ret) { - ret = vbe_setup_video(dev, broadwell_igd_int15_handler); + ret = vesa_setup_video(dev, broadwell_igd_int15_handler); if (ret) debug("failed to run video BIOS: %d\n", ret); } diff --git a/drivers/video/coreboot.c b/drivers/video/coreboot.c index 7237542c07..d2d87c75c8 100644 --- a/drivers/video/coreboot.c +++ b/drivers/video/coreboot.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -57,7 +57,7 @@ static int coreboot_video_probe(struct udevice *dev) goto err; } - ret = vbe_setup_video_priv(vesa, uc_priv, plat); + ret = vesa_setup_video_priv(vesa, uc_priv, plat); if (ret) { ret = log_msg_ret("setup", ret); goto err; diff --git a/drivers/video/efi.c b/drivers/video/efi.c index 5f9031f2ec..b11e42c0eb 100644 --- a/drivers/video/efi.c +++ b/drivers/video/efi.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include struct pixel { @@ -149,7 +149,7 @@ static int efi_video_probe(struct udevice *dev) if (ret) goto err; - ret = vbe_setup_video_priv(vesa, uc_priv, plat); + ret = vesa_setup_video_priv(vesa, uc_priv, plat); if (ret) goto err; diff --git a/drivers/video/ivybridge_igd.c b/drivers/video/ivybridge_igd.c index 1aa5317dd5..9264dd6770 100644 --- a/drivers/video/ivybridge_igd.c +++ b/drivers/video/ivybridge_igd.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -762,7 +762,7 @@ static int bd82x6x_video_probe(struct udevice *dev) rev = gma_func0_init(dev); if (rev < 0) return rev; - ret = vbe_setup_video(dev, int15_handler); + ret = vesa_setup_video(dev, int15_handler); if (ret) return ret; diff --git a/drivers/video/vesa.c b/drivers/video/vesa.c index 869e546973..cac3bb0c33 100644 --- a/drivers/video/vesa.c +++ b/drivers/video/vesa.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -17,7 +17,7 @@ static int vesa_video_probe(struct udevice *dev) ulong fbbase; int ret; - ret = vbe_setup_video(dev, NULL); + ret = vesa_setup_video(dev, NULL); if (ret) return log_ret(ret); diff --git a/include/bios_emul.h b/include/bios_emul.h index 72410dc794..a7e6d73972 100644 --- a/include/bios_emul.h +++ b/include/bios_emul.h @@ -36,14 +36,14 @@ typedef struct { u8 LowMem[1536]; } BE_VGAInfo; -struct vbe_mode_info; +struct vesa_state; int BootVideoCardBIOS(struct udevice *pcidev, BE_VGAInfo **pVGAInfo, int clean_up); /* Run a BIOS ROM natively (only supported on x86 machines) */ void bios_run_on_x86(struct udevice *dev, unsigned long addr, int vesa_mode, - struct vbe_mode_info *mode_info); + struct vesa_state *mode_info); /** * bios_set_interrupt_handler() - Install an interrupt handler for the BIOS @@ -61,6 +61,6 @@ int biosemu_setup(struct udevice *pcidev, BE_VGAInfo **pVGAInfo); int biosemu_run(struct udevice *dev, uchar *bios_rom, int bios_len, BE_VGAInfo *vga_info, int clean_up, int vesa_mode, - struct vbe_mode_info *mode_info); + struct vesa_state *mode_info); #endif diff --git a/include/bootflow.h b/include/bootflow.h index c30ba042a4..6aa3d1fff8 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -77,12 +77,14 @@ struct bootflow { * @BOOTFLOWF_SHOW: Show each bootdev before scanning it * @BOOTFLOWF_ALL: Return bootflows with errors as well * @BOOTFLOWF_SINGLE_DEV: Just scan one bootmeth + * @BOOTFLOWF_SKIP_GLOBAL: Don't scan global bootmeths */ enum bootflow_flags_t { BOOTFLOWF_FIXED = 1 << 0, BOOTFLOWF_SHOW = 1 << 1, BOOTFLOWF_ALL = 1 << 2, BOOTFLOWF_SINGLE_DEV = 1 << 3, + BOOTFLOWF_SKIP_GLOBAL = 1 << 4, }; /** @@ -102,8 +104,10 @@ enum bootflow_flags_t { * updated to a larger value, no less than the number of available partitions. * This ensures that iteration works through all partitions on the bootdev. * - * @flags: Flags to use (see enum bootflow_flags_t) - * @dev: Current bootdev + * @flags: Flags to use (see enum bootflow_flags_t). If BOOTFLOWF_GLOBAL_FIRST is + * enabled then the global bootmeths are being scanned, otherwise we have + * moved onto the bootdevs + * @dev: Current bootdev, NULL if none * @part: Current partition number (0 for whole device) * @method: Current bootmeth * @max_part: Maximum hardware partition number in @dev, 0 if there is no @@ -117,7 +121,11 @@ enum bootflow_flags_t { * with the first one on the list * @num_methods: Number of bootmeth devices in @method_order * @cur_method: Current method number, an index into @method_order - * @method_order: List of bootmeth devices to use, in order + * @first_glob_method: First global method, if any, else -1 + * @method_order: List of bootmeth devices to use, in order. The normal methods + * appear first, then the global ones, if any + * @doing_global: true if we are iterating through the global bootmeths (which + * happens before the normal ones) */ struct bootflow_iter { int flags; @@ -131,7 +139,9 @@ struct bootflow_iter { struct udevice **dev_order; int num_methods; int cur_method; + int first_glob_method; struct udevice **method_order; + bool doing_global; }; /** @@ -169,9 +179,9 @@ int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, * If @flags includes BOOTFLOWF_ALL then bootflows with errors are returned too * * @dev: Boot device to scan, NULL to work through all of them until it - * finds one that * can supply a bootflow + * finds one that can supply a bootflow * @iter: Place to store private info (inited by this call) - * @flags: Flags for bootdev (enum bootflow_flags_t) + * @flags: Flags for iterator (enum bootflow_flags_t) * @bflow: Place to put the bootflow if found * Return: 0 if found, -ENODEV if no device, other -ve on other error * (iteration can continue) diff --git a/include/bootmeth.h b/include/bootmeth.h index 484e503e33..50ded055f3 100644 --- a/include/bootmeth.h +++ b/include/bootmeth.h @@ -12,19 +12,48 @@ struct bootflow; struct bootflow_iter; struct udevice; +/** + * enum bootmeth_flags - Flags for bootmeths + * + * @BOOTMETHF_GLOBAL: bootmeth handles bootdev selection automatically + */ +enum bootmeth_flags { + BOOTMETHF_GLOBAL = BIT(0), +}; + /** * struct bootmeth_uc_plat - information the uclass keeps about each bootmeth * * @desc: A long description of the bootmeth + * @flags: Flags for this bootmeth (enum bootmeth_flags) */ struct bootmeth_uc_plat { const char *desc; + int flags; }; /** struct bootmeth_ops - Operations for boot methods */ struct bootmeth_ops { /** - * check_supported() - check if a bootmeth supports this bootflow + * get_state_desc() - get detailed state information + * + * Prodecues a textual description of the state of the bootmeth. This + * can include newline characters if it extends to multiple lines. It + * must be a nul-terminated string. + * + * This may involve reading state from the system, e.g. some data in + * the firmware area. + * + * @dev: Bootmethod device to check + * @buf: Buffer to place the info in (terminator must fit) + * @maxsize: Size of buffer + * Returns: 0 if OK, -ENOSPC is buffer is too small, other -ve error if + * something else went wrong + */ + int (*get_state_desc)(struct udevice *dev, char *buf, int maxsize); + + /** + * check_supported() - check if a bootmeth supports this bootdev * * This is optional. If not provided, the bootdev is assumed to be * supported @@ -91,6 +120,24 @@ struct bootmeth_ops { #define bootmeth_get_ops(dev) ((struct bootmeth_ops *)(dev)->driver->ops) +/** + * bootmeth_get_state_desc() - get detailed state information + * + * Prodecues a textual description of the state of the bootmeth. This + * can include newline characters if it extends to multiple lines. It + * must be a nul-terminated string. + * + * This may involve reading state from the system, e.g. some data in + * the firmware area. + * + * @dev: Bootmethod device to check + * @buf: Buffer to place the info in (terminator must fit) + * @maxsize: Size of buffer + * Returns: 0 if OK, -ENOSPC is buffer is too small, other -ve error if + * something else went wrong + */ +int bootmeth_get_state_desc(struct udevice *dev, char *buf, int maxsize); + /** * bootmeth_check() - check if a bootmeth supports this bootflow * @@ -162,10 +209,12 @@ int bootmeth_boot(struct udevice *dev, struct bootflow *bflow); * ordering there, then all bootmethods are added * * @iter: Iterator to update with the order + * @include_global: true to add the global bootmeths, in which case they appear + * first * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve * on other error */ -int bootmeth_setup_iter_order(struct bootflow_iter *iter); +int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global); /** * bootmeth_set_order() - Set the bootmeth order @@ -231,4 +280,16 @@ int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align); int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, const char *file_path, ulong addr, ulong *sizep); +/** + * bootmeth_get_bootflow() - Get a bootflow from a global bootmeth + * + * Check the bootmeth for a bootflow which can be used. In this case the + * bootmeth handles all bootdev selection, etc. + * + * @dev: bootmeth device to read from + * @bflow: Bootflow information + * @return 0 on success, -ve if a bootflow could not be found or had an error + */ +int bootmeth_get_bootflow(struct udevice *dev, struct bootflow *bflow); + #endif diff --git a/include/bootstd.h b/include/bootstd.h index b002365f4f..01be249d16 100644 --- a/include/bootstd.h +++ b/include/bootstd.h @@ -26,6 +26,7 @@ struct udevice; * @glob_head: Head for the global list of all bootflows across all bootdevs * @bootmeth_count: Number of bootmeth devices in @bootmeth_order * @bootmeth_order: List of bootmeth devices to use, in order, NULL-terminated + * @vbe_bootmeth: Currently selected VBE bootmeth, NULL if none */ struct bootstd_priv { const char **prefixes; @@ -35,6 +36,7 @@ struct bootstd_priv { struct list_head glob_head; int bootmeth_count; struct udevice **bootmeth_order; + struct udevice *vbe_bootmeth; }; /** diff --git a/include/dm/of_access.h b/include/dm/of_access.h index ec6e6e2c7c..5b7821d0a1 100644 --- a/include/dm/of_access.h +++ b/include/dm/of_access.h @@ -197,6 +197,11 @@ struct device_node *of_get_parent(const struct device_node *np); /** * of_find_node_opts_by_path() - Find a node matching a full OF path * + * Note that alias processing is only available on the control FDT (gd->of_root). + * For other trees it is skipped, so any attempt to obtain an alias will result + * in returning NULL. + * + * @root: Root node of the tree to use. If this is NULL, then gd->of_root is used * @path: Either the full path to match, or if the path does not start with * '/', the name of a property of the /aliases node (an alias). In the * case of an alias, the node matching the alias' value will be returned. @@ -210,12 +215,13 @@ struct device_node *of_get_parent(const struct device_node *np); * * Return: a node pointer or NULL if not found */ -struct device_node *of_find_node_opts_by_path(const char *path, +struct device_node *of_find_node_opts_by_path(struct device_node *root, + const char *path, const char **opts); static inline struct device_node *of_find_node_by_path(const char *path) { - return of_find_node_opts_by_path(path, NULL); + return of_find_node_opts_by_path(NULL, path, NULL); } /** @@ -513,4 +519,16 @@ int of_alias_get_highest_id(const char *stem); */ struct device_node *of_get_stdout(void); +/** + * of_write_prop() - Write a property to the device tree + * + * @np: device node to which the property value is to be written + * @propname: name of the property to write + * @value: value of the property + * @len: length of the property in bytes + * Returns: 0 if OK, -ve on error + */ +int of_write_prop(struct device_node *np, const char *propname, int len, + const void *value); + #endif diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h index bb60433124..7ce1e4c6d9 100644 --- a/include/dm/ofnode.h +++ b/include/dm/ofnode.h @@ -19,41 +19,7 @@ struct resource; -/** - * typedef union ofnode_union ofnode - reference to a device tree node - * - * This union can hold either a straightforward pointer to a struct device_node - * in the live device tree, or an offset within the flat device tree. In the - * latter case, the pointer value is just the integer offset within the flat DT. - * - * Thus we can reference nodes in both the live tree (once available) and the - * flat tree (until then). Functions are available to translate between an - * ofnode and either an offset or a `struct device_node *`. - * - * The reference can also hold a null offset, in which case the pointer value - * here is NULL. This corresponds to a struct device_node * value of - * NULL, or an offset of -1. - * - * There is no ambiguity as to whether ofnode holds an offset or a node - * pointer: when the live tree is active it holds a node pointer, otherwise it - * holds an offset. The value itself does not need to be unique and in theory - * the same value could point to a valid device node or a valid offset. We - * could arrange for a unique value to be used (e.g. by making the pointer - * point to an offset within the flat device tree in the case of an offset) but - * this increases code size slightly due to the subtraction. Since it offers no - * real benefit, the approach described here seems best. - * - * For now these points use constant types, since we don't allow writing - * the DT. - * - * @np: Pointer to device node, used for live tree - * @of_offset: Pointer into flat device tree, used for flat tree. Note that this - * is not a really a pointer to a node: it is an offset value. See above. - */ -typedef union ofnode_union { - const struct device_node *np; - long of_offset; -} ofnode; +#include struct ofnode_phandle_args { ofnode node; @@ -61,31 +27,6 @@ struct ofnode_phandle_args { uint32_t args[OF_MAX_PHANDLE_ARGS]; }; -/** - * struct ofprop - reference to a property of a device tree node - * - * This struct hold the reference on one property of one node, - * using struct ofnode and an offset within the flat device tree or either - * a pointer to a struct property in the live device tree. - * - * Thus we can reference arguments in both the live tree and the flat tree. - * - * The property reference can also hold a null reference. This corresponds to - * a struct property NULL pointer or an offset of -1. - * - * @node: Pointer to device node - * @offset: Pointer into flat device tree, used for flat tree. - * @prop: Pointer to property, used for live treee. - */ - -struct ofprop { - ofnode node; - union { - int offset; - const struct property *prop; - }; -}; - /** * ofnode_to_np() - convert an ofnode to a live DT node pointer * @@ -103,6 +44,24 @@ static inline const struct device_node *ofnode_to_np(ofnode node) return node.np; } +/** + * ofnode_to_npw() - convert an ofnode to a writeable live DT node pointer + * + * This cannot be called if the reference contains an offset. + * + * @node: Reference containing struct device_node * (possibly invalid) + * Return: pointer to device node (can be NULL) + */ +static inline struct device_node *ofnode_to_npw(ofnode node) +{ +#ifdef OF_CHECKS + if (!of_live_active()) + return NULL; +#endif + /* Drop constant */ + return (struct device_node *)node.np; +} + /** * ofnode_to_offset() - convert an ofnode to a flat DT offset * @@ -235,6 +194,23 @@ static inline ofnode ofnode_root(void) return node; } +/** + * oftree_default() - Returns the default device tree (U-Boot's control FDT) + * + * Returns: reference to the control FDT + */ +static inline oftree oftree_default(void) +{ + oftree tree; + + if (of_live_active()) + tree.np = gd_of_root(); + else + tree.fdt = (void *)gd->fdt_blob; + + return tree; +} + /** * ofnode_name_eq() - Check if the node name is equivalent to a given name * ignoring the unit address @@ -699,11 +675,22 @@ int ofnode_count_phandle_with_args(ofnode node, const char *list_name, /** * ofnode_path() - find a node by full path * + * This uses the control FDT. + * * @path: Full path to node, e.g. "/bus/spi@1" * Return: reference to the node found. Use ofnode_valid() to check if it exists */ ofnode ofnode_path(const char *path); +/** + * ofnode_path_root() - find a node by full path from a root node + * + * @tree: Device tree to use + * @path: Full path to node, e.g. "/bus/spi@1" + * Return: reference to the node found. Use ofnode_valid() to check if it exists + */ +ofnode ofnode_path_root(oftree tree, const char *path); + /** * ofnode_read_chosen_prop() - get the value of a chosen property * @@ -1140,17 +1127,18 @@ int ofnode_device_is_compatible(ofnode node, const char *compat); * ofnode_write_prop() - Set a property of a ofnode * * Note that the value passed to the function is *not* allocated by the - * function itself, but must be allocated by the caller if necessary. + * function itself, but must be allocated by the caller if necessary. However + * it does allocate memory for the property struct and name. * * @node: The node for whose property should be set * @propname: The name of the property to set - * @len: The length of the new value of the property * @value: The new value of the property (must be valid prior to calling * the function) + * @len: The length of the new value of the property * Return: 0 if successful, -ve on error */ -int ofnode_write_prop(ofnode node, const char *propname, int len, - const void *value); +int ofnode_write_prop(ofnode node, const char *propname, const void *value, + int len); /** * ofnode_write_string() - Set a string property of a ofnode @@ -1166,6 +1154,16 @@ int ofnode_write_prop(ofnode node, const char *propname, int len, */ int ofnode_write_string(ofnode node, const char *propname, const char *value); +/** + * ofnode_write_u32() - Set an integer property of an ofnode + * + * @node: The node for whose string property should be set + * @propname: The name of the string property to set + * @value: The new value of the 32-bit integer property + * Return: 0 if successful, -ve on error + */ +int ofnode_write_u32(ofnode node, const char *propname, u32 value); + /** * ofnode_set_enabled() - Enable or disable a device tree node given by its * ofnode diff --git a/include/dm/ofnode_decl.h b/include/dm/ofnode_decl.h new file mode 100644 index 0000000000..266253d5e3 --- /dev/null +++ b/include/dm/ofnode_decl.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#ifndef _DM_OFNODE_DECL_H +#define _DM_OFNODE_DECL_H + +/** + * typedef union ofnode_union ofnode - reference to a device tree node + * + * This union can hold either a straightforward pointer to a struct device_node + * in the live device tree, or an offset within the flat device tree. In the + * latter case, the pointer value is just the integer offset within the flat DT. + * + * Thus we can reference nodes in both the live tree (once available) and the + * flat tree (until then). Functions are available to translate between an + * ofnode and either an offset or a `struct device_node *`. + * + * The reference can also hold a null offset, in which case the pointer value + * here is NULL. This corresponds to a struct device_node * value of + * NULL, or an offset of -1. + * + * There is no ambiguity as to whether ofnode holds an offset or a node + * pointer: when the live tree is active it holds a node pointer, otherwise it + * holds an offset. The value itself does not need to be unique and in theory + * the same value could point to a valid device node or a valid offset. We + * could arrange for a unique value to be used (e.g. by making the pointer + * point to an offset within the flat device tree in the case of an offset) but + * this increases code size slightly due to the subtraction. Since it offers no + * real benefit, the approach described here seems best. + * + * For now these points use constant types, since we don't allow writing + * the DT. + * + * @np: Pointer to device node, used for live tree + * @of_offset: Pointer into flat device tree, used for flat tree. Note that this + * is not a really a pointer to a node: it is an offset value. See above. + */ +typedef union ofnode_union { + const struct device_node *np; + long of_offset; +} ofnode; + +/** + * struct ofprop - reference to a property of a device tree node + * + * This struct hold the reference on one property of one node, + * using struct ofnode and an offset within the flat device tree or either + * a pointer to a struct property in the live device tree. + * + * Thus we can reference arguments in both the live tree and the flat tree. + * + * The property reference can also hold a null reference. This corresponds to + * a struct property NULL pointer or an offset of -1. + * + * @node: Pointer to device node + * @offset: Pointer into flat device tree, used for flat tree. + * @prop: Pointer to property, used for live treee. + */ + +struct ofprop { + ofnode node; + union { + int offset; + const struct property *prop; + }; +}; + +/** + * union oftree_union - reference to a tree of device tree nodes + * + * One or other of the members is used, depending on of_live_active() + * + * @np: Pointer to roott device node, used for live tree + * @fdt: Pointer to the flat device tree, used for flat tree + */ +typedef union oftree_union { + struct device_node *np; + void *fdt; +} oftree; + +#endif + diff --git a/include/event.h b/include/event.h index c00c4fb68d..e8f2f55c63 100644 --- a/include/event.h +++ b/include/event.h @@ -10,6 +10,8 @@ #ifndef __event_h #define __event_h +#include + /** * enum event_t - Types of events supported by U-Boot * @@ -29,6 +31,9 @@ enum event_t { /* Init hooks */ EVT_MISC_INIT_F, + /* Device tree fixups before booting */ + EVT_FT_FIXUP, + EVT_COUNT }; @@ -50,6 +55,15 @@ union event_data { struct event_dm { struct udevice *dev; } dm; + + /** + * struct event_ft_fixup - FDT fixup before booting + * + * @tree: tree to update + */ + struct event_ft_fixup { + oftree tree; + } ft_fixup; }; /** @@ -123,10 +137,13 @@ static inline const char *event_spy_id(struct evspy_info *spy) * The only solution I can think of is to mark linker-list entries as 'used' * using an attribute. This should be safe, since we don't actually want to drop * any of these. However this does slightly limit LTO's optimisation choices. + * + * Another issue has come up, only with clang: using 'static' makes it throw + * away the linker-list entry sometimes, e.g. with the EVT_FT_FIXUP entry in + * vbe_simple.c - so for now, make it global. */ #define EVENT_SPY(_type, _func) \ - static __attribute__((used)) ll_entry_declare(struct evspy_info, \ - _type, evspy_info) = \ + __used ll_entry_declare(struct evspy_info, _type, evspy_info) = \ _ESPY_REC(_type, _func) /** diff --git a/include/of_live.h b/include/of_live.h index b2b9679ae8..f59d6af335 100644 --- a/include/of_live.h +++ b/include/of_live.h @@ -20,4 +20,20 @@ struct device_node; */ int of_live_build(const void *fdt_blob, struct device_node **rootp); +/** + * unflatten_device_tree() - create tree of device_nodes from flat blob + * + * Note that this allocates a single block of memory, pointed to by *mynodes. + * To free the tree, use free(*mynodes) + * + * unflattens a device-tree, creating the + * tree of struct device_node. It also fills the "name" and "type" + * pointers of the nodes so the normal device-tree walking functions + * can be used. + * @blob: The blob to expand + * @mynodes: The device_node tree created by the call + * Return: 0 if OK, -ve on error + */ +int unflatten_device_tree(const void *blob, struct device_node **mynodes); + #endif diff --git a/include/test/test.h b/include/test/test.h index 0104e189f6..c888d68b1e 100644 --- a/include/test/test.h +++ b/include/test/test.h @@ -46,6 +46,8 @@ enum { UT_TESTF_CONSOLE_REC = BIT(5), /* needs console recording */ /* do extra driver model init and uninit */ UT_TESTF_DM = BIT(6), + /* live or flat device tree, but not both in the same executable */ + UT_TESTF_LIVE_OR_FLAT = BIT(4), }; /** diff --git a/include/vbe.h b/include/vbe.h index 1631260eb7..b83f6f0c51 100644 --- a/include/vbe.h +++ b/include/vbe.h @@ -1,113 +1,48 @@ -/* SPDX-License-Identifier: BSD-2-Clause */ -/****************************************************************************** - * Copyright (c) 2004, 2008 IBM Corporation - * Copyright (c) 2009 Pattrick Hueper - * All rights reserved. +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Verified Boot for Embedded (VBE) support + * See doc/develop/vbe.rst * - * Contributors: - * IBM Corporation - initial implementation - *****************************************************************************/ -#ifndef _VBE_H -#define _VBE_H + * Copyright 2022 Google LLC + * Written by Simon Glass + */ -/* these structs are for input from and output to OF */ -struct __packed vbe_screen_info { - u8 display_type; /* 0=NONE, 1= analog, 2=digital */ - u16 screen_width; - u16 screen_height; - /* bytes per line in framebuffer, may be more than screen_width */ - u16 screen_linebytes; - u8 color_depth; /* color depth in bits per pixel */ - u32 framebuffer_address; - u8 edid_block_zero[128]; -}; +#ifndef __VBE_H +#define __VBE_H -struct __packed vbe_screen_info_input { - u8 signature[4]; - u16 size_reserved; - u8 monitor_number; - u16 max_screen_width; - u8 color_depth; -}; +/** + * vbe_list() - List the VBE bootmeths + * + * This shows a list of the VBE bootmeth devices + * + * @return 0 (always) + */ +int vbe_list(void); -/* these structs only store the required a subset of the VBE-defined fields */ -struct __packed vbe_info { - char signature[4]; - u16 version; - u32 oem_string_ptr; - u32 capabilities; - u32 modes_ptr; - u16 total_memory; - u16 oem_version; - u32 vendor_name_ptr; - u32 product_name_ptr; - u32 product_rev_ptr; -}; +/** + * vbe_find_by_any() - Find a VBE bootmeth by name or sequence + * + * @name: name (e.g. "vbe-simple"), or sequence ("2") to find + * @devp: returns the device found, on success + * Return: 0 if OK, -ve on error + */ +int vbe_find_by_any(const char *name, struct udevice **devp); -struct __packed vesa_mode_info { - u16 mode_attributes; /* 00 */ - u8 win_a_attributes; /* 02 */ - u8 win_b_attributes; /* 03 */ - u16 win_granularity; /* 04 */ - u16 win_size; /* 06 */ - u16 win_a_segment; /* 08 */ - u16 win_b_segment; /* 0a */ - u32 win_func_ptr; /* 0c */ - u16 bytes_per_scanline; /* 10 */ - u16 x_resolution; /* 12 */ - u16 y_resolution; /* 14 */ - u8 x_charsize; /* 16 */ - u8 y_charsize; /* 17 */ - u8 number_of_planes; /* 18 */ - u8 bits_per_pixel; /* 19 */ - u8 number_of_banks; /* 20 */ - u8 memory_model; /* 21 */ - u8 bank_size; /* 22 */ - u8 number_of_image_pages; /* 23 */ - u8 reserved_page; - u8 red_mask_size; - u8 red_mask_pos; - u8 green_mask_size; - u8 green_mask_pos; - u8 blue_mask_size; - u8 blue_mask_pos; - u8 reserved_mask_size; - u8 reserved_mask_pos; - u8 direct_color_mode_info; - u32 phys_base_ptr; - u32 offscreen_mem_offset; - u16 offscreen_mem_size; - u8 reserved[206]; -}; +/** + * vbe_find_first_device() - Find the first VBE bootmeth + * + * @devp: Returns first available VBE bootmeth, or NULL if none + * Returns: 0 (always) + */ +int vbe_find_first_device(struct udevice **devp); -struct vbe_mode_info { - u16 video_mode; - bool valid; - union { - struct vesa_mode_info vesa; - u8 mode_info_block[256]; - }; -}; - -struct vbe_ddc_info { - u8 port_number; /* i.e. monitor number */ - u8 edid_transfer_time; - u8 ddc_level; - u8 edid_block_zero[128]; -}; - -#define VESA_GET_INFO 0x4f00 -#define VESA_GET_MODE_INFO 0x4f01 -#define VESA_SET_MODE 0x4f02 -#define VESA_GET_CUR_MODE 0x4f03 - -extern struct vbe_mode_info mode_info; - -struct video_priv; -struct video_uc_plat; -int vbe_setup_video_priv(struct vesa_mode_info *vesa, - struct video_priv *uc_priv, - struct video_uc_plat *plat); -int vbe_setup_video(struct udevice *dev, int (*int15_handler)(void)); +/** + * vbe_find_next_device() - Find the next available VBE bootmeth + * + * @devp: Previous device to start from. Returns next available VBE bootmeth, + * or NULL if none + * Returns: 0 (always) + */ +int vbe_find_next_device(struct udevice **devp); #endif diff --git a/include/vesa.h b/include/vesa.h new file mode 100644 index 0000000000..a42c179686 --- /dev/null +++ b/include/vesa.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/****************************************************************************** + * Copyright (c) 2004, 2008 IBM Corporation + * Copyright (c) 2009 Pattrick Hueper + * All rights reserved. + * + * Contributors: + * IBM Corporation - initial implementation + *****************************************************************************/ +#ifndef _VESA_H +#define _VESA_H + +/* these structs are for input from and output to OF */ +struct __packed vesa_screen_info { + u8 display_type; /* 0=NONE, 1= analog, 2=digital */ + u16 screen_width; + u16 screen_height; + /* bytes per line in framebuffer, may be more than screen_width */ + u16 screen_linebytes; + u8 color_depth; /* color depth in bits per pixel */ + u32 framebuffer_address; + u8 edid_block_zero[128]; +}; + +struct __packed vesa_screen_info_input { + u8 signature[4]; + u16 size_reserved; + u8 monitor_number; + u16 max_screen_width; + u8 color_depth; +}; + +/* + * These structs only store the required subset of fields in Vesa BIOS + * Extensions + */ +struct __packed vesa_bios_ext_info { + char signature[4]; + u16 version; + u32 oem_string_ptr; + u32 capabilities; + u32 modes_ptr; + u16 total_memory; + u16 oem_version; + u32 vendor_name_ptr; + u32 product_name_ptr; + u32 product_rev_ptr; +}; + +struct __packed vesa_mode_info { + u16 mode_attributes; /* 00 */ + u8 win_a_attributes; /* 02 */ + u8 win_b_attributes; /* 03 */ + u16 win_granularity; /* 04 */ + u16 win_size; /* 06 */ + u16 win_a_segment; /* 08 */ + u16 win_b_segment; /* 0a */ + u32 win_func_ptr; /* 0c */ + u16 bytes_per_scanline; /* 10 */ + u16 x_resolution; /* 12 */ + u16 y_resolution; /* 14 */ + u8 x_charsize; /* 16 */ + u8 y_charsize; /* 17 */ + u8 number_of_planes; /* 18 */ + u8 bits_per_pixel; /* 19 */ + u8 number_of_banks; /* 20 */ + u8 memory_model; /* 21 */ + u8 bank_size; /* 22 */ + u8 number_of_image_pages; /* 23 */ + u8 reserved_page; + u8 red_mask_size; + u8 red_mask_pos; + u8 green_mask_size; + u8 green_mask_pos; + u8 blue_mask_size; + u8 blue_mask_pos; + u8 reserved_mask_size; + u8 reserved_mask_pos; + u8 direct_color_mode_info; + u32 phys_base_ptr; + u32 offscreen_mem_offset; + u16 offscreen_mem_size; + u8 reserved[206]; +}; + +struct vesa_state { + u16 video_mode; + bool valid; + union { + struct vesa_mode_info vesa; + u8 mode_info_block[256]; + }; +}; + +struct vesa_ddc_info { + u8 port_number; /* i.e. monitor number */ + u8 edid_transfer_time; + u8 ddc_level; + u8 edid_block_zero[128]; +}; + +#define VESA_GET_INFO 0x4f00 +#define VESA_GET_MODE_INFO 0x4f01 +#define VESA_SET_MODE 0x4f02 +#define VESA_GET_CUR_MODE 0x4f03 + +extern struct vesa_state mode_info; + +struct video_priv; +struct video_uc_plat; +int vesa_setup_video_priv(struct vesa_mode_info *vesa, + struct video_priv *uc_priv, + struct video_uc_plat *plat); +int vesa_setup_video(struct udevice *dev, int (*int15_handler)(void)); + +#endif diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index e3f2402d0e..5cfff8c56b 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -37,6 +37,7 @@ if EFI_LOADER config CMD_BOOTEFI_BOOTMGR bool "UEFI Boot Manager" default y + select BOOTMETH_GLOBAL if BOOTSTD help Select this option if you want to select the UEFI binary to be booted via UEFI variables Boot####, BootOrder, and BootNext. This enables the diff --git a/lib/elf.c b/lib/elf.c index d074e4e0a7..0476b2614c 100644 --- a/lib/elf.c +++ b/lib/elf.c @@ -11,7 +11,7 @@ #include #include #ifdef CONFIG_X86 -#include +#include #include #include #endif diff --git a/lib/of_live.c b/lib/of_live.c index 2cb0dd9c07..30cae9ab88 100644 --- a/lib/of_live.c +++ b/lib/of_live.c @@ -248,19 +248,7 @@ static void *unflatten_dt_node(const void *blob, void *mem, int *poffset, return mem; } -/** - * unflatten_device_tree() - create tree of device_nodes from flat blob - * - * unflattens a device-tree, creating the - * tree of struct device_node. It also fills the "name" and "type" - * pointers of the nodes so the normal device-tree walking functions - * can be used. - * @blob: The blob to expand - * @mynodes: The device_node tree created by the call - * Return: 0 if OK, -ve on error - */ -static int unflatten_device_tree(const void *blob, - struct device_node **mynodes) +int unflatten_device_tree(const void *blob, struct device_node **mynodes) { unsigned long size; int start; diff --git a/test/boot/Makefile b/test/boot/Makefile index 1730792b5f..9e9d5ae21f 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 1ebb789e97..85305234e0 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -9,13 +9,33 @@ #include #include #include +#include #include #include +#ifdef CONFIG_SANDBOX +#include +#endif #include #include #include #include "bootstd_common.h" +static int inject_response(struct unit_test_state *uts) +{ + /* + * The image being booted presents a menu of options: + * + * Fedora-Workstation-armhfp-31-1.9 Boot Options. + * 1: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl) + * Enter choice: + * + * Provide input for this, to avoid waiting two seconds for a timeout. + */ + ut_asserteq(2, console_in_puts("1\n")); + + return 0; +} + /* Check 'bootflow scan/list' commands */ static int bootflow_cmd(struct unit_test_state *uts) { @@ -73,7 +93,7 @@ static int bootflow_cmd_glob(struct unit_test_state *uts) ut_assertok(bootstd_test_drop_bootdev_order(uts)); console_record_reset_enable(); - ut_assertok(run_command("bootflow scan -l", 0)); + ut_assertok(run_command("bootflow scan -lG", 0)); ut_assert_nextline("Scanning for bootflows in all bootdevs"); ut_assert_nextline("Seq Method State Uclass Part Name Filename"); ut_assert_nextlinen("---"); @@ -105,7 +125,7 @@ static int bootflow_cmd_scan_e(struct unit_test_state *uts) ut_assertok(bootstd_test_drop_bootdev_order(uts)); console_record_reset_enable(); - ut_assertok(run_command("bootflow scan -ale", 0)); + ut_assertok(run_command("bootflow scan -aleG", 0)); ut_assert_nextline("Scanning for bootflows in all bootdevs"); ut_assert_nextline("Seq Method State Uclass Part Name Filename"); ut_assert_nextlinen("---"); @@ -188,6 +208,7 @@ BOOTSTD_TEST(bootflow_cmd_info, UT_TESTF_DM | UT_TESTF_SCAN_FDT); static int bootflow_scan_boot(struct unit_test_state *uts) { console_record_reset_enable(); + ut_assertok(inject_response(uts)); ut_assertok(run_command("bootflow scan -b", 0)); ut_assert_nextline( "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux"); @@ -215,7 +236,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)); + bootflow_scan_first(&iter, BOOTFLOWF_ALL | BOOTFLOWF_SKIP_GLOBAL, &bflow)); ut_asserteq(2, iter.num_methods); ut_asserteq(0, iter.cur_method); ut_asserteq(0, iter.part); @@ -224,7 +245,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. @@ -302,34 +323,31 @@ static int bootflow_iter(struct unit_test_state *uts) } BOOTSTD_TEST(bootflow_iter, UT_TESTF_DM | UT_TESTF_SCAN_FDT); +#if defined(CONFIG_SANDBOX) && defined(CONFIG_BOOTMETH_GLOBAL) /* Check using the system bootdev */ static int bootflow_system(struct unit_test_state *uts) { - struct udevice *bootstd, *dev; + struct udevice *dev; - /* Add the EFI bootmgr driver */ - ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); - ut_assertok(device_bind_driver(bootstd, "bootmeth_efi_mgr", "bootmgr", - &dev)); - - /* Add the system bootdev that it uses */ - ut_assertok(device_bind_driver(bootstd, "system_bootdev", - "system-bootdev", &dev)); - - ut_assertok(bootstd_test_drop_bootdev_order(uts)); + ut_assertok(uclass_get_device_by_name(UCLASS_BOOTMETH, "efi_mgr", + &dev)); + sandbox_set_fake_efi_mgr_dev(dev, true); /* We should get a single 'bootmgr' method right at the end */ bootstd_clear_glob(); console_record_reset_enable(); ut_assertok(run_command("bootflow scan -l", 0)); - ut_assert_skip_to_line(" 1 bootmgr ready bootstd 0 "); - ut_assert_nextline("No more bootdevs"); - ut_assert_skip_to_line("(2 bootflows, 2 valid)"); + ut_assert_skip_to_line( + " 0 efi_mgr ready (none) 0 "); + ut_assert_skip_to_line("No more bootdevs"); + ut_assert_skip_to_line("(5 bootflows, 5 valid)"); ut_assert_console_end(); return 0; } -BOOTSTD_TEST(bootflow_system, UT_TESTF_DM | UT_TESTF_SCAN_FDT); +BOOTSTD_TEST(bootflow_system, UT_TESTF_DM | UT_TESTF_SCAN_PDATA | + UT_TESTF_SCAN_FDT); +#endif /* Check disabling a bootmethod if it requests it */ static int bootflow_iter_disable(struct unit_test_state *uts) @@ -344,13 +362,11 @@ static int bootflow_iter_disable(struct unit_test_state *uts) ut_assertok(device_bind_driver(bootstd, "bootmeth_sandbox", "sandbox", &dev)); - /* Add the system bootdev that it uses */ - ut_assertok(device_bind_driver(bootstd, "system_bootdev", - "system-bootdev", &dev)); - ut_assertok(bootstd_test_drop_bootdev_order(uts)); bootstd_clear_glob(); + console_record_reset_enable(); + ut_assertok(inject_response(uts)); ut_assertok(run_command("bootflow scan -lb", 0)); /* Try to boot the bootmgr flow, which will fail */ @@ -358,6 +374,7 @@ static int bootflow_iter_disable(struct unit_test_state *uts) ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); ut_asserteq(3, iter.num_methods); ut_asserteq_str("sandbox", iter.method->name); + ut_assertok(inject_response(uts)); ut_asserteq(-ENOTSUPP, bootflow_run_boot(&iter, &bflow)); ut_assert_skip_to_line("Boot method 'sandbox' failed and will not be retried"); @@ -372,6 +389,47 @@ static int bootflow_iter_disable(struct unit_test_state *uts) } BOOTSTD_TEST(bootflow_iter_disable, UT_TESTF_DM | UT_TESTF_SCAN_FDT); +/* Check 'bootflow scan' with a bootmeth ordering including a global bootmeth */ +static int bootflow_scan_glob_bootmeth(struct unit_test_state *uts) +{ + if (!IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) + return 0; + + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + /* + * Make sure that the -G flag makes the scan fail, since this is not + * supported when an ordering is provided + */ + console_record_reset_enable(); + ut_assertok(bootmeth_set_order("efi firmware0")); + ut_assertok(run_command("bootflow scan -lG", 0)); + ut_assert_nextline("Scanning for bootflows in all bootdevs"); + ut_assert_nextline( + "Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(0 bootflows, 0 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow scan -l", 0)); + ut_assert_nextline("Scanning for bootflows in all bootdevs"); + ut_assert_nextline( + "Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline("Scanning global bootmeth 'firmware0':"); + ut_assert_nextline("Scanning bootdev 'mmc2.bootdev':"); + ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); + ut_assert_nextline("Scanning bootdev 'mmc0.bootdev':"); + ut_assert_nextline("No more bootdevs"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(0 bootflows, 0 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_scan_glob_bootmeth, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + /* Check 'bootflow boot' to boot a selected bootflow */ static int bootflow_cmd_boot(struct unit_test_state *uts) { @@ -382,6 +440,8 @@ static int bootflow_cmd_boot(struct unit_test_state *uts) ut_assert_console_end(); ut_assertok(run_command("bootflow select 0", 0)); ut_assert_console_end(); + + ut_assertok(inject_response(uts)); ut_asserteq(1, run_command("bootflow boot", 0)); ut_assert_nextline( "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux"); diff --git a/test/boot/bootmeth.c b/test/boot/bootmeth.c index 07776c5368..fb62731339 100644 --- a/test/boot/bootmeth.c +++ b/test/boot/bootmeth.c @@ -1,13 +1,15 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Test for bootdev functions. All start with 'bootdev' + * Test for bootdev functions. All start with 'bootmeth' * * Copyright 2021 Google LLC * Written by Simon Glass */ #include +#include #include +#include #include #include #include "bootstd_common.h" @@ -21,8 +23,11 @@ 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"); + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) + ut_assert_nextline(" glob 2 firmware0 VBE simple"); ut_assert_nextlinen("---"); - ut_assert_nextline("(2 bootmeths)"); + ut_assert_nextline(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? + "(3 bootmeths)" : "(2 bootmeths)"); ut_assert_console_end(); return 0; @@ -54,8 +59,11 @@ 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"); + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) + ut_assert_nextline(" glob 2 firmware0 VBE simple"); ut_assert_nextlinen("---"); - ut_assert_nextline("(2 bootmeths)"); + ut_assert_nextline(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? + "(3 bootmeths)" : "(2 bootmeths)"); ut_assert_console_end(); /* Check the -a flag with the reverse order */ @@ -66,8 +74,11 @@ 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"); + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) + ut_assert_nextline(" glob 2 firmware0 VBE simple"); ut_assert_nextlinen("---"); - ut_assert_nextline("(2 bootmeths)"); + ut_assert_nextline(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? + "(3 bootmeths)" : "(2 bootmeths)"); ut_assert_console_end(); /* Now reset the order to empty, which should show all of them again */ @@ -75,7 +86,8 @@ 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(IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) ? + "(3 bootmeths)" : "(2 bootmeths)"); /* Try reverse order */ ut_assertok(run_command("bootmeth order \"efi syslinux\"", 0)); @@ -91,6 +103,23 @@ static int bootmeth_cmd_order(struct unit_test_state *uts) ut_asserteq_str("efi syslinux", env_get("bootmeths")); ut_assert_console_end(); + /* Try with global bootmeths */ + if (!IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) + return 0; + + ut_assertok(run_command("bootmeth order \"efi firmware0\"", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootmeth list", 0)); + 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_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assertnonnull(env_get("bootmeths")); + ut_asserteq_str("efi firmware0", env_get("bootmeths")); + ut_assert_console_end(); + return 0; } BOOTSTD_TEST(bootmeth_cmd_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT); @@ -120,3 +149,19 @@ static int bootmeth_env(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootmeth_env, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check the get_state_desc() method */ +static int bootmeth_state(struct unit_test_state *uts) +{ + struct udevice *dev; + char buf[50]; + + ut_assertok(uclass_first_device(UCLASS_BOOTMETH, &dev)); + ut_assertnonnull(dev); + + ut_assertok(bootmeth_get_state_desc(dev, buf, sizeof(buf))); + ut_asserteq_str("OK", buf); + + return 0; +} +BOOTSTD_TEST(bootmeth_state, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/vbe_simple.c b/test/boot/vbe_simple.c new file mode 100644 index 0000000000..2f6979cafc --- /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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c index 61ae1db62d..f80993f892 100644 --- a/test/dm/ofnode.c +++ b/test/dm/ofnode.c @@ -3,8 +3,13 @@ #include #include #include +#include +#include +#include #include +#include #include +#include #include #include @@ -469,3 +474,130 @@ static int dm_test_ofnode_get_phy(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_ofnode_get_phy, 0); + +/** + * make_ofnode_fdt() - Create an FDT for testing with ofnode + * + * The size is set to the minimum needed + * + * @uts: Test state + * @fdt: Place to write FDT + * @size: Maximum size of space for fdt + */ +static int make_ofnode_fdt(struct unit_test_state *uts, void *fdt, int size) +{ + ut_assertok(fdt_create(fdt, size)); + ut_assertok(fdt_finish_reservemap(fdt)); + ut_assert(fdt_begin_node(fdt, "") >= 0); + + ut_assert(fdt_begin_node(fdt, "aliases") >= 0); + ut_assertok(fdt_property_string(fdt, "mmc0", "/new-mmc")); + ut_assertok(fdt_end_node(fdt)); + + ut_assert(fdt_begin_node(fdt, "new-mmc") >= 0); + ut_assertok(fdt_end_node(fdt)); + + ut_assertok(fdt_end_node(fdt)); + ut_assertok(fdt_finish(fdt)); + + return 0; +} + +static int dm_test_ofnode_root(struct unit_test_state *uts) +{ + struct device_node *root = NULL; + char fdt[256]; + oftree tree; + ofnode node; + + /* Check that aliases work on the control FDT */ + node = ofnode_get_aliases_node("ethernet3"); + ut_assert(ofnode_valid(node)); + ut_asserteq_str("sbe5", ofnode_get_name(node)); + + ut_assertok(make_ofnode_fdt(uts, fdt, sizeof(fdt))); + if (of_live_active()) { + ut_assertok(unflatten_device_tree(fdt, &root)); + tree.np = root; + } else { + tree.fdt = fdt; + } + + /* Make sure they don't work on this new tree */ + node = ofnode_path_root(tree, "mmc0"); + ut_assert(!ofnode_valid(node)); + + /* It should appear in the new tree */ + node = ofnode_path_root(tree, "/new-mmc"); + ut_assert(ofnode_valid(node)); + + /* ...and not in the control FDT */ + node = ofnode_path_root(oftree_default(), "/new-mmc"); + ut_assert(!ofnode_valid(node)); + + free(root); + + return 0; +} +DM_TEST(dm_test_ofnode_root, UT_TESTF_SCAN_FDT); + +static int dm_test_ofnode_livetree_writing(struct unit_test_state *uts) +{ + struct udevice *dev; + ofnode node; + + /* Test enabling devices */ + node = ofnode_path("/usb@2"); + + ut_assert(!ofnode_is_enabled(node)); + ut_assertok(ofnode_set_enabled(node, true)); + ut_asserteq(true, ofnode_is_enabled(node)); + + device_bind_driver_to_node(dm_root(), "usb_sandbox", "usb@2", node, + &dev); + ut_assertok(uclass_find_device_by_seq(UCLASS_USB, 2, &dev)); + + /* Test string property setting */ + ut_assert(device_is_compatible(dev, "sandbox,usb")); + ofnode_write_string(node, "compatible", "gdsys,super-usb"); + ut_assert(device_is_compatible(dev, "gdsys,super-usb")); + ofnode_write_string(node, "compatible", "sandbox,usb"); + ut_assert(device_is_compatible(dev, "sandbox,usb")); + + /* Test setting generic properties */ + + /* Non-existent in DTB */ + ut_asserteq_64(FDT_ADDR_T_NONE, dev_read_addr(dev)); + /* reg = 0x42, size = 0x100 */ + ut_assertok(ofnode_write_prop(node, "reg", + "\x00\x00\x00\x42\x00\x00\x01\x00", 8)); + ut_asserteq(0x42, dev_read_addr(dev)); + + /* Test disabling devices */ + device_remove(dev, DM_REMOVE_NORMAL); + device_unbind(dev); + + ut_assert(ofnode_is_enabled(node)); + ut_assertok(ofnode_set_enabled(node, false)); + ut_assert(!ofnode_is_enabled(node)); + + return 0; +} +DM_TEST(dm_test_ofnode_livetree_writing, + UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT | UT_TESTF_LIVE_OR_FLAT); + +static int dm_test_ofnode_u32(struct unit_test_state *uts) +{ + ofnode node; + + node = ofnode_path("/lcd"); + ut_assert(ofnode_valid(node)); + ut_asserteq(1366, ofnode_read_u32_default(node, "xres", 123)); + ut_assertok(ofnode_write_u32(node, "xres", 1367)); + ut_asserteq(1367, ofnode_read_u32_default(node, "xres", 123)); + ut_assertok(ofnode_write_u32(node, "xres", 1366)); + + return 0; +} +DM_TEST(dm_test_ofnode_u32, + UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT | UT_TESTF_LIVE_OR_FLAT); diff --git a/test/dm/test-fdt.c b/test/dm/test-fdt.c index f9e8174759..6118ad42ca 100644 --- a/test/dm/test-fdt.c +++ b/test/dm/test-fdt.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -735,58 +734,6 @@ static int dm_test_fdt_remap_addr_name_live(struct unit_test_state *uts) DM_TEST(dm_test_fdt_remap_addr_name_live, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); -static int dm_test_fdt_livetree_writing(struct unit_test_state *uts) -{ - struct udevice *dev; - ofnode node; - - if (!of_live_active()) { - printf("Live tree not active; ignore test\n"); - return 0; - } - - /* Test enabling devices */ - - node = ofnode_path("/usb@2"); - - ut_assert(!of_device_is_available(ofnode_to_np(node))); - ofnode_set_enabled(node, true); - ut_assert(of_device_is_available(ofnode_to_np(node))); - - device_bind_driver_to_node(dm_root(), "usb_sandbox", "usb@2", node, - &dev); - ut_assertok(uclass_find_device_by_seq(UCLASS_USB, 2, &dev)); - - /* Test string property setting */ - - ut_assert(device_is_compatible(dev, "sandbox,usb")); - ofnode_write_string(node, "compatible", "gdsys,super-usb"); - ut_assert(device_is_compatible(dev, "gdsys,super-usb")); - ofnode_write_string(node, "compatible", "sandbox,usb"); - ut_assert(device_is_compatible(dev, "sandbox,usb")); - - /* Test setting generic properties */ - - /* Non-existent in DTB */ - ut_asserteq_64(FDT_ADDR_T_NONE, dev_read_addr(dev)); - /* reg = 0x42, size = 0x100 */ - ut_assertok(ofnode_write_prop(node, "reg", 8, - "\x00\x00\x00\x42\x00\x00\x01\x00")); - ut_asserteq(0x42, dev_read_addr(dev)); - - /* Test disabling devices */ - - device_remove(dev, DM_REMOVE_NORMAL); - device_unbind(dev); - - ut_assert(of_device_is_available(ofnode_to_np(node))); - ofnode_set_enabled(node, false); - ut_assert(!of_device_is_available(ofnode_to_np(node))); - - return 0; -} -DM_TEST(dm_test_fdt_livetree_writing, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); - static int dm_test_fdt_disable_enable_by_path(struct unit_test_state *uts) { ofnode node; diff --git a/test/py/tests/test_event_dump.py b/test/py/tests/test_event_dump.py index b753e804ac..bc54149e8f 100644 --- a/test/py/tests/test_event_dump.py +++ b/test/py/tests/test_event_dump.py @@ -16,5 +16,6 @@ def test_event_dump(u_boot_console): out = util.run_and_log(cons, ['scripts/event_dump.py', sandbox]) expect = '''.*Event type Id Source location -------------------- ------------------------------ ------------------------------ +EVT_FT_FIXUP bootmeth_vbe_simple_ft_fixup .*boot/vbe_simple.c:.* EVT_MISC_INIT_F sandbox_misc_init_f .*arch/sandbox/cpu/start.c:''' assert re.match(expect, out, re.MULTILINE) is not None diff --git a/test/test-main.c b/test/test-main.c index ee38d1faea..31837e57a8 100644 --- a/test/test-main.c +++ b/test/test-main.c @@ -228,8 +228,10 @@ static int test_pre_run(struct unit_test_state *uts, struct unit_test *test) uts->start = mallinfo(); - if (test->flags & UT_TESTF_SCAN_PDATA) + if (test->flags & UT_TESTF_SCAN_PDATA) { ut_assertok(dm_scan_plat(false)); + ut_assertok(dm_scan_other(false)); + } if (test->flags & UT_TESTF_PROBE_TEST) ut_assertok(do_autoprobe(uts)); @@ -338,7 +340,8 @@ static int ut_run_test_live_flat(struct unit_test_state *uts, /* Run with the live tree if possible */ runs = 0; if (CONFIG_IS_ENABLED(OF_LIVE)) { - if (!(test->flags & UT_TESTF_FLAT_TREE)) { + if (!(test->flags & + (UT_TESTF_FLAT_TREE | UT_TESTF_LIVE_OR_FLAT))) { uts->of_live = true; ut_assertok(ut_run_test(uts, test, test->name)); runs++;