A virtual machine may have no guest-local persistent storage or working
userspace after a panic. Preserve the newest kernel log tail in the host
vmware.log, which remains available after the VM terminates.

Allocate one page during early init and register a panic-only kmsg dumper.
Copy the newest records once, prefix them with "log ", and send one
explicit-length RPC message over the low-bandwidth register path. The page
limit bounds panic-time work and avoids sharing guest memory with the host.

Keep the source immutable for up to three attempts. Retry only checkpoint
responses, zero-fill a short final word, and close the single RPC channel
on every exit after open. The panic callback allocates nothing, does not
sleep, and emits no printk messages.

Free the page and leave panic logging disabled if dumper registration
fails, including when CONFIG_PRINTK=n.

Link: 
https://lore.kernel.org/r/[email protected]
Co-developed-by: Bo Gan <[email protected]>
Signed-off-by: Bo Gan <[email protected]>
Co-developed-by: Alexey Makhalov <[email protected]>
Signed-off-by: Alexey Makhalov <[email protected]>
Signed-off-by: Zack Rusin <[email protected]>
---
 arch/x86/include/asm/vmware.h |   1 +
 arch/x86/kernel/cpu/vmware.c  | 158 ++++++++++++++++++++++++++++++++++
 2 files changed, 159 insertions(+)

diff --git a/arch/x86/include/asm/vmware.h b/arch/x86/include/asm/vmware.h
index 4220dae14a2d..598d4cd448ec 100644
--- a/arch/x86/include/asm/vmware.h
+++ b/arch/x86/include/asm/vmware.h
@@ -57,6 +57,7 @@
 #define VMWARE_HYPERVISOR_MAGIC                0x564d5868U
 
 #define VMWARE_CMD_GETVERSION          10
+#define VMWARE_CMD_MESSAGE             30
 #define VMWARE_CMD_GETHZ               45
 #define VMWARE_CMD_GETVCPU_INFO                68
 #define VMWARE_CMD_STEALCLOCK          91
diff --git a/arch/x86/kernel/cpu/vmware.c b/arch/x86/kernel/cpu/vmware.c
index 34b73573b108..bf59653d6e07 100644
--- a/arch/x86/kernel/cpu/vmware.c
+++ b/arch/x86/kernel/cpu/vmware.c
@@ -24,11 +24,14 @@
 #include <linux/dmi.h>
 #include <linux/init.h>
 #include <linux/export.h>
+#include <linux/kmsg_dump.h>
+#include <linux/mm.h>
 #include <linux/clocksource.h>
 #include <linux/cpu.h>
 #include <linux/efi.h>
 #include <linux/reboot.h>
 #include <linux/static_call.h>
+#include <linux/wordpart.h>
 #include <linux/sched/cputime.h>
 #include <asm/div64.h>
 #include <asm/x86_init.h>
@@ -52,6 +55,22 @@
 #define STEALCLOCK_DISABLED        0
 #define STEALCLOCK_ENABLED         1
 
