bootctl: Initial experimentation
This provides a basic prototype for boot control. Some documentation is in boot/bootctl/README.rst
This commit is contained in:
14
boot/bootctl/Makefile
Normal file
14
boot/bootctl/Makefile
Normal 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
10
boot/bootctl/README.rst
Normal 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>
|
||||
45
boot/bootctl/bootctl-uclass.c
Normal file
45
boot/bootctl/bootctl-uclass.c
Normal 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
66
boot/bootctl/bootctl.c
Normal 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
93
boot/bootctl/bootctl.rst
Normal 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
69
boot/bootctl/efi_os.c
Normal 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
70
boot/bootctl/extlinux.c
Normal 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
322
boot/bootctl/logic.c
Normal 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
356
boot/bootctl/simple_meas.c
Normal 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
447
boot/bootctl/simple_state.c
Normal 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
274
boot/bootctl/simple_ui.c
Normal 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
309
boot/bootctl/util.c
Normal 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");
|
||||
}
|
||||
Reference in New Issue
Block a user