bootctl: Initial experimentation

This provides a basic prototype for boot control.

Some documentation is in boot/bootctl/README.rst
This commit is contained in:
Simon Glass
2025-03-14 07:47:28 +00:00
parent 8bae99245b
commit d9152ea75e
30 changed files with 3371 additions and 4 deletions

14
boot/bootctl/Makefile Normal file
View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright 2025 Canonical Ltd
# Written by Simon Glass <simon.glass@canonical.com>
obj-y += bootctl-uclass.o
obj-y += bootctl.o
obj-y += efi_os.o
obj-y += extlinux.o
obj-y += logic.o
obj-y += simple_meas.o
obj-y += simple_state.o
obj-y += simple_ui.o
obj-y += util.o

10
boot/bootctl/README.rst Normal file
View File

@@ -0,0 +1,10 @@
.. SPDX-License-Identifier: GPL-2.0+
Bootctl Library
===============
This library is a reference implementation of boot control, a way to control
the boot, used for experimentation.
.. Copyright 2025 Canonical Ltd
.. Written by Simon Glass <simon.glass@canonical.com>

View File

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Uclass implementation for boot schema
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <bootctl.h>
#include <dm.h>
UCLASS_DRIVER(bootctrl) = {
.id = UCLASS_BOOTCTL,
.name = "bootctrl",
#if CONFIG_IS_ENABLED(OF_REAL)
.post_bind = dm_scan_fdt_dev,
#endif
.per_device_plat_auto = sizeof(struct bootctl_uc_plat),
};
UCLASS_DRIVER(bootctrl_measure) = {
.id = UCLASS_BOOTCTL_MEASURE,
.name = "bootctrl_measure",
.per_device_plat_auto = sizeof(struct bootctl_uc_plat),
};
UCLASS_DRIVER(bootctrl_oslist) = {
.id = UCLASS_BOOTCTL_OSLIST,
.name = "bootctrl_oslist",
.per_device_plat_auto = sizeof(struct bootctl_uc_plat),
};
UCLASS_DRIVER(bootctrl_state) = {
.id = UCLASS_BOOTCTL_STATE,
.name = "bootctrl_state",
.per_device_plat_auto = sizeof(struct bootctl_uc_plat),
};
UCLASS_DRIVER(bootctrl_ui) = {
.id = UCLASS_BOOTCTL_UI,
.name = "bootctrl_ui",
.per_device_plat_auto = sizeof(struct bootctl_uc_plat),
};

66
boot/bootctl/bootctl.c Normal file
View File

@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of the logic to perform a boot
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <bootctl.h>
#include <dm.h>
#include <hang.h>
#include <log.h>
#include <version.h>
#include <bootctl/logic.h>
#include <bootctl/oslist.h>
#include <bootctl/state.h>
#include <bootctl/ui.h>
#include <bootctl/util.h>
#include <dm/device-internal.h>
int bootctl_get_dev(enum uclass_id type, struct udevice **devp)
{
struct udevice *dev;
int ret;
ret = uclass_first_device_err(type, &dev);
if (ret)
return log_msg_ret("bfd", ret);
*devp = dev;
return 0;
}
int bootctl_run(void)
{
struct udevice *logic;
int ret;
printf("Boot Control (using U-Boot v%d.%02d)\n", U_BOOT_VERSION_NUM,
U_BOOT_VERSION_NUM_PATCH);
/* figure out the bootctl to use */
ret = bootctl_get_dev(UCLASS_BOOTCTL, &logic);
if (ret)
return log_msg_ret("bgl", ret);
ret = bc_logic_prepare(logic);
if (ret)
return log_msg_ret("bcl", ret);
ret = bc_logic_start(logic);
if (ret)
return log_msg_ret("bcL", ret);
do {
ret = bc_logic_poll(logic);
if (ret) {
printf("logic err %dE\n", ret);
hang();
}
} while (ret != -ESHUTDOWN);
return 0;
}

93
boot/bootctl/bootctl.rst Normal file
View File

@@ -0,0 +1,93 @@
bootctl - Boot Schema
=====================
Introduction
------------
This is a very basic prototype which aims to show some of the concepts behind
the 'boot schema' idea and how they can be implemented in practice.
Please see the FO215 document for details on the schema.
Features
--------
Very, very few features are supported:
- basic menu
- finding OSes for the menu (only extlinux.conf and EFI are supported)
- measurement of images using a TPM
Running on QEMU
---------------
To run this, first create an image with Ubuntu 2024.04. This script can help
but you will need to edit some variables at the top (imagedir and mnt) or pass
these vars into the script.
This runs the installer:
./scripts/build-qemu.sh -a x86 -r -k -d root.img -R 24.04
Go through the full install and then reboot.
Then run without the OS image:
./scripts/build-qemu.sh -a x86 -r -k -d root.img
Now you can install the u-boot-tools package so that an extlinux.conf file is
created on the disk.
When you reboot you should see a bootmenu with a few options.
If you know U-Boot well you can probably run on sandbox
Where is the schema?
--------------------
For now the schema is in `include/bootctl.dtsi` and is in devicetree format.
We will likely use YAML for this, although it may be useful to then compile the
YAML into devicetree in some cases.
Where is the boot logic?
------------------------
See `boot/bootctl/bootctl.c` for the top-level program. It really just gets the
logic driver and polls it until it either boots or gives up.
The real logic is in `boot/bootctl/logic.c`. The `logic_start()` function sets
things up, then `logic_poll()` actually manages finding things to boot and
sending them to the UI.
The data between `logic.c` and `ui.c` is a bit messy and can likely be tidied
up.
Source map
----------
boot/bootctl
Directory containing the source for use with U-Boot
cmd/
Provides a very simple 'bootctl' command to start things up
include/bootctl
Include files for bootctl
test/boot/bootctl
A few very simple tests to give a flavour of how tests might work
What about all the other patches related to expo? Please just ignore these. My
original prototype was terribly ugly so I spent some time trying to clean it
up.
Comments
--------
Please send any and all comments and suggestions to me.
--
Simon Glass
26-Mar-25

69
boot/bootctl/efi_os.c Normal file
View File