+#define VMWARE_MSG_STATUS_SUCCESS      BIT(16)
+#define VMWARE_MSG_STATUS_CPT          BIT(20)
+
+#define VMWARE_RPCI_PROTOCOL           0x49435052
+#define VMWARE_GUESTMSG_COOKIE         BIT(31)
+
+#define VMWARE_MSG_TYPE(n)             ((n) << 16)
+#define VMWARE_MSG_OPEN                        VMWARE_MSG_TYPE(0U)
+#define VMWARE_MSG_SENDSIZE            VMWARE_MSG_TYPE(1U)
+#define VMWARE_MSG_SENDPAYLOAD         VMWARE_MSG_TYPE(2U)
+#define VMWARE_MSG_CLOSE               VMWARE_MSG_TYPE(6U)
+
+#define VMWARE_LOG_ATTEMPTS            3
+#define VMWARE_LOG_PREFIX              "log "
+#define VMWARE_LOG_PREFIX_LEN          (sizeof(VMWARE_LOG_PREFIX) - 1)
+
 struct vmware_steal_time {
        union {
                u64 clock;      /* stolen time counter in units of vtsc */
@@ -64,6 +83,12 @@ struct vmware_steal_time {
        u64 reserved[7];
 };
 
+struct vmware_rpc_channel {
+       u16 id;
+       u32 cookie_high;
+       u32 cookie_low;
+};
+
 static unsigned long vmware_tsc_khz __ro_after_init;
 static u8 vmware_hypercall_mode     __ro_after_init;
 
@@ -142,6 +167,139 @@ static unsigned long vmware_get_tsc_khz(void)
        return vmware_tsc_khz;
 }
 
+static int vmware_log_open(struct vmware_rpc_channel *channel)
+{
+       u32 info, id;
+
+       vmware_hypercall6(VMWARE_CMD_MESSAGE | VMWARE_MSG_OPEN,
+                         VMWARE_RPCI_PROTOCOL | VMWARE_GUESTMSG_COOKIE, 0,
+                         &info, &id, &channel->cookie_high,
+                         &channel->cookie_low);
+       if (!(info & VMWARE_MSG_STATUS_SUCCESS))
+               return -EIO;
+
+       channel->id = upper_16_bits(id);
+       return 0;
+}
+
+static int vmware_log_close(const struct vmware_rpc_channel *channel)
+{
+       u32 info;
+
+       vmware_hypercall5(VMWARE_CMD_MESSAGE | VMWARE_MSG_CLOSE, 0,
+                         (u32)channel->id << 16, channel->cookie_high,
+                         channel->cookie_low, &info);
+
+       return info & VMWARE_MSG_STATUS_SUCCESS ? 0 : -EIO;
+}
+
+static int vmware_log_send_once(const struct vmware_rpc_channel *channel,
+                               const char *buffer, size_t length)
+{
+       u32 info;
+
+       vmware_hypercall5(VMWARE_CMD_MESSAGE | VMWARE_MSG_SENDSIZE, length,
+                         (u32)channel->id << 16, channel->cookie_high,
+                         channel->cookie_low, &info);
+       if (!(info & VMWARE_MSG_STATUS_SUCCESS))
+               return info & VMWARE_MSG_STATUS_CPT ? -EAGAIN : -EIO;
+
+       while (length) {
+               size_t bytes = min_t(size_t, length, sizeof(u32));
+               u32 word = 0;
+
+               memcpy(&word, buffer, bytes);
+               vmware_hypercall5(VMWARE_CMD_MESSAGE | VMWARE_MSG_SENDPAYLOAD,
+                                 word, (u32)channel->id << 16,
+                                 channel->cookie_high, channel->cookie_low,
+                                 &info);
+               if (!(info & VMWARE_MSG_STATUS_SUCCESS))
+                       return info & VMWARE_MSG_STATUS_CPT ? -EAGAIN : -EIO;
+
+               buffer += bytes;
+               length -= bytes;
+       }
+
+       return 0;
+}
+
+static int vmware_log_send(const struct vmware_rpc_channel *channel,
+                          const char *buffer, size_t length)
+{
+       int attempt, ret;
+
+       for (attempt = 0; attempt < VMWARE_LOG_ATTEMPTS; attempt++) {
+               ret = vmware_log_send_once(channel, buffer, length);
+               if (ret != -EAGAIN)
+                       return ret;
+       }
+
+       return -EAGAIN;
+}
+
+static int vmware_log_rpc(const char *buffer, size_t length)
+{
+       struct vmware_rpc_channel channel;
+       int close_ret, ret;
+
+       ret = vmware_log_open(&channel);
+       if (ret)
+               return ret;
+
+       ret = vmware_log_send(&channel, buffer, length);
+       close_ret = vmware_log_close(&channel);
+       if (!ret && close_ret)
+               ret = close_ret;
+
+       return ret;
+}
+
+static struct page *vmware_panic_page;
+
+static void vmware_panic_log_dump(struct kmsg_dumper *dumper,
+                                 struct kmsg_dump_detail *detail)
+{
+       struct kmsg_dump_iter iter;
+       char *buffer = page_address(vmware_panic_page);
+       size_t length = 0;
+
+       memcpy(buffer, VMWARE_LOG_PREFIX, VMWARE_LOG_PREFIX_LEN);
+       kmsg_dump_rewind(&iter);
+       (void)kmsg_dump_get_buffer(&iter, true,
+                                  buffer + VMWARE_LOG_PREFIX_LEN,
+                                  PAGE_SIZE - VMWARE_LOG_PREFIX_LEN, &length);
+       (void)vmware_log_rpc(buffer, length + VMWARE_LOG_PREFIX_LEN);
+}
+
+static struct kmsg_dumper vmware_panic_log_dumper = {
+       .dump = vmware_panic_log_dump,
+       .max_reason = KMSG_DUMP_PANIC,
+};
+
+static int __init vmware_panic_log_init(void)
+{
+       int ret;
+
+       if (!hypervisor_is_type(X86_HYPER_VMWARE))
+               return 0;
+
+       vmware_panic_page = alloc_page(GFP_KERNEL);
+       if (!vmware_panic_page) {
+               pr_err("failed to allocate panic log buffer\n");
+               return 0;
+       }
+
+       ret = kmsg_dump_register(&vmware_panic_log_dumper);
+       if (ret) {
+               pr_err("failed to register panic log dumper: %d\n", ret);
+               __free_page(vmware_panic_page);
+               vmware_panic_page = NULL;
+       }
+
+       return 0;
+}
+early_initcall(vmware_panic_log_init);
+
 #ifdef CONFIG_PARAVIRT
 static struct cyc2ns_data vmware_cyc2ns __ro_after_init;
 static bool vmw_sched_clock __initdata = true;
-- 
2.53.0


Reply via email to