Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * Building an expo from an FDT description
 *
 * Copyright 2022 Google LLC
 * Written by Simon Glass <sjg@chromium.org>
 */

#define LOG_CATEGORY	LOGC_EXPO

#include <cedit.h>
#include <ctype.h>
#include <errno.h>
#include <expo.h>
#include <log.h>
#include <malloc.h>
#include <string.h>
#include <vsprintf.h>
#include <asm/cb_sysinfo.h>

/**
 * struct build_info - Information to use when building
 */
struct build_info {
	const struct cb_cmos_option_table *tab;
	struct cedit_priv *priv;
};

/**
 * convert_to_title() - Convert text to 'title' format and allocate a string
 *
 * Converts "this_is_a_test" to "This is a test" so it looks better
 *
 * @text: Text to convert
 * Return: Allocated string, or NULL if out of memory
 */
static char *convert_to_title(const char *text)
{
	int len = strlen(text);
	char *buf, *s;

	buf = malloc(len + 1);
	if (!buf)
		return NULL;

	for (s = buf; *text; s++, text++) {
		if (s == buf)
			*s = toupper(*text);
		else if (*text == '_')
			*s = ' ';
		else
			*s = *text;
	}
	*s = '\0';

	return buf;
}

/**
 * menu_build() - Build a menu and add it to a scene
 *
 * See doc/developer/expo.rst for a description of the format
 *
 * @info: Build information
 * @entry: CMOS entry to build a menu for
 * @scn: Scene to add the menu to
 * @objp: Returns the object pointer
 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
 * error, -ENOENT if there is a references to a non-existent string
 */
static int menu_build(struct build_info *info,
		      const struct cb_cmos_entries *entry, struct scene *scn,
		      struct scene_obj **objp)
{
	struct scene_obj_menu *menu;
	const void *ptr, *end;
	uint menu_id;
	char *title;
	int ret, i;

	ret = scene_menu(scn, entry->name, 0, &menu);
	if (ret < 0)
		return log_msg_ret("men", ret);
	menu_id = ret;

	title = convert_to_title(entry->name);
	if (!title)
		return log_msg_ret("con", -ENOMEM);

	/* Set the title */
	ret = scene_txt_str(scn, "title", 0, 0, title, NULL);
	if (ret < 0)
		return log_msg_ret("tit", ret);
	menu->title_id = ret;

	end = (void *)info->tab + info->tab->size;
	for (ptr = (void *)info->tab + info->tab->header_length, i = 0;
	     ptr < end; i++) {
		const struct cb_cmos_enums *enums = ptr;
		struct scene_menitem *item;
		uint label;

		ptr += enums->size;
		if (enums->tag != CB_TAG_OPTION_ENUM ||
		    enums->config_id != entry->config_id)
			continue;

		ret = scene_txt_str(scn, enums->text, 0, 0, enums->text, NULL);
		if (ret < 0)
			return log_msg_ret("tit", ret);
		label = ret;

		ret = scene_menuitem(scn, menu_id, simple_xtoa(i), 0, 0, label,
				     0, 0, 0, &item);
		if (ret < 0)
			return log_msg_ret("mi", ret);
		item->value = enums->value;
	}
	*objp = &menu->obj;

	return 0;
}

/**
 * scene_build() - Build a scene and all its objects
 *
 * See doc/developer/expo.rst for a description of the format
 *
 * @info: Build information
 * @scn: Scene to add the object to
 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
 * error, -ENOENT if there is a references to a non-existent string
 */
static int scene_build(struct build_info *info, struct expo *exp)
{
	struct scene_obj_menu *menu;
	const void *ptr, *end;
	struct scene_obj *obj;
	struct scene *scn;
	uint label, menu_id;
	int ret;

	ret = scene_new(exp, "cmos", 0, &scn);
	if (ret < 0)
		return log_msg_ret("scn", ret);

	ret = scene_txt_str(scn, "title", 0, 0, "CMOS RAM settings", NULL);
	if (ret < 0)
		return log_msg_ret("add", ret);
	scn->title_id = ret;

	ret = scene_txt_str(scn, "prompt", 0, 0,
			    "UP and DOWN to choose, ENTER to select", NULL);
	if (ret < 0)
		return log_msg_ret("add", ret);

	end = (void *)info->tab + info->tab->size;
	for (ptr = (void *)info->tab + info->tab->header_length; ptr < end;) {
		const struct cb_cmos_entries *entry;
		const struct cb_record *rec = ptr;

		entry = ptr;
		ptr += rec->size;
		if (rec->tag != CB_TAG_OPTION)
			continue;
		switch (entry->config) {
		case 'e':
			ret = menu_build(info, entry, scn, &obj);
			break;
		default:
			continue;
		}
		if (ret < 0)
			return log_msg_ret("add", ret);

		obj->start_bit = entry->bit;
		obj->bit_length = entry->length;
	}

	ret = scene_menu(scn, "save", EXPOID_SAVE, &menu);
	if (ret < 0)
		return log_msg_ret("men", ret);
	menu_id = ret;

	ret = scene_txt_str(scn, "save", 0, 0, "Save and exit", NULL);
	if (ret < 0)
		return log_msg_ret("sav", ret);
	label = ret;
	ret = scene_menuitem(scn, menu_id, "save", 0, 0, label,
			     0, 0, 0, NULL);
	if (ret < 0)
		return log_msg_ret("mi", ret);

	ret = scene_menu(scn, "nosave", EXPOID_DISCARD, &menu);
	if (ret < 0)
		return log_msg_ret("men", ret);
	menu_id = ret;

	ret = scene_txt_str(scn, "nosave", 0, 0, "Exit without saving", NULL);
	if (ret < 0)
		return log_msg_ret("nos", ret);
	label = ret;
	ret = scene_menuitem(scn, menu_id, "exit", 0, 0, label,
			     0, 0, 0, NULL);
	if (ret < 0)
		return log_msg_ret("mi", ret);

	return 0;
}

static int build_it(struct build_info *info, struct expo **expp)
{
	struct expo *exp;
	int ret;

	ret = expo_new("coreboot", NULL, &exp);
	if (ret)
		return log_msg_ret("exp", ret);
	expo_set_dynamic_start(exp, EXPOID_BASE_ID);

	ret = scene_build(info, exp);
	if (ret < 0)
		return log_msg_ret("scn", ret);

	*expp = exp;

	return 0;
}

int cb_expo_build(struct expo **expp)
{
	struct build_info info;
	struct expo *exp;
	int ret;

	info.tab = lib_sysinfo.option_table;
	if (!info.tab)
		return log_msg_ret("tab", -ENOENT);

	ret = build_it(&info, &exp);
	if (ret)
		return log_msg_ret("bui", ret);
	*expp = exp;

	return 0;
}