diff mbox series

[v3,6/7] tools: add genguid tool

Message ID 20240531-b4-dynamic-uuid-v3-6-ca4a4865db00@linaro.org
State New
Headers show
Series efi: CapsuleUpdate: support for dynamic UUIDs | expand

Commit Message

Caleb Connolly May 31, 2024, 1:50 p.m. UTC
Add a tool that can generate GUIDs that match those generated internally
by U-Boot for capsule update fw_images.

Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
with the board model, compatible, and fw_image name.

This tool accepts the same inputs and will produce the same GUID as
U-Boot would at runtime.

Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
---
 doc/genguid.1   |  52 +++++++++++++++++++
 tools/Kconfig   |   7 +++
 tools/Makefile  |   3 ++
 tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 216 insertions(+)

Comments

Simon Glass June 5, 2024, 2:13 a.m. UTC | #1
Hi Caleb,

On Fri, 31 May 2024 at 07:50, Caleb Connolly <caleb.connolly@linaro.org> wrote:
>
> Add a tool that can generate GUIDs that match those generated internally
> by U-Boot for capsule update fw_images.
>
> Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
> with the board model, compatible, and fw_image name.
>
> This tool accepts the same inputs and will produce the same GUID as
> U-Boot would at runtime.
>
> Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
> ---
>  doc/genguid.1   |  52 +++++++++++++++++++
>  tools/Kconfig   |   7 +++
>  tools/Makefile  |   3 ++
>  tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 216 insertions(+)

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

Can I suggest you look at patman, which will provide a per-patch change list?

Regards,
SImon
Heinrich Schuchardt June 5, 2024, 6:36 a.m. UTC | #2
On 5/31/24 15:50, Caleb Connolly wrote:
> Add a tool that can generate GUIDs that match those generated internally
> by U-Boot for capsule update fw_images.
>
> Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
> with the board model, compatible, and fw_image name.
>
> This tool accepts the same inputs and will produce the same GUID as
> U-Boot would at runtime.

This functionality should be integrated into the mkeficapsule.

Just pass the device-tree into mkeficapsule and generate the GUIDs
whereever they are needed.

Best regards

Heinrich

>
> Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
> ---
>   doc/genguid.1   |  52 +++++++++++++++++++
>   tools/Kconfig   |   7 +++
>   tools/Makefile  |   3 ++
>   tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 216 insertions(+)
>
> diff --git a/doc/genguid.1 b/doc/genguid.1
> new file mode 100644
> index 000000000000..4128055b3a9a
> --- /dev/null
> +++ b/doc/genguid.1
> @@ -0,0 +1,52 @@
> +.\" SPDX-License-Identifier: GPL-2.0+
> +.\" Copyright (c) 2024, Linaro Limited
> +.TH GENGUID 1 "May 2024"
> +
> +.SH NAME
> +genguid \- Generate deterministic EFI capsule image GUIDs for a board
> +
> +.SH SYNOPSIS
> +.B genguid
> +.RI GUID " " [ -vj ] " " -c " " COMPAT " " NAME...
> +
> +.SH "DESCRIPTION"
> +The
> +.B genguid
> +command is used to determine the update image GUIDs for a board using
> +dynamic UUIDs. The command takes a namespace GUID (defined in the boards
> +defconfig), the boards first compatible string, and the names of the
> +firmware images. The command will output the GUIDs for each image.
> +
> +As the dynamic UUID mechanism generates GUIDs at runtime, it would be
> +necessary to actually boot U-Boot on the board and enable debug logs
> +to retrieve the generated GUIDs. This tools just simplifies that process.
> +
> +.SH "OPTIONS"
> +
> +.TP
> +.BI GUID
> +The namespace/salt GUID, same as CONFIG_EFI_CAPSULE_NAMESPACE_UUID.
> +The format is:
> +    xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
> +
> +.TP
> +.BI "-v\fR,\fB --verbose "
> +Print additional information to stderr.
> +
> +.TP
> +.BI "-j\fR,\fB --json "
> +Output the results in JSON format (array of object with name/uuid properties).
> +
> +.TP
> +.BI "-c\fR,\fB --compat " COMPAT
> +The first entry in the boards root compatible property.
> +
> +.TP
> +.BI NAME...
> +The names of the firmware images to generate GUIDs for (e.g. "SANDBOX-UBOOT-ENV").
> +
> +.SH AUTHORS
> +Written by Caleb Connolly <caleb.connolly@linaro.org>
> +
> +.SH HOMEPAGE
> +https://u-boot.org
> diff --git a/tools/Kconfig b/tools/Kconfig
> index 667807b33173..13201ff61fd4 100644
> --- a/tools/Kconfig
> +++ b/tools/Kconfig
> @@ -103,8 +103,15 @@ config TOOLS_MKEFICAPSULE
>   	  This command allows users to create a UEFI capsule file and,
>   	  optionally sign that file. If you want to enable UEFI capsule
>   	  update feature on your target, you certainly need this.
>
> +config TOOLS_GENGUID
> +	bool "Build genguid command"
> +	default y if EFI_CAPSULE_DYNAMIC_UUIDS

Distros have a package u-boot-tools. You want this package to contain
all tools.

Please, ensure that the new tool is built by tools-only_defconfig.

