1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Provide a menu of available bootflows and related options 4 * 5 * Copyright 2022 Google LLC 6 * Written by Simon Glass <sjg@chromium.org> 7 */ 8 9#define LOG_CATEGORY UCLASS_BOOTSTD 10 11#include <common.h> 12#include <bootflow.h> 13#include <bootstd.h> 14#include <cli.h> 15#include <dm.h> 16#include <expo.h> 17#include <malloc.h> 18#include <menu.h> 19#include <video_console.h> 20#include <watchdog.h> 21#include <linux/delay.h> 22#include "bootflow_internal.h" 23 24/** 25 * struct menu_priv - information about the menu 26 * 27 * @num_bootflows: Number of bootflows in the menu 28 */ 29struct menu_priv { 30 int num_bootflows; 31}; 32 33int bootflow_menu_new(struct expo **expp) 34{ 35 struct udevice *last_bootdev; 36 struct scene_obj_menu *menu; 37 struct menu_priv *priv; 38 struct bootflow *bflow; 39 struct scene *scn; 40 struct expo *exp; 41 void *logo; 42 int ret, i; 43 44 priv = calloc(1, sizeof(*priv)); 45 if (!priv) 46 return log_msg_ret("prv", -ENOMEM); 47 48 ret = expo_new("bootflows", priv, &exp); 49 if (ret) 50 return log_msg_ret("exp", ret); 51 52 ret = scene_new(exp, "main", MAIN, &scn); 53 if (ret < 0) 54 return log_msg_ret("scn", ret); 55 56 ret |= scene_txt_str(scn, "prompt", OBJ_PROMPT, STR_PROMPT, 57 "UP and DOWN to choose, ENTER to select", NULL); 58 59 ret = scene_menu(scn, "main", OBJ_MENU, &menu); 60 ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100); 61 ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE, 62 "U-Boot - Boot Menu", NULL); 63 ret |= scene_menu_set_title(scn, OBJ_MENU, OBJ_PROMPT); 64 65 logo = video_get_u_boot_logo(); 66 if (logo) { 67 ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL); 68 ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, -4, 4); 69 } 70 71 ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">", 72 NULL); 73 ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER); 74 if (ret < 0) 75 return log_msg_ret("new", -EINVAL); 76 77 last_bootdev = NULL; 78 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36; 79 ret = bootflow_next_glob(&bflow), i++) { 80 char str[2], *label, *key; 81 uint preview_id; 82 bool add_gap; 83 84 if (bflow->state != BOOTFLOWST_READY) 85 continue; 86 87 *str = i < 10 ? '0' + i : 'A' + i - 10; 88 str[1] = '\0'; 89 key = strdup(str); 90 if (!key) 91 return log_msg_ret("key", -ENOMEM); 92 label = strdup(dev_get_parent(bflow->dev)->name); 93 if (!label) { 94 free(key); 95 return log_msg_ret("nam", -ENOMEM); 96 } 97 98 add_gap = last_bootdev != bflow->dev; 99 last_bootdev = bflow->dev; 100 101 ret = expo_str(exp, "prompt", STR_POINTER, ">"); 102 ret |= scene_txt_str(scn, "label", ITEM_LABEL + i, 103 STR_LABEL + i, label, NULL); 104 ret |= scene_txt_str(scn, "desc", ITEM_DESC + i, STR_DESC + i, 105 bflow->os_name ? bflow->os_name : 106 bflow->name, NULL); 107 ret |= scene_txt_str(scn, "key", ITEM_KEY + i, STR_KEY + i, key, 108 NULL); 109 preview_id = 0; 110 if (bflow->logo) { 111 preview_id = ITEM_PREVIEW + i; 112 ret |= scene_img(scn, "preview", preview_id, 113 bflow->logo, NULL); 114 } 115 ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + i, 116 ITEM_KEY + i, ITEM_LABEL + i, 117 ITEM_DESC + i, preview_id, 118 add_gap ? SCENEMIF_GAP_BEFORE : 0, 119 NULL); 120 121 if (ret < 0) 122 return log_msg_ret("itm", -EINVAL); 123 priv->num_bootflows++; 124 } 125 126 ret = scene_arrange(scn); 127 if (ret) 128 return log_msg_ret("arr", ret); 129 130 *expp = exp; 131 132 return 0; 133} 134 135int bootflow_menu_apply_theme(struct expo *exp, ofnode node) 136{ 137 struct menu_priv *priv = exp->priv; 138 struct scene *scn; 139 u32 font_size; 140 int ret; 141 142 log_debug("Applying theme %s\n", ofnode_get_name(node)); 143 scn = expo_lookup_scene_id(exp, MAIN); 144 if (!scn) 145 return log_msg_ret("scn", -ENOENT); 146 147 /* Avoid error-checking optional items */ 148 if (!ofnode_read_u32(node, "font-size", &font_size)) { 149 int i; 150 151 log_debug("font size %d\n", font_size); 152 scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size); 153 scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size); 154 for (i = 0; i < priv->num_bootflows; i++) { 155 ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL, 156 font_size); 157 if (ret) 158 return log_msg_ret("des", ret); 159 scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size); 160 scene_txt_set_font(scn, ITEM_LABEL + i, NULL, 161 font_size); 162 } 163 } 164 165 ret = scene_arrange(scn); 166 if (ret) 167 return log_msg_ret("arr", ret); 168 169 return 0; 170} 171 172int bootflow_menu_run(struct bootstd_priv *std, bool text_mode, 173 struct bootflow **bflowp) 174{ 175 struct cli_ch_state s_cch, *cch = &s_cch; 176 struct bootflow *sel_bflow; 177 struct udevice *dev; 178 struct expo *exp; 179 uint sel_id; 180 bool done; 181 int ret; 182 183 cli_ch_init(cch); 184 185 sel_bflow = NULL; 186 *bflowp = NULL; 187 188 ret = bootflow_menu_new(&exp); 189 if (ret) 190 return log_msg_ret("exp", ret); 191 192 if (ofnode_valid(std->theme)) { 193 ret = bootflow_menu_apply_theme(exp, std->theme); 194 if (ret) 195 return log_msg_ret("thm", ret); 196 } 197 198 /* For now we only support a video console */ 199 ret = uclass_first_device_err(UCLASS_VIDEO, &dev); 200 if (ret) 201 return log_msg_ret("vid", ret); 202 ret = expo_set_display(exp, dev); 203 if (ret) 204 return log_msg_ret("dis", ret); 205 206 ret = expo_set_scene_id(exp, MAIN); 207 if (ret) 208 return log_msg_ret("scn", ret); 209 210 if (text_mode) 211 expo_set_text_mode(exp, text_mode); 212 213 done = false; 214 do { 215 struct expo_action act; 216 int ichar, key; 217 218 ret = expo_render(exp); 219 if (ret) 220 break; 221 222 ichar = cli_ch_process(cch, 0); 223 if (!ichar) { 224 while (!ichar && !tstc()) { 225 schedule(); 226 mdelay(2); 227 ichar = cli_ch_process(cch, -ETIMEDOUT); 228 } 229 if (!ichar) { 230 ichar = getchar(); 231 ichar = cli_ch_process(cch, ichar); 232 } 233 } 234 235 key = 0; 236 if (ichar) { 237 key = bootmenu_conv_key(ichar); 238 if (key == BKEY_NONE) 239 key = ichar; 240 } 241 if (!key) 242 continue; 243 244 ret = expo_send_key(exp, key); 245 if (ret) 246 break; 247 248 ret = expo_action_get(exp, &act); 249 if (!ret) { 250 switch (act.type) { 251 case EXPOACT_SELECT: 252 sel_id = act.select.id; 253 done = true; 254 break; 255 case EXPOACT_QUIT: 256 done = true; 257 break; 258 default: 259 break; 260 } 261 } 262 } while (!done); 263 264 if (ret) 265 return log_msg_ret("end", ret); 266 267 if (sel_id) { 268 struct bootflow *bflow; 269 int i; 270 271 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36; 272 ret = bootflow_next_glob(&bflow), i++) { 273 if (i == sel_id - ITEM) { 274 sel_bflow = bflow; 275 break; 276 } 277 } 278 } 279 280 expo_destroy(exp); 281 282 if (!sel_bflow) 283 return -EAGAIN; 284 *bflowp = sel_bflow; 285 286 return 0; 287} 288