diff mbox series

bootloader: support for NVIDIA cboot - bootinfo

Message ID 20230714064652.13819-1-sbabic@denx.de
State Accepted
Delegated to: Stefano Babic
Headers show
Series bootloader: support for NVIDIA cboot - bootinfo | expand

Commit Message

Stefano Babic July 14, 2023, 6:46 a.m. UTC
Some NVIDIA Tegra SOCs do not support U-Boot. NVIDIA has developed an
own bootloader as replacement for U-Boot. cboot (it should be patched)
can have access to the "bootinfo" database, that is stored on dependency
of the SOC type on fixed memory. The tegra-boot-tools provide a
convenient library to access programmatically to the environment. The
environment is redundant like in U-Boot, and this gurantees to have a
power-cut safe update of the variables.

This patch intoduce Tegra's "bootinfo" as bootloader interface, and it
depends on the libtegra-boot-tools library.

Signed-off-by: Stefano Babic <sbabic@denx.de>
---
 Kconfig              |   4 +
 Makefile.deps        |   4 +
 Makefile.flags       |   8 ++
 bootloader/Config.in |  19 +++++
 bootloader/Makefile  |   1 +
 bootloader/cboot.c   | 197 +++++++++++++++++++++++++++++++++++++++++++
 include/bootloader.h |   1 +
 7 files changed, 234 insertions(+)
 create mode 100644 bootloader/cboot.c
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 0bf3ae1d..19439b4f 100644
--- a/Kconfig
+++ b/Kconfig
@@ -61,6 +61,10 @@  config HAVE_LIBEBGENV
        bool
        option env="HAVE_LIBEBGENV"
 
+config HAVE_LIBTEGRABOOT_TOOLS
+       bool
+       option env="HAVE_LIBTEGRABOOT_TOOLS"
+
 config HAVE_LIBEXT2FS
 	bool
 	option env="HAVE_LIBEXT2FS"
diff --git a/Makefile.deps b/Makefile.deps
index 56028c50..397e08c4 100644
--- a/Makefile.deps
+++ b/Makefile.deps
@@ -50,6 +50,10 @@  ifeq ($(HAVE_LIBEBGENV),)
 export HAVE_LIBEBGENV = y
 endif
 
+ifeq ($(HAVE_LIBTEGRABOOT_TOOLS),)
+export HAVE_LIBTEGRABOOT_TOOLS = y
+endif
+
 ifeq ($(HAVE_LIBZEROMQ),)
 export HAVE_LIBZEROMQ = y
 endif
diff --git a/Makefile.flags b/Makefile.flags
index 2a021c89..2d27a8f8 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -225,6 +225,9 @@  endif
 ifeq ($(CONFIG_BOOTLOADER_EBG),y)
 LDLIBS += ebgenv
 endif
+ifeq ($(CONFIG_BOOTLOADER_CBOOT),y)
+LDLIBS += tegra-boot-tools
+endif
 else
 ifeq ($(CONFIG_UBOOT),y)
 LDLIBS += dl
@@ -232,6 +235,9 @@  endif
 ifeq ($(CONFIG_BOOTLOADER_EBG),y)
 LDLIBS += dl
 endif
+ifeq ($(CONFIG_BOOTLOADER_CBOOT),y)
+LDLIBS += dl
+endif
 endif
 
 ifeq ($(CONFIG_SYSTEMD),y)
@@ -246,6 +252,8 @@  else ifeq ($(CONFIG_BOOTLOADER_DEFAULT_UBOOT),y)
 KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="uboot"
 else ifeq ($(CONFIG_BOOTLOADER_DEFAULT_EBG),y)
 KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="ebg"
+else ifeq ($(CONFIG_BOOTLOADER_DEFAULT_CBOOT),y)
+KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="cboot"
 else
 KBUILD_CPPFLAGS += -DBOOTLOADER_DEFAULT="none"
 endif
diff --git a/bootloader/Config.in b/bootloader/Config.in
index 6a6cc3c0..6d07146a 100644
--- a/bootloader/Config.in
+++ b/bootloader/Config.in
@@ -63,6 +63,19 @@  config GRUBENV_PATH
 	help
 	  Provide path to GRUB environment block file
 
