efi: add ovmf debug log driver
authorGerd Hoffmann <kraxel@redhat.com>
Tue, 8 Jul 2025 12:56:23 +0000 (14:56 +0200)
committerArd Biesheuvel <ardb@kernel.org>
Wed, 9 Jul 2025 10:31:38 +0000 (20:31 +1000)
Recent OVMF versions (edk2-stable202508 + newer) can write their debug
log to a memory buffer.  This driver exposes the log content via sysfs
(/sys/firmware/efi/ovmf_debug_log).

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
drivers/firmware/efi/Kconfig
drivers/firmware/efi/Makefile
drivers/firmware/efi/efi.c
drivers/firmware/efi/ovmf-debug-log.c [new file with mode: 0644]
include/linux/efi.h

index db8c5c03d3a2547b3806ca5aff247a3e6e427f9b..eb1bff6968a5bc9f2240162c2b90f2d91d01a777 100644 (file)
@@ -263,6 +263,14 @@ config EFI_COCO_SECRET
          virt/coco/efi_secret module to access the secrets, which in turn
          allows userspace programs to access the injected secrets.
 
+config OVMF_DEBUG_LOG
+       bool "Expose OVMF firmware debug log via sysfs"
+       depends on EFI
+       help
+         Recent OVMF versions (edk2-stable202508 + newer) can write
+         their debug log to a memory buffer.  This driver exposes the
+         log content via sysfs (/sys/firmware/efi/ovmf_debug_log).
+
 config UNACCEPTED_MEMORY
        bool
        depends on EFI_STUB
index a2d0009560d0f0322d504c8569941efc3fc650e9..8efbcf699e4ff9d0717eda19bd029f6a89110de7 100644 (file)
@@ -29,6 +29,7 @@ obj-$(CONFIG_APPLE_PROPERTIES)                += apple-properties.o
 obj-$(CONFIG_EFI_RCI2_TABLE)           += rci2-table.o
 obj-$(CONFIG_EFI_EMBEDDED_FIRMWARE)    += embedded-firmware.o
 obj-$(CONFIG_LOAD_UEFI_KEYS)           += mokvar-table.o
+obj-$(CONFIG_OVMF_DEBUG_LOG)           += ovmf-debug-log.o
 
 obj-$(CONFIG_SYSFB)                    += sysfb_efi.o
 
