Merge branch 'proa' into 'master'
bootctl: Initial experimentation See merge request u-boot/u-boot!189
This commit is contained in:
@@ -185,6 +185,9 @@
|
||||
version-offset = <0xdffe00>;
|
||||
version-size = <0x100>;
|
||||
};
|
||||
|
||||
boot_ctl: bootctl {
|
||||
};
|
||||
};
|
||||
|
||||
cedit: cedit {
|
||||
@@ -2101,3 +2104,4 @@
|
||||
#endif
|
||||
|
||||
#include "cedit.dtsi"
|
||||
#include "bootctl.dtsi"
|
||||
|
||||
@@ -42,6 +42,19 @@
|
||||
};
|
||||
};
|
||||
|
||||
boot_ctl: bootctl {
|
||||
};
|
||||
|
||||
bootstd {
|
||||
compatible = "u-boot,boot-std";
|
||||
theme {
|
||||
white-on-black;
|
||||
font-size = <30>;
|
||||
menu-inset = <3>;
|
||||
menuitem-gap-y = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
pci {
|
||||
compatible = "pci-x86";
|
||||
#address-cells = <3>;
|
||||
@@ -73,3 +86,5 @@
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#include "bootctl.dtsi"
|
||||
|
||||
@@ -53,6 +53,19 @@
|
||||
};
|
||||
};
|
||||
|
||||
boot_ctl: bootctl {
|
||||
};
|
||||
|
||||
bootstd {
|
||||
compatible = "u-boot,boot-std";
|
||||
theme {
|
||||
white-on-black;
|
||||
font-size = <30>;
|
||||
menu-inset = <3>;
|
||||
menuitem-gap-y = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
pci {
|
||||
compatible = "pci-x86";
|
||||
#address-cells = <3>;
|
||||
@@ -92,3 +105,5 @@
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#include "bootctl.dtsi"
|
||||
|
||||
10
boot/Kconfig
10
boot/Kconfig
@@ -505,6 +505,16 @@ config BOOT_DEFAULTS
|
||||
of U-Boot to boot various images. Currently much functionality is
|
||||
tied to enabling the command that exercises it.
|
||||
|
||||
config BOOTCTL
|
||||
bool "Support for boot control"
|
||||
depends on BOOTSTD
|
||||
default y
|
||||
help
|
||||
This supports an experimental boot control, providing a way to
|
||||
discover and boot Operating Systems.
|
||||
|
||||
For now, this is experimental.
|
||||
|
||||
menuconfig BOOTSTD
|
||||
bool "Standard boot"
|
||||
default y if BLK
|
||||
|
||||
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");
|
||||
}
|
||||
10
cmd/Kconfig
10
cmd/Kconfig
@@ -317,6 +317,16 @@ config CMD_BOOTM_PRE_LOAD
|
||||
This stage allow to check or modify the image provided
|
||||
to the bootm command.
|
||||
|
||||
config CMD_BOOTCTL
|
||||
bool "bootctl"
|
||||
depends on BOOTCTL
|
||||
default y
|
||||
help
|
||||
Provide information about the bootctl system, including access to
|
||||
drivers and other features.
|
||||
|
||||
This command is not necessary for bootctl to work.
|
||||
|
||||
config CMD_BOOTDEV
|
||||
bool "bootdev"
|
||||
depends on BOOTSTD
|
||||
|
||||
58
cmd/bootctl.c
Normal file
58
cmd/bootctl.c
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* 'bootctl' command
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#include <bootctl.h>
|
||||
#include <command.h>
|
||||
#include <dm.h>
|
||||
|
||||
static int do_bootctl_list(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||
char *const argv[])
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct uclass *uc;
|
||||
int i;
|
||||
|
||||
printf("Seq Name Type Description\n");
|
||||
printf("--- -------------- -------------- --------------------\n");
|
||||
|
||||
i = 0;
|
||||
uclass_id_foreach_dev(UCLASS_BOOTCTL, dev, uc) {
|
||||
struct bootctl_uc_plat *ucp = dev_get_uclass_plat(dev);
|
||||
|
||||
printf("%3d %-15.15s %-15.15s %s\n", i, dev->name,
|
||||
dev_get_uclass_name(dev), ucp->desc);
|
||||
i++;
|
||||
}
|
||||
|
||||
printf("--- -------------- -------------- --------------------\n");
|
||||
printf("(%d driver%s)\n", i, i != 1 ? "s" : "");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_bootctl_run(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||
char *const argv[])
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bootctl_run();
|
||||
if (ret) {
|
||||
printf("Boot failed (err=%dE)\n", ret);
|
||||
return CMD_RET_FAILURE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
U_BOOT_LONGHELP(bootctl,
|
||||
"list - list bootctl drivers\n"
|
||||
"run - run a boot");
|
||||
|
||||
U_BOOT_CMD_WITH_SUBCMDS(bootctl, "Boot control", bootctl_help_text,
|
||||
U_BOOT_SUBCMD_MKENT(list, 1, 1, do_bootctl_list),
|
||||
U_BOOT_SUBCMD_MKENT(run, 1, 1, do_bootctl_run));
|
||||
@@ -1,6 +1,6 @@
|
||||
CONFIG_TEXT_BASE=0
|
||||
CONFIG_SYS_MALLOC_LEN=0x6000000
|
||||
CONFIG_BLOBLIST_SIZE_RELOC=0x5000
|
||||
CONFIG_BLOBLIST_SIZE_RELOC=0x20000
|
||||
CONFIG_NR_DRAM_BANKS=1
|
||||
CONFIG_ENV_SIZE=0x2000
|
||||
CONFIG_DEFAULT_DEVICE_TREE="sandbox"
|
||||
@@ -26,6 +26,8 @@ CONFIG_FIT=y
|
||||
CONFIG_FIT_RSASSA_PSS=y
|
||||
CONFIG_FIT_CIPHER=y
|
||||
CONFIG_FIT_VERBOSE=y
|
||||
CONFIG_BOOTM_OPENRTOS=y
|
||||
CONFIG_BOOTM_OSE=y
|
||||
CONFIG_BOOTMETH_ANDROID=y
|
||||
CONFIG_UPL=y
|
||||
CONFIG_LEGACY_IMAGE_FORMAT=y
|
||||
@@ -62,8 +64,6 @@ CONFIG_CMD_LICENSE=y
|
||||
CONFIG_CMD_SMBIOS=y
|
||||
CONFIG_CMD_BOOTM_PRE_LOAD=y
|
||||
CONFIG_CMD_BOOTZ=y
|
||||
CONFIG_BOOTM_OPENRTOS=y
|
||||
CONFIG_BOOTM_OSE=y
|
||||
CONFIG_CMD_BOOTEFI_HELLO=y
|
||||
CONFIG_CMD_BOOTMENU=y
|
||||
CONFIG_CMD_ABOOTIMG=y
|
||||
@@ -342,6 +342,7 @@ CONFIG_OSD=y
|
||||
CONFIG_SANDBOX_OSD=y
|
||||
CONFIG_BMP_16BPP=y
|
||||
CONFIG_BMP_24BPP=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
CONFIG_W1=y
|
||||
CONFIG_W1_GPIO=y
|
||||
CONFIG_W1_EEPROM=y
|
||||
@@ -367,4 +368,3 @@ CONFIG_TEST_FDTDEC=y
|
||||
CONFIG_UNIT_TEST=y
|
||||
CONFIG_UT_TIME=y
|
||||
CONFIG_UT_DM=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
|
||||
124
include/bootctl.dtsi
Normal file
124
include/bootctl.dtsi
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* This contains the various components needed to boot Ubuntu 24.04 Desktop. It
|
||||
* provides a description of the process, similar to a grub.cfg file.
|
||||
*/
|
||||
|
||||
&boot_ctl {
|
||||
compatible = "bootctl,ubuntu-desktop", "bootctl,logic";
|
||||
|
||||
options {
|
||||
/* use persistent storage for variables */
|
||||
persist-state;
|
||||
|
||||
/* store a default OS in the persistent storage */
|
||||
default-os;
|
||||
|
||||
/* timeout in seconds before booting a default OS */
|
||||
timeout = <5>;
|
||||
|
||||
/* skip timeout if boot was successful */
|
||||
skip-timeout-on-success;
|
||||
|
||||
/* track boot success / failure in state */
|
||||
track-success;
|
||||
|
||||
/* auto-boot the default OS after a timeout */
|
||||
autoboot;
|
||||
|
||||
/* measure loaded images, etc. */
|
||||
#if !defined(CONFIG_QEMU) && !defined(CONFIG_EFI_APP)
|
||||
measure;
|
||||
#endif
|
||||
|
||||
/* restrict labels to boot (separated by space) */
|
||||
#ifdef CONFIG_QEMU
|
||||
labels = "virtio";
|
||||
#endif
|
||||
#ifdef CONFIG_SANDBOX
|
||||
labels = "mmc usb";
|
||||
#endif
|
||||
};
|
||||
|
||||
measure {
|
||||
compatible = "bootctl,simple-measure";
|
||||
|
||||
tpm-log-size = <0x10000>;
|
||||
|
||||
/*
|
||||
* should we use compatible strings for each subnode? That might
|
||||
* provide more flexibility for expansion
|
||||
*/
|
||||
|
||||
os {
|
||||
method = "tpm-pcr";
|
||||
pcr-number = <6>;
|
||||
algos = "sha256";
|
||||
};
|
||||
|
||||
initrd {
|
||||
method = "tpm-pcr";
|
||||
pcr-number = <9>;
|
||||
algos = "sha256";
|
||||
};
|
||||
|
||||
fdt {
|
||||
method = "tpm-pcr";
|
||||
pcr-number = <8>;
|
||||
algos = "sha256";
|
||||
};
|
||||
|
||||
cmdline {
|
||||
method = "tpm-pcr";
|
||||
pcr-number = <8>;
|
||||
algos = "sha256";
|
||||
optional;
|
||||
};
|
||||
};
|
||||
|
||||
oslist-extlinux {
|
||||
compatible = "bootctl,extlinux-oslist";
|
||||
|
||||
/* indicates the filesystems needed to access extlinux */
|
||||
filesystems {
|
||||
ext4 {
|
||||
/* add enabled options here? */
|
||||
};
|
||||
|
||||
fat {
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#ifndef CONFIG_SANDBOX
|
||||
oslist-efi {
|
||||
compatible = "bootctl,efifile-oslist";
|
||||
|
||||
/* indicates the filesystems needed to access EFI files */
|
||||
filesystems {
|
||||
fat {
|
||||
};
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
state {
|
||||
compatible = "bootctl,simple-state", "bootctl,state";
|
||||
|
||||
/* revisit, as we may want to reference the hardware device */
|
||||
#ifdef CONFIG_SANDBOX
|
||||
location = "hostfs", "0";
|
||||
filename = "bootctl.ini";
|
||||
#else
|
||||
location = "efi", "1:5";
|
||||
filename = "/boot/bootctl/bootctl.ini";
|
||||
#endif
|
||||
};
|
||||
|
||||
ui {
|
||||
compatible = "bootctl,multiboot-ui", "bootctl,simple-ui",
|
||||
"bootctl,ui";
|
||||
graphical = "if-available";
|
||||
textual = "if-available";
|
||||
};
|
||||
};
|
||||
41
include/bootctl.h
Normal file
41
include/bootctl.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Uclass implementation for boot schema
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_h
|
||||
#define __bootctl_h
|
||||
|
||||
#include <bootflow.h>
|
||||
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* struct bootctl_uc_plat - information the uclass keeps about each bootctl
|
||||
*
|
||||
* @desc: A long description of the bootctl
|
||||
*/
|
||||
struct bootctl_uc_plat {
|
||||
const char *desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* bootctl_get_dev() - Get a device of a given type
|
||||
*
|
||||
* @type: Type to search for
|
||||
* @devp: Return the device found, on success
|
||||
* Return: 0 on success, or -ve error
|
||||
*/
|
||||
int bootctl_get_dev(enum uclass_id type, struct udevice **devp);
|
||||
|
||||
/**
|
||||
* bootctl_run() - Run a boot
|
||||
*
|
||||
* Return: 0 on success, or -ve error
|
||||
*/
|
||||
int bootctl_run(void);
|
||||
|
||||
#endif
|
||||
158
include/bootctl/logic.h
Normal file
158
include/bootctl/logic.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/* 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>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_logic_h
|
||||
#define __bootctl_logic_h
|
||||
|
||||
#include <bootctl/oslist.h>
|
||||
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* struct logic_priv - Information maintained by the boot logic as it works
|
||||
*
|
||||
* @opt_persist_state: true if state can be preserved across reboots
|
||||
* @opt_default_os: true if we record a default OS to boot
|
||||
* @opt_timeout: boot timeout in seconds
|
||||
* @opt_skip_timeout: true to skip any boot timeout if the last boot succeeded
|
||||
* @opt_track_success: true to track whether the last boot succeeded (made it to
|
||||
* user space)
|
||||
* @opt_labels: if non-NULL, a space-separated list of bootstd labels which can
|
||||
* be used to boot
|
||||
* @opt_autoboot: true to autoboot the default OS after a timeout
|
||||
* @opt_measure: true to measure loaded images, etc.
|
||||
*
|
||||
* @state_loaded: true if the state information has been loaded
|
||||
* @scanning: true if scanning for new OSes
|
||||
* @start_time: monotonic time when the boot started
|
||||
* @next_countdown: next monotonic time to check the timeout
|
||||
* @autoboot_remain_s: remaining autoboot time in seconds
|
||||
* @autoboot_active: true if autoboot is active
|
||||
* @default_os: name of the default OS to boot
|
||||
* @osinfo: List of OSes to show
|
||||
* @refresh: true if we need to refresh the UI because something has changed
|
||||
*
|
||||
* @iter: oslist iterator, used to find new OSes
|
||||
* @selected: index of selected OS in osinfo alist, or -1 if none has been
|
||||
* selected yet
|
||||
* @meas: TPM-measurement device
|
||||
* @oslist: provides OSes to boot; we iterate through each osinfo driver to find
|
||||
* all OSes
|
||||
* @state: provides persistent state
|
||||
* @ui: provides a visual boot menu on a display / console device
|
||||
*/
|
||||
struct logic_priv {
|
||||
bool opt_persist_state;
|
||||
bool opt_default_os;
|
||||
uint opt_timeout;
|
||||
bool opt_skip_timeout;
|
||||
bool opt_track_success;
|
||||
const char *opt_labels;
|
||||
bool opt_autoboot;
|
||||
bool opt_measure;
|
||||
|
||||
bool state_loaded;
|
||||
bool state_saved;
|
||||
bool scanning;
|
||||
ulong start_time;
|
||||
uint next_countdown;
|
||||
uint autoboot_remain_s;
|
||||
bool autoboot_active;
|
||||
const char *default_os;
|
||||
struct alist osinfo;
|
||||
bool refresh;
|
||||
|
||||
struct oslist_iter iter;
|
||||
struct udevice *meas;
|
||||
struct udevice *oslist;
|
||||
struct udevice *state;
|
||||
struct udevice *ui;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bc_logic_ops - Operations related to boot loader
|
||||
*/
|
||||
struct bc_logic_ops {
|
||||
/**
|
||||
* prepare() - Prepare the components needed for the boot
|
||||
*
|
||||
* This sets up the various device, like ui and oslist
|
||||
*
|
||||
* This must be called before start()
|
||||
*
|
||||
* @dev: Logic device
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*prepare)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* start() - Start the boot process
|
||||
*
|
||||
* Gets things ready, shows the UI, etc.
|
||||
*
|
||||
* This pust be called before poll()
|
||||
*
|
||||
* @dev: Logic device
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*start)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* poll() - Poll the boot process
|
||||
*
|
||||
* Try to progress the boot towards a result
|
||||
*
|
||||
* This should be called repeatedly until it either boots and OS (iwc
|
||||
* it won't return) or returns an error code
|
||||
*
|
||||
* @dev: Logic device
|
||||
* Return: does not return if OK, -ESHUTDOWN if something went wrong
|
||||
*/
|
||||
int (*poll)(struct udevice *dev);
|
||||
};
|
||||
|
||||
#define bc_logic_get_ops(dev) ((struct bc_logic_ops *)(dev)->driver->ops)
|
||||
|
||||
/**
|
||||
* bc_logic_prepare() - Prepare the components needed for the boot
|
||||
*
|
||||
* This sets up the various device, like ui and oslist
|
||||
*
|
||||
* This must be called before start()
|
||||
*
|
||||
* @dev: Logic device
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_logic_prepare(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_logic_start() - Start the boot process
|
||||
*
|
||||
* Gets things ready, shows the UI, etc.
|
||||
*
|
||||
* This pust be called before poll()
|
||||
*
|
||||
* @dev: Logic device
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_logic_start(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_logic_poll() - Poll the boot process
|
||||
*
|
||||
* Try to progress the boot towards a result
|
||||
*
|
||||
* This should be called repeatedly until it either boots and OS (iwc
|
||||
* it won't return) or returns an error code
|
||||
*
|
||||
* @dev: Logic device
|
||||
* Return: does not return if OK, -ESHUTDOWN if something went wrong
|
||||
*/
|
||||
int bc_logic_poll(struct udevice *dev);
|
||||
|
||||
#endif
|
||||
95
include/bootctl/measure.h
Normal file
95
include/bootctl/measure.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Implementation of measurement of loaded images and the like
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_measure_h
|
||||
#define __bootctl_measure_h
|
||||
|
||||
#include <alist.h>
|
||||
|
||||
struct osinfo;
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* enum measure_t - Types of measurement supported
|
||||
*
|
||||
* @BCMEAST_OS: OS image loaded from storage
|
||||
* @BCMEAST_INITRD: Initial ramdisk loaded from storage
|
||||
* @BCMEAST_CMDLINE: Command-line arguments constructed for the OS
|
||||
* @BCMEAST_FDT: Flattened device tree for use by the OS
|
||||
*/
|
||||
enum measure_t {
|
||||
BCMEAST_IMAGE,
|
||||
BCMEAST_CMDLINE,
|
||||
BCMEAST_FDT,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct measure_info - Information about a particular measurement
|
||||
*
|
||||
* TODO: Add more details about the measurement
|
||||
*
|
||||
* @img: Image which was measured
|
||||
*/
|
||||
struct measure_info {
|
||||
const struct bootflow_img *img;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bc_measure_ops - Operations for measurement, e.g. with a TPM
|
||||
*/
|
||||
struct bc_measure_ops {
|
||||
/**
|
||||
* start() - Start up ready for measurement
|
||||
*
|
||||
* Sets up the TPM log and starts the TPM
|
||||
*
|
||||
* @dev: Device to access
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*start)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* process() - Measurement of images, etc.
|
||||
*
|
||||
* Process the required measurements to produce results
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @osinfo: OS being booting
|
||||
* @result: Inits and returns a list of struct measure_info records
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*process)(struct udevice *dev, const struct osinfo *osinfo,
|
||||
struct alist *result);
|
||||
};
|
||||
|
||||
#define bc_measure_get_ops(dev) ((struct bc_measure_ops *)(dev)->driver->ops)
|
||||
|
||||
/**
|
||||
* bc_measure_start() - Start up ready for measurement
|
||||
*
|
||||
* Sets up the TPM log and starts the TPM
|
||||
*
|
||||
* @dev: Device to access
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_measure_start(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_measure_process() - Measurement of images, etc.
|
||||
*
|
||||
* Processes the required measurements to produce results
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @osinfo: OS being booting
|
||||
* @result: Inits and returns a list of struct measure_info records
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_measure_process(struct udevice *dev, const struct osinfo *osinfo,
|
||||
struct alist *result);
|
||||
|
||||
#endif
|
||||
70
include/bootctl/oslist.h
Normal file
70
include/bootctl/oslist.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Uclass implementation for boot schema
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_oslist_h
|
||||
#define __bootctl_oslist_h
|
||||
|
||||
#include <bootflow.h>
|
||||
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* struct osinfo - Information about an OS which is available for booting
|
||||
*
|
||||
* @bflow: Bootflow for this OS
|
||||
*/
|
||||
struct osinfo {
|
||||
struct bootflow bflow;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct oslist_iter - Iterator
|
||||
*
|
||||
* @active: true if the scan has started
|
||||
* @bf_iter: bootstd iterator being used
|
||||
*/
|
||||
struct oslist_iter {
|
||||
bool active;
|
||||
struct bootflow_iter bf_iter;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bc_oslist_ops - Operations for displays
|
||||
*/
|
||||
struct bc_oslist_ops {
|
||||
/**
|
||||
* next() - Find the next available OS
|
||||
*
|
||||
* @info: Returns info on success
|
||||
* Return 0 if OK, -ENOENT if there are no more
|
||||
*/
|
||||
int (*next)(struct udevice *dev, struct oslist_iter *iter,
|
||||
struct osinfo *info);
|
||||
};
|
||||
|
||||
#define bc_oslist_get_ops(dev) ((struct bc_oslist_ops *)(dev)->driver->ops)
|
||||
|
||||
/**
|
||||
* bc_oslist_setup_iter() - Set up a new iterator ready for use
|
||||
*
|
||||
* This must be called before using the iterator with bc_oslist_next()
|
||||
*
|
||||
* @iter: Pointer to iterator to set up
|
||||
*/
|
||||
void bc_oslist_setup_iter(struct oslist_iter *iter);
|
||||
|
||||
/**
|
||||
* bc_oslist_next() - Find the next available OS
|
||||
*
|
||||
* @info: Returns info on success
|
||||
* Return 0 if OK, -ENOENT if there are no more
|
||||
*/
|
||||
int bc_oslist_next(struct udevice *dev, struct oslist_iter *iter,
|
||||
struct osinfo *info);
|
||||
|
||||
#endif
|
||||
228
include/bootctl/state.h
Normal file
228
include/bootctl/state.h
Normal file
@@ -0,0 +1,228 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Uclass implementation for boot schema
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_state_h
|
||||
#define __bootctl_state_h
|
||||
|
||||
#include <abuf.h>
|
||||
#include <alist.h>
|
||||
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* struct state - State information which can be read and written
|
||||
*
|
||||
* @bflow: Bootflow for this OS
|
||||
*/
|
||||
struct bc_state {
|
||||
struct alist subnodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bc_state_ops - Operations for the UI
|
||||
*/
|
||||
struct bc_state_ops {
|
||||
/**
|
||||
* read_bool() - Read a boolean value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @valp: Returns boolean value on success
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*read_bool)(struct udevice *dev, const char *key, bool *valp);
|
||||
|
||||
/**
|
||||
* write_bool() - Write a boolean value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @val: Value to write
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*write_bool)(struct udevice *dev, const char *key, bool val);
|
||||
|
||||
/**
|
||||
* read_int() - Read an integer value
|
||||
*
|
||||
* This holds a 64-bit integer
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @valp: Returns integer value on success
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*read_int)(struct udevice *dev, const char *key, long *valp);
|
||||
|
||||
/**
|
||||
* write_str() - Write an integer value
|
||||
*
|
||||
* This holds a 64-bit integer
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @val: Value to write
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*write_int)(struct udevice *dev, const char *key, long val);
|
||||
|
||||
/**
|
||||
* read_str() - Read a string value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @valp: Returns string value on success
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*read_str)(struct udevice *dev, const char *key,
|
||||
const char **valp);
|
||||
|
||||
/**
|
||||
* write_str() - Write a string value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @val: Value to write
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*write_str)(struct udevice *dev, const char *key, const char *val);
|
||||
|
||||
/**
|
||||
* load() - Read in the current state
|
||||
*
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*load)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* save() - Write out the current state
|
||||
*
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*save)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* save_to_buf() - Write the current state to a buffer
|
||||
*
|
||||
* The buffer is inited and filled with the contents of the state as it
|
||||
* would be written to a file
|
||||
*
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*save_to_buf)(struct udevice *dev, struct abuf *buf);
|
||||
|
||||
/**
|
||||
* clear() - Clear all values
|
||||
*
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int (*clear)(struct udevice *dev);
|
||||
};
|
||||
|
||||
#define bc_state_get_ops(dev) ((struct bc_state_ops *)(dev)->driver->ops)
|
||||
|
||||
/**
|
||||
* bc_state_read_bool() - Read a boolean value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @valp: Returns boolean value on success
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_read_bool(struct udevice *dev, const char *key, bool *valp);
|
||||
|
||||
/**
|
||||
* bc_state_write_bool() - Write a boolean value
|
||||
*
|
||||
* Sets the value for a key, overwriting any existing value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @val: Value to write
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_write_bool(struct udevice *dev, const char *key, bool val);
|
||||
|
||||
/**
|
||||
* bc_state_read_int() - Read an integer value
|
||||
*
|
||||
* This holds a 64-bit integer
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @valp: Returns integer value on success
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_read_int(struct udevice *dev, const char *key, long *valp);
|
||||
|
||||
/**
|
||||
* bc_state_write_str() - Write an integer value
|
||||
*
|
||||
* This holds a 64-bit integer
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @val: Value to write
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_write_int(struct udevice *dev, const char *key, long val);
|
||||
|
||||
/**
|
||||
* bc_state_read_str() - Read a string value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @valp: Returns string value on success
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_read_str(struct udevice *dev, const char *key, const char **valp);
|
||||
|
||||
/**
|
||||
* bc_state_write_str() - Write a string value
|
||||
*
|
||||
* @dev: Device to access
|
||||
* @key: Key to access
|
||||
* @val: Value to write
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_write_str(struct udevice *dev, const char *key, const char *val);
|
||||
|
||||
/**
|
||||
* bc_state_load() - Load state from a file
|
||||
*
|
||||
* @dev: Device to access
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_load(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_state_save() - Save state to a file
|
||||
*
|
||||
* @dev: Device to access
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_save(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_state_save_to_buf() - Write the current state to a buffer
|
||||
*
|
||||
* The buffer is inited and filled with the contents of the state as it
|
||||
* would be written to a file
|
||||
*
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_save_to_buf(struct udevice *dev, struct abuf *buf);
|
||||
|
||||
/**
|
||||
* bc_state_clear() - Clear all values
|
||||
*
|
||||
* Return: 0 if OK, or -ve error code
|
||||
*/
|
||||
int bc_state_clear(struct udevice *dev);
|
||||
|
||||
#endif
|
||||
108
include/bootctl/ui.h
Normal file
108
include/bootctl/ui.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Bootctl display
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_display_h
|
||||
#define __bootctl_display_h
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct osinfo;
|
||||
struct oslist_iter;
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* struct bc_ui_ops - Operations for displays
|
||||
*/
|
||||
struct bc_ui_ops {
|
||||
/**
|
||||
* print() - Show a string on the display
|
||||
*
|
||||
* @dev: Display device
|
||||
* @msg: Message to show
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*print)(struct udevice *dev, const char *msg);
|
||||
|
||||
/**
|
||||
* show() - Show the display, ready to accept boot options
|
||||
*
|
||||
* @dev: Display device
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*show)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* add() - Add an OS to the display, so the user can select it
|
||||
*
|
||||
* @dev: Display device
|
||||
* @info: Information about the OS to display
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*add)(struct udevice *dev, struct osinfo *info);
|
||||
|
||||
/**
|
||||
* render() - Render any updates to the display
|
||||
*
|
||||
* @dev: Display device
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*render)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* poll() - Check for user activity
|
||||
*
|
||||
* @dev: Display device
|
||||
* @seqp: Returns the sequence number of the osinfo that is currently
|
||||
* pointed to/highlighted, or -1 if nothing
|
||||
* @selectedp: Returns true if the user selected an item, else false
|
||||
* Return: 0 if OK, -EPIPE if the user tried to quit the menu, other
|
||||
* -ve on error
|
||||
*/
|
||||
int (*poll)(struct udevice *dev, int *seqp, bool *selectedp);
|
||||
};
|
||||
|
||||
#define bc_ui_get_ops(dev) ((struct bc_ui_ops *)(dev)->driver->ops)
|
||||
|
||||
/**
|
||||
* bc_ui_show() - Show the display, ready to accept boot options
|
||||
*
|
||||
* @dev: Display device
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int bc_ui_show(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_ui_add() - Add an OS to the display, so the user can select it
|
||||
*
|
||||
* @dev: Display device
|
||||
* @info: Information about the OS to display
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int bc_ui_add(struct udevice *dev, struct osinfo *info);
|
||||
|
||||
/**
|
||||
* bc_ui_render() - Render any updates to the display
|
||||
*
|
||||
* @dev: Display device
|
||||
* Return 0 if OK, -ve on error
|
||||
*/
|
||||
int bc_ui_render(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* bc_ui_poll() - Check for user activity
|
||||
*
|
||||
* @dev: Display device
|
||||
* @seqp: Returns the sequence number of the osinfo that is currently
|
||||
* pointed to/highlighted, or -1 if nothing
|
||||
* @selectedp: Returns true if the user selected an item, else false
|
||||
* Return: 0 if OK, -EPIPE if the user tried to quit the menu, other
|
||||
* -ve on error
|
||||
*/
|
||||
int bc_ui_poll(struct udevice *dev, int *seqp, bool *selectedp);
|
||||
|
||||
#endif
|
||||
32
include/bootctl/util.h
Normal file
32
include/bootctl/util.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Utility functions
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_util_h
|
||||
#define __bootctl_util_h
|
||||
|
||||
struct alist;
|
||||
struct udevice;
|
||||
|
||||
/**
|
||||
* 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, ...)
|
||||
__attribute__ ((format (__printf__, 2, 3)));
|
||||
|
||||
/**
|
||||
* show_measures() - Show measurements that have been completed
|
||||
*
|
||||
* @result: List of struct measure_info records
|
||||
*/
|
||||
void show_measures(struct alist *result);
|
||||
|
||||
#endif
|
||||
@@ -46,6 +46,11 @@ enum uclass_id {
|
||||
UCLASS_BLK, /* Block device */
|
||||
UCLASS_BLKMAP, /* Composable virtual block device */
|
||||
UCLASS_BOOTCOUNT, /* Bootcount backing store */
|
||||
UCLASS_BOOTCTL, /* Provides the boot logic */
|
||||
UCLASS_BOOTCTL_MEASURE, /* Measurement of images and the like */
|
||||
UCLASS_BOOTCTL_OSLIST, /* Provides list of Operating Systems to boot */
|
||||
UCLASS_BOOTCTL_STATE, /* Stores state information about booting */
|
||||
UCLASS_BOOTCTL_UI, /* Display of information to the user */
|
||||
UCLASS_BOOTDEV, /* Boot device for locating an OS to boot */
|
||||
UCLASS_BOOTMETH, /* Bootmethod for booting an OS */
|
||||
UCLASS_BOOTSTD, /* Standard boot driver */
|
||||
|
||||
@@ -74,6 +74,11 @@ config UT_LIB_RSA
|
||||
|
||||
endif # UT_LIB
|
||||
|
||||
config UT_BOOTCTL
|
||||
bool "Unit tests for boot schema"
|
||||
depends on BOOTSTD && SANDBOX
|
||||
default y
|
||||
|
||||
config UT_BOOTSTD
|
||||
bool "Unit tests for standard boot"
|
||||
depends on BOOTSTD && SANDBOX
|
||||
|
||||
5
test/boot/bootctl/Makefile
Normal file
5
test/boot/bootctl/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright 2025 Canonical Ltd
|
||||
|
||||
obj-y += bootctl.o
|
||||
327
test/boot/bootctl/bootctl.c
Normal file
327
test/boot/bootctl/bootctl.c
Normal file
@@ -0,0 +1,327 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Tests for bootctl
|
||||
*
|
||||
* For now this is just samples, showing how the different functions can be
|
||||
* tested
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <bootctl.h>
|
||||
#include <bootmeth.h>
|
||||
#include <dm.h>
|
||||
#include <os.h>
|
||||
#include <test/ut.h>
|
||||
#include "bootctl_common.h"
|
||||
#include <bootctl/measure.h>
|
||||
#include <bootctl/oslist.h>
|
||||
#include <bootctl/state.h>
|
||||
#include "../bootstd_common.h"
|
||||
|
||||
/* test that expected devices are available and can be probed */
|
||||
static int bootctl_base(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_UI, &dev));
|
||||
ut_asserteq_str("ui", dev->name);
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_OSLIST, &dev));
|
||||
ut_asserteq_str("oslist", dev->name);
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
|
||||
ut_asserteq_str("state", dev->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_base, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test finding an OS */
|
||||
static int bootctl_oslist(struct unit_test_state *uts)
|
||||
{
|
||||
struct oslist_iter iter;
|
||||
struct osinfo info;
|
||||
struct bootflow *bflow = &info.bflow;
|
||||
struct udevice *dev;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_OSLIST, &dev));
|
||||
ut_asserteq_str("oslist", dev->name);
|
||||
|
||||
/* initially we should only see Fedora */
|
||||
bc_oslist_setup_iter(&iter);
|
||||
ut_assertok(bc_oslist_next(dev, &iter, &info));
|
||||
ut_asserteq_str("mmc1.bootdev.part_1", bflow->name);
|
||||
ut_asserteq_strn("Fedora-Workstation", bflow->os_name);
|
||||
|
||||
ut_asserteq(-ENODEV, bc_oslist_next(dev, &iter, &info));
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_oslist, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test finding OSes on mmc and usb */
|
||||
static int bootctl_oslist_usb(struct unit_test_state *uts)
|
||||
{
|
||||
struct oslist_iter iter;
|
||||
struct osinfo info;
|
||||
struct bootflow *bflow = &info.bflow;
|
||||
struct udevice *dev;
|
||||
|
||||
test_set_skip_delays(true);
|
||||
bootstd_reset_usb();
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_OSLIST, &dev));
|
||||
ut_asserteq_str("oslist", dev->name);
|
||||
|
||||
/* include usb in the bootdev order */
|
||||
ut_assertok(bootdev_set_order("mmc usb"));
|
||||
|
||||
bc_oslist_setup_iter(&iter);
|
||||
ut_assertok(bc_oslist_next(dev, &iter, &info));
|
||||
ut_asserteq_str("mmc1.bootdev.part_1", bflow->name);
|
||||
|
||||
ut_assertok(bc_oslist_next(dev, &iter, &info));
|
||||
ut_asserteq_str("hub1.p4.usb_mass_storage.lun0.bootdev.part_1", bflow->name);
|
||||
|
||||
ut_asserteq(-ENODEV, bc_oslist_next(dev, &iter, &info));
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_oslist_usb, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test basic use of state */
|
||||
static int bootctl_simple_state_base(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev;
|
||||
const char *sval;
|
||||
struct abuf buf;
|
||||
bool bval;
|
||||
long ival;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
|
||||
ut_assertok(bc_state_write_bool(dev, "fred", false));
|
||||
ut_assertok(bc_state_write_bool(dev, "mary", true));
|
||||
ut_assertok(bc_state_write_int(dev, "alex", 123));
|
||||
ut_assertok(bc_state_write_str(dev, "john", "abc"));
|
||||
|
||||
ut_assertok(bc_state_read_bool(dev, "fred", &bval));
|
||||
ut_asserteq(false, bval);
|
||||
|
||||
ut_assertok(bc_state_read_bool(dev, "mary", &bval));
|
||||
ut_asserteq(true, bval);
|
||||
|
||||
ut_assertok(bc_state_read_int(dev, "alex", &ival));
|
||||
ut_asserteq(123, ival);
|
||||
|
||||
ut_assertok(bc_state_read_str(dev, "john", &sval));
|
||||
ut_asserteq_str("abc", sval);
|
||||
|
||||
/* check the buffer contents, including the nul terminator */
|
||||
ut_assertok(bc_state_save_to_buf(dev, &buf));
|
||||
ut_asserteq_str("fred=0\nmary=1\nalex=123\njohn=abc\n", buf.data);
|
||||
ut_asserteq(strlen("fred=0\nmary=1\nalex=123\njohn=abc\n") + 1,
|
||||
buf.size);
|
||||
ut_asserteq(0, *((char *)buf.data + buf.size - 1));
|
||||
abuf_uninit(&buf);
|
||||
|
||||
/* overwrite */
|
||||
ut_assertok(bc_state_write_str(dev, "fred", "def"));
|
||||
ut_assertok(bc_state_read_str(dev, "fred", &sval));
|
||||
ut_asserteq_str("def", sval);
|
||||
|
||||
ut_assertok(bc_state_clear(dev));
|
||||
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "fred", &bval));
|
||||
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "mary", &bval));
|
||||
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "john", &bval));
|
||||
ut_asserteq(-ENOENT, bc_state_read_bool(dev, "alex", &bval));
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_simple_state_base, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test loading / saving state */
|
||||
static int bootctl_simple_state_loadsave(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev;
|
||||
char *buf;
|
||||
int size;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
|
||||
ut_assertok(bc_state_write_bool(dev, "fred", false));
|
||||
ut_assertok(bc_state_write_bool(dev, "mary", true));
|
||||
ut_assertok(bc_state_save(dev));
|
||||
|
||||
/* check the file contents, including the nul terminator */
|
||||
ut_assertok(os_read_file("bootctl.ini", (void **)&buf, &size));
|
||||
ut_asserteq_str("fred=0\nmary=1\n", buf);
|
||||
ut_asserteq(strlen("fred=0\nmary=1\n") + 1, size);
|
||||
ut_asserteq(0, buf[size - 1]);
|
||||
os_free(buf);
|
||||
|
||||
ut_assertok(bc_state_load(dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_simple_state_loadsave, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test limits */
|
||||
static int bootctl_simple_state_limits(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev;
|
||||
char long_key[32]; /* avoid using constants from impl */
|
||||
struct abuf buf;
|
||||
char *data;
|
||||
int ch;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
|
||||
|
||||
/* cannot use NULL as a key or value */
|
||||
ut_asserteq(-EINVAL, bc_state_write_bool(dev, NULL, false));
|
||||
ut_asserteq(-EINVAL, bc_state_write_str(dev, "key", NULL));
|
||||
|
||||
/* empty key and value */
|
||||
ut_asserteq(-EINVAL, bc_state_write_str(dev, "", "val"));
|
||||
ut_assertok(bc_state_write_str(dev, "empty", ""));
|
||||
|
||||
/* no spaces allowed in a key */
|
||||
ut_asserteq(-EKEYREJECTED, bc_state_write_str(dev, "my key", "val"));
|
||||
|
||||
/* check key characters */
|
||||
for (ch = 1; ch < 256; ch++) {
|
||||
char key[4] = "key";
|
||||
bool ok;
|
||||
|
||||
ok = ch == '_' || (ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= '0' && ch <= '9');
|
||||
|
||||
key[1] = ch;
|
||||
printf("checking ch %x\n", ch);
|
||||
if (ok)
|
||||
ut_assertok(bc_state_write_str(dev, key, "val"));
|
||||
else
|
||||
ut_asserteq(-EKEYREJECTED, bc_state_write_str(dev, key, "val"));
|
||||
}
|
||||
|
||||
/* key too long */
|
||||
strcpy(long_key, "1234567890123456789012345678901");
|
||||
ut_asserteq(-EKEYREJECTED, bc_state_write_str(dev, long_key, "val"));
|
||||
long_key[30] = '\0';
|
||||
ut_assertok(bc_state_write_str(dev, long_key, "val"));
|
||||
|
||||
/* value too long */
|
||||
abuf_init(&buf);
|
||||
ut_asserteq(true, abuf_realloc(&buf, 0x1002));
|
||||
data = buf.data;
|
||||
memset(data, 'x', 0x1001);
|
||||
data[0x1001] = '\0';
|
||||
ut_asserteq(-E2BIG, bc_state_write_str(dev, "try", data));
|
||||
data[0x1000] = '\0';
|
||||
ut_assertok(bc_state_write_str(dev, "try", data));
|
||||
abuf_uninit(&buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_simple_state_limits, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test integers */
|
||||
static int bootctl_simple_state_int(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev;
|
||||
long ival;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_STATE, &dev));
|
||||
|
||||
/* basic integers */
|
||||
ut_assertok(bc_state_write_int(dev, "val", 0));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq(0, ival);
|
||||
|
||||
ut_assertok(bc_state_write_int(dev, "val", 1));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq(1, ival);
|
||||
|
||||
ut_assertok(bc_state_write_int(dev, "val", -1));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq(-1, ival);
|
||||
|
||||
/* large ints */
|
||||
ut_assertok(bc_state_write_int(dev, "val", 0xffffffffl));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq(0xffffffffl, ival);
|
||||
|
||||
ut_assertok(bc_state_write_int(dev, "val", -0xffffffffl));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq_64(-0xffffffffl, ival);
|
||||
|
||||
ut_assertok(bc_state_write_int(dev, "val", 0x7fffffffffffffffll));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq_64(0x7fffffffffffffffll, ival);
|
||||
|
||||
ut_assertok(bc_state_write_int(dev, "val", -0x7fffffffffffffffll));
|
||||
ut_assertok(bc_state_read_int(dev, "val", &ival));
|
||||
ut_asserteq_64(-0x7fffffffffffffffll, ival);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_simple_state_int, UTF_DM | UTF_SCAN_FDT);
|
||||
|
||||
/* test measurement */
|
||||
static int bootctl_simple_measure(struct unit_test_state *uts)
|
||||
{
|
||||
struct bootflow_img *img[3];
|
||||
struct osinfo osinfo;
|
||||
struct bootflow *bflow = &osinfo.bflow;
|
||||
const struct measure_info *info;
|
||||
struct udevice *dev;
|
||||
struct alist result;
|
||||
|
||||
ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL_MEASURE, &dev));
|
||||
|
||||
ut_assertok(bc_measure_start(dev));
|
||||
|
||||
/* set up some data */
|
||||
memset(&osinfo, '\0', sizeof(struct osinfo));
|
||||
alist_init_struct(&bflow->images, struct bootflow_img);
|
||||
|
||||
/* add a few images */
|
||||
img[0] = bootflow_img_add(bflow, "kernel",
|
||||
(enum bootflow_img_t)IH_TYPE_KERNEL, 0,
|
||||
0x100);
|
||||
ut_assertnonnull(img);
|
||||
img[1] = bootflow_img_add(bflow, "initrd",
|
||||
(enum bootflow_img_t)IH_TYPE_RAMDISK, 0x100,
|
||||
0x200);
|
||||
ut_assertnonnull(img);
|
||||
|
||||
/* the fdt is missing so this should fail */
|
||||
ut_asserteq(-ENOENT, bc_measure_process(dev, &osinfo, &result));
|
||||
if (IS_ENABLED(CONFIG_LOGF_FUNC))
|
||||
ut_assert_nextline(" simple_process() Missing image 'flat_dt'");
|
||||
else
|
||||
ut_assert_nextline("Missing image 'flat_dt'");
|
||||
ut_assert_console_end();
|
||||
|
||||
alist_uninit(&result);
|
||||
|
||||
img[2] = bootflow_img_add(bflow, "fdt",
|
||||
(enum bootflow_img_t)IH_TYPE_FLATDT, 0x300,
|
||||
0x30);
|
||||
ut_assertok(bc_measure_process(dev, &osinfo, &result));
|
||||
|
||||
/* check the result */
|
||||
ut_asserteq(3, result.count);
|
||||
info = alist_get(&result, 0, struct measure_info);
|
||||
ut_asserteq_ptr(img[0], info[0].img);
|
||||
ut_asserteq_ptr(img[1], info[1].img);
|
||||
ut_asserteq_ptr(img[2], info[2].img);
|
||||
|
||||
/* TODO: We should also a) read out the TPM log and b) check TPM PCRs */
|
||||
|
||||
ut_assertnonnull(img);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BOOTCTL_TEST(bootctl_simple_measure, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE);
|
||||
14
test/boot/bootctl/bootctl_common.h
Normal file
14
test/boot/bootctl/bootctl_common.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Common definitions for bootctl tests
|
||||
*
|
||||
* Copyright 2025 Canonical Ltd
|
||||
* Written by Simon Glass <simon.glass@canonical.com>
|
||||
*/
|
||||
|
||||
#ifndef __bootctl_common_h
|
||||
#define __bootctl_common_h
|
||||
|
||||
#define BOOTCTL_TEST(_name, _flags) UNIT_TEST(_name, _flags, bootctl)
|
||||
|
||||
#endif
|
||||
@@ -50,6 +50,7 @@ static int do_ut_info(bool show_suites);
|
||||
SUITE_DECL(addrmap);
|
||||
SUITE_DECL(bdinfo);
|
||||
SUITE_DECL(bloblist);
|
||||
SUITE_DECL(bootctl);
|
||||
SUITE_DECL(bootm);
|
||||
SUITE_DECL(bootstd);
|
||||
SUITE_DECL(cmd);
|
||||
@@ -77,6 +78,7 @@ static struct suite suites[] = {
|
||||
SUITE(addrmap, "very basic test of addrmap command"),
|
||||
SUITE(bdinfo, "bdinfo (board info) command"),
|
||||
SUITE(bloblist, "bloblist implementation"),
|
||||
SUITE(bootctl, "bootctl (boot schema)"),
|
||||
SUITE(bootm, "bootm command"),
|
||||
SUITE(bootstd, "standard boot implementation"),
|
||||
SUITE(cmd, "various commands"),
|
||||
|
||||
Reference in New Issue
Block a user