amd: Fall back to reading rom3 range in case of ROM Armor

AMD is pushing ROM Armor forward, which leaves the SPI handling to
the PSP and only a mailbox interface (guarded by SMM) for the main
CPU. With the current ROM Armor 3, there is no opt-out in the BIOS
setup anymore.

Only access left for the main CPU is the read-only memory mapping.
We make this available when active ROM Armor is detected (SPI BAR
register reads all ff). Probing of the flash size is peculiar, we
can only try to guess it when memory contents look repetitive.

To not pollute the `amd_spi100` driver, we start a new one.

Story: https://icon.sourcearcade.org/posts/amd_firmware_reading/
Change-Id: Ib4866084fe80853fd66501176dbc6b766750062f
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/350
diff --git a/amd_rom3read.c b/amd_rom3read.c
new file mode 100644
index 0000000..9708ea9
--- /dev/null
+++ b/amd_rom3read.c
@@ -0,0 +1,228 @@
+/*
+ * This file is part of the flashprog project.
+ *
+ * Copyright (C) 2025 Nico Huber <nico.h@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "flash.h"
+#include "hwaccess_physmap.h"
+#include "programmer.h"
+
+struct spi100 {
+	const uint8_t *memory;
+	size_t size_override;
+};
+
+static uint16_t spi100_read16(const char *spibar, unsigned int reg)
+{
+	return mmio_readw(spibar + reg);
+}
+
+static uint32_t spi100_read32(const char *spibar, unsigned int reg)
+{
+	return mmio_readl(spibar + reg);
+}
+
+static uint64_t spi100_read64(const char *spibar, unsigned int reg)
+{
+	return (uint64_t)mmio_readl(spibar + reg + 4) << 32 | mmio_readl(spibar + reg);
+}
+
+static int spi100_mmap_read(struct flashctx *flash, uint8_t *dst, unsigned int start, unsigned int len)
+{
+	const struct spi100 *const spi100 = flash->mst.opaque->data;
+	mmio_readn_aligned(spi100->memory + start, dst, len, 8);
+	return 0;
+}
+
+static int compare64(const char *const s1, const char *const s2, unsigned int offset)
+{
+	offset &= ~63;
+	return memcmp(s1 + offset, s2 + offset, 64);
+}
+
+/* Compare two memory ranges at pseudo-random offsets. */
+static int compare_sparse(const void *const s1, const void *const s2, const size_t n)
+{
+	const unsigned int offsets[] = {
+		12, 123, 1234, 12345, 123456, 123456, 1234567, 12345678, 123456789,
+		0x12, 0x123, 0x1234, 0x12345, 0x123456, 0x1234567, 0x12345678,
+		0, 01, 012, 0123, 01234, 012345, 0123456, 01234567,
+	};
+	const unsigned int step = 1234;
+
+	if (n < step + 64)
+		return 0;
+
+	unsigned int i;
+	for (i = 0; i < ARRAY_SIZE(offsets); ++i) {
+		const unsigned int offset = offsets[i] % ((n - 64) / step) * step;
+
+		const int diff1 = compare64(s1, s2, offset);
+		if (diff1)
+			return diff1;
+
+		const int diff2 = compare64(s1, s2, n - 64 - offset);
+		if (diff2)
+			return diff2;
+	}
+
+	return 0;
+}
+
+static int rom3read_probe(struct flashctx *const flash)
+{
+	const struct spi100 *const spi100 = flash->mst.opaque->data;
+	const void *const rom3 = spi100->memory;
+
+	size_t flash_size = spi100->size_override;
+	if (flash_size)
+		goto size_known;
+
+	/*
+	 * Only thing to probe is the size. That's going to be peculiar,
+	 * though: As the whole 64MiB rom3 range is decoded, we can only
+	 * look for repeating memory contents.
+	 */
+	msg_pinfo("Trying to probe flash size based on its contents and read patterns. If this\n"
+		  "doesn't work, you can override probing with `-p internal:rom_size_mb=<size>`.\n");
+
+	/*
+	 * We start comparing the two halves of the 64 MiB space. And if
+	 * they match, split the lower half, and so on until we find a
+	 * mismatch (or not, in the unlikely case of empty memory?).
+	 */
+	for (flash_size = 64*MiB; flash_size > 0; flash_size /= 2) {
+		if (compare_sparse(rom3, rom3 + flash_size / 2, flash_size / 2))
+			break;
+	}
+
+size_known:
+	flash->chip->total_size = flash_size / KiB;
+	flash->chip->feature_bits |= FEATURE_NO_ERASE;
+	flash->chip->tested =
+		(struct tested){ .probe = OK, .read = OK, .erase = NA, .write = NA, .wp = NA };
+
+	return !!flash->chip->total_size;
+}
+
+static int rom3read_read(struct flashctx *const flash, uint8_t *buf, unsigned int start, unsigned int len)
+{
+	/* Use top-aligned decoding, for some reason it's
+	   faster after using the bottom end for probing. */
+	start += 64*MiB - flashprog_flash_getsize(flash);
+	return flashprog_read_chunked(flash, buf, start, len, MAX_DATA_READ_UNLIMITED, spi100_mmap_read);
+}
+
+static int rom3read_write(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len)
+{
+	msg_perr("Write is not supported with ROM Armor enabled.\n");
+	return 1;
+}
+
+static int rom3read_erase(struct flashctx *flash, unsigned int blockaddr, unsigned int blocklen)
+{
+	msg_perr("Erase is not supported with ROM Armor enabled.\n");
+	return 1;
+}
+
+static int rom3read_shutdown(void *spi100)
+{
+	free(spi100);
+	return 0;
+}
+
+static const struct opaque_master rom3read_master = {
+	.max_data_read	= MAX_DATA_UNSPECIFIED,
+	.max_data_write	= MAX_DATA_UNSPECIFIED,
+	.probe		= rom3read_probe,
+	.read		= rom3read_read,
+	.write		= rom3read_write,
+	.erase		= rom3read_erase,
+	.shutdown	= rom3read_shutdown,
+};
+
+static bool spi100_check_4ba(const void *const spibar)
+{
+	const uint16_t rom2_addr_override = spi100_read16(spibar, 0x30);
+	const uint32_t addr32_ctrl3 = spi100_read32(spibar, 0x5c);
+
+	/* Most bits are undocumented ("reserved"), so we play safe. */
+	if (rom2_addr_override != 0x14c0) {
+		msg_perr("ROM2 address override *not* in default configuration.\n");
+		return false;
+	}
+
+	/* Another override (xor'ed) for the most-significant address bits. */
+	if (addr32_ctrl3 & 0xff) {
+		msg_perr("SPI ROM page bits set: 0x%02x\n", addr32_ctrl3 & 0xff);
+		return false;
+	}
+
+	return true;
+}
+
+int amd_rom3read_probe(const void *const spibar, const void *const rom2,
+		       const void *const rom3, const size_t rom3_len)
+{
+	if (rom3_len != 64*MiB) {
+		msg_perr("Error: Only 64MiB rom range 3 supported.\n");
+		return ERROR_FATAL;
+	}
+
+	if (!spi100_check_4ba(spibar))
+		return ERROR_FATAL;
+
+	size_t size = 0;
+	char *const size_override = extract_programmer_param("rom_size_mb");
+	if (size_override) {
+		char *endptr;
+		size = strtoul(size_override, &endptr, 10);
+		if (*endptr || size < 1 || size > 64 || (size & (size - 1)))  {
+			msg_perr("Error: Invalid ROM size override: \"%s\".\n"
+				 "Valid values are powers of 2 from 1 through 64 (MiB).\n",
+				 size_override);
+			free(size_override);
+			return -1;
+		}
+		size *= MiB;
+	}
+	free(size_override);
+
+	const uint64_t rom3_base = spi100_read64(spibar, 0x60);
+	if (rom3_base != 0xfd00000000) {
+		msg_perr("Unexpected value for Rom3 base: 0x%"PRIx64"\n", rom3_base);
+		return ERROR_FATAL;
+	}
+
+	if (compare_sparse(rom2, rom3 + 48*MiB, 16*MiB)) {
+		msg_perr("Rom2 and Rom3 don't seem to map the same memory.\n");
+		return ERROR_FATAL;
+	}
+
+	struct spi100 *const spi100 = malloc(sizeof(*spi100));
+	if (!spi100) {
+		msg_perr("Out of memory!\n");
+		return ERROR_FATAL;
+	}
+	spi100->memory = rom3;
+	spi100->size_override = size;
+
+	return register_opaque_master(&rom3read_master, spi100);
+}