index e57bff702b5f4fb91478a4afea5ae4e1088e3950..1ce428e2ac8a0ea86f89df77d51df191775b6d5d 100644 (file)
@@ -45,6 +45,7 @@ struct efi __read_mostly efi = {
        .esrt                   = EFI_INVALID_TABLE_ADDR,
        .tpm_log                = EFI_INVALID_TABLE_ADDR,
        .tpm_final_log          = EFI_INVALID_TABLE_ADDR,
+       .ovmf_debug_log         = EFI_INVALID_TABLE_ADDR,
 #ifdef CONFIG_LOAD_UEFI_KEYS
        .mokvar_table           = EFI_INVALID_TABLE_ADDR,
 #endif
@@ -473,6 +474,10 @@ static int __init efisubsys_init(void)
                platform_device_register_simple("efi_secret", 0, NULL, 0);
 #endif
 
+       if (IS_ENABLED(CONFIG_OVMF_DEBUG_LOG) &&
+           efi.ovmf_debug_log != EFI_INVALID_TABLE_ADDR)
+               ovmf_log_probe(efi.ovmf_debug_log);
+
        return 0;
 
 err_remove_group:
@@ -617,6 +622,9 @@ static const efi_config_table_type_t common_tables[] __initconst = {
        {LINUX_EFI_MEMRESERVE_TABLE_GUID,       &mem_reserve,           "MEMRESERVE"    },
        {LINUX_EFI_INITRD_MEDIA_GUID,           &initrd,                "INITRD"        },
        {EFI_RT_PROPERTIES_TABLE_GUID,          &rt_prop,               "RTPROP"        },
+#ifdef CONFIG_OVMF_DEBUG_LOG
+       {OVMF_MEMORY_LOG_TABLE_GUID,            &efi.ovmf_debug_log,    "OvmfDebugLog"  },
+#endif
 #ifdef CONFIG_EFI_RCI2_TABLE
        {DELLEMC_EFI_RCI2_TABLE_GUID,           &rci2_table_phys                        },
 #endif
diff --git a/drivers/firmware/efi/ovmf-debug-log.c b/drivers/firmware/efi/ovmf-debug-log.c
new file mode 100644 (file)
index 0000000..5b2471f
--- /dev/null
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+
+#define OVMF_DEBUG_LOG_MAGIC1  0x3167646d666d766f  // "ovmfmdg1"
+#define OVMF_DEBUG_LOG_MAGIC2  0x3267646d666d766f  // "ovmfmdg2"
+
+struct ovmf_debug_log_header {
+       u64    magic1;
+       u64    magic2;
+       u64    hdr_size;
+       u64    log_size;
+       u64    lock; // edk2 spinlock
+       u64    head_off;
+       u64    tail_off;
+       u64    truncated;
+       u8     fw_version[128];
+};
+
+static struct ovmf_debug_log_header *hdr;
+static u8 *logbuf;
+static u64 logbufsize;
+
+static ssize_t ovmf_log_read(struct file *filp, struct kobject *kobj,
+                            const struct bin_attribute *attr, char *buf,
+                            loff_t offset, size_t count)
+{
+       u64 start, end;
+
+       start = hdr->head_off + offset;
+       if (hdr->head_off > hdr->tail_off && start >= hdr->log_size)
+               start -= hdr->log_size;
+
+       end = start + count;
+       if (start > hdr->tail_off) {
+               if (end > hdr->log_size)
+                       end = hdr->log_size;
+       } else {
+               if (end > hdr->tail_off)
+                       end = hdr->tail_off;
+       }
+
+       if (start > logbufsize || end > logbufsize)
+               return 0;
+       if (start >= end)
+               return 0;
+
+       memcpy(buf, logbuf + start, end - start);
+       return end - start;
+}
+
+static struct bin_attribute ovmf_log_bin_attr = {
+       .attr = {
+               .name = "ovmf_debug_log",
+               .mode = 0444,
+       },
+       .read = ovmf_log_read,
+};
+
+int __init ovmf_log_probe(unsigned long ovmf_debug_log_table)
+{
+       int ret = -EINVAL;
+       u64 size;
+
+       /* map + verify header */
+       hdr = memremap(ovmf_debug_log_table, sizeof(*hdr), MEMREMAP_WB);
+       if (!hdr) {
+               pr_err("OVMF debug log: header map failed\n");
+               return -EINVAL;
+       }
+
+       if (hdr->magic1 != OVMF_DEBUG_LOG_MAGIC1 ||
+           hdr->magic2 != OVMF_DEBUG_LOG_MAGIC2) {
+               printk(KERN_ERR "OVMF debug log: magic mismatch\n");
+               goto err_unmap;
+       }
+
+       size = hdr->hdr_size + hdr->log_size;
+       pr_info("OVMF debug log: firmware version: \"%s\"\n", hdr->fw_version);
+       pr_info("OVMF debug log: buffer size: %lluk\n", size / 1024);
+
+       /* map complete log buffer */
+       memunmap(hdr);
+       hdr = memremap(ovmf_debug_log_table, size, MEMREMAP_WB);
+       if (!hdr) {
+               pr_err("OVMF debug log: buffer map failed\n");
+               return -EINVAL;
+       }
+       logbuf = (void *)hdr + hdr->hdr_size;
+       logbufsize = hdr->log_size;
+
+       ovmf_log_bin_attr.size = size;
+       ret = sysfs_create_bin_file(efi_kobj, &ovmf_log_bin_attr);
+       if (ret != 0) {
+               pr_err("OVMF debug log: sysfs register failed\n");
+               goto err_unmap;
+       }
+
+       return 0;
+
+err_unmap:
+       memunmap(hdr);
+       return ret;
+}
index 7d63d1d75f22f20bccdcb82c8aaac967eba35870..50db7df0efab860d2efe24d0cf325aeef1718e24 100644 (file)
@@ -439,6 +439,7 @@ void efi_native_runtime_setup(void);
 
 /* OVMF protocol GUIDs */
 #define OVMF_SEV_MEMORY_ACCEPTANCE_PROTOCOL_GUID       EFI_GUID(0xc5a010fe, 0x38a7, 0x4531,  0x8a, 0x4a, 0x05, 0x00, 0xd2, 0xfd, 0x16, 0x49)
+#define OVMF_MEMORY_LOG_TABLE_GUID             EFI_GUID(0x95305139, 0xb20f, 0x4723,  0x84, 0x25, 0x62, 0x7c, 0x88, 0x8f, 0xf1, 0x21)
 
 typedef struct {
        efi_guid_t guid;
@@ -642,6 +643,7 @@ extern struct efi {
        unsigned long                   esrt;                   /* ESRT table */
        unsigned long                   tpm_log;                /* TPM2 Event Log table */
        unsigned long                   tpm_final_log;          /* TPM2 Final Events Log table */
+       unsigned long                   ovmf_debug_log;
        unsigned long                   mokvar_table;           /* MOK variable config table */
        unsigned long                   coco_secret;            /* Confidential computing secret table */
        unsigned long                   unaccepted;             /* Unaccepted memory table */
@@ -1344,6 +1346,8 @@ bool efi_config_table_is_usable(const efi_guid_t *guid, unsigned long table)
 
 umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n);
 
+int ovmf_log_probe(unsigned long ovmf_debug_log_table);
+
 /*
  * efivar ops event type
  */