+config BOOTLOADER_CBOOT
+	bool "NVIDIA Tegra Cboot"
+	depends on HAVE_LIBTEGRABOOT_TOOLS
+	help
+	  Support for NVIDIA's cboot used on some Tegra platform. Tegra
+	  uses different bootloader, depending on the SOC and on the release
+	  provided by vendor. Some SOCs (associated with some releases) are still
+	  using U-Boot (Jetson Nano, TX, TX2), some of them are using Cboot (Xavier),
+	  and some of them rely on UEFI. This adds support for cboot.
+
+comment "Cboot needs libtegra-boot-tools"
+	depends on !HAVE_LIBTEGRABOOT_TOOLS
+
 endmenu
 
 choice
@@ -98,6 +111,12 @@  config BOOTLOADER_DEFAULT_GRUB
 	help
 	  Use GRUB as default bootloader interface.
 
+config BOOTLOADER_DEFAULT_CBOOT
+	bool "Cboot"
+	depends on BOOTLOADER_CBOOT
+	help
+	  Use NVIDIA Cboot as default bootloader interface.
+
 config BOOTLOADER_DEFAULT_NONE
 	bool "Environment in RAM"
 	depends on BOOTLOADER_NONE
diff --git a/bootloader/Makefile b/bootloader/Makefile
index 5d99cde5..2f9b6ea8 100644
--- a/bootloader/Makefile
+++ b/bootloader/Makefile
@@ -6,6 +6,7 @@  obj-$(CONFIG_UBOOT)		+= uboot.o
 obj-$(CONFIG_BOOTLOADER_NONE)	+= none.o
 obj-$(CONFIG_BOOTLOADER_GRUB)	+= grub.o
 obj-$(CONFIG_BOOTLOADER_EBG)	+= ebg.o
+obj-$(CONFIG_BOOTLOADER_CBOOT)	+= cboot.o
 
 # Make sure none.o is compiled-in as fallback.
 ifeq ($(CONFIG_UBOOT),)
