mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 20:18:18 +00:00 
			
		
		
		
	The global bootmeths don't set the dev in bootflow struct which means the dev_get_parent(bflow->dev) triggers a NULL-pointer dereference and crash U-Boot. So before trying to handle a bootflow, check that the associated bootmeth isn't global, otherwise skip it. Suggested-by: Simon Glass <sjg@chromium.org> Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de> Reviewed-by: Simon Glass <sjg@chromium.org>
		
			
				
	
	
		
			294 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * Provide a menu of available bootflows and related options
 | |
|  *
 | |
|  * Copyright 2022 Google LLC
 | |
|  * Written by Simon Glass <sjg@chromium.org>
 | |
|  */
 | |
| 
 | |
| #define LOG_CATEGORY UCLASS_BOOTSTD
 | |
| 
 | |
| #include <bootflow.h>
 | |
| #include <bootmeth.h>
 | |
| #include <bootstd.h>
 | |
| #include <cli.h>
 | |
| #include <dm.h>
 | |
| #include <expo.h>
 | |
| #include <malloc.h>
 | |
| #include <menu.h>
 | |
| #include <video_console.h>
 | |
| #include <watchdog.h>
 | |
| #include <linux/delay.h>
 | |
| #include "bootflow_internal.h"
 | |
| 
 | |
| /**
 | |
|  * struct menu_priv - information about the menu
 | |
|  *
 | |
|  * @num_bootflows: Number of bootflows in the menu
 | |
|  */
 | |
| struct menu_priv {
 | |
| 	int num_bootflows;
 | |
| };
 | |
| 
 | |
| int bootflow_menu_new(struct expo **expp)
 | |
| {
 | |
| 	struct udevice *last_bootdev;
 | |
| 	struct scene_obj_menu *menu;
 | |
| 	struct menu_priv *priv;
 | |
| 	struct bootflow *bflow;
 | |
| 	struct scene *scn;
 | |
| 	struct expo *exp;
 | |
| 	void *logo;
 | |
| 	int ret, i;
 | |
| 
 | |
| 	priv = calloc(1, sizeof(*priv));
 | |
| 	if (!priv)
 | |
| 		return log_msg_ret("prv", -ENOMEM);
 | |
| 
 | |
| 	ret = expo_new("bootflows", priv, &exp);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("exp", ret);
 | |
| 
 | |
| 	ret = scene_new(exp, "main", MAIN, &scn);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("scn", ret);
 | |
| 
 | |
| 	ret |= scene_txt_str(scn, "prompt", OBJ_PROMPT, STR_PROMPT,
 | |
| 			     "UP and DOWN to choose, ENTER to select", NULL);
 | |
| 
 | |
| 	ret = scene_menu(scn, "main", OBJ_MENU, &menu);
 | |
| 	ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
 | |
| 	ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
 | |
| 			     "U-Boot - Boot Menu", NULL);
 | |
| 	ret |= scene_menu_set_title(scn, OBJ_MENU, OBJ_PROMPT);
 | |
| 
 | |
| 	logo = video_get_u_boot_logo();
 | |
| 	if (logo) {
 | |
| 		ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
 | |
| 		ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, -4, 4);
 | |
| 	}
 | |
| 
 | |
| 	ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
 | |
| 			     NULL);
 | |
| 	ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("new", -EINVAL);
 | |
| 
 | |
| 	last_bootdev = NULL;
 | |
| 	for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
 | |
| 	     ret = bootflow_next_glob(&bflow), i++) {
 | |
| 		struct bootmeth_uc_plat *ucp;
 | |
| 		char str[2], *label, *key;
 | |
| 		uint preview_id;
 | |
| 		bool add_gap;
 | |
| 
 | |
| 		if (bflow->state != BOOTFLOWST_READY)
 | |
| 			continue;
 | |
| 
 | |
| 		/* No media to show for BOOTMETHF_GLOBAL bootmeths */
 | |
| 		ucp = dev_get_uclass_plat(bflow->method);
 | |
| 		if (ucp->flags & BOOTMETHF_GLOBAL)
 | |
| 			continue;
 | |
| 
 | |
| 		*str = i < 10 ? '0' + i : 'A' + i - 10;
 | |
| 		str[1] = '\0';
 | |
| 		key = strdup(str);
 | |
| 		if (!key)
 | |
| 			return log_msg_ret("key", -ENOMEM);
 | |
| 		label = strdup(dev_get_parent(bflow->dev)->name);
 | |
| 		if (!label) {
 | |
| 			free(key);
 | |
| 			return log_msg_ret("nam", -ENOMEM);
 | |
| 		}
 | |
| 
 | |
| 		add_gap = last_bootdev != bflow->dev;
 | |
| 		last_bootdev = bflow->dev;
 | |
| 
 | |
| 		ret = expo_str(exp, "prompt", STR_POINTER, ">");
 | |
| 		ret |= scene_txt_str(scn, "label", ITEM_LABEL + i,
 | |
| 				      STR_LABEL + i, label, NULL);
 | |
| 		ret |= scene_txt_str(scn, "desc", ITEM_DESC + i, STR_DESC + i,
 | |
| 				    bflow->os_name ? bflow->os_name :
 | |
| 				    bflow->name, NULL);
 | |
| 		ret |= scene_txt_str(scn, "key", ITEM_KEY + i, STR_KEY + i, key,
 | |
| 				      NULL);
 | |
| 		preview_id = 0;
 | |
| 		if (bflow->logo) {
 | |
| 			preview_id = ITEM_PREVIEW + i;
 | |
| 			ret |= scene_img(scn, "preview", preview_id,
 | |
| 					     bflow->logo, NULL);
 | |
| 		}
 | |
| 		ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + i,
 | |
| 					  ITEM_KEY + i, ITEM_LABEL + i,
 | |
| 					  ITEM_DESC + i, preview_id,
 | |
| 					  add_gap ? SCENEMIF_GAP_BEFORE : 0,
 | |
| 					  NULL);
 | |