@@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Access to EFI files containing an 'opaque' OS
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <bootctl.h>
#include <bootmeth.h>
#include <bootstd.h>
#include <dm.h>
#include <log.h>
#include <bootctl/oslist.h>
static int efifile_bind(struct udevice *dev)
{
struct bootctl_uc_plat *ucp = dev_get_uclass_plat(dev);
ucp->desc = "Provides OSes to boot";
return 0;
}
static int efifile_next(struct udevice *dev, struct oslist_iter *iter,
struct osinfo *info)
{
int ret;
if (!iter->active) {
bootstd_clear_glob();
iter->active = true;
ret = bootmeth_set_order("efi");
if (ret)
return log_msg_ret("esf", ret);
ret = bootflow_scan_first(NULL, NULL, &iter->bf_iter,
BOOTFLOWIF_HUNT, &info->bflow);
if (ret)
return log_msg_ret("Esf", ret);
} else {
ret = bootflow_scan_next(&iter->bf_iter, &info->bflow);
if (ret) {
iter->active = false;
return log_msg_ret("Esn", ret);
}
}
return 0;
}
static struct bc_oslist_ops ops = {
.next = efifile_next,
};
static const struct udevice_id efifile_ids[] = {
{ .compatible = "bootctl,efifile-oslist" },
{ .compatible = "bootctl,os-list" },
{ }
};
U_BOOT_DRIVER(efifile) = {
.name = "efifile",
.id = UCLASS_BOOTCTL_OSLIST,
.of_match = efifile_ids,
.bind = efifile_bind,
.ops = &ops,
};

70
boot/bootctl/extlinux.c Normal file
View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Access to extlinux.conf files containing OS information
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <bootctl.h>
#include <bootmeth.h>
#include <bootstd.h>
#include <dm.h>
#include <log.h>
#include <bootctl/oslist.h>
static int extlinux_bind(struct udevice *dev)
{
struct bootctl_uc_plat *ucp = dev_get_uclass_plat(dev);
ucp->desc = "Provides OSes to boot";
return 0;
}
static int extlinux_next(struct udevice *dev, struct oslist_iter *iter,
struct osinfo *info)
{
int ret;
if (!iter->active) {
bootstd_clear_glob();
iter->active = true;
ret = bootmeth_set_order("extlinux");
if (ret)
return log_msg_ret("eso", ret);
ret = bootflow_scan_first(NULL, NULL, &iter->bf_iter,
BOOTFLOWIF_HUNT, &info->bflow);
if (ret)
return log_msg_ret("esf", ret);
} else {
ret = bootflow_scan_next(&iter->bf_iter, &info->bflow);
if (ret) {
iter->active = false;
if (ret)
return log_msg_ret("esn", ret);
}
}
return 0;
}
static struct bc_oslist_ops ops = {
.next = extlinux_next,
};
static const struct udevice_id extlinux_ids[] = {
{ .compatible = "bootctl,extlinux-oslist" },
{ .compatible = "bootctl,os-list" },
{ }
};
U_BOOT_DRIVER(extlinux) = {
.name = "extlinux",
.id = UCLASS_BOOTCTL_OSLIST,
.of_match = extlinux_ids,
.bind = extlinux_bind,
.ops = &ops,
};

322
boot/bootctl/logic.c Normal file
View File