diff --git a/bootloader/cboot.c b/bootloader/cboot.c
new file mode 100644
index 00000000..1780e5f0
--- /dev/null
+++ b/bootloader/cboot.c
@@ -0,0 +1,197 @@ 
+/*
+ * (C) Copyright 2023 Stefano Babic, stefano.babic@swupdate.org
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+/*
+ * This uses the API of the libtegra-boot-tools library
+ * see info on https://github.com/OE4T/tegra-boot-tools
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "generated/autoconf.h"
+#include "util.h"
+#include "dlfcn.h"
+#include "bootloader.h"
+
+/*
+ * thes edefines are not exported by the library
+ */
+#define BOOTINFO_O_RDONLY (0<<0)
+#define BOOTINFO_O_RDWR	  (3<<0)
+#define BOOTINFO_O_CREAT  (1<<2)
+#define BOOTINFO_O_FORCE_INIT (1<<3)
+
+/*
+ * Define the API from bootinfo.h. File is not (yet ?)
+ * exported by the library, and they are defined here.
+ */
+struct bootinfo_context_s;
+typedef struct bootinfo_context_s bootinfo_context_t;
+extern int bootinfo_open(unsigned int flags, bootinfo_context_t **ctxp);
+extern int bootinfo_close(bootinfo_context_t *ctx);
+extern int bootinfo_var_get(bootinfo_context_t *ctx, const char *name, char *valuebuf, size_t valuebuf_size);
+extern int bootinfo_var_set(bootinfo_context_t *ctx, const char *name, const char *value);
+
+/*
+ * Structure for external library callbacks
+ */
+static struct {
+	int (*open)(unsigned int flags, bootinfo_context_t **ctxp);
+	int (*close)(bootinfo_context_t *ctx);
+	int (*get_env)(bootinfo_context_t *ctx, const char *name, char *valuebuf, size_t valuebuf_size);
+	int (*set_env)(bootinfo_context_t *ctx, const char *name, const char *value);
+} libcboot;
+
+static char *do_env_get(const char *name)
+{
+	bootinfo_context_t *ctx;
+	static char valuebuf[65536];
+	char *value;
+
+	if (!name)
+		return NULL;
+
+	if (libcboot.open(BOOTINFO_O_RDONLY, &ctx) < 0) {
+		ERROR("libcboot.open returns with error");
+		return NULL;
+	}
+
+	if (libcboot.get_env(ctx, name, valuebuf, sizeof(valuebuf)) < 0)
+		valuebuf[0] = '\0';
+
+	value = strdup(valuebuf);
+
+	if (libcboot.close(ctx) < 0) {
+		ERROR("libcboot.close returns with error, environmen not saved");
+	}
+	return value;
+}
+
+static int do_env_set(const char *name, const char *value)
+{
+	int ret = 0;
+	bootinfo_context_t *ctx;
+
+	if (!name || !value)
+		return -EINVAL;
+
+	if (libcboot.open(BOOTINFO_O_RDWR, &ctx) < 0) {
+		ERROR("libcboot.open returns with error");
+		return -ENOENT;
+	}
+	if (libcboot.set_env(ctx, name, value) < 0) {
+		ERROR("libcboot.set_env");
+		ret = -EFAULT;
+	}
+	if (libcboot.close(ctx) < 0) {
+		ERROR("libcboot.close returns with error, environment not saved");
+		ret = -EFAULT;
+	}
+	return ret;
+}
+
+static int do_env_unset(const char *name)
+{
+	int ret = 0;
+	bootinfo_context_t *ctx;
+
+	if (!name)
+		return -EINVAL;
+
+	if (libcboot.open(BOOTINFO_O_RDWR, &ctx) < 0) {
+		ERROR("libcboot.open returns with error");
+		return -ENOENT;
+	}
+	if (libcboot.set_env(ctx, name, NULL) < 0) {
+		ERROR("libcboot.set_env for unset");
+		ret = -EFAULT;
+	}
+	if (libcboot.close(ctx) < 0) {
+		ERROR("libcboot.close returns with error, environment not saved");
+		ret = -EFAULT;
+	}
+	return ret;
+}
+
+static int do_apply_list(const char *filename)
+{
+	errno = 0;
+	bootinfo_context_t *ctx;
+
+	FILE *file = fopen(filename, "rb");
+	if (!file) {
+		ERROR("Cannot open bootloader environment source file %s: %s",
+		      filename, strerror(errno));
+		return -EIO;
+	}
+
+	char *line = NULL;
+	size_t length = 0;
+	int result = 0;
+	if (libcboot.open(BOOTINFO_O_RDWR, &ctx) < 0) {
+		ERROR("libcboot.open returns with error");
+		return -ENOENT;
+	}
+	while ((getline(&line, &length, file)) != -1) {
+		char *key = strtok(line, "=");
+		char *value = strtok(NULL, "\t\n");
+		if (key != NULL) {
+			result = libcboot.set_env(ctx, key, value);
+			if (result < 0) {
+				ERROR("Error %s boot var %s(%s)", value ? "storing" : "deleting",
+					key, value ? value : "");
+			}
+		}
+	}
+
+	if (libcboot.close(ctx) < 0) {
+		ERROR("libcboot.close returns with error, environment not saved");
+		result = -EFAULT;
+	}
+	fclose(file);
+	free(line);
+	return result;
+}
+
+static bootloader cboot = {
+	.env_get = &do_env_get,
+	.env_set = &do_env_set,
+	.env_unset = &do_env_unset,
+	.apply_list = &do_apply_list
+};
+
+static bootloader* probe(void)
+{
+#if defined(BOOTLOADER_STATIC_LINKED)
+	libcboot.open = bootinfo_open;
+	libcboot.close = bootinfo_close;
+	libcboot.get_env = bootinfo_var_get;
+	libcboot.set_env = bootinfo_var_set;
+#else
+	void* handle = dlopen("libtegra-boot-tools.so.1", RTLD_NOW | RTLD_GLOBAL);
+	if (!handle) {
+		return NULL;
+	}
+
+	(void)dlerror();
+	load_symbol(handle, &libcboot.open, "bootinfo_open");
+	load_symbol(handle, &libcboot.close, "bootinfo_close");
+	load_symbol(handle, &libcboot.get_env, "bootinfo_var_get");
+	load_symbol(handle, &libcboot.set_env, "bootinfo_var_set");
+#endif
+	return &cboot;
+}
+
+__attribute__((constructor))
+static void cboot_probe(void)
+{
+	(void)register_bootloader(BOOTLOADER_CBOOT, probe());
+}
diff --git a/include/bootloader.h b/include/bootloader.h
index 529ec127..171181ec 100644
--- a/include/bootloader.h
+++ b/include/bootloader.h
@@ -12,6 +12,7 @@ 
 #define BOOTLOADER_NONE  "none"
 #define BOOTLOADER_GRUB  "grub"
 #define BOOTLOADER_UBOOT "uboot"
+#define BOOTLOADER_CBOOT "cboot"
 
 #define load_symbol(handle, container, fname) \
 	*(void**)(container) = dlsym(handle, fname); \