Edit

IABSD.fr/src/sys/dev/pv/vmt.c

Branch :

  • Show log

    Commit

  • Author : jsg
    Date : 2022-01-09 05:42:36
    Hash : 4b1a56af
    Message : spelling feedback and ok tb@ jmc@ ok ratchov@

  • sys/dev/pv/vmt.c
  • /*	$OpenBSD: vmt.c,v 1.25 2022/01/09 05:42:58 jsg Exp $ */
    
    /*
     * 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.
     */
    
    #if !defined(__i386__) && !defined(__amd64__)
    #error vmt(4) is only supported on i386 and amd64
    #endif
    
    /*
     * Protocol reverse engineered by Ken Kato:
     * https://sites.google.com/site/chitchatvmback/backdoor
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/kernel.h>
    #include <sys/malloc.h>
    #include <sys/timeout.h>
    #include <sys/signalvar.h>
    #include <sys/syslog.h>
    #include <sys/proc.h>
    #include <sys/socket.h>
    #include <sys/ioctl.h>
    #include <sys/mount.h>
    #include <sys/task.h>
    #include <sys/sensors.h>
    
    #include <net/if.h>
    #include <net/if_dl.h>
    #include <net/if_var.h>
    #include <net/if_types.h>
    #include <net/rtable.h>
    #include <netinet/in.h>
    #include <netinet/if_ether.h>
    
    #include <dev/pv/pvvar.h>
    
    /* "The" magic number, always occupies the EAX register. */
    #define VM_MAGIC			0x564D5868
    
    /* Port numbers, passed on EDX.LOW . */
    #define VM_PORT_CMD			0x5658
    #define VM_PORT_RPC			0x5659
    
    /* Commands, passed on ECX.LOW. */
    #define VM_CMD_GET_SPEED		0x01
    #define VM_CMD_APM			0x02
    #define VM_CMD_GET_MOUSEPOS		0x04
    #define VM_CMD_SET_MOUSEPOS		0x05
    #define VM_CMD_GET_CLIPBOARD_LEN	0x06
    #define VM_CMD_GET_CLIPBOARD		0x07
    #define VM_CMD_SET_CLIPBOARD_LEN	0x08
    #define VM_CMD_SET_CLIPBOARD		0x09
    #define VM_CMD_GET_VERSION		0x0a
    #define  VM_VERSION_UNMANAGED			0x7fffffff
    #define VM_CMD_GET_DEVINFO		0x0b
    #define VM_CMD_DEV_ADDREMOVE		0x0c
    #define VM_CMD_GET_GUI_OPTIONS		0x0d
    #define VM_CMD_SET_GUI_OPTIONS		0x0e
    #define VM_CMD_GET_SCREEN_SIZE		0x0f
    #define VM_CMD_GET_HWVER		0x11
    #define VM_CMD_POPUP_OSNOTFOUND		0x12
    #define VM_CMD_GET_BIOS_UUID		0x13
    #define VM_CMD_GET_MEM_SIZE		0x14
    /*#define VM_CMD_GET_TIME		0x17 */	/* deprecated */
    #define VM_CMD_RPC			0x1e
    #define VM_CMD_GET_TIME_FULL		0x2e
    
    /* RPC sub-commands, passed on ECX.HIGH. */
    #define VM_RPC_OPEN			0x00
    #define VM_RPC_SET_LENGTH		0x01
    #define VM_RPC_SET_DATA			0x02
    #define VM_RPC_GET_LENGTH		0x03
    #define VM_RPC_GET_DATA			0x04
    #define VM_RPC_GET_END			0x05
    #define VM_RPC_CLOSE			0x06
    
    /* RPC magic numbers, passed on EBX. */
    #define VM_RPC_OPEN_RPCI	0x49435052UL /* with VM_RPC_OPEN. */
    #define VM_RPC_OPEN_TCLO	0x4F4C4354UL /* with VP_RPC_OPEN. */
    #define VM_RPC_ENH_DATA		0x00010000UL /* with enhanced RPC data calls. */
    
    #define VM_RPC_FLAG_COOKIE	0x80000000UL
    
    /* RPC reply flags */
    #define VM_RPC_REPLY_SUCCESS	0x0001
    #define VM_RPC_REPLY_DORECV	0x0002		/* incoming message available */
    #define VM_RPC_REPLY_CLOSED	0x0004		/* RPC channel is closed */
    #define VM_RPC_REPLY_UNSENT	0x0008		/* incoming message was removed? */
    #define VM_RPC_REPLY_CHECKPOINT	0x0010		/* checkpoint occurred -> retry */
    #define VM_RPC_REPLY_POWEROFF	0x0020		/* underlying device is powering off */
    #define VM_RPC_REPLY_TIMEOUT	0x0040
    #define VM_RPC_REPLY_HB		0x0080		/* high-bandwidth tx/rx available */
    
    /* VM state change IDs */
    #define VM_STATE_CHANGE_HALT	1
    #define VM_STATE_CHANGE_REBOOT	2
    #define VM_STATE_CHANGE_POWERON 3
    #define VM_STATE_CHANGE_RESUME  4
    #define VM_STATE_CHANGE_SUSPEND 5
    
    /* VM guest info keys */
    #define VM_GUEST_INFO_DNS_NAME		1
    #define VM_GUEST_INFO_IP_ADDRESS	2
    #define VM_GUEST_INFO_DISK_FREE_SPACE	3
    #define VM_GUEST_INFO_BUILD_NUMBER	4
    #define VM_GUEST_INFO_OS_NAME_FULL	5
    #define VM_GUEST_INFO_OS_NAME		6
    #define VM_GUEST_INFO_UPTIME		7
    #define VM_GUEST_INFO_MEMORY		8
    #define VM_GUEST_INFO_IP_ADDRESS_V2	9
    #define VM_GUEST_INFO_IP_ADDRESS_V3	10
    
    /* RPC responses */
    #define VM_RPC_REPLY_OK			"OK "
    #define VM_RPC_RESET_REPLY		"OK ATR toolbox"
    #define VM_RPC_REPLY_ERROR		"ERROR Unknown command"
    #define VM_RPC_REPLY_ERROR_IP_ADDR	"ERROR Unable to find guest IP address"
    
    /* VM backup error codes */
    #define VM_BACKUP_SUCCESS		0
    #define VM_BACKUP_SYNC_ERROR		3
    #define VM_BACKUP_REMOTE_ABORT		4
    
    #define VM_BACKUP_TIMEOUT		30 /* seconds */
    
    /* NIC/IP address stuff */
    #define VM_NICINFO_VERSION		3
    
    #define VM_NICINFO_IP_LEN		64
    #define VM_NICINFO_MAX_NICS		16
    #define VM_NICINFO_MAX_ADDRS		2048
    #define VM_NICINFO_MAC_LEN		20
    
    #define VM_NICINFO_ADDR_IPV4		1
    #define VM_NICINFO_ADDR_IPV6		2
    
    struct vm_nicinfo_addr_v4 {
    	uint32_t	v4_addr_type;
    	uint32_t	v4_addr_len;
    	struct in_addr	v4_addr;
    	uint32_t	v4_prefix_len;
    	uint32_t	v4_origin;
    	uint32_t	v4_status;
    };
    
    struct vm_nicinfo_addr_v6 {
    	uint32_t	v6_addr_type;
    	uint32_t	v6_addr_len;
    	struct in6_addr v6_addr;
    	uint32_t	v6_prefix_len;
    	uint32_t	v6_origin;
    	uint32_t	v6_status;
    };
    
    struct vm_nicinfo_nic {
    	uint32_t	ni_mac_len;
    	char		ni_mac[VM_NICINFO_MAC_LEN];
    	uint32_t	ni_num_addrs;
    };
    
    struct vm_nicinfo_nic_nomac {
    	uint32_t	nn_mac_len;
    	uint32_t	nn_num_addrs;
    };
    
    struct vm_nicinfo_nic_post {
    	uint32_t	np_dns_config;
    	uint32_t	np_wins_config;
    	uint32_t	np_dhcpv4_config;
    	uint32_t	np_dhcpv6_config;
    };
    
    struct vm_nicinfo_nic_list {
    	uint32_t	nl_version;
    	uint32_t	nl_nic_list;
    	uint32_t	nl_num_nics;
    };
    
    struct vm_nicinfo_nic_list_post {
    	uint32_t	nl_num_routes;
    	uint32_t	nl_dns_config;
    	uint32_t	nl_wins_config;
    	uint32_t	nl_dhcpv4_config;
    	uint32_t	nl_dhcpv6_config;
    };
    
    #define VM_NICINFO_CMD			"SetGuestInfo  10 "
    
    /* A register. */
    union vm_reg {
    	struct {
    		uint16_t low;
    		uint16_t high;
    	} part;
    	uint32_t word;
    #ifdef __amd64__
    	struct {
    		uint32_t low;
    		uint32_t high;
    	} words;
    	uint64_t quad;
    #endif
    } __packed;
    
    /* A register frame. */
    struct vm_backdoor {
    	union vm_reg eax;
    	union vm_reg ebx;
    	union vm_reg ecx;
    	union vm_reg edx;
    	union vm_reg esi;
    	union vm_reg edi;
    	union vm_reg ebp;
    } __packed;
    
    /* RPC context. */
    struct vm_rpc {
    	uint16_t channel;
    	uint32_t cookie1;
    	uint32_t cookie2;
    };
    
    struct vmt_softc {
    	struct device		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_quiesce;
    	struct task		sc_quiesce_task;
    	struct task		sc_nicinfo_task;
    #define VMT_RPC_BUFLEN		4096
    
    	struct timeout		sc_tick;
    	struct timeout		sc_tclo_tick;
    	struct ksensordev	sc_sensordev;
    	struct ksensor		sc_sensor;
    
    	char			sc_hostname[MAXHOSTNAMELEN];
    	size_t			sc_nic_info_size;
    	char			*sc_nic_info;
    };
    
    #ifdef VMT_DEBUG
    #define DPRINTF(_arg...)	printf(_arg)
    #else
    #define DPRINTF(_arg...)	do {} while(0)
    #endif
    #define DEVNAME(_s)		((_s)->sc_dev.dv_xname)
    
    void	 vm_cmd(struct vm_backdoor *);
    void	 vm_ins(struct vm_backdoor *);
    void	 vm_outs(struct vm_backdoor *);
    
    /* Functions for communicating with the VM Host. */
    int	 vm_rpc_open(struct vm_rpc *, uint32_t);
    int	 vm_rpc_close(struct vm_rpc *);
    int	 vm_rpc_send(const struct vm_rpc *, const uint8_t *, uint32_t);
    int	 vm_rpc_send_str(const struct vm_rpc *, const uint8_t *);
    int	 vm_rpc_get_length(const struct vm_rpc *, uint32_t *, uint16_t *);
    int	 vm_rpc_get_data(const struct vm_rpc *, char *, uint32_t, uint16_t);
    int	 vm_rpc_send_rpci_tx_buf(struct vmt_softc *, const uint8_t *, uint32_t);
    int	 vm_rpc_send_rpci_tx(struct vmt_softc *, const char *, ...)
    	    __attribute__((__format__(__kprintf__,2,3)));
    int	 vm_rpci_response_successful(struct vmt_softc *);
    
    int	 vmt_kvop(void *, int, char *, char *, size_t);
    
    void	 vmt_probe_cmd(struct vm_backdoor *, uint16_t);
    void	 vmt_tclo_state_change_success(struct vmt_softc *, int, char);
    void	 vmt_do_reboot(struct vmt_softc *);
    void	 vmt_do_shutdown(struct vmt_softc *);
    void	 vmt_shutdown(void *);
    
    void	 vmt_clear_guest_info(struct vmt_softc *);
    void	 vmt_update_guest_info(struct vmt_softc *);
    void	 vmt_update_guest_uptime(struct vmt_softc *);
    
    void	 vmt_tick_hook(struct device *self);
    void	 vmt_tick(void *);
    void	 vmt_resume(void);
    
    int	 vmt_match(struct device *, void *, void *);
    void	 vmt_attach(struct device *, struct device *, void *);
    int	 vmt_activate(struct device *, int);
    
    void	 vmt_tclo_tick(void *);
    int	 vmt_tclo_process(struct vmt_softc *, const char *);
    void	 vmt_tclo_reset(struct vmt_softc *);
    void	 vmt_tclo_ping(struct vmt_softc *);
    void	 vmt_tclo_halt(struct vmt_softc *);
    void	 vmt_tclo_reboot(struct vmt_softc *);
    void	 vmt_tclo_poweron(struct vmt_softc *);
    void	 vmt_tclo_suspend(struct vmt_softc *);
    void	 vmt_tclo_resume(struct vmt_softc *);
    void	 vmt_tclo_capreg(struct vmt_softc *);
    void	 vmt_tclo_broadcastip(struct vmt_softc *);
    
    void	 vmt_set_backup_status(struct vmt_softc *, const char *, int,
    	    const char *);
    void	 vmt_quiesce_task(void *);
    void	 vmt_quiesce_done_task(void *);
    void	 vmt_tclo_abortbackup(struct vmt_softc *);
    void	 vmt_tclo_startbackup(struct vmt_softc *);
    void	 vmt_tclo_backupdone(struct vmt_softc *);
    
    size_t	 vmt_xdr_ifaddr(struct ifaddr *, char *);
    size_t	 vmt_xdr_nic_entry(struct ifnet *, char *);
    size_t	 vmt_xdr_nic_info(char *);
    void	 vmt_nicinfo_task(void *);
    
    int	 vmt_probe(void);
    
    struct vmt_tclo_rpc {
    	const char	*name;
    	void		(*cb)(struct vmt_softc *);
    } vmt_tclo_rpc[] = {
    	/* Keep sorted by name (case-sensitive) */
            { "Capabilities_Register",      vmt_tclo_capreg },
            { "OS_Halt",                    vmt_tclo_halt },
            { "OS_PowerOn",                 vmt_tclo_poweron },
            { "OS_Reboot",                  vmt_tclo_reboot },
            { "OS_Resume",                  vmt_tclo_resume },
            { "OS_Suspend",                 vmt_tclo_suspend },
            { "Set_Option broadcastIP 1",   vmt_tclo_broadcastip },
            { "ping",                       vmt_tclo_ping },
            { "reset",                      vmt_tclo_reset },
            { "vmbackup.abort",		vmt_tclo_abortbackup },
            { "vmbackup.snapshotDone",	vmt_tclo_backupdone },
            { "vmbackup.start 1",		vmt_tclo_startbackup },
            { NULL },
    #if 0
    	/* Various unsupported commands */
    	{ "Set_Option autohide 0" },
    	{ "Set_Option copypaste 1" },
    	{ "Set_Option enableDnD 1" },
    	{ "Set_Option enableMessageBusTunnel 0" },
    	{ "Set_Option linkRootHgfsShare 0" },
    	{ "Set_Option mapRootHgfsShare 0" },
    	{ "Set_Option synctime 1" },
    	{ "Set_Option synctime.period 0" },
    	{ "Set_Option time.synchronize.tools.enable 1" },
    	{ "Set_Option time.synchronize.tools.percentCorrection 0" },
    	{ "Set_Option time.synchronize.tools.slewCorrection 1" },
    	{ "Set_Option time.synchronize.tools.startup 1" },
    	{ "Set_Option toolScripts.afterPowerOn 1" },
    	{ "Set_Option toolScripts.afterResume 1" },
    	{ "Set_Option toolScripts.beforePowerOff 1" },
    	{ "Set_Option toolScripts.beforeSuspend 1" },
    	{ "Time_Synchronize 0" },
    	{ "Vix_1_Relayed_Command \"38cdcae40e075d66\"" },
    #endif
    };
    
    const struct cfattach vmt_ca = {
    	sizeof(struct vmt_softc),
    	vmt_match,
    	vmt_attach,
    	NULL,
    	vmt_activate
    };
    
    struct cfdriver vmt_cd = {
    	NULL,
    	"vmt",
    	DV_DULL
    };
    
    extern char hostname[MAXHOSTNAMELEN];
    
    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);
    }
    
    int
    vmt_probe(void)
    {
    	struct vm_backdoor frame;
    
    	vmt_probe_cmd(&frame, VM_CMD_GET_VERSION);
    	if (frame.eax.word == 0xffffffff ||
    	    frame.ebx.word != VM_MAGIC)
    		return (0);
    
    	vmt_probe_cmd(&frame, VM_CMD_GET_SPEED);
    	if (frame.eax.word == VM_MAGIC)
    		return (0);
    
    	return (1);
    }
    
    int
    vmt_match(struct device *parent, void *match, void *aux)
    {
    	struct pv_attach_args	*pva = aux;
    	struct pvbus_hv		*hv = &pva->pva_hv[PVBUS_VMWARE];
    
    	if (hv->hv_base == 0)
    		return (0);
    	if (!vmt_probe())
    		return (0);
    
    	return (1);
    }
    
    void
    vmt_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct vmt_softc *sc = (struct vmt_softc *)self;
    	struct pv_attach_args	*pva = aux;
    	struct pvbus_hv		*hv = &pva->pva_hv[PVBUS_VMWARE];
    
    	printf("\n");
    	sc->sc_rpc_buf = malloc(VMT_RPC_BUFLEN, M_DEVBUF, M_NOWAIT);
    	if (sc->sc_rpc_buf == NULL) {
    		printf("%s: unable to allocate buffer for RPC\n",
    		    DEVNAME(sc));
    		return;
    	}
    
    	if (vm_rpc_open(&sc->sc_tclo_rpc, VM_RPC_OPEN_TCLO) != 0) {
    		printf("%s: failed to open backdoor RPC channel "
    		    "(TCLO protocol)\n", DEVNAME(sc));
    		goto free;
    	}
    
    	/* don't know if this is important at all yet */
    	if (vm_rpc_send_rpci_tx(sc,
    	    "tools.capability.hgfs_server toolbox 1") != 0) {
    		printf(": failed to set HGFS server capability\n");
    		goto free;
    	}
    
    	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
    	    sizeof(sc->sc_sensordev.xname));
    
    	sc->sc_sensor.type = SENSOR_TIMEDELTA;
    	sc->sc_sensor.status = SENSOR_S_UNKNOWN;
    
    	sensor_attach(&sc->sc_sensordev, &sc->sc_sensor);
    	sensordev_install(&sc->sc_sensordev);
    
    	config_mountroot(self, vmt_tick_hook);
    
    	timeout_set(&sc->sc_tclo_tick, vmt_tclo_tick, sc);
    	timeout_add_sec(&sc->sc_tclo_tick, 1);
    	sc->sc_tclo_ping = 1;
    
    	task_set(&sc->sc_nicinfo_task, vmt_nicinfo_task, sc);
    
    	/* pvbus(4) key/value interface */
    	hv->hv_kvop = vmt_kvop;
    	hv->hv_arg = sc;
    
    	return;
    
    free:
    	free(sc->sc_rpc_buf, M_DEVBUF, VMT_RPC_BUFLEN);
    }
    
    int
    vmt_kvop(void *arg, int op, char *key, char *value, size_t valuelen)
    {
    	struct vmt_softc *sc = arg;
    	char *buf = NULL, *ptr;
    	size_t bufsz;
    	int error = 0;
    
    	bufsz = VMT_RPC_BUFLEN;
    	buf = malloc(bufsz, M_TEMP, M_WAITOK | M_ZERO);
    
    	switch (op) {
    	case PVBUS_KVWRITE:
    		if ((size_t)snprintf(buf, bufsz, "info-set %s %s",
    		    key, value) >= bufsz) {
    			DPRINTF("%s: write command too long", DEVNAME(sc));
    			error = EINVAL;
    			goto done;
    		}
    		break;
    	case PVBUS_KVREAD:
    		if ((size_t)snprintf(buf, bufsz, "info-get %s",
    		    key) >= bufsz) {
    			DPRINTF("%s: read command too long", DEVNAME(sc));
    			error = EINVAL;
    			goto done;
    		}
    		break;
    	default:
    		error = EOPNOTSUPP;
    		goto done;
    	}
    
    	if (vm_rpc_send_rpci_tx(sc, "%s", buf) != 0) {
    		DPRINTF("%s: error sending command: %s\n", DEVNAME(sc), buf);
    		sc->sc_rpc_error = 1;
    		error = EIO;
    		goto done;
    	}
    
    	if (vm_rpci_response_successful(sc) == 0) {
    		DPRINTF("%s: host rejected command: %s\n", DEVNAME(sc), buf);
    		error = EINVAL;
    		goto done;
    	}
    
    	/* skip response that was tested in vm_rpci_response_successful() */
    	ptr = sc->sc_rpc_buf + 2;
    
    	/* might truncate, copy anyway but return error */
    	if (strlcpy(value, ptr, valuelen) >= valuelen)
    		error = ENOMEM;
    
     done:
    	free(buf, M_TEMP, bufsz);
    	return (error);
    }
    
    void
    vmt_resume(void)
    {
    	struct vm_backdoor frame;
    	extern void rdrand(void *);
    
    	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);
    
    	rdrand(NULL);
    	enqueue_randomness(frame.eax.word);
    	enqueue_randomness(frame.esi.word);
    	enqueue_randomness(frame.edx.word);
    	enqueue_randomness(frame.ebx.word);
    	resume_randomness(NULL, 0);
    }
    
    int
    vmt_activate(struct device *self, int act)
    {
    	int rv = 0;
    
    	switch (act) {
    	case DVACT_POWERDOWN:
    		vmt_shutdown(self);
    		break;
    	case DVACT_RESUME:
    		vmt_resume();
    		break;
    	}
    	return (rv);
    }
    
    
    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)getuptime()) != 0) {
    		DPRINTF("%s: unable to set guest uptime", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_clear_guest_info(struct vmt_softc *sc)
    {
    	if (sc->sc_nic_info_size != 0) {
    		free(sc->sc_nic_info, M_DEVBUF, sc->sc_nic_info_size);
    		sc->sc_nic_info = NULL;
    		sc->sc_nic_info_size = 0;
    	}
    	sc->sc_hostname[0] = '\0';
    	sc->sc_set_guest_os = 0;
    }
    
    void
    vmt_update_guest_info(struct vmt_softc *sc)
    {
    	if (strncmp(sc->sc_hostname, hostname, sizeof(sc->sc_hostname)) != 0) {
    		strlcpy(sc->sc_hostname, hostname, sizeof(sc->sc_hostname));
    
    		if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo  %d %s",
    		    VM_GUEST_INFO_DNS_NAME, sc->sc_hostname) != 0) {
    			DPRINTF("%s: unable to set hostname", DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    	}
    
    	if (sc->sc_set_guest_os == 0) {
    		if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo  %d %s %s %s",
    		    VM_GUEST_INFO_OS_NAME_FULL,
    		    ostype, osrelease, osversion) != 0) {
    			DPRINTF("%s: unable to set full guest OS", DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    
    		/*
    		 * Host doesn't like it if we send an OS name it doesn't
    		 * recognise, so use the closest match, which happens
    		 * to be FreeBSD.
    		 */
    
    		if (vm_rpc_send_rpci_tx(sc, "SetGuestInfo  %d %s",
    		    VM_GUEST_INFO_OS_NAME, "FreeBSD") != 0) {
    			DPRINTF("%s: unable to set guest OS", DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    
    		sc->sc_set_guest_os = 1;
    	}
    
    	task_add(systq, &sc->sc_nicinfo_task);
    }
    
    void
    vmt_tick_hook(struct device *self)
    {
    	struct vmt_softc *sc = (struct vmt_softc *)self;
    
    	timeout_set(&sc->sc_tick, vmt_tick, sc);
    	vmt_tick(sc);
    }
    
    void
    vmt_tick(void *xarg)
    {
    	struct vmt_softc *sc = xarg;
    	struct vm_backdoor frame;
    	struct timeval *guest = &sc->sc_sensor.tv;
    	struct timeval host, diff;
    
    	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;
    
    		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;
    	} else {
    		sc->sc_sensor.status = SENSOR_S_UNKNOWN;
    	}
    
    	vmt_update_guest_info(sc);
    	vmt_update_guest_uptime(sc);
    
    	timeout_add_sec(&sc->sc_tick, 15);
    }
    
    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) {
    		DPRINTF("%s: unable to send state change result\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    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);
    	pvbus_shutdown(&sc->sc_dev);
    }
    
    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);
    	pvbus_reboot(&sc->sc_dev);
    }
    
    void
    vmt_shutdown(void *arg)
    {
    	struct vmt_softc *sc = arg;
    
    	if (vm_rpc_send_rpci_tx(sc,
    	    "tools.capability.hgfs_server toolbox 0") != 0) {
    		DPRINTF("%s: failed to disable hgfs server capability\n",
    		    DEVNAME(sc));
    	}
    
    	if (vm_rpc_send(&sc->sc_tclo_rpc, NULL, 0) != 0) {
    		DPRINTF("%s: failed to send shutdown ping\n", DEVNAME(sc));
    	}
    
    	vm_rpc_close(&sc->sc_tclo_rpc);
    }
    
    void
    vmt_tclo_reset(struct vmt_softc *sc)
    {
    	if (sc->sc_rpc_error != 0) {
    		DPRINTF("%s: resetting rpc\n", DEVNAME(sc));
    		vm_rpc_close(&sc->sc_tclo_rpc);
    
    		/* reopen and send the reset reply next time around */
    		sc->sc_rpc_error = 1;
    		return;
    	}
    
    	if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_RESET_REPLY) != 0) {
    		DPRINTF("%s: failed to send reset reply\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_ping(struct vmt_softc *sc)
    {
    	if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) {
    		DPRINTF("%s: error sending ping response\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_halt(struct vmt_softc *sc)
    {
    	vmt_do_shutdown(sc);
    }
    
    void
    vmt_tclo_reboot(struct vmt_softc *sc)
    {
    	vmt_do_reboot(sc);
    }
    
    void
    vmt_tclo_poweron(struct vmt_softc *sc)
    {
    	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) {
    		DPRINTF("%s: error sending poweron response\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_suspend(struct vmt_softc *sc)
    {
    	log(LOG_KERN | LOG_NOTICE,
    	    "VMware guest entering suspended state\n");
    
    	suspend_randomness();
    
    	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) {
    		DPRINTF("%s: error sending suspend response\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_resume(struct vmt_softc *sc)
    {
    	log(LOG_KERN | LOG_NOTICE,
    	    "VMware guest resuming from suspended state\n");
    
    	/* force guest info update */
    	vmt_clear_guest_info(sc);
    	vmt_update_guest_info(sc);
    	vmt_resume();
    
    	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) {
    		DPRINTF("%s: error sending resume response\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_capreg(struct vmt_softc *sc)
    {
    	/* don't know if this is important at all */
    	if (vm_rpc_send_rpci_tx(sc,
    	    "vmx.capability.unified_loop toolbox") != 0) {
    		DPRINTF("%s: unable to set unified loop\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    
    	if (vm_rpci_response_successful(sc) == 0) {
    		DPRINTF("%s: host rejected unified loop setting\n",
    		    DEVNAME(sc));
    	}
    
    	/* the trailing space is apparently important here */
    	if (vm_rpc_send_rpci_tx(sc,
    	    "tools.capability.statechange ") != 0) {
    		DPRINTF("%s: unable to send statechange capability\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    
    	if (vm_rpci_response_successful(sc) == 0) {
    		DPRINTF("%s: host rejected statechange capability\n",
    		    DEVNAME(sc));
    	}
    
    	if (vm_rpc_send_rpci_tx(sc, "tools.set.version %u",
    	    VM_VERSION_UNMANAGED) != 0) {
    		DPRINTF("%s: unable to set tools version\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    
    	vmt_clear_guest_info(sc);
    	vmt_update_guest_uptime(sc);
    
    	if (vm_rpc_send_str(&sc->sc_tclo_rpc, VM_RPC_REPLY_OK) != 0) {
    		DPRINTF("%s: error sending capabilities_register"
    		    " response\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_broadcastip(struct vmt_softc *sc)
    {
    	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) {
    		char ip[INET_ADDRSTRLEN];
    
    		inet_ntop(AF_INET, &guest_ip->sin_addr, ip, sizeof(ip));
    		if (vm_rpc_send_rpci_tx(sc, "info-set guestinfo.ip %s",
    		    ip) != 0) {
    			DPRINTF("%s: unable to send guest IP address\n",
    			    DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    
    		if (vm_rpc_send_str(&sc->sc_tclo_rpc,
    		    VM_RPC_REPLY_OK) != 0) {
    			DPRINTF("%s: error sending broadcastIP"
    			    " response\n", DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    	} else {
    		if (vm_rpc_send_str(&sc->sc_tclo_rpc,
    		    VM_RPC_REPLY_ERROR_IP_ADDR) != 0) {
    			DPRINTF("%s: error sending broadcastIP"
    			    " error response\n", DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    	}
    }
    
    void
    vmt_set_backup_status(struct vmt_softc *sc, const char *state, int code,
        const char *desc)
    {
    	if (vm_rpc_send_rpci_tx(sc, "vmbackup.eventSet %s %d %s",
    	    state, code, desc) != 0) {
    		DPRINTF("%s: setting backup status failed\n", DEVNAME(sc));
    	}
    }
    
    void
    vmt_quiesce_task(void *data)
    {
    	struct vmt_softc *sc = data;
    	int err;
    
    	DPRINTF("%s: quiescing filesystems for backup\n", DEVNAME(sc));
    	err = vfs_stall(curproc, 1);
    	if (err != 0) {
    		printf("%s: unable to quiesce filesystems\n", DEVNAME(sc));
    		vfs_stall(curproc, 0);
    
    		vmt_set_backup_status(sc, "req.aborted", VM_BACKUP_SYNC_ERROR,
    		    "vfs_stall failed");
    		vmt_set_backup_status(sc, "req.done", VM_BACKUP_SUCCESS, "");
    		sc->sc_quiesce = 0;
    		return;
    	}
    
    	DPRINTF("%s: filesystems quiesced\n", DEVNAME(sc));
    	vmt_set_backup_status(sc, "prov.snapshotCommit", VM_BACKUP_SUCCESS, "");
    }
    
    void
    vmt_quiesce_done_task(void *data)
    {
    	struct vmt_softc *sc = data;
    
    	vfs_stall(curproc, 0);
    
    	if (sc->sc_quiesce == -1)
    		vmt_set_backup_status(sc, "req.aborted", VM_BACKUP_REMOTE_ABORT,
    		    "");
    
    	vmt_set_backup_status(sc, "req.done", VM_BACKUP_SUCCESS, "");
    	sc->sc_quiesce = 0;
    }
    
    void
    vmt_tclo_abortbackup(struct vmt_softc *sc)
    {
    	const char *reply = VM_RPC_REPLY_OK;
    
    	if (sc->sc_quiesce > 0) {
    		DPRINTF("%s: aborting backup\n", DEVNAME(sc));
    		sc->sc_quiesce = -1;
    		task_set(&sc->sc_quiesce_task, vmt_quiesce_done_task, sc);
    		task_add(systq, &sc->sc_quiesce_task);
    	} else {
    		DPRINTF("%s: can't abort, no backup in progress\n",
    		    DEVNAME(sc));
    		reply = VM_RPC_REPLY_ERROR;
    	}
    
    	if (vm_rpc_send_str(&sc->sc_tclo_rpc, reply) != 0) {
    		DPRINTF("%s: error sending vmbackup.abort reply\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_startbackup(struct vmt_softc *sc)
    {
    	const char *reply = VM_RPC_REPLY_OK;
    
    	if (sc->sc_quiesce == 0) {
    		DPRINTF("%s: starting quiesce\n", DEVNAME(sc));
    		vmt_set_backup_status(sc, "reset", VM_BACKUP_SUCCESS, "");
    
    		task_set(&sc->sc_quiesce_task, vmt_quiesce_task, sc);
    		task_add(systq, &sc->sc_quiesce_task);
    		sc->sc_quiesce = 1;
    	} else {
    		DPRINTF("%s: can't start backup, already in progress\n",
    		    DEVNAME(sc));
    		reply = VM_RPC_REPLY_ERROR;
    	}
    
    	if (vm_rpc_send_str(&sc->sc_tclo_rpc, reply) != 0) {
    		DPRINTF("%s: error sending vmbackup.start reply\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    void
    vmt_tclo_backupdone(struct vmt_softc *sc)
    {
    	const char *reply = VM_RPC_REPLY_OK;
    	if (sc->sc_quiesce > 0) {
    		DPRINTF("%s: backup complete\n", DEVNAME(sc));
    		task_set(&sc->sc_quiesce_task, vmt_quiesce_done_task, sc);
    		task_add(systq, &sc->sc_quiesce_task);
    	} else {
    		DPRINTF("%s: got backup complete, but not doing a backup\n",
    		    DEVNAME(sc));
    		reply = VM_RPC_REPLY_ERROR;
    	}
    
    	if (vm_rpc_send_str(&sc->sc_tclo_rpc, reply) != 0) {
    		DPRINTF("%s: error sending vmbackup.snapshotDone reply\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    	}
    }
    
    int
    vmt_tclo_process(struct vmt_softc *sc, const char *name)
    {
    	int i;
    
    	/* Search for rpc command and call handler */
    	for (i = 0; vmt_tclo_rpc[i].name != NULL; i++) {
    		if (strcmp(vmt_tclo_rpc[i].name, sc->sc_rpc_buf) == 0) {
    			vmt_tclo_rpc[i].cb(sc);
    			return (0);
    		}
    	}
    
    	DPRINTF("%s: unknown command: \"%s\"\n", DEVNAME(sc), name);
    
    	return (-1);
    }
    
    void
    vmt_tclo_tick(void *xarg)
    {
    	struct vmt_softc *sc = xarg;
    	u_int32_t rlen;
    	u_int16_t ack;
    	int delay;
    
    	/* By default, poll every second for new messages */
    	delay = 1;
    
    	if (sc->sc_quiesce > 0) {
    		/* abort quiesce if it's taking too long */
    		if (sc->sc_quiesce++ == VM_BACKUP_TIMEOUT) {
    			printf("%s: aborting quiesce\n", DEVNAME(sc));
    			sc->sc_quiesce = -1;
    			task_set(&sc->sc_quiesce_task, vmt_quiesce_done_task,
    			    sc);
    			task_add(systq, &sc->sc_quiesce_task);
    		} else
    			vmt_set_backup_status(sc, "req.keepAlive",
    			    VM_BACKUP_SUCCESS, "");
    	}
    
    	/* 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) {
    			DPRINTF("%s: unable to reopen TCLO channel\n",
    			    DEVNAME(sc));
    			delay = 15;
    			goto out;
    		}
    
    		if (vm_rpc_send_str(&sc->sc_tclo_rpc,
    		    VM_RPC_RESET_REPLY) != 0) {
    			DPRINTF("%s: failed to send reset reply\n",
    			    DEVNAME(sc));
    			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) {
    			DPRINTF("%s: failed to send TCLO outgoing ping\n",
    			    DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    			goto out;
    		}
    	}
    
    	if (vm_rpc_get_length(&sc->sc_tclo_rpc, &rlen, &ack) != 0) {
    		DPRINTF("%s: failed to get length of incoming TCLO data\n",
    		    DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    		goto out;
    	}
    
    	if (rlen == 0) {
    		sc->sc_tclo_ping = 1;
    		goto out;
    	}
    
    	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) {
    		DPRINTF("%s: failed to get incoming TCLO data\n", DEVNAME(sc));
    		sc->sc_rpc_error = 1;
    		goto out;
    	}
    	sc->sc_tclo_ping = 0;
    
    	/* The VM host can queue multiple messages; continue without delay */
    	delay = 0;
    
    	if (vmt_tclo_process(sc, sc->sc_rpc_buf) != 0) {
    		if (vm_rpc_send_str(&sc->sc_tclo_rpc,
    		    VM_RPC_REPLY_ERROR) != 0) {
    			DPRINTF("%s: error sending unknown command reply\n",
    			    DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    	}
    
    	if (sc->sc_rpc_error == 1) {
    		/* On error, give time to recover and wait a second */
    		delay = 1;
    	}
    
    out:
    	timeout_add_sec(&sc->sc_tclo_tick, delay);
    }
    
    size_t
    vmt_xdr_ifaddr(struct ifaddr *ifa, char *data)
    {
    	struct sockaddr_in *sin;
    	struct vm_nicinfo_addr_v4 v4;
    #ifdef INET6
    	struct sockaddr_in6 *sin6;
    	struct vm_nicinfo_addr_v6 v6;
    #endif
    
    	/* skip loopback addresses and anything that isn't ipv4/v6 */
    	switch (ifa->ifa_addr->sa_family) {
    	case AF_INET:
    		sin = satosin(ifa->ifa_addr);
    		if ((ntohl(sin->sin_addr.s_addr) >>
    		    IN_CLASSA_NSHIFT) != IN_LOOPBACKNET) {
    			if (data != NULL) {
    				memset(&v4, 0, sizeof(v4));
    				htobem32(&v4.v4_addr_type,
    				    VM_NICINFO_ADDR_IPV4);
    				htobem32(&v4.v4_addr_len,
    				    sizeof(struct in_addr));
    				memcpy(&v4.v4_addr, &sin->sin_addr.s_addr,
    				    sizeof(struct in_addr));
    				htobem32(&v4.v4_prefix_len,
    				    rtable_satoplen(AF_INET, ifa->ifa_netmask));
    				memcpy(data, &v4, sizeof(v4));
    			}
    			return (sizeof (v4));
    		}
    		break;
    
    #ifdef INET6
    	case AF_INET6:
    		sin6 = satosin6(ifa->ifa_addr);
    		if (!IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) &&
    		    !IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) {
    			if (data != NULL) {
    				memset(&v6, 0, sizeof(v6));
    				htobem32(&v6.v6_addr_type,
    				    VM_NICINFO_ADDR_IPV6);
    				htobem32(&v6.v6_addr_len,
    				    sizeof(sin6->sin6_addr));
    				memcpy(&v6.v6_addr, &sin6->sin6_addr,
    				    sizeof(sin6->sin6_addr));
    				htobem32(&v6.v6_prefix_len,
    				    rtable_satoplen(AF_INET6,
    				        ifa->ifa_netmask));
    				memcpy(data, &v6, sizeof(v6));
    			}
    			return (sizeof (v6));
    		}
    		break;
    #endif
    
    	default:
    		break;
    	}
    
    	return (0);
    }
    
    size_t
    vmt_xdr_nic_entry(struct ifnet *iface, char *data)
    {
    	struct ifaddr *iface_addr;
    	struct sockaddr_dl *sdl;
    	struct vm_nicinfo_nic nic;
    	struct vm_nicinfo_nic_nomac nnic;
    	char *nicdata;
    	const char *mac;
    	size_t addrsize, total;
    	int addrs;
    
    	total = 0;
    	addrs = 0;
    
    	/* work out if we have a mac address */
    	sdl = iface->if_sadl;
    	if (sdl != NULL && sdl->sdl_alen &&
    	    (sdl->sdl_type == IFT_ETHER || sdl->sdl_type == IFT_CARP))
    		mac = ether_sprintf(sdl->sdl_data + sdl->sdl_nlen);
    	else
    		mac = NULL;
    
    	if (data != NULL) {
    		nicdata = data;
    		if (mac != NULL)
    			data += sizeof(nic);
    		else
    			data += sizeof(nnic);
    	}
    
    	TAILQ_FOREACH(iface_addr, &iface->if_addrlist, ifa_list) {
    		addrsize = vmt_xdr_ifaddr(iface_addr, data);
    		if (addrsize == 0)
    			continue;
    
    		if (data != NULL)
    			data += addrsize;
    		total += addrsize;
    		addrs++;
    		if (addrs == VM_NICINFO_MAX_ADDRS)
    			break;
    	}
    
    	if (addrs == 0)
    		return (0);
    
    	if (data != NULL) {
    		/* fill in mac address, if any */
    		if (mac != NULL) {
    			memset(&nic, 0, sizeof(nic));
    			htobem32(&nic.ni_mac_len, strlen(mac));
    			strncpy(nic.ni_mac, mac, VM_NICINFO_MAC_LEN);
    			htobem32(&nic.ni_num_addrs, addrs);
    			memcpy(nicdata, &nic, sizeof(nic));
    		} else {
    			nnic.nn_mac_len = 0;
    			htobem32(&nnic.nn_num_addrs, addrs);
    			memcpy(nicdata, &nnic, sizeof(nnic));
    		}
    
    		/* we don't actually set anything in vm_nicinfo_nic_post */
    	}
    
    	if (mac != NULL)
    		total += sizeof(nic);
    	else
    		total += sizeof(nnic);
    	total += sizeof(struct vm_nicinfo_nic_post);
    	return (total);
    }
    
    size_t
    vmt_xdr_nic_info(char *data)
    {
    	struct ifnet *iface;
    	struct vm_nicinfo_nic_list nl;
    	size_t total, nictotal;
    	char *listdata = NULL;
    	int nics;
    
    	NET_ASSERT_LOCKED();
    
    	total = sizeof(nl);
    	if (data != NULL) {
    		listdata = data;
    		data += sizeof(nl);
    	}
    
    	nics = 0;
    	TAILQ_FOREACH(iface, &ifnet, if_list) {
    		nictotal = vmt_xdr_nic_entry(iface, data);
    		if (nictotal == 0)
    			continue;
    		
    		if (data != NULL)
    			data += nictotal;
    
    		total += nictotal;
    		nics++;
    		if (nics == VM_NICINFO_MAX_NICS)
    			break;
    	}
    
    	if (listdata != NULL) {
    		memset(&nl, 0, sizeof(nl));
    		htobem32(&nl.nl_version, VM_NICINFO_VERSION);
    		htobem32(&nl.nl_nic_list, 1);
    		htobem32(&nl.nl_num_nics, nics);
    		memcpy(listdata, &nl, sizeof(nl));
    	}
    
    	/* we don't actually set anything in vm_nicinfo_nic_list_post */
    	total += sizeof(struct vm_nicinfo_nic_list_post);
    
    	return (total);
    }
    
    void
    vmt_nicinfo_task(void *data)
    {
    	struct vmt_softc *sc = data;
    	size_t nic_info_size;
    	char *nic_info;
    
    	NET_LOCK();
    
    	nic_info_size = vmt_xdr_nic_info(NULL) + sizeof(VM_NICINFO_CMD) - 1;
    	nic_info = malloc(nic_info_size, M_DEVBUF, M_WAITOK | M_ZERO);
    
    	strncpy(nic_info, VM_NICINFO_CMD, nic_info_size);
    	vmt_xdr_nic_info(nic_info + sizeof(VM_NICINFO_CMD) - 1);
    
    	NET_UNLOCK();
    
    	if (nic_info_size != sc->sc_nic_info_size ||
    	    (memcmp(nic_info, sc->sc_nic_info, nic_info_size) != 0)) {
    		if (vm_rpc_send_rpci_tx_buf(sc, nic_info,
    		    nic_info_size) != 0) {
    			DPRINTF("%s: unable to send nic info",
    			    DEVNAME(sc));
    			sc->sc_rpc_error = 1;
    		}
    
    		free(sc->sc_nic_info, M_DEVBUF, sc->sc_nic_info_size);
    		sc->sc_nic_info = nic_info;
    		sc->sc_nic_info_size = nic_info_size;
    	} else {
    		free(nic_info, M_DEVBUF, nic_info_size);
    	}
    }
    
    #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
    
    void
    vm_cmd(struct vm_backdoor *frame)
    {
    	BACKDOOR_OP("inl %%dx, %%eax;", frame);
    }
    
    void
    vm_ins(struct vm_backdoor *frame)
    {
    	BACKDOOR_OP("cld;\n\trep insb;", frame);
    }
    
    void
    vm_outs(struct vm_backdoor *frame)
    {
    	BACKDOOR_OP("cld;\n\trep outsb;", frame);
    }
    
    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.. */
    		DPRINTF("vmware: 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;
    }
    
    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) {
    		DPRINTF("vmware: 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;
    }
    
    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) {
    		DPRINTF("vmware: sending length failed, eax=%08x, ecx=%08x\n",
    		    frame.eax.word, frame.ecx.word);
    		return EIO;
    	}
    
    	if (length == 0)
    		return 0; /* Only need to poke once if command is null. */
    
    	/* 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 */
    		DPRINTF("vmware: send failed, ebx=%08x\n", frame.ebx.word);
    		return EIO;
    	}
    
    	return 0;
    }
    
    int
    vm_rpc_send_str(const struct vm_rpc *rpc, const uint8_t *str)
    {
    	return vm_rpc_send(rpc, str, strlen(str));
    }
    
    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) {
    		DPRINTF("vmware: 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) {
    		DPRINTF("vmware: ack data failed, eax=%08x, ecx=%08x\n",
    		    frame.eax.word, frame.ecx.word);
    		return EIO;
    	}
    
    	return 0;
    }
    
    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) {
    		DPRINTF("vmware: 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;
    }
    
    int
    vm_rpci_response_successful(struct vmt_softc *sc)
    {
    	return (sc->sc_rpc_buf[0] == '1' && sc->sc_rpc_buf[1] == ' ');
    }
    
    int
    vm_rpc_send_rpci_tx_buf(struct vmt_softc *sc, const uint8_t *buf,
        uint32_t length)
    {
    	struct vm_rpc rpci;
    	u_int32_t rlen;
    	u_int16_t ack;
    	int result = 0;
    
    	if (vm_rpc_open(&rpci, VM_RPC_OPEN_RPCI) != 0) {
    		DPRINTF("%s: rpci channel open failed\n", DEVNAME(sc));
    		return EIO;
    	}
    
    	if (vm_rpc_send(&rpci, buf, length) != 0) {
    		DPRINTF("%s: unable to send rpci command\n", DEVNAME(sc));
    		result = EIO;
    		goto out;
    	}
    
    	if (vm_rpc_get_length(&rpci, &rlen, &ack) != 0) {
    		DPRINTF("%s: failed to get length of rpci response data\n",
    		    DEVNAME(sc));
    		result = 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) {
    			DPRINTF("%s: failed to get rpci response data\n",
    			    DEVNAME(sc));
    			result = EIO;
    			goto out;
    		}
    	}
    
    out:
    	if (vm_rpc_close(&rpci) != 0) {
    		DPRINTF("%s: unable to close rpci channel\n", DEVNAME(sc));
    	}
    
    	return result;
    }
    
    int
    vm_rpc_send_rpci_tx(struct vmt_softc *sc, const char *fmt, ...)
    {
    	va_list args;
    	int len;
    
    	va_start(args, fmt);
    	len = vsnprintf(sc->sc_rpc_buf, VMT_RPC_BUFLEN, fmt, args);
    	va_end(args);
    
    	if (len >= VMT_RPC_BUFLEN) {
    		DPRINTF("%s: rpci command didn't fit in buffer\n", DEVNAME(sc));
    		return EIO;
    	}
    
    	return vm_rpc_send_rpci_tx_buf(sc, sc->sc_rpc_buf, len);
    }
    
    #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);
    	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);
    #endif
    
    /*
     * Notes on tracing backdoor activity in vmware-guestd:
     *
     * - Find the addresses of the inl / rep insb / rep outsb
     *   instructions used to perform backdoor operations.
     *   One way to do this is to disassemble vmware-guestd:
     *
     *   $ objdump -S /emul/freebsd/sbin/vmware-guestd > vmware-guestd.S
     *
     *   and search for '<tab>in ' in the resulting file.  The rep insb and
     *   rep outsb code is directly below that.
     *
     * - Run vmware-guestd under gdb, setting up breakpoints as follows:
     *   (the addresses shown here are the ones from VMware-server-1.0.10-203137,
     *   the last version that actually works in FreeBSD emulation on OpenBSD)
     *
     * break *0x805497b   (address of 'in' instruction)
     * commands 1
     * silent
     * echo INOUT\n
     * print/x $ecx
     * print/x $ebx
     * print/x $edx
     * continue
     * end
     * break *0x805497c   (address of instruction after 'in')
     * commands 2
     * silent
     * echo ===\n
     * print/x $ecx
     * print/x $ebx
     * print/x $edx
     * echo \n
     * continue
     * end
     * break *0x80549b7   (address of instruction before 'rep insb')
     * commands 3
     * silent
     * set variable $inaddr = $edi
     * set variable $incount = $ecx
     * continue
     * end
     * break *0x80549ba   (address of instruction after 'rep insb')
     * commands 4
     * silent
     * echo IN\n
     * print $incount
     * x/s $inaddr
     * echo \n
     * continue
     * end
     * break *0x80549fb    (address of instruction before 'rep outsb')
     * commands 5
     * silent
     * echo OUT\n
     * print $ecx
     * x/s $esi
     * echo \n
     * continue
     * end
     *
     * This will produce a log of the backdoor operations, including the
     * data sent and received and the relevant register values.  You can then
     * match the register values to the various constants in this file.
     */