@@ -0,0 +1,322 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of the logic to perform a boot
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <bootctl.h>
#include <dm.h>
#include <log.h>
#include <time.h>
#include <version.h>
#include <bootctl/logic.h>
#include <bootctl/measure.h>
#include <bootctl/oslist.h>
#include <bootctl/state.h>
#include <bootctl/ui.h>
#include <bootctl/util.h>
#include <dm/device-internal.h>
enum {
COUNTDOWN_INTERVAL_MS = 1000, /* inteval between autoboot updates */
};
static int logic_prepare(struct udevice *dev)
{
struct logic_priv *priv = dev_get_priv(dev);
int ret;
/* figure out the UI to use */
ret = bootctl_get_dev(UCLASS_BOOTCTL_UI, &priv->ui);
if (ret) {
log_err("UI required but failed (err=%dE)\n", ret);
return log_msg_ret("bgd", ret);
}
/* figure out the measurement to use */
if (priv->opt_measure) {
ret = bootctl_get_dev(UCLASS_BOOTCTL_MEASURE,
&priv->meas);
if (ret) {
log_err("Measurement required but failed (err=%dE)\n",
ret);
return log_msg_ret("bgs", ret);
}
}
/* figure out at least one oslist driver to use */
ret = uclass_first_device_err(UCLASS_BOOTCTL_OSLIST, &priv->oslist);
if (ret)
return log_msg_ret("bgo", ret);
/* figure out the state to use */
ret = bootctl_get_dev(UCLASS_BOOTCTL_STATE, &priv->state);
if (ret)
return log_msg_ret("bgs", ret);
if (priv->opt_labels) {
ret = bootdev_set_order(priv->opt_labels);
if (ret)
return log_msg_ret("blo", ret);
}
return 0;
}
static int logic_start(struct udevice *dev)
{
struct logic_priv *priv = dev_get_priv(dev);
int ret;
if (priv->opt_persist_state) {
int ret;
/* read in our state */
ret = bc_state_load(priv->state);
if (ret)
log_warning("Cannot load state, starting fresh (err=%dE)\n", ret);
else
priv->state_loaded = true;
}
ret = bc_ui_show(priv->ui);
if (ret) {
log_err("Cannot show display (err=%dE)\n", ret);
return log_msg_ret("bds", ret);
}
priv->start_time = get_timer(0);
if (priv->opt_autoboot) {
priv->next_countdown = COUNTDOWN_INTERVAL_MS;
priv->autoboot_remain_s = priv->opt_timeout;
priv->autoboot_active = true;
}
if (priv->opt_default_os)
bc_state_read_str(priv->state, "default", &priv->default_os);
if (priv->opt_measure) {
ret = bc_measure_start(priv->meas);
if (ret)
return log_msg_ret("pme", ret);
}
/* start scanning for OSes */
bc_oslist_setup_iter(&priv->iter);
priv->scanning = true;
return 0;
}
/**
* prepare_for_boot() - Get ready to boot an OS
*
* Intended to include at least:
* - A/B/recovery logic
* - persist the state
* - devicetree fix-up
* - measure images
*
* @dev: Bootctrl logic device
* @osinfo: OS to boot
* Return: 0 if OK, -ve on error
*/
static int prepare_for_boot(struct udevice *dev, struct osinfo *osinfo)
{
struct logic_priv *priv = dev_get_priv(dev);
int ret;
if (priv->opt_track_success) {
ret = bc_state_write_bool(priv->state, "recordfail", true);
if (ret)
log_warning("Cannot set up recordfail (err=%dE)\n",
ret);
}
if (priv->opt_persist_state) {
ret = bc_state_save(priv->state);
if (ret)
log_warning("Cannot save state (err=%dE)\n", ret);
else
priv->state_saved = true;
}
/* devicetree fix-ups go here */
/* measure loaded images */
if (priv->opt_measure) {
struct alist result;
ret = bc_measure_process(priv->meas, osinfo, &result);
if (ret)
return log_msg_ret("pbp", ret);
show_measures(&result);
/* TODO: pass these measurements on to OS */
}
return 0;
}
/**
* read_images() - Read all the images needed to boot an OS
*
* @dev: Bootctrl logic device
* @osinfo: OS we intend to boot
* Return: 0 if OK, -ve on error
*/
static int read_images(struct udevice *dev, struct osinfo *osinfo)
{
struct bootflow *bflow = &osinfo->bflow;
int ret;
ret = bootflow_read_all(bflow);
if (ret)
return log_msg_ret("rea", ret);
log_debug("Images read: %d\n", bflow->images.count);
return 0;
}
static int logic_poll(struct udevice *dev)
{
struct logic_priv *priv = dev_get_priv(dev);
struct osinfo info;
bool selected;
int ret, seq;
/* scan for the next OS, if any */
if (priv->scanning) {
ret = bc_oslist_next(priv->oslist, &priv->iter, &info);
if (!ret) {
ret = bc_ui_add(priv->ui, &info);
if (ret)
return log_msg_ret("bda", ret);
priv->refresh = true;
} else {
/* No more OSes from this driver, try the next */
ret = uclass_next_device_err(&priv->oslist);
if (ret)
priv->scanning = false;
else
memset(&priv->iter, '\0',
sizeof(struct oslist_iter));
}
}
if (priv->autoboot_active &&
get_timer(priv->start_time) > priv->next_countdown) {
ulong secs = get_timer(priv->start_time) / 1000;
priv->autoboot_remain_s = secs >= priv->opt_timeout ? 0 :
max(priv->opt_timeout - secs, 0ul);
priv->next_countdown += COUNTDOWN_INTERVAL_MS;
priv->refresh = true;
}
if (priv->refresh) {
ret = bc_ui_render(priv->ui);
if (ret)
return log_msg_ret("bdr", ret);
priv->refresh = false;
}
ret = bc_ui_poll(priv->ui, &seq, &selected);
if (ret < 0)
return log_msg_ret("bdo", ret);
else if (ret)
priv->refresh = true;
if (!selected && priv->autoboot_active && !priv->autoboot_remain_s &&
seq >= 0) {
log_info("Selecting %d due to timeout\n", seq);
selected = true;
}
if (selected) {
struct osinfo *os;
os = alist_getw(&priv->osinfo, seq, struct osinfo);
log_info("Selected %d: %s\n", seq, os->bflow.os_name);
/*
* try to read the images first; some methods don't support
* this
*/
ret = read_images(dev, os);
if (ret && ret != -ENOSYS) {
if (ret)
return log_msg_ret("lri", ret);
}
ret = prepare_for_boot(dev, os);
if (ret)
return log_msg_ret("lpb", ret);
/* debugging */
// return -ESHUTDOWN;
/* boot OS */
ret = bootflow_boot(&os->bflow);
if (ret)
log_warning("Boot failed (err=%dE)\n", ret);
return -ESHUTDOWN;
}
return 0;
}
static int logic_of_to_plat(struct udevice *dev)
{
struct logic_priv *priv = dev_get_priv(dev);
ofnode node = ofnode_find_subnode(dev_ofnode(dev), "options");
priv->opt_persist_state = ofnode_read_bool(node, "persist-state");
priv->opt_default_os = ofnode_read_bool(node, "default-os");
ofnode_read_u32(node, "timeout", &priv->opt_timeout);
priv->opt_skip_timeout = ofnode_read_bool(node,
"skip-timeout-on-success");
priv->opt_track_success = ofnode_read_bool(node, "track-success");
priv->opt_labels = ofnode_read_string(node, "labels");
priv->opt_autoboot = ofnode_read_bool(node, "autoboot");
priv->opt_measure = ofnode_read_bool(node, "measure");
return 0;
}
static int logic_probe(struct udevice *dev)
{
struct logic_priv *priv = dev_get_priv(dev);
alist_init_struct(&priv->osinfo, struct osinfo);
return 0;
}
static struct bc_logic_ops ops = {
.prepare = logic_prepare,
.start = logic_start,
.poll = logic_poll,
};
static const struct udevice_id logic_ids[] = {
{ .compatible = "bootctl,ubuntu-desktop" },
{ .compatible = "bootctl,logic" },
{ }
};
U_BOOT_DRIVER(bc_logic) = {
.name = "bc_logic",
.id = UCLASS_BOOTCTL,
.of_match = logic_ids,
.ops = &ops,
.of_to_plat = logic_of_to_plat,
.probe = logic_probe,
.priv_auto = sizeof(struct logic_priv),
};

356
boot/bootctl/simple_meas.c Normal file
View File

