diff --git a/arch/x86/kernel/cpu/vmware.c b/arch/x86/kernel/cpu/vmware.c index 628a059..a964013 100644 --- a/arch/x86/kernel/cpu/vmware.c +++ b/arch/x86/kernel/cpu/vmware.c @@ -26,16 +26,20 @@ #include <asm/div64.h> #include <asm/x86_init.h> #include <asm/hypervisor.h> +#include <linux/kmsg_dump.h> -#define CPUID_VMWARE_INFO_LEAF 0x40000000 -#define VMWARE_HYPERVISOR_MAGIC 0x564D5868 -#define VMWARE_HYPERVISOR_PORT 0x5658 +#define CPUID_VMWARE_INFO_LEAF 0x40000000 +#define VMWARE_HYPERVISOR_MAGIC 0x564D5868 +#define VMWARE_HYPERVISOR_PORT 0x5658 +#define VMWARE_HYPERVISOR_HB_PORT 0x5659 #define VMWARE_PORT_CMD_GETVERSION 10 #define VMWARE_PORT_CMD_GETHZ 45 #define VMWARE_PORT_CMD_GETVCPU_INFO 68 #define VMWARE_PORT_CMD_LEGACY_X2APIC 3 #define VMWARE_PORT_CMD_VCPU_RESERVED 31 +#define VMWARE_PORT_CMD_MESSAGE 30 +#define VMWARE_HB_PORT_CMD_MESSAGE 0 #define VMWARE_PORT(cmd, eax, ebx, ecx, edx) \ __asm__("inl (%%dx)" : \ @@ -75,6 +79,13 @@ static unsigned long vmware_get_tsc_khz(void) return tsc_hz; } +static void kmsg_dumper_vmware_log(struct kmsg_dumper *dumper, + enum kmsg_dump_reason reason); + +static struct kmsg_dumper kmsg_dumper = { + .dump = kmsg_dumper_vmware_log +}; + static void __init vmware_platform_setup(void) { uint32_t eax, ebx, ecx, edx; @@ -86,6 +97,8 @@ static void __init vmware_platform_setup(void) else printk(KERN_WARNING "Failed to get TSC freq from the hypervisor\n"); + + kmsg_dump_register(&kmsg_dumper); } /* @@ -145,3 +158,127 @@ const __refconst struct hypervisor_x86 x86_hyper_vmware = { .x2apic_available = vmware_legacy_x2apic_available, }; EXPORT_SYMBOL(x86_hyper_vmware); + +#define MESSAGE_STATUS_SUCCESS (0x01 << 16) +#define MESSAGE_STATUS_CPT (0x10 << 16) +#define MESSAGE_STATUS_HB (0x80 << 16) + +#define RPCI_PROTOCOL_NUM 0x49435052 /* 'RPCI' */ +#define GUESTMSG_FLAG_COOKIE 0x80000000 + +#define MESSAGE_TYPE_OPEN (0 << 16) +#define MESSAGE_TYPE_SENDSIZE (1 << 16) +#define MESSAGE_TYPE_CLOSE (6 << 16) + +typedef struct { + uint32_t id; + uint32_t cookieHigh; + uint32_t cookieLow; +} vmw_msg; + +static int +vmware_log_open(vmw_msg *msg) { + uint32_t result, info, dx, si, di; + __asm__ __volatile__ ("inl (%%dx)" + : "=a" (result), + "=c" (info), + "=d" (dx), + "=S" (si), + "=D" (di) + : "a" (VMWARE_HYPERVISOR_MAGIC), + "c" (VMWARE_PORT_CMD_MESSAGE | MESSAGE_TYPE_OPEN), + "d" (VMWARE_HYPERVISOR_PORT), + "b" (RPCI_PROTOCOL_NUM | GUESTMSG_FLAG_COOKIE)); + + if ((info & MESSAGE_STATUS_SUCCESS) == 0) + return 1; + + msg->id = dx & 0xffff0000; + msg->cookieHigh = si; + msg->cookieLow = di; + return 0; +} + +static int +vmware_log_close(vmw_msg *msg) { + uint32_t result, info; + __asm__ __volatile__ ("inl (%%dx)" + : "=a" (result), + "=c" (info) + : "a" (VMWARE_HYPERVISOR_MAGIC), + "c" (VMWARE_PORT_CMD_MESSAGE | MESSAGE_TYPE_CLOSE), + "d" (VMWARE_HYPERVISOR_PORT | msg->id), + "b" (0), + "S" (msg->cookieHigh), + "D" (msg->cookieLow)); + + if ((info & MESSAGE_STATUS_SUCCESS) == 0) + return 1; + return 0; +} + +static int +vmware_log_send(vmw_msg *msg, const char *string) { + uint32_t result, info; + uint32_t len = strlen(string); + +retry: + __asm__ __volatile__ ("inl (%%dx)" + : "=a" (result), + "=c" (info) + : "a" (VMWARE_HYPERVISOR_MAGIC), + "c" (VMWARE_PORT_CMD_MESSAGE | MESSAGE_TYPE_SENDSIZE), + "d" (VMWARE_HYPERVISOR_PORT | msg->id), + "b" (len), + "S" (msg->cookieHigh), + "D" (msg->cookieLow)); + + if ((info & MESSAGE_STATUS_SUCCESS) == 0 || + (info & MESSAGE_STATUS_HB) == 0) + /* Expected success + high-bandwidth. Give up. */ + return 1; + + __asm__ __volatile__ ("pushq %%rbp\n\t" + "movl %[rbp], %%ebp\n\t" + "cld\n\t" + "rep; outsb\n\t" + "popq %%rbp\n\t" + : "=a" (result), + "=b" (info) + : "a" (VMWARE_HYPERVISOR_MAGIC), + "c" (len), + "d" (VMWARE_HYPERVISOR_HB_PORT | msg->id), + "b" (VMWARE_HB_PORT_CMD_MESSAGE | MESSAGE_STATUS_SUCCESS), + "S" (string), + [rbp] "r" (msg->cookieHigh), + "D" (msg->cookieLow)); + + if ((info & MESSAGE_STATUS_SUCCESS) == 0) { + if (info & MESSAGE_STATUS_CPT) + /* A checkpoint occurred. Retry. */ + goto retry; + return 1; + } + return 0; +} + +static void kmsg_dumper_vmware_log(struct kmsg_dumper *dumper, + enum kmsg_dump_reason reason) +{ + vmw_msg msg; + static char line[1024]; + size_t len = 0; + + line[0] = 'l'; + line[1] = 'o'; + line[2] = 'g'; + line[3] = ' '; + + while (kmsg_dump_get_line(dumper, true, line + 4, sizeof(line) - 4, &len)) { + line[len + 4] = '\0'; + if (vmware_log_open(&msg) || + vmware_log_send(&msg, line) || + vmware_log_close(&msg)) + break; + } +}