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>
324 lines
7.3 KiB
C
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),
|
|
};
|