diff options
86 files changed, 7188 insertions, 110 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 8e49a84..212d577 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -694,6 +694,27 @@ M: Alper Nebi Yasak <alpernebiyasak@gmail.com> S: Maintained F: tools/binman/ +BOOTDEVICE +M: Simon Glass <sjg@chromium.org> +S: Maintained +F: boot/bootdev*.c +F: boot/bootflow.c +F: boot/bootmeth*.c +F: boot/bootstd.c +F: cmd/bootdev.c +F: cmd/bootflow.c +F: doc/develop/bootstd.rst +F: doc/usage/bootdev.rst +F: doc/usage/bootflow.rst +F: doc/usage/bootmeth.rst +F: drivers/mmc/mmc_bootdev.c +F: include/bootdev.h +F: include/bootflow.h +F: include/bootmeth.h +F: include/bootstd.h +F: net/eth_bootdevice.c +F: test/boot/ + BTRFS M: Marek Behun <marek.behun@nic.cz> R: Qu Wenruo <wqu@suse.com> diff --git a/arch/sandbox/dts/sandbox.dts b/arch/sandbox/dts/sandbox.dts index 127f168..18fde1c 100644 --- a/arch/sandbox/dts/sandbox.dts +++ b/arch/sandbox/dts/sandbox.dts @@ -66,6 +66,10 @@ fake-host-hwaddr = [00 00 66 44 22 00]; }; + host-fs { + compatible = "sandbox,bootdev-host"; + }; + i2c_0: i2c@0 { #address-cells = <1>; #size-cells = <0>; diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi index 826db26..29306ac 100644 --- a/arch/sandbox/dts/sandbox.dtsi +++ b/arch/sandbox/dts/sandbox.dtsi @@ -19,6 +19,11 @@ #sound-dai-cells = <1>; }; + bootstd { + compatible = "u-boot,boot-std"; + filename-prefixes = "./"; + }; + buttons { compatible = "gpio-keys"; diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 5b38ee4..a8a86bc 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -74,6 +74,21 @@ }; }; + bootstd { + compatible = "u-boot,boot-std"; + + filename-prefixes = "/", "/boot/"; + bootdev-order = "mmc2", "mmc1"; + + syslinux { + compatible = "u-boot,distro-syslinux"; + }; + + efi { + compatible = "u-boot,distro-efi"; + }; + }; + reboot-mode0 { compatible = "reboot-mode-gpio"; gpios = <&gpio_c 0 GPIO_ACTIVE_HIGH>, <&gpio_c 1 GPIO_ACTIVE_HIGH>; @@ -891,10 +906,13 @@ non-removable; }; + /* This is used for the bootdev tests */ mmc1 { compatible = "sandbox,mmc"; + filename = "mmc1.img"; }; + /* This is used for the fastboot tests */ mmc0 { compatible = "sandbox,mmc"; }; diff --git a/boot/Kconfig b/boot/Kconfig index ec5b956..4b0802b 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -292,6 +292,117 @@ endif # SPL endif # FIT +config BOOTSTD + bool "Standard boot support" + default y + depends on DM && OF_CONTROL && BLK + help + U-Boot supports a standard way of locating something to boot, + typically an Operating System such as Linux, provided by a distro such + as Arch Linux or Debian. Enable this to support iterating through + available bootdevs and using bootmeths to find bootflows suitable for + booting. + + Standard boot is not a standard way of booting, just a framework + within U-Boot for supporting all the different ways that exist. + + Terminology: + + - bootdev - a device which can hold a distro (e.g. MMC) + - bootmeth - a method to scan a bootdev to find bootflows (owned by + U-Boot) + - bootflow - a description of how to boot (owned by the distro) + +config BOOTSTD_FULL + bool "Enhanced features for standard boot" + default y if SANDBOX + help + This enables various useful features for standard boot, which are not + essential for operation: + + - bootdev, bootmeth commands + - extra features in the bootflow command + - support for selecting the ordering of bootmeths ("bootmeth order") + - support for selecting the ordering of bootdevs using the devicetree + as well as the "boot_targets" environment variable + +if BOOTSTD + +config BOOTSTD_BOOTCOMMAND + bool "Use bootstd to boot" + default y if !DISTRO_DEFAULTS + help + Enable this to select a default boot-command suitable for booting + with standard boot. This can be overridden by the board if needed, + but the default command should be enough for most boards which use + standard boot. + + For now this is only selected if distro boot is NOT used, since + standard boot does not support all of the features of distro boot + yet. + +config BOOTMETH_DISTRO + bool "Bootdev support for distro boot" + depends on CMD_PXE + default y + help + Enables support for distro boot using bootdevs. This makes the + bootdevs look for a 'extlinux/extlinux.conf' on each filesystem + they scan. + + This provides a way to try out standard boot on an existing boot flow. + +config BOOTMETH_DISTRO_PXE + bool "Bootdev support for distro boot over network" + depends on CMD_PXE && CMD_NET && DM_ETH + default y + help + Enables support for distro boot using bootdevs. This makes the + bootdevs look for a 'extlinux/extlinux.conf' on the tftp server. + + This provides a way to try out standard boot on an existing boot flow. + +config BOOTMETH_EFILOADER + bool "Bootdev support for EFI boot" + depends on CMD_BOOTEFI + default y + help + Enables support for EFI boot using bootdevs. This makes the + bootdevs look for a 'boot<arch>.efi' on each filesystem + they scan. The resulting file is booted after enabling U-Boot's + EFI loader support. + + The <arch> depends on the architecture of the board: + + aa64 - aarch64 (ARM 64-bit) + arm - ARM 32-bit + ia32 - x86 32-bit + x64 - x86 64-bit + riscv32 - RISC-V 32-bit + riscv64 - RISC-V 64-bit + + This provides a way to try out standard boot on an existing boot flow. + +config BOOTMETH_SANDBOX + def_bool y + depends on SANDBOX + help + This is a sandbox bootmeth driver used for testing. It always returns + -ENOTSUPP when attempting to boot. + +config BOOTMETH_SCRIPT + bool "Bootdev support for U-Boot scripts" + default y if BOOTSTD_FULL + help + Enables support for booting a distro via a U-Boot script. This makes + the bootdevs look for a 'boot/boot.scr' file which can be used to + boot the distro. + + This provides a way to try out standard boot on an existing boot flow. + It is not enabled by default to save space. + +endif + config LEGACY_IMAGE_FORMAT bool "Enable support for the legacy image format" default y if !FIT_SIGNATURE @@ -1162,7 +1273,9 @@ config USE_BOOTCOMMAND config BOOTCOMMAND string "bootcmd value" depends on USE_BOOTCOMMAND && !USE_DEFAULT_ENV_FILE - default "run distro_bootcmd" if DISTRO_DEFAULTS + default "bootflow scan -lb" if BOOTSTD_BOOTCOMMAND && CMD_BOOTFLOW_FULL + default "bootflow scan" if BOOTSTD_BOOTCOMMAND && !CMD_BOOTFLOW_FULL + default "run distro_bootcmd" if !BOOTSTD_BOOTCOMMAND && DISTRO_DEFAULTS help This is the string of commands that will be used as bootcmd and if AUTOBOOT is set, automatically run. diff --git a/boot/Makefile b/boot/Makefile index 1b99e6e..a706742 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -18,6 +18,21 @@ endif 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) += bootflow.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o + +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_DISTRO) += bootmeth_distro.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_DISTRO_PXE) += bootmeth_pxe.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_EFILOADER) += bootmeth_efi.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SANDBOX) += bootmeth_sandbox.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SCRIPT) += bootmeth_script.o +ifdef CONFIG_$(SPL_TPL_)BOOTSTD_FULL +obj-$(CONFIG_$(SPL_TPL_)CMD_BOOTEFI_BOOTMGR) += bootmeth_efi_mgr.o +endif + obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c new file mode 100644 index 0000000..1ede933 --- /dev/null +++ b/boot/bootdev-uclass.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <dm.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <env.h> +#include <fs.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <sort.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/uclass-internal.h> + +enum { + /* + * Set some sort of limit on the number of partitions a bootdev can + * have. Note that for disks this limits the partitions numbers that + * are scanned to 1..MAX_BOOTFLOWS_PER_BOOTDEV + */ + MAX_PART_PER_BOOTDEV = 30, + + /* Maximum supported length of the "boot_targets" env string */ + BOOT_TARGETS_MAX_LEN = 100, +}; + +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; + + assert(bflow->dev); + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + new = malloc(sizeof(*bflow)); + if (!new) + return log_msg_ret("bflow", -ENOMEM); + memcpy(new, bflow, sizeof(*bflow)); + + list_add_tail(&new->glob_node, &std->glob_head); + list_add_tail(&new->bm_node, &ucp->bootflow_head); + + return 0; +} + +int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + if (list_empty(&ucp->bootflow_head)) + return -ENOENT; + + *bflowp = list_first_entry(&ucp->bootflow_head, struct bootflow, + bm_node); + + return 0; +} + +int bootdev_next_bootflow(struct bootflow **bflowp) +{ + struct bootflow *bflow = *bflowp; + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); + + *bflowp = NULL; + + if (list_is_last(&bflow->bm_node, &ucp->bootflow_head)) + return -ENOENT; + + *bflowp = list_entry(bflow->bm_node.next, struct bootflow, bm_node); + + return 0; +} + +int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, + struct udevice **devp) +{ + struct udevice *dev; + char dev_name[30]; + char *str; + int ret; + + snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name); + str = strdup(dev_name); + if (!str) + return -ENOMEM; + ret = device_bind_driver(parent, drv_name, str, &dev); + if (ret) + return ret; + device_set_name_alloced(dev); + *devp = dev; + + return 0; +} + +int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, + struct bootflow_iter *iter, struct bootflow *bflow) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + struct disk_partition info; + char partstr[20]; + char name[60]; + int ret; + + /* Sanity check */ + if (iter->part >= MAX_PART_PER_BOOTDEV) + return log_msg_ret("max", -ESHUTDOWN); + + bflow->blk = blk; + if (iter->part) + snprintf(partstr, sizeof(partstr), "part_%x", iter->part); + else + strcpy(partstr, "whole"); + snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); + bflow->name = strdup(name); + if (!bflow->name) + return log_msg_ret("name", -ENOMEM); + + bflow->part = iter->part; + + ret = bootmeth_check(bflow->method, iter); + if (ret) + return log_msg_ret("check", ret); + + /* + * partition numbers start at 0 so this cannot succeed, but it can tell + * us whether there is valid media there + */ + ret = part_get_info(desc, iter->part, &info); + if (!iter->part && ret == -ENOENT) + ret = 0; + + /* + * This error indicates the media is not present. Otherwise we just + * blindly scan the next partition. We could be more intelligent here + * and check which partition numbers actually exist. + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + else + bflow->state = BOOTFLOWST_MEDIA; + if (ret) + return log_msg_ret("part", ret); + + /* + * Currently we don't get the number of partitions, so just + * assume a large number + */ + iter->max_part = MAX_PART_PER_BOOTDEV; + + if (iter->part) { + ret = fs_set_blk_dev_with_part(desc, bflow->part); + bflow->state = BOOTFLOWST_PART; + + /* Use an #ifdef due to info.sys_ind */ +#ifdef CONFIG_DOS_PARTITION + log_debug("%s: Found partition %x type %x fstype %d\n", + blk->name, bflow->part, info.sys_ind, + ret ? -1 : fs_get_type()); +#endif + if (ret) + return log_msg_ret("fs", ret); + bflow->state = BOOTFLOWST_FS; + } + + ret = bootmeth_read_bootflow(bflow->method, bflow); + if (ret) + return log_msg_ret("method", ret); + + return 0; +} + +void bootdev_list(bool probe) +{ + struct udevice *dev; + int ret; + int i; + + printf("Seq Probed Status Uclass Name\n"); + printf("--- ------ ------ -------- ------------------\n"); + if (probe) + ret = uclass_first_device_err(UCLASS_BOOTDEV, &dev); + else + ret = uclass_find_first_device(UCLASS_BOOTDEV, &dev); + for (i = 0; dev; i++) { + printf("%3x [ %c ] %6s %-9.9s %s\n", dev_seq(dev), + device_active(dev) ? '+' : ' ', + ret ? simple_itoa(ret) : "OK", + dev_get_uclass_name(dev_get_parent(dev)), dev->name); + if (probe) + ret = uclass_next_device_err(&dev); + else + ret = uclass_find_next_device(&dev); + } + printf("--- ------ ------ -------- ------------------\n"); + printf("(%d bootdev%s)\n", i, i != 1 ? "s" : ""); +} + +int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name) +{ + struct udevice *bdev; + int ret; + + ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, + &bdev); + if (ret) { + if (ret != -ENODEV) { + log_debug("Cannot access bootdev device\n"); + return ret; + } + + ret = bootdev_bind(parent, drv_name, "bootdev", &bdev); + if (ret) { + log_debug("Cannot create bootdev device\n"); + return ret; + } + } + + return 0; +} + +int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name) +{ + struct udevice *parent, *dev; + char dev_name[50]; + int ret; + + snprintf(dev_name, sizeof(dev_name), "%s.%s", blk->name, "bootdev"); + + parent = dev_get_parent(blk); + ret = device_find_child_by_name(parent, dev_name, &dev); + if (ret) { + char *str; + + if (ret != -ENODEV) { + log_debug("Cannot access bootdev device\n"); + return ret; + } + str = strdup(dev_name); + if (!str) + return -ENOMEM; + + ret = device_bind_driver(parent, drv_name, str, &dev); + if (ret) { + log_debug("Cannot create bootdev device\n"); + return ret; + } + device_set_name_alloced(dev); + } + + return 0; +} + +int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp) +{ + struct udevice *parent = dev_get_parent(dev); + struct udevice *blk; + int ret, len; + char *p; + + if (device_get_uclass_id(dev) != UCLASS_BOOTDEV) + return -EINVAL; + + /* This should always work if bootdev_setup_sibling_blk() was used */ + p = strstr(dev->name, ".bootdev"); + if (!p) + return log_msg_ret("str", -EINVAL); + + len = p - dev->name; + ret = device_find_child_by_namelen(parent, dev->name, len, &blk); + if (ret) + return log_msg_ret("find", ret); + *blkp = blk; + + return 0; +} + +static int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp) +{ + struct udevice *parent = dev_get_parent(blk); + struct udevice *bootdev; + char dev_name[50]; + int ret; + + if (device_get_uclass_id(blk) != UCLASS_BLK) + return -EINVAL; + + /* This should always work if bootdev_setup_sibling_blk() was used */ + snprintf(dev_name, sizeof(dev_name), "%s.%s", blk->name, "bootdev"); + ret = device_find_child_by_name(parent, dev_name, &bootdev); + if (ret) + return log_msg_ret("find", ret); + *bootdevp = bootdev; + + return 0; +} + +int bootdev_unbind_dev(struct udevice *parent) +{ + struct udevice *dev; + int ret; + + ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, &dev); + if (!ret) { + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) + return log_msg_ret("rem", ret); + ret = device_unbind(dev); + if (ret) + return log_msg_ret("unb", ret); + } + + return 0; +} + +/** + * bootdev_find_by_label() - Convert a label string to a bootdev device + * + * Looks up a label name to find the associated bootdev. For example, if the + * label name is "mmc2", this will find a bootdev for an mmc device whose + * sequence number is 2. + * + * @label: Label string to convert, e.g. "mmc2" + * @devp: Returns bootdev device corresponding to that boot label + * Return: 0 if OK, -EINVAL if the label name (e.g. "mmc") does not refer to a + * uclass, -ENOENT if no bootdev for that media has the sequence number + * (e.g. 2) + */ +int bootdev_find_by_label(const char *label, struct udevice **devp) +{ + struct udevice *media; + struct uclass *uc; + enum uclass_id id; + const char *end; + int seq; + + seq = trailing_strtoln_end(label, NULL, &end); + id = uclass_get_by_namelen(label, end - label); + log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id, + uclass_get_name(id)); + if (id == UCLASS_INVALID) { + log_warning("Unknown uclass '%s' in label\n", label); + return -EINVAL; + } + if (id == UCLASS_USB) + id = UCLASS_MASS_STORAGE; + + /* Iterate through devices in the media uclass (e.g. UCLASS_MMC) */ + uclass_id_foreach_dev(id, media, uc) { + struct udevice *bdev, *blk; + int ret; + + /* if there is no seq, match anything */ + if (seq != -1 && dev_seq(media) != seq) { + log_debug("- skip, media seq=%d\n", dev_seq(media)); + continue; + } + + ret = device_find_first_child_by_uclass(media, UCLASS_BOOTDEV, + &bdev); + if (ret) { + log_debug("- looking via blk, seq=%d, id=%d\n", seq, + id); + ret = blk_find_device(id, seq, &blk); + if (!ret) { + log_debug("- get from blk %s\n", blk->name); + ret = bootdev_get_from_blk(blk, &bdev); + } + } + if (!ret) { + log_debug("- found %s\n", bdev->name); + *devp = bdev; + return 0; + } + log_debug("- no device in %s\n", media->name); + } + log_warning("Unknown seq %d for label '%s'\n", seq, label); + + return -ENOENT; +} + +int bootdev_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, label or number */ + if (*endp) { + ret = uclass_get_device_by_name(UCLASS_BOOTDEV, name, &dev); + if (ret == -ENODEV) { + ret = bootdev_find_by_label(name, &dev); + if (ret) { + printf("Cannot find bootdev '%s' (err=%d)\n", + name, ret); + return ret; + } + ret = device_probe(dev); + } + if (ret) { + printf("Cannot probe bootdev '%s' (err=%d)\n", name, + ret); + return ret; + } + } else { + ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev); + } + if (ret) { + printf("Cannot find '%s' (err=%d)\n", name, ret); + return ret; + } + + *devp = dev; + + return 0; +} + +int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + const struct bootdev_ops *ops = bootdev_get_ops(dev); + + if (!ops->get_bootflow) + return -ENOSYS; + memset(bflow, '\0', sizeof(*bflow)); + bflow->dev = dev; + bflow->method = iter->method; + bflow->state = BOOTFLOWST_BASE; + + return ops->get_bootflow(dev, iter, bflow); +} + +void bootdev_clear_bootflows(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + while (!list_empty(&ucp->bootflow_head)) { + struct bootflow *bflow; + + bflow = list_first_entry(&ucp->bootflow_head, struct bootflow, + bm_node); + bootflow_remove(bflow); + } +} + +/** + * h_cmp_bootdev() - Compare two bootdevs to find out which should go first + * + * @v1: struct udevice * of first bootdev device + * @v2: struct udevice * of second bootdev device + * Return: sort order (<0 if dev1 < dev2, ==0 if equal, >0 if dev1 > dev2) + */ +static int h_cmp_bootdev(const void *v1, const void *v2) +{ + const struct udevice *dev1 = *(struct udevice **)v1; + const struct udevice *dev2 = *(struct udevice **)v2; + const struct bootdev_uc_plat *ucp1 = dev_get_uclass_plat(dev1); + const struct bootdev_uc_plat *ucp2 = dev_get_uclass_plat(dev2); + int diff; + + /* Use priority first */ + diff = ucp1->prio - ucp2->prio; + if (diff) + return diff; + + /* Fall back to seq for devices of the same priority */ + diff = dev_seq(dev1) - dev_seq(dev2); + + return diff; +} + +/** + * build_order() - Build the ordered list of bootdevs to use + * + * This builds an ordered list of devices by one of three methods: + * - using the boot_targets environment variable, if non-empty + * - using the bootdev-order devicetree property, if present + * - sorted by priority and sequence number + * + * @bootstd: BOOTSTD device to use + * @order: Bootdevs listed in default order + * @max_count: Number of entries in @order + * Return: number of bootdevs found in the ordering, or -E2BIG if the + * boot_targets string is too long, or -EXDEV if the ordering produced 0 results + */ +static int build_order(struct udevice *bootstd, struct udevice **order, + int max_count) +{ + const char *overflow_target = NULL; + const char *const *labels; + struct udevice *dev; + const char *targets; + int i, ret, count; + + targets = env_get("boot_targets"); + labels = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + bootstd_get_bootdev_order(bootstd) : NULL; + if (targets) { + char str[BOOT_TARGETS_MAX_LEN]; + char *target; + + if (strlen(targets) >= BOOT_TARGETS_MAX_LEN) + return log_msg_ret("len", -E2BIG); + + /* make a copy of the string, since strok() will change it */ + strcpy(str, targets); + for (i = 0, target = strtok(str, " "); target; + target = strtok(NULL, " ")) { + ret = bootdev_find_by_label(target, &dev); + if (!ret) { + if (i == max_count) { + overflow_target = target; + break; + } + order[i++] = dev; + } + } + count = i; + } else if (labels) { + int upto; + + upto = 0; + for (i = 0; labels[i]; i++) { + ret = bootdev_find_by_label(labels[i], &dev); + if (!ret) { + if (upto == max_count) { + overflow_target = labels[i]; + break; + } + order[upto++] = dev; + } + } + count = upto; + } else { + /* sort them into priority order */ + count = max_count; + qsort(order, count, sizeof(struct udevice *), h_cmp_bootdev); + } + + if (overflow_target) { + log_warning("Expected at most %d bootdevs, but overflowed with boot_target '%s'\n", + max_count, overflow_target); + } + + if (!count) + return log_msg_ret("targ", -EXDEV); + + return count; +} + +int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp) +{ + struct udevice *bootstd, *dev = *devp, **order; + int upto, i; + int count; + int ret; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) { + log_err("Missing bootstd device\n"); + return log_msg_ret("std", ret); + } + + /* Handle scanning a single device */ + if (dev) { + iter->flags |= BOOTFLOWF_SINGLE_DEV; + return 0; + } + + count = uclass_id_count(UCLASS_BOOTDEV); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + /* + * Get a list of bootdevs, 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 = 0; upto < count && i < 20 + count * 2; i++) { + ret = uclass_find_device_by_seq(UCLASS_BOOTDEV, i, &dev); + if (!ret) + order[upto++] = dev; + } + log_debug("Found %d bootdevs\n", count); + if (upto != count) + log_debug("Expected %d bootdevs, found %d using aliases\n", + count, upto); + + count = build_order(bootstd, order, upto); + if (count < 0) { + free(order); + return log_msg_ret("build", count); + } + + iter->dev_order = order; + iter->num_devs = count; + iter->cur_dev = 0; + + dev = *order; + ret = device_probe(dev); + if (ret) + return log_msg_ret("probe", ret); + *devp = dev; + + return 0; +} + +static int bootdev_post_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + INIT_LIST_HEAD(&ucp->bootflow_head); + + return 0; +} + +static int bootdev_pre_unbind(struct udevice *dev) +{ + bootdev_clear_bootflows(dev); + + return 0; +} + +UCLASS_DRIVER(bootdev) = { + .id = UCLASS_BOOTDEV, + .name = "bootdev", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_plat_auto = sizeof(struct bootdev_uc_plat), + .post_bind = bootdev_post_bind, + .pre_unbind = bootdev_pre_unbind, +}; diff --git a/boot/bootflow.c b/boot/bootflow.c new file mode 100644 index 0000000..24ba3c3 --- /dev/null +++ b/boot/bootflow.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <dm.h> +#include <malloc.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +/* error codes used to signal running out of things */ +enum { + BF_NO_MORE_PARTS = -ESHUTDOWN, + BF_NO_MORE_DEVICES = -ENODEV, +}; + +/** + * bootflow_state - name for each state + * + * See enum bootflow_state_t for what each of these means + */ +static const char *const bootflow_state[BOOTFLOWST_COUNT] = { + "base", + "media", + "part", + "fs", + "file", + "ready", +}; + +const char *bootflow_state_get_name(enum bootflow_state_t state) +{ + /* This doesn't need to be a useful name, since it will never occur */ + if (state < 0 || state >= BOOTFLOWST_COUNT) + return "?"; + + return bootflow_state[state]; +} + +int bootflow_first_glob(struct bootflow **bflowp) +{ + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + if (list_empty(&std->glob_head)) + return -ENOENT; + + *bflowp = list_first_entry(&std->glob_head, struct bootflow, + glob_node); + + return 0; +} + +int bootflow_next_glob(struct bootflow **bflowp) +{ + struct bootstd_priv *std; + struct bootflow *bflow = *bflowp; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + *bflowp = NULL; + + if (list_is_last(&bflow->glob_node, &std->glob_head)) + return -ENOENT; + + *bflowp = list_entry(bflow->glob_node.next, struct bootflow, glob_node); + + return 0; +} + +void bootflow_iter_init(struct bootflow_iter *iter, int flags) +{ + memset(iter, '\0', sizeof(*iter)); + iter->flags = flags; +} + +void bootflow_iter_uninit(struct bootflow_iter *iter) +{ + free(iter->dev_order); + free(iter->method_order); +} + +int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, + const struct udevice *bmeth) +{ + /* We only support disabling the current bootmeth */ + if (bmeth != iter->method || iter->cur_method >= iter->num_methods || + iter->method_order[iter->cur_method] != bmeth) + return -EINVAL; + + memmove(&iter->method_order[iter->cur_method], + &iter->method_order[iter->cur_method + 1], + (iter->num_methods - iter->cur_method - 1) * sizeof(void *)); + + iter->num_methods--; + + return 0; +} + +static void bootflow_iter_set_dev(struct bootflow_iter *iter, + struct udevice *dev) +{ + iter->dev = dev; + if ((iter->flags & (BOOTFLOWF_SHOW | BOOTFLOWF_SINGLE_DEV)) == + BOOTFLOWF_SHOW) { + if (dev) + printf("Scanning bootdev '%s':\n", dev->name); + else + printf("No more bootdevs\n"); + } +} + +/** + * iter_incr() - Move to the next item (method, part, bootdev) + * + * Return: 0 if OK, BF_NO_MORE_DEVICES if there are no more bootdevs + */ +static int iter_incr(struct bootflow_iter *iter) +{ + struct udevice *dev; + int ret; + + if (iter->err == BF_NO_MORE_DEVICES) + return BF_NO_MORE_DEVICES; + + if (iter->err != BF_NO_MORE_PARTS) { + /* Get the next boothmethod */ + if (++iter->cur_method < iter->num_methods) { + iter->method = iter->method_order[iter->cur_method]; + return 0; + } + } + + /* No more bootmeths; start at the first one, and... */ + iter->cur_method = 0; + iter->method = iter->method_order[iter->cur_method]; + + if (iter->err != BF_NO_MORE_PARTS) { + /* ...select next partition */ + if (++iter->part <= iter->max_part) + return 0; + } + + /* No more partitions; start at the first one and...*/ + iter->part = 0; + + /* + * Note: as far as we know, there is no partition table on the next + * bootdev, so set max_part to 0 until we discover otherwise. See + * bootdev_find_in_blk() for where this is set. + */ + iter->max_part = 0; + + /* ...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 there are no more bootdevs, give up */ + if (ret) + return log_msg_ret("incr", BF_NO_MORE_DEVICES); + + return 0; +} + +/** + * bootflow_check() - Check if a bootflow can be obtained + * + * @iter: Provides part, bootmeth to use + * @bflow: Bootflow to update on success + * Return: 0 if OK, -ENOSYS if there is no bootflow support on this device, + * BF_NO_MORE_PARTS if there are no more partitions on bootdev + */ +static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) +{ + struct udevice *dev; + int ret; + + dev = iter->dev; + ret = bootdev_get_bootflow(dev, iter, bflow); + + /* If we got a valid bootflow, return it */ + if (!ret) { + log_debug("Bootdevice '%s' part %d method '%s': Found bootflow\n", + dev->name, iter->part, iter->method->name); + return 0; + } + + /* Unless there is nothing more to try, move to the next device */ + else if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + log_debug("Bootdevice '%s' part %d method '%s': Error %d\n", + dev->name, iter->part, iter->method->name, ret); + /* + * For 'all' we return all bootflows, even + * those with errors + */ + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + if (ret) + return log_msg_ret("check", ret); + + return 0; +} + +int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter, + int flags, struct bootflow *bflow) +{ + int ret; + + 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); + 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]; + + ret = bootflow_check(iter, bflow); + if (ret) { + if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + iter->err = ret; + ret = bootflow_scan_next(iter, bflow); + if (ret) + return log_msg_ret("get", ret); + } + + return 0; +} + +int bootflow_scan_first(struct bootflow_iter *iter, int flags, + struct bootflow *bflow) +{ + int ret; + + ret = bootflow_scan_bootdev(NULL, iter, flags, bflow); + if (ret) + return log_msg_ret("start", ret); + + return 0; +} + +int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow) +{ + int ret; + + do { + ret = iter_incr(iter); + if (ret == BF_NO_MORE_DEVICES) + return log_msg_ret("done", ret); + + if (!ret) { + ret = bootflow_check(iter, bflow); + if (!ret) + return 0; + iter->err = ret; + if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + } else { + iter->err = ret; + } + + } while (1); +} + +void bootflow_free(struct bootflow *bflow) +{ + free(bflow->name); + free(bflow->subdir); + free(bflow->fname); + free(bflow->buf); +} + +void bootflow_remove(struct bootflow *bflow) +{ + list_del(&bflow->bm_node); + list_del(&bflow->glob_node); + + bootflow_free(bflow); + free(bflow); +} + +int bootflow_boot(struct bootflow *bflow) +{ + int ret; + + if (bflow->state != BOOTFLOWST_READY) + return log_msg_ret("load", -EPROTO); + + ret = bootmeth_boot(bflow->method, bflow); + if (ret) + return log_msg_ret("boot", ret); + + /* + * internal error, should not get here since we should have booted + * something or returned an error + */ + + return log_msg_ret("end", -EFAULT); +} + +int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow) +{ + int ret; + + printf("** Booting bootflow '%s' with %s\n", bflow->name, + bflow->method->name); + ret = bootflow_boot(bflow); + if (!IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + printf("Boot failed (err=%d)\n", ret); + return ret; + } + + switch (ret) { + case -EPROTO: + printf("Bootflow not loaded (state '%s')\n", + bootflow_state_get_name(bflow->state)); + break; + case -ENOSYS: + printf("Boot method '%s' not supported\n", bflow->method->name); + break; + case -ENOTSUPP: + /* Disable this bootflow for this iteration */ + if (iter) { + int ret2; + + ret2 = bootflow_iter_drop_bootmeth(iter, bflow->method); + if (!ret2) { + printf("Boot method '%s' failed and will not be retried\n", + bflow->method->name); + } + } + + break; + default: + printf("Boot failed (err=%d)\n", ret); + break; + } + + return ret; +} + +int bootflow_iter_uses_blk_dev(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id != UCLASS_ETH && id != UCLASS_BOOTSTD) + return 0; + + return -ENOTSUPP; +} + +int bootflow_iter_uses_network(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_ETH) + return 0; + + return -ENOTSUPP; +} + +int bootflow_iter_uses_system(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_BOOTSTD) + return 0; + + return -ENOTSUPP; +} diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c new file mode 100644 index 0000000..c040d5f --- /dev/null +++ b/boot/bootmeth-uclass.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <blk.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <dm.h> +#include <env_internal.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <dm/uclass-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->check) + return 0; + + return ops->check(dev, iter); +} + +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_bootflow) + return -ENOSYS; + + return ops->read_bootflow(dev, bflow); +} + +int bootmeth_boot(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->boot) + return -ENOSYS; + + return ops->boot(dev, bflow); +} + +int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_file) + return -ENOSYS; + + 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) +{ + struct bootstd_priv *std; + struct udevice **order; + int count; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + /* Create an array large enough */ + count = std->bootmeth_count ? std->bootmeth_count : + uclass_id_count(UCLASS_BOOTMETH); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + /* 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; + + /* + * 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 = 0; upto < count && i < 20 + count * 2; i++) { + ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, i, + &dev); + if (!ret) + order[upto++] = dev; + } + count = upto; + } + + iter->method_order = order; + iter->num_methods = count; + iter->cur_method = 0; + + return 0; +} + +int bootmeth_set_order(const char *order_str) +{ + struct bootstd_priv *std; + struct udevice **order; + int count, ret, i, len; + const char *s, *p; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + if (!order_str) { + free(std->bootmeth_order); + std->bootmeth_order = NULL; + std->bootmeth_count = 0; + return 0; + } + + /* Create an array large enough */ + count = uclass_id_count(UCLASS_BOOTMETH); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count + 1, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + for (i = 0, s = order_str; *s && i < count; s = p + (*p == ' '), i++) { + struct udevice *dev; + + p = strchrnul(s, ' '); + len = p - s; + ret = uclass_find_device_by_namelen(UCLASS_BOOTMETH, s, len, + &dev); + if (ret) { + printf("Unknown bootmeth '%.*s'\n", len, s); + free(order); + return ret; + } + order[i] = dev; + } + order[i] = NULL; + free(std->bootmeth_order); + std->bootmeth_order = order; + std->bootmeth_count = i; + + return 0; +} + +/** + * setup_fs() - Set up read to read a file + * + * We must redo the setup before each filesystem operation. This function + * handles that, including setting the filesystem type if a block device is not + * being used + * + * @bflow: Information about file to try + * @desc: Block descriptor to read from (NULL if not a block device) + * Return: 0 if OK, -ve on error + */ +static int setup_fs(struct bootflow *bflow, struct blk_desc *desc) +{ + int ret; + + if (desc) { + ret = fs_set_blk_dev_with_part(desc, bflow->part); + if (ret) + return log_msg_ret("set", ret); + } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) { + fs_set_type(bflow->fs_type); + } + + return 0; +} + +int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, + const char *prefix, const char *fname) +{ + char path[200]; + loff_t size; + int ret, ret2; + + snprintf(path, sizeof(path), "%s%s", prefix ? prefix : "", fname); + log_debug("trying: %s\n", path); + + free(bflow->fname); + bflow->fname = strdup(path); + if (!bflow->fname) + return log_msg_ret("name", -ENOMEM); + + if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) + fs_set_type(bflow->fs_type); + + ret = fs_size(path, &size); + log_debug(" %s - err=%d\n", path, ret); + + /* Sadly FS closes the file after fs_size() so we must redo this */ + ret2 = setup_fs(bflow, desc); + if (ret2) + return log_msg_ret("fs", ret2); + + if (ret) + return log_msg_ret("size", ret); + + bflow->size = size; + bflow->state = BOOTFLOWST_FILE; + + return 0; +} + +int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align) +{ + loff_t bytes_read; + ulong addr; + char *buf; + uint size; + int ret; + + size = bflow->size; + log_debug(" - script file size %x\n", size); + if (size > size_limit) + return log_msg_ret("chk", -E2BIG); + + buf = memalign(align, size + 1); + if (!buf) + return log_msg_ret("buf", -ENOMEM); + addr = map_to_sysmem(buf); + + ret = fs_read(bflow->fname, addr, 0, 0, &bytes_read); + if (ret) { + free(buf); + return log_msg_ret("read", ret); + } + if (size != bytes_read) + return log_msg_ret("bread", -EINVAL); + buf[size] = '\0'; + bflow->state = BOOTFLOWST_READY; + bflow->buf = buf; + + return 0; +} + +int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + struct blk_desc *desc = NULL; + loff_t len_read; + loff_t size; + int ret; + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + + ret = setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = fs_size(file_path, &size); + if (ret) + return log_msg_ret("size", ret); + if (size > *sizep) + return log_msg_ret("spc", -ENOSPC); + + ret = setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = fs_read(file_path, addr, 0, 0, &len_read); + if (ret) + return ret; + *sizep = len_read; + + return 0; +} + +#ifdef CONFIG_BOOTSTD_FULL +/** + * on_bootmeths() - Update the bootmeth order + * + * This will check for a valid baudrate and only apply it if valid. + */ +static int on_bootmeths(const char *name, const char *value, enum env_op op, + int flags) +{ + int ret; + + switch (op) { + case env_op_create: + case env_op_overwrite: + ret = bootmeth_set_order(value); + if (ret) + return 1; + return 0; + case env_op_delete: + bootmeth_set_order(NULL); + fallthrough; + default: + return 0; + } +} +U_BOOT_ENV_CALLBACK(bootmeths, on_bootmeths); +#endif /* CONFIG_BOOTSTD_FULL */ + +UCLASS_DRIVER(bootmeth) = { + .id = UCLASS_BOOTMETH, + .name = "bootmeth", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_plat_auto = sizeof(struct bootmeth_uc_plat), +}; diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c new file mode 100644 index 0000000..2b41e65 --- /dev/null +++ b/boot/bootmeth_distro.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot (syslinux boot from a block device) + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <distro.h> +#include <dm.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <pxe_utils.h> + +static int disto_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep) +{ + struct distro_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + + /* Allow up to 1GB */ + *sizep = 1 << 30; + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc; + const char *const *prefixes; + struct udevice *bootstd; + const char *prefix; + loff_t size; + int ret, i; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) + return log_msg_ret("std", ret); + + /* If a block device, we require a partition table */ + if (bflow->blk && !bflow->part) + return -ENOENT; + + prefixes = bootstd_get_prefixes(bootstd); + i = 0; + desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL; + do { + prefix = prefixes ? prefixes[i] : NULL; + + ret = bootmeth_try_file(bflow, desc, prefix, DISTRO_FNAME); + } while (ret && prefixes && prefixes[++i]); + if (ret) + return log_msg_ret("try", ret); + size = bflow->size; + + ret = bootmeth_alloc_file(bflow, 0x10000, 1); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct cmd_tbl cmdtp = {}; /* dummy */ + struct pxe_context ctx; + struct distro_info info; + ulong addr; + int ret; + + addr = map_to_sysmem(bflow->buf); + info.dev = dev; + info.bflow = bflow; + ret = pxe_setup_ctx(&ctx, &cmdtp, disto_getfile, &info, true, + bflow->subdir); + if (ret) + return log_msg_ret("ctx", -EINVAL); + + ret = pxe_process(&ctx, addr, false); + if (ret) + return log_msg_ret("bread", -EINVAL); + + return 0; +} + +static int distro_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "Syslinux boot from a block device" : "syslinux"; + + return 0; +} + +static struct bootmeth_ops distro_bootmeth_ops = { + .check = distro_check, + .read_bootflow = distro_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = distro_boot, +}; + +static const struct udevice_id distro_bootmeth_ids[] = { + { .compatible = "u-boot,distro-syslinux" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_distro) = { + .name = "bootmeth_distro", + .id = UCLASS_BOOTMETH, + .of_match = distro_bootmeth_ids, + .ops = &distro_bootmeth_ops, + .bind = distro_bootmeth_bind, +}; diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c new file mode 100644 index 0000000..d5438eb --- /dev/null +++ b/boot/bootmeth_efi.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot via EFI + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <dm.h> +#include <efi_loader.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <pxe_utils.h> + +#define EFI_DIRNAME "efi/boot/" + +/** + * get_efi_leafname() - Get the leaf name for the EFI file we expect + * + * @str: Place to put leaf name for this architecture, e.g. "bootaa64.efi". + * Must have at least 16 bytes of space + * @max_len: Length of @str, must be >=16 + */ +static int get_efi_leafname(char *str, int max_len) +{ + const char *base; + + if (max_len < 16) + return log_msg_ret("spc", -ENOSPC); + if (IS_ENABLED(CONFIG_ARM64)) + base = "bootaa64"; + else if (IS_ENABLED(CONFIG_ARM)) + base = "bootarm"; + else if (IS_ENABLED(CONFIG_X86_RUN_32BIT)) + base = "bootia32"; + else if (IS_ENABLED(CONFIG_X86_RUN_64BIT)) + base = "bootx64"; + else if (IS_ENABLED(CONFIG_ARCH_RV32I)) + base = "bootriscv32"; + else if (IS_ENABLED(CONFIG_ARCH_RV64I)) + base = "bootriscv64"; + else if (IS_ENABLED(CONFIG_SANDBOX)) + base = "bootsbox"; + else + return -EINVAL; + + strcpy(str, base); + strcat(str, ".efi"); + + return 0; +} + +static int efiload_read_file(struct blk_desc *desc, struct bootflow *bflow) +{ + const struct udevice *media_dev; + int size = bflow->size; + const char *dev_name; + char devnum_str[9]; + char dirname[200]; + char *last_slash; + int ret; + + ret = bootmeth_alloc_file(bflow, 0x2000000, 0x10000); + if (ret) + return log_msg_ret("read", ret); + + /* + * This is a horrible hack to tell EFI about this boot device. Once we + * unify EFI with the rest of U-Boot we can clean this up. The same hack + * exists in multiple places, e.g. in the fs, tftp and load commands. + * + * Once we can clean up the EFI code to make proper use of driver model, + * this can go away. + */ + media_dev = dev_get_parent(bflow->dev); + snprintf(devnum_str, sizeof(devnum_str), "%x", dev_seq(media_dev)); + + strlcpy(dirname, bflow->fname, sizeof(dirname)); + last_slash = strrchr(dirname, '/'); + if (last_slash) + *last_slash = '\0'; + + log_debug("setting bootdev %s, %s, %s, %p, %x\n", + dev_get_uclass_name(media_dev), devnum_str, bflow->fname, + bflow->buf, size); + dev_name = device_get_uclass_id(media_dev) == UCLASS_MASS_STORAGE ? + "usb" : dev_get_uclass_name(media_dev); + efi_set_bootdev(dev_name, devnum_str, bflow->fname, bflow->buf, size); + + return 0; +} + +static int distro_efi_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int distro_efi_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = NULL; + char fname[sizeof(EFI_DIRNAME) + 16]; + int ret; + + /* We require a partition table */ + if (!bflow->part) + return -ENOENT; + + strcpy(fname, EFI_DIRNAME); + ret = get_efi_leafname(fname + strlen(fname), + sizeof(fname) - strlen(fname)); + if (ret) + return log_msg_ret("leaf", ret); + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + ret = bootmeth_try_file(bflow, desc, NULL, fname); + if (ret) + return log_msg_ret("try", ret); + + ret = efiload_read_file(desc, bflow); + if (ret) + return log_msg_ret("read", -EINVAL); + + return 0; +} + +int distro_efi_boot(struct udevice *dev, struct bootflow *bflow) +{ + char cmd[50]; + + /* + * At some point we can add a real interface to bootefi so we can call + * this directly. For now, go through the CLI like distro boot. + */ + snprintf(cmd, sizeof(cmd), "bootefi %lx %lx", + (ulong)map_to_sysmem(bflow->buf), + (ulong)map_to_sysmem(gd->fdt_blob)); + if (run_command(cmd, 0)) + return log_msg_ret("run", -EINVAL); + + return 0; +} + +static int distro_bootmeth_efi_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "EFI boot from an .efi file" : "EFI"; + + return 0; +} + +static struct bootmeth_ops distro_efi_bootmeth_ops = { + .check = distro_efi_check, + .read_bootflow = distro_efi_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = distro_efi_boot, +}; + +static const struct udevice_id distro_efi_bootmeth_ids[] = { + { .compatible = "u-boot,distro-efi" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_efi) = { + .name = "bootmeth_efi", + .id = UCLASS_BOOTMETH, + .of_match = distro_efi_bootmeth_ids, + .ops = &distro_efi_bootmeth_ops, + .bind = distro_bootmeth_efi_bind, +}; diff --git a/boot/bootmeth_efi_mgr.c b/boot/bootmeth_efi_mgr.c new file mode 100644 index 0000000..a691446 --- /dev/null +++ b/boot/bootmeth_efi_mgr.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for EFI boot manager + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <dm.h> + +static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* Must be an bootstd device */ + ret = bootflow_iter_uses_system(iter); + if (ret) + return log_msg_ret("net", ret); + + return 0; +} + +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; + + return 0; +} + +static int efi_mgr_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + /* Files are loaded by the 'bootefi bootmgr' command */ + + return -ENOSYS; +} + +static int efi_mgr_boot(struct udevice *dev, struct bootflow *bflow) +{ + int ret; + + /* Booting is handled by the 'bootefi bootmgr' command */ + ret = run_command("bootefi bootmgr", 0); + + return 0; +} + +static int bootmeth_efi_mgr_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = "EFI bootmgr flow"; + + return 0; +} + +static struct bootmeth_ops efi_mgr_bootmeth_ops = { + .check = efi_mgr_check, + .read_bootflow = efi_mgr_read_bootflow, + .read_file = efi_mgr_read_file, + .boot = efi_mgr_boot, +}; + +static const struct udevice_id efi_mgr_bootmeth_ids[] = { + { .compatible = "u-boot,efi-bootmgr" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_zefi_mgr) = { + .name = "bootmeth_efi_mgr", + .id = UCLASS_BOOTMETH, + .of_match = efi_mgr_bootmeth_ids, + .ops = &efi_mgr_bootmeth_ops, + .bind = bootmeth_efi_mgr_bind, +}; diff --git a/boot/bootmeth_pxe.c b/boot/bootmeth_pxe.c new file mode 100644 index 0000000..f1e2b4c --- /dev/null +++ b/boot/bootmeth_pxe.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot using PXE (network boot) + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <distro.h> +#include <dm.h> +#include <fs.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <net.h> +#include <pxe_utils.h> + +static int disto_pxe_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep) +{ + struct distro_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_pxe_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on network devices */ + ret = bootflow_iter_uses_network(iter); + if (ret) + return log_msg_ret("net", ret); + + return 0; +} + +static int distro_pxe_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + const char *addr_str; + char fname[200]; + char *bootdir; + ulong addr; + ulong size; + char *buf; + int ret; + + addr_str = env_get("pxefile_addr_r"); + if (!addr_str) + return log_msg_ret("pxeb", -EPERM); + addr = simple_strtoul(addr_str, NULL, 16); + + log_debug("calling pxe_get()\n"); + ret = pxe_get(addr, &bootdir, &size); + log_debug("pxe_get() returned %d\n", ret); + if (ret) + return log_msg_ret("pxeb", ret); + bflow->size = size; + + /* Use the directory of the dhcp bootdir as our subdir, if provided */ + if (bootdir) { + const char *last_slash; + int path_len; + + last_slash = strrchr(bootdir, '/'); + if (last_slash) { + path_len = (last_slash - bootdir) + 1; + bflow->subdir = malloc(path_len + 1); + memcpy(bflow->subdir, bootdir, path_len); + bflow->subdir[path_len] = '\0'; + } + } + snprintf(fname, sizeof(fname), "%s%s", + bflow->subdir ? bflow->subdir : "", DISTRO_FNAME); + + bflow->fname = strdup(fname); + if (!bflow->fname) + return log_msg_ret("name", -ENOMEM); + + bflow->state = BOOTFLOWST_READY; + + /* Allocate the buffer, including the \0 byte added by get_pxe_file() */ + buf = malloc(size + 1); + if (!buf) + return log_msg_ret("buf", -ENOMEM); + memcpy(buf, map_sysmem(addr, 0), size + 1); + bflow->buf = buf; + + return 0; +} + +static int distro_pxe_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; + struct pxe_context *ctx = dev_get_priv(dev); + char file_addr[17]; + ulong size; + int ret; + + sprintf(file_addr, "%lx", addr); + tftp_argv[1] = file_addr; + tftp_argv[2] = (void *)file_path; + + if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv)) + return -ENOENT; + ret = pxe_get_file_size(&size); + if (ret) + return log_msg_ret("tftp", ret); + if (size > *sizep) + return log_msg_ret("spc", -ENOSPC); + *sizep = size; + + return 0; +} + +static int distro_pxe_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct pxe_context *ctx = dev_get_priv(dev); + struct cmd_tbl cmdtp = {}; /* dummy */ + struct distro_info info; + ulong addr; + int ret; + + addr = map_to_sysmem(bflow->buf); + info.dev = dev; + info.bflow = bflow; + info.cmdtp = &cmdtp; + ret = pxe_setup_ctx(ctx, &cmdtp, disto_pxe_getfile, &info, false, + bflow->subdir); + if (ret) + return log_msg_ret("ctx", -EINVAL); + + ret = pxe_process(ctx, addr, false); + if (ret) + return log_msg_ret("bread", -EINVAL); + + return 0; +} + +static int distro_bootmeth_pxe_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "PXE boot from a network device" : "PXE"; + + return 0; +} + +static struct bootmeth_ops distro_bootmeth_pxe_ops = { + .check = distro_pxe_check, + .read_bootflow = distro_pxe_read_bootflow, + .read_file = distro_pxe_read_file, + .boot = distro_pxe_boot, +}; + +static const struct udevice_id distro_bootmeth_pxe_ids[] = { + { .compatible = "u-boot,distro-pxe" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_pxe) = { + .name = "bootmeth_pxe", + .id = UCLASS_BOOTMETH, + .of_match = distro_bootmeth_pxe_ids, + .ops = &distro_bootmeth_pxe_ops, + .bind = distro_bootmeth_pxe_bind, + .priv_auto = sizeof(struct pxe_context), +}; diff --git a/boot/bootmeth_sandbox.c b/boot/bootmeth_sandbox.c new file mode 100644 index 0000000..13ec5e9 --- /dev/null +++ b/boot/bootmeth_sandbox.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for sandbox testing + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <dm.h> + +static int sandbox_check(struct udevice *dev, struct bootflow_iter *iter) +{ + return 0; +} + +static int sandbox_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + /* pretend we are ready */ + bflow->state = BOOTFLOWST_READY; + + return 0; +} + +static int sandbox_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + return -ENOSYS; +} + +static int sandbox_boot(struct udevice *dev, struct bootflow *bflow) +{ + /* always fail: see bootflow_iter_disable() */ + return -ENOTSUPP; +} + +static int sandbox_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = "Sandbox boot for testing"; + + return 0; +} + +static struct bootmeth_ops sandbox_bootmeth_ops = { + .check = sandbox_check, + .read_bootflow = sandbox_read_bootflow, + .read_file = sandbox_read_file, + .boot = sandbox_boot, +}; + +static const struct udevice_id sandbox_bootmeth_ids[] = { + { .compatible = "u-boot,sandbox-syslinux" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_sandbox) = { + .name = "bootmeth_sandbox", + .id = UCLASS_BOOTMETH, + .of_match = sandbox_bootmeth_ids, + .ops = &sandbox_bootmeth_ops, + .bind = sandbox_bootmeth_bind, +}; diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c new file mode 100644 index 0000000..d1c3f94 --- /dev/null +++ b/boot/bootmeth_script.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for booting via a U-Boot script + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <blk.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <dm.h> +#include <env.h> +#include <fs.h> +#include <image.h> +#include <malloc.h> +#include <mapmem.h> + +#define SCRIPT_FNAME1 "boot.scr.uimg" +#define SCRIPT_FNAME2 "boot.scr" + +static int script_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = NULL; + const char *const *prefixes; + struct udevice *bootstd; + const char *prefix; + int ret, i; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) + return log_msg_ret("std", ret); + + /* We require a partition table */ + if (!bflow->part) + return -ENOENT; + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + + prefixes = bootstd_get_prefixes(bootstd); + i = 0; + do { + prefix = prefixes ? prefixes[i] : NULL; + + ret = bootmeth_try_file(bflow, desc, prefix, SCRIPT_FNAME1); + if (ret) + ret = bootmeth_try_file(bflow, desc, prefix, + SCRIPT_FNAME2); + } while (ret && prefixes && prefixes[++i]); + if (ret) + return log_msg_ret("try", ret); + + bflow->subdir = strdup(prefix ? prefix : ""); + if (!bflow->subdir) + return log_msg_ret("prefix", -ENOMEM); + + ret = bootmeth_alloc_file(bflow, 0x10000, 1); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int script_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); + ulong addr; + int ret; + + ret = env_set("devtype", blk_get_devtype(bflow->blk)); + if (!ret) + ret = env_set_hex("devnum", desc->devnum); + if (!ret) + ret = env_set("prefix", bflow->subdir); + if (!ret && IS_ENABLED(CONFIG_ARCH_SUNXI) && + !strcmp("mmc", blk_get_devtype(bflow->blk))) + ret = env_set_hex("mmc_bootdev", desc->devnum); + if (ret) + return log_msg_ret("env", ret); + + log_debug("devtype: %s\n", env_get("devtype")); + log_debug("devnum: %s\n", env_get("devnum")); + log_debug("prefix: %s\n", env_get("prefix")); + log_debug("mmc_bootdev: %s\n", env_get("mmc_bootdev")); + + addr = map_to_sysmem(bflow->buf); + ret = image_source_script(addr, NULL); + if (ret) + return log_msg_ret("boot", ret); + + return 0; +} + +static int script_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "Script boot from a block device" : "script"; + + return 0; +} + +static struct bootmeth_ops script_bootmeth_ops = { + .check = script_check, + .read_bootflow = script_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = script_boot, +}; + +static const struct udevice_id script_bootmeth_ids[] = { + { .compatible = "u-boot,script" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_script) = { + .name = "bootmeth_script", + .id = UCLASS_BOOTMETH, + .of_match = script_bootmeth_ids, + .ops = &script_bootmeth_ops, + .bind = script_bootmeth_bind, +}; diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c new file mode 100644 index 0000000..3c6c32a --- /dev/null +++ b/boot/bootstd-uclass.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Uclass implementation for standard boot + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootflow.h> +#include <bootstd.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/read.h> +#include <dm/uclass-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* These are used if filename-prefixes is not present */ +const char *const default_prefixes[] = {"/", "/boot/", NULL}; + +static int bootstd_of_to_plat(struct udevice *dev) +{ + struct bootstd_priv *priv = dev_get_priv(dev); + int ret; + + if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + /* Don't check errors since livetree and flattree are different */ + ret = dev_read_string_list(dev, "filename-prefixes", + &priv->prefixes); + dev_read_string_list(dev, "bootdev-order", + &priv->bootdev_order); + } + + return 0; +} + +static void bootstd_clear_glob_(struct bootstd_priv *priv) +{ + while (!list_empty(&priv->glob_head)) { + struct bootflow *bflow; + + bflow = list_first_entry(&priv->glob_head, struct bootflow, + glob_node); + bootflow_remove(bflow); + } +} + +void bootstd_clear_glob(void) +{ + struct bootstd_priv *std; + + if (bootstd_get_priv(&std)) + return; + + bootstd_clear_glob_(std); +} + +static int bootstd_remove(struct udevice *dev) +{ + struct bootstd_priv *priv = dev_get_priv(dev); + + free(priv->prefixes); + free(priv->bootdev_order); + bootstd_clear_glob_(priv); + + return 0; +} + +const char *const *const bootstd_get_bootdev_order(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + return std->bootdev_order; +} + +const char *const *const bootstd_get_prefixes(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + return std->prefixes ? std->prefixes : default_prefixes; +} + +int bootstd_get_priv(struct bootstd_priv **stdp) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &dev); + if (ret) + return ret; + *stdp = dev_get_priv(dev); + + return 0; +} + +static int bootstd_probe(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + INIT_LIST_HEAD(&std->glob_head); + + return 0; +} + +/* For now, bind the boormethod device if none are found in the devicetree */ +int dm_scan_other(bool pre_reloc_only) +{ + struct driver *drv = ll_entry_start(struct driver, driver); + const int n_ents = ll_entry_count(struct driver, driver); + struct udevice *dev, *bootstd; + int i, ret; + + /* These are not needed before relocation */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + /* Create a bootstd device if needed */ + uclass_find_first_device(UCLASS_BOOTSTD, &bootstd); + if (!bootstd) { + ret = device_bind_driver(gd->dm_root, "bootstd_drv", "bootstd", + &bootstd); + if (ret) + return log_msg_ret("bootstd", ret); + } + + /* If there are no bootmeth devices, create them */ + uclass_find_first_device(UCLASS_BOOTMETH, &dev); + if (dev) + 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)) { + const char *name = drv->name; + + if (!strncmp("bootmeth_", name, 9)) + name += 9; + ret = device_bind(bootstd, drv, name, 0, ofnode_null(), + &dev); + if (ret) + return log_msg_ret("meth", ret); + } + } + + /* 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; +} + +static const struct udevice_id bootstd_ids[] = { + { .compatible = "u-boot,boot-std" }, + { } +}; + +U_BOOT_DRIVER(bootstd_drv) = { + .id = UCLASS_BOOTSTD, + .name = "bootstd_drv", + .of_to_plat = bootstd_of_to_plat, + .probe = bootstd_probe, + .remove = bootstd_remove, + .of_match = bootstd_ids, + .priv_auto = sizeof(struct bootstd_priv), +}; + +UCLASS_DRIVER(bootstd) = { + .id = UCLASS_BOOTSTD, + .name = "bootstd", +#if CONFIG_IS_ENABLED(OF_REAL) + .post_bind = dm_scan_fdt_dev, +#endif +}; diff --git a/boot/system_bootdev.c b/boot/system_bootdev.c new file mode 100644 index 0000000..432d203 --- /dev/null +++ b/boot/system_bootdev.c @@ -0,0 +1,66 @@ +// 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 <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <distro.h> +#include <dm.h> +#include <log.h> +#include <net.h> + +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/cmd/Kconfig b/cmd/Kconfig index 65517cb..2b575a2 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -211,6 +211,45 @@ config CMD_BOOTM_PRE_LOAD This stage allow to check or modify the image provided to the bootm command. +config CMD_BOOTDEV + bool "bootdev" + depends on BOOTSTD + default y if BOOTSTD_FULL + help + Support listing available bootdevs (boot devices) which can provide an + OS to boot, as well as showing information about a particular one. + + This command is not necessary for bootstd to work. + +config CMD_BOOTFLOW + bool "bootflow" + depends on BOOTSTD + default y + help + Support scanning for bootflows available with the bootdevs. The + bootflows can optionally be booted. + +config CMD_BOOTFLOW_FULL + bool "bootflow - extract subcommands" + depends on BOOTSTD_FULL + default y if BOOTSTD_FULL + help + Add the ability to list the available bootflows, select one and obtain + information about it. + + This command is not necessary for bootstd to work. + +config CMD_BOOTMETH + bool "bootmeth" + depends on BOOTSTD + default y if BOOTSTD_FULL + help + Support listing available bootmethds (methods used to boot an + Operating System), as well as selecting the order that the bootmeths + are used. + + This command is not necessary for bootstd to work. + config BOOTM_EFI bool "Support booting UEFI FIT images" depends on CMD_BOOTEFI && CMD_BOOTM && FIT diff --git a/cmd/Makefile b/cmd/Makefile index ede634d..5e43a1e 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -19,6 +19,9 @@ obj-$(CONFIG_CMD_AB_SELECT) += ab_select.o obj-$(CONFIG_CMD_ADC) += adc.o obj-$(CONFIG_CMD_ARMFLASH) += armflash.o obj-$(CONFIG_HAVE_BLOCK_DEVICE) += blk_common.o +obj-$(CONFIG_CMD_BOOTDEV) += bootdev.o +obj-$(CONFIG_CMD_BOOTFLOW) += bootflow.o +obj-$(CONFIG_CMD_BOOTMETH) += bootmeth.o obj-$(CONFIG_CMD_SOURCE) += source.o obj-$(CONFIG_CMD_BCB) += bcb.o obj-$(CONFIG_CMD_BDI) += bdinfo.o diff --git a/cmd/bootdev.c b/cmd/bootdev.c new file mode 100644 index 0000000..ecd797c --- /dev/null +++ b/cmd/bootdev.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootdev' command + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootstd.h> +#include <command.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +static int bootdev_check_state(struct bootstd_priv **stdp) +{ + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + if (!std->cur_bootdev) { + printf("Please use 'bootdev select' first\n"); + return -ENOENT; + } + *stdp = std; + + return 0; +} + +static int do_bootdev_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool probe; + + probe = argc >= 2 && !strcmp(argv[1], "-p"); + bootdev_list(probe); + + return 0; +} + +static int do_bootdev_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->cur_bootdev = NULL; + return 0; + } + if (bootdev_find_by_any(argv[1], &dev)) + return CMD_RET_FAILURE; + + std->cur_bootdev = dev; + + return 0; +} + +static int do_bootdev_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *priv; + struct bootflow *bflow; + int ret, i, num_valid; + struct udevice *dev; + bool probe; + + probe = argc >= 2 && !strcmp(argv[1], "-p"); + + ret = bootdev_check_state(&priv); + if (ret) + return CMD_RET_FAILURE; + + dev = priv->cur_bootdev; + + /* Count the number of bootflows, including how many are valid*/ + num_valid = 0; + for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; + !ret; + ret = bootdev_next_bootflow(&bflow), i++) + num_valid += bflow->state == BOOTFLOWST_READY; + + /* + * Prove the device, if requested, otherwise assume that there is no + * error + */ + ret = 0; + if (probe) + ret = device_probe(dev); + + printf("Name: %s\n", dev->name); + printf("Sequence: %d\n", dev_seq(dev)); + printf("Status: %s\n", ret ? simple_itoa(ret) : device_active(dev) ? + "Probed" : "OK"); + printf("Uclass: %s\n", dev_get_uclass_name(dev_get_parent(dev))); + printf("Bootflows: %d (%d valid)\n", i, num_valid); + + return 0; +} + +#ifdef CONFIG_SYS_LONGHELP +static char bootdev_help_text[] = + "list [-p] - list all available bootdevs (-p to probe)\n" + "bootdev select <bd> - select a bootdev by name | label | seq\n" + "bootdev info [-p] - show information about a bootdev (-p to probe)"; +#endif + +U_BOOT_CMD_WITH_SUBCMDS(bootdev, "Boot devices", bootdev_help_text, + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootdev_list), + U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootdev_select), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootdev_info)); diff --git a/cmd/bootflow.c b/cmd/bootflow.c new file mode 100644 index 0000000..af4b9c3 --- /dev/null +++ b/cmd/bootflow.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootflow' command + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootstd.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <mapmem.h> + +/** + * report_bootflow_err() - Report where a bootflow failed + * + * When a bootflow does not make it to the 'loaded' state, something went wrong. + * Print a helpful message if there is an error + * + * @bflow: Bootflow to process + * @err: Error code (0 if none) + */ +static void report_bootflow_err(struct bootflow *bflow, int err) +{ + if (!err) + return; + + /* Indent out to 'Method' */ + printf(" ** "); + + switch (bflow->state) { + case BOOTFLOWST_BASE: + printf("No media/partition found"); + break; + case BOOTFLOWST_MEDIA: + printf("No partition found"); + break; + case BOOTFLOWST_PART: + printf("No filesystem found"); + break; + case BOOTFLOWST_FS: + printf("File not found"); + break; + case BOOTFLOWST_FILE: + printf("File cannot be loaded"); + break; + case BOOTFLOWST_READY: + printf("Ready"); + break; + case BOOTFLOWST_COUNT: + break; + } + + printf(", err=%d\n", err); +} + +/** + * show_bootflow() - Show the status of a bootflow + * + * @seq: Bootflow index + * @bflow: Bootflow to show + * @errors: True to show the error received, if any + */ +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); + if (errors) + report_bootflow_err(bflow, bflow->err); +} + +static void show_header(void) +{ + printf("Seq Method State Uclass Part Name Filename\n"); + printf("--- ----------- ------ -------- ---- ------------------------ ----------------\n"); +} + +static void show_footer(int count, int num_valid) +{ + printf("--- ----------- ------ -------- ---- ------------------------ ----------------\n"); + printf("(%d bootflow%s, %d valid)\n", count, count != 1 ? "s" : "", + num_valid); +} + +static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow_iter iter; + struct udevice *dev; + struct bootflow bflow; + bool all = false, boot = false, errors = false, list = false; + int num_valid = 0; + bool has_args; + int ret, i; + int flags; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + dev = std->cur_bootdev; + + has_args = argc > 1 && *argv[1] == '-'; + if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL)) { + if (has_args) { + all = strchr(argv[1], 'a'); + boot = strchr(argv[1], 'b'); + errors = strchr(argv[1], 'e'); + list = strchr(argv[1], 'l'); + argc--; + argv++; + } + if (argc > 1) { + const char *label = argv[1]; + + if (bootdev_find_by_any(label, &dev)) + return CMD_RET_FAILURE; + } + } else { + if (has_args) { + printf("Flags not supported: enable CONFIG_BOOTFLOW_FULL\n"); + return CMD_RET_USAGE; + } + boot = true; + } + + std->cur_bootflow = NULL; + + flags = 0; + if (list) + flags |= BOOTFLOWF_SHOW; + if (all) + flags |= BOOTFLOWF_ALL; + + /* + * If we have a device, just scan for bootflows attached to that device + */ + if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && dev) { + if (list) { + printf("Scanning for bootflows in bootdev '%s'\n", + dev->name); + show_header(); + } + bootdev_clear_bootflows(dev); + for (i = 0, + ret = bootflow_scan_bootdev(dev, &iter, flags, &bflow); + i < 1000 && ret != -ENODEV; + i++, ret = bootflow_scan_next(&iter, &bflow)) { + bflow.err = ret; + if (!ret) + num_valid++; + ret = bootdev_add_bootflow(&bflow); + if (ret) { + printf("Out of memory\n"); + return CMD_RET_FAILURE; + } + if (list) + show_bootflow(i, &bflow, errors); + if (boot && !bflow.err) + bootflow_run_boot(&iter, &bflow); + } + } else { + if (list) { + printf("Scanning for bootflows in all bootdevs\n"); + show_header(); + } + bootstd_clear_glob(); + + for (i = 0, + ret = bootflow_scan_first(&iter, flags, &bflow); + i < 1000 && ret != -ENODEV; + i++, ret = bootflow_scan_next(&iter, &bflow)) { + bflow.err = ret; + if (!ret) + num_valid++; + ret = bootdev_add_bootflow(&bflow); + if (ret) { + printf("Out of memory\n"); + return CMD_RET_FAILURE; + } + if (list) + show_bootflow(i, &bflow, errors); + if (boot && !bflow.err) + bootflow_run_boot(&iter, &bflow); + } + } + bootflow_iter_uninit(&iter); + if (list) + show_footer(i, num_valid); + + return 0; +} + +#ifdef CONFIG_CMD_BOOTFLOW_FULL +static int do_bootflow_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + struct bootflow *bflow; + int num_valid = 0; + bool errors = false; + int ret, i; + + if (argc > 1 && *argv[1] == '-') + errors = strchr(argv[1], 'e'); + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + dev = std->cur_bootdev; + + /* If we have a device, just list bootflows attached to that device */ + if (dev) { + printf("Showing bootflows for bootdev '%s'\n", dev->name); + show_header(); + for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; + !ret; + ret = bootdev_next_bootflow(&bflow), i++) { + num_valid += bflow->state == BOOTFLOWST_READY; + show_bootflow(i, bflow, errors); + } + } else { + printf("Showing all bootflows\n"); + show_header(); + for (ret = bootflow_first_glob(&bflow), i = 0; + !ret; + ret = bootflow_next_glob(&bflow), i++) { + num_valid += bflow->state == BOOTFLOWST_READY; + show_bootflow(i, bflow, errors); + } + } + show_footer(i, num_valid); + + return 0; +} + +static int do_bootflow_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow, *found; + struct udevice *dev; + const char *name; + char *endp; + int seq, i; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; +; + if (argc < 2) { + std->cur_bootflow = NULL; + return 0; + } + dev = std->cur_bootdev; + + name = argv[1]; + seq = simple_strtol(name, &endp, 16); + found = NULL; + + /* + * If we have a bootdev device, only allow selection of bootflows + * attached to that device + */ + if (dev) { + for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; + !ret; + ret = bootdev_next_bootflow(&bflow), i++) { + if (*endp ? !strcmp(bflow->name, name) : i == seq) { + found = bflow; + break; + } + } + } else { + for (ret = bootflow_first_glob(&bflow), i = 0; + !ret; + ret = bootflow_next_glob(&bflow), i++) { + if (*endp ? !strcmp(bflow->name, name) : i == seq) { + found = bflow; + break; + } + } + } + + if (!found) { + printf("Cannot find bootflow '%s' ", name); + if (dev) + printf("in bootdev '%s' ", dev->name); + printf("(err=%d)\n", ret); + return CMD_RET_FAILURE; + } + std->cur_bootflow = found; + + return 0; +} + +static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + bool dump = false; + int ret; + + if (argc > 1 && *argv[1] == '-') + dump = strchr(argv[1], 'd'); + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + if (!std->cur_bootflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + bflow = std->cur_bootflow; + + printf("Name: %s\n", bflow->name); + printf("Device: %s\n", bflow->dev->name); + printf("Block dev: %s\n", bflow->blk ? bflow->blk->name : "(none)"); + printf("Method: %s\n", bflow->method->name); + printf("State: %s\n", bootflow_state_get_name(bflow->state)); + printf("Partition: %d\n", bflow->part); + printf("Subdir: %s\n", bflow->subdir ? bflow->subdir : "(none)"); + printf("Filename: %s\n", bflow->fname); + printf("Buffer: %lx\n", (ulong)map_to_sysmem(bflow->buf)); + printf("Size: %x (%d bytes)\n", bflow->size, bflow->size); + printf("Error: %d\n", bflow->err); + if (dump && bflow->buf) { + /* Set some sort of maximum on the size */ + int size = min(bflow->size, 10 << 10); + int i; + + printf("Contents:\n\n"); + for (i = 0; i < size; i++) { + putc(bflow->buf[i]); + if (!(i % 128) && ctrlc()) { + printf("...interrupted\n"); + break; + } + } + } + + return 0; +} + +static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + /* + * Require a current bootflow. Users can use 'bootflow scan -b' to + * automatically scan and boot, if needed. + */ + if (!std->cur_bootflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + bflow = std->cur_bootflow; + ret = bootflow_run_boot(NULL, bflow); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} +#endif /* CONFIG_CMD_BOOTFLOW_FULL */ + +#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" + "bootflow list [-e] - list scanned bootflows (-e errors)\n" + "bootflow select [<num>|<name>] - select a bootflow\n" + "bootflow info [-d] - show info on current bootflow (-d dump bootflow)\n" + "bootflow boot - boot current bootflow (or first available if none selected)"; +#else + "scan - boot first available bootflow\n"; +#endif +#endif /* CONFIG_SYS_LONGHELP */ + +U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text, + U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_bootflow_scan), +#ifdef CONFIG_CMD_BOOTFLOW_FULL + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list), + U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info), + U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot) +#endif +); diff --git a/cmd/bootmeth.c b/cmd/bootmeth.c new file mode 100644 index 0000000..c9a27fe --- /dev/null +++ b/cmd/bootmeth.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootmeth' command + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <dm.h> +#include <malloc.h> +#include <dm/uclass-internal.h> + +static int do_bootmeth_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + bool use_order; + bool all = false; + int ret; + int i; + + if (argc > 1 && *argv[1] == '-') { + all = strchr(argv[1], 'a'); + argc--; + argv++; + } + + ret = bootstd_get_priv(&std); + if (ret) { + printf("Cannot get bootstd (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + + printf("Order Seq Name Description\n"); + printf("----- --- ------------------ ------------------\n"); + + /* + * Use the ordering if we have one, so long as we are not trying to list + * all bootmethds + */ + use_order = std->bootmeth_count && !all; + if (use_order) + dev = std->bootmeth_order[0]; + else + ret = uclass_find_first_device(UCLASS_BOOTMETH, &dev); + + for (i = 0; dev;) { + struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(dev); + int order = i; + + /* + * With the -a flag we may list bootdevs that are not in the + * ordering. Find their place in the order + */ + if (all && std->bootmeth_count) { + int j; + + /* Find the position of this bootmeth in the order */ + order = -1; + for (j = 0; j < std->bootmeth_count; j++) { + if (std->bootmeth_order[j] == dev) + order = j; + } + } + + if (order == -1) + printf("%5s", "-"); + else + printf("%5x", order); + printf(" %3x %-19.19s %s\n", dev_seq(dev), dev->name, + ucp->desc); + i++; + if (use_order) + dev = std->bootmeth_order[i]; + else + uclass_find_next_device(&dev); + } + printf("----- --- ------------------ ------------------\n"); + printf("(%d bootmeth%s)\n", i, i != 1 ? "s" : ""); + + return 0; +} + +static int do_bootmeth_order(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = bootmeth_set_order(argv[1]); + if (ret) { + printf("Failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + env_set("bootmeths", argv[1]); + + return 0; +} + +#ifdef CONFIG_SYS_LONGHELP +static char bootmeth_help_text[] = + "list [-a] - list available bootmeths (-a all)\n" + "bootmeth order [<bd> ...] - select bootmeth order / subset to use"; +#endif + +U_BOOT_CMD_WITH_SUBCMDS(bootmeth, "Boot methods", bootmeth_help_text, + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootmeth_list), + U_BOOT_SUBCMD_MKENT(order, CONFIG_SYS_MAXARGS, 1, do_bootmeth_order)); diff --git a/common/usb_storage.c b/common/usb_storage.c index 291728f..eaa3137 100644 --- a/common/usb_storage.c +++ b/common/usb_storage.c @@ -34,6 +34,7 @@ #include <common.h> #include <blk.h> +#include <bootdev.h> #include <command.h> #include <dm.h> #include <errno.h> @@ -243,6 +244,16 @@ static int usb_stor_probe_device(struct usb_device *udev) ret = blk_probe_or_unbind(dev); if (ret) return ret; + + ret = bootdev_setup_sibling_blk(dev, "usb_bootdev"); + if (ret) { + int ret2; + + ret2 = device_unbind(dev); + if (ret2) + return log_msg_ret("bootdev", ret2); + return log_msg_ret("bootdev", ret); + } } #else /* We don't have space to even probe if we hit the maximum */ diff --git a/configs/efi-x86_app32_defconfig b/configs/efi-x86_app32_defconfig index 228643a..7a723c1 100644 --- a/configs/efi-x86_app32_defconfig +++ b/configs/efi-x86_app32_defconfig @@ -8,6 +8,7 @@ CONFIG_VENDOR_EFI=y CONFIG_TARGET_EFI_APP32=y CONFIG_DEBUG_UART=y CONFIG_FIT=y +# CONFIG_BOOTSTD is not set CONFIG_SHOW_BOOT_PROGRESS=y CONFIG_USE_BOOTARGS=y CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro" diff --git a/configs/efi-x86_app64_defconfig b/configs/efi-x86_app64_defconfig index 1ed2f13..98f91d8 100644 --- a/configs/efi-x86_app64_defconfig +++ b/configs/efi-x86_app64_defconfig @@ -8,6 +8,7 @@ CONFIG_VENDOR_EFI=y CONFIG_TARGET_EFI_APP64=y CONFIG_DEBUG_UART=y CONFIG_FIT=y +# CONFIG_BOOTSTD is not set CONFIG_SHOW_BOOT_PROGRESS=y CONFIG_USE_BOOTARGS=y CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro" diff --git a/configs/rcar3_salvator-x_defconfig b/configs/rcar3_salvator-x_defconfig index f333291..6ef62ab 100644 --- a/configs/rcar3_salvator-x_defconfig +++ b/configs/rcar3_salvator-x_defconfig @@ -14,6 +14,7 @@ CONFIG_SYS_LOAD_ADDR=0x58000000 CONFIG_LTO=y CONFIG_REMAKE_ELF=y CONFIG_FIT=y +# CONFIG_BOOTSTD is not set CONFIG_SUPPORT_RAW_INITRD=y CONFIG_SYS_MONITOR_BASE=0x00000000 CONFIG_USE_BOOTARGS=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 14d7af4..fe8ea46 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -28,7 +28,7 @@ CONFIG_AUTOBOOT_STOP_STR_CRYPT="$5$rounds=640000$HrpE65IkB8CM5nCL$BKT3QdF98Bo8fJ CONFIG_IMAGE_PRE_LOAD=y CONFIG_IMAGE_PRE_LOAD_SIG=y CONFIG_CONSOLE_RECORD=y -CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000 +CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000 CONFIG_PRE_CONSOLE_BUFFER=y CONFIG_LOG=y CONFIG_DISPLAY_BOARDINFO_LATE=y @@ -120,6 +120,7 @@ CONFIG_ENV_IS_IN_EXT4=y CONFIG_ENV_EXT4_INTERFACE="host" CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0" CONFIG_ENV_IMPORT_FDT=y +# CONFIG_BOOTDEV_ETH is not set CONFIG_BOOTP_SEND_HOSTNAME=y CONFIG_NETCONSOLE=y CONFIG_IP_DEFRAG=y diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig index ded16af..80a4be4 100644 --- a/configs/sandbox_flattree_defconfig +++ b/configs/sandbox_flattree_defconfig @@ -17,7 +17,7 @@ CONFIG_BOOTSTAGE_FDT=y CONFIG_BOOTSTAGE_STASH=y CONFIG_BOOTSTAGE_STASH_SIZE=0x4096 CONFIG_CONSOLE_RECORD=y -CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000 +CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000 CONFIG_DISPLAY_BOARDINFO_LATE=y CONFIG_CMD_CPU=y CONFIG_CMD_LICENSE=y @@ -72,6 +72,7 @@ CONFIG_ENV_IS_NOWHERE=y CONFIG_ENV_IS_IN_EXT4=y CONFIG_ENV_EXT4_INTERFACE="host" CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0" +# CONFIG_BOOTDEV_ETH is not set CONFIG_BOOTP_SEND_HOSTNAME=y CONFIG_NETCONSOLE=y CONFIG_IP_DEFRAG=y diff --git a/configs/tbs2910_defconfig b/configs/tbs2910_defconfig index 60a8160..9f7642f 100644 --- a/configs/tbs2910_defconfig +++ b/configs/tbs2910_defconfig @@ -17,6 +17,7 @@ CONFIG_SYS_MEMTEST_END=0x2f400000 CONFIG_LTO=y CONFIG_HAS_BOARD_SIZE_LIMIT=y CONFIG_BOARD_SIZE_LIMIT=392192 +# CONFIG_BOOTSTD is not set CONFIG_SUPPORT_RAW_INITRD=y CONFIG_BOOTDELAY=3 CONFIG_USE_BOOTCOMMAND=y diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst new file mode 100644 index 0000000..5e9c0d2 --- /dev/null +++ b/doc/develop/bootstd.rst @@ -0,0 +1,638 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +U-Boot Standard Boot +==================== + +Introduction +------------ + +Standard boot provides a built-in way for U-Boot to automatically boot +an Operating System without custom scripting and other customisation. It +introduces the following concepts: + + - bootdev - a device which can hold or access a distro (e.g. MMC, Ethernet) + - bootmeth - a method to scan a bootdev to find bootflows (e.g. distro boot) + - bootflow - a description of how to boot (provided by the distro) + +For Linux, the distro (Linux distribution, e.g. Debian, Fedora) is responsible +for creating a bootflow for each kernel combination that it wants to offer. +These bootflows are stored on media so they can be discovered by U-Boot. This +feature is typically called `distro boot` (see :doc:`distro`) because it is +a way for distributions to boot on any hardware. + +Traditionally U-Boot has relied on scripts to implement this feature. See +disto_boodcmd_ for details. This is done because U-Boot has no native support +for scanning devices. While the scripts work remarkably well, they can be hard +to understand and extend, and the feature does not include tests. They are also +making it difficult to move away from ad-hoc CONFIGs, since they are implemented +using the environment and a lot of #defines. + +Standard boot is a generalisation of distro boot. It provides a more built-in +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. + + +Bootflow +-------- + +A bootflow is a file that describes how to boot a distro. Conceptually there can +be different formats for that file but at present U-Boot only supports the +BootLoaderSpec_ format. which looks something like this:: + + menu autoboot Welcome to Fedora-Workstation-armhfp-31-1.9. Automatic boot in # second{,s}. Press a key for options. + menu title Fedora-Workstation-armhfp-31-1.9 Boot Options. + menu hidden + + label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl) + kernel /vmlinuz-5.3.7-301.fc31.armv7hl + append ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB + fdtdir /dtb-5.3.7-301.fc31.armv7hl/ + initrd /initramfs-5.3.7-301.fc31.armv7hl.img + +As you can see it specifies a kernel, a ramdisk (initrd) and a directory from +which to load devicetree files. The details are described in disto_boodcmd_. + +The bootflow is provided by the distro. It is not part of U-Boot. U-Boot's job +is simply to interpret the file and carry out the instructions. This allows +distros to boot on essentially any device supported by U-Boot. + +Typically the first available bootflow is selected and booted. If that fails, +then the next one is tried. + + +Bootdev +------- + +Where does U-Boot find the media that holds the operating systems? That is the +job of bootdev. A bootdev is simply a layer on top of a media device (such as +MMC, NVMe). The bootdev accesses the device, including partitions and +filesystems that might contain things related to an operating system. + +For example, an MMC bootdev provides access to the individual partitions on the +MMC device. It scans through these to find filesystems, then provides a list of +these for consideration. + + +Bootmeth +-------- + +Once the list of filesystems is provided, how does U-Boot find the bootflow +files in these filesystems. That is the job of bootmeth. Each boot method has +its own way of doing this. + +For example, the distro bootmeth simply looks through the provided filesystem +for a file called `extlinux/extlinux.conf`. This files constitutes a bootflow. +If the distro bootmeth is used on multiple partitions it may produce multiple +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. + + +Boot process +------------ + +U-Boot tries to use the 'lazy init' approach whereever possible and distro boot +is no exception. The algorithm is:: + + while (get next bootdev) + while (get next bootmeth) + while (get next bootflow) + try to boot it + +So U-Boot works its way through the bootdevs, trying each bootmeth in turn to +obtain bootflows, until it either boots or exhausts the available options. + +Instead of 500 lines of #defines and a 4KB boot script, all that is needed is +the following command:: + + bootflow scan -lb + +which scans for available bootflows, optionally listing each find it finds (-l) +and trying to boot it (-b). + + +Controlling ordering +-------------------- + +Several options are available to control the ordering of boot scanning: + + +boot_targets +~~~~~~~~~~~~ + +This environment variable can be used to control the list of bootdevs searched +and their ordering, for example:: + + setenv boot_targets "mmc0 mmc1 usb pxe" + +Entries may be removed or re-ordered in this list to affect the boot order. If +the variable is empty, the default ordering is used, based on the priority of +bootdevs and their sequence numbers. + + +bootmeths +~~~~~~~~~ + +This environment variable can be used to control the list of bootmeths used and +their ordering for example:: + + setenv bootmeths "syslinux efi" + +Entries may be removed or re-ordered in this list to affect the order the +bootmeths are tried on each bootdev. If the variable is empty, the default +ordering is used, based on the bootmeth sequence numbers, which can be +controlled by aliases. + +The :ref:`usage/cmd/bootmeth:bootmeth command` (`bootmeth order`) operates in +the same way as setting this variable. + + +Bootdev uclass +-------------- + +The bootdev uclass provides an simple API call to obtain a bootflows from a +device:: + + int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow); + +This takes a iterator which indicates the bootdev, partition and bootmeth to +use. It returns a bootflow. This is the core of the bootdev implementation. The +bootdev drivers that implement this differ depending on the media they are +reading from, but each is responsible for returning a valid bootflow if +available. + +A helper called `bootdev_find_in_blk()` makes it fairly easy to implement this +function for each media device uclass, in a few lines of code. + + +Bootdev drivers +--------------- + +A bootdev driver is typically fairly simple. Here is one for mmc:: + + static int mmc_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) + { + struct udevice *mmc_dev = dev_get_parent(dev); + struct udevice *blk; + int ret; + + ret = mmc_get_blk(mmc_dev, &blk); + /* + * If there is no media, indicate that no more partitions should be + * checked + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + if (ret) + return log_msg_ret("blk", ret); + assert(blk); + ret = bootdev_find_in_blk(dev, blk, iter, bflow); + if (ret) + return log_msg_ret("find", ret); + + return 0; + } + + static int mmc_bootdev_bind(struct udevice *dev) + { + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_0_INTERNAL_FAST; + + return 0; + } + + struct bootdev_ops mmc_bootdev_ops = { + .get_bootflow = mmc_get_bootflow, + }; + + static const struct udevice_id mmc_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-mmc" }, + { } + }; + + U_BOOT_DRIVER(mmc_bootdev) = { + .name = "mmc_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &mmc_bootdev_ops, + .bind = mmc_bootdev_bind, + .of_match = mmc_bootdev_ids, + }; + +The implementation of the `get_bootflow()` method is simply to obtain the +block device and call a bootdev helper function to do the rest. The +implementation of `bootdev_find_in_blk()` checks the partition table, and +attempts to read a file from a filesystem on the partition number given by the +`@iter->part` parameter. + +Each bootdev has a priority, which indicates the order in which it is used. +Faster bootdevs are used first, since they are more likely to be able to boot +the device quickly. + + +Device hierarchy +---------------- + +A bootdev device is a child of the media device. In this example, you can see +that the bootdev is a sibling of the block device and both are children of +media device:: + + mmc 0 [ + ] bcm2835-sdhost | |-- mmc@7e202000 + blk 0 [ + ] mmc_blk | | |-- mmc@7e202000.blk + bootdev 0 [ ] mmc_bootdev | | `-- mmc@7e202000.bootdev + mmc 1 [ + ] sdhci-bcm2835 | |-- sdhci@7e300000 + blk 1 [ ] mmc_blk | | |-- sdhci@7e300000.blk + bootdev 1 [ ] mmc_bootdev | | `-- sdhci@7e300000.bootdev + +The bootdev device is typically created automatically in the media uclass' +`post_bind()` method by calling `bootdev_setup_for_dev()`. The code typically +something like this:: + + ret = bootdev_setup_for_dev(dev, "eth_bootdev"); + if (ret) + return log_msg_ret("bootdev", ret); + +Here, `eth_bootdev` is the name of the Ethernet bootdev driver and `dev` +is the ethernet device. This function is safe to call even if standard boot is +not enabled, since it does nothing in that case. It can be added to all uclasses +which implement suitable media. + + +The bootstd device +------------------ + +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. + + +.. _`Automatic Devices`: + +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. + +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. + + +Using devicetree +---------------- + +If a bootdev is complicated or needs configuration information, it can be +added to the devicetree as a child of the media device. For example, imagine a +bootdev which reads a bootflow from SPI flash. The devicetree fragment might +look like this:: + + spi@0 { + flash@0 { + reg = <0>; + compatible = "spansion,m25p16", "jedec,spi-nor"; + spi-max-frequency = <40000000>; + + bootdev { + compatible = "u-boot,sf-bootdev"; + offset = <0x2000>; + size = <0x1000>; + }; + }; + }; + +The `sf-bootdev` driver can implement a way to read from the SPI flash, using +the offset and size provided, and return that bootflow file back to the caller. +When distro boot wants to read the kernel it calls disto_getfile() which must +provide a way to read from the SPI flash. See `distro_boot()` at distro_boot_ +for more details. + +Of course this is all internal to U-Boot. All the distro sees is another way +to boot. + + +Configuration +------------- + +Standard boot is enabled with `CONFIG_BOOTSTD`. Each bootmeth has its own CONFIG +option also. For example, `CONFIG_BOOTMETH_DISTRO` enables support for distro +boot from a disk. + + +Available bootmeth drivers +-------------------------- + +Bootmeth drivers are provided for: + + - distro boot from a disk (syslinux) + - distro boot from a network (PXE) + - EFI boot using bootefi + - EFI boot using boot manager + + +Command interface +----------------- + +Three commands are available: + +`bootdev` + Allows listing of available bootdevs, selecting a particular one and + getting information about it. See :doc:`../usage/cmd/bootdev` + +`bootflow` + Allows scanning one or more bootdevs for bootflows, listing available + bootflows, selecting one, obtaining information about it and booting it. + See :doc:`../usage/cmd/bootflow` + +`bootmeth` + Allow listing of available bootmethds and setting the order in which they + are tried. See :doc:`../usage/cmd/bootmeth` + +.. _BootflowStates: + +Bootflow states +--------------- + +Here is a list of states that a bootflow can be in: + +======= ======================================================================= +State Meaning +======= ======================================================================= +base Starting-out state, indicates that no media/partition was found. For an + SD card socket it may indicate that the card is not inserted. +media Media was found (e.g. SD card is inserted) but no partition information + was found. It might lack a partition table or have a read error. +part Partition was found but a filesystem could not be read. This could be + because the partition does not hold a filesystem or the filesystem is + very corrupted. +fs Filesystem was found but the file could not be read. It could be + missing or in the wrong subdirectory. +file File was found and its size detected, but it could not be read. This + could indicate filesystem corruption. +ready File was loaded and is ready for use. In this state the bootflow is + ready to be booted. +======= ======================================================================= + + +Theory of operation +------------------- + +This describes how standard boot progresses through to booting an operating +system. + +To start. all the necessary devices must be bound, including bootstd, which +provides the top-level `struct bootstd_priv` containing optional configuration +information. The bootstd device is also holds the various lists used while +scanning. This step is normally handled automatically by driver model, as +described in `Automatic Devices`_. + +Bootdevs are also required, to provide access to the media to use. These are not +useful by themselves: bootmeths are needed to provide the means of scanning +those bootdevs. So, all up, we need a single bootstd device, one or more bootdev +devices and one or more bootmeth devices. + +Once these are ready, typically a `bootflow scan` command is issued. This kicks +of the iteration process, which involves looking through the bootdevs and their +partitions one by one to find bootflows. + +Iteration is kicked off using `bootflow_scan_first()`, which calls +`bootflow_scan_bootdev()`. + +The iterator is set up with `bootflow_iter_init()`. This simply creates an +empty one with the given flags. Flags are used to control whether each +iteration is displayed, whether to return iterations even if they did not result +in a valid bootflow, whether to iterate through just a single bootdev, etc. + +Then the ordering of bootdevs is determined, by `bootdev_setup_iter_order()`. By +default, the bootdevs are used in the order specified by the `boot_targets` +environment variable (e.g. "mmc2 mmc0 usb"). If that is missing then their +sequence order is used, as determined by the `/aliases` node, or failing that +their order in the devicetree. For BOOTSTD_FULL, if there is a `bootdev-order` +property in the bootstd node, then this is used as a final fallback. In any +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()`. +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. + +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 +about is 0, meaning that, as far as we know, there is no partition table on this +bootdev. + +With the iterator ready, `bootflow_scan_bootdev()` checks whether the current +settings produce a valid bootflow. This is handled by `bootflow_check()`, which +either returns 0 (if it got something) or an error if not (more on that later). +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. + +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. + +The actual 'moving on' part is implemented in `iter_incr()`. This is a very +simple function. It increments the first counter. If that hits its maximum, it +sets it to zero and increments the second counter. You can think of all the +counters together as a number with three digits which increment in order, with +the least-sigificant digit on the right, counting like this: + + ======== ======= ======= + bootdev part method + ======== ======= ======= + 0 0 0 + 0 0 1 + 0 0 2 + 0 1 0 + 0 1 1 + 0 1 1 + 1 0 0 + 1 0 1 + ======== ======= ======= + +The maximum value for `method` is `num_methods - 1` so when it exceeds that, it +goes back to 0 and the next `part` is considered. The maximum value for that is +`max_part`, which is initially zero for all bootdevs. If we find a partition +table on that bootdev, `max_part` can be updated during the iteration to a +higher value - see `bootdev_find_in_blk()` for that, described later. If that +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. + +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 +completes. + +Also note that the iterator hold the **current** combination being considered. +So when `iter_incr()` is called, it increments to the next one and returns it, +the new **current** combination. + +Note also the `err` field in `struct bootflow_iter`. This is normally 0 and has +thus has no effect on `iter_inc()`. But if it is non-zero, signalling an error, +it indicates to the iterator what it should do when called. It can force moving +to the next partition, or bootdev, for example. The special values +`BF_NO_MORE_PARTS` and `BF_NO_MORE_DEVICES` handle this. When `iter_incr` sees +`BF_NO_MORE_PARTS` it knows that it should immediately move to the next bootdev. +When it sees `BF_NO_MORE_DEVICES` it knows that there is nothing more it can do +so it should immediately return. The caller of `iter_incr()` is responsible for +updating the `err` field, based on the return value it sees. + +The above describes the iteration process at a high level. It is basically a +very simple increment function with a checker called `bootflow_check()` that +checks the result of each iteration generated, to determine whether it can +produce a bootflow. + +So what happens inside of `bootflow_check()`? It simply calls the uclass +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. + +Based on what the bootdev 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. + +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 +`bootdev_get_bootflow()` ends up in `mmc_get_bootflow()`. It locates the parent +device of the bootdev, i.e. the `UCLASS_MMC` device itself, then finds the block +device associated with it. It then calls the helper function +`bootdev_find_in_blk()` to do all the work. This is common with just about any +bootdev that is based on a media device. + +The `bootdev_find_in_blk()` helper is implemented in the bootdev uclass. It +names the bootflow and copies the partition number in from the iterator. Then it +calls the bootmeth device to check if it can support this device. This is +important since some bootmeths only work with network devices, for example. If +that check fails, it stops. + +Assuming the bootmeth is happy, or at least indicates that it is willing to try +(by returning 0 from its `check()` method), the next step is to try the +partition. If that works it tries to detect a file system. If that works then it +calls the bootmeth device once more, this time to read the bootflow. + +Note: At present a filesystem is needed for the bootmeth to be called on block +devices, simply because we don't have any examples where this is not the case. +This feature can be added as needed. + +If we take the example of the `bootmeth_distro` driver, this call ends up at +`distro_read_bootflow()`. It has the filesystem ready, so tries various +filenames to try to find the `extlinux.conf` file, reading it if possible. If +all goes well the bootflow ends up in the `BOOTFLOWST_READY` state. + +At this point, we fall back from the bootmeth driver, to +`bootdev_find_in_blk()`, then back to `mmc_get_bootflow()`, then to +`bootdev_get_bootflow()`, then to `bootflow_check()` and finally to its caller, +either `bootflow_scan_bootdev()` or `bootflow_scan_next()`. In either case, +the bootflow is returned as the result of this iteration, assuming it made it to +the `BOOTFLOWST_READY` state. + +That is the basic operation of scanning for bootflows. The process of booting a +bootflow is handled by the bootmeth driver for that bootflow. In the case of +distro boot, this parses and processes the `extlinux.conf` file that was read. +See `distro_boot()` for how that works. The processing may involve reading +additional files, which is handled by the `read_file()` method, which is +`distro_read_file()` in this case. All bootmethds should support reading files, +since the bootflow is typically only the basic instructions and does not include +the operating system itself, ramdisk, device tree, etc. + +The vast majority of the bootstd code is concerned with iterating through +partitions on bootdevs and using bootmethds to find bootflows. + +How about bootdevs which are not block devices? They are handled by the same +methods as above, but with a different implementation. For example, the bootmeth +for PXE boot (over a network) uses `tftp` to read files rather than `fs_read()`. +But other than that it is very similar. + + +Tests +----- + +Tests are located in `test/boot` and cover the core functionality as well as +the commands. All tests use sandbox so can be run on a standard Linux computer +and in U-Boot's CI. + +For testing, a DOS-formatted disk image is used with a single FAT partition on +it. This is created in `setup_bootflow_image()`, with a canned one from the +source tree used if it cannot be created (e.g. in CI). + + +Bootflow internals +------------------ + +The bootstd device holds a linked list of scanned bootflows as well as the +currently selected bootdev and bootflow (for use by commands). This is in +`struct bootstd_priv`. + +Each bootdev device has its own `struct bootdev_uc_plat` which holds a +list of scanned bootflows just for that device. + +The bootflow itself is documented in bootflow_h_. It includes various bits of +information about the bootflow and a buffer to hold the file. + + +Future +------ + +Apart from the to-do items below, different types of bootflow files may be +implemented in future, e.g. Chromium OS support which is currently only +available as a script in chromebook_coral. + + +To do +----- + +Some things that need to be done to completely replace the distro-boot scripts: + +- add bootdev drivers for dhcp, sata, scsi, ide, virtio +- PXE boot for EFI +- support for loading U-Boot scripts + +Other ideas: + +- `bootflow prep` to load everything preparing for boot, so that `bootflow boot` + can just do the boot. +- automatically load kernel, FDT, etc. to suitable addresses so the board does + not need to specify things like `pxefile_addr_r` + + +.. _disto_boodcmd: https://github.com/u-boot/u-boot/blob/master/include/config_distro_bootcmd.h +.. _BootLoaderSpec: http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/ +.. _distro_boot: https://github.com/u-boot/u-boot/blob/master/boot/distro.c +.. _bootflow_h: https://github.com/u-boot/u-boot/blob/master/include/bootflow.h diff --git a/doc/develop/distro.rst b/doc/develop/distro.rst index 0113a3d..3ee3dac 100644 --- a/doc/develop/distro.rst +++ b/doc/develop/distro.rst @@ -157,6 +157,9 @@ a line with "CONFIG_DISTRO_DEFAULTS=y". If you want to enable this from Kconfig itself, for e.g. all boards using a specific SoC then add a "imply DISTRO_DEFAULTS" to your SoC CONFIG option. + +TO BE UPDATED: + In your board configuration file, include the following:: #ifndef CONFIG_SPL_BUILD diff --git a/doc/develop/index.rst b/doc/develop/index.rst index 2e6d6c3..d32ec49 100644 --- a/doc/develop/index.rst +++ b/doc/develop/index.rst @@ -10,6 +10,7 @@ Implementation :maxdepth: 1 bloblist + bootstd ci_testing commands config_binding diff --git a/doc/device-tree-bindings/bootdev.txt b/doc/device-tree-bindings/bootdev.txt new file mode 100644 index 0000000..4bb2345 --- /dev/null +++ b/doc/device-tree-bindings/bootdev.txt @@ -0,0 +1,26 @@ +U-Boot boot device (bootdev) +============================ + +A bootdev provides a way to obtain a bootflow file from a device. It is a +child of the media device (UCLASS_MMC, UCLASS_SPI_FLASH, etc.) + +The bootdev driver is provided by the media devices. The bindings for each +are described in this file (to come). + +Required properties: + +compatible: + "u-boot,bootdev-eth" - Ethernet bootdev + "u-boot,bootdev-mmc" - MMC bootdev + "u-boot,bootdev-usb" - USB bootdev + + +Example: + + mmc1 { + compatible = "sandbox,mmc"; + + mmc-bootdev { + compatible = "u-boot,bootdev-eth"; + }; + }; diff --git a/doc/device-tree-bindings/bootmeth.txt b/doc/device-tree-bindings/bootmeth.txt new file mode 100644 index 0000000..de6396a --- /dev/null +++ b/doc/device-tree-bindings/bootmeth.txt @@ -0,0 +1,31 @@ +U-Boot standard boot methods (bootmeth) +====================================== + +This provides methods (called bootmeths) for locating bootflows on a boot +device (bootdev). These are normally created as children of the bootstd device. + +Required properties: + +compatible: + "u-boot,distro-syslinux" - distro boot from a block device + "u-boot,distro-pxe" - distro boot from a network device + "u-boot,distro-efi" - EFI boot from an .efi file + "u-boot,efi-bootmgr" - EFI boot using boot manager (bootmgr) + + +Example: + + bootstd { + compatible = "u-boot,boot-std"; + + filename-prefixes = "/", "/boot/"; + bootdev-order = "mmc2", "mmc1"; + + syslinux { + compatible = "u-boot,distro-syslinux"; + }; + + efi { + compatible = "u-boot,distro-efi"; + }; + }; diff --git a/doc/device-tree-bindings/bootstd.txt b/doc/device-tree-bindings/bootstd.txt new file mode 100644 index 0000000..8706c5f --- /dev/null +++ b/doc/device-tree-bindings/bootstd.txt @@ -0,0 +1,36 @@ +U-Boot standard boot device (bootstd) +===================================== + +This is the controlling device for U-Boot standard boot, providing a way to +boot operating systems in a way that can be controlled by distros. + +Required properties: + +compatible: "u-boot,boot-std" + +Optional properties: + +filename-prefixes: + List of strings, each a directory to search for bootflow files + +bootdev-order: + List of bootdevs to check for bootflows, each a bootdev label (the media + uclass followed by the numeric sequence number of the media device) + + +Example: + + bootstd { + compatible = "u-boot,boot-std"; + + filename-prefixes = "/", "/boot/"; + bootdev-order = "mmc2", "mmc1"; + + syslinux { + compatible = "u-boot,distro-syslinux"; + }; + + efi { + compatible = "u-boot,distro-efi"; + }; + }; diff --git a/doc/usage/cmd/bootdev.rst b/doc/usage/cmd/bootdev.rst new file mode 100644 index 0000000..5e02e32 --- /dev/null +++ b/doc/usage/cmd/bootdev.rst @@ -0,0 +1,135 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +bootdev command +=============== + +Synopis +------- + +:: + + bootdev list [-p] - list all available bootdevs (-p to probe)\n" + bootdev select <bm> - select a bootdev by name\n" + bootdev info [-p] - show information about a bootdev"; + +Description +----------- + +The `bootdev` command is used to manage bootdevs. It can list available +bootdevs, select one and obtain information about it. + +See :doc:`../../develop/bootstd` for more information about bootdevs in general. + + +bootdev list +~~~~~~~~~~~~ + +This lists available bootdevs + +Scanning with `-p` causes the bootdevs to be probed. This happens automatically +when they are used. + +The list looks something like this: + +=== ====== ====== ======== ========================= +Seq Probed Status Uclass Name +=== ====== ====== ======== ========================= + 0 [ + ] OK mmc mmc@7e202000.bootdev + 1 [ ] OK mmc sdhci@7e300000.bootdev + 2 [ ] OK ethernet smsc95xx_eth.bootdev +=== ====== ====== ======== ========================= + + +The fields are as follows: + +Seq: + Sequence number in the scan, used to reference the bootflow later + +Probed: + Shows a plus (+) if the device is probed, empty if not. + +Status: + Shows the status of the device. Typically this is `OK` meaning that there is + no error. If you use -p and an error occurs when probing, then this shows + the error number. You can look up Linux error codes to find the meaning of + the number. + +Uclass: + Name of the media device's Uclass. This indicates the type of the parent + device (e.g. MMC, Ethernet). + +Name: + Name of the bootdev. This is generated from the media device appended + with `.bootdev` + + +bootdev select +~~~~~~~~~~~~~~~~~ + +Use this to select a particular bootdev. You can select it by the sequence +number or name, as shown in `bootdev list`. + +Once a bootdev is selected, you can use `bootdev info` to look at it or +`bootflow scan` to scan it. + +If no bootdev name or number is provided, then any existing bootdev is +unselected. + + +bootdev info +~~~~~~~~~~~~~~~ + +This shows information on the current bootdev, with the format looking like +this: + +========= ======================= +Name mmc@7e202000.bootdev +Sequence 0 +Status Probed +Uclass mmc +Bootflows 1 (1 valid) +========= ======================= + +Most of the information is the same as `bootdev list` above. The new fields +are: + +Device + Name of the bootdev + +Status + Shows `Probed` if the device is probed, `OK` if not. If `-p` is used and the + device fails to probe, an error code is shown. + +Bootflows + Indicates the number of bootflows attached to the bootdev. This is 0 + unless you have used 'bootflow scan' on the bootflow, or on all bootflows. + + +Example +------- + +This example shows listing available bootdev and getting information about +one of them:: + + U-Boot> bootdev list + Seq Probed Status Uclass Name + --- ------ ------ -------- ------------------ + 0 [ + ] OK mmc mmc@7e202000.bootdev + 1 [ ] OK mmc sdhci@7e300000.bootdev + 2 [ ] OK ethernet smsc95xx_eth.bootdev + --- ------ ------ -------- ------------------ + (3 devices) + U-Boot> bootdev sel 0 + U-Boot> bootflow scan + U-Boot> bootdev info + Name: mmc@7e202000.bootdev + Sequence: 0 + Status: Probed + Uclass: mmc + Bootflows: 1 (1 valid) + + +Return value +------------ + +The return value $? is always 0 (true). diff --git a/doc/usage/cmd/bootflow.rst b/doc/usage/cmd/bootflow.rst new file mode 100644 index 0000000..aa12dc2 --- /dev/null +++ b/doc/usage/cmd/bootflow.rst @@ -0,0 +1,427 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +bootflow command +================ + +Synopis +------- + +:: + + bootflow scan [-abel] [bootdev] + bootflow list [-e] + bootflow select [<num|name>] + bootflow info [-d] + bootflow boot + + +Description +----------- + +The `bootflow` command is used to manage bootflows. It can scan bootdevs to +locate bootflows, list them and boot them. + +See :doc:`../../develop/bootstd` for more information. + + +bootflow scan +~~~~~~~~~~~~~ + +Scans for available bootflows, optionally booting the first valid one it finds. +This operates in two modes: + +- If no bootdev is selected (see `bootdev select`) it scans bootflows one + by one, extracting all the bootdevs from each +- If a bootdev is selected, it just scans that one bootflow + +Flags are: + +-a + Collect all bootflows, even those that cannot be loaded. Normally if a file + is not where it is expected, then the bootflow fails and so is dropped + during the scan. With this option you can see why each bootflow would be + dropped. + +-b + Boot each valid bootflow as it is scanned. Typically only the first bootflow + matters, since by then the system boots in the OS and U-Boot is no-longer + running. `bootflow scan -b` is a quick way to boot the first available OS. + A valid bootflow is one that made it all the way to the `loaded` state. + +-e + Used with -l to also show errors for each bootflow. The shows detailed error + information for each bootflow that failed to make it to the `loaded` state. + +-l + List bootflows while scanning. This is helpful when you want to see what + is happening during scanning. Use it with the `-b` flag to see which + bootdev and bootflows are being tried. + +The optional argument specifies a particular bootdev to scan. This can either be +the name of a bootdev or its sequence number (both shown with `bootdev list`). +Alternatively a convenience label can be used, like `mmc0`, which is the type of +device and an optional sequence number. Specifically, the label is the uclass of +the bootdev's parent followed by the sequence number of that parent. Sequence +numbers are typically set by aliases, so if you have 'mmc0' in your devicetree +alias section, then `mmc0` refers to the bootdev attached to that device. + + +bootflow list +~~~~~~~~~~~~~ + +Lists the previously scanned bootflows. You must use `bootflow scan` before this +to see anything. + +If you scanned with -a and have bootflows with errors, -e can be used to show +those errors. + +The list looks something like this: + +=== ====== ====== ======== ==== =============================== ================ +Seq Method State Uclass Part Name Filename +=== ====== ====== ======== ==== =============================== ================ + 0 distro ready mmc 2 mmc\@7e202000.bootdev.part_2 /boot/extlinux/extlinux.conf + 1 pxe ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf +=== ====== ====== ======== ==== =============================== ================ + +The fields are as follows: + +Seq: + Sequence number in the scan, used to reference the bootflow later + +Method: + The boot method (bootmeth) used to find the bootflow. Several methods are + included in U-Boot. + +State: + Current state of the bootflow, indicating how far the bootdev got in + obtaining a valid one. See :ref:`BootflowStates` for a list of states. + +Uclass: + Name of the media device's Uclass. This indicates the type of the parent + device (e.g. MMC, Ethernet). + +Part: + Partition number being accesseed, numbered from 1. Normally a device will + have a partition table with a small number of partitions. For devices + without partition tables (e.g. network) this field is 0. + +Name: + Name of the bootflow. This is generated from the bootdev appended with + the partition information + +Filename: + Name of the bootflow file. This indicates where the file is on the + filesystem or network device. + + +bootflow select +~~~~~~~~~~~~~~~ + +Use this to select a particular bootflow. You can select it by the sequence +number or name, as shown in `bootflow list`. + +Once a bootflow is selected, you can use `bootflow info` and `bootflow boot`. + +If no bootflow name or number is provided, then any existing bootflow is +unselected. + + +bootflow info +~~~~~~~~~~~~~ + +This shows information on the current bootflow, with the format looking like +this: + +========= =============================== +Name mmc\@7e202000.bootdev.part_2 +Device mmc\@7e202000.bootdev +Block dev mmc\@7e202000.blk +Type distro +Method: syslinux +State ready +Partition 2 +Subdir (none) +Filename /extlinux/extlinux.conf +Buffer 3db7ad48 +Size 232 (562 bytes) +Error 0 +========= =============================== + +Most of the information is the same as `bootflow list` above. The new fields +are: + +Device + Name of the bootdev + +Block dev + Name of the block device, if any. Network devices don't have a block device. + +Subdir + Subdirectory used for retrieving files. For network bootdevs this is the + directory of the 'bootfile' parameter passed from DHCP. All file retrievals + when booting are relative to this. + +Buffer + Buffer containing the bootflow file. You can use the :doc:`md` to look at + it, or dump it with `bootflow info -d`. + +Size + Size of the bootflow file + +Error + Error number returned from scanning for the bootflow. This is 0 if the + bootflow is in the 'loaded' state, or a negative error value on error. You + can look up Linux error codes to find the meaning of the number. + +Use the `-d` flag to dump out the contents of the bootfile file. + + +bootflow boot +~~~~~~~~~~~~~ + +This boots the current bootflow. + + +Example +------- + +Here is an example of scanning for bootflows, then listing them:: + + U-Boot> bootflow scan -l + Scanning for bootflows in all bootdevs + Seq Type State Uclass Part Name Filename + --- ----------- ------ -------- ---- ------------------------ ---------------- + Scanning bootdev 'mmc@7e202000.bootdev': + 0 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf + Scanning bootdev 'sdhci@7e300000.bootdev': + Card did not respond to voltage select! : -110 + Scanning bootdev 'smsc95xx_eth.bootdev': + Waiting for Ethernet connection... done. + BOOTP broadcast 1 + DHCP client bound to address 192.168.4.30 (4 ms) + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe/'. + Load address: 0x200000 + Loading: * + TFTP error: 'Is a directory' (0) + Starting again + + missing environment variable: pxeuuid + Retrieving file: rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1 + Waiting for Ethernet connection... done. + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1'. + Load address: 0x2500000 + Loading: ################################################## 566 Bytes + 45.9 KiB/s + done + Bytes transferred = 566 (236 hex) + 1 distro ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf + No more bootdevs + --- ----------- ------ -------- ---- ------------------------ ---------------- + (2 bootflows, 2 valid) + U-Boot> bootflow l + Showing all bootflows + Seq Type State Uclass Part Name Filename + --- ----------- ------ -------- ---- ------------------------ ---------------- + 0 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf + 1 pxe ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf + --- ----------- ------ -------- ---- ------------------------ ---------------- + (2 bootflows, 2 valid) + + +The second one is then selected by name (we could instead use `bootflow sel 0`), +displayed and booted:: + + U-Boot> bootflow info + No bootflow selected + U-Boot> bootflow sel mmc@7e202000.bootdev.part_2 + U-Boot> bootflow info + Name: mmc@7e202000.bootdev.part_2 + Device: mmc@7e202000.bootdev + Block dev: mmc@7e202000.blk + Sequence: 1 + Method: distro + State: ready + Partition: 2 + Subdir: (none) + Filename: extlinux/extlinux.conf + Buffer: 3db7ae88 + Size: 232 (562 bytes) + Error: 0 + U-Boot> bootflow boot + ** Booting bootflow 'smsc95xx_eth.bootdev.0' + Ignoring unknown command: ui + Ignoring malformed menu command: autoboot + Ignoring malformed menu command: hidden + Ignoring unknown command: totaltimeout + 1: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl) + Retrieving file: rpi.pxe/initramfs-5.3.7-301.fc31.armv7hl.img + get 2700000 rpi.pxe/initramfs-5.3.7-301.fc31.armv7hl.img + Waiting for Ethernet connection... done. + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe/initramfs-5.3.7-301.fc31.armv7hl.img'. + Load address: 0x2700000 + Loading: ###################################T ############### 57.7 MiB + 1.9 MiB/s + done + Bytes transferred = 60498594 (39b22a2 hex) + Retrieving file: rpi.pxe//vmlinuz-5.3.7-301.fc31.armv7hl + get 80000 rpi.pxe//vmlinuz-5.3.7-301.fc31.armv7hl + Waiting for Ethernet connection... done. + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe//vmlinuz-5.3.7-301.fc31.armv7hl'. + Load address: 0x80000 + Loading: ################################################## 7.2 MiB + 2.3 MiB/s + done + Bytes transferred = 7508480 (729200 hex) + append: ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB + Retrieving file: rpi.pxe//dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb + get 2600000 rpi.pxe//dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb + Waiting for Ethernet connection... done. + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe//dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb'. + Load address: 0x2600000 + Loading: ################################################## 13.8 KiB + 764.6 KiB/s + done + Bytes transferred = 14102 (3716 hex) + Kernel image @ 0x080000 [ 0x000000 - 0x729200 ] + ## Flattened Device Tree blob at 02600000 + Booting using the fdt blob at 0x2600000 + Using Device Tree in place at 02600000, end 02606715 + + Starting kernel ... + + [ OK ] Started Show Plymouth Boot Screen. + [ OK ] Started Forward Password R…s to Plymouth Directory Watch. + [ OK ] Reached target Local Encrypted Volumes. + [ OK ] Reached target Paths. + .... + + +Here we scan for bootflows and boot the first one found:: + + U-Boot> bootflow scan -bl + Scanning for bootflows in all bootdevs + Seq Method State Uclass Part Name Filename + --- ----------- ------ -------- ---- ---------------------- ---------------- + Scanning bootdev 'mmc@7e202000.bootdev': + 0 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf + ** Booting bootflow 'mmc@7e202000.bootdev.part_2' + Ignoring unknown command: ui + Ignoring malformed menu command: autoboot + Ignoring malformed menu command: hidden + Ignoring unknown command: totaltimeout + 1: Fedora-KDE-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl) + Retrieving file: /initramfs-5.3.7-301.fc31.armv7hl.img + getfile 2700000 /initramfs-5.3.7-301.fc31.armv7hl.img + Retrieving file: /vmlinuz-5.3.7-301.fc31.armv7hl + getfile 80000 /vmlinuz-5.3.7-301.fc31.armv7hl + append: ro root=UUID=b8781f09-e2dd-4cb8-979b-7df5eeaaabea rhgb LANG=en_US.UTF-8 cma=192MB console=tty0 console=ttyS1,115200 + Retrieving file: /dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb + getfile 2600000 /dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb + Kernel image @ 0x080000 [ 0x000000 - 0x729200 ] + ## Flattened Device Tree blob at 02600000 + Booting using the fdt blob at 0x2600000 + Using Device Tree in place at 02600000, end 02606715 + + Starting kernel ... + + [ 0.000000] Booting Linux on physical CPU 0x0 + + +Here is am example using the -e flag to see all errors:: + + U-Boot> bootflow scan -a + Card did not respond to voltage select! : -110 + Waiting for Ethernet connection... done. + BOOTP broadcast 1 + DHCP client bound to address 192.168.4.30 (4 ms) + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe/'. + Load address: 0x200000 + Loading: * + TFTP error: 'Is a directory' (0) + Starting again + + missing environment variable: pxeuuid + Retrieving file: rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1 + Waiting for Ethernet connection... done. + Using smsc95xx_eth device + TFTP from server 192.168.4.1; our IP address is 192.168.4.30 + Filename 'rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1'. + Load address: 0x2500000 + Loading: ################################################## 566 Bytes + 49.8 KiB/s + done + Bytes transferred = 566 (236 hex) + U-Boot> bootflow l -e + Showing all bootflows + Seq Type State Uclass Part Name Filename + --- ----------- ------ -------- ---- --------------------- ---------------- + 0 distro fs mmc 1 mmc@7e202000.bootdev.p /extlinux/extlinux.conf + ** File not found, err=-2 + 1 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf + 2 distro fs mmc 3 mmc@7e202000.bootdev.p /extlinux/extlinux.conf + ** File not found, err=-1 + 3 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 4 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 5 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 6 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 7 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 8 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 9 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + a distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + b distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + c distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + d distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + e distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + f distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 10 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 11 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 12 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 13 distro media mmc 0 mmc@7e202000.bootdev.p <NULL> + ** No partition found, err=-2 + 14 distro ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf + --- ----------- ------ -------- ---- --------------------- ---------------- + (21 bootflows, 2 valid) + U-Boot> + + +Return value +------------ + +On success `bootflow boot` normally boots into the Operating System and does not +return to U-Boot. If something about the U-Boot processing fails, then the +return value $? is 1. If the boot succeeds but for some reason the Operating +System returns, then $? is 0, indicating success. + +For other subcommands, the return value $? is always 0 (true). + + +.. BootflowStates_: diff --git a/doc/usage/cmd/bootmeth.rst b/doc/usage/cmd/bootmeth.rst new file mode 100644 index 0000000..9fc7ebf --- /dev/null +++ b/doc/usage/cmd/bootmeth.rst @@ -0,0 +1,108 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +bootmeth command +================ + +Synopis +------- + +:: + + bootmeth list [-a] - list selected bootmeths (-a for all) + bootmeth order "[<bm> ...]" - select the order of bootmeths\n" + + +Description +----------- + +The `bootmeth` command is used to manage bootmeths. It can list them and change +the order in which they are used. + +See :doc:`../../develop/bootstd` for more information. + + +.. _bootmeth_order: + +bootmeth order +~~~~~~~~~~~~~~ + +Selects which bootmeths to use and the order in which they are invoked. When +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. + + +bootmeth list +~~~~~~~~~~~~~ + +This lists the selected bootmeths, or all of them, if the `-a` flag is used. +The format looks like this: + +===== === ================== ================================= +Order Seq Name Description +===== === ================== ================================= + 0 0 distro Syslinux boot from a block device + 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 +===== === ================== ================================= + +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. + +Seq: + The sequence number of the bootmeth, i.e. the normal ordering if none is set + +Name: + Name of the bootmeth + +Description: + A friendly description for the bootmeth + + +Example +------- + +This shows listing bootmeths. All are present and in the normal order:: + + => bootmeth list + Order Seq Name Description + ----- --- ------------------ ------------------ + 0 0 distro Syslinux boot from a block device + 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 + ----- --- ------------------ ------------------ + (5 bootmeths) + +Now the order is changed, to include only two of them:: + + => bootmeth order "sandbox distro" + => bootmeth list + Order Seq Name Description + ----- --- ------------------ ------------------ + 0 3 sandbox Sandbox boot for testing + 1 0 distro Syslinux boot from a block device + ----- --- ------------------ ------------------ + (2 bootmeths) + +The -a flag shows all bootmeths so you can clearly see which ones are used and +which are not:: + + => bootmeth list -a + Order Seq Name Description + ----- --- ------------------ ------------------ + 1 0 distro Syslinux boot from a block device + - 1 efi EFI boot from an .efi file + - 2 pxe PXE boot from a network device + 0 3 sandbox Sandbox boot for testing + - 4 efi_mgr EFI bootmgr flow + ----- --- ------------------ ------------------ + (5 bootmeths) diff --git a/doc/usage/index.rst b/doc/usage/index.rst index f343e4e..c03f4ae 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -23,9 +23,12 @@ Shell commands cmd/addrmap cmd/askenv cmd/base + cmd/bootdev cmd/bootefi + cmd/bootflow cmd/booti cmd/bootmenu + cmd/bootmeth cmd/button cmd/cbsysinfo cmd/conitrace diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c index 791e26c..21c5209 100644 --- a/drivers/block/blk-uclass.c +++ b/drivers/block/blk-uclass.c @@ -509,6 +509,13 @@ int blk_get_from_parent(struct udevice *parent, struct udevice **devp) return 0; } +const char *blk_get_devtype(struct udevice *dev) +{ + struct udevice *parent = dev_get_parent(dev); + + return uclass_get_name(device_get_uclass_id(parent)); +}; + int blk_find_max_devnum(enum if_type if_type) { struct udevice *dev; diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c index 2578803..08d9ed8 100644 --- a/drivers/core/uclass.c +++ b/drivers/core/uclass.c @@ -180,14 +180,15 @@ void uclass_set_priv(struct uclass *uc, void *priv) uc->priv_ = priv; } -enum uclass_id uclass_get_by_name_len(const char *name, int len) +enum uclass_id uclass_get_by_namelen(const char *name, int len) { int i; for (i = 0; i < UCLASS_COUNT; i++) { struct uclass_driver *uc_drv = lists_uclass_lookup(i); - if (uc_drv && !strncmp(uc_drv->name, name, len)) + if (uc_drv && !strncmp(uc_drv->name, name, len) && + strlen(uc_drv->name) == len) return i; } @@ -196,7 +197,7 @@ enum uclass_id uclass_get_by_name_len(const char *name, int len) enum uclass_id uclass_get_by_name(const char *name) { - return uclass_get_by_name_len(name, strlen(name)); + return uclass_get_by_namelen(name, strlen(name)); } int dev_get_uclass_index(struct udevice *dev, struct uclass **ucp) @@ -273,8 +274,8 @@ int uclass_find_next_device(struct udevice **devp) return 0; } -int uclass_find_device_by_name(enum uclass_id id, const char *name, - struct udevice **devp) +int uclass_find_device_by_namelen(enum uclass_id id, const char *name, int len, + struct udevice **devp) { struct uclass *uc; struct udevice *dev; @@ -288,7 +289,8 @@ int uclass_find_device_by_name(enum uclass_id id, const char *name, return ret; uclass_foreach_dev(dev, uc) { - if (!strcmp(dev->name, name)) { + if (!strncmp(dev->name, name, len) && + strlen(dev->name) == len) { *devp = dev; return 0; } @@ -297,6 +299,12 @@ int uclass_find_device_by_name(enum uclass_id id, const char *name, return -ENODEV; } +int uclass_find_device_by_name(enum uclass_id id, const char *name, + struct udevice **devp) +{ + return uclass_find_device_by_namelen(id, name, strlen(name), devp); +} + int uclass_find_next_free_seq(struct uclass *uc) { struct udevice *dev; diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 17ebc04..9627509 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -5,6 +5,11 @@ obj-y += mmc.o obj-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o + +ifdef CONFIG_$(SPL_TPL_)DM_MMC +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += mmc_bootdev.o +endif + obj-$(CONFIG_$(SPL_)MMC_WRITE) += mmc_write.o obj-$(CONFIG_MMC_PWRSEQ) += mmc-pwrseq.o obj-$(CONFIG_MMC_SDHCI_ADMA_HELPERS) += sdhci-adma.o diff --git a/drivers/mmc/mmc-uclass.c b/drivers/mmc/mmc-uclass.c index 57da788..688bdc0 100644 --- a/drivers/mmc/mmc-uclass.c +++ b/drivers/mmc/mmc-uclass.c @@ -8,6 +8,7 @@ #define LOG_CATEGORY UCLASS_MMC #include <common.h> +#include <bootdev.h> #include <log.h> #include <mmc.h> #include <dm.h> @@ -315,6 +316,20 @@ int mmc_get_next_devnum(void) return blk_find_max_devnum(IF_TYPE_MMC); } +int mmc_get_blk(struct udevice *dev, struct udevice **blkp) +{ + struct udevice *blk; + int ret; + + device_find_first_child_by_uclass(dev, UCLASS_BLK, &blk); + ret = device_probe(blk); + if (ret) + return ret; + *blkp = blk; + + return 0; +} + struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) { struct blk_desc *desc; @@ -406,6 +421,10 @@ int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg) mmc->cfg = cfg; mmc->priv = dev; + ret = bootdev_setup_for_dev(dev, "mmc_bootdev"); + if (ret) + return log_msg_ret("bootdev", ret); + /* the following chunk was from mmc_register() */ /* Setup dsr related values */ @@ -424,12 +443,16 @@ int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg) int mmc_unbind(struct udevice *dev) { struct udevice *bdev; + int ret; device_find_first_child_by_uclass(dev, UCLASS_BLK, &bdev); if (bdev) { device_remove(bdev, DM_REMOVE_NORMAL); device_unbind(bdev); } + ret = bootdev_unbind_dev(dev); + if (ret) + return log_msg_ret("bootdev", ret); return 0; } diff --git a/drivers/mmc/mmc_bootdev.c b/drivers/mmc/mmc_bootdev.c new file mode 100644 index 0000000..b4f41fb --- /dev/null +++ b/drivers/mmc/mmc_bootdev.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdevice for MMC + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <dm.h> +#include <mmc.h> + +static int mmc_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + struct udevice *mmc_dev = dev_get_parent(dev); + struct udevice *blk; + int ret; + + ret = mmc_get_blk(mmc_dev, &blk); + /* + * If there is no media, indicate that no more partitions should be + * checked + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + if (ret) + return log_msg_ret("blk", ret); + assert(blk); + ret = bootdev_find_in_blk(dev, blk, iter, bflow); + if (ret) + return log_msg_ret("find", ret); + + return 0; +} + +static int mmc_bootdev_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_0_INTERNAL_FAST; + + return 0; +} + +struct bootdev_ops mmc_bootdev_ops = { + .get_bootflow = mmc_get_bootflow, +}; + +static const struct udevice_id mmc_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-mmc" }, + { } +}; + +U_BOOT_DRIVER(mmc_bootdev) = { + .name = "mmc_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &mmc_bootdev_ops, + .bind = mmc_bootdev_bind, + .of_match = mmc_bootdev_ids, +}; diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index a4472da..7785b37 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -8,6 +8,10 @@ obj-y += usb-uclass.o obj-$(CONFIG_SANDBOX) += usb-sandbox.o endif +ifdef CONFIG_$(SPL_TPL_)USB_STORAGE +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += usb_bootdev.o +endif + # ohci obj-$(CONFIG_USB_OHCI_NEW) += ohci-hcd.o obj-$(CONFIG_USB_ATMEL) += ohci-at91.o diff --git a/drivers/usb/host/usb_bootdev.c b/drivers/usb/host/usb_bootdev.c new file mode 100644 index 0000000..b85f699 --- /dev/null +++ b/drivers/usb/host/usb_bootdev.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdevice for USB + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <dm.h> +#include <usb.h> + +static int usb_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + struct udevice *blk; + int ret; + + ret = bootdev_get_sibling_blk(dev, &blk); + /* + * If there is no media, indicate that no more partitions should be + * checked + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + if (ret) + return log_msg_ret("blk", ret); + assert(blk); + ret = bootdev_find_in_blk(dev, blk, iter, bflow); + if (ret) + return log_msg_ret("find", ret); + + return 0; +} + +static int usb_bootdev_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_3_SCAN_SLOW; + + return 0; +} + +struct bootdev_ops usb_bootdev_ops = { + .get_bootflow = usb_get_bootflow, +}; + +static const struct udevice_id usb_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-usb" }, + { } +}; + +U_BOOT_DRIVER(usb_bootdev) = { + .name = "usb_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &usb_bootdev_ops, + .bind = usb_bootdev_bind, + .of_match = usb_bootdev_ids, +}; @@ -16,6 +16,8 @@ source "fs/fat/Kconfig" source "fs/jffs2/Kconfig" +source "fs/sandbox/Kconfig" + source "fs/ubifs/Kconfig" source "fs/cramfs/Kconfig" @@ -36,6 +36,11 @@ static int fs_dev_part; static struct disk_partition fs_partition; static int fs_type = FS_TYPE_ANY; +void fs_set_type(int type) +{ + fs_type = type; +} + static inline int fs_probe_unsupported(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { diff --git a/fs/sandbox/Kconfig b/fs/sandbox/Kconfig new file mode 100644 index 0000000..b2af848 --- /dev/null +++ b/fs/sandbox/Kconfig @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0+ +# diff --git a/fs/sandbox/Makefile b/fs/sandbox/Makefile index 0609051..880d59d 100644 --- a/fs/sandbox/Makefile +++ b/fs/sandbox/Makefile @@ -9,3 +9,4 @@ # Pavel Bartusek, Sysgo Real-Time Solutions AG, pba@sysgo.de obj-y := sandboxfs.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += host_bootdev.o diff --git a/fs/sandbox/host_bootdev.c b/fs/sandbox/host_bootdev.c new file mode 100644 index 0000000..0d12ee4 --- /dev/null +++ b/fs/sandbox/host_bootdev.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdevice for MMC + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <dm.h> +#include <fs.h> + +static int host_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + int ret; + + if (iter->part) + return log_msg_ret("max", -ESHUTDOWN); + + bflow->name = strdup(dev->name); + if (!bflow->name) + return log_msg_ret("name", -ENOMEM); + + ret = bootmeth_check(bflow->method, iter); + if (ret) + return log_msg_ret("check", ret); + + bflow->state = BOOTFLOWST_MEDIA; + bflow->fs_type = FS_TYPE_SANDBOX; + + ret = bootmeth_read_bootflow(bflow->method, bflow); + if (ret) + return log_msg_ret("method", ret); + + return 0; +} + +struct bootdev_ops host_bootdev_ops = { + .get_bootflow = host_get_bootflow, +}; + +static const struct udevice_id host_bootdev_ids[] = { + { .compatible = "sandbox,bootdev-host" }, + { } +}; + +U_BOOT_DRIVER(host_bootdev) = { + .name = "host_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &host_bootdev_ops, + .of_match = host_bootdev_ids, +}; diff --git a/include/blk.h b/include/blk.h index dbe9ae2..9503369 100644 --- a/include/blk.h +++ b/include/blk.h @@ -434,6 +434,14 @@ int blk_select_hwpart(struct udevice *dev, int hwpart); int blk_get_from_parent(struct udevice *parent, struct udevice **devp); /** + * blk_get_devtype() - Get the device type of a block device + * + * @dev: Block device to check + * Return: device tree, i.e. the uclass name of its parent, e.g. "mmc" + */ +const char *blk_get_devtype(struct udevice *dev); + +/** * blk_get_by_device() - Get the block device descriptor for the given device * @dev: Instance of a storage device * diff --git a/include/bootdev.h b/include/bootdev.h new file mode 100644 index 0000000..9fc2198 --- /dev/null +++ b/include/bootdev.h @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __bootdev_h +#define __bootdev_h + +#include <linux/list.h> + +struct bootflow; +struct bootflow_iter; +struct udevice; + +/** + * enum bootdev_prio_t - priority of each bootdev + * + * These values are associated with each bootdev and set up by the driver. + * + * Smallest value is the highest priority. By default, bootdevs are scanned from + * highest to lowest priority + */ +enum bootdev_prio_t { + BOOTDEVP_0_INTERNAL_FAST = 10, + BOOTDEVP_1_INTERNAL_SLOW = 20, + BOOTDEVP_2_SCAN_FAST = 30, + BOOTDEVP_3_SCAN_SLOW = 40, + BOOTDEVP_4_NET_BASE = 50, + BOOTDEVP_5_NET_FALLBACK = 60, + BOOTDEVP_6_SYSTEM = 70, + + BOOTDEVP_COUNT, +}; + +/** + * struct bootdev_uc_plat - uclass information about a bootdev + * + * This is attached to each device in the bootdev uclass and accessible via + * dev_get_uclass_plat(dev) + * + * @bootflows: List of available bootflows for this bootdev + * @piro: Priority of this bootdev + */ +struct bootdev_uc_plat { + struct list_head bootflow_head; + enum bootdev_prio_t prio; +}; + +/** struct bootdev_ops - Operations for the bootdev uclass */ +struct bootdev_ops { + /** + * get_bootflow() - get a bootflow + * + * @dev: Bootflow device to check + * @iter: Provides current dev, part, method to get. Should update + * max_part if there is a partition table. Should update state, + * subdir, fname, buf, size according to progress + * @bflow: Updated bootflow if found + * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this + * device, -ENOSYS if this device doesn't support bootflows, + * other -ve value on other error + */ + int (*get_bootflow)(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow); +}; + +#define bootdev_get_ops(dev) ((struct bootdev_ops *)(dev)->driver->ops) + +/** + * bootdev_get_bootflow() - get a bootflow + * + * @dev: Bootflow device to check + * @iter: Provides current part, method to get + * @bflow: Returns bootflow if found + * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this device, + * -ENOSYS if this device doesn't support bootflows, other -ve value on + * other error + */ +int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow); + +/** + * bootdev_bind() - Bind a new named bootdev device + * + * @parent: Parent of the new device + * @drv_name: Driver name to use for the bootdev device + * @name: Name for the device (parent name is prepended) + * @devp: the new device (which has not been probed) + */ +int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, + struct udevice **devp); + +/** + * bootdev_find_in_blk() - Find a bootdev in a block device + * + * @dev: Bootflow device associated with this block device + * @blk: Block device to search + * @iter: Provides current dev, part, method to get. Should update + * max_part if there is a partition table + * @bflow: On entry, provides information about the partition and device to + * check. On exit, returns bootflow if found + * Return: 0 if found, -ESHUTDOWN if no more bootflows, other -ve on error + */ +int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, + struct bootflow_iter *iter, struct bootflow *bflow); + +/** + * bootdev_list() - List all available bootdevs + * + * @probe: true to probe devices, false to leave them as is + */ +void bootdev_list(bool probe); + +/** + * bootdev_clear_bootflows() - Clear bootflows from a bootdev + * + * Each bootdev maintains a list of discovered bootflows. This provides a + * way to clear it. These bootflows are removed from the global list too. + * + * @dev: bootdev device to update + */ +void bootdev_clear_bootflows(struct udevice *dev); + +/** + * bootdev_add_bootflow() - Add a bootflow to the bootdev's list + * + * All fields in @bflow must be set up. Note that @bflow->dev is used to add the + * bootflow to that device. + * + * @dev: Bootdevice device to add to + * @bflow: Bootflow to add. Note that fields within bflow must be allocated + * since this function takes over ownership of these. This functions makes + * a copy of @bflow itself (without allocating its fields again), so the + * caller must dispose of the memory used by the @bflow pointer itself + * Return: 0 if OK, -ENOMEM if out of memory + */ +int bootdev_add_bootflow(struct bootflow *bflow); + +/** + * bootdev_first_bootflow() - Get the first bootflow from a bootdev + * + * Returns the first bootflow attached to a bootdev + * + * @dev: bootdev device + * @bflowp: Returns a pointer to the bootflow + * Return: 0 if found, -ENOENT if there are no bootflows + */ +int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp); + +/** + * bootdev_next_bootflow() - Get the next bootflow from a bootdev + * + * Returns the next bootflow attached to a bootdev + * + * @bflowp: On entry, the last bootflow returned , e.g. from + * bootdev_first_bootflow() + * Return: 0 if found, -ENOENT if there are no more bootflows + */ +int bootdev_next_bootflow(struct bootflow **bflowp); + +/** + * bootdev_find_by_label() - Look up a bootdev by label + * + * Each bootdev has a label which contains the media-uclass name and a number, + * e.g. 'mmc2'. This looks up the label and returns the associated bootdev + * + * The lookup is performed based on the media device's sequence number. So for + * 'mmc2' this looks for a device in UCLASS_MMC with a dev_seq() of 2. + * + * @label: Label to look up (e.g. "mmc1" or "mmc0") + * @devp: Returns the bootdev device found, or NULL if none (note it does not + * return the media device, but its bootdev child) + * Return: 0 if OK, -EINVAL if the uclass is not supported by this board, + * -ENOENT if there is no device with that number + */ +int bootdev_find_by_label(const char *label, struct udevice **devp); + +/** + * bootdev_find_by_any() - Find a bootdev by name, label or sequence + * + * @name: name (e.g. "mmc2.bootdev"), label ("mmc2"), or sequence ("2") to find + * @devp: returns the device found, on success + * Return: 0 if OK, -ve on error + */ +int bootdev_find_by_any(const char *name, struct udevice **devp); + +/** + * bootdev_setup_iter_order() - Set up the ordering of bootdevs to scan + * + * This sets up the ordering information in @iter, based on the priority of each + * bootdev and the bootdev-order property in the bootstd node + * + * If a single device is requested, no ordering is needed + * + * @iter: Iterator to update with the order + * @devp: On entry, *devp is NULL to scan all, otherwise this is the (single) + * device to scan. Returns the first device to use, which is the passed-in + * @devp if it was non-NULL + * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve + * on other error + */ +int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp); + +#if CONFIG_IS_ENABLED(BOOTSTD) +/** + * bootdev_setup_for_dev() - Bind a new bootdev device + * + * Creates a bootdev device as a child of @parent. This should be called from + * the driver's bind() method or its uclass' post_bind() method. + * + * If a child bootdev already exists, this function does nothing + * + * @parent: Parent device (e.g. MMC or Ethernet) + * @drv_name: Name of bootdev driver to bind + * Return: 0 if OK, -ve on error + */ +int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name); + +/** + * bootdev_setup_for_blk() - Bind a new bootdev device for a blk device + * + * Creates a bootdev device as a sibling of @blk. This should be called from + * the driver's bind() method or its uclass' post_bind() method, at the same + * time as the bould device is bound + * + * If a device of the same name already exists, this function does nothing + * + * @parent: Parent device (e.g. MMC or Ethernet) + * @drv_name: Name of bootdev driver to bind + * Return: 0 if OK, -ve on error + */ +int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name); + +/** + * bootdev_get_sibling_blk() - Locate the block device for a bootdev + * + * @dev: bootdev to check + * @blkp: returns associated block device + * Return: 0 if OK, -EINVAL if @dev is not a bootdev device, other -ve on other + * error + */ +int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp); + +/** + * bootdev_unbind_dev() - Unbind a bootdev device + * + * Remove and unbind a bootdev device which is a child of @parent. This should + * be called from the driver's unbind() method or its uclass' post_bind() + * method. + * + * @parent: Parent device (e.g. MMC or Ethernet) + * Return: 0 if OK, -ve on error + */ +int bootdev_unbind_dev(struct udevice *parent); +#else +static inline int bootdev_setup_for_dev(struct udevice *parent, + const char *drv_name) +{ + return 0; +} + +static inline int bootdev_setup_sibling_blk(struct udevice *blk, + const char *drv_name) +{ + return 0; +} + +static inline int bootdev_unbind_dev(struct udevice *parent) +{ + return 0; +} +#endif + +#endif diff --git a/include/bootflow.h b/include/bootflow.h new file mode 100644 index 0000000..c30ba04 --- /dev/null +++ b/include/bootflow.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __bootflow_h +#define __bootflow_h + +#include <linux/list.h> + +/** + * enum bootflow_state_t - states that a particular bootflow can be in + * + * Only bootflows in state BOOTFLOWST_READY can be used to boot. + * + * See bootflow_state[] for the names for each of these + */ +enum bootflow_state_t { + BOOTFLOWST_BASE, /**< Nothing known yet */ + BOOTFLOWST_MEDIA, /**< Media exists */ + BOOTFLOWST_PART, /**< Partition exists */ + BOOTFLOWST_FS, /**< Filesystem exists */ + BOOTFLOWST_FILE, /**< Bootflow file exists */ + BOOTFLOWST_READY, /**< Bootflow file loaded */ + + BOOTFLOWST_COUNT +}; + +/** + * struct bootflow - information about a bootflow + * + * This is connected into two separate linked lists: + * + * bm_sibling - links all bootflows in the same bootdev + * glob_sibling - links all bootflows in all bootdevs + * + * @bm_node: Points to siblings in the same bootdev + * @glob_node: Points to siblings in the global list (all bootdev) + * @dev: Bootdevice device which produced this bootflow + * @blk: Block device which contains this bootflow, NULL if this is a network + * device + * @part: Partition number (0 for whole device) + * @fs_type: Filesystem type (FS_TYPE...) if this is fixed by the media, else 0. + * For example, the sandbox host-filesystem bootdev sets this to + * FS_TYPE_SANDBOX + * @method: Bootmethod device used to perform the boot and read files + * @name: Name of bootflow (allocated) + * @state: Current state (enum bootflow_state_t) + * @subdir: Subdirectory to fetch files from (with trailing /), or NULL if none + * @fname: Filename of bootflow file (allocated) + * @buf: Bootflow file contents (allocated) + * @size: Size of bootflow file in bytes + * @err: Error number received (0 if OK) + */ +struct bootflow { + struct list_head bm_node; + struct list_head glob_node; + struct udevice *dev; + struct udevice *blk; + int part; + int fs_type; + struct udevice *method; + char *name; + enum bootflow_state_t state; + char *subdir; + char *fname; + char *buf; + int size; + int err; +}; + +/** + * enum bootflow_flags_t - flags for the bootflow iterator + * + * @BOOTFLOWF_FIXED: Only used fixed/internal media + * @BOOTFLOWF_SHOW: Show each bootdev before scanning it + * @BOOTFLOWF_ALL: Return bootflows with errors as well + * @BOOTFLOWF_SINGLE_DEV: Just scan one bootmeth + */ +enum bootflow_flags_t { + BOOTFLOWF_FIXED = 1 << 0, + BOOTFLOWF_SHOW = 1 << 1, + BOOTFLOWF_ALL = 1 << 2, + BOOTFLOWF_SINGLE_DEV = 1 << 3, +}; + +/** + * struct bootflow_iter - state for iterating through bootflows + * + * This starts at with the first bootdev/partition/bootmeth and can be used to + * iterate through all of them. + * + * Iteration starts with the bootdev. The first partition (0, i.e. whole device) + * is scanned first. For partition 0, it iterates through all the available + * bootmeths to see which one(s) can provide a bootflow. Then it moves to + * parition 1 (if there is one) and the process continues. Once all partitions + * are examined, it moves to the next bootdev. + * + * Initially @max_part is 0, meaning that only the whole device (@part=0) can be + * used. During scanning, if a partition table is found, then @max_part is + * 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 + * @part: Current partition number (0 for whole device) + * @method: Current bootmeth + * @max_part: Maximum hardware partition number in @dev, 0 if there is no + * partition table + * @err: Error obtained from checking the last iteration. This is used to skip + * forward (e.g. to skip the current partition because it is not valid) + * -ESHUTDOWN: try next bootdev + * @num_devs: Number of bootdevs in @dev_order + * @cur_dev: Current bootdev number, an index into @dev_order[] + * @dev_order: List of bootdevs to scan, in order of priority. The scan starts + * 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 + */ +struct bootflow_iter { + int flags; + struct udevice *dev; + int part; + struct udevice *method; + int max_part; + int err; + int num_devs; + int cur_dev; + struct udevice **dev_order; + int num_methods; + int cur_method; + struct udevice **method_order; +}; + +/** + * bootflow_iter_init() - Reset a bootflow iterator + * + * This sets everything to the starting point, ready for use. + * + * @iter: Place to store private info (inited by this call) + * @flags: Flags to use (see enum bootflow_flags_t) + */ +void bootflow_iter_init(struct bootflow_iter *iter, int flags); + +/** + * bootflow_iter_uninit() - Free memory used by an interator + * + * @iter: Iterator to free + */ +void bootflow_iter_uninit(struct bootflow_iter *iter); + +/** + * bootflow_iter_drop_bootmeth() - Remove a bootmeth from an iterator + * + * Update the iterator so that the bootmeth will not be used again while this + * iterator is in use + * + * @iter: Iterator to update + * @bmeth: Boot method to remove + */ +int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, + const struct udevice *bmeth); + +/** + * bootflow_scan_bootdev() - find the first bootflow in a bootdev + * + * 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 + * @iter: Place to store private info (inited by this call) + * @flags: Flags for bootdev (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) + */ +int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter, + int flags, struct bootflow *bflow); + +/** + * bootflow_scan_first() - find the first bootflow + * + * This works through the available bootdev devices until it finds one that + * can supply a bootflow. It then returns that + * + * If @flags includes BOOTFLOWF_ALL then bootflows with errors are returned too + * + * @iter: Place to store private info (inited by this call), with + * @flags: Flags for bootdev (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) + */ +int bootflow_scan_first(struct bootflow_iter *iter, int flags, + struct bootflow *bflow); + +/** + * bootflow_scan_next() - find the next bootflow + * + * This works through the available bootdev devices until it finds one that + * can supply a bootflow. It then returns that bootflow + * + * @iter: Private info (as set up by bootflow_scan_first()) + * @bflow: Place to put the bootflow if found + * Return: 0 if found, -ENODEV if no device, -ESHUTDOWN if no more bootflows, + * other -ve on other error (iteration can continue) + */ +int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow); + +/** + * bootflow_first_glob() - Get the first bootflow from the global list + * + * Returns the first bootflow in the global list, no matter what bootflow it is + * attached to + * + * @bflowp: Returns a pointer to the bootflow + * Return: 0 if found, -ENOENT if there are no bootflows + */ +int bootflow_first_glob(struct bootflow **bflowp); + +/** + * bootflow_next_glob() - Get the next bootflow from the global list + * + * Returns the next bootflow in the global list, no matter what bootflow it is + * attached to + * + * @bflowp: On entry, the last bootflow returned , e.g. from + * bootflow_first_glob() + * Return: 0 if found, -ENOENT if there are no more bootflows + */ +int bootflow_next_glob(struct bootflow **bflowp); + +/** + * bootflow_free() - Free memory used by a bootflow + * + * This frees fields within @bflow, but not the @bflow pointer itself + */ +void bootflow_free(struct bootflow *bflow); + +/** + * bootflow_boot() - boot a bootflow + * + * @bflow: Bootflow to boot + * Return: -EPROTO if bootflow has not been loaded, -ENOSYS if the bootflow + * type is not supported, -EFAULT if the boot returned without an error + * when we are expecting it to boot, -ENOTSUPP if trying method resulted in + * finding out that is not actually supported for this boot and should not + * be tried again unless something changes + */ +int bootflow_boot(struct bootflow *bflow); + +/** + * bootflow_run_boot() - Try to boot a bootflow + * + * @iter: Current iteration (or NULL if none). Used to disable a bootmeth if the + * boot returns -ENOTSUPP + * @bflow: Bootflow to boot + * Return: result of trying to boot + */ +int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow); + +/** + * bootflow_state_get_name() - Get the name of a bootflow state + * + * @state: State to check + * Return: name, or "?" if invalid + */ +const char *bootflow_state_get_name(enum bootflow_state_t state); + +/** + * bootflow_remove() - Remove a bootflow and free its memory + * + * This updates the linked lists containing the bootflow then frees it. + * + * @bflow: Bootflow to remove + */ +void bootflow_remove(struct bootflow *bflow); + +/** + * bootflow_iter_uses_blk_dev() - Check that a bootflow uses a block device + * + * This checks the bootdev in the bootflow to make sure it uses a block device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. ethernet) + */ +int bootflow_iter_uses_blk_dev(const struct bootflow_iter *iter); + +/** + * bootflow_iter_uses_network() - Check that a bootflow uses a network device + * + * This checks the bootdev in the bootflow to make sure it uses a network + * device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. MMC) + */ +int bootflow_iter_uses_network(const struct bootflow_iter *iter); + +/** + * bootflow_iter_uses_system() - Check that a bootflow uses the bootstd device + * + * This checks the bootdev in the bootflow to make sure it uses the bootstd + * device + * + * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. MMC) + */ +int bootflow_iter_uses_system(const struct bootflow_iter *iter); + +#endif diff --git a/include/bootmeth.h b/include/bootmeth.h new file mode 100644 index 0000000..484e503 --- /dev/null +++ b/include/bootmeth.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __bootmeth_h +#define __bootmeth_h + +struct blk_desc; +struct bootflow; +struct bootflow_iter; +struct udevice; + +/** + * struct bootmeth_uc_plat - information the uclass keeps about each bootmeth + * + * @desc: A long description of the bootmeth + */ +struct bootmeth_uc_plat { + const char *desc; +}; + +/** struct bootmeth_ops - Operations for boot methods */ +struct bootmeth_ops { + /** + * check_supported() - check if a bootmeth supports this bootflow + * + * This is optional. If not provided, the bootdev is assumed to be + * supported + * + * The bootmeth can check the bootdev (e.g. to make sure it is a + * network device) or the partition information. The following fields + * in @iter are available: + * + * name, dev, state, part + * max_part may be set if part != 0 (i.e. there is a valid partition + * table). Otherwise max_part is 0 + * method is available but is the same as @dev + * the partition has not yet been read, nor has the filesystem been + * checked + * + * It may update only the flags in @iter + * + * @dev: Bootmethod device to check against + * @iter: On entry, provides bootdev, hwpart, part + * Return: 0 if OK, -ENOTSUPP if this bootdev is not supported + */ + int (*check)(struct udevice *dev, struct bootflow_iter *iter); + + /** + * read_bootflow() - read a bootflow for a device + * + * @dev: Bootmethod device to use + * @bflow: On entry, provides dev, hwpart, part and method. + * Returns updated bootflow if found + * Return: 0 if OK, -ve on error + */ + int (*read_bootflow)(struct udevice *dev, struct bootflow *bflow); + + /** + * read_file() - read a file needed for a bootflow + * + * Read a file from the same place as the bootflow came from + * + * @dev: Bootmethod device to use + * @bflow: Bootflow providing info on where to read from + * @file_path: Path to file (may be absolute or relative) + * @addr: Address to load file + * @sizep: On entry provides the maximum permitted size; on exit + * returns the size of the file + * Return: 0 if OK, -ENOSPC if the file is too large for @sizep, other + * -ve value if something else goes wrong + */ + int (*read_file)(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep); + + /** + * boot() - boot a bootflow + * + * @dev: Bootmethod device to boot + * @bflow: Bootflow to boot + * Return: does not return on success, since it should boot the + * Operating Systemn. Returns -EFAULT if that fails, -ENOTSUPP if + * trying method resulted in finding out that is not actually + * supported for this boot and should not be tried again unless + * something changes, other -ve on other error + */ + int (*boot)(struct udevice *dev, struct bootflow *bflow); +}; + +#define bootmeth_get_ops(dev) ((struct bootmeth_ops *)(dev)->driver->ops) + +/** + * bootmeth_check() - check if a bootmeth supports this bootflow + * + * This is optional. If not provided, the bootdev is assumed to be + * supported + * + * The bootmeth can check the bootdev (e.g. to make sure it is a + * network device) or the partition information. The following fields + * in @iter are available: + * + * name, dev, state, part + * max_part may be set if part != 0 (i.e. there is a valid partition + * table). Otherwise max_part is 0 + * method is available but is the same as @dev + * the partition has not yet been read, nor has the filesystem been + * checked + * + * It may update only the flags in @iter + * + * @dev: Bootmethod device to check against + * @iter: On entry, provides bootdev, hwpart, part + * Return: 0 if OK, -ENOTSUPP if this bootdev is not supported + */ +int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter); + +/** + * bootmeth_read_bootflow() - set up a bootflow for a device + * + * @dev: Bootmethod device to check + * @bflow: On entry, provides dev, hwpart, part and method. + * Returns updated bootflow if found + * Return: 0 if OK, -ve on error + */ +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow); + +/** + * bootmeth_read_file() - read a file needed for a bootflow + * + * Read a file from the same place as the bootflow came from + * + * @dev: Bootmethod device to use + * @bflow: Bootflow providing info on where to read from + * @file_path: Path to file (may be absolute or relative) + * @addr: Address to load file + * @sizep: On entry provides the maximum permitted size; on exit + * returns the size of the file + * Return: 0 if OK, -ENOSPC if the file is too large for @sizep, other + * -ve value if something else goes wrong + */ +int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep); + +/** + * bootmeth_boot() - boot a bootflow + * + * @dev: Bootmethod device to boot + * @bflow: Bootflow to boot + * Return: does not return on success, since it should boot the + * Operating Systemn. Returns -EFAULT if that fails, other -ve on + * other error + */ +int bootmeth_boot(struct udevice *dev, struct bootflow *bflow); + +/** + * 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); + +/** + * bootmeth_set_order() - Set the bootmeth order + * + * This selects the ordering to use for bootmeths + * + * @order_str: String containing the ordering. This is a comma-separate list of + * bootmeth-device names, e.g. "syslinux,efi". If empty then a default ordering + * is used, based on the sequence number of devices (i.e. using aliases) + * Return: 0 if OK, -ENODEV if an unknown bootmeth is mentioned, -ENOMEM if + * out of memory, -ENOENT if there are no bootmeth devices + */ +int bootmeth_set_order(const char *order_str); + +/** + * bootmeth_try_file() - See we can access a given file + * + * Check for a file with a given name. If found, the filename is allocated in + * @bflow + * + * Sets the state to BOOTFLOWST_FILE on success. It also calls + * fs_set_blk_dev_with_part() so that this does not need to be done by the + * caller before reading the file. + * + * @bflow: Information about file to try + * @desc: Block descriptor to read from + * @prefix: Filename prefix to prepend to @fname (NULL for none) + * @fname: Filename to read + * Return: 0 if OK, -ENOMEM if not enough memory to allocate bflow->fname, + * other -ve value on other error + */ +int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, + const char *prefix, const char *fname); + +/** + * bootmeth_alloc_file() - Allocate and read a bootflow file + * + * Allocates memory for a bootflow file and reads it in. Sets the state to + * BOOTFLOWST_READY on success + * + * Note that fs_set_blk_dev_with_part() must have been called previously. + * + * @bflow: Information about file to read + * @size_limit: Maximum file size to permit + * @align: Allocation alignment (1 for unaligned) + * Return: 0 if OK, -E2BIG if file is too large, -ENOMEM if out of memory, + * other -ve on other error + */ +int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align); + +/** + * bootmeth_common_read_file() - Common handler for reading a file + * + * Reads a named file from the same location as the bootflow file. + * + * @dev: bootmeth device to read from + * @bflow: Bootflow information + * @file_path: Path to file + * @addr: Address to load file to + * @sizep: On entry, the maximum file size to accept, on exit the actual file + * size read + */ +int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep); + +#endif diff --git a/include/bootstd.h b/include/bootstd.h new file mode 100644 index 0000000..b002365 --- /dev/null +++ b/include/bootstd.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Standard U-Boot boot framework + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __bootstd_h +#define __bootstd_h + +struct udevice; + +/** + * struct bootstd_priv - priv data for the bootstd driver + * + * This is attached to the (only) bootstd device, so there is only one instance + * of this struct. It provides overall information about bootdevs and bootflows. + * + * @prefixes: NULL-terminated list of prefixes to use for bootflow filenames, + * e.g. "/", "/boot/"; NULL if none + * @bootdev_order: Order to use for bootdevs (or NULL if none), with each item + * being a bootdev label, e.g. "mmc2", "mmc1"; + * @cur_bootdev: Currently selected bootdev (for commands) + * @cur_bootflow: Currently selected bootflow (for commands) + * @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 + */ +struct bootstd_priv { + const char **prefixes; + const char **bootdev_order; + struct udevice *cur_bootdev; + struct bootflow *cur_bootflow; + struct list_head glob_head; + int bootmeth_count; + struct udevice **bootmeth_order; +}; + +/** + * bootstd_get_bootdev_order() - Get the boot-order list + * + * This reads the boot order, e.g. {"mmc0", "mmc2", NULL} + * + * The list is alloced by the bootstd driver so should not be freed. That is the + * reason for all the const stuff in the function signature + * + * Return: list of string points, terminated by NULL; or NULL if no boot order + */ +const char *const *const bootstd_get_bootdev_order(struct udevice *dev); + +/** + * bootstd_get_prefixes() - Get the filename-prefixes list + * + * This reads the prefixes, e.g. {"/", "/bpot", NULL} + * + * The list is alloced by the bootstd driver so should not be freed. That is the + * reason for all the const stuff in the function signature + * + * Return: list of string points, terminated by NULL; or NULL if no boot order + */ +const char *const *const bootstd_get_prefixes(struct udevice *dev); + +/** + * bootstd_get_priv() - Get the (single) state for the bootstd system + * + * The state holds a global list of all bootflows that have been found. + * + * Return: 0 if OK, -ve if the uclass does not exist + */ +int bootstd_get_priv(struct bootstd_priv **stdp); + +/** + * bootstd_clear_glob() - Clear the global list of bootflows + * + * This removes all bootflows globally and across all bootdevs. + */ +void bootstd_clear_glob(void); + +#endif diff --git a/include/distro.h b/include/distro.h new file mode 100644 index 0000000..2ee1458 --- /dev/null +++ b/include/distro.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __distro_h +#define __distro_h + +#define DISTRO_FNAME "extlinux/extlinux.conf" + +/** + * struct distro_info - useful information for distro_getfile() + * + * @dev: bootmethod device being used to boot + * @bflow: bootflow being booted + */ +struct distro_info { + struct udevice *dev; + struct bootflow *bflow; + struct cmd_tbl *cmdtp; +}; + +#endif diff --git a/include/dm/device.h b/include/dm/device.h index e0f86f5..b474888 100644 --- a/include/dm/device.h +++ b/include/dm/device.h @@ -799,7 +799,7 @@ int device_find_first_child_by_uclass(const struct udevice *parent, struct udevice **devp); /** - * device_find_child_by_name() - Find a child by device name + * device_find_child_by_namelen() - Find a child by device name * * @parent: Parent device to search * @name: Name to look for diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 230b1ea..3ba69ad 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -38,6 +38,9 @@ enum uclass_id { UCLASS_AXI, /* AXI bus */ UCLASS_BLK, /* Block device */ UCLASS_BOOTCOUNT, /* Bootcount backing store */ + UCLASS_BOOTDEV, /* Boot device for locating an OS to boot */ + UCLASS_BOOTMETH, /* Bootmethod for booting an OS */ + UCLASS_BOOTSTD, /* Standard boot driver */ UCLASS_BUTTON, /* Button */ UCLASS_CACHE, /* Cache controller */ UCLASS_CLK, /* Clock source, e.g. used by peripherals */ diff --git a/include/dm/uclass-internal.h b/include/dm/uclass-internal.h index daf856c..3ddcdd2 100644 --- a/include/dm/uclass-internal.h +++ b/include/dm/uclass-internal.h @@ -155,6 +155,22 @@ int uclass_find_first_device(enum uclass_id id, struct udevice **devp); int uclass_find_next_device(struct udevice **devp); /** + * uclass_find_device_by_namelen() - Find uclass device based on ID and name + * + * This searches for a device with the exactly given name. + * + * The device is NOT probed, it is merely returned. + * + * @id: ID to look up + * @name: name of a device to find + * @len: Length of @name (the uclass driver name must have the same length) + * @devp: Returns pointer to device (the first one with the name) + * Return: 0 if OK, -ve on error + */ +int uclass_find_device_by_namelen(enum uclass_id id, const char *name, int len, + struct udevice **devp); + +/** * uclass_find_device_by_name() - Find uclass device based on ID and name * * This searches for a device with the exactly given name. diff --git a/include/dm/uclass.h b/include/dm/uclass.h index aafe652..f6c0110 100644 --- a/include/dm/uclass.h +++ b/include/dm/uclass.h @@ -173,13 +173,13 @@ int uclass_get(enum uclass_id key, struct uclass **ucp); const char *uclass_get_name(enum uclass_id id); /** - * uclass_get_by_name_len() - Look up a uclass by its partial driver name + * uclass_get_by_namelen() - Look up a uclass by its driver name * * @name: Name to look up - * @len: Length of the partial name + * @len: Length of @name (the uclass driver name must have the same length) * Return: the associated uclass ID, or UCLASS_INVALID if not found */ -enum uclass_id uclass_get_by_name_len(const char *name, int len); +enum uclass_id uclass_get_by_namelen(const char *name, int len); /** * uclass_get_by_name() - Look up a uclass by its driver name diff --git a/include/env_callback.h b/include/env_callback.h index 05e9516..d5d2b2f 100644 --- a/include/env_callback.h +++ b/include/env_callback.h @@ -57,6 +57,12 @@ #define NET_CALLBACKS #endif +#ifdef CONFIG_BOOTSTD +#define BOOTSTD_CALLBACK "bootmeths:bootmeths," +#else +#define BOOTSTD_CALLBACK +#endif + /* * This list of callback bindings is static, but may be overridden by defining * a new association in the ".callbacks" environment variable. @@ -65,6 +71,7 @@ ENV_DOT_ESCAPE ENV_FLAGS_VAR ":flags," \ "baudrate:baudrate," \ NET_CALLBACKS \ + BOOTSTD_CALLBACK \ "loadaddr:loadaddr," \ SILENT_CALLBACK \ SPLASHIMAGE_CALLBACK \ diff --git a/include/fs.h b/include/fs.h index e2beba3..b43f16a 100644 --- a/include/fs.h +++ b/include/fs.h @@ -57,6 +57,17 @@ int do_ext2load(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); */ int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype); +/** + * fs_set_type() - Tell fs layer which filesystem type is used + * + * This is needed when reading from a non-block device such as sandbox. It does + * a similar job to fs_set_blk_dev() but just sets the filesystem type instead + * of detecting it and loading it on the block device + * + * @type: Filesystem type to use (FS_TYPE...) + */ +void fs_set_type(int type); + /* * fs_set_blk_dev_with_part - Set current block device + partition * diff --git a/include/mmc.h b/include/mmc.h index 6bdcce8..9b4dc68 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -956,11 +956,21 @@ int mmc_get_env_dev(void); * mmc_get_blk_desc() - Get the block descriptor for an MMC device * * @mmc: MMC device - * Return: block device if found, else NULL + * Return: block descriptor if found, else NULL */ struct blk_desc *mmc_get_blk_desc(struct mmc *mmc); /** + * mmc_get_blk() - Get the block device for an MMC device + * + * @dev: MMC device + * @blkp: Returns pointer to probed block device on sucesss + * + * Return: 0 on success, -ve on error + */ +int mmc_get_blk(struct udevice *dev, struct udevice **blkp); + +/** * mmc_send_ext_csd() - read the extended CSD register * * @mmc: MMC device diff --git a/include/test/suites.h b/include/test/suites.h index 6553a76..ee6858a 100644 --- a/include/test/suites.h +++ b/include/test/suites.h @@ -29,6 +29,8 @@ int cmd_ut_category(const char *name, const char *prefix, int do_ut_addrmap(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_ut_bootstd(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]); int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_common(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); diff --git a/include/vsprintf.h b/include/vsprintf.h index 532ef36..e006af2 100644 --- a/include/vsprintf.h +++ b/include/vsprintf.h @@ -13,9 +13,9 @@ /** * simple_strtoul - convert a string to an unsigned long * - * @param cp The string to be converted - * @param endp Updated to point to the first character not converted - * @param base The number base to use (0 for the default) + * @cp: The string to be converted + * @endp: Updated to point to the first character not converted + * @base: The number base to use (0 for the default) * Return: value decoded from string (0 if invalid) * * Converts a string to an unsigned long. If there are invalid characters at @@ -34,8 +34,8 @@ ulong simple_strtoul(const char *cp, char **endp, unsigned int base); /** * hex_strtoul - convert a string in hex to an unsigned long * - * @param cp The string to be converted - * @param endp Updated to point to the first character not converted + * @cp: The string to be converted + * @endp: Updated to point to the first character not converted * Return: value decoded from string (0 if invalid) * * Converts a hex string to an unsigned long. If there are invalid characters at @@ -47,8 +47,8 @@ unsigned long hextoul(const char *cp, char **endp); /** * dec_strtoul - convert a string in decimal to an unsigned long * - * @param cp The string to be converted - * @param endp Updated to point to the first character not converted + * @cp: The string to be converted + * @endp: Updated to point to the first character not converted * Return: value decoded from string (0 if invalid) * * Converts a decimal string to an unsigned long. If there are invalid @@ -59,11 +59,11 @@ unsigned long dectoul(const char *cp, char **endp); /** * strict_strtoul - convert a string to an unsigned long strictly - * @param cp The string to be converted - * @param base The number base to use (0 for the default) - * @param res The converted result value - * Return: 0 if conversion is successful and *res is set to the converted - * value, otherwise it returns -EINVAL and *res is set to 0. + * @cp: The string to be converted + * @base: The number base to use (0 for the default) + * @res: The converted result value + * Return: 0 if conversion is successful and `*res` is set to the converted + * value, otherwise it returns -EINVAL and `*res` is set to 0. * * strict_strtoul converts a string to an unsigned long only if the * string is really an unsigned long string, any string containing @@ -98,8 +98,11 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base); * Given a string this finds a trailing number on the string and returns it. * For example, "abc123" would return 123. * - * @str: String to exxamine - * Return: training number if found, else -1 + * Note that this does not handle a string without a prefix. See dectoul() for + * that case. + * + * @str: String to examine + * Return: trailing number if found, else -1 */ long trailing_strtol(const char *str); @@ -111,20 +114,38 @@ long trailing_strtol(const char *str); * characters between @str and @end - 1 are examined. If @end is NULL, it is * set to str + strlen(str). * - * @str: String to exxamine + * @str: String to examine * @end: Pointer to end of string to examine, or NULL to use the * whole string - * Return: training number if found, else -1 + * Return: trailing number if found, else -1 */ long trailing_strtoln(const char *str, const char *end); /** + * trailing_strtoln_end() - extract trailing integer from a fixed-length string + * + * Given a fixed-length string this finds a trailing number on the string + * and returns it. For example, "abc123" would return 123. Only the + * characters between @str and @end - 1 are examined. If @end is NULL, it is + * set to str + strlen(str). + * + * @str: String to examine + * @end: Pointer to end of string to examine, or NULL to use the + * whole string + * @endp: If non-NULL, this is set to point to the character where the + * number starts, e.g. for "mmc0" this would be point to the '0'; if no + * trailing number is found, it is set to the end of the string + * Return: training number if found, else -1 + */ +long trailing_strtoln_end(const char *str, const char *end, char const **endp); + +/** * panic() - Print a message and reset/hang * * Prints a message on the console(s) and then resets. If CONFIG_PANIC_HANG is * defined, then it will hang instead of resetting. * - * @param fmt: printf() format string for message, which should not include + * @fmt: printf() format string for message, which should not include * \n, followed by arguments */ void panic(const char *fmt, ...) @@ -139,16 +160,16 @@ void panic(const char *fmt, ...) * This function can be used instead of panic() when your board does not * already use printf(), * to keep code size small. * - * @param fmt: string to display, which should not include \n + * @str: string to display, which should not include \n */ void panic_str(const char *str) __attribute__ ((noreturn)); /** * Format a string and place it in a buffer * - * @param buf The buffer to place the result into - * @param fmt The format string to use - * @param ... Arguments for the format string + * @buf: The buffer to place the result into + * @fmt: The format string to use + * @...: Arguments for the format string * * The function returns the number of characters written * into @buf. @@ -161,9 +182,9 @@ int sprintf(char *buf, const char *fmt, ...) /** * Format a string and place it in a buffer (va_list version) * - * @param buf The buffer to place the result into - * @param fmt The format string to use - * @param args Arguments for the format string + * @buf: The buffer to place the result into + * @fmt: The format string to use + * @args: Arguments for the format string * Return: the number of characters which have been written into * the @buf not including the trailing '\0'. * @@ -178,7 +199,7 @@ int vsprintf(char *buf, const char *fmt, va_list args); * * This returns a static string containing the decimal representation of the * given value. The returned value may be overwritten by other calls to other - * simple_... functions, so should be used immediately + * simple... functions, so should be used immediately * * @val: Value to convert * Return: string containing the decimal representation of @val @@ -190,9 +211,9 @@ char *simple_itoa(ulong val); * * This returns a static string containing the hexadecimal representation of the * given value. The returned value may be overwritten by other calls to other - * simple_... functions, so should be used immediately + * simple... functions, so should be used immediately * - * @val: Value to convert + * @num: Value to convert * Return: string containing the hexecimal representation of @val */ char *simple_xtoa(ulong num); @@ -200,10 +221,10 @@ char *simple_xtoa(ulong num); /** * Format a string and place it in a buffer * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param ... Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @...: Arguments for the format string * Return: the number of characters which would be * generated for the given input, excluding the trailing null, * as per ISO C99. If the return is greater than or equal to @@ -217,10 +238,10 @@ int snprintf(char *buf, size_t size, const char *fmt, ...) /** * Format a string and place it in a buffer * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param ... Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @...: Arguments for the format string * * The return value is the number of characters written into @buf not including * the trailing '\0'. If @size is == 0 the function returns 0. @@ -233,10 +254,10 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...) /** * Format a string and place it in a buffer (base function) * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param args Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @args: Arguments for the format string * Return: The number characters which would be generated for the given * input, excluding the trailing '\0', as per ISO C99. Note that fewer * characters may be written if this number of characters is >= size. @@ -258,10 +279,10 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args); /** * Format a string and place it in a buffer (va_list version) * - * @param buf The buffer to place the result into - * @param size The size of the buffer, including the trailing null space - * @param fmt The format string to use - * @param args Arguments for the format string + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @args: Arguments for the format string * Return: the number of characters which have been written into * the @buf not including the trailing '\0'. If @size is == 0 the function * returns 0. @@ -278,8 +299,8 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args); * This prints a value with grouped digits, like 12,345,678 to make it easier * to read. * - * @val: Value to print - * @digits: Number of digiits to print + * @int_val: Value to print + * @digits: Number of digiits to print */ void print_grouped_ull(unsigned long long int_val, int digits); @@ -309,9 +330,9 @@ void str_to_upper(const char *in, char *out, size_t len); /** * vsscanf - Unformat a buffer into a list of arguments - * @buf: input buffer - * @fmt: format of buffer - * @args: arguments + * @inp: input buffer + * @fmt0: format of buffer + * @ap: arguments */ int vsscanf(const char *inp, char const *fmt0, va_list ap); diff --git a/lib/strto.c b/lib/strto.c index f191884..6462d4f 100644 --- a/lib/strto.c +++ b/lib/strto.c @@ -183,22 +183,33 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base) return simple_strtoull(cp, endp, base); } -long trailing_strtoln(const char *str, const char *end) +long trailing_strtoln_end(const char *str, const char *end, char const **endp) { const char *p; if (!end) end = str + strlen(str); - if (isdigit(end[-1])) { - for (p = end - 1; p > str; p--) { - if (!isdigit(*p)) - return dectoul(p + 1, NULL); - } + p = end - 1; + if (p > str && isdigit(*p)) { + do { + if (!isdigit(p[-1])) { + if (endp) + *endp = p; + return dectoul(p, NULL); + } + } while (--p > str); } + if (endp) + *endp = end; return -1; } +long trailing_strtoln(const char *str, const char *end) +{ + return trailing_strtoln_end(str, end, NULL); +} + long trailing_strtol(const char *str) { return trailing_strtoln(str, NULL); diff --git a/net/Kconfig b/net/Kconfig index ef0aa16..964a4fe 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -25,6 +25,15 @@ config PROT_UDP Enable a generic udp framework that allows defining a custom handler for udp protocol. +config BOOTDEV_ETH + bool "Enable bootdev for ethernet" + depends on BOOTSTD + default y + help + Provide a bootdev for ethernet so that is it possible to boot + an operationg system over the network, using the PXE (Preboot + Execution Environment) protocol. + config BOOTP_SEND_HOSTNAME bool "Send hostname to DNS server" help diff --git a/net/Makefile b/net/Makefile index fb3eba8..6c81250 100644 --- a/net/Makefile +++ b/net/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_CMD_DNS) += dns.o obj-$(CONFIG_DM_DSA) += dsa-uclass.o ifdef CONFIG_DM_ETH obj-$(CONFIG_NET) += eth-uclass.o +obj-$(CONFIG_$(SPL_TPL_)BOOTDEV_ETH) += eth_bootdev.o else obj-$(CONFIG_NET) += eth_legacy.o endif diff --git a/net/eth-uclass.c b/net/eth-uclass.c index 58c308f..bcefc54 100644 --- a/net/eth-uclass.c +++ b/net/eth-uclass.c @@ -8,6 +8,7 @@ #define LOG_CATEGORY UCLASS_ETH #include <common.h> +#include <bootdev.h> #include <bootstage.h> #include <dm.h> #include <env.h> @@ -473,6 +474,8 @@ int eth_initialize(void) static int eth_post_bind(struct udevice *dev) { + int ret; + if (strchr(dev->name, ' ')) { printf("\nError: eth device name \"%s\" has a space!\n", dev->name); @@ -482,6 +485,11 @@ static int eth_post_bind(struct udevice *dev) #ifdef CONFIG_DM_ETH_PHY eth_phy_binds_nodes(dev); #endif + if (CONFIG_IS_ENABLED(BOOTDEV_ETH)) { + ret = bootdev_setup_for_dev(dev, "eth_bootdev"); + if (ret) + return log_msg_ret("bootdev", ret); + } return 0; } diff --git a/net/eth_bootdev.c b/net/eth_bootdev.c new file mode 100644 index 0000000..b735966 --- /dev/null +++ b/net/eth_bootdev.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdevice for ethernet (uses PXE) + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <command.h> +#include <bootmeth.h> +#include <distro.h> +#include <dm.h> +#include <log.h> +#include <net.h> + +static int eth_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + char name[60]; + int ret; + + /* Must be an Ethernet device */ + ret = bootflow_iter_uses_network(iter); + if (ret) + return log_msg_ret("net", ret); + + ret = bootmeth_check(bflow->method, iter); + if (ret) + return log_msg_ret("check", ret); + + /* + * Like distro boot, this assumes there is only one Ethernet device. + * In this case, that means that @eth is ignored + */ + + snprintf(name, sizeof(name), "%s.%d", dev->name, iter->part); + bflow->name = strdup(name); + if (!bflow->name) + return log_msg_ret("name", -ENOMEM); + + /* + * There is not a direct interface to the network stack so run + * everything through the command-line interpreter for now. + * + * Don't bother checking the result of dhcp. It can fail with: + * + * DHCP client bound to address 192.168.4.50 (4 ms) + * *** Warning: no boot file name; using 'C0A80432.img' + * Using smsc95xx_eth device + * TFTP from server 192.168.4.1; our IP address is 192.168.4.50 + * Filename 'C0A80432.img'. + * Load address: 0x200000 + * Loading: * + * TFTP error: 'File not found' (1) + * + * This is not a real failure, since we don't actually care if the + * boot file exists. + */ + log_debug("running dhcp\n"); + run_command("dhcp", 0); + bflow->state = BOOTFLOWST_MEDIA; + + /* See distro_pxe_read_bootflow() for the standard impl of this */ + log_debug("dhcp complete - reading bootflow with method %s\n", + bflow->method->name); + ret = bootmeth_read_bootflow(bflow->method, bflow); + log_debug("reading bootflow returned %d\n", ret); + if (ret) + return log_msg_ret("method", ret); + + return 0; +} + +static int eth_bootdev_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_4_NET_BASE; + + return 0; +} + +struct bootdev_ops eth_bootdev_ops = { + .get_bootflow = eth_get_bootflow, +}; + +static const struct udevice_id eth_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-eth" }, + { } +}; + +U_BOOT_DRIVER(eth_bootdev) = { + .name = "eth_bootdev", + .id = UCLASS_BOOTDEV, + .ops = ð_bootdev_ops, + .bind = eth_bootdev_bind, + .of_match = eth_bootdev_ids, +}; diff --git a/test/Makefile b/test/Makefile index b3b2902..abd605a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_UT_TIME) += time_ut.o obj-y += ut.o ifeq ($(CONFIG_SPL_BUILD),) +obj-$(CONFIG_UNIT_TEST) += boot/ obj-$(CONFIG_UNIT_TEST) += common/ obj-$(CONFIG_UNIT_TEST) += lib/ obj-y += log/ diff --git a/test/boot/Makefile b/test/boot/Makefile new file mode 100644 index 0000000..1730792 --- /dev/null +++ b/test/boot/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2021 Google LLC + +obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o diff --git a/test/boot/bootdev.c b/test/boot/bootdev.c new file mode 100644 index 0000000..1c2a79f --- /dev/null +++ b/test/boot/bootdev.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootstd.h> +#include <dm.h> +#include <bootdev.h> +#include <bootflow.h> +#include <mapmem.h> +#include <os.h> +#include <test/suites.h> +#include <test/ut.h> +#include "bootstd_common.h" + +/* Allow reseting the USB-started flag */ +extern char usb_started; + +/* Check 'bootdev list' command */ +static int bootdev_test_cmd_list(struct unit_test_state *uts) +{ + int probed; + + console_record_reset_enable(); + for (probed = 0; probed < 2; probed++) { + int probe_ch = probed ? '+' : ' '; + + ut_assertok(run_command(probed ? "bootdev list -p" : + "bootdev list", 0)); + ut_assert_nextline("Seq Probed Status Uclass Name"); + ut_assert_nextlinen("---"); + ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 0, probe_ch, "OK", + "mmc", "mmc2.bootdev"); + ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 1, probe_ch, "OK", + "mmc", "mmc1.bootdev"); + ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 2, probe_ch, "OK", + "mmc", "mmc0.bootdev"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(3 bootdevs)"); + ut_assert_console_end(); + } + + return 0; +} +BOOTSTD_TEST(bootdev_test_cmd_list, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootdev select' and 'info' commands */ +static int bootdev_test_cmd_select(struct unit_test_state *uts) +{ + struct bootstd_priv *std; + + /* get access to the CLI's cur_bootdev */ + ut_assertok(bootstd_get_priv(&std)); + + console_record_reset_enable(); + ut_asserteq(1, run_command("bootdev info", 0)); + ut_assert_nextlinen("Please use"); + ut_assert_console_end(); + + /* select by sequence */ + ut_assertok(run_command("bootdev select 0", 0)); + ut_assert_console_end(); + + ut_assertok(run_command("bootdev info", 0)); + ut_assert_nextline("Name: mmc2.bootdev"); + ut_assert_nextline("Sequence: 0"); + ut_assert_nextline("Status: Probed"); + ut_assert_nextline("Uclass: mmc"); + ut_assert_nextline("Bootflows: 0 (0 valid)"); + ut_assert_console_end(); + + /* select by bootdev name */ + ut_assertok(run_command("bootdev select mmc1.bootdev", 0)); + ut_assert_console_end(); + ut_assertnonnull(std->cur_bootdev); + ut_asserteq_str("mmc1.bootdev", std->cur_bootdev->name); + + /* select by bootdev label*/ + ut_assertok(run_command("bootdev select mmc1", 0)); + ut_assert_console_end(); + ut_assertnonnull(std->cur_bootdev); + ut_asserteq_str("mmc1.bootdev", std->cur_bootdev->name); + + /* deselect */ + ut_assertok(run_command("bootdev select", 0)); + ut_assert_console_end(); + ut_assertnull(std->cur_bootdev); + + ut_asserteq(1, run_command("bootdev info", 0)); + ut_assert_nextlinen("Please use"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootdev_test_cmd_select, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check bootdev labels */ +static int bootdev_test_labels(struct unit_test_state *uts) +{ + struct udevice *dev, *media; + + ut_assertok(bootdev_find_by_label("mmc2", &dev)); + ut_asserteq(UCLASS_BOOTDEV, device_get_uclass_id(dev)); + media = dev_get_parent(dev); + ut_asserteq(UCLASS_MMC, device_get_uclass_id(media)); + ut_asserteq_str("mmc2", media->name); + + /* Check invalid uclass */ + ut_asserteq(-EINVAL, bootdev_find_by_label("fred0", &dev)); + + /* Check unknown sequence number */ + ut_asserteq(-ENOENT, bootdev_find_by_label("mmc6", &dev)); + + return 0; +} +BOOTSTD_TEST(bootdev_test_labels, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check bootdev ordering with the bootdev-order property */ +static int bootdev_test_order(struct unit_test_state *uts) +{ + struct bootflow_iter iter; + struct bootflow bflow; + + /* + * First try the order set by the bootdev-order property + * Like all sandbox unit tests this relies on the devicetree setting up + * the required devices: + * + * mmc0 - nothing connected + * mmc1 - connected to mmc1.img file + * mmc2 - nothing connected + */ + ut_assertok(env_set("boot_targets", NULL)); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(2, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[1]->name); + bootflow_iter_uninit(&iter); + + /* Use the environment variable to override it */ + ut_assertok(env_set("boot_targets", "mmc1 mmc2")); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(2, iter.num_devs); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[1]->name); + bootflow_iter_uninit(&iter); + + /* + * Now drop both orderings, to check the default (prioriy/sequence) + * ordering + */ + ut_assertok(env_set("boot_targets", NULL)); + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(3, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[1]->name); + ut_asserteq_str("mmc0.bootdev", iter.dev_order[2]->name); + + /* + * Check that adding aliases for the bootdevs works. We just fake it by + * setting the sequence numbers directly. + */ + iter.dev_order[0]->seq_ = 0; + iter.dev_order[1]->seq_ = 3; + iter.dev_order[2]->seq_ = 2; + bootflow_iter_uninit(&iter); + + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(3, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("mmc0.bootdev", iter.dev_order[1]->name); + ut_asserteq_str("mmc1.bootdev", iter.dev_order[2]->name); + bootflow_iter_uninit(&iter); + + return 0; +} +BOOTSTD_TEST(bootdev_test_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check bootdev ordering with the uclass priority */ +static int bootdev_test_prio(struct unit_test_state *uts) +{ + struct bootdev_uc_plat *ucp; + struct bootflow_iter iter; + struct bootflow bflow; + struct udevice *blk; + + /* Start up USB which gives us three additional bootdevs */ + usb_started = false; + ut_assertok(run_command("usb start", 0)); + + ut_assertok(bootstd_test_drop_bootdev_order(uts)); + + /* 3 MMC and 3 USB bootdevs: MMC should come before USB */ + console_record_reset_enable(); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(6, iter.num_devs); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name); + ut_asserteq_str("usb_mass_storage.lun0.bootdev", + iter.dev_order[3]->name); + + ut_assertok(bootdev_get_sibling_blk(iter.dev_order[3], &blk)); + ut_asserteq_str("usb_mass_storage.lun0", blk->name); + + /* adjust the priority of the first USB bootdev to the highest */ + ucp = dev_get_uclass_plat(iter.dev_order[3]); + ucp->prio = 1; + + bootflow_iter_uninit(&iter); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(6, iter.num_devs); + ut_asserteq_str("usb_mass_storage.lun0.bootdev", + iter.dev_order[0]->name); + ut_asserteq_str("mmc2.bootdev", iter.dev_order[1]->name); + + return 0; +} +BOOTSTD_TEST(bootdev_test_prio, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c new file mode 100644 index 0000000..1ebb789 --- /dev/null +++ b/test/boot/bootflow.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootstd.h> +#include <dm.h> +#include <dm/lists.h> +#include <test/suites.h> +#include <test/ut.h> +#include "bootstd_common.h" + +/* Check 'bootflow scan/list' commands */ +static int bootflow_cmd(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootdev select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow scan -l", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc1.bootdev'"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing bootflows for bootdev 'mmc1.bootdev'"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan' with a name / label / seq */ +static int bootflow_cmd_label(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -l mmc1", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc1.bootdev'"); + ut_assert_skip_to_line("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow scan -l mmc0.bootdev", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc0.bootdev'"); + ut_assert_skip_to_line("(0 bootflows, 0 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow scan -l 0", 0)); + ut_assert_nextline("Scanning for bootflows in bootdev 'mmc2.bootdev'"); + ut_assert_skip_to_line("(0 bootflows, 0 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_label, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan/list' commands using all bootdevs */ +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_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 bootdev 'mmc2.bootdev':"); + ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextline("Scanning bootdev 'mmc0.bootdev':"); + ut_assert_nextline("No more bootdevs"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing all bootflows"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootflow, 1 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_glob, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan -e' */ +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_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 bootdev 'mmc2.bootdev':"); + ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole <NULL>"); + ut_assert_nextline(" ** No partition found, err=-93"); + ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole <NULL>"); + ut_assert_nextline(" ** No partition found, err=-93"); + + ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':"); + ut_assert_nextline(" 2 syslinux media mmc 0 mmc1.bootdev.whole <NULL>"); + ut_assert_nextline(" ** No partition found, err=-2"); + ut_assert_nextline(" 3 efi media mmc 0 mmc1.bootdev.whole <NULL>"); + ut_assert_nextline(" ** No partition found, err=-2"); + ut_assert_nextline(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_nextline(" 5 efi fs mmc 1 mmc1.bootdev.part_1 efi/boot/bootsbox.efi"); + + ut_assert_skip_to_line("Scanning bootdev 'mmc0.bootdev':"); + ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole <NULL>"); + ut_assert_nextline(" ** No partition found, err=-93"); + ut_assert_nextline("No more bootdevs"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow list", 0)); + ut_assert_nextline("Showing all bootflows"); + ut_assert_nextline("Seq Method State Uclass Part Name Filename"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole <NULL>"); + ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole <NULL>"); + ut_assert_skip_to_line(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf"); + ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole <NULL>"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(64 bootflows, 1 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_scan_e, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow info' */ +static int bootflow_cmd_info(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootdev select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow scan", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow select 0", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow info", 0)); + ut_assert_nextline("Name: mmc1.bootdev.part_1"); + ut_assert_nextline("Device: mmc1.bootdev"); + ut_assert_nextline("Block dev: mmc1.blk"); + ut_assert_nextline("Method: syslinux"); + ut_assert_nextline("State: ready"); + ut_assert_nextline("Partition: 1"); + ut_assert_nextline("Subdir: (none)"); + ut_assert_nextline("Filename: /extlinux/extlinux.conf"); + ut_assert_nextlinen("Buffer: "); + ut_assert_nextline("Size: 253 (595 bytes)"); + ut_assert_nextline("Error: 0"); + ut_assert_console_end(); + + ut_assertok(run_command("bootflow info -d", 0)); + ut_assert_nextline("Name: mmc1.bootdev.part_1"); + ut_assert_skip_to_line("Error: 0"); + ut_assert_nextline("Contents:"); + ut_assert_nextline("%s", ""); + ut_assert_nextline("# extlinux.conf generated by appliance-creator"); + ut_assert_skip_to_line(" initrd /initramfs-5.3.7-301.fc31.armv7hl.img"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_info, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootflow scan -b' to boot the first available bootdev */ +static int bootflow_scan_boot(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootflow scan -b", 0)); + ut_assert_nextline( + "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux"); + ut_assert_nextline("Ignoring unknown command: ui"); + + /* + * We expect it to get through to boot although sandbox always returns + * -EFAULT as it cannot actually boot the kernel + */ + ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux"); + ut_assert_nextline("Boot failed (err=-14)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_scan_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check iterating through available bootflows */ +static int bootflow_iter(struct unit_test_state *uts) +{ + struct bootflow_iter iter; + struct bootflow bflow; + + bootstd_clear_glob(); + + /* The first device is mmc2.bootdev which has no media */ + ut_asserteq(-EPROTONOSUPPORT, + bootflow_scan_first(&iter, BOOTFLOWF_ALL, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + + /* + * This shows MEDIA even though there is none, since int + * bootdev_find_in_blk() we call part_get_info() which returns + * -EPROTONOSUPPORT. Ideally it would return -EEOPNOTSUPP and we would + * know. + */ + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + + ut_asserteq(-EPROTONOSUPPORT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(1, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0, iter.max_part); + ut_asserteq_str("efi", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + /* The next device is mmc1.bootdev - at first we use the whole device */ + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(1, iter.cur_method); + ut_asserteq(0, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("efi", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + /* Then more to partition 1 where we find something */ + ut_assertok(bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(1, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_READY, bflow.state); + bootflow_free(&bflow); + + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(1, iter.cur_method); + ut_asserteq(1, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("efi", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_FS, bflow.state); + bootflow_free(&bflow); + + /* Then more to partition 2 which doesn't exist */ + ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow)); + ut_asserteq(2, iter.num_methods); + ut_asserteq(0, iter.cur_method); + ut_asserteq(2, iter.part); + ut_asserteq(0x1e, iter.max_part); + ut_asserteq_str("syslinux", iter.method->name); + ut_asserteq(0, bflow.err); + ut_asserteq(BOOTFLOWST_MEDIA, bflow.state); + bootflow_free(&bflow); + + bootflow_iter_uninit(&iter); + + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_iter, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check using the system bootdev */ +static int bootflow_system(struct unit_test_state *uts) +{ + struct udevice *bootstd, *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)); + + /* 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 <NULL> <NULL>"); + ut_assert_nextline("No more bootdevs"); + ut_assert_skip_to_line("(2 bootflows, 2 valid)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_system, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check disabling a bootmethod if it requests it */ +static int bootflow_iter_disable(struct unit_test_state *uts) +{ + struct udevice *bootstd, *dev; + struct bootflow_iter iter; + struct bootflow bflow; + int i; + + /* Add the EFI bootmgr driver */ + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + 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(); + ut_assertok(run_command("bootflow scan -lb", 0)); + + /* Try to boot the bootmgr flow, which will fail */ + console_record_reset_enable(); + ut_assertok(bootflow_scan_first(&iter, 0, &bflow)); + ut_asserteq(3, iter.num_methods); + ut_asserteq_str("sandbox", iter.method->name); + ut_asserteq(-ENOTSUPP, bootflow_run_boot(&iter, &bflow)); + + ut_assert_skip_to_line("Boot method 'sandbox' failed and will not be retried"); + ut_assert_console_end(); + + /* Check that the sandbox bootmeth has been removed */ + ut_asserteq(2, iter.num_methods); + for (i = 0; i < iter.num_methods; i++) + ut_assert(strcmp("sandbox", iter.method_order[i]->name)); + + return 0; +} +BOOTSTD_TEST(bootflow_iter_disable, 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) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootdev select 1", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow scan", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootflow select 0", 0)); + ut_assert_console_end(); + ut_asserteq(1, run_command("bootflow boot", 0)); + ut_assert_nextline( + "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux"); + ut_assert_nextline("Ignoring unknown command: ui"); + + /* + * We expect it to get through to boot although sandbox always returns + * -EFAULT as it cannot actually boot the kernel + */ + ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux"); + ut_assert_nextline("Boot failed (err=-14)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/bootmeth.c b/test/boot/bootmeth.c new file mode 100644 index 0000000..07776c5 --- /dev/null +++ b/test/boot/bootmeth.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootstd.h> +#include <test/suites.h> +#include <test/ut.h> +#include "bootstd_common.h" + +/* Check 'bootmeth list' command */ +static int bootmeth_cmd_list(struct unit_test_state *uts) +{ + console_record_reset_enable(); + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); + ut_assert_nextline(" 1 1 efi EFI boot from an .efi file"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootmeth_cmd_list, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootmeth order' command */ +static int bootmeth_cmd_order(struct unit_test_state *uts) +{ + /* Select just one bootmethod */ + console_record_reset_enable(); + ut_assertok(run_command("bootmeth order syslinux", 0)); + ut_assert_console_end(); + ut_assertnonnull(env_get("bootmeths")); + ut_asserteq_str("syslinux", env_get("bootmeths")); + + /* Only that one should be listed */ + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(1 bootmeth)"); + ut_assert_console_end(); + + /* Check the -a flag, efi should show as not in the order ("-") */ + ut_assertok(run_command("bootmeth list -a", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device"); + ut_assert_nextline(" - 1 efi EFI boot from an .efi file"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assert_console_end(); + + /* Check the -a flag with the reverse order */ + ut_assertok(run_command("bootmeth order \"efi syslinux\"", 0)); + ut_assert_console_end(); + ut_assertok(run_command("bootmeth list -a", 0)); + ut_assert_nextline("Order Seq Name Description"); + ut_assert_nextlinen("---"); + ut_assert_nextline(" 1 0 syslinux Syslinux boot from a block device"); + ut_assert_nextline(" 0 1 efi EFI boot from an .efi file"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assert_console_end(); + + /* Now reset the order to empty, which should show all of them again */ + ut_assertok(run_command("bootmeth order", 0)); + ut_assert_console_end(); + ut_assertnull(env_get("bootmeths")); + ut_assertok(run_command("bootmeth list", 0)); + ut_assert_skip_to_line("(2 bootmeths)"); + + /* Try reverse order */ + ut_assertok(run_command("bootmeth order \"efi syslinux\"", 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(" 1 0 syslinux Syslinux boot from a block device"); + ut_assert_nextlinen("---"); + ut_assert_nextline("(2 bootmeths)"); + ut_assertnonnull(env_get("bootmeths")); + ut_asserteq_str("efi syslinux", env_get("bootmeths")); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootmeth_cmd_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT); + +/* Check 'bootmeths' env var */ +static int bootmeth_env(struct unit_test_state *uts) +{ + struct bootstd_priv *std; + + ut_assertok(bootstd_get_priv(&std)); + + /* Select just one bootmethod */ + console_record_reset_enable(); + ut_assertok(env_set("bootmeths", "syslinux")); + ut_asserteq(1, std->bootmeth_count); + + /* Select an invalid bootmethod */ + ut_asserteq(1, run_command("setenv bootmeths fred", 0)); + ut_assert_nextline("Unknown bootmeth 'fred'"); + ut_assert_nextlinen("## Error inserting"); + ut_assert_console_end(); + + ut_assertok(env_set("bootmeths", "efi syslinux")); + ut_asserteq(2, std->bootmeth_count); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootmeth_env, UT_TESTF_DM | UT_TESTF_SCAN_FDT); diff --git a/test/boot/bootstd_common.c b/test/boot/bootstd_common.c new file mode 100644 index 0000000..05347d8 --- /dev/null +++ b/test/boot/bootstd_common.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for bootdev functions. All start with 'bootdev' + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <bootstd.h> +#include <dm.h> +#include <test/suites.h> +#include <test/ut.h> +#include "bootstd_common.h" + +int bootstd_test_drop_bootdev_order(struct unit_test_state *uts) +{ + struct bootstd_priv *priv; + struct udevice *bootstd; + + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + priv = dev_get_priv(bootstd); + priv->bootdev_order = NULL; + + return 0; +} + +int do_ut_bootstd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct unit_test *tests = UNIT_TEST_SUITE_START(bootstd_test); + const int n_ents = UNIT_TEST_SUITE_COUNT(bootstd_test); + + return cmd_ut_category("bootstd", "bootstd_test_", + tests, n_ents, argc, argv); +} diff --git a/test/boot/bootstd_common.h b/test/boot/bootstd_common.h new file mode 100644 index 0000000..676ef0a --- /dev/null +++ b/test/boot/bootstd_common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Common header file for bootdev, bootflow, bootmeth tests + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#ifndef __bootstd_common_h +#define __bootstd_common_h + +/* Declare a new bootdev test */ +#define BOOTSTD_TEST(_name, _flags) \ + UNIT_TEST(_name, _flags, bootstd_test) + +struct unit_test_state; + +/** + * bootstd_test_drop_bootdev_order() - Remove the existing boot order + * + * Drop the boot order so that all bootdevs are used in their alias order + * + * @uts: Unit test state to use for ut_assert...() functions + */ +int bootstd_test_drop_bootdev_order(struct unit_test_state *uts); + +#endif diff --git a/test/cmd_ut.c b/test/cmd_ut.c index 90b260f..67a13ee 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -28,6 +28,10 @@ int cmd_ut_category(const char *name, const char *prefix, static struct cmd_tbl cmd_ut_sub[] = { U_BOOT_CMD_MKENT(all, CONFIG_SYS_MAXARGS, 1, do_ut_all, "", ""), +#ifdef CONFIG_BOOTSTD + U_BOOT_CMD_MKENT(bootstd, CONFIG_SYS_MAXARGS, 1, do_ut_bootstd, + "", ""), +#endif U_BOOT_CMD_MKENT(common, CONFIG_SYS_MAXARGS, 1, do_ut_common, "", ""), #if defined(CONFIG_UT_DM) U_BOOT_CMD_MKENT(dm, CONFIG_SYS_MAXARGS, 1, do_ut_dm, "", ""), @@ -115,6 +119,9 @@ static char ut_help_text[] = "ut bloblist - Test bloblist implementation\n" "ut compression - Test compressors and bootm decompression\n" #endif +#ifdef CONFIG_BOOTSTD + "ut bootstd - Test standard boot implementation\n" +#endif #ifdef CONFIG_UT_DM "ut dm [test-name]\n" #endif diff --git a/test/dm/blk.c b/test/dm/blk.c index 8556cc7..85c3a3b 100644 --- a/test/dm/blk.c +++ b/test/dm/blk.c @@ -15,6 +15,9 @@ DECLARE_GLOBAL_DATA_PTR; +/* Allow resetting the USB-started flag */ +extern char usb_started; + /* Test that block devices can be created */ static int dm_test_blk_base(struct unit_test_state *uts) { @@ -66,8 +69,11 @@ static int dm_test_blk_usb(struct unit_test_state *uts) struct udevice *usb_dev, *dev; struct blk_desc *dev_desc; + usb_started = false; + /* Get a flash device */ state_set_skip_delays(true); + ut_assertok(usb_stop()); ut_assertok(usb_init()); ut_assertok(uclass_get_device(UCLASS_MASS_STORAGE, 0, &usb_dev)); ut_assertok(blk_get_device_by_str("usb", "0", &dev_desc)); diff --git a/test/dm/core.c b/test/dm/core.c index 0ce0b3c..ebd5044 100644 --- a/test/dm/core.c +++ b/test/dm/core.c @@ -1161,6 +1161,8 @@ static int dm_test_uclass_names(struct unit_test_state *uts) ut_asserteq_str("test", uclass_get_name(UCLASS_TEST)); ut_asserteq(UCLASS_TEST, uclass_get_by_name("test")); + ut_asserteq(UCLASS_SPI, uclass_get_by_name("spi")); + return 0; } DM_TEST(dm_test_uclass_names, UT_TESTF_SCAN_PDATA); @@ -1258,3 +1260,18 @@ static int dm_test_get_stats(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_get_stats, UT_TESTF_SCAN_FDT); + +/* Test uclass_find_device_by_name() */ +static int dm_test_uclass_find_device(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(uclass_find_device_by_name(UCLASS_I2C, "i2c@0", &dev)); + ut_asserteq(-ENODEV, + uclass_find_device_by_name(UCLASS_I2C, "i2c@0x", &dev)); + ut_assertok(uclass_find_device_by_namelen(UCLASS_I2C, "i2c@0x", 5, + &dev)); + + return 0; +} +DM_TEST(dm_test_uclass_find_device, UT_TESTF_SCAN_FDT); diff --git a/test/dm/fastboot.c b/test/dm/fastboot.c index e7f8c36..758538d 100644 --- a/test/dm/fastboot.c +++ b/test/dm/fastboot.c @@ -81,9 +81,9 @@ static int dm_test_fastboot_mmc_part(struct unit_test_state *uts) &part_info, response)); ut_asserteq(0, fastboot_mmc_get_part_info("0.0:0", &fb_dev_desc, &part_info, response)); - ut_asserteq(0, fastboot_mmc_get_part_info("1", &fb_dev_desc, + ut_asserteq(0, fastboot_mmc_get_part_info("2", &fb_dev_desc, &part_info, response)); - ut_asserteq(0, fastboot_mmc_get_part_info("1.0", &fb_dev_desc, + ut_asserteq(0, fastboot_mmc_get_part_info("2.0", &fb_dev_desc, &part_info, response)); ut_asserteq(1, fastboot_mmc_get_part_info(":1", &fb_dev_desc, &part_info, response)); diff --git a/test/print_ut.c b/test/print_ut.c index 247011f..47a6ce5 100644 --- a/test/print_ut.c +++ b/test/print_ut.c @@ -345,26 +345,6 @@ static int print_do_hex_dump(struct unit_test_state *uts) } PRINT_TEST(print_do_hex_dump, UT_TESTF_CONSOLE_REC); -static int print_itoa(struct unit_test_state *uts) -{ - ut_asserteq_str("123", simple_itoa(123)); - ut_asserteq_str("0", simple_itoa(0)); - ut_asserteq_str("2147483647", simple_itoa(0x7fffffff)); - ut_asserteq_str("4294967295", simple_itoa(0xffffffff)); - - /* Use #ifdef here to avoid a compiler warning on 32-bit machines */ -#ifdef CONFIG_PHYS_64BIT - if (sizeof(ulong) == 8) { - ut_asserteq_str("9223372036854775807", - simple_itoa((1UL << 63) - 1)); - ut_asserteq_str("18446744073709551615", simple_itoa(-1)); - } -#endif /* CONFIG_PHYS_64BIT */ - - return 0; -} -PRINT_TEST(print_itoa, 0); - static int snprint(struct unit_test_state *uts) { char buf[10] = "xxxxxxxxx"; @@ -391,26 +371,6 @@ static int snprint(struct unit_test_state *uts) } PRINT_TEST(snprint, 0); -static int print_xtoa(struct unit_test_state *uts) -{ - ut_asserteq_str("7f", simple_xtoa(127)); - ut_asserteq_str("00", simple_xtoa(0)); - ut_asserteq_str("7fffffff", simple_xtoa(0x7fffffff)); - ut_asserteq_str("ffffffff", simple_xtoa(0xffffffff)); - - /* Use #ifdef here to avoid a compiler warning on 32-bit machines */ -#ifdef CONFIG_PHYS_64BIT - if (sizeof(ulong) == 8) { - ut_asserteq_str("7fffffffffffffff", - simple_xtoa((1UL << 63) - 1)); - ut_asserteq_str("ffffffffffffffff", simple_xtoa(-1)); - } -#endif /* CONFIG_PHYS_64BIT */ - - return 0; -} -PRINT_TEST(print_xtoa, 0); - int do_ut_print(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { struct unit_test *tests = UNIT_TEST_SUITE_START(print_test); diff --git a/test/py/tests/bootstd/mmc1.img.xz b/test/py/tests/bootstd/mmc1.img.xz Binary files differnew file mode 100644 index 0000000..4e7f39b --- /dev/null +++ b/test/py/tests/bootstd/mmc1.img.xz diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 01c2b3f..35fb393 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -1,9 +1,102 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +import gzip +import os import os.path import pytest +import u_boot_utils + +def mkdir_cond(dirname): + """Create a directory if it doesn't already exist + + Args: + dirname: Name of directory to create + """ + if not os.path.exists(dirname): + os.mkdir(dirname) + +def setup_bootflow_image(u_boot_console): + """Create a 20MB disk image with a single FAT partition""" + cons = u_boot_console + fname = os.path.join(cons.config.source_dir, 'mmc1.img') + mnt = os.path.join(cons.config.persistent_data_dir, 'mnt') + mkdir_cond(mnt) + + u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname) + u_boot_utils.run_and_log(cons, 'sudo sfdisk %s' % fname, + stdin=b'type=c') + + loop = None + mounted = False + complete = False + try: + out = u_boot_utils.run_and_log(cons, + 'sudo losetup --show -f -P %s' % fname) + loop = out.strip() + fatpart = '%sp1' % loop + u_boot_utils.run_and_log(cons, 'sudo mkfs.vfat %s' % fatpart) + u_boot_utils.run_and_log( + cons, 'sudo mount -o loop %s %s -o uid=%d,gid=%d' % + (fatpart, mnt, os.getuid(), os.getgid())) + mounted = True + + vmlinux = 'vmlinuz-5.3.7-301.fc31.armv7hl' + initrd = 'initramfs-5.3.7-301.fc31.armv7hl.img' + dtbdir = 'dtb-5.3.7-301.fc31.armv7hl' + script = '''# extlinux.conf generated by appliance-creator +ui menu.c32 +menu autoboot Welcome to Fedora-Workstation-armhfp-31-1.9. Automatic boot in # second{,s}. Press a key for options. +menu title Fedora-Workstation-armhfp-31-1.9 Boot Options. +menu hidden +timeout 20 +totaltimeout 600 + +label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl) + kernel /%s + append ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB + fdtdir /%s/ + initrd /%s''' % (vmlinux, dtbdir, initrd) + ext = os.path.join(mnt, 'extlinux') + mkdir_cond(ext) + + with open(os.path.join(ext, 'extlinux.conf'), 'w') as fd: + print(script, file=fd) + + inf = os.path.join(cons.config.persistent_data_dir, 'inf') + with open(inf, 'wb') as fd: + fd.write(gzip.compress(b'vmlinux')) + u_boot_utils.run_and_log(cons, 'mkimage -f auto -d %s %s' % + (inf, os.path.join(mnt, vmlinux))) + + with open(os.path.join(mnt, initrd), 'w') as fd: + print('initrd', file=fd) + + mkdir_cond(os.path.join(mnt, dtbdir)) + + dtb_file = os.path.join(mnt, '%s/sandbox.dtb' % dtbdir) + u_boot_utils.run_and_log( + cons, 'dtc -o %s' % dtb_file, stdin=b'/dts-v1/; / {};') + complete = True + except ValueError as exc: + print('Falled to create image, failing back to prepared copy: %s', + str(exc)) + finally: + if mounted: + u_boot_utils.run_and_log(cons, 'sudo umount %s' % mnt) + if loop: + u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop) + + if not complete: + # Use a prepared image since we cannot create one + infname = os.path.join(cons.config.source_dir, + 'test/py/tests/bootstd/mmc1.img.xz') + u_boot_utils.run_and_log( + cons, + ['sh', '-c', 'xz -dc %s >%s' % (infname, fname)]) + + @pytest.mark.buildconfigspec('ut_dm') def test_ut_dm_init(u_boot_console): """Initialize data for ut dm tests.""" @@ -21,6 +114,16 @@ def test_ut_dm_init(u_boot_console): with open(fn, 'wb') as fh: fh.write(data) +@pytest.mark.buildconfigspec('cmd_bootflow') +def test_ut_dm_init_bootstd(u_boot_console): + """Initialise data for bootflow tests""" + + setup_bootflow_image(u_boot_console) + + # Restart so that the new mmc1.img is picked up + u_boot_console.restart_uboot() + + def test_ut(u_boot_console, ut_subtest): """Execute a "ut" subtest. diff --git a/test/str_ut.c b/test/str_ut.c index d2840d5..5a84434 100644 --- a/test/str_ut.c +++ b/test/str_ut.c @@ -202,6 +202,78 @@ static int str_dectoul(struct unit_test_state *uts) } STR_TEST(str_dectoul, 0); +static int str_itoa(struct unit_test_state *uts) +{ + ut_asserteq_str("123", simple_itoa(123)); + ut_asserteq_str("0", simple_itoa(0)); + ut_asserteq_str("2147483647", simple_itoa(0x7fffffff)); + ut_asserteq_str("4294967295", simple_itoa(0xffffffff)); + + /* Use #ifdef here to avoid a compiler warning on 32-bit machines */ +#ifdef CONFIG_PHYS_64BIT + if (sizeof(ulong) == 8) { + ut_asserteq_str("9223372036854775807", + simple_itoa((1UL << 63) - 1)); + ut_asserteq_str("18446744073709551615", simple_itoa(-1)); + } +#endif /* CONFIG_PHYS_64BIT */ + + return 0; +} +STR_TEST(str_itoa, 0); + +static int str_xtoa(struct unit_test_state *uts) +{ + ut_asserteq_str("7f", simple_xtoa(127)); + ut_asserteq_str("00", simple_xtoa(0)); + ut_asserteq_str("7fffffff", simple_xtoa(0x7fffffff)); + ut_asserteq_str("ffffffff", simple_xtoa(0xffffffff)); + + /* Use #ifdef here to avoid a compiler warning on 32-bit machines */ +#ifdef CONFIG_PHYS_64BIT + if (sizeof(ulong) == 8) { + ut_asserteq_str("7fffffffffffffff", + simple_xtoa((1UL << 63) - 1)); + ut_asserteq_str("ffffffffffffffff", simple_xtoa(-1)); + } +#endif /* CONFIG_PHYS_64BIT */ + + return 0; +} +STR_TEST(str_xtoa, 0); + +static int str_trailing(struct unit_test_state *uts) +{ + const char str1[] = "abc123def"; + const char str2[] = "abc123def456"; + const char *end; + + ut_asserteq(-1, trailing_strtol("")); + ut_asserteq(-1, trailing_strtol("123")); + ut_asserteq(123, trailing_strtol("abc123")); + ut_asserteq(4, trailing_strtol("12c4")); + ut_asserteq(-1, trailing_strtol("abd")); + ut_asserteq(-1, trailing_strtol("abc123def")); + + ut_asserteq(-1, trailing_strtoln(str1, NULL)); + ut_asserteq(123, trailing_strtoln(str1, str1 + 6)); + ut_asserteq(-1, trailing_strtoln(str1, str1 + 9)); + + ut_asserteq(3, trailing_strtol("a3")); + + ut_asserteq(123, trailing_strtoln_end(str1, str1 + 6, &end)); + ut_asserteq(3, end - str1); + + ut_asserteq(-1, trailing_strtoln_end(str1, str1 + 7, &end)); + ut_asserteq(7, end - str1); + + ut_asserteq(456, trailing_strtoln_end(str2, NULL, &end)); + ut_asserteq(9, end - str2); + + return 0; +} +STR_TEST(str_trailing, 0); + int do_ut_str(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { struct unit_test *tests = UNIT_TEST_SUITE_START(str_test); |