diff mbox series

[v3,22/31] bootstd: Add an implementation of EFI boot

Message ID 20220119014315.1938157-14-sjg@chromium.org
State RFC
Delegated to: Tom Rini
Headers show
Series Initial implementation of standard boot | expand

Commit Message

Simon Glass Jan. 19, 2022, 1:43 a.m. UTC
Add a bootmeth driver which handles EFI boot, using EFI_LOADER.

In effect, this provides the same functionality as the 'bootefi' command
and shares the same code. But the interface into it is via a bootmeth,
so it does not require any special scripts, etc.

For now this requires the 'bootefi' command be enabled. Future work may
tidy this up so that it can be used without CONFIG_CMDLINE being enabled.

There was much discussion about whether this is needed, but it seems
that it is, at least for now.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

Changes in v3:
- Add a log category
- Use a short name when BOOTSTD_FULL is not enabled
- Align the EFI load address
- Use common bootmeth functions

 boot/Kconfig        |  21 +++++
 boot/Makefile       |   1 +
 boot/bootmeth_efi.c | 183 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 205 insertions(+)
 create mode 100644 boot/bootmeth_efi.c

Comments

Michael Walle Jan. 19, 2022, 8:08 a.m. UTC | #1
> Add a bootmeth driver which handles EFI boot, using EFI_LOADER.
> 
> In effect, this provides the same functionality as the 'bootefi' command
> and shares the same code. But the interface into it is via a bootmeth,
> so it does not require any special scripts, etc.
> 
> For now this requires the 'bootefi' command be enabled. Future work may
> tidy this up so that it can be used without CONFIG_CMDLINE being enabled.
> 
> There was much discussion about whether this is needed, but it seems
> that it is, at least for now.

FWIW, the distro boot will also load the device tree from the installer
medium for example. The filename is fetched from fdtfile. This is missing
here, thus for my board, this cannot be a replacement for distro boot;
which is the ultimate goal here, no?

-michael
Heinrich Schuchardt Jan. 19, 2022, 11:45 a.m. UTC | #2
On 1/19/22 02:43, Simon Glass wrote:
> Add a bootmeth driver which handles EFI boot, using EFI_LOADER.
>
> In effect, this provides the same functionality as the 'bootefi' command
> and shares the same code. But the interface into it is via a bootmeth,
> so it does not require any special scripts, etc.
>
> For now this requires the 'bootefi' command be enabled. Future work may
> tidy this up so that it can be used without CONFIG_CMDLINE being enabled.
>
> There was much discussion about whether this is needed, but it seems
> that it is, at least for now.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
>
> Changes in v3:
> - Add a log category
> - Use a short name when BOOTSTD_FULL is not enabled
> - Align the EFI load address
> - Use common bootmeth functions
>
>   boot/Kconfig        |  21 +++++
>   boot/Makefile       |   1 +
>   boot/bootmeth_efi.c | 183 ++++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 205 insertions(+)
>   create mode 100644 boot/bootmeth_efi.c
>
> diff --git a/boot/Kconfig b/boot/Kconfig
> index 61df705141d..0fee35070b0 100644
> --- a/boot/Kconfig
> +++ b/boot/Kconfig
> @@ -338,6 +338,27 @@ config BOOTMETH_DISTRO_PXE
>
>   	  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.
> +
>   endif
>
>   config LEGACY_IMAGE_FORMAT
> diff --git a/boot/Makefile b/boot/Makefile
> index 170fcac8ec4..c2345435201 100644
> --- a/boot/Makefile
> +++ b/boot/Makefile
> @@ -30,6 +30,7 @@ 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_)OF_LIBFDT) += image-fdt.o
>   obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o
> diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
> new file mode 100644
> index 00000000000..4c0c3494c3b
> --- /dev/null
> +++ b/boot/bootmeth_efi.c
> @@ -0,0 +1,183 @@
> +// 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;
> +	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\n", dev_get_uclass_name(media_dev),
> +		  bflow->fname);
> +	efi_set_bootdev(dev_get_uclass_name(media_dev), 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 = dev_get_uclass_plat(bflow->blk);
> +	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);
> +
> +	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 a .efi file" : "EFI";

nits:
%s/a/an/

> +
> +	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,
> +};

Where is the device tree read?

Best regards

Heinrich

> +
> +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,
> +};
Simon Glass March 6, 2022, 3:08 a.m. UTC | #3
Hi Heinrich,

