Dear Maintainers,

I hope this message finds you well.

Attached is the patch containing some of my changes I'm proposing:
0001-boot-Allow-setting-bootorder-from-CMOS-entry.patch

The change allows seabios to load bootorder not only from CBFS but
also from the NVRAM as well, making use of coreboot's cmos.layout.

To prepare testing environment:
- Prepare the coreboot source code with
coreboot-qemu-q35-cmos.layout-test.patch.
Configure coreboot for QEMU Q35 platform. For the payload, you can
select anything (I simply choose SeaBIOS for simplicity), with
secondary payloads of coreinfo and nvramcui. I've attached the
qemu-q35-defconfig to eliminate possible ambiguity.
- Compile a coreboot.rom.
- Prepare the SeaBIOS source code with
0001-boot-Allow-setting-bootorder-from-CMOS-entry.patch, and compile it.
- On the newly compiled coreboot.rom, use cbfstool (which gets
compiled along with the coreboot.rom) to remove fallback/payload, then
add the newly compiled bios.bin.elf as the new fallback/payload.
- Prepare a Linux bootable. In general, any Linux system would do as
long as you can run the required software. I go with Alpine Linux live
disk as its ISO image is only 64MB. Any additional files go in a
secondary disk, including its overlay system. I simply created a 16MB
image using dd command and then formatted it with FAT.
- Compile cbmem and nvramtool from coreboot's util directory. For my
Alpine Linux setup, I need to make sure to compile with musl instead
of glibc (e.g. in an Alpine chroot)
- Include cbmem and nvramtool to the Linux bootable. For my Alpine
setup, I simply have a secondary disk image (also where I put the
overlay).
- For convenience, I'd put everything in the same directory.

To actually test:
- Run QEMU with `qemu-system-x86_64 -enable-kvm -cpu host -M q35 -bios
coreboot-qemu.rom -m 1G <disks>`
- For the QEMU options, serial output can be used as well, however it
tends to break when loading coreinfo & nvramcui. Regardless, it's
useful when all I have to do is debug with cbmem output.
- On the BIOS screen, press Esc to see the menu. Note the disks should
be on top. Boot to the disk.
- Once your Linux system boots on QEMU, login to CLI as root, use
nvramtool to modify the 'bootorder' entry. I myself ran `nvramtool -w
bootorder="/rom@img/coreinfo"` to make it boot coreinfo first thing.
- Reboot the QEMU system (do NOT kill the QEMU), press Esc again and
see that coreinfo now jumps to the top. You can also not press Esc and
see it loads coreinfo. Feel free to replace coreinfo with something
else!

Some todo
- Create a tool to modify bootorder more properly (i.e. loading from
file would be nice, for starters) -- in progress
- Make bootorder a reserved section on cmos.layout to prevent
unintended changes.
- Test on a read hardware, I have a Thinkpad X230 eager to try it

Do let me know if you have any feedback!


Warm regards,
Timothy Kenno Handojo

Attachment: qemu-q35-defconfig
Description: Binary data

From 055a831ed9872b99bf6d060c70b495e286ded1ba Mon Sep 17 00:00:00 2001
From: Timothy Kenno Handojo <timkenhan@mailbox.org>
Date: Thu, 31 Oct 2024 08:11:38 +0700
Subject: [PATCH] boot: Allow setting bootorder from CMOS entry

The original bootorder from CBFS remains, but takes lower priority.

Signed-off-by: Timothy Kenno Handojo <timkenhan@mailbox.org>
---
 Makefile    |   2 +-
 src/boot.c  |   9 +++-
 src/nvram.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/nvram.h |  37 +++++++++++++++
 4 files changed, 176 insertions(+), 2 deletions(-)
 create mode 100644 src/nvram.c
 create mode 100644 src/nvram.h

diff --git a/Makefile b/Makefile
index d3341870..48f0b9ac 100644
--- a/Makefile
+++ b/Makefile
@@ -46,7 +46,7 @@ SRC32FLAT=$(SRCBOTH) post.c e820map.c malloc.c romfile.c x86.c		\
     fw/mtrr.c fw/xen.c fw/acpi.c fw/mptable.c fw/pirtable.c		\
     fw/smbios.c fw/romfile_loader.c fw/dsdt_parser.c hw/virtio-ring.c	\
     hw/virtio-pci.c hw/virtio-mmio.c hw/virtio-blk.c hw/virtio-scsi.c	\
