Date: Sun, 4 Aug 2013 22:14:02 +0000 (UTC) From: Bryan Venteicher <bryanv@FreeBSD.org> To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r253945 - in projects/vmxnet/sys: dev/vmware/vmt modules/vmware/vmt Message-ID: <201308042214.r74ME2Bq017053@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: bryanv Date: Sun Aug 4 22:14:02 2013 New Revision: 253945 URL: http://svnweb.freebsd.org/changeset/base/253945 Log: Add compile only tested port of OpenBSD VMware Tools driver (vmt) I doubt I'll have any more time to work on this any time soon, but commit it now in case somebody is interested in hacking on it. Added: projects/vmxnet/sys/dev/vmware/vmt/ projects/vmxnet/sys/dev/vmware/vmt/vmt.c (contents, props changed) projects/vmxnet/sys/dev/vmware/vmt/vmtreg.h (contents, props changed) projects/vmxnet/sys/modules/vmware/vmt/ projects/vmxnet/sys/modules/vmware/vmt/Makefile (contents, props changed) Added: projects/vmxnet/sys/dev/vmware/vmt/vmt.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/vmxnet/sys/dev/vmware/vmt/vmt.c Sun Aug 4 22:14:02 2013 (r253945) @@ -0,0 +1,1069 @@ +/*- + * Copyright (c) 2007 David Crawshaw <david@zentus.com> + * Copyright (c) 2008 David Gwynne <dlg@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: src/sys/dev/vmt.c,v 1.13 2013/07/03 15:26:02 sf Exp $ + */ + +/* + * Protocol reverse engineered by Ken Kato: + * http://chitchat.at.infoseek.co.jp/vmware/backdoor.html + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/callout.h> +#include <sys/reboot.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/time.h> +#include <sys/sysctl.h> +#include <sys/syslog.h> +#include <sys/proc.h> + +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> + +#include <machine/stdarg.h> + +#include "vmtreg.h" + +#define VMT_RPC_BUFLEN 256 + +struct vmt_softc { + struct mtx sc_mtx; + device_t sc_dev; + struct vm_rpc sc_tclo_rpc; + char *sc_rpc_buf; + int sc_rpc_error; + int sc_tclo_ping; + int sc_set_guest_os; + int sc_removing; + struct callout sc_tick; + struct callout sc_tclo_tick; + char sc_hostname[MAXHOSTNAMELEN]; +}; + +#define VMT_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define VMT_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) + +static void vmt_identify(driver_t *, device_t); +static int vmt_probe(device_t); +static int vmt_attach(device_t); +static int vmt_detach(device_t); +static int vmt_shutdown(device_t); + +static void vm_cmd(struct vm_backdoor *); +static void vm_ins(struct vm_backdoor *); +static void vm_outs(struct vm_backdoor *); + +/* Functions for communicating with the VM Host. */ +static int vm_rpc_open(struct vm_rpc *, uint32_t); +static int vm_rpc_close(struct vm_rpc *); +static int vm_rpc_send(const struct vm_rpc *, const uint8_t *, uint32_t); +static int vm_rpc_send_str(const struct vm_rpc *, const uint8_t *); +static int vm_rpc_get_length(const struct vm_rpc *, uint32_t *, + uint16_t *); +static int vm_rpc_get_data(const struct vm_rpc *, char *, uint32_t, + uint16_t); +static int vm_rpc_send_rpci_tx_buf(struct vmt_softc *, const uint8_t *, + uint32_t); +static int vm_rpc_send_rpci_tx(struct vmt_softc *, const char *, ...) + __printflike(2 ,3); +static int vm_rpci_response_successful(struct vmt_softc *); + +static void vmt_probe_cmd(struct vm_backdoor *, uint16_t); +static void vmt_tclo_state_change_success(struct vmt_softc *, int, char); +static void vmt_do_reboot(struct vmt_softc *); +static void vmt_do_shutdown(struct vmt_softc *); + +static void vmt_disconnect(struct vmt_softc *); + +static void vmt_update_guest_info(struct vmt_softc *); +static void vmt_update_guest_uptime(struct vmt_softc *); + +static void vmt_tick(void *); +static void vmt_tclo_tick(void *); + +extern char hostname[MAXHOSTNAMELEN]; /* prison0.pr_hostname */ + +static device_method_t vmt_methods[] = { + DEVMETHOD(device_identify, vmt_identify), + DEVMETHOD(device_probe, vmt_probe), + DEVMETHOD(device_attach, vmt_attach), + DEVMETHOD(device_detach, vmt_detach), + DEVMETHOD(device_shutdown, vmt_shutdown), + + DEVMETHOD_END +}; + +static driver_t vmt_driver = { + "vmt", vmt_methods, sizeof(struct vmt_softc) +}; + +static devclass_t vmt_devclass; +DRIVER_MODULE(vmt, nexus, vmt_driver, vmt_devclass, 0, 0); + +static void +vmt_probe_cmd(struct vm_backdoor *frame, uint16_t cmd) +{ + + bzero(frame, sizeof(*frame)); + + frame->eax.word = VM_MAGIC; + frame->ebx.word = ~VM_MAGIC; + frame->ecx.part.low = cmd; + frame->ecx.part.high = 0xFFFF; + frame->edx.part.low = VM_PORT_CMD; + frame->edx.part.high = 0; + + vm_cmd(frame); +} + +static void +vmt_identify(driver_t *driver, device_t parent) +{ + struct vm_backdoor frame; + + if (vm_guest != VM_GUEST_VM) + return; + + if (device_find_child(parent, driver->name, -1) != NULL) + return; + + vmt_probe_cmd(&frame, VM_CMD_GET_VERSION); + if (frame.eax.word == 0XFFFFFFFF || frame.ebx.word != VM_MAGIC) + return; + + vmt_probe_cmd(&frame, VM_CMD_GET_SPEED); + if (frame.eax.word == VM_MAGIC) + return; + + if (BUS_ADD_CHILD(parent, 0, driver->name, 0) == NULL) + device_printf(parent, "add vmt child failed\n"); +} + +static int +vmt_probe(device_t dev) +{ + + device_set_desc(dev, "VMware Tools Device"); + return (0); +} + +static int +vmt_attach(device_t dev) +{ + struct vmt_softc *sc; + int error; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + + mtx_init(&sc->sc_mtx, "vmt", NULL, MTX_DEF); + + sc->sc_rpc_buf = malloc(VMT_RPC_BUFLEN, M_DEVBUF, M_NOWAIT | M_ZERO); + if (sc->sc_rpc_buf == NULL) { + error = ENOMEM; + device_printf(dev, "unable to allocate buffer for RPC\n"); + goto fail; + } + + error = vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO); + if (error) { + device_printf(dev, + "failed to open backdoor RPC channel (TCLO protocol)\n"); + goto fail; + } + + /* Don't know if this is important at all yet. */ + error = vm_rpc_send_rpci_tx(sc, + "tools.capability.hgfs_server toolbox 1"); + if (error) { + device_printf(dev, "failed to set HGFS server capability\n"); + goto fail; + } + + callout_init_mtx(&sc->sc_tick, &sc->sc_mtx, 0); + callout_reset(&sc->sc_tick, hz, vmt_tick, sc); + + sc->sc_tclo_ping = 1; + callout_init_mtx(&sc->sc_tclo_tick, &sc->sc_mtx, 0); + callout_reset(&sc->sc_tclo_tick, hz, vmt_tclo_tick, sc); + +fail: + if (error) + vmt_detach(dev); + + return (error); +} + +static int +vmt_detach(device_t dev) +{ + struct vmt_softc *sc; + + sc = device_get_softc(dev); + + if (device_is_attached(dev)) { + VMT_LOCK(sc); + sc->sc_removing = 1; + vmt_disconnect(sc); + VMT_UNLOCK(sc); + + callout_drain(&sc->sc_tick); + callout_drain(&sc->sc_tclo_tick); + } + + if (sc->sc_rpc_buf != NULL) { + free(sc->sc_rpc_buf, M_DEVBUF); + sc->sc_rpc_buf = NULL; + } + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +vmt_shutdown(device_t dev) +{ + + return (0); +} + +static void +vmt_update_guest_uptime(struct vmt_softc *sc) +{ + + /* Host wants uptime in hundredths of a second. */ + if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %lld00", + VM_GUEST_INFO_UPTIME, (long long)time_uptime) != 0) { + device_printf(sc->sc_dev, "unable to set guest uptime\n"); + sc->sc_rpc_error = 1; + } +} + +static void +vmt_update_guest_info(struct vmt_softc *sc) +{ + device_t dev; + int error; + + dev = sc->sc_dev; + + if (strncmp(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)) != 0) { + strlcpy(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)); + + error = vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s", + VM_GUEST_INFO_DNS_NAME, sc->sc_hostname); + if (error) { + device_printf(dev, "unable to set hostname\n"); + sc->sc_rpc_error = 1; + } + } + + /* + * We're supposed to pass the full network address information back + * here, but that involves xdr (sunrpc) data encoding, which seems + * a bit unreasonable. + */ + + if (sc->sc_set_guest_os == 0) { + /* See linux_misc.c for this ... */ + error = vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s %s %s", + VM_GUEST_INFO_OS_NAME_FULL, ostype, osrelease, version); + if (error) { + device_printf(dev, "unable to set full guest OS\n"); + sc->sc_rpc_error = 1; + } + + error = vm_rpc_send_rpci_tx(sc, "SetGuestInfo %d %s", + VM_GUEST_INFO_OS_NAME, "FreeBSD"); + if (error) { + device_printf(dev, "unable to set guest OS\n"); + sc->sc_rpc_error = 1; + } + + sc->sc_set_guest_os = 1; + } +} + +static void +vmt_tick(void *xsc) +{ + struct vmt_softc *sc; + struct vm_backdoor frame; + struct timeval guest; + struct timeval host, diff __unused; + + sc = xsc; + + if (sc->sc_removing != 0) + return; + + microtime(&guest); + + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ecx.part.low = VM_CMD_GET_TIME_FULL; + frame.edx.part.low = VM_PORT_CMD; + vm_cmd(&frame); + + if (frame.eax.word != 0XFFFFFFFF) { + host.tv_sec = ((uint64_t)frame.esi.word << 32) | frame.edx.word; + host.tv_usec = frame.ebx.word; + +#if 0 + timersub(&guest, &host, &diff); + sc->sc_sensor.value = (u_int64_t)diff.tv_sec * 1000000000LL + + (u_int64_t)diff.tv_usec * 1000LL; + sc->sc_sensor.status = SENSOR_S_OK; +#endif + } +#if 0 + else + sc->sc_sensor.status = SENSOR_S_UNKNOWN; +#endif + + vmt_update_guest_info(sc); + vmt_update_guest_uptime(sc); + + callout_schedule(&sc->sc_tick, 15 * hz); +} + +static void +vmt_tclo_state_change_success(struct vmt_softc *sc, int success, char state) +{ + + if (vm_rpc_send_rpci_tx(sc, "tools.os.statechange.status %d %d", + success, state) != 0) { + device_printf(sc->sc_dev, + "unable to send state change result\n"); + sc->sc_rpc_error = 1; + } +} + +static void +vmt_do_reboot(struct vmt_softc *sc) +{ + + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_REBOOT); + vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK); + + log(LOG_KERN | LOG_NOTICE, + "Rebooting in response to request from VMware host\n"); + shutdown_nice(0); +} + +static void +vmt_do_shutdown(struct vmt_softc *sc) +{ + + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_HALT); + vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK); + + log(LOG_KERN | LOG_NOTICE, + "Shutting down in response to request from VMware host\n"); + shutdown_nice(RB_POWEROFF | RB_HALT); +} + +static void +vmt_disconnect(struct vmt_softc *sc) +{ + device_t dev; + int error; + + dev = sc->sc_dev; + + error = vm_rpc_send_rpci_tx(sc, + "tools.capability.hgfs_server toolbox 0"); + if (error) + device_printf(dev, "failed to disable hgfs server capability\n"); + + if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) + device_printf(dev, "failed to send shutdown ping\n"); + + vm_rpc_close(&sc->sc_tclo_rpc); +} + +static void +vmt_tclo_tick(void *xsc) +{ + struct vmt_softc *sc; + device_t dev; + uint32_t rlen; + uint16_t ack; + int error; + + sc = xsc; + dev = sc->sc_dev; + + if (sc->sc_removing != 0) + return; + + /* Reopen tclo channel if it's currently closed. */ + if (sc->sc_tclo_rpc.channel == 0 && sc->sc_tclo_rpc.cookie1 == 0 && + sc->sc_tclo_rpc.cookie2 == 0) { + if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) { + device_printf(dev, "unable to reopen TCLO channel\n"); + callout_schedule(&sc->sc_tclo_tick, 15 * hz); + return; + } + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) { + device_printf(dev, "failed to send reset reply\n"); + sc->sc_rpc_error = 1; + goto out; + } else + sc->sc_rpc_error = 0; + } + + if (sc->sc_tclo_ping) { + if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) { + device_printf(dev, "failed to send TCLO outgoing ping\n"); + sc->sc_rpc_error = 1; + goto out; + } + } + + if (vm_rpc_get_length(&sc->sc_tclo_rpc, &rlen, &ack) != 0) { + device_printf(dev, + "failed to get length of incoming TCLO data\n"); + sc->sc_rpc_error = 1; + goto out; + } + + if (rlen == 0) { + sc->sc_tclo_ping = 1; + goto out; + } else if (rlen >= VMT_RPC_BUFLEN) + rlen = VMT_RPC_BUFLEN - 1; + + if (vm_rpc_get_data(&sc->sc_tclo_rpc, sc->sc_rpc_buf, rlen, ack) != 0) { + device_printf(dev, "failed to get incoming TCLO data\n"); + sc->sc_rpc_error = 1; + goto out; + } + + sc->sc_tclo_ping = 0; + + if (strcmp(sc->sc_rpc_buf, "reset") == 0) { + if (sc->sc_rpc_error != 0) { + device_printf(dev, "resetting rpc\n"); + vm_rpc_close(&sc->sc_tclo_rpc); + /* Reopen and send the reset reply next time around. */ + goto out; + } + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) { + device_printf(dev, "failed to send reset reply\n"); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "ping") == 0) { + vmt_update_guest_info(sc); + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + device_printf(dev, "error sending ping response\n"); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "OS_Halt") == 0) { + vmt_do_shutdown(sc); + } else if (strcmp(sc->sc_rpc_buf, "OS_Reboot") == 0) { + vmt_do_reboot(sc); + } else if (strcmp(sc->sc_rpc_buf, "OS_PowerOn") == 0) { + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_POWERON); + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + device_printf(dev, "error sending poweron response\n"); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "OS_Suspend") == 0) { + log(LOG_KERN | LOG_NOTICE, + "VMware guest entering suspended state\n"); + + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_SUSPEND); + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + device_printf(dev, "error sending suspend response\n"); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "OS_Resume") == 0) { + log(LOG_KERN | LOG_NOTICE, + "VMware guest resuming from suspended state\n"); + + /* Force guest info update. */ + sc->sc_hostname[0] = '\0'; + sc->sc_set_guest_os = 0; + vmt_update_guest_info(sc); + + vmt_tclo_state_change_success(sc, 1, VM_STATE_CHANGE_RESUME); + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + device_printf(dev, "error sending resume response\n"); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "Capabilities_Register") == 0) { + /* Don't know if this is important at all. */ + if (vm_rpc_send_rpci_tx(sc, + "vmx.capability.unified_loop toolbox") != 0) { + device_printf(dev, "unable to set unified loop\n"); + sc->sc_rpc_error = 1; + } else if (vm_rpci_response_successful(sc) == 0) + device_printf(dev, "host rejected unified loop setting\n"); + + /* The trailing space is apparently important here. */ + if (vm_rpc_send_rpci_tx(sc, "tools.capability.statechange ") != 0) { + device_printf(dev, + "unable to send statechange capability\n"); + sc->sc_rpc_error = 1; + } else if (vm_rpci_response_successful(sc) == 0) + device_printf(dev, "host rejected statechange capability\n"); + + if (vm_rpc_send_rpci_tx(sc, "tools.set.version %u", + VM_VERSION_UNMANAGED) != 0) { + device_printf(dev, "unable to set tools version\n"); + sc->sc_rpc_error = 1; + } + + vmt_update_guest_uptime(sc); + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) { + device_printf(dev, + "error sending capabilities_register response\n"); + sc->sc_rpc_error = 1; + } + } else if (strcmp(sc->sc_rpc_buf, "Set_Option broadcastIP 1") == 0) { +#if 0 + struct ifnet *iface; + struct sockaddr_in *guest_ip; + + /* Find first available ipv4 address. */ + guest_ip = NULL; + TAILQ_FOREACH(iface, &ifnet, if_list) { + struct ifaddr *iface_addr; + + /* skip loopback */ + if (strncmp(iface->if_xname, "lo", 2) == 0 && + iface->if_xname[2] >= '0' && iface->if_xname[2] <= '9') { + continue; + } + + TAILQ_FOREACH(iface_addr, &iface->if_addrlist, ifa_list) { + if (iface_addr->ifa_addr->sa_family != AF_INET) { + continue; + } + + guest_ip = satosin(iface_addr->ifa_addr); + break; + } + } + + if (guest_ip != NULL) { + if (vm_rpc_send_rpci_tx(sc, "info-set guestinfo.ip %s", + inet_ntoa(guest_ip->sin_addr)) != 0) { + device_printf(dev, + "unable to send guest IP address\n"); + sc->sc_rpc_error = 1; + } + + if (vm_rpc_send_str(&sc->sc_tclo_rpc, + VM_RPC_REPLY_OK) != 0) { + device_printf(dev, + "error sending broadcastIP response\n"); + sc->sc_rpc_error = 1; + } + } else { + if (vm_rpc_send_str(&sc->sc_tclo_rpc, + VM_RPC_REPLY_ERROR_IP_ADDR) != 0) { + device_printf(dev, + "error sending broadcastIP error response\n"); + sc->sc_rpc_error = 1; + } + } +#endif + } else { + error = vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_ERROR); + if (error) { + device_printf(dev, + "error sending unknown command reply\n"); + sc->sc_rpc_error = 1; + } + } + +out: + callout_schedule(&sc->sc_tclo_tick, hz); +} + +#define BACKDOOR_OP_I386(op, frame) \ + __asm__ __volatile__ ( \ + "pushal;" \ + "pushl %%eax;" \ + "movl 0x18(%%eax), %%ebp;" \ + "movl 0x14(%%eax), %%edi;" \ + "movl 0x10(%%eax), %%esi;" \ + "movl 0x0c(%%eax), %%edx;" \ + "movl 0x08(%%eax), %%ecx;" \ + "movl 0x04(%%eax), %%ebx;" \ + "movl 0x00(%%eax), %%eax;" \ + op \ + "xchgl %%eax, 0x00(%%esp);" \ + "movl %%ebp, 0x18(%%eax);" \ + "movl %%edi, 0x14(%%eax);" \ + "movl %%esi, 0x10(%%eax);" \ + "movl %%edx, 0x0c(%%eax);" \ + "movl %%ecx, 0x08(%%eax);" \ + "movl %%ebx, 0x04(%%eax);" \ + "popl 0x00(%%eax);" \ + "popal;" \ + ::"a"(frame) \ + ) + +#define BACKDOOR_OP_AMD64(op, frame) \ + __asm__ __volatile__ ( \ + "pushq %%rbp; \n\t" \ + "pushq %%rax; \n\t" \ + "movq 0x30(%%rax), %%rbp; \n\t" \ + "movq 0x28(%%rax), %%rdi; \n\t" \ + "movq 0x20(%%rax), %%rsi; \n\t" \ + "movq 0x18(%%rax), %%rdx; \n\t" \ + "movq 0x10(%%rax), %%rcx; \n\t" \ + "movq 0x08(%%rax), %%rbx; \n\t" \ + "movq 0x00(%%rax), %%rax; \n\t" \ + op "\n\t" \ + "xchgq %%rax, 0x00(%%rsp); \n\t" \ + "movq %%rbp, 0x30(%%rax); \n\t" \ + "movq %%rdi, 0x28(%%rax); \n\t" \ + "movq %%rsi, 0x20(%%rax); \n\t" \ + "movq %%rdx, 0x18(%%rax); \n\t" \ + "movq %%rcx, 0x10(%%rax); \n\t" \ + "movq %%rbx, 0x08(%%rax); \n\t" \ + "popq 0x00(%%rax); \n\t" \ + "popq %%rbp; \n\t" \ + : /* No outputs. */ : "a" (frame) \ + /* No pushal on amd64 so warn gcc about the clobbered registers. */ \ + : "rbx", "rcx", "rdx", "rdi", "rsi", "cc", "memory" \ + ) + +#ifdef __i386__ +#define BACKDOOR_OP(op, frame) BACKDOOR_OP_I386(op, frame) +#else +#define BACKDOOR_OP(op, frame) BACKDOOR_OP_AMD64(op, frame) +#endif + +static void +vm_cmd(struct vm_backdoor *frame) +{ + + BACKDOOR_OP("inl %%dx, %%eax;", frame); +} + +static void +vm_ins(struct vm_backdoor *frame) +{ + + BACKDOOR_OP("cld;\n\trep insb;", frame); +} + +static void +vm_outs(struct vm_backdoor *frame) +{ + + BACKDOOR_OP("cld;\n\trep outsb;", frame); +} + +static int +vm_rpc_open(struct vm_rpc *rpc, uint32_t proto) +{ + struct vm_backdoor frame; + + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = proto | VM_RPC_FLAG_COOKIE; + frame.ecx.part.low = VM_CMD_RPC; + frame.ecx.part.high = VM_RPC_OPEN; + frame.edx.part.low = VM_PORT_CMD; + frame.edx.part.high = 0; + + vm_cmd(&frame); + + if (frame.ecx.part.high != 1 || frame.edx.part.low != 0) { + /* open-vm-tools retries without VM_RPC_FLAG_COOKIE here.. */ + printf("vmt: open failed, eax=%08x, ecx=%08x, edx=%08x\n", + frame.eax.word, frame.ecx.word, frame.edx.word); + return (EIO); + } + + rpc->channel = frame.edx.part.high; + rpc->cookie1 = frame.esi.word; + rpc->cookie2 = frame.edi.word; + + return (0); +} + +static int +vm_rpc_close(struct vm_rpc *rpc) +{ + struct vm_backdoor frame; + + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = 0; + frame.ecx.part.low = VM_CMD_RPC; + frame.ecx.part.high = VM_RPC_CLOSE; + frame.edx.part.low = VM_PORT_CMD; + frame.edx.part.high = rpc->channel; + frame.edi.word = rpc->cookie2; + frame.esi.word = rpc->cookie1; + + vm_cmd(&frame); + + if (frame.ecx.part.high == 0 || frame.ecx.part.low != 0) { + printf("vmt: close failed, eax=%08x, ecx=%08x\n", + frame.eax.word, frame.ecx.word); + return (EIO); + } + + rpc->channel = 0; + rpc->cookie1 = 0; + rpc->cookie2 = 0; + + return (0); +} + +static int +vm_rpc_send(const struct vm_rpc *rpc, const uint8_t *buf, uint32_t length) +{ + struct vm_backdoor frame; + + /* Send the length of the command. */ + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = length; + frame.ecx.part.low = VM_CMD_RPC; + frame.ecx.part.high = VM_RPC_SET_LENGTH; + frame.edx.part.low = VM_PORT_CMD; + frame.edx.part.high = rpc->channel; + frame.esi.word = rpc->cookie1; + frame.edi.word = rpc->cookie2; + + vm_cmd(&frame); + + if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) { + printf("vmt: sending length failed, eax=%08x, ecx=%08x\n", + frame.eax.word, frame.ecx.word); + return (EIO); + } + + /* Only need to poke once if command is null. */ + if (length == 0) + return (0); + + /* Send the command using enhanced RPC. */ + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = VM_RPC_ENH_DATA; + frame.ecx.word = length; + frame.edx.part.low = VM_PORT_RPC; + frame.edx.part.high = rpc->channel; + frame.ebp.word = rpc->cookie1; + frame.edi.word = rpc->cookie2; +#ifdef __amd64__ + frame.esi.quad = (uint64_t)buf; +#else + frame.esi.word = (uint32_t)buf; +#endif + + vm_outs(&frame); + + if (frame.ebx.word != VM_RPC_ENH_DATA) { + /* open-vm-tools retries on VM_RPC_REPLY_CHECKPOINT */ + printf("vmt: send failed, ebx=%08x\n", frame.ebx.word); + return (EIO); + } + + return (0); +} + +static int +vm_rpc_send_str(const struct vm_rpc *rpc, const uint8_t *str) +{ + + return (vm_rpc_send(rpc, str, strlen(str))); +} + +static int +vm_rpc_get_data(const struct vm_rpc *rpc, char *data, uint32_t length, + uint16_t dataid) +{ + struct vm_backdoor frame; + + /* Get data using enhanced RPC. */ + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = VM_RPC_ENH_DATA; + frame.ecx.word = length; + frame.edx.part.low = VM_PORT_RPC; + frame.edx.part.high = rpc->channel; + frame.esi.word = rpc->cookie1; +#ifdef __amd64__ + frame.edi.quad = (uint64_t)data; +#else + frame.edi.word = (uint32_t)data; +#endif + frame.ebp.word = rpc->cookie2; + + vm_ins(&frame); + + /* NUL-terminate the data. */ + data[length] = '\0'; + + if (frame.ebx.word != VM_RPC_ENH_DATA) { + printf("vmt: get data failed, ebx=%08x\n", frame.ebx.word); + return (EIO); + } + + /* Acknowledge data received. */ + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = dataid; + frame.ecx.part.low = VM_CMD_RPC; + frame.ecx.part.high = VM_RPC_GET_END; + frame.edx.part.low = VM_PORT_CMD; + frame.edx.part.high = rpc->channel; + frame.esi.word = rpc->cookie1; + frame.edi.word = rpc->cookie2; + + vm_cmd(&frame); + + if (frame.ecx.part.high == 0) { + printf("vmt: ack data failed, eax=%08x, ecx=%08x\n", + frame.eax.word, frame.ecx.word); + return (EIO); + } + + return (0); +} + +static int +vm_rpc_get_length(const struct vm_rpc *rpc, uint32_t *length, uint16_t *dataid) +{ + struct vm_backdoor frame; + + bzero(&frame, sizeof(frame)); + frame.eax.word = VM_MAGIC; + frame.ebx.word = 0; + frame.ecx.part.low = VM_CMD_RPC; + frame.ecx.part.high = VM_RPC_GET_LENGTH; + frame.edx.part.low = VM_PORT_CMD; + frame.edx.part.high = rpc->channel; + frame.esi.word = rpc->cookie1; + frame.edi.word = rpc->cookie2; + + vm_cmd(&frame); + + if ((frame.ecx.part.high & VM_RPC_REPLY_SUCCESS) == 0) { + printf("vmt: get length failed, eax=%08x, ecx=%08x\n", + frame.eax.word, frame.ecx.word); + return (EIO); + } + + if ((frame.ecx.part.high & VM_RPC_REPLY_DORECV) == 0) { + *length = 0; + *dataid = 0; + } else { + *length = frame.ebx.word; + *dataid = frame.edx.part.high; + } + + return (0); +} + +static int +vm_rpci_response_successful(struct vmt_softc *sc) +{ + + return (sc->sc_rpc_buf[0] == '1' && sc->sc_rpc_buf[1] == ' '); +} + +static int +vm_rpc_send_rpci_tx_buf(struct vmt_softc *sc, const uint8_t *buf, + uint32_t length) +{ + device_t dev; + struct vm_rpc rpci; + uint32_t rlen; + uint16_t ack; + int error; + + dev = sc->sc_dev; + error = 0; + + if (vm_rpc_open(&rpci, VM_RPC_OPEN_RPCI) != 0) { + device_printf(dev, "rpci channel open failed\n"); + return (EIO); + } + + if (vm_rpc_send(&rpci, sc->sc_rpc_buf, length) != 0) { + device_printf(dev, "unable to send rpci command\n"); + error = EIO; + goto out; + } + + if (vm_rpc_get_length(&rpci, &rlen, &ack) != 0) { + device_printf(dev, + "failed to get length of rpci response data\n"); + error = EIO; + goto out; + } + + if (rlen > 0) { + if (rlen >= VMT_RPC_BUFLEN) + rlen = VMT_RPC_BUFLEN - 1; + + if (vm_rpc_get_data(&rpci, sc->sc_rpc_buf, rlen, ack) != 0) { + device_printf(dev, + "failed to get rpci response data\n"); + error = EIO; + goto out; + } + } + +out: + if (vm_rpc_close(&rpci) != 0) + device_printf(dev, "unable to close rpci channel\n"); + + return (error); +} + +static int +vm_rpc_send_rpci_tx(struct vmt_softc *sc, const char *fmt, ...) +{ + va_list args; + int len, error; + + va_start(args, fmt); + len = vsnprintf(sc->sc_rpc_buf, VMT_RPC_BUFLEN, fmt, args); + va_end(args); + + if (len >= VMT_RPC_BUFLEN) { + device_printf(sc->sc_dev, + "rpci command didn't fit in buffer\n"); + error = EIO; + } else + error = vm_rpc_send_rpci_tx_buf(sc, sc->sc_rpc_buf, len); + + return (error); +} + +#if 0 + struct vm_backdoor frame; + + bzero(&frame, sizeof(frame)); + + frame.eax.word = VM_MAGIC; + frame.ecx.part.low = VM_CMD_GET_VERSION; + frame.edx.part.low = VM_PORT_CMD; + + printf("\n"); + printf("eax 0x%08x\n", frame.eax.word); + printf("ebx 0x%08x\n", frame.ebx.word); + printf("ecx 0x%08x\n", frame.ecx.word); + printf("edx 0x%08x\n", frame.edx.word); + printf("ebp 0x%08x\n", frame.ebp.word); + printf("edi 0x%08x\n", frame.edi.word); + printf("esi 0x%08x\n", frame.esi.word); + + vm_cmd(&frame); + + printf("-\n"); + printf("eax 0x%08x\n", frame.eax.word); + printf("ebx 0x%08x\n", frame.ebx.word); *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201308042214.r74ME2Bq017053>