x86: coreboot: Add a command to check and update CMOS RAM

Coreboot tables provide information about the CMOS-RAM checksum. Add a
command which can check and update this.

With this it is possible to adjust CMOS-RAM settings and tidy up the
checksum afterwards.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 4fba9fe..6368336 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2871,6 +2871,17 @@
 	  memory by coreboot before jumping to U-Boot. It can be useful for
 	  debugging the beaaviour of coreboot or U-Boot.
 
+config CMD_CBCMOS
+	bool "cbcmos"
+	depends on X86
+	default y if SYS_COREBOOT
+	help
+	  This provides information options to check the CMOS RAM checksum,
+	  if present, as well as to update it.
+
+	  It is useful when coreboot CMOS-RAM settings must be examined or
+	  updated.
+
 config CMD_CYCLIC
 	bool "cyclic - Show information about cyclic functions"
 	depends on CYCLIC
diff --git a/cmd/x86/Makefile b/cmd/x86/Makefile
index 9252152..5f3f5be 100644
--- a/cmd/x86/Makefile
+++ b/cmd/x86/Makefile
@@ -2,6 +2,7 @@
 
 obj-$(CONFIG_CMD_CBSYSINFO) += cbsysinfo.o
 obj-y += cpuid.o msr.o mtrr.o
+obj-$(CONFIG_CMD_CBCMOS) += cbcmos.o
 obj-$(CONFIG_CMD_EXCEPTION) += exception.o
 obj-$(CONFIG_USE_HOB) += hob.o
 obj-$(CONFIG_HAVE_FSP) += fsp.o
diff --git a/cmd/x86/cbcmos.c b/cmd/x86/cbcmos.c
new file mode 100644
index 0000000..fe5582f
--- /dev/null
+++ b/cmd/x86/cbcmos.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for booting from coreboot
+ *
+ * Copyright 2021 Google LLC
+ */
+
+#define LOG_CATEGORY	UCLASS_RTC
+
+#include <command.h>
+#include <dm.h>
+#include <rtc.h>
+#include <asm/cb_sysinfo.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+const struct sysinfo_t *get_table(void)
+{
+	if (!gd->arch.coreboot_table) {
+		printf("No coreboot sysinfo table found\n");
+		return NULL;
+	}
+
+	return &lib_sysinfo;
+}
+
+static int calc_sum(struct udevice *dev, uint start_bit, uint bit_count)
+{
+	uint start_byte = start_bit / 8;
+	uint byte_count = bit_count / 8;
+	int ret, i;
+	uint sum;
+
+	log_debug("Calc sum from %x: %x bytes\n", start_byte, byte_count);
+	sum = 0;
+	for (i = 0; i < bit_count / 8; i++) {
+		ret = rtc_read8(dev, start_bit / 8 + i);
+		if (ret < 0)
+			return ret;
+		sum += ret;
+	}
+
+	return (sum & 0xff) << 8 | (sum & 0xff00) >> 8;
+}
+
+/**
+ * prep_cbcmos() - Prepare for a CMOS-RAM command
+ *
+ * @tab: coreboot table
+ * @devnum: RTC device name to use, or NULL for the first one
+ * @dep: Returns RTC device on success
+ * Return: calculated checksum for CMOS RAM or -ve on error
+ */
+static int prep_cbcmos(const struct sysinfo_t *tab, const char *devname,
+		       struct udevice **devp)
+{
+	struct udevice *dev;
+	int ret;
+
+	if (!tab)
+		return CMD_RET_FAILURE;
+	if (devname)
+		ret = uclass_get_device_by_name(UCLASS_RTC, devname, &dev);
+	else
+		ret = uclass_first_device_err(UCLASS_RTC, &dev);
+	if (ret) {
+		printf("Failed to get RTC device: %dE\n", ret);
+		return ret;
+	}
+
+	ret = calc_sum(dev, tab->cmos_range_start,
+		       tab->cmos_range_end + 1 - tab->cmos_range_start);
+	if (ret < 0) {
+		printf("Failed to read RTC device: %dE\n", ret);
+		return ret;
+	}
+	*devp = dev;
+
+	return ret;
+}
+
+static int do_cbcmos_check(struct cmd_tbl *cmdtp, int flag, int argc,
+			   char *const argv[])
+{
+	const struct sysinfo_t *tab = get_table();
+	struct udevice *dev;
+	u16 cur, sum;
+	int ret;
+
+	ret = prep_cbcmos(tab, argv[1], &dev);
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+	sum = ret;
+
+	ret = rtc_read16(dev, tab->cmos_checksum_location / 8, &cur);
+	if (ret < 0) {
+		printf("Failed to read RTC device: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+	if (sum != cur) {
+		printf("Checksum %04x error: calculated %04x\n", cur, sum);
+		return CMD_RET_FAILURE;
+	}
+
+	return 0;
+}
+
+static int do_cbcmos_update(struct cmd_tbl *cmdtp, int flag, int argc,
+			    char *const argv[])
+{
+	const struct sysinfo_t *tab = get_table();
+	struct udevice *dev;
+	u16 sum;
+	int ret;
+
+	ret = prep_cbcmos(tab, argv[1], &dev);
+	if (ret < 0)
+		return CMD_RET_FAILURE;
+	sum = ret;
+
+	ret = rtc_write16(dev, tab->cmos_checksum_location / 8, sum);
+	if (ret < 0) {
+		printf("Failed to read RTC device: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+	printf("Checksum %04x written\n", sum);
+
+	return 0;
+}
+
+U_BOOT_LONGHELP(cbcmos,
+	"check     - check CMOS RAM\n"
+	"cbcmos update    - Update CMOS-RAM checksum";
+);
+
+U_BOOT_CMD_WITH_SUBCMDS(cbcmos, "coreboot CMOS RAM", cbcmos_help_text,
+	U_BOOT_SUBCMD_MKENT(check, 2, 1, do_cbcmos_check),
+	U_BOOT_SUBCMD_MKENT(update, 2, 1, do_cbcmos_update));