@@ -0,0 +1,356 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Simple control of which display/output to use while booting
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <bloblist.h>
#include <dm.h>
#include <tpm_api.h>
#include <tpm_tcg2.h>
#include <version_string.h>
#include <bootctl/measure.h>
#include <bootctl/oslist.h>
#include <linux/sizes.h>
enum {
/* align TPM log to 4K boundary */
ALIGN_LOG2 = 12,
};
/**
* enum meas_algo_t - Available algorithms
*
* @ALGOT_SHA256: SHA256
* @ALGOT_COUNT: Number of algorithms
*/
enum meas_algo_t {
ALGOT_SHA256,
ALGOT_COUNT,
};
/**
* enum meas_payload_t - Types of things we can measure
*
* @PAYLOADT_OS: Operating system
* @PAYLOADT_INITRD: Initial ramdisk
* @PAYLOADT_FDT: Flattened devicetree
* @PAYLOADT_CMDLINE: OS command line
* @PAYLOADT_COUNT: Number of payload types
*/
enum meas_payload_t {
PAYLOADT_OS,
PAYLOADT_INITRD,
PAYLOADT_FDT,
PAYLOADT_CMDLINE,
PAYLOADT_COUNT,
};
/**
* struct meas_step - An individual measurement, e.g. for a single image
*
* For now only tpm-pcr is supported, so there is no field for the method - it
* is assumed to be tpm-pcr
*
* These parameters are read from the devicetree.
*
* @payload_type: Image type to measure
* @algos: Bitmap of algorithms to use to measure in this step
* @pcr: TPM Platform Configuration Register to use for this step
* @optional: true if it is OK if the image is missing and cannot be measured
*/
struct meas_step {
enum meas_payload_t type;
uint algos;
uint pcr;
bool optional;
};
/**
* struct measure_priv - Private information for the measure driver
*
* @tpm: TPM to use for measurement
* @tpm_log_size: Configured of TPM log
* @elog: Information about TPM log
* @steps: List of measurement steps, each struct measure_step
*/
struct measure_priv {
struct udevice *tpm;
u32 tpm_log_size;
struct tcg2_event_log elog;
struct alist steps;
};
static const char *const algo_name[ALGOT_COUNT] = {
[ALGOT_SHA256] = "sha256"
};
static struct payload {
const char *name;
enum bootflow_img_t type;
} payload_info[PAYLOADT_COUNT] = {
[PAYLOADT_OS] = {"os", (enum bootflow_img_t)IH_TYPE_KERNEL},
[PAYLOADT_INITRD] = {"initrd",
(enum bootflow_img_t)IH_TYPE_RAMDISK},
[PAYLOADT_FDT] = {"fdt", (enum bootflow_img_t)IH_TYPE_FLATDT},
[PAYLOADT_CMDLINE] = {"cmdline", BFI_CMDLINE},
};
static int simple_start(struct udevice *dev)
{
struct measure_priv *priv = dev_get_priv(dev);
struct tcg2_event_log *elog = &priv->elog;
struct udevice *tpm = priv->tpm;
int ret, size;
void *blob;
if (!IS_ENABLED(CONFIG_TPM_V2) || !IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL))
return log_msg_ret("spt", -ENOSYS);
blob = bloblist_get_blob(BLOBLISTT_TPM_EVLOG, &size);
if (blob) {
if (size < priv->tpm_log_size)
return log_msg_ret("spb", -ENOBUFS);
/* we don't support changing the alignment at present */
if ((ulong)blob != ALIGN((ulong)blob, 1 << ALIGN_LOG2))
return log_msg_ret("spf", -EBADF);
ret = bloblist_resize(BLOBLISTT_TPM_EVLOG, priv->tpm_log_size);
if (ret)
return log_msg_ret("msr", ret);
} else {
blob = bloblist_add(BLOBLISTT_TPM_EVLOG, priv->tpm_log_size,
ALIGN_LOG2);
if (!blob)
return log_msg_ret("sps", -ENOSPC);
}
ret = tpm_auto_start(tpm);
if (ret)
return log_msg_ret("spa", ret);
elog->log = blob;
elog->log_size = priv->tpm_log_size;
ret = tcg2_log_init(tpm, elog);
if (ret)
return log_msg_ret("spi", ret);
ret = tcg2_measure_event(tpm, elog, 0, EV_S_CRTM_VERSION,
strlen(version_string) + 1, version_string);
if (ret) {
tcg2_measurement_term(tpm, elog, true);
return log_msg_ret("spe", ret);
}
return 0;
}
static const char *get_typename(enum bootflow_img_t type)
{
switch ((int)type) {
case IH_TYPE_KERNEL:
return "linux";
case IH_TYPE_RAMDISK:
return "initrd";
case IH_TYPE_FLATDT:
return "dts";
default:
return NULL;
}
}
static int simple_process(struct udevice *dev, const struct osinfo *osinfo,
struct alist *result)
{
const struct bootflow *bflow = &osinfo->bflow;
struct measure_priv *priv = dev_get_priv(dev);
struct tcg2_event_log *elog = &priv->elog;
const struct meas_step *step;
if (!IS_ENABLED(CONFIG_TPM_V2) || !IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL))
return log_msg_ret("ptp", -ENOSYS);
alist_init_struct(result, struct measure_info);
alist_for_each(step, &priv->steps) {
enum bootflow_img_t type = payload_info[step->type].type;
const struct bootflow_img *img;
struct measure_info info;
const char *typename;
const void *ptr;
int ret;
log_debug("measuring %s\n", payload_info[step->type].name);
img = bootflow_img_find(bflow, type);
if (!img) {
if (step->optional)
continue;
log_err("Missing image '%s'\n",
bootflow_img_type_name(type));
return log_msg_ret("smi", -ENOENT);
}
ptr = map_sysmem(img->addr, img->size);
typename = get_typename(img->type);
if (!typename) {
log_err("Unknown image type %d (%s)\n", img->type,
bootflow_img_type_name(img->type));
return log_msg_ret("pim", -EINVAL);
}
/*
* TODO: Use the requested algos or at least check that the TPM
* supports them
*
* tcg2_measure_data() actually measures with the algorithms
* determined by the TPM. It also does this silently, so we
* don't really know what it did.
*
* See tcg2_get_pcr_info() for where this information is
* collected. We should add a function to the API that lets us
* see what is active
*
* Really the TPM code this should be integrated with the hash.h
* code too, rather than having parallel tables.
*/
ret = tcg2_measure_data(priv->tpm, elog, 8, img->size, ptr,
EV_COMPACT_HASH, strlen(typename) + 1
, typename);
if (ret)
return log_msg_ret("stc", ret);
log_debug("Measured '%s'\n", bootflow_img_type_name(type));
info.img = img;
if (!alist_add(result, img))
return log_msg_ret("sra", -ENOMEM);
}
return 0;
}
static int measure_probe(struct udevice *dev)
{
struct measure_priv *priv = dev_get_priv(dev);
struct udevice *tpm;
if (!IS_ENABLED(CONFIG_TPM_V2))
return log_msg_ret("spT", -ENOSYS);
/*
* TODO: We have to probe TPMs to find out what version they are. This
* could be updated to happen in the bind() method of the TPM
*/
uclass_foreach_dev_probe(UCLASS_TPM, tpm) {
if (tpm_get_version(tpm) == TPM_V2) {
priv->tpm = tpm;
break;
}
}
/* TODO: Add policy for what to do in this case */
if (!priv->tpm)
log_warning("TPM not present\n");
return 0;
}
static int measure_of_to_plat(struct udevice *dev)
{
struct measure_priv *priv = dev_get_priv(dev);
ofnode node;
alist_init_struct(&priv->steps, struct meas_step);
priv->tpm_log_size = SZ_64K;
dev_read_u32(dev, "tpm-log-size", &priv->tpm_log_size);
/* errors should not happen in production code, so use log_debug() */
for (node = ofnode_first_subnode(dev_ofnode(dev)); ofnode_valid(node);
node = ofnode_next_subnode(node)) {
const char *node_name = ofnode_get_name(node);
const char *method = ofnode_read_string(node, "method");
struct meas_step step = {0};
int count, i, j, ret, found;
/* for now we use the node name as the payload name */
for (j = 0, found = -1; j < ARRAY_SIZE(payload_info); j++) {
if (!strcmp(payload_info[j].name, node_name))
found = j;
}
if (found == -1) {
log_debug("Unknown payload '%s'\n", node_name);
return log_msg_ret("mta", -EINVAL);
}
step.type = found;
step.optional = ofnode_read_bool(node, "optional");
if (!method || strcmp("tpm-pcr", method)) {
log_debug("Unknown method in '%s': '%s'\n", node_name,
method);
return log_msg_ret("mtp", -EINVAL);
}
ret = ofnode_read_u32(node, "pcr-number", &step.pcr);
if (ret) {
log_debug("Missing pcr-number in '%s'", node_name);
return log_msg_ret("mtP", ret);
}
count = ofnode_read_string_count(node, "algos");
if (count < 1) {
log_debug("Missing algos in '%s'\n", node_name);
return log_msg_ret("mta", -EINVAL);
}
for (i = 0; i < count; i++) {
const char *name;
ret = ofnode_read_string_index(node, "algos", i, &name);
if (ret)
return log_msg_ret("mta", ret);
for (j = 0, found = -1;
j < ARRAY_SIZE(algo_name); j++) {
if (!strcmp(algo_name[j], name))
found = j;
}
if (found == -1) {
log_debug("Unknown algo in '%s': '%s'\n",
node_name, name);
return log_msg_ret("mta", -EINVAL);
}
step.algos |= BIT(found);
}
if (!alist_add(&priv->steps, step))
return log_msg_ret("mal", -ENOMEM);
}
return 0;
}
static struct bc_measure_ops measure_ops = {
.start = simple_start,
.process = simple_process,
};
static const struct udevice_id measure_ids[] = {
{ .compatible = "bootctl,simple-measure" },
{ .compatible = "bootctl,measure" },
{ }
};
U_BOOT_DRIVER(simple_measure) = {
.name = "simple_meas",
.id = UCLASS_BOOTCTL_MEASURE,
.of_match = measure_ids,
.ops = &measure_ops,
.priv_auto = sizeof(struct measure_priv),
.of_to_plat = measure_of_to_plat,
.probe = measure_probe,
};