> +	help
> +	  This command allows users to generate the GUIDs that a given
> +	  board would use for UEFI capsule update feature.
> +
>   menuconfig FSPI_CONF_HEADER
>   	bool "FlexSPI Header Configuration"
>   	help
>   	  FSPI Header Configuration
> diff --git a/tools/Makefile b/tools/Makefile
> index 6a4280e3668f..29e9a93b0f24 100644
> --- a/tools/Makefile
> +++ b/tools/Makefile
> @@ -253,8 +253,11 @@ HOSTLDLIBS_mkeficapsule += \
>   HOSTLDLIBS_mkeficapsule += \
>   	$(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid")
>   hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule
>
> +genguid-objs := generated/lib/uuid.o generated/lib/sha1.o genguid.o
> +hostprogs-$(CONFIG_TOOLS_GENGUID) += genguid
> +
>   mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o
>   HOSTLDLIBS_mkfwumdata += -luuid
>   hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
>
> diff --git a/tools/genguid.c b/tools/genguid.c
> new file mode 100644
> index 000000000000..e71bc1d48f95
> --- /dev/null
> +++ b/tools/genguid.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2024 Linaro Ltd.
> + *   Author: Caleb Connolly
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <linux/types.h>
> +
> +#include <uuid.h>
> +
> +static struct option options[] = {
> +	{"dtb", required_argument, NULL, 'd'},
> +	{"compat", required_argument, NULL, 'c'},
> +	{"help", no_argument, NULL, 'h'},
> +	{"verbose", no_argument, NULL, 'v'},
> +	{"json", no_argument, NULL, 'j'},
> +	{NULL, 0, NULL, 0},
> +};
> +
> +static void usage(const char *progname)
> +{
> +	fprintf(stderr, "Usage: %s GUID [-v] -c COMPAT NAME...\n", progname);
> +	fprintf(stderr,
> +		"Generate a v5 GUID for one of more U-Boot fw_images the same way U-Boot does at runtime.\n");
> +	fprintf(stderr,
> +		"\nOptions:\n"
> +		"  GUID                     namespace/salt GUID in 8-4-4-4-12 format\n"
> +		"  -h, --help               display this help and exit\n"
> +		"  -c, --compat=COMPAT      first compatible property in the board devicetree\n"

We don't need the first compatible string but the one in the root node.

> +		"  -v, --verbose            print debug messages\n"
> +		"  -j, --json               output in JSON format\n"
> +		"  NAME...                  one or more names of fw_images to generate GUIDs for\n"
> +	);
> +	fprintf(stderr, "\nExample:\n");
> +	fprintf(stderr, "  %s 2a5aa852-b856-4d97-baa9-5c5f4421551f \\\n"
> +			"\t-c \"qcom,qrb4210-rb2\" \\\n"
> +			"\tQUALCOMM-UBOOT\n", progname);
> +}
> +
> +static size_t u16_strsize(const uint16_t *in)
> +{
> +	size_t i = 0, count = UINT16_MAX;
> +
> +	while (count-- && in[i])
> +		i++;
> +
> +	return (i + 1) * sizeof(uint16_t);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct uuid namespace;
> +	char *namespace_str;
> +	char uuid_str[37];
> +	char **image_uuids;
> +	char *compatible = NULL;
> +	uint16_t **images_u16;
> +	char **images;
> +	int c, n_images;
> +	bool debug = false, json = false;
> +
> +	if (argc < 2) {
> +		usage(argv[0]);
> +		return 1;
> +	}
> +
> +	namespace_str = argv[1];
> +
> +	/* The first arg is the GUID so skip it */
> +	while ((c = getopt_long(argc, argv, "c:hvj", options, NULL)) != -1) {
> +		switch (c) {
> +		case 'c':
> +			compatible = strdup(optarg);
> +			break;
> +		case 'h':
> +			usage(argv[0]);
> +			return 0;
> +		case 'v':
> +			debug = true;
> +			break;
> +		case 'j':
> +			json = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +			return 1;
> +		}
> +	}
> +
> +	if (!compatible) {
> +		fprintf(stderr, "ERROR: Please specify the compatible property.\n\n");
> +		usage(argv[0]);
> +		return 1;
> +	}
> +
> +	if (uuid_str_to_bin(namespace_str, (unsigned char *)&namespace, UUID_STR_FORMAT_GUID)) {
> +		fprintf(stderr, "ERROR: Check that your UUID is formatted correctly.\n");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	/* This is probably not the best way to convert a string to a "u16" string */

Do you mean UTF-16?

Instead of writing "not the best way", please, describe the restrictions.

> +	n_images = argc - optind - 1;
> +	images = argv + optind + 1;
> +	images_u16 = calloc(n_images, sizeof(char *));

Please, check that the result in not NULL.

> +	for (int i = 0; i < n_images; i++) {
> +		images_u16[i] = calloc(1, strlen(images[i]) * 2 + 2);

ditto

> +		for (int j = 0; j < strlen(images[i]); j++)
> +			images_u16[i][j] = (uint16_t)images[i][j];

This is definitively not working for non-ASCII characters. You should
throw an error for non-ASCII or provide a conversion routine.

Best regards

Heinrich

> +	}
> +
> +	if (debug) {
> +		fprintf(stderr, "GUID:         ");
> +		uuid_bin_to_str((uint8_t *)&namespace, uuid_str, UUID_STR_FORMAT_GUID);
> +		fprintf(stderr, "%s\n", uuid_str);
> +		fprintf(stderr, "Compatible:  \"%s\"\n", compatible);
> +		fprintf(stderr, "Images:      ");
> +		for (int i = 0; i < n_images; i++)
> +			fprintf(stderr, "\"%s\"%s", argv[optind + i + 1],
> +				i == n_images - 1 ? "\n" : ", ");
> +	}
> +
> +	image_uuids = calloc(n_images, sizeof(char *));
> +	for (int i = 0; i < n_images; i++) {
> +		struct uuid image_type_id;
> +
> +		gen_uuid_v5(&namespace, &image_type_id,
> +			    compatible, strlen(compatible),
> +			    images_u16[i], u16_strsize(images_u16[i]) - sizeof(uint16_t),
> +			    NULL);
> +
> +		uuid_bin_to_str((uint8_t *)&image_type_id, uuid_str, UUID_STR_FORMAT_GUID);
> +		image_uuids[i] = strdup(uuid_str);
> +	}
> +
> +	if (json) {
> +		printf("[\n");
> +		for (int i = 0; i < n_images; i++)
> +			printf("\t{\"name\": \"%s\", \"uuid\": \"%s\"}%s\n", images[i], image_uuids[i],
> +			       i == n_images - 1 ? "" : ",");
> +		printf("]\n");
> +	} else {
> +		for (int i = 0; i < n_images; i++)
> +			printf("%-24s| %s\n", images[i], image_uuids[i]);
> +	}
> +
> +	return 0;
> +}
> +
>
Heinrich Schuchardt June 5, 2024, 6:38 a.m. UTC | #3
On 6/5/24 08:36, Heinrich Schuchardt wrote:
> On 5/31/24 15:50, Caleb Connolly wrote:
>> Add a tool that can generate GUIDs that match those generated internally
>> by U-Boot for capsule update fw_images.
>>
>> Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
>> with the board model, compatible, and fw_image name.
>>
>> This tool accepts the same inputs and will produce the same GUID as
>> U-Boot would at runtime.
>
> This functionality should be integrated into the mkeficapsule.
>
> Just pass the device-tree into mkeficapsule and generate the GUIDs
> whereever they are needed.
>
> Best regards
>
> Heinrich
>
>>
>> Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
>> ---
>>   doc/genguid.1   |  52 +++++++++++++++++++
>>   tools/Kconfig   |   7 +++
>>   tools/Makefile  |   3 ++
>>   tools/genguid.c | 154

A change to MAINTAINERS is missing.

Best regards

Heinrich

>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   4 files changed, 216 insertions(+)
>>
>> diff --git a/doc/genguid.1 b/doc/genguid.1
>> new file mode 100644
>> index 000000000000..4128055b3a9a
>> --- /dev/null
>> +++ b/doc/genguid.1
>> @@ -0,0 +1,52 @@
>> +.\" SPDX-License-Identifier: GPL-2.0+
>> +.\" Copyright (c) 2024, Linaro Limited
>> +.TH GENGUID 1 "May 2024"
>> +
>> +.SH NAME
>> +genguid \- Generate deterministic EFI capsule image GUIDs for a board
>> +
>> +.SH SYNOPSIS
>> +.B genguid
>> +.RI GUID " " [ -vj ] " " -c " " COMPAT " " NAME...
>> +
>> +.SH "DESCRIPTION"
>> +The
>> +.B genguid
>> +command is used to determine the update image GUIDs for a board using
>> +dynamic UUIDs. The command takes a namespace GUID (defined in the boards
>> +defconfig), the boards first compatible string, and the names of the
>> +firmware images. The command will output the GUIDs for each image.
>> +
>> +As the dynamic UUID mechanism generates GUIDs at runtime, it would be
>> +necessary to actually boot U-Boot on the board and enable debug logs
>> +to retrieve the generated GUIDs. This tools just simplifies that
>> process.
>> +
>> +.SH "OPTIONS"
>> +
>> +.TP
>> +.BI GUID
>> +The namespace/salt GUID, same as CONFIG_EFI_CAPSULE_NAMESPACE_UUID.
>> +The format is:
>> +    xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
>> +
>> +.TP
>> +.BI "-v\fR,\fB --verbose "
>> +Print additional information to stderr.
>> +
>> +.TP
>> +.BI "-j\fR,\fB --json "
>> +Output the results in JSON format (array of object with name/uuid
>> properties).
>> +
>> +.TP
>> +.BI "-c\fR,\fB --compat " COMPAT
>> +The first entry in the boards root compatible property.
>> +
>> +.TP
>> +.BI NAME...
>> +The names of the firmware images to generate GUIDs for (e.g.
>> "SANDBOX-UBOOT-ENV").
>> +
>> +.SH AUTHORS
>> +Written by Caleb Connolly <caleb.connolly@linaro.org>
>> +
>> +.SH HOMEPAGE
>> +https://u-boot.org
>> diff --git a/tools/Kconfig b/tools/Kconfig
>> index 667807b33173..13201ff61fd4 100644
>> --- a/tools/Kconfig
>> +++ b/tools/Kconfig
>> @@ -103,8 +103,15 @@ config TOOLS_MKEFICAPSULE
>>         This command allows users to create a UEFI capsule file and,
>>         optionally sign that file. If you want to enable UEFI capsule
>>         update feature on your target, you certainly need this.
>>
>> +config TOOLS_GENGUID
>> +    bool "Build genguid command"
>> +    default y if EFI_CAPSULE_DYNAMIC_UUIDS
>
> Distros have a package u-boot-tools. You want this package to contain
> all tools.
>
> Please, ensure that the new tool is built by tools-only_defconfig.
>
>> +    help
>> +      This command allows users to generate the GUIDs that a given
>> +      board would use for UEFI capsule update feature.
>> +
>>   menuconfig FSPI_CONF_HEADER
>>       bool "FlexSPI Header Configuration"
>>       help
>>         FSPI Header Configuration
>> diff --git a/tools/Makefile b/tools/Makefile
>> index 6a4280e3668f..29e9a93b0f24 100644
>> --- a/tools/Makefile
>> +++ b/tools/Makefile
>> @@ -253,8 +253,11 @@ HOSTLDLIBS_mkeficapsule += \
>>   HOSTLDLIBS_mkeficapsule += \
>>       $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid")
>>   hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule
>>
>> +genguid-objs := generated/lib/uuid.o generated/lib/sha1.o genguid.o
>> +hostprogs-$(CONFIG_TOOLS_GENGUID) += genguid
>> +
>>   mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o
>>   HOSTLDLIBS_mkfwumdata += -luuid
>>   hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
>>
>> diff --git a/tools/genguid.c b/tools/genguid.c
>> new file mode 100644
>> index 000000000000..e71bc1d48f95
>> --- /dev/null
>> +++ b/tools/genguid.c
>> @@ -0,0 +1,154 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2024 Linaro Ltd.
>> + *   Author: Caleb Connolly
>> + */
>> +
>> +#include <getopt.h>
>> +#include <stdbool.h>
>> +#include <stdint.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <linux/types.h>
>> +
>> +#include <uuid.h>
>> +
>> +static struct option options[] = {
>> +    {"dtb", required_argument, NULL, 'd'},
>> +    {"compat", required_argument, NULL, 'c'},
>> +    {"help", no_argument, NULL, 'h'},
>> +    {"verbose", no_argument, NULL, 'v'},
>> +    {"json", no_argument, NULL, 'j'},
>> +    {NULL, 0, NULL, 0},
>> +};
>> +
>> +static void usage(const char *progname)
>> +{
>> +    fprintf(stderr, "Usage: %s GUID [-v] -c COMPAT NAME...\n",
>> progname);
>> +    fprintf(stderr,
>> +        "Generate a v5 GUID for one of more U-Boot fw_images the same
>> way U-Boot does at runtime.\n");
>> +    fprintf(stderr,
>> +        "\nOptions:\n"
>> +        "  GUID                     namespace/salt GUID in 8-4-4-4-12
>> format\n"
>> +        "  -h, --help               display this help and exit\n"
>> +        "  -c, --compat=COMPAT      first compatible property in the
>> board devicetree\n"
>
> We don't need the first compatible string but the one in the root node.
>
>> +        "  -v, --verbose            print debug messages\n"
>> +        "  -j, --json               output in JSON format\n"
>> +        "  NAME...                  one or more names of fw_images to
>> generate GUIDs for\n"
>> +    );
>> +    fprintf(stderr, "\nExample:\n");
>> +    fprintf(stderr, "  %s 2a5aa852-b856-4d97-baa9-5c5f4421551f \\\n"
>> +            "\t-c \"qcom,qrb4210-rb2\" \\\n"
>> +            "\tQUALCOMM-UBOOT\n", progname);
>> +}
>> +
>> +static size_t u16_strsize(const uint16_t *in)
>> +{
>> +    size_t i = 0, count = UINT16_MAX;
>> +
>> +    while (count-- && in[i])
>> +        i++;
>> +
>> +    return (i + 1) * sizeof(uint16_t);
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> +    struct uuid namespace;
>> +    char *namespace_str;
>> +    char uuid_str[37];
>> +    char **image_uuids;
>> +    char *compatible = NULL;
>> +    uint16_t **images_u16;
>> +    char **images;
>> +    int c, n_images;
>> +    bool debug = false, json = false;
>> +
>> +    if (argc < 2) {
>> +        usage(argv[0]);
>> +        return 1;
>> +    }
>> +
>> +    namespace_str = argv[1];
>> +
>> +    /* The first arg is the GUID so skip it */
>> +    while ((c = getopt_long(argc, argv, "c:hvj", options, NULL)) !=
>> -1) {
>> +        switch (c) {
>> +        case 'c':
>> +            compatible = strdup(optarg);
>> +            break;
>> +        case 'h':
>> +            usage(argv[0]);
>> +            return 0;
>> +        case 'v':
>> +            debug = true;
>> +            break;
>> +        case 'j':
>> +            json = true;
>> +            break;
>> +        default:
>> +            usage(argv[0]);
>> +            return 1;
>> +        }
>> +    }
>> +
>> +    if (!compatible) {
>> +        fprintf(stderr, "ERROR: Please specify the compatible
>> property.\n\n");
>> +        usage(argv[0]);
>> +        return 1;
>> +    }
>> +
>> +    if (uuid_str_to_bin(namespace_str, (unsigned char *)&namespace,
>> UUID_STR_FORMAT_GUID)) {
>> +        fprintf(stderr, "ERROR: Check that your UUID is formatted
>> correctly.\n");
>> +        exit(EXIT_FAILURE);
>> +    }
>> +
>> +    /* This is probably not the best way to convert a string to a
>> "u16" string */
>
> Do you mean UTF-16?
>
> Instead of writing "not the best way", please, describe the restrictions.
>
>> +    n_images = argc - optind - 1;
>> +    images = argv + optind + 1;
>> +    images_u16 = calloc(n_images, sizeof(char *));
>
> Please, check that the result in not NULL.
>
>> +    for (int i = 0; i < n_images; i++) {
>> +        images_u16[i] = calloc(1, strlen(images[i]) * 2 + 2);
>
> ditto
>
>> +        for (int j = 0; j < strlen(images[i]); j++)
>> +            images_u16[i][j] = (uint16_t)images[i][j];
>
> This is definitively not working for non-ASCII characters. You should
> throw an error for non-ASCII or provide a conversion routine.
>
> Best regards
>
> Heinrich
>
>> +    }
>> +
>> +    if (debug) {
>> +        fprintf(stderr, "GUID:         ");
>> +        uuid_bin_to_str((uint8_t *)&namespace, uuid_str,
>> UUID_STR_FORMAT_GUID);
>> +        fprintf(stderr, "%s\n", uuid_str);
>> +        fprintf(stderr, "Compatible:  \"%s\"\n", compatible);
>> +        fprintf(stderr, "Images:      ");
>> +        for (int i = 0; i < n_images; i++)
>> +            fprintf(stderr, "\"%s\"%s", argv[optind + i + 1],
>> +                i == n_images - 1 ? "\n" : ", ");
>> +    }
>> +
>> +    image_uuids = calloc(n_images, sizeof(char *));
>> +    for (int i = 0; i < n_images; i++) {
>> +        struct uuid image_type_id;
>> +
>> +        gen_uuid_v5(&namespace, &image_type_id,
>> +                compatible, strlen(compatible),
>> +                images_u16[i], u16_strsize(images_u16[i]) -
>> sizeof(uint16_t),
>> +                NULL);
>> +
>> +        uuid_bin_to_str((uint8_t *)&image_type_id, uuid_str,
>> UUID_STR_FORMAT_GUID);
>> +        image_uuids[i] = strdup(uuid_str);
>> +    }
>> +
>> +    if (json) {
>> +        printf("[\n");
>> +        for (int i = 0; i < n_images; i++)
>> +            printf("\t{\"name\": \"%s\", \"uuid\": \"%s\"}%s\n",
>> images[i], image_uuids[i],
>> +                   i == n_images - 1 ? "" : ",");
>> +        printf("]\n");
>> +    } else {
>> +        for (int i = 0; i < n_images; i++)
>> +            printf("%-24s| %s\n", images[i], image_uuids[i]);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>>
>
Ilias Apalodimas June 5, 2024, 9:25 a.m. UTC | #4
Hi Heinrich

On Wed, 5 Jun 2024 at 09:36, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>
> On 5/31/24 15:50, Caleb Connolly wrote:
> > Add a tool that can generate GUIDs that match those generated internally
> > by U-Boot for capsule update fw_images.
> >
> > Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
> > with the board model, compatible, and fw_image name.
> >
> > This tool accepts the same inputs and will produce the same GUID as
> > U-Boot would at runtime.
>
> This functionality should be integrated into the mkeficapsule.
>
> Just pass the device-tree into mkeficapsule and generate the GUIDs
> whereever they are needed.

I like the idea of moving this mkeficapsule which is already part of
distro packages but,  isn't it easier to pass the compatible string
node and the namespace?

Thanks
/Ilias
>
> Best regards
>
> Heinrich

[...]

Thanks
/Ilias
Caleb Connolly June 5, 2024, 12:29 p.m. UTC | #5
On 05/06/2024 11:25, Ilias Apalodimas wrote:
> Hi Heinrich
> 
> On Wed, 5 Jun 2024 at 09:36, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
>>
>> On 5/31/24 15:50, Caleb Connolly wrote:
>>> Add a tool that can generate GUIDs that match those generated internally
>>> by U-Boot for capsule update fw_images.
>>>
>>> Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
>>> with the board model, compatible, and fw_image name.
>>>
>>> This tool accepts the same inputs and will produce the same GUID as
>>> U-Boot would at runtime.
>>
>> This functionality should be integrated into the mkeficapsule.
>>
>> Just pass the device-tree into mkeficapsule and generate the GUIDs
>> whereever they are needed.
> 
> I like the idea of moving this mkeficapsule which is already part of
> distro packages but,  isn't it easier to pass the compatible string
> node and the namespace?

Yeah, we only need the first entry in the compatible property, so 
parsing the whole DT is a bit overkill.

I can look at combining these tools, can mkeficapsule generate a capsule 
for multiple images?

Would we want it to work such that you provide the namespace GUID, 
compatible, and image names, and have it generate a capsule with the 
right image GUIDs?

Or would it be simple enough to just incorporate the functionality of 
genguid?

Kind regards,
> 
> Thanks
> /Ilias
>>
>> Best regards
>>
>> Heinrich
> 
> [...]
> 
> Thanks
> /Ilias
Ilias Apalodimas June 5, 2024, 1:43 p.m. UTC | #6
+ CC Sughosh

On Wed, 5 Jun 2024 at 15:29, Caleb Connolly <caleb.connolly@linaro.org> wrote:
>
>
>
> On 05/06/2024 11:25, Ilias Apalodimas wrote:
> > Hi Heinrich
> >
> > On Wed, 5 Jun 2024 at 09:36, Heinrich Schuchardt <xypron.glpk@gmx.de> wrote:
> >>
> >> On 5/31/24 15:50, Caleb Connolly wrote:
> >>> Add a tool that can generate GUIDs that match those generated internally
> >>> by U-Boot for capsule update fw_images.
> >>>
> >>> Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
> >>> with the board model, compatible, and fw_image name.
> >>>
> >>> This tool accepts the same inputs and will produce the same GUID as
> >>> U-Boot would at runtime.
> >>
> >> This functionality should be integrated into the mkeficapsule.
> >>
> >> Just pass the device-tree into mkeficapsule and generate the GUIDs
> >> whereever they are needed.
> >
> > I like the idea of moving this mkeficapsule which is already part of
> > distro packages but,  isn't it easier to pass the compatible string
> > node and the namespace?
>
> Yeah, we only need the first entry in the compatible property, so
> parsing the whole DT is a bit overkill.
>
> I can look at combining these tools, can mkeficapsule generate a capsule
> for multiple images?

Not yet. There's [0] which adds that functionality but we are
discussing parsing a .yaml file instead of a custom format

>
> Would we want it to work such that you provide the namespace GUID,
> compatible, and image names, and have it generate a capsule with the
> right image GUIDs?
>
> Or would it be simple enough to just incorporate the functionality of
> genguid?
>

I'd argue for both since people might choose other tools to generate a capsule.
So you want 2 flags ideally (random flags)
-x: dump the autogenerated GUIDs
-y use the autogenerated GUIDs on capsule generation

Since mkeficapsule can't generate a capsule with multiple payloads
yet, you will be limited to just one, but I guess it's easy to expand
it once [0] is merged


[0] https://lore.kernel.org/u-boot/20240419065542.1160527-1-sughosh.ganu@linaro.org/

Thanks
/Ilias
Vincent Stehlé June 20, 2024, 2:58 p.m. UTC | #7
On Fri, May 31, 2024 at 03:50:40PM +0200, Caleb Connolly wrote:
> Add a tool that can generate GUIDs that match those generated internally
> by U-Boot for capsule update fw_images.

Hi Caleb,

Thanks for working on this.
I think there is a leftover "dtb" option; see below.

Best regards,
Vincent.

> 
> Dynamic UUIDs in U-Boot work by taking a namespace UUID and hashing it
> with the board model, compatible, and fw_image name.
> 
> This tool accepts the same inputs and will produce the same GUID as
> U-Boot would at runtime.
> 
> Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
> ---
>  doc/genguid.1   |  52 +++++++++++++++++++
>  tools/Kconfig   |   7 +++
>  tools/Makefile  |   3 ++
>  tools/genguid.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 216 insertions(+)

(...)

> diff --git a/tools/genguid.c b/tools/genguid.c
> new file mode 100644
> index 000000000000..e71bc1d48f95
> --- /dev/null
> +++ b/tools/genguid.c
> @@ -0,0 +1,154 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2024 Linaro Ltd.
> + *   Author: Caleb Connolly
> + */
> +
> +#include <getopt.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <linux/types.h>
> +
> +#include <uuid.h>
> +
> +static struct option options[] = {
> +	{"dtb", required_argument, NULL, 'd'},

I think this is unused.

> +	{"compat", required_argument, NULL, 'c'},
> +	{"help", no_argument, NULL, 'h'},
> +	{"verbose", no_argument, NULL, 'v'},
> +	{"json", no_argument, NULL, 'j'},
> +	{NULL, 0, NULL, 0},
> +};
> +
> +static void usage(const char *progname)
> +{
> +	fprintf(stderr, "Usage: %s GUID [-v] -c COMPAT NAME...\n", progname);
> +	fprintf(stderr,
> +		"Generate a v5 GUID for one of more U-Boot fw_images the same way U-Boot does at runtime.\n");
> +	fprintf(stderr,
> +		"\nOptions:\n"
> +		"  GUID                     namespace/salt GUID in 8-4-4-4-12 format\n"
> +		"  -h, --help               display this help and exit\n"
> +		"  -c, --compat=COMPAT      first compatible property in the board devicetree\n"
> +		"  -v, --verbose            print debug messages\n"
> +		"  -j, --json               output in JSON format\n"
> +		"  NAME...                  one or more names of fw_images to generate GUIDs for\n"
> +	);
> +	fprintf(stderr, "\nExample:\n");
> +	fprintf(stderr, "  %s 2a5aa852-b856-4d97-baa9-5c5f4421551f \\\n"
> +			"\t-c \"qcom,qrb4210-rb2\" \\\n"
> +			"\tQUALCOMM-UBOOT\n", progname);
> +}
> +
> +static size_t u16_strsize(const uint16_t *in)
> +{
> +	size_t i = 0, count = UINT16_MAX;
> +
> +	while (count-- && in[i])
> +		i++;
> +
> +	return (i + 1) * sizeof(uint16_t);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	struct uuid namespace;
> +	char *namespace_str;
> +	char uuid_str[37];
> +	char **image_uuids;
> +	char *compatible = NULL;
> +	uint16_t **images_u16;
> +	char **images;
> +	int c, n_images;
> +	bool debug = false, json = false;
> +
> +	if (argc < 2) {
> +		usage(argv[0]);
> +		return 1;
> +	}
> +
> +	namespace_str = argv[1];
> +
> +	/* The first arg is the GUID so skip it */
> +	while ((c = getopt_long(argc, argv, "c:hvj", options, NULL)) != -1) {
> +		switch (c) {
> +		case 'c':
> +			compatible = strdup(optarg);
> +			break;
> +		case 'h':
> +			usage(argv[0]);
> +			return 0;
> +		case 'v':
> +			debug = true;
> +			break;
> +		case 'j':
> +			json = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +			return 1;
> +		}
> +	}
> +
> +	if (!compatible) {
> +		fprintf(stderr, "ERROR: Please specify the compatible property.\n\n");
> +		usage(argv[0]);
> +		return 1;
> +	}
> +
> +	if (uuid_str_to_bin(namespace_str, (unsigned char *)&namespace, UUID_STR_FORMAT_GUID)) {
> +		fprintf(stderr, "ERROR: Check that your UUID is formatted correctly.\n");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	/* This is probably not the best way to convert a string to a "u16" string */
> +	n_images = argc - optind - 1;
> +	images = argv + optind + 1;
> +	images_u16 = calloc(n_images, sizeof(char *));
> +	for (int i = 0; i < n_images; i++) {
> +		images_u16[i] = calloc(1, strlen(images[i]) * 2 + 2);
> +		for (int j = 0; j < strlen(images[i]); j++)
> +			images_u16[i][j] = (uint16_t)images[i][j];
> +	}
> +
> +	if (debug) {
> +		fprintf(stderr, "GUID:         ");
> +		uuid_bin_to_str((uint8_t *)&namespace, uuid_str, UUID_STR_FORMAT_GUID);
> +		fprintf(stderr, "%s\n", uuid_str);
> +		fprintf(stderr, "Compatible:  \"%s\"\n", compatible);
> +		fprintf(stderr, "Images:      ");
> +		for (int i = 0; i < n_images; i++)
> +			fprintf(stderr, "\"%s\"%s", argv[optind + i + 1],
> +				i == n_images - 1 ? "\n" : ", ");
> +	}
> +
> +	image_uuids = calloc(n_images, sizeof(char *));
> +	for (int i = 0; i < n_images; i++) {
> +		struct uuid image_type_id;
> +
> +		gen_uuid_v5(&namespace, &image_type_id,
> +			    compatible, strlen(compatible),
> +			    images_u16[i], u16_strsize(images_u16[i]) - sizeof(uint16_t),
> +			    NULL);
> +
> +		uuid_bin_to_str((uint8_t *)&image_type_id, uuid_str, UUID_STR_FORMAT_GUID);
> +		image_uuids[i] = strdup(uuid_str);
> +	}
> +
> +	if (json) {
> +		printf("[\n");
> +		for (int i = 0; i < n_images; i++)
> +			printf("\t{\"name\": \"%s\", \"uuid\": \"%s\"}%s\n", images[i], image_uuids[i],
> +			       i == n_images - 1 ? "" : ",");
> +		printf("]\n");
> +	} else {
> +		for (int i = 0; i < n_images; i++)
> +			printf("%-24s| %s\n", images[i], image_uuids[i]);
> +	}
> +
> +	return 0;
> +}
> +
> 
> -- 
> 2.45.0
>
diff mbox series

Patch

diff --git a/doc/genguid.1 b/doc/genguid.1
new file mode 100644
index 000000000000..4128055b3a9a
--- /dev/null
+++ b/doc/genguid.1
@@ -0,0 +1,52 @@ 
+.\" SPDX-License-Identifier: GPL-2.0+
+.\" Copyright (c) 2024, Linaro Limited
+.TH GENGUID 1 "May 2024"
+
+.SH NAME
+genguid \- Generate deterministic EFI capsule image GUIDs for a board
+
+.SH SYNOPSIS
+.B genguid
+.RI GUID " " [ -vj ] " " -c " " COMPAT " " NAME...
+
+.SH "DESCRIPTION"
+The
+.B genguid
+command is used to determine the update image GUIDs for a board using
+dynamic UUIDs. The command takes a namespace GUID (defined in the boards
+defconfig), the boards first compatible string, and the names of the
+firmware images. The command will output the GUIDs for each image.
+
+As the dynamic UUID mechanism generates GUIDs at runtime, it would be
+necessary to actually boot U-Boot on the board and enable debug logs
+to retrieve the generated GUIDs. This tools just simplifies that process.
+
+.SH "OPTIONS"
+
+.TP
+.BI GUID
+The namespace/salt GUID, same as CONFIG_EFI_CAPSULE_NAMESPACE_UUID.
+The format is:
+    xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+
+.TP
+.BI "-v\fR,\fB --verbose "
+Print additional information to stderr.
+
+.TP
+.BI "-j\fR,\fB --json "
+Output the results in JSON format (array of object with name/uuid properties).
+
+.TP
+.BI "-c\fR,\fB --compat " COMPAT
+The first entry in the boards root compatible property.
+
+.TP
+.BI NAME...
+The names of the firmware images to generate GUIDs for (e.g. "SANDBOX-UBOOT-ENV").
+
+.SH AUTHORS
+Written by Caleb Connolly <caleb.connolly@linaro.org>
+
+.SH HOMEPAGE
+https://u-boot.org
diff --git a/tools/Kconfig b/tools/Kconfig
index 667807b33173..13201ff61fd4 100644
--- a/tools/Kconfig
+++ b/tools/Kconfig
@@ -103,8 +103,15 @@  config TOOLS_MKEFICAPSULE
 	  This command allows users to create a UEFI capsule file and,
 	  optionally sign that file. If you want to enable UEFI capsule
 	  update feature on your target, you certainly need this.
 
+config TOOLS_GENGUID
+	bool "Build genguid command"
+	default y if EFI_CAPSULE_DYNAMIC_UUIDS
+	help
+	  This command allows users to generate the GUIDs that a given
+	  board would use for UEFI capsule update feature.
+
 menuconfig FSPI_CONF_HEADER
 	bool "FlexSPI Header Configuration"
 	help
 	  FSPI Header Configuration
diff --git a/tools/Makefile b/tools/Makefile
index 6a4280e3668f..29e9a93b0f24 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -253,8 +253,11 @@  HOSTLDLIBS_mkeficapsule += \
 HOSTLDLIBS_mkeficapsule += \
 	$(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid")
 hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule
 
+genguid-objs := generated/lib/uuid.o generated/lib/sha1.o genguid.o
+hostprogs-$(CONFIG_TOOLS_GENGUID) += genguid
+
 mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o
 HOSTLDLIBS_mkfwumdata += -luuid
 hostprogs-$(CONFIG_TOOLS_MKFWUMDATA) += mkfwumdata
 
diff --git a/tools/genguid.c b/tools/genguid.c
new file mode 100644
index 000000000000..e71bc1d48f95
--- /dev/null
+++ b/tools/genguid.c
@@ -0,0 +1,154 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024 Linaro Ltd.
+ *   Author: Caleb Connolly
+ */
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/types.h>
+
+#include <uuid.h>
+
+static struct option options[] = {
+	{"dtb", required_argument, NULL, 'd'},
+	{"compat", required_argument, NULL, 'c'},
+	{"help", no_argument, NULL, 'h'},
+	{"verbose", no_argument, NULL, 'v'},
+	{"json", no_argument, NULL, 'j'},
+	{NULL, 0, NULL, 0},
+};
+
+static void usage(const char *progname)
+{
+	fprintf(stderr, "Usage: %s GUID [-v] -c COMPAT NAME...\n", progname);
+	fprintf(stderr,
+		"Generate a v5 GUID for one of more U-Boot fw_images the same way U-Boot does at runtime.\n");
+	fprintf(stderr,
+		"\nOptions:\n"
+		"  GUID                     namespace/salt GUID in 8-4-4-4-12 format\n"
+		"  -h, --help               display this help and exit\n"
+		"  -c, --compat=COMPAT      first compatible property in the board devicetree\n"
+		"  -v, --verbose            print debug messages\n"
+		"  -j, --json               output in JSON format\n"
+		"  NAME...                  one or more names of fw_images to generate GUIDs for\n"
+	);
+	fprintf(stderr, "\nExample:\n");
+	fprintf(stderr, "  %s 2a5aa852-b856-4d97-baa9-5c5f4421551f \\\n"
+			"\t-c \"qcom,qrb4210-rb2\" \\\n"
+			"\tQUALCOMM-UBOOT\n", progname);
+}
+
+static size_t u16_strsize(const uint16_t *in)
+{
+	size_t i = 0, count = UINT16_MAX;
+
+	while (count-- && in[i])
+		i++;
+
+	return (i + 1) * sizeof(uint16_t);
+}
+
+int main(int argc, char **argv)
+{
+	struct uuid namespace;
+	char *namespace_str;
+	char uuid_str[37];
+	char **image_uuids;
+	char *compatible = NULL;
+	uint16_t **images_u16;
+	char **images;
+	int c, n_images;
+	bool debug = false, json = false;
+
+	if (argc < 2) {
+		usage(argv[0]);
+		return 1;
+	}
+
+	namespace_str = argv[1];
+
+	/* The first arg is the GUID so skip it */
+	while ((c = getopt_long(argc, argv, "c:hvj", options, NULL)) != -1) {
+		switch (c) {
+		case 'c':
+			compatible = strdup(optarg);
+			break;
+		case 'h':
+			usage(argv[0]);
+			return 0;
+		case 'v':
+			debug = true;
+			break;
+		case 'j':
+			json = true;
+			break;
+		default:
+			usage(argv[0]);
+			return 1;
+		}
+	}
+
+	if (!compatible) {
+		fprintf(stderr, "ERROR: Please specify the compatible property.\n\n");
+		usage(argv[0]);
+		return 1;
+	}
+
+	if (uuid_str_to_bin(namespace_str, (unsigned char *)&namespace, UUID_STR_FORMAT_GUID)) {
+		fprintf(stderr, "ERROR: Check that your UUID is formatted correctly.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* This is probably not the best way to convert a string to a "u16" string */
+	n_images = argc - optind - 1;
+	images = argv + optind + 1;
+	images_u16 = calloc(n_images, sizeof(char *));
+	for (int i = 0; i < n_images; i++) {
+		images_u16[i] = calloc(1, strlen(images[i]) * 2 + 2);
+		for (int j = 0; j < strlen(images[i]); j++)
+			images_u16[i][j] = (uint16_t)images[i][j];
+	}
+
+	if (debug) {
+		fprintf(stderr, "GUID:         ");
+		uuid_bin_to_str((uint8_t *)&namespace, uuid_str, UUID_STR_FORMAT_GUID);
+		fprintf(stderr, "%s\n", uuid_str);
+		fprintf(stderr, "Compatible:  \"%s\"\n", compatible);
+		fprintf(stderr, "Images:      ");
+		for (int i = 0; i < n_images; i++)
+			fprintf(stderr, "\"%s\"%s", argv[optind + i + 1],
+				i == n_images - 1 ? "\n" : ", ");
+	}
+
+	image_uuids = calloc(n_images, sizeof(char *));
+	for (int i = 0; i < n_images; i++) {
+		struct uuid image_type_id;
+
+		gen_uuid_v5(&namespace, &image_type_id,
+			    compatible, strlen(compatible),
+			    images_u16[i], u16_strsize(images_u16[i]) - sizeof(uint16_t),
+			    NULL);
+
+		uuid_bin_to_str((uint8_t *)&image_type_id, uuid_str, UUID_STR_FORMAT_GUID);
+		image_uuids[i] = strdup(uuid_str);
+	}
+
+	if (json) {
+		printf("[\n");
+		for (int i = 0; i < n_images; i++)
+			printf("\t{\"name\": \"%s\", \"uuid\": \"%s\"}%s\n", images[i], image_uuids[i],
+			       i == n_images - 1 ? "" : ",");
+		printf("]\n");
+	} else {
+		for (int i = 0; i < n_images; i++)
+			printf("%-24s| %s\n", images[i], image_uuids[i]);
+	}
+
+	return 0;
+}
+