On Wed, 19 Jan 2022 at 04:45, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 1/19/22 02:43, Simon Glass wrote:
> > Add a bootmeth driver which handles EFI boot, using EFI_LOADER.
> >
> > In effect, this provides the same functionality as the 'bootefi' command
> > and shares the same code. But the interface into it is via a bootmeth,
> > so it does not require any special scripts, etc.
> >
> > For now this requires the 'bootefi' command be enabled. Future work may
> > tidy this up so that it can be used without CONFIG_CMDLINE being enabled.
> >
> > There was much discussion about whether this is needed, but it seems
> > that it is, at least for now.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> > Changes in v3:
> > - Add a log category
> > - Use a short name when BOOTSTD_FULL is not enabled
> > - Align the EFI load address
> > - Use common bootmeth functions
> >
> >   boot/Kconfig        |  21 +++++
> >   boot/Makefile       |   1 +
> >   boot/bootmeth_efi.c | 183 ++++++++++++++++++++++++++++++++++++++++++++
> >   3 files changed, 205 insertions(+)
> >   create mode 100644 boot/bootmeth_efi.c
> >
> > diff --git a/boot/Kconfig b/boot/Kconfig
> > index 61df705141d..0fee35070b0 100644
> > --- a/boot/Kconfig
> > +++ b/boot/Kconfig
> > @@ -338,6 +338,27 @@ config BOOTMETH_DISTRO_PXE
> >
> >         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.
> > +
> >   endif
> >
> >   config LEGACY_IMAGE_FORMAT
> > diff --git a/boot/Makefile b/boot/Makefile
> > index 170fcac8ec4..c2345435201 100644
> > --- a/boot/Makefile
> > +++ b/boot/Makefile
> > @@ -30,6 +30,7 @@ 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_)OF_LIBFDT) += image-fdt.o
> >   obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o
> > diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
> > new file mode 100644
> > index 00000000000..4c0c3494c3b
> > --- /dev/null
> > +++ b/boot/bootmeth_efi.c
> > @@ -0,0 +1,183 @@
> > +// 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;
> > +     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\n", dev_get_uclass_name(media_dev),
> > +               bflow->fname);
> > +     efi_set_bootdev(dev_get_uclass_name(media_dev), 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 = dev_get_uclass_plat(bflow->blk);
> > +     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);
> > +
> > +     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 a .efi file" : "EFI";
>
> nits:
> %s/a/an/
>
> > +
> > +     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,
> > +};
>
> Where is the device tree read?

I'm not sure...where is it read with the scripts?

Do you know a distro that uses that feature? So far I don't have one.

Of course there are no tests with the scripts so I am flying blind here.

Regards,
Simon
Michael Walle March 7, 2022, 9:03 a.m. UTC | #4
>On Wed, 19 Jan 2022 at 04:45, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 1/19/22 02:43, Simon Glass wrote:
>> > Add a bootmeth driver which handles EFI boot, using EFI_LOADER.
>> >
>> > In effect, this provides the same functionality as the 'bootefi' command
>> > and shares the same code. But the interface into it is via a bootmeth,
>> > so it does not require any special scripts, etc.
>> >
>> > For now this requires the 'bootefi' command be enabled. Future work may
>> > tidy this up so that it can be used without CONFIG_CMDLINE being enabled.
>> >
>> > There was much discussion about whether this is needed, but it seems
>> > that it is, at least for now.
>> >
>> > Signed-off-by: Simon Glass <sjg@chromium.org>
>> > ---
>> >
>> > Changes in v3:
>> > - Add a log category
>> > - Use a short name when BOOTSTD_FULL is not enabled
>> > - Align the EFI load address
>> > - Use common bootmeth functions
>> >
>> >   boot/Kconfig        |  21 +++++
>> >   boot/Makefile       |   1 +
>> >   boot/bootmeth_efi.c | 183 ++++++++++++++++++++++++++++++++++++++++++++
>> >   3 files changed, 205 insertions(+)
>> >   create mode 100644 boot/bootmeth_efi.c
>> >
>> > diff --git a/boot/Kconfig b/boot/Kconfig
>> > index 61df705141d..0fee35070b0 100644
>> > --- a/boot/Kconfig
>> > +++ b/boot/Kconfig
>> > @@ -338,6 +338,27 @@ config BOOTMETH_DISTRO_PXE
>> >
>> >         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.
>> > +
>> >   endif
>> >
>> >   config LEGACY_IMAGE_FORMAT
>> > diff --git a/boot/Makefile b/boot/Makefile
>> > index 170fcac8ec4..c2345435201 100644
>> > --- a/boot/Makefile
>> > +++ b/boot/Makefile
>> > @@ -30,6 +30,7 @@ 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_)OF_LIBFDT) += image-fdt.o
>> >   obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o
>> > diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
>> > new file mode 100644
>> > index 00000000000..4c0c3494c3b
>> > --- /dev/null
>> > +++ b/boot/bootmeth_efi.c
>> > @@ -0,0 +1,183 @@
>> > +// 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;
>> > +     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\n", dev_get_uclass_name(media_dev),
>> > +               bflow->fname);
>> > +     efi_set_bootdev(dev_get_uclass_name(media_dev), 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 = dev_get_uclass_plat(bflow->blk);
>> > +     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);
>> > +
>> > +     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 a .efi file" : "EFI";
>>
>> nits:
>> %s/a/an/
>>
>> > +
>> > +     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,
>> > +};
>>
>> Where is the device tree read?
>
> I'm not sure...where is it read with the scripts?
> 
> Do you know a distro that uses that feature? So far I don't have one.

At least the debian arm64 mini.iso should support it. See also
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991141

-michael
diff mbox series

Patch

diff --git a/boot/Kconfig b/boot/Kconfig
index 61df705141d..0fee35070b0 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -338,6 +338,27 @@  config BOOTMETH_DISTRO_PXE
 
 	  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.
+
 endif
 
 config LEGACY_IMAGE_FORMAT
diff --git a/boot/Makefile b/boot/Makefile
index 170fcac8ec4..c2345435201 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -30,6 +30,7 @@  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_)OF_LIBFDT) += image-fdt.o
 obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o
diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
new file mode 100644
index 00000000000..4c0c3494c3b
--- /dev/null
+++ b/boot/bootmeth_efi.c
@@ -0,0 +1,183 @@ 
+// 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;
+	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\n", dev_get_uclass_name(media_dev),
+		  bflow->fname);
+	efi_set_bootdev(dev_get_uclass_name(media_dev), 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 = dev_get_uclass_plat(bflow->blk);
+	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);
+
+	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 a .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,
+};