447
boot/bootctl/simple_state.c Normal file
View File

@@ -0,0 +1,447 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Provides a simple name/value pair
*
* The file format a ordered series of lines of the form:
* key=value\n
*
* with a nul terminator at the end. Strings are stored without quoting. Ints
* are stored as decimal, perhaps with leading '-'. Bools are stored as 0 or 1
*
* keys consist only of characters a-z, _ and 0-9
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <alist.h>
#include <abuf.h>
#include <bootctl.h>
#include <bootmeth.h>
#include <bootstd.h>
#include <ctype.h>
#include <dm.h>
#include <fs_legacy.h>
#include <log.h>
#include <malloc.h>
#include <vsprintf.h>
#include <bootctl/state.h>
#include <linux/sizes.h>
enum {
/* maximum length of a key, excluding nul terminator */
MAX_KEY_LEN = 30,
/* maximum length of a value, excluding nul terminator */
MAX_VAL_LEN = SZ_4K,
MAX_LINE_LEN = MAX_KEY_LEN + MAX_VAL_LEN + 10,
MAX_FILE_SIZE = SZ_64K,
};
struct keyval {
char *key;
char *val;
};
/**
* struct sstate_priv - Private data for simple-state
*
* @ifname: Interface which stores the state
* @dev_part: Device and partition number which stores the state
* @filename: Filename which stores the state
* @items: List of struct keyval
*/
struct sstate_priv {
const char *ifname;
const char *dev_part;
const char *fname;
struct alist items;
};
static void clear_vals(struct sstate_priv *priv)
{
struct keyval *kv;
log_debug("clearing\n");
alist_for_each(kv, &priv->items) {
free(kv->key);
free(kv->val);
}
alist_empty(&priv->items);
}
static struct keyval *find_item(struct sstate_priv *priv, const char *key)
{
struct keyval *kv;
log_debug("find %s: ", key);
alist_for_each(kv, &priv->items) {
if (!strcmp(kv->key, key)) {
log_debug("found\n");
return kv;
}
}
log_debug("not found\n");
return NULL;
}
static int add_val(struct sstate_priv *priv, const char *key,
const char *val)
{
int keylen, vallen;
struct keyval kv;
const unsigned char *p;
log_content("add %s=%s\n", key, val);
for (keylen = 0, p = key; keylen <= MAX_KEY_LEN && *p; keylen++, p++) {
if (!(*p == '_' || isdigit(*p) || islower(*p)) || *p >= 127) {
log_content("- invalid character %02x\n", *p);
return log_msg_ret("wvk", -EKEYREJECTED);
}
}
if (!keylen) {
log_content("- empty key\n");
return log_msg_ret("wve", -EINVAL);
}
if (keylen > MAX_KEY_LEN) {
log_content("- key too long %d\n", keylen);
return log_msg_ret("wvl", -EKEYREJECTED);
}
vallen = strnlen(val, MAX_VAL_LEN + 1);
if (vallen > MAX_VAL_LEN) {
log_content("- val too long\n");
return log_msg_ret("wvv", -E2BIG);
}
kv.key = strndup(key, MAX_KEY_LEN);
if (!kv.key) {
return log_msg_ret("avk", -ENOMEM);
}
kv.val = strndup(val, MAX_VAL_LEN);
if (!kv.val) {
free(kv.key);
return log_msg_ret("avv", -ENOMEM);
}
if (!alist_add(&priv->items, kv)) {
free(kv.key);
free(kv.val);
return log_msg_ret("avl", -ENOMEM);
}
return 0;
}
static int write_val(struct sstate_priv *priv, const char *key,
const char *val)
{
struct keyval *kv;
int ret;
log_content("write %s=%s\n", key, val);
if (!key || !val)
return log_msg_ret("wkn", -EINVAL);
kv = find_item(priv, key);
if (kv) {
int len = strnlen(val, MAX_VAL_LEN + 1);
char *new;
if (len > MAX_VAL_LEN) {
log_content("- val too long\n");
return log_msg_ret("wvr", -E2BIG);
}
log_content("- update\n");
new = realloc(kv->val, len);
if (!new)
return log_msg_ret("wvr", -ENOMEM);
strlcpy(new, val, len + 1);
kv->val = new;
} else {
ret = add_val(priv, key, val);
if (ret)
return log_msg_ret("swB", ret);
}
log_content("done\n");
return 0;
}
static int sstate_clear(struct udevice *dev)
{
struct sstate_priv *priv = dev_get_priv(dev);
clear_vals(priv);
return 0;
}
static int sstate_load(struct udevice *dev)
{
struct sstate_priv *priv = dev_get_priv(dev);
struct membuf inf;
struct abuf buf;
char line[MAX_LINE_LEN];
bool ok;
int len;
int ret;
log_debug("loading\n");
clear_vals(priv);
log_debug("read file ifname '%s' dev_part '%s' fname '%s'\n",
priv->ifname, priv->dev_part, priv->fname);
ret = fs_load_alloc(priv->ifname, priv->dev_part, priv->fname,
MAX_FILE_SIZE, 0, &buf);
if (ret)
return log_msg_ret("ssa", ret);
log_debug("parsing\n");
membuf_init_with_data(&inf, buf.data, buf.size);
for (ok = true;
len = membuf_readline(&inf, line, sizeof(line), ' ', true),
len && ok;) {
char *key = strtok(line, "=");
char *val = strtok(NULL, "=");
if (key && val)
ok = !add_val(priv, key, val);
}
abuf_uninit(&buf);
if (!ok) {
clear_vals(priv);
return log_msg_ret("ssr", -ENOMEM);
}
return 0;
}
static int sstate_save_to_buf(struct udevice *dev, struct abuf *buf)
{
struct sstate_priv *priv = dev_get_priv(dev);
struct membuf inf;
struct keyval *kv;
char *data;
int size;
log_debug("saving\n");
abuf_init(buf);
if (!abuf_realloc(buf, MAX_FILE_SIZE))
return log_msg_ret("ssa", -ENOMEM);
membuf_init(&inf, buf->data, buf->size);
alist_for_each(kv, &priv->items) {
int keylen = strnlen(kv->key, MAX_KEY_LEN + 1);
int vallen = strnlen(kv->val, MAX_VAL_LEN + 1);
if (keylen > MAX_KEY_LEN || vallen > MAX_VAL_LEN)
return log_msg_ret("ssp", -E2BIG);
log_content("save %s=%s\n", kv->key, kv->val);
if (membuf_put(&inf, kv->key, keylen) != keylen ||
membuf_put(&inf, "=", 1) != 1 ||
membuf_put(&inf, kv->val, vallen) != vallen ||
membuf_put(&inf, "\n", 1) != 1)
return log_msg_ret("ssp", -ENOSPC);
}
if (membuf_put(&inf, "", 1) != 1)
return log_msg_ret("ssp", -ENOSPC);
size = membuf_getraw(&inf, MAX_FILE_SIZE, true, &data);
if (data != buf->data)
return log_msg_ret("ssp", -EFAULT);
buf->size = size;
return 0;
}
static int sstate_save(struct udevice *dev)
{
struct sstate_priv *priv = dev_get_priv(dev);
loff_t actwrite;
struct abuf buf;
int ret;
ret = sstate_save_to_buf(dev, &buf);
if (ret)
return log_msg_ret("sss", ret);
log_debug("set dest ifname '%s' dev_part '%s'\n", priv->ifname,
priv->dev_part);
ret = fs_set_blk_dev(priv->ifname, priv->dev_part, FS_TYPE_ANY);
if (ret) {
ret = log_msg_ret("sss", ret);
} else {
log_debug("write fname '%s' size %zx\n", priv->fname, buf.size);
ret = fs_write(priv->fname, abuf_addr(&buf), 0, buf.size,
&actwrite);
if (ret)
ret = log_msg_ret("ssw", ret);
}
abuf_uninit(&buf);
if (ret)
return ret;
return 0;
}
static int sstate_read_bool(struct udevice *dev, const char *prop, bool *valp)
{
struct sstate_priv *priv = dev_get_priv(dev);
const struct keyval *kv;
log_debug("read_bool\n");
kv = find_item(priv, prop);
if (!kv)
return log_msg_ret("srb", -ENOENT);
*valp = !strcmp(kv->val, "1") ? true : false;
log_debug("- val %s: %d\n", kv->val, *valp);
return 0;
}
static int sstate_write_bool(struct udevice *dev, const char *prop, bool val)
{
struct sstate_priv *priv = dev_get_priv(dev);
int ret;
ret = write_val(priv, prop, simple_itoa(val));
if (ret)
return log_msg_ret("swb", ret);
return 0;
}
static int sstate_read_int(struct udevice *dev, const char *prop, long *valp)
{
struct sstate_priv *priv = dev_get_priv(dev);
const struct keyval *kv;
log_debug("read_bool\n");
kv = find_item(priv, prop);
if (!kv)
return log_msg_ret("srb", -ENOENT);
*valp = simple_strtol(kv->val, NULL, 10);
log_debug("- val %s: %ld\n", kv->val, *valp);
return 0;
}
static int sstate_write_int(struct udevice *dev, const char *prop, long val)
{
struct sstate_priv *priv = dev_get_priv(dev);
int ret;
log_debug("write_int %ld\n", val);
ret = write_val(priv, prop, simple_itoa(val));
if (ret)
return log_msg_ret("swb", ret);
return 0;
}
static int sstate_read_str(struct udevice *dev, const char *prop,
const char **valp)
{
struct sstate_priv *priv = dev_get_priv(dev);
const struct keyval *kv;
log_debug("read_bool\n");
kv = find_item(priv, prop);
if (!kv)
return log_msg_ret("srb", -ENOENT);
*valp = kv->val;
log_debug("- val %s: %s\n", kv->val, *valp);
return 0;
}
static int sstate_write_str(struct udevice *dev, const char *prop,
const char *str)
{
struct sstate_priv *priv = dev_get_priv(dev);
int ret;
ret = write_val(priv, prop, str);
if (ret)
return log_msg_ret("swb", ret);
return 0;
}
static int sstate_probe(struct udevice *dev)
{
struct sstate_priv *priv = dev_get_priv(dev);
alist_init_struct(&priv->items, struct keyval);
return 0;
}
static int sstate_of_to_plat(struct udevice *dev)
{
struct sstate_priv *priv = dev_get_priv(dev);
int ret;
ret = dev_read_string_index(dev, "location", 0, &priv->ifname);
if (ret)
return log_msg_ret("ssi", ret);
ret = dev_read_string_index(dev, "location", 1, &priv->dev_part);
if (ret)
return log_msg_ret("ssd", ret);
priv->fname = dev_read_string(dev, "filename");
if (!priv->ifname || !priv->dev_part || !priv->fname)
return log_msg_ret("ssp", -EINVAL);
return 0;
}
static int sstate_bind(struct udevice *dev)
{
struct bootctl_uc_plat *ucp = dev_get_uclass_plat(dev);
ucp->desc = "Stores state information about booting";
return 0;
}
static struct bc_state_ops ops = {
.load = sstate_load,
.save = sstate_save,
.save_to_buf = sstate_save_to_buf,
.clear = sstate_clear,
.read_bool = sstate_read_bool,
.write_bool = sstate_write_bool,
.read_int = sstate_read_int,
.write_int = sstate_write_int,
.read_str = sstate_read_str,
.write_str = sstate_write_str,
};
static const struct udevice_id sstate_ids[] = {
{ .compatible = "bootctl,simple-state" },
{ .compatible = "bootctl,state" },
{ }
};
U_BOOT_DRIVER(simple_state) = {
.name = "simple_state",
.id = UCLASS_BOOTCTL_STATE,
.of_match = sstate_ids,
.ops = &ops,
.bind = sstate_bind,
.probe = sstate_probe,
.of_to_plat = sstate_of_to_plat,
.priv_auto = sizeof(struct sstate_priv),
};

