Files
u-boot/boot/bootctl/logic.c
Simon Glass 46a205b1ca bootctl: Provide an option to slow the display refresh
When a mouse pointer is not used, there is typically no need to refresh
the display unless something changes. Add an option to allow slow
refresh.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-10-18 09:38:25 +01:00

324 lines
7.3 KiB
C

// 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->opt_slow_refresh || 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");
priv->opt_slow_refresh = ofnode_read_bool(node, "slow-refresh");
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),
};