]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
x86: coreboot: Allow building an expo for editing CMOS config
authorSimon Glass <sjg@chromium.org>
Mon, 14 Oct 2024 22:32:11 +0000 (16:32 -0600)
committerTom Rini <trini@konsulko.com>
Mon, 4 Nov 2024 03:27:12 +0000 (21:27 -0600)
Coreboot provides the CMOS layout in the tables it passes to U-Boot.
Use that to build an editor for the CMOS settings.

Signed-off-by: Simon Glass <sjg@chromium.org>
boot/Makefile
boot/expo_build_cb.c [new file with mode: 0644]
cmd/cedit.c
doc/board/coreboot/coreboot.rst
doc/develop/cedit.rst
doc/usage/cmd/cbcmos.rst
doc/usage/cmd/cedit.rst
include/expo.h
test/cmd/coreboot.c

index 0e0afad68d1749f0aefd7ece837c8a084ed49d41..43def7c33d7b67283cdeaca5266601b4429389ee 100644 (file)
@@ -59,6 +59,9 @@ obj-$(CONFIG_$(PHASE_)LOAD_FIT) += common_fit.o
 
 obj-$(CONFIG_$(PHASE_)EXPO) += expo.o scene.o expo_build.o
 obj-$(CONFIG_$(PHASE_)EXPO) += scene_menu.o scene_textline.o
+ifdef CONFIG_COREBOOT_SYSINFO
+obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo_build_cb.o
+endif
 
 obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE) += vbe.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_REQUEST) += vbe_request.o
diff --git a/boot/expo_build_cb.c b/boot/expo_build_cb.c
new file mode 100644 (file)
index 0000000..442ad76
--- /dev/null
@@ -0,0 +1,245 @@
+// 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;
+}
index fec67a8e334980b3aa7b8e6253cbab5f8a994faa..f696356419e49b43675f289f0d084df4647184ab 100644 (file)
@@ -67,6 +67,28 @@ static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc,
        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[])
 {
@@ -271,6 +293,9 @@ static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
 
 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"
@@ -281,6 +306,9 @@ U_BOOT_LONGHELP(cedit,
 
 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),
index a177265c16eb97e1dbf9b0f0b55521aafc724ab7..f52b24ff43db3eabe6bc3b4b646da60d950cc3f3 100644 (file)
@@ -182,3 +182,9 @@ CI runs tests using a pre-built coreboot image. This ensures that U-Boot can
 boot as a coreboot payload, based on a known-good build of coreboot.
 
 To update the `coreboot.rom` file which is used, see ``tools/Dockerfile``
+
+Editing CMOS RAM settings
+-------------------------
+
+U-Boot supports creating a configuration editor to edit coreboot CMOS-RAM
+settings. See :ref:`cedit_cb_load`.
index 310be8892404fd117a4af956d70f04783baf6574..1ac55ab1219b2ef46164c952052c48f94c47f768 100644 (file)
@@ -172,4 +172,4 @@ Cedit provides several options for persistent settings:
 
 For now, reading and writing settings is not automatic. See the
 :doc:`../usage/cmd/cedit` for how to do this on the command line or in a
-script.
+script. For x86 devices, see :ref:`cedit_cb_load`.
index 156521dd02b9165fb79baa4f357478e98364a295..9395cf1cbd71d659972fdc5170d6259253a395af 100644 (file)
@@ -40,3 +40,6 @@ CMOS RAM::
     Checksum 6600 written
     => cbc check
     =>
+
+See also :ref:`cedit_cb_load` which shows an example that includes the
+configuration editor.
index f29f1b3f38899d90bfb0a388fdedfca5b05c3ce3..e54ea204b9f06ac56c2a8c23830e9117810776c6 100644 (file)
@@ -18,6 +18,7 @@ Synopsis
     cedit write_env [-v]
     cedit read_env [-v]
     cedit write_cmos [-v] [dev]
+    cedit cb_load
 
 Description
 -----------
@@ -92,6 +93,13 @@ updated.
 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
 -------
@@ -158,3 +166,71 @@ Here is an example with the device specified::
 
     => 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
+    =>
index 8cb37260db53e8904ecec7254868934f812a4513..3c383d2e2ee03cc4f3edbc2d08e7b351233862e4 100644 (file)
@@ -762,4 +762,12 @@ int expo_apply_theme(struct expo *exp, ofnode node);
  */
 int expo_build(ofnode root, struct expo **expp);
 
+/**
+ * cb_expo_build() - Build an expo for coreboot CMOS RAM
+ *
+ * @expp: Returns the expo created
+ * Return: 0 if OK, -ve on error
+ */
+int cb_expo_build(struct expo **expp);
+
 #endif /*__EXPO_H */
index e1acf8697e879bb6ec41013cc671eacd6eedbbb4..a99898d15c48ccb62054736fa986d6b163819077 100644 (file)
@@ -6,12 +6,16 @@
  * Written by Simon Glass <sjg@chromium.org>
  */
 
+#include <cedit.h>
 #include <command.h>
 #include <dm.h>
+#include <expo.h>
 #include <rtc.h>
+#include <test/cedit-test.h>
 #include <test/cmd.h>
 #include <test/test.h>
 #include <test/ut.h>
+#include "../../boot/scene_internal.h"
 
 enum {
        CSUM_LOC        = 0x3f0 / 8,
@@ -82,3 +86,34 @@ static int test_cmd_cbcmos(struct unit_test_state *uts)
        return 0;
 }
 CMD_TEST(test_cmd_cbcmos, UTF_CONSOLE);
+
+/* test 'cedit cb_load' command */
+static int test_cmd_cedit_cb_load(struct unit_test_state *uts)
+{
+       struct scene_obj_menu *menu;
+       struct video_priv *vid_priv;
+       struct scene_obj_txt *txt;
+       struct scene *scn;
+       struct expo *exp;
+       int scn_id;
+
+       ut_assertok(run_command("cedit cb_load", 0));
+       ut_assertok(run_command("cedit read_cmos", 0));
+       ut_assert_console_end();
+
+       exp = cur_exp;
+       scn_id = cedit_prepare(exp, &vid_priv, &scn);
+       ut_assert(scn_id > 0);
+       ut_assertnonnull(scn);
+
+       /* just do a very basic test that the first menu is present */
+       menu = scene_obj_find(scn, scn->highlight_id, SCENEOBJT_NONE);
+       ut_assertnonnull(menu);
+
+       txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_NONE);
+       ut_assertnonnull(txt);
+       ut_asserteq_str("Boot option", expo_get_str(exp, txt->str_id));
+
+       return 0;
+}
+CMD_TEST(test_cmd_cedit_cb_load, UTF_CONSOLE);