Preserve the newest kernel log tail in the host's vmware.log, which can outlive a guest with no working userspace or persistent storage.
Allocate an 8 KiB buffer during early init and register on panic_pre_kdump_list. A 4 KiB buffer can omit useful stack-trace context. Use a fixed byte budget so transfer work does not scale with PAGE_SIZE. Copy the newest records, prefix them with "log ", and send one RPC over the low-bandwidth register interface. Bound the transfer to 8 KiB and at most three checkpoint attempts; zero-fill a partial payload word and close the channel after every successful open. The callback allocates nothing and emits no printk messages. Enable it for ordinary guests, leaving encrypted guests disabled until explicitly enabled by the sysctl. Skip initialization without printk support, and leave logging disabled after allocation or registration failure. Suggested-by: Michael Kelley <[email protected]> Link: https://lore.kernel.org/r/sn6pr02mb41574a11d309a0530c63e0fed4...@sn6pr02mb4157.namprd02.prod.outlook.com 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]> --- v2: use the pre-kdump notifier, establish safe guest defaults, and increase the fixed buffer to 8 KiB as Michael suggested. Maaz's v1 Reviewed-by is omitted for renewed review of the new callback. arch/x86/include/asm/vmware.h | 1 + arch/x86/kernel/cpu/vmware.c | 173 ++++++++++++++++++++++++++++++++++ 2 files changed, 174 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..ccf8dc84b30e 100644 --- a/arch/x86/kernel/cpu/vmware.c +++ b/arch/x86/kernel/cpu/vmware.c @@ -24,11 +24,16 @@ #include <linux/dmi.h> #include <linux/init.h> #include <linux/export.h> +#include <linux/kmsg_dump.h> +#include <linux/mm.h> +#include <linux/panic_notifier.h> #include <linux/clocksource.h> #include <linux/cpu.h> #include <linux/efi.h> #include <linux/reboot.h> +#include <linux/sizes.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 +57,23 @@ #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_BUF_SIZE SZ_8K +#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 +86,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 +170,151 @@ 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 bool vmware_record_panic_msg; + +static int vmware_panic_log_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct kmsg_dump_iter iter; + char *buffer = page_address(vmware_panic_page); + size_t length = 0; + + if (!READ_ONCE(vmware_record_panic_msg)) + return NOTIFY_DONE; + + 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, + VMWARE_LOG_BUF_SIZE - VMWARE_LOG_PREFIX_LEN, + &length); + (void)vmware_log_rpc(buffer, length + VMWARE_LOG_PREFIX_LEN); + + return NOTIFY_DONE; +} + +static struct notifier_block vmware_panic_log_nb = { + .notifier_call = vmware_panic_log_notify, +}; + +static int __init vmware_panic_log_init(void) +{ + int ret; + + if (!IS_ENABLED(CONFIG_PRINTK) || + !hypervisor_is_type(X86_HYPER_VMWARE)) + return 0; + + vmware_record_panic_msg = + !cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT); + + vmware_panic_page = alloc_pages(GFP_KERNEL, + get_order(VMWARE_LOG_BUF_SIZE)); + if (!vmware_panic_page) { + pr_err("failed to allocate panic log buffer\n"); + return 0; + } + + ret = atomic_notifier_chain_register(&panic_pre_kdump_list, + &vmware_panic_log_nb); + if (ret) { + pr_err("failed to register panic log notifier: %d\n", ret); + __free_pages(vmware_panic_page, get_order(VMWARE_LOG_BUF_SIZE)); + 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;

