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