| 
 | |
| 		if (ret < 0)
 | |
| 			return log_msg_ret("itm", -EINVAL);
 | |
| 		priv->num_bootflows++;
 | |
| 	}
 | |
| 
 | |
| 	ret = scene_arrange(scn);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("arr", ret);
 | |
| 
 | |
| 	*expp = exp;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
 | |
| {
 | |
| 	struct menu_priv *priv = exp->priv;
 | |
| 	struct scene *scn;
 | |
| 	u32 font_size;
 | |
| 	int ret;
 | |
| 
 | |
| 	log_debug("Applying theme %s\n", ofnode_get_name(node));
 | |
| 	scn = expo_lookup_scene_id(exp, MAIN);
 | |
| 	if (!scn)
 | |
| 		return log_msg_ret("scn", -ENOENT);
 | |
| 
 | |
| 	/* Avoid error-checking optional items */
 | |
| 	if (!ofnode_read_u32(node, "font-size", &font_size)) {
 | |
| 		int i;
 | |
| 
 | |
| 		log_debug("font size %d\n", font_size);
 | |
| 		scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
 | |
| 		scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
 | |
| 		for (i = 0; i < priv->num_bootflows; i++) {
 | |
| 			ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
 | |
| 						 font_size);
 | |
| 			if (ret)
 | |
| 				return log_msg_ret("des", ret);
 | |
| 			scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
 | |
| 			scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
 | |
| 					   font_size);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ret = scene_arrange(scn);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("arr", ret);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
 | |
| 		      struct bootflow **bflowp)
 | |
| {
 | |
| 	struct cli_ch_state s_cch, *cch = &s_cch;
 | |
| 	struct bootflow *sel_bflow;
 | |
| 	struct udevice *dev;
 | |
| 	struct expo *exp;
 | |
| 	uint sel_id;
 | |
| 	bool done;
 | |
| 	int ret;
 | |
| 
 | |
| 	cli_ch_init(cch);
 | |
| 
 | |
| 	sel_bflow = NULL;
 | |
| 	*bflowp = NULL;
 | |
| 
 | |
| 	ret = bootflow_menu_new(&exp);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("exp", ret);
 | |
| 
 | |
| 	if (ofnode_valid(std->theme)) {
 | |
| 		ret = bootflow_menu_apply_theme(exp, std->theme);
 | |
| 		if (ret)
 | |
| 			return log_msg_ret("thm", ret);
 | |
| 	}
 | |
| 
 | |
| 	/* For now we only support a video console */
 | |
| 	ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("vid", ret);
 | |
| 	ret = expo_set_display(exp, dev);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("dis", ret);
 | |
| 
 | |
| 	ret = expo_set_scene_id(exp, MAIN);
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("scn", ret);
 | |
| 
 | |
| 	if (text_mode)
 | |
| 		expo_set_text_mode(exp, text_mode);
 | |
| 
 | |
| 	done = false;
 | |
| 	do {
 | |
| 		struct expo_action act;
 | |
| 		int ichar, key;
 | |
| 
 | |
| 		ret = expo_render(exp);
 | |
| 		if (ret)
 | |
| 			break;
 | |
| 
 | |
| 		ichar = cli_ch_process(cch, 0);
 | |
| 		if (!ichar) {
 | |
| 			while (!ichar && !tstc()) {
 | |
| 				schedule();
 | |
| 				mdelay(2);
 | |
| 				ichar = cli_ch_process(cch, -ETIMEDOUT);
 | |
| 			}
 | |
| 			if (!ichar) {
 | |
| 				ichar = getchar();
 | |
| 				ichar = cli_ch_process(cch, ichar);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		key = 0;
 | |
| 		if (ichar) {
 | |
| 			key = bootmenu_conv_key(ichar);
 | |
| 			if (key == BKEY_NONE)
 | |
| 				key = ichar;
 | |
| 		}
 | |
| 		if (!key)
 | |
| 			continue;
 | |
| 
 | |
| 		ret = expo_send_key(exp, key);
 | |
| 		if (ret)
 | |
| 			break;
 | |
| 
 | |
| 		ret = expo_action_get(exp, &act);
 | |
| 		if (!ret) {
 | |
| 			switch (act.type) {
 | |
| 			case EXPOACT_SELECT:
 | |
| 				sel_id = act.select.id;
 | |
| 				done = true;
 | |
| 				break;
 | |
| 			case EXPOACT_QUIT:
 | |
| 				done = true;
 | |
| 				break;
 | |
| 			default:
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	} while (!done);
 | |
| 
 | |
| 	if (ret)
 | |
| 		return log_msg_ret("end", ret);
 | |
| 
 | |
| 	if (sel_id) {
 | |
| 		struct bootflow *bflow;
 | |
| 		int i;
 | |
| 
 | |
| 		for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
 | |
| 		     ret = bootflow_next_glob(&bflow), i++) {
 | |
| 			if (i == sel_id - ITEM) {
 | |
| 				sel_bflow = bflow;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	expo_destroy(exp);
 | |
| 
 | |
| 	if (!sel_bflow)
 | |
| 		return -EAGAIN;
 | |
| 	*bflowp = sel_bflow;
 | |
| 
 | |
| 	return 0;
 | |
| }
 |