Commit f393a761 authored by Gerd Hoffmann's avatar Gerd Hoffmann Committed by Ard Biesheuvel
Browse files

efi: add ovmf debug log driver



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: default avatarGerd Hoffmann <kraxel@redhat.com>
Signed-off-by: default avatarArd Biesheuvel <ardb@kernel.org>
parent 19272b37
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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

+8 −0
Original line number Diff line number Diff line
@@ -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
+111 −0
Original line number Diff line number Diff line
// 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;
}
+4 −0
Original line number Diff line number Diff line
@@ -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
 */