274
boot/bootctl/simple_ui.c Normal file
View File

@@ -0,0 +1,274 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Provides a simple boot menu on a graphical display
*
* TODO: Support a text display / serial terminal
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <alist.h>
#include <bootctl.h>
#include <bootstd.h>
#include <dm.h>
#include <expo.h>
#include <video_console.h>
#include <bootctl/logic.h>
#include <bootctl/oslist.h>
#include <bootctl/ui.h>
#include <bootctl/util.h>
#include "../bootflow_internal.h"
/* TODO: Define to 1 to use text mode (for terminals), 0 for graphics */
#define TEXT_MODE 0
/**
* struct ui_priv - information about the display
*
* @expo: Expo containing the menu
* @scn: Current scene being shown
* @lpriv: Private data of logic device
* @console: vidconsole device in use
* @autoboot_template: template string to use for autoboot
* @autoboot_str: current string displayed for autoboot timeout
* @logo: logo in bitmap format, NULL to use default
* @logo_size: size of the logo in bytes
*/
struct ui_priv {
struct expo *expo;
struct scene *scn; /* consider dropping this */
struct logic_priv *lpriv;
struct udevice *console;
struct abuf autoboot_template;
struct abuf *autoboot_str;
const void *logo;
int logo_size;
};
static int simple_ui_probe(struct udevice *dev)
{
struct ui_priv *priv = dev_get_priv(dev);
struct udevice *ldev;
int ret;
ret = uclass_first_device_err(UCLASS_BOOTCTL, &ldev);
if (ret)
return log_msg_ret("sup", ret);
priv->lpriv = dev_get_priv(ldev);
return 0;
}
static int simple_ui_bind(struct udevice *dev)
{
struct bootctl_uc_plat *ucp = dev_get_uclass_plat(dev);
ucp->desc = "Graphical or textual display for user";
return 0;
}
static int simple_ui_print(struct udevice *dev, const char *msg)
{
printf("%s", msg);
return 0;
}
static int simple_ui_show(struct udevice *dev)
{
struct ui_priv *priv = dev_get_priv(dev);
struct bootstd_priv *std;
struct scene *scn;
struct abuf *buf;
uint scene_id;
int ret;
ret = bootstd_get_priv(&std);
if (ret)
return log_msg_ret("sdb", ret);
ret = bootflow_menu_setup(std, TEXT_MODE, &priv->expo);
if (ret)
return log_msg_ret("sds", ret);
ret = expo_first_scene_id(priv->expo);
if (ret < 0)
return log_msg_ret("ufs", ret);
scene_id = ret;
scn = expo_lookup_scene_id(priv->expo, scene_id);
scene_obj_set_hide(scn, OBJ_AUTOBOOT, false);
ret = expo_edit_str(priv->expo, STR_AUTOBOOT,
&priv->autoboot_template,
&priv->autoboot_str);
if (ret)
return log_msg_ret("ses", ret);
ret = expo_edit_str(priv->expo, STR_MENU_TITLE, NULL, &buf);
if (ret)
return log_msg_ret("set", ret);
abuf_printf(buf, "Boot control");
if (priv->logo) {
ret = scene_img_set_data(scn, OBJ_U_BOOT_LOGO,
priv->logo, priv->logo_size);
if (ret)
return log_msg_ret("log", ret);
ret = scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1135, 10);
if (ret)
return log_msg_ret("lop", ret);
}
log_debug("theme '%s'\n", ofnode_get_name(std->theme));
if (ofnode_valid(std->theme)) {
ret = expo_setup_theme(priv->expo, std->theme);
if (ret)
return log_msg_ret("thm", ret);
}
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("usa", ret);
scene_set_highlight_id(scn, OBJ_MENU);
priv->scn = scn;
ret = device_find_first_child_by_uclass(priv->expo->display,
UCLASS_VIDEO_CONSOLE,
&priv->console);
if (ret)
return log_msg_ret("suq", ret);
vidconsole_set_quiet(priv->console, true);
return 0;
}
static int simple_ui_add(struct udevice *dev, struct osinfo *info)
{
struct ui_priv *priv = dev_get_priv(dev);
struct logic_priv *lpriv = priv->lpriv;
int seq = lpriv->osinfo.count;
struct bootstd_priv *std;
struct scene *scn;
int ret;
info = alist_add(&lpriv->osinfo, *info);
if (!info)
return -ENOMEM;
ret = bootflow_menu_add(priv->expo, &info->bflow, seq, &scn);
if (ret)
return log_msg_ret("sda", ret);
ret = bootstd_get_priv(&std);
if (ret)
return log_msg_ret("sup", ret);
if (ofnode_valid(std->theme)) {
ret = expo_setup_theme(priv->expo, std->theme);
if (ret)
return log_msg_ret("thm", ret);
}
ret = expo_calc_dims(priv->expo);
if (ret)
return log_msg_ret("ecd", ret);
if (lpriv->default_os &&
!strcmp(lpriv->default_os, info->bflow.os_name))
scene_menu_select_item(scn, OBJ_MENU, ITEM + seq);
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("sua", ret);
return 0;
}
static int simple_ui_render(struct udevice *dev)
{
struct ui_priv *priv = dev_get_priv(dev);
int ret;
ret = abuf_printf(priv->autoboot_str,
priv->autoboot_template.data,
priv->lpriv->autoboot_remain_s);
if (ret < 0)
return log_msg_ret("uip", ret);
ret = expo_arrange(priv->expo);
if (ret)
return log_msg_ret("sda", ret);
ret = expo_render(priv->expo);
if (ret)
return log_msg_ret("sdr", ret);
return 0;
}
static int simple_ui_poll(struct udevice *dev, int *seqp, bool *selectedp)
{
struct ui_priv *priv = dev_get_priv(dev);
struct logic_priv *lpriv = priv->lpriv;
int seq, ret;
bool ok = true;
*seqp = -1;
*selectedp = false;
ret = bootflow_menu_poll(priv->expo, &seq);
ok = !ret;
if (ret == -ERESTART || ret == -EREMCHG) {
lpriv->autoboot_active = false;
scene_obj_set_hide(priv->scn, OBJ_AUTOBOOT, true);
ok = true;
} else if (ret == -EAGAIN) {
ok = true;
}
*seqp = seq;
if (ret) {
if (!ok)
return log_msg_ret("sdp", ret);
if (ret == -EAGAIN || ret == -ERESTART)
return 0;
return 1;
}
*selectedp = true;
return 0;
}
static int simple_ui_of_to_plat(struct udevice *dev)
{
struct ui_priv *priv = dev_get_priv(dev);
priv->logo = dev_read_prop(dev, "logo", &priv->logo_size);
return 0;
}
static struct bc_ui_ops ops = {
.print = simple_ui_print,
.show = simple_ui_show,
.add = simple_ui_add,
.render = simple_ui_render,
.poll = simple_ui_poll,
};
static const struct udevice_id simple_ui_ids[] = {
{ .compatible = "bootctl,simple-ui" },
{ .compatible = "bootctl,ui" },
{ }
};
U_BOOT_DRIVER(simple_ui) = {
.name = "simple_ui",
.id = UCLASS_BOOTCTL_UI,
.of_match = simple_ui_ids,
.of_to_plat = simple_ui_of_to_plat,
.bind = simple_ui_bind,
.probe = simple_ui_probe,
.ops = &ops,
.priv_auto = sizeof(struct ui_priv),
};

