--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * (C) Copyright 2023 Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
+ */
+
+#define LOG_CATEGORY UCLASS_QFW
+
+#include <efi_loader.h>
+#include <errno.h>
+#include <log.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <qfw.h>
+#include <smbios.h>
+#include <tables_csum.h>
+#include <linux/sizes.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * qfw_load_smbios_table() - load a QEMU firmware file
+ *
+ * @dev: QEMU firmware device
+ * @size: parameter to return the size of the loaded table
+ * @name: name of the table to load
+ * Return: address of the loaded table, NULL on error
+ */
+static void *qfw_load_smbios_table(struct udevice *dev, uint32_t *size,
+ char *name)
+{
+ struct fw_file *file;
+ struct bios_linker_entry *table;
+
+ file = qfw_find_file(dev, name);
+ if (!file) {
+ log_debug("Can't find %s\n", name);
+ return NULL;
+ }
+
+ *size = be32_to_cpu(file->cfg.size);
+
+ table = malloc(*size);
+ if (!table) {
+ log_err("Out of memory\n");
+ return NULL;
+ }
+
+ qfw_read_entry(dev, be16_to_cpu(file->cfg.select), *size, table);
+
+ return table;
+}
+
+/**
+ * qfw_parse_smbios_anchor() - parse QEMU's SMBIOS anchor
+ *
+ * @dev: QEMU firmware device
+ * @entry: SMBIOS 3 structure to be filled from QEMU's anchor
+ * Return: 0 for success, -ve on error
+ */
+static int qfw_parse_smbios_anchor(struct udevice *dev,
+ struct smbios3_entry *entry)
+{
+ void *table;
+ uint32_t size;
+ struct smbios_entry *entry2;
+ struct smbios3_entry *entry3;
+ const char smbios_sig[] = "_SM_";
+ const char smbios3_sig[] = "_SM3_";
+ int ret = 0;
+
+ table = qfw_load_smbios_table(dev, &size, "etc/smbios/smbios-anchor");
+ if (!table)
+ return -ENOMEM;
+ if (!memcmp(table, smbios3_sig, sizeof(smbios3_sig) - 1)) {
+ entry3 = table;
+ if (entry3->length != sizeof(struct smbios3_entry)) {
+ ret = -ENOENT;
+ goto out;
+ }
+ memcpy(entry, entry3, sizeof(struct smbios3_entry));
+ } else if (!memcmp(table, smbios_sig, sizeof(smbios_sig) - 1)) {
+ entry2 = table;
+ if (entry2->length != sizeof(struct smbios_entry)) {
+ ret = -ENOENT;
+ goto out;
+ }
+ memset(entry, 0, sizeof(struct smbios3_entry));
+ memcpy(entry, smbios3_sig, sizeof(smbios3_sig));
+ entry->length = sizeof(struct smbios3_entry);
+ entry->major_ver = entry2->major_ver;
+ entry->minor_ver = entry2->minor_ver;
+ entry->max_struct_size = entry2->max_struct_size;
+ } else {
+ ret = -ENOENT;
+ goto out;
+ }
+ ret = 0;
+out:
+ free(table);
+
+ return ret;
+}
+
+/**
+ * qfw_write_smbios_tables() - copy SMBIOS tables from QEMU
+ *
+ * @addr: target buffer
+ * @size: size of target buffer
+ * Return: 0 for success, -ve on error
+ */
+static int qfw_write_smbios_tables(u8 *addr, uint32_t size)
+{
+ int ret;
+ struct udevice *dev;
+ struct smbios3_entry *entry = (void *)addr;
+ void *table;
+ uint32_t table_size;
+
+ ret = qfw_get_dev(&dev);
+ if (ret) {
+ log_err("No QEMU firmware device\n");
+ return ret;
+ }
+
+ ret = qfw_read_firmware_list(dev);
+ if (ret) {
+ log_err("Can't read firmware file list\n");
+ return ret;
+ }
+
+ ret = qfw_parse_smbios_anchor(dev, entry);
+ if (ret) {
+ log_debug("Can't parse anchor\n");
+ return ret;
+ }
+
+ addr += entry->length;
+ entry->struct_table_address = (uintptr_t)addr;
+ entry->checksum = 0;
+ entry->checksum = table_compute_checksum(entry,
+ sizeof(struct smbios3_entry));
+
+ table = qfw_load_smbios_table(dev, &table_size,
+ "etc/smbios/smbios-tables");
+ if (table_size + sizeof(struct smbios3_entry) > size) {
+ free(table);
+ return -ENOMEM;
+ }
+ memcpy(addr, table, table_size);
+ free(table);
+
+ return 0;
+}
+
+/**
+ * qfw_evt_write_smbios_tables() - event handler for copying QEMU SMBIOS tables
+ *
+ * Return: 0 on success, -ve on error (only out of memory)
+ */
+static int qfw_evt_write_smbios_tables(void)
+{
+ phys_addr_t addr;
+ void *ptr;
+ int ret;
+ /*
+ * TODO:
+ * This size is currently hard coded in lib/efi_loader/efi_smbios.c.
+ * We need a field in global data for the size.
+ */
+ uint32_t size = SZ_4K;
+
+ /* Reserve 64K for SMBIOS tables, aligned to a 4K boundary */
+ ptr = memalign(SZ_4K, size);
+ if (!ptr) {
+ log_err("Out of memory\n");
+ return -ENOMEM;
+ }
+ addr = map_to_sysmem(ptr);
+
+ /* Generate SMBIOS tables */
+ ret = qfw_write_smbios_tables(ptr, size);
+ if (ret) {
+ if (CONFIG_IS_ENABLED(GENERATE_SMBIOS_TABLE)) {
+ log_info("Falling back to U-Boot generated SMBIOS tables\n");
+ write_smbios_table(addr);
+ }
+ } else {
+ log_debug("SMBIOS tables copied from QEMU\n");
+ }
+
+ gd_set_smbios_start(addr);
+
+ return 0;
+}
+
+EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, qfw_evt_write_smbios_tables);