From 7b12f93ad5602a95f38a77e15c2a2c6f02bae11e Mon Sep 17 00:00:00 2001 From: Krystian Hebel Date: Wed, 15 May 2024 18:08:30 +0200 Subject: mb/qemu-{i440fx,q35}/rom_media.c: add code for writable flash Depending on how firmware image was passed to QEMU, it may behave as: - ROM - memory mapped reads, writes are ignored (FW image mounted with '-bios'); - RAM - memory mapped reads and writes (FW image mounted with e.g. '-device loader'); - flash - memory mapped reads, write and erase possible through commands. Contrary to physical flash devices erase is not required before writing, but it also doesn't hurt. Flash may be split into read-only and read-write parts, like OVMF_CODE.fd and OVMF_VARS.fd. Combined size of system firmware must not exceed 8 MiB by default (FW image(s) mounted with '-drive if=pflash'). This function detects which of the above applies and fills region_device_ops accordingly. Tested by starting QEMU with firmware passed as '-drive if=pflash', '-drive if=pflash,readonly=on' and '-bios'. When started with firmware passed through '-device loader', coreboot complains about corrupted FMAP, but this is the same behavior as without this change: [ERROR] Invalid FMAP at 0x40000 [EMERG] Cannot locate primary CBFS Writable pflash support was added about 17 years ago, so it should be supported by all QEMU versions currently in use. Since QEMU 5.0.0 it is possible to change the limit of firmware size with `max-fw-size` machine configuration option, up to 16 MiB, as bigger sizes would overlap with default IO APIC memory range. Change-Id: I3ab9f22c6165064a769881d4be5eab13a0a2f519 Signed-off-by: Krystian Hebel Reviewed-on: https://review.coreboot.org/c/coreboot/+/82555 Reviewed-by: Patrick Rudolph Tested-by: build bot (Jenkins) Reviewed-by: Sergii Dmytruk --- src/mainboard/emulation/qemu-i440fx/Kconfig | 1 + src/mainboard/emulation/qemu-i440fx/Makefile.mk | 1 + src/mainboard/emulation/qemu-i440fx/rom_media.c | 212 ++++++++++++++++++++++++ src/mainboard/emulation/qemu-q35/Kconfig | 1 + src/mainboard/emulation/qemu-q35/Makefile.mk | 2 + 5 files changed, 217 insertions(+) create mode 100644 src/mainboard/emulation/qemu-i440fx/rom_media.c (limited to 'src/mainboard') diff --git a/src/mainboard/emulation/qemu-i440fx/Kconfig b/src/mainboard/emulation/qemu-i440fx/Kconfig index 24dfa7a2fc..842c053185 100644 --- a/src/mainboard/emulation/qemu-i440fx/Kconfig +++ b/src/mainboard/emulation/qemu-i440fx/Kconfig @@ -19,6 +19,7 @@ config BOARD_SPECIFIC_OPTIONS select NO_SMM select BOOT_DEVICE_NOT_SPI_FLASH select BOOT_DEVICE_MEMORY_MAPPED + select BOOT_DEVICE_SUPPORTS_WRITES config VBOOT select VBOOT_MUST_REQUEST_DISPLAY diff --git a/src/mainboard/emulation/qemu-i440fx/Makefile.mk b/src/mainboard/emulation/qemu-i440fx/Makefile.mk index 7d6041b62d..6ff2f73f11 100644 --- a/src/mainboard/emulation/qemu-i440fx/Makefile.mk +++ b/src/mainboard/emulation/qemu-i440fx/Makefile.mk @@ -7,6 +7,7 @@ postcar-y += exit_car.S ramstage-y += memmap.c ramstage-y += northbridge.c +ramstage-y += rom_media.c all-y += fw_cfg.c all-y += bootmode.c diff --git a/src/mainboard/emulation/qemu-i440fx/rom_media.c b/src/mainboard/emulation/qemu-i440fx/rom_media.c new file mode 100644 index 0000000000..d2469bade2 --- /dev/null +++ b/src/mainboard/emulation/qemu-i440fx/rom_media.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* Inspired by OvmfPkg/QemuFlashFvbServicesRuntimeDxe/QemuFlash.c from edk2 */ + +#include +#include +#include +#include +#include +#include + +#define WRITE_BYTE_CMD 0x10 +#define BLOCK_ERASE_CMD 0x20 +#define CLEAR_STATUS_CMD 0x50 +#define READ_STATUS_CMD 0x70 +#define BLOCK_ERASE_CONFIRM_CMD 0xD0 +#define READ_ARRAY_CMD 0xFF + +#define CLEARED_ARRAY_STATUS 0x00 + +#define QEMU_FLASH_BLOCK_SIZE 0x1000 + +#if CONFIG(ELOG) +#include + +/* + * ELOG and VBOOT options are automatically enabled when building with + * CHROMEOS=y. While the former allows for logging PCH state (not that there is + * much to log on QEMU), the latter currently forces 16 MiB ROM size, which in + * turn doesn't allow mounting as pflash in QEMU. Using pflash is required to + * have writable flash, which means that the following function will not be + * able to write to the flash based log until ROM size and layout is changed in + * Flashmap used when building for vboot. + */ +void pch_log_state(void) {} +#endif + +static ssize_t qemu_writeat(const struct region_device *rd, const void *b, + size_t offset, size_t size) +{ + const struct mem_region_device *mdev; + size_t i; + volatile char *ptr; + const char *buf = b; + + mdev = container_of(rd, __typeof__(*mdev), rdev); + ptr = &mdev->base[offset]; + + for (i = 0; i < size; i++) { + write8(ptr, WRITE_BYTE_CMD); + write8(ptr, buf[i]); + ptr++; + } + + /* Restore flash to read mode. */ + if (size > 0) { + write8(ptr - 1, READ_ARRAY_CMD); + } + + return size; +} + +static ssize_t qemu_eraseat(const struct region_device *rd, size_t offset, + size_t size) +{ + const struct mem_region_device *mdev; + size_t i; + volatile char *ptr; + + mdev = container_of(rd, __typeof__(*mdev), rdev); + ptr = &mdev->base[offset]; + + if (!IS_ALIGNED(offset, QEMU_FLASH_BLOCK_SIZE)) { + printk(BIOS_ERR, "%s: erased offset isn't multiple of block size\n", + __func__); + return -1; + } + + if (!IS_ALIGNED(size, QEMU_FLASH_BLOCK_SIZE)) { + printk(BIOS_ERR, "%s: erased size isn't multiple of block size\n", + __func__); + return -1; + } + + for (i = 0; i < size; i += QEMU_FLASH_BLOCK_SIZE) { + write8(ptr, BLOCK_ERASE_CMD); + write8(ptr, BLOCK_ERASE_CONFIRM_CMD); + ptr += QEMU_FLASH_BLOCK_SIZE; + } + + /* Restore flash to read mode. */ + if (size > 0) { + write8(ptr - QEMU_FLASH_BLOCK_SIZE, READ_ARRAY_CMD); + } + + return size; +} + +static struct region_device_ops flash_ops; +static struct mem_region_device boot_dev; + +/* + * Depending on how firmware image was passed to QEMU, it may behave as: + * + * - ROM - memory mapped reads, writes are ignored (FW image mounted with + * '-bios'); + * - RAM - memory mapped reads and writes (FW image mounted with e.g. + * '-device loader'); + * - flash - memory mapped reads, write and erase possible through commands. + * Contrary to physical flash devices erase is not required before writing, + * but it also doesn't hurt. Flash may be split into read-only and read-write + * parts, like OVMF_CODE.fd and OVMF_VARS.fd. Maximal combined size of system + * firmware is hardcoded (QEMU < 5.0.0) or set by default to 8 MiB. On QEMU + * version 5.0.0 or newer, it is configurable with `max-fw-size` machine + * configuration option, up to 16 MiB to not overlap with IOAPIC memory range + * (FW image(s) mounted with '-drive if=pflash'). + * + * This function detects which of the above applies and fills region_device_ops + * accordingly. + */ +void boot_device_init(void) +{ + volatile char *ptr; + char original, readback; + static bool initialized = false; + + if (initialized) + return; + + /* + * mmap, munmap and readat are always identical to mem_rdev_rw_ops, other + * functions may vary. + */ + flash_ops = mem_rdev_rw_ops; + + boot_dev.base = (void *)(uintptr_t)(0x100000000ULL - CONFIG_ROM_SIZE); + boot_dev.rdev.ops = &flash_ops; + boot_dev.rdev.region.size = CONFIG_ROM_SIZE; + + /* + * Find first byte different than any of the commands, simplified. + * + * Detection code few lines below writes commands and tries to read back + * the response. To make that code simpler, make sure that original byte + * is different than any of the commands or expected responses. It is + * expected that such byte will always be found - it is virtually + * impossible to write valid x86 code with just bytes ending with 0, and + * there are also ASCII characters in metadata (CBFS, FMAP) that has bytes + * matching those assumptions. + */ + ptr = (volatile char *)boot_dev.base; + original = read8(ptr); + while (original == (char)0xFF || (original & 0x0F) == 0) + original = read8(++ptr); + + /* + * Detect what type of flash we're dealing with. This also clears any stale + * status bits, so the next read of status register should return known + * value (if pflash is used). + */ + write8(ptr, CLEAR_STATUS_CMD); + readback = read8(ptr); + if (readback == CLEAR_STATUS_CMD) { + printk(BIOS_DEBUG, "QEMU flash behaves as RAM\n"); + /* Restore original content. */ + write8(ptr, original); + } else { + /* Either ROM or QEMU flash implementation. */ + write8(ptr, READ_STATUS_CMD); + readback = read8(ptr); + if (readback == original) { + printk(BIOS_DEBUG, "QEMU flash behaves as ROM\n"); + /* ROM means no writing nor erasing. */ + flash_ops.writeat = NULL; + flash_ops.eraseat = NULL; + } else if (readback == CLEARED_ARRAY_STATUS) { + /* Try writing original value to test whether flash is writable. */ + write8(ptr, WRITE_BYTE_CMD); + write8(ptr, original); + write8(ptr, READ_STATUS_CMD); + readback = read8(ptr); + if (readback & 0x10 /* programming error */) { + printk(BIOS_DEBUG, + "QEMU flash behaves as write-protected flash\n"); + flash_ops.writeat = NULL; + flash_ops.eraseat = NULL; + } else { + printk(BIOS_DEBUG, "QEMU flash behaves as writable flash\n"); + flash_ops.writeat = qemu_writeat; + flash_ops.eraseat = qemu_eraseat; + } + /* Restore flash to read mode. */ + write8(ptr, READ_ARRAY_CMD); + } else { + printk(BIOS_ERR, "Unexpected QEMU flash behavior, assuming ROM\n"); + /* + * This shouldn't happen and first byte of flash may already be + * corrupted by testing, but don't take any further risk. + */ + flash_ops.writeat = NULL; + flash_ops.eraseat = NULL; + } + } + + initialized = true; +} + +/* boot_device_ro() is defined in arch/x86/mmap_boot.c */ +const struct region_device *boot_device_rw(void) +{ + return &boot_dev.rdev; +} diff --git a/src/mainboard/emulation/qemu-q35/Kconfig b/src/mainboard/emulation/qemu-q35/Kconfig index 11ea750496..2fb180b7ec 100644 --- a/src/mainboard/emulation/qemu-q35/Kconfig +++ b/src/mainboard/emulation/qemu-q35/Kconfig @@ -17,6 +17,7 @@ config BOARD_SPECIFIC_OPTIONS select MAINBOARD_HAS_CHROMEOS select BOOT_DEVICE_NOT_SPI_FLASH select BOOT_DEVICE_MEMORY_MAPPED + select BOOT_DEVICE_SUPPORTS_WRITES config VBOOT select VBOOT_MUST_REQUEST_DISPLAY diff --git a/src/mainboard/emulation/qemu-q35/Makefile.mk b/src/mainboard/emulation/qemu-q35/Makefile.mk index bc73edcbbb..a60fbd6367 100644 --- a/src/mainboard/emulation/qemu-q35/Makefile.mk +++ b/src/mainboard/emulation/qemu-q35/Makefile.mk @@ -12,6 +12,7 @@ postcar-y += memmap.c ramstage-y += ../qemu-i440fx/memmap.c ramstage-y += ../qemu-i440fx/northbridge.c +ramstage-y += ../qemu-i440fx/rom_media.c ramstage-y += memmap.c ramstage-y += cpu.c @@ -20,4 +21,5 @@ all-y += ../qemu-i440fx/bootmode.c ramstage-$(CONFIG_CHROMEOS) += chromeos.c +smm-y += ../qemu-i440fx/rom_media.c smm-y += memmap.c -- cgit v1.2.3