Files
u-boot/boot/expo.c
Simon Glass dffd7b41a5 expo: Ensure that cyclic tasks can run
Since expo now runs with no delays it can trigger the watchdog on
sandbox. Add a call to schedule() to avoid this.

Series-to: concept
Signed-off-by: Simon Glass <sjg@chromium.org>

Change-Id: 0b1b9f90177b7b2e682d57f3df20cf3cdb453517
Series-links: 1:60
2025-11-12 10:20:02 -07:00

624 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of a expo, a collection of scenes providing menu options
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#include <dm.h>
#include <expo.h>
#include <expo_test.h>
#include <log.h>
#include <malloc.h>
#include <mapmem.h>
#include <menu.h>
#include <mouse.h>
#include <time.h>
#include <video.h>
#include <watchdog.h>
#include <linux/delay.h>
#include "scene_internal.h"
int expo_new(const char *name, void *priv, struct expo **expp)
{
struct expo *exp;
int ret;
exp = calloc(1, sizeof(struct expo));
if (!exp)
return log_msg_ret("expo", -ENOMEM);
exp->name = strdup(name);
if (!exp->name) {
free(exp);
return log_msg_ret("name", -ENOMEM);
}
ret = expo_test_init(exp);
if (ret) {
free(exp->name);
free(exp);
return log_msg_ret("tst", ret);
}
exp->priv = priv;
INIT_LIST_HEAD(&exp->scene_head);
INIT_LIST_HEAD(&exp->str_head);
exp->next_id = EXPOID_BASE_ID;
cli_ch_init(&exp->cch);
exp->last_key_ms = get_timer(0);
*expp = exp;
return 0;
}
static void estr_destroy(struct expo_string *estr)
{
free(estr);
}
void expo_destroy(struct expo *exp)
{
struct scene *scn, *next;
struct expo_string *estr, *enext;
expo_test_uninit(exp);
list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
scene_destroy(scn);
list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
estr_destroy(estr);
free(exp->name);
free(exp);
}
uint resolve_id(struct expo *exp, uint id)
{
log_debug("resolve id %d\n", id);
if (!id)
id = exp->next_id++;
else if (id >= exp->next_id)
exp->next_id = id + 1;
return id;
}
void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
{
exp->next_id = dyn_start;
}
int expo_str(struct expo *exp, const char *name, uint id, const char *str)
{
struct expo_string *estr;
estr = calloc(1, sizeof(struct expo_string));
if (!estr)
return log_msg_ret("obj", -ENOMEM);
estr->id = resolve_id(exp, id);
abuf_init_const(&estr->buf, str, strlen(str) + 1);
list_add_tail(&estr->sibling, &exp->str_head);
return estr->id;
}
const char *expo_get_str(struct expo *exp, uint id)
{
struct expo_string *estr;
list_for_each_entry(estr, &exp->str_head, sibling) {
if (estr->id == id)
return estr->buf.data;
}
return NULL;
}
int expo_edit_str(struct expo *exp, uint id, struct abuf *orig,
struct abuf **copyp)
{
struct expo_string *estr;
struct abuf old;
list_for_each_entry(estr, &exp->str_head, sibling) {
if (estr->id == id) {
old = estr->buf;
if (!abuf_copy(&old, &estr->buf))
return -ENOMEM;
*copyp = &estr->buf;
if (orig)
*orig = old;
return 0;
}
}
return -ENOENT;
}
int expo_set_display(struct expo *exp, struct udevice *dev)
{
struct udevice *cons;
int ret;
ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
&cons);
if (ret)
return log_msg_ret("con", ret);
exp->display = dev;
exp->cons = cons;
if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) {
/*
* Tell the mouse driver about the video device for coordinate
* scaling
*/
ret = mouse_set_video(exp->mouse, exp->display);
if (ret)
return log_msg_ret("msv", ret);
mouse_get_pos(exp->mouse, &exp->mouse_pos);
}
return 0;
}
int expo_calc_dims(struct expo *exp)
{
struct scene *scn;
int ret;
if (!exp->cons)
return log_msg_ret("dim", -ENOTSUPP);
list_for_each_entry(scn, &exp->scene_head, sibling) {
ret = scene_calc_dims(scn);
if (ret)
return log_msg_ret("scn", ret);
}
return 0;
}
void expo_set_text_mode(struct expo *exp, bool text_mode)
{
exp->text_mode = text_mode;
}
int expo_set_mouse_enable(struct expo *exp, bool enable)
{
int ret;
if (!IS_ENABLED(CONFIG_MOUSE) || !enable) {
exp->mouse_enabled = false;
return 0;
}
ret = uclass_first_device_err(UCLASS_MOUSE, &exp->mouse);
if (ret)
return log_msg_ret("sme", ret);
/* Get mouse pointer image and dimensions */
exp->mouse_ptr = video_image_getptr(riscos_arrow);
if (exp->mouse_ptr) {
ulong width, height;
uint bpix;
video_bmp_get_info(exp->mouse_ptr, &width, &height, &bpix);
exp->mouse_size.w = width;
exp->mouse_size.h = height;
}
exp->mouse_enabled = true;
return 0;
}
struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
{
struct scene *scn;
list_for_each_entry(scn, &exp->scene_head, sibling) {
if (scn->id == scene_id)
return scn;
}
return NULL;
}
int expo_set_scene_id(struct expo *exp, uint scene_id)
{
struct scene *scn;
scn = expo_lookup_scene_id(exp, scene_id);
if (!scn)
return log_msg_ret("id", -ENOENT);
exp->scene_id = scene_id;
return 0;
}
int expo_first_scene_id(struct expo *exp)
{
struct scene *scn;
if (list_empty(&exp->scene_head))
return -ENOENT;
scn = list_first_entry(&exp->scene_head, struct scene, sibling);
return scn->id;
}
int expo_arrange(struct expo *exp)
{
struct scene *scn;
int ret;
if (!exp->scene_id)
return 0;
scn = expo_lookup_scene_id(exp, exp->scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("ear", ret);
return 0;
}
static int update_mouse_position(struct expo *exp)
{
struct mouse_event event;
int ret;
if (!IS_ENABLED(CONFIG_MOUSE) || !exp->mouse_enabled)
return 0;
/* Process all available mouse events to get latest position */
while (1) {
ret = mouse_get_event(exp->mouse, &event);
if (ret)
break; /* No more events available */
if (event.type == MOUSE_EV_MOTION) {
exp->mouse_pos.x = event.motion.x;
exp->mouse_pos.y = event.motion.y;
} else if (event.type == MOUSE_EV_BUTTON) {
exp->mouse_pos.x = event.button.x;
exp->mouse_pos.y = event.button.y;
}
}
return 0;
}
/**
* render_mouse_pointer() - Render the mouse pointer if enabled and visible
*
* @exp: Expo containing mouse state
* Return: 0 if OK, -ve on error
*/
static int render_mouse_pointer(struct expo *exp)
{
struct udevice *dev = exp->display;
int ret;
if (!IS_ENABLED(CONFIG_MOUSE) || !exp->mouse_enabled || !exp->mouse_ptr)
return 0;
/* Use white (0xffffff) as transparent color */
ret = video_bmp_displaya(dev, map_to_sysmem(exp->mouse_ptr),
exp->mouse_pos.x, exp->mouse_pos.y, false, true,
0xffffff);
if (ret)
log_debug("Failed to display mouse pointer: %d\n", ret);
return 0;
}
static int expo_render_(struct expo *exp, bool dirty_only)
{
struct udevice *dev = exp->display;
struct video_priv *vid_priv = dev_get_uclass_priv(dev);
struct scene *scn = NULL;
enum colour_idx back;
u32 colour;
int ret;
expo_test_mark(exp);
expo_test_update(exp);
back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE;
colour = video_index_to_colour(vid_priv, back);
ret = video_fill(dev, colour);
if (ret)
return log_msg_ret("fill", ret);
if (exp->scene_id) {
scn = expo_lookup_scene_id(exp, exp->scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
ret = scene_render(scn, dirty_only);
if (ret)
return log_msg_ret("ren", ret);
}
/* Render mouse pointer if mouse is enabled */
ret = render_mouse_pointer(exp);
if (ret)
return log_msg_ret("mou", ret);
/* Render test-mode info if enabled */
ret = expo_test_render(exp);
if (ret)
return log_msg_ret("tst", ret);
video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH);
expo_test_sync(exp);
return scn ? 0 : -ECHILD;
}
int expo_render(struct expo *exp)
{
return expo_render_(exp, false);
}
int expo_render_dirty(struct expo *exp)
{
return expo_render_(exp, true);
}
int expo_send_key(struct expo *exp, int key)
{
struct scene *scn = NULL;
if (exp->scene_id) {
int ret;
scn = expo_lookup_scene_id(exp, exp->scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
ret = scene_send_key(scn, key, &exp->action);
if (ret)
return log_msg_ret("key", ret);
/* arrange it to get any changes */
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
}
return scn ? 0 : -ECHILD;
}
int expo_send_click(struct expo *exp, int x, int y)
{
struct scene *scn = NULL;
if (exp->scene_id) {
int ret;
scn = expo_lookup_scene_id(exp, exp->scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
ret = scene_send_click(scn, x, y, &exp->action);
if (ret)
return log_msg_ret("click", ret);
/* arrange it to get any changes */
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
}
return scn ? 0 : -ECHILD;
}
int expo_action_get(struct expo *exp, struct expo_action *act)
{
*act = exp->action;
exp->action.type = EXPOACT_NONE;
return act->type == EXPOACT_NONE ? -EAGAIN : 0;
}
int expo_apply_theme(struct expo *exp, bool do_objs)
{
struct expo_theme *theme = &exp->theme;
struct scene *scn;
if (exp->display)
video_set_white_on_black(exp->display, theme->white_on_black);
if (do_objs) {
list_for_each_entry(scn, &exp->scene_head, sibling) {
int ret;
ret = scene_apply_theme(scn, theme);
if (ret)
return log_msg_ret("asn", ret);
}
}
return 0;
}
int expo_setup_theme(struct expo *exp, ofnode node)
{
struct expo_theme *theme = &exp->theme;
int ret;
log_debug("Applying theme %s\n", ofnode_get_name(node));
memset(theme, '\0', sizeof(struct expo_theme));
ofnode_read_u32(node, "font-size", &theme->font_size);
ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
ofnode_read_u32(node, "menu-title-margin-x",
&theme->menu_title_margin_x);
ofnode_read_u32(node, "textline-label-margin-x",
&theme->textline_label_margin_x);
theme->white_on_black = ofnode_read_bool(node, "white-on-black");
ret = expo_apply_theme(exp, true);
if (ret)
return log_msg_ret("asn", ret);
return 0;
}
int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
void *priv)
{
struct scene *scn;
int ret;
list_for_each_entry(scn, &exp->scene_head, sibling) {
ret = scene_iter_objs(scn, iter, priv);
if (ret)
return log_msg_ret("wr", ret);
}
return 0;
}
static int poll_keys(struct expo *exp)
{
int ch = 0, ichar, key;
ichar = cli_ch_process(&exp->cch, 0);
if (!ichar) {
/* Check once for available input */
if (tstc()) {
ch = getchar();
ichar = cli_ch_process(&exp->cch, ch);
}
if (!ch && get_timer(exp->last_key_ms) >= 10)
ichar = cli_ch_process(&exp->cch, -ETIMEDOUT);
}
key = 0;
if (ichar) {
exp->last_key_ms = get_timer(0);
key = bootmenu_conv_key(ichar);
if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA)
key = ichar;
}
return key ? key : -EAGAIN;
}
static int poll_mouse(struct expo *exp, struct vid_pos *pos)
{
int ret;
if (!exp->mouse_enabled)
return -EAGAIN;
/* First check if we have a click available */
ret = mouse_get_click(exp->mouse, pos);
if (ret)
return log_msg_ret("epm", ret);
return 0; /* Click available */
}
int expo_poll(struct expo *exp, struct expo_action *act)
{
int key, ret = -EAGAIN;
schedule();
expo_test_mark(exp);
/* update mouse position if mouse is enabled */
update_mouse_position(exp);
key = poll_keys(exp);
if (key != -EAGAIN) {
ret = expo_send_key(exp, key);
} else if (IS_ENABLED(CONFIG_MOUSE)) {
struct vid_pos pos;
ret = poll_mouse(exp, &pos);
if (!ret)
ret = expo_send_click(exp, pos.x, pos.y);
}
if (ret) {
expo_test_poll(exp);
return log_msg_ret("epk", ret);
}
/* get the action (either a key or a click) */
ret = expo_action_get(exp, act);
expo_test_poll(exp);
if (ret)
return log_msg_ret("eag", ret);
return 0;
}
void expo_req_size(struct expo *exp, int width, int height)
{
exp->req_width = width;
exp->req_height = height;
}
void expo_enter_mode(struct expo *exp)
{
video_set_manual_sync(true);
if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled)
mouse_set_ptr_visible(exp->mouse, false);
expo_test_checkenv(exp);
}
void expo_exit_mode(struct expo *exp)
{
video_set_manual_sync(false);
if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled)
mouse_set_ptr_visible(exp->mouse, true);
}
void expo_damage_reset(struct expo *exp)
{
exp->damage.x0 = 0;
exp->damage.y0 = 0;
exp->damage.x1 = 0;
exp->damage.y1 = 0;
}
void expo_damage_add(struct expo *exp, const struct vid_bbox *bbox)
{
/* If bbox is invalid (empty), do nothing */
if (bbox->x1 <= bbox->x0 || bbox->y1 <= bbox->y0)
return;
/* If current damage is empty, set it to the new bbox */
if (exp->damage.x1 <= exp->damage.x0 || exp->damage.y1 <= exp->damage.y0) {
exp->damage = *bbox;
} else {
/* Expand damage area to include new bbox */
if (bbox->x0 < exp->damage.x0)
exp->damage.x0 = bbox->x0;
if (bbox->y0 < exp->damage.y0)
exp->damage.y0 = bbox->y0;
if (bbox->x1 > exp->damage.x1)
exp->damage.x1 = bbox->x1;
if (bbox->y1 > exp->damage.y1)
exp->damage.y1 = bbox->y1;
}
}