-    hw/tpm_drivers.c hw/nvme.c sha256.c sha512.c
+    hw/tpm_drivers.c hw/nvme.c sha256.c sha512.c nvram.c
 SRC32SEG=string.c output.c pcibios.c apm.c stacks.c hw/pci.c hw/serialio.c
 DIRS=src src/hw src/fw vgasrc
 
diff --git a/src/boot.c b/src/boot.c
index 1effd802..0689d7de 100644
--- a/src/boot.c
+++ b/src/boot.c
@@ -21,6 +21,8 @@
 #include "string.h" // memset
 #include "util.h" // irqtimer_calc
 #include "tcgbios.h" // tpm_*
+#include "nvram.h" // get_nvram_bootorder
+
 
 /****************************************************************
  * Helper search functions
@@ -250,8 +252,13 @@ loadBootOrder(void)
 {
     if (!CONFIG_BOOTORDER)
         return;
+    char *f = NULL;
+
+    f = get_nvram_bootorder();
+
+    if (!f)
+        f = romfile_loadfile("bootorder", NULL);
 
-    char *f = romfile_loadfile("bootorder", NULL);
     if (!f)
         return;
 
diff --git a/src/nvram.c b/src/nvram.c
new file mode 100644
index 00000000..0634cf9c
--- /dev/null
+++ b/src/nvram.c
@@ -0,0 +1,130 @@
+#include "nvram.h"
+#include "string.h" // memcmp, strlen
+#include "malloc.h" // malloc_low
+#include "x86.h" // inb, outb
+#include "output.h" // dprintf
+#include "util.h" // cb_header, find_cb_table, find_cb_subtable
+
+#define CB_TAG_CMOS_OPTION_TABLE 0x00c8
+#define CB_TAG_OPTION 0x00c9
+#define CB_TAG_OPTION_CHECKSUM 0x00cc
+
+
+u8 nvram_read(u8 addr){
+	u16 rtc_port = addr < 128 ? RTC_PORT_STANDARD : RTC_PORT_EXTENDED;
+
+	outb(addr, rtc_port);
+	return inb(rtc_port + 1);
+}
+
+void nvram_write(u8 val, u8 addr)
+{
+	u16 rtc_port = addr < 128 ? RTC_PORT_STANDARD : RTC_PORT_EXTENDED;
+
+	outb(addr, rtc_port);
+	outb(val, rtc_port + 1);
+}
+
+struct nvram_accessor {
+	u8 (*read)(u8 reg);
+	void (*write)(u8 val, u8 reg);
+};
+
+struct nvram_accessor *use_nvram = &(struct nvram_accessor) {
+	nvram_read,
+	nvram_write
+};
+
+static struct cb_cmos_entries *lookup_cmos_entry(struct cb_cmos_option_table *option_table, const char *name)
+{
+	struct cb_cmos_entries *cmos_entry, *next;
+	int len = name ? strlen(name) : 0;
+
+	/* CMOS entries are located right after the option table */
+	cmos_entry = (struct cb_cmos_entries*)((unsigned char *)option_table + option_table->header_length);
+	while (cmos_entry) {
+		if (memcmp((const char*)cmos_entry->name, name, len) == 0)
+			return cmos_entry;
+		next = (struct cb_cmos_entries*)((unsigned char *)cmos_entry + cmos_entry->size);
+		cmos_entry = (next->tag == CB_TAG_OPTION) ? next : NULL;
+	}
+
+	dprintf(1, "ERROR: No such CMOS option (%s)\n", name);
+	return NULL;
+}
+
+static int get_cmos_value(const struct nvram_accessor *nvram, u32 bitnum, u32 len, void *valptr)
+{
+	u8 *value = valptr;
+	int offs = 0;
+	u32 addr, bit;
+	u8 reg8;
+
+	/* Convert to byte borders */
+	addr=(bitnum / 8);
+	bit=(bitnum % 8);
+
+	/* Handle single byte or less */
+	if(len <= 8) {
+		reg8 = nvram->read(addr);
+		reg8 >>= bit;
+		value[0] = reg8 & ((1 << len) -1);
+		return 0;
+	}
+
+	/* When handling more than a byte, copy whole bytes */
+	while (len > 0) {
+		len -= 8;
+		value[offs++]=nvram->read(addr++);
+	}
+
+	return 0;
+}
+
+int options_checksum_valid(const struct nvram_accessor *nvram, struct cb_cmos_checksum *option_checksum)
+{
+	int i;
+	int checksum_location = option_checksum->location / 8;
+	u16 checksum = 0, checksum_old;
+
+	for(i = option_checksum->range_start; i <= option_checksum->range_end; i++) {
+		checksum += nvram->read(i);
+	}
+
+	checksum_old = ((nvram->read(checksum_location)<<8) | nvram->read(checksum_location+1));
+
+	return (checksum_old == checksum);
+}
+
+void *get_nvram_bootorder()
+{
+	const char *name = "bootorder";
+	struct cb_header *cbh = find_cb_table();
+
+    if (!cbh)
+        return NULL;
+    struct cb_cmos_option_table *option_table = find_cb_subtable(cbh, CB_TAG_CMOS_OPTION_TABLE);
+    struct cb_cmos_checksum *option_checksum = find_cb_subtable(cbh, CB_TAG_OPTION_CHECKSUM);
+    if (option_table == NULL) {
+        dprintf(1, "Could not find coreboot option table.\n");
+        return NULL;
+    }
+
+	struct cb_cmos_entries *cmos_entry = lookup_cmos_entry(option_table, name);
+
+	if (!cmos_entry)
+		return NULL;
+	int cmos_length = cmos_entry->length;
+
+	/* extra byte to ensure 0-terminated strings */
+	void *buf = malloc_low(cmos_length+1);
+	memset(buf, 0, cmos_length+1);
+
+	if(!options_checksum_valid(use_nvram, option_checksum)) {
+		dprintf(1, "Invalid option checksum.\n");
+		return NULL;
+	}
+	get_cmos_value(use_nvram, cmos_entry->bit, cmos_entry->length, buf);
+
+    return buf;
+}
diff --git a/src/nvram.h b/src/nvram.h
new file mode 100644
index 00000000..4c9d559e
--- /dev/null
+++ b/src/nvram.h
@@ -0,0 +1,37 @@
+#ifndef __NVRAM_H
+#define __NVRAM_H
+
+#include "types.h" // u32, u8
+
+#define RTC_PORT_STANDARD      0x70
+#define RTC_PORT_EXTENDED      0x72
+
+struct cb_cmos_option_table {
+	u32 tag;
+	u32 size;
+	u32 header_length;
+};
+
+#define CB_CMOS_MAX_NAME_LENGTH    32
+struct cb_cmos_entries {
+	u32 tag;
+	u32 size;
+	u32 bit;
+	u32 length;
+	u32 config;
+	u32 config_id;
+	u8 name[CB_CMOS_MAX_NAME_LENGTH];
+};
+
+struct	cb_cmos_checksum {
+	u32 tag;
+	u32 size;
+	u32 range_start;
+	u32 range_end;
+	u32 location;
+	u32 type;
+};
+
+void *get_nvram_bootorder();
+
+#endif
\ No newline at end of file
-- 
2.39.5

diff --git a/src/mainboard/emulation/qemu-q35/cmos.layout b/src/mainboard/emulation/qemu-q35/cmos.layout
index 5e5a901da0..10ff567a42 100644
--- a/src/mainboard/emulation/qemu-q35/cmos.layout
+++ b/src/mainboard/emulation/qemu-q35/cmos.layout
@@ -11,8 +11,10 @@ entries
 448	1	e	1	power_on_after_fail
 452	4	e	6	debug_level
 
+456	400	s	0	bootorder
+
 # VBOOT
-464	128	r	0	vbnv
+880	128	r	0	vbnv
 
 1008	16	h	0	check_sum
 
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-le...@seabios.org

Reply via email to