--- /dev/null
+// 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 <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;
+}
return 0;
}
+#ifdef CONFIG_COREBOOT_SYSINFO
+static int do_cedit_cb_load(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct expo *exp;
+ int ret;
+
+ if (argc > 1)
+ return CMD_RET_USAGE;
+
+ ret = cb_expo_build(&exp);
+ if (ret) {
+ printf("Failed to build expo: %dE\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ cur_exp = exp;
+
+ return 0;
+}
+#endif /* CONFIG_COREBOOT_SYSINFO */
+
static int do_cedit_write_fdt(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
U_BOOT_LONGHELP(cedit,
"load <interface> <dev[:part]> <filename> - load config editor\n"
+#ifdef CONFIG_COREBOOT_SYSINFO
+ "cb_load - load coreboot CMOS editor\n"
+#endif
"cedit read_fdt <i/f> <dev[:part]> <filename> - read settings\n"
"cedit write_fdt <i/f> <dev[:part]> <filename> - write settings\n"
"cedit read_env [-v] - read settings from env vars\n"
U_BOOT_CMD_WITH_SUBCMDS(cedit, "Configuration editor", cedit_help_text,
U_BOOT_SUBCMD_MKENT(load, 5, 1, do_cedit_load),
+#ifdef CONFIG_COREBOOT_SYSINFO
+ U_BOOT_SUBCMD_MKENT(cb_load, 5, 1, do_cedit_cb_load),
+#endif
U_BOOT_SUBCMD_MKENT(read_fdt, 5, 1, do_cedit_read_fdt),
U_BOOT_SUBCMD_MKENT(write_fdt, 5, 1, do_cedit_write_fdt),
U_BOOT_SUBCMD_MKENT(read_env, 2, 1, do_cedit_read_env),
cedit write_env [-v]
cedit read_env [-v]
cedit write_cmos [-v] [dev]
+ cedit cb_load
Description
-----------
Normally the first RTC device is used to hold the data. You can specify a
different device by name using the `dev` parameter.
+.. _cedit_cb_load:
+
+cedit cb_load
+~~~~~~~~~~~~~
+
+This is supported only on x86 devices booted from coreboot. It creates a new
+configuration editor which can be used to edit CMOS settings.
Example
-------
=> cedit write_cmos rtc@43
=>
+
+This example shows editing coreboot CMOS-RAM settings. A script could be used
+to automate this::
+
+ => cbsysinfo
+ Coreboot table at 500, size 5c4, records 1d (dec 29), decoded to 000000007dce3f40, forwarded to 000000007ff9a000
+
+ CPU KHz : 0
+ Serial I/O port: 00000000
+ base : 00000000
+ pointer : 000000007ff9a370
+ type : 1
+ base : 000003f8
+ baud : 0d115200
+ regwidth : 1
+ input_hz : 0d1843200
+ PCI addr : 00000010
+ Mem ranges : 7
+ id: type || base || size
+ 0: 10:table 0000000000000000 0000000000001000
+ 1: 01:ram 0000000000001000 000000000009f000
+ 2: 02:reserved 00000000000a0000 0000000000060000
+ 3: 01:ram 0000000000100000 000000007fe6d000
+ 4: 10:table 000000007ff6d000 0000000000093000
+ 5: 02:reserved 00000000fec00000 0000000000001000
+ 6: 02:reserved 00000000ff800000 0000000000800000
+ option_table: 000000007ff9a018
+ Bit Len Cfg ID Name
+ 0 180 r 0 reserved_memory
+ 180 1 e 4 boot_option 0:Fallback 1:Normal
+ 184 4 h 0 reboot_counter
+ 190 8 r 0 reserved_century
+ 1b8 8 r 0 reserved_ibm_ps2_century
+ 1c0 1 e 1 power_on_after_fail 0:Disable 1:Enable
+ 1c4 4 e 6 debug_level 5:Notice 6:Info 7:Debug 8:Spew
+ 1d0 80 r 0 vbnv
+ 3f0 10 h 0 check_sum
+ CMOS start : 1c0
+ CMOS end : 1cf
+ CMOS csum loc: 3f0
+ VBNV start : ffffffff
+ VBNV size : ffffffff
+ ...
+ Unimpl. : 10 37 40
+
+Check that the CMOS RAM checksum is correct, then create a configuration editor
+and load the settings from CMOS RAM::
+
+ => cbcmos check
+ => cedit cb
+ => cedit read_cmos
+
+Now run the cedit. In this case the user selected 'save' so `cedit run` returns
+success::
+
+ => if cedit run; then cedit write_cmos -v; fi
+ Write 2 bytes from offset 30 to 38
+ => echo $?
+ 0
+
+Update the checksum in CMOS RAM::
+
+ => cbcmos check
+ Checksum 6100 error: calculated 7100
+ => cbcmos update
+ Checksum 7100 written
+ => cbcmos check
+ =>