309
boot/bootctl/util.c Normal file
View File

@@ -0,0 +1,309 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Utility functions
*
* Copyright 2025 Canonical Ltd
* Written by Simon Glass <simon.glass@canonical.com>
*/
#define LOG_CATEGORY UCLASS_BOOTCTL
#include <alist.h>
#include <bootctl.h>
#include <dm.h>
#include <log.h>
#include <stdarg.h>
#include <vsprintf.h>
#include <bootctl/logic.h>
#include <bootctl/measure.h>
#include <bootctl/oslist.h>
#include <bootctl/state.h>
#include <bootctl/ui.h>
#include <bootctl/util.h>
/**
* bc_printf() - Print a string to the display
*
* @fmt: printf() format string for log record
* @...: Optional parameters, according to the format string @fmt
* Return: Number of characters emitted
*/
int bc_printf(struct udevice *disp, const char *fmt, ...)
{
struct bc_ui_ops *ops = bc_ui_get_ops(disp);
char buf[CONFIG_SYS_CBSIZE];
va_list args;
int count;
int ret;
va_start(args, fmt);
count = vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
ret = ops->print(disp, buf);
if (ret)
return log_msg_ret("bpp", ret);
return count;
}
int bc_ui_show(struct udevice *disp)
{
struct bc_ui_ops *ops = bc_ui_get_ops(disp);
int ret;
ret = ops->show(disp);
if (ret)
return log_msg_ret("bds", ret);
return 0;
}
int bc_ui_add(struct udevice *dev, struct osinfo *info)
{
struct bc_ui_ops *ops = bc_ui_get_ops(dev);
int ret;
ret = ops->add(dev, info);
if (ret)
return log_msg_ret("bda", ret);
return 0;
}
int bc_ui_render(struct udevice *disp)
{
struct bc_ui_ops *ops = bc_ui_get_ops(disp);
int ret;
ret = ops->render(disp);
if (ret)
return log_msg_ret("bds", ret);
return 0;
}
int bc_ui_poll(struct udevice *disp, int *seqp, bool *selectedp)
{
struct bc_ui_ops *ops = bc_ui_get_ops(disp);
int ret;
ret = ops->poll(disp, seqp, selectedp);
if (ret)
return log_msg_ret("bdp", ret);
return ret;
}
void bc_oslist_setup_iter(struct oslist_iter *iter)
{
memset(iter, '\0', sizeof(struct oslist_iter));
}
int bc_oslist_next(struct udevice *dev, struct oslist_iter *iter,
struct osinfo *info)
{
struct bc_oslist_ops *ops = bc_oslist_get_ops(dev);
int ret;
log_debug("oslist flags %x\n", iter->bf_iter.flags);
ret = ops->next(dev, iter, info);
if (ret)
return log_msg_ret("bon", ret);
return 0;
}
int bc_state_load(struct udevice *dev)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->load(dev);
if (ret)
return log_msg_ret("bsl", ret);
return 0;
}
int bc_state_save(struct udevice *dev)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->save(dev);
if (ret)
return log_msg_ret("bss", ret);
return 0;
}
int bc_state_save_to_buf(struct udevice *dev, struct abuf *buf)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->save_to_buf(dev, buf);
if (ret)
return log_msg_ret("bsb", ret);
return 0;
}
int bc_state_clear(struct udevice *dev)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->clear(dev);
if (ret)
return log_msg_ret("bsc", ret);
return 0;
}
int bc_state_read_bool(struct udevice *dev, const char *key, bool *valp)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->read_bool(dev, key, valp);
if (ret)
return log_msg_ret("srb", ret);
return 0;
}
int bc_state_write_bool(struct udevice *dev, const char *key, bool val)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->write_bool(dev, key, val);
if (ret)
return log_msg_ret("swb", ret);
return 0;
}
int bc_state_read_int(struct udevice *dev, const char *key, long *valp)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->read_int(dev, key, valp);
if (ret)
return log_msg_ret("sri", ret);
return 0;
}
int bc_state_write_int(struct udevice *dev, const char *key, long val)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->write_int(dev, key, val);
if (ret)
return log_msg_ret("swi", ret);
return 0;
}
int bc_state_read_str(struct udevice *dev, const char *key, const char **valp)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->read_str(dev, key, valp);
if (ret)
return log_msg_ret("srs", ret);
return 0;
}
int bc_state_write_str(struct udevice *dev, const char *key, const char *val)
{
struct bc_state_ops *ops = bc_state_get_ops(dev);
int ret;
ret = ops->write_str(dev, key, val);
if (ret)
return log_msg_ret("sws", ret);
return 0;
}
int bc_logic_prepare(struct udevice *dev)
{
struct bc_logic_ops *ops = bc_logic_get_ops(dev);
int ret;
ret = ops->prepare(dev);
if (ret)
return log_msg_ret("blp", ret);
return 0;
}
int bc_logic_start(struct udevice *dev)
{
struct bc_logic_ops *ops = bc_logic_get_ops(dev);
int ret;
ret = ops->start(dev);
if (ret)
return log_msg_ret("bls", ret);
return 0;
}
int bc_logic_poll(struct udevice *dev)
{
struct bc_logic_ops *ops = bc_logic_get_ops(dev);
int ret;
ret = ops->poll(dev);
if (ret)
return log_msg_ret("blP", ret);
return 0;
}
int bc_measure_start(struct udevice *dev)
{
struct bc_measure_ops *ops = bc_measure_get_ops(dev);
int ret;
ret = ops->start(dev);
if (ret)
return log_msg_ret("blM", ret);
return 0;
}
int bc_measure_process(struct udevice *dev, const struct osinfo *osinfo,
struct alist *result)
{
struct bc_measure_ops *ops = bc_measure_get_ops(dev);
int ret;
alist_init_struct(result, struct measure_info);
ret = ops->process(dev, osinfo, result);
if (ret)
return log_msg_ret("blm", ret);
return 0;
}
void show_measures(struct alist *result)
{
struct measure_info *res;
/* TODO: expand to show more info? */
printf("Measurement report:");
alist_for_each(res, result)
printf(" %s", bootflow_img_type_name(res->img->type));
printf("\n");
}