|  | // 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; | 
|  | } |