/*
 * Copyright 2002-2006 by VMware, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of the copyright holder(s)
 * and author(s) shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from the copyright holder(s) and author(s).
 */

/*
 * vmmouse_client.c --
 *
 *      VMware Virtual Mouse Client
 *
 * This module provides functions to enable, operate and process
 * packets via the VMMouse module hosted in the VMX.
 *
 */
#include "config.h"

#include <stdbool.h>

#include "vmmouse_client.h"
#include "vmmouse_proto.h"

/*
 *----------------------------------------------------------------------------
 *
 * VMMouseClientVMCheck --
 *
 *      Checks if we're running in a VM by sending the GETVERSION command.
 *
 * Returns:
 *      0 if we're running natively/the version command failed,
 *      1 if we're in a VM.
 *
 *----------------------------------------------------------------------------
 */

static bool
VMMouseClientVMCheck(void)
{
   VMMouseProtoCmd vmpc;

   vmpc.in.vEbx = ~VMMOUSE_PROTO_MAGIC;
   vmpc.in.command = VMMOUSE_PROTO_CMD_GETVERSION;
   VMMouseProto_SendCmd(&vmpc);

   /*
    * ebx should contain VMMOUSE_PROTO_MAGIC
    * eax should contain version
    */
   if (vmpc.out.vEbx != VMMOUSE_PROTO_MAGIC || vmpc.out.vEax == 0xffffffff) {
      return false;
   }

   return true;
}


/*
 *----------------------------------------------------------------------
 *
 * VMMouseClient_Disable --
 *
 *	Tries to disable VMMouse communication mode on the host.
 *	The caller is responsible for maintaining state (we don't check
 *	if we're enabled before attempting to disable the VMMouse).
 *
 * Results:
 *	true if we successfully disable the VMMouse communication mode,
 *	false if something went wrong.
 *
 * Side effects:
 *	Disables the absolute positioning mode.
 *
 *----------------------------------------------------------------------
 */

void
VMMouseClient_Disable(void)
{
   uint32_t status;
   VMMouseProtoCmd vmpc;

   VMwareLog(("VMMouseClient_Disable: writing disable command to port\n"));
   vmpc.in.vEbx = VMMOUSE_CMD_DISABLE;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND;
   VMMouseProto_SendCmd(&vmpc);
   /*
    * We should get 0xffff in the flags now.
    */
   vmpc.in.vEbx = 0;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_STATUS;
   VMMouseProto_SendCmd(&vmpc);
   status = vmpc.out.vEax;
   if ((status & VMMOUSE_ERROR) != VMMOUSE_ERROR) {
      VMwareLog(("VMMouseClient_Disable: wrong status returned\n"));
   }
}


/*
 *----------------------------------------------------------------------
 *
 * VMMouseClient_Enable --
 *
 *	Public Enable entry point. The driver calls this once it feels
 *	ready to deal with VMMouse stuff. For now, we just try to enable
 *	and return the result, but conceivably we could do more.
 *
 * Results:
 *	true if the enable succeeds, false otherwise.
 *
 * Side effects:
 *	Causes host-side state change.
 *
 *----------------------------------------------------------------------
 */

bool
VMMouseClient_Enable(void) {

   uint32_t status;
   uint32_t data;
   VMMouseProtoCmd vmpc;

   /*
    * First, make sure we're in a VM; i.e. in dualboot configurations we might
    * find ourselves running on real hardware.
    */

   if (!VMMouseClientVMCheck()) {
      return false;
   }

   VMwareLog(("VMMouseClientVMCheck succeeded, checking VMMOUSE version\n"));
   VMwareLog(("VMMouseClient_Enable: READ_ID 0x%08x, VERSION_ID 0x%08x\n",
       VMMOUSE_CMD_READ_ID, VMMOUSE_VERSION_ID));

   /*
    * We probe for the VMMouse backend by sending the ENABLE
    * command to the mouse. We should get back the VERSION_ID on
    * the data port.
    */
   vmpc.in.vEbx = VMMOUSE_CMD_READ_ID;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND;
   VMMouseProto_SendCmd(&vmpc);

   /*
    * Check whether the VMMOUSE_VERSION_ID is available to read
    */
   vmpc.in.vEbx = 0;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_STATUS;
   VMMouseProto_SendCmd(&vmpc);
   status = vmpc.out.vEax;
   if ((status & 0x0000ffff) == 0) {
      VMwareLog(("VMMouseClient_Enable: no data on port."));
      return false;
   }

   /*
    * Get the VMMOUSE_VERSION_ID then
    */
   /* Get just one item */
   vmpc.in.vEbx = 1;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_DATA;
   VMMouseProto_SendCmd(&vmpc);
   data = vmpc.out.vEax;
   if (data!= VMMOUSE_VERSION_ID) {
      VMwareLog(("VMMouseClient_Enable: data was not VERSION_ID"));
      return false;
   }

   /*
    * Restrict access to the VMMouse backdoor handler.
    */
   vmpc.in.vEbx = VMMOUSE_RESTRICT_IOPL;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_RESTRICT;
   VMMouseProto_SendCmd(&vmpc);

   /*
    * To quote Jeremy, "Go Go Go!"
    */

   VMwareLog(("VMMouseClient_Enable: go go go!\n"));
   return true;
}


/*
 *----------------------------------------------------------------------
 *
 * VMMouseClient_GetInput --
 *
 *	Retrieves a 4-word input packet from the VMMouse data port and
 *	stores it in the specified input structure.
 *
 * Results:
 *	The number of packets in the queue, including the retrieved
 *	packet.
 *
 * Side effects:
 *	Could cause host state change.
 *
 *----------------------------------------------------------------------
 */

unsigned int
VMMouseClient_GetInput (PVMMOUSE_INPUT_DATA pvmmouseInput) {

   uint32_t status;
   uint16_t numWords;
   uint32_t packetInfo;
   VMMouseProtoCmd vmpc;

   /*
    * The status dword has two parts: the high 16 bits are
    * for flags, the low 16-bits are the number of DWORDs
    * waiting in the data queue. VMMOUSE_ERROR is a special
    * case that indicates there's something wrong on the
    * host end, e.g. the VMMouse was disabled on the host-side.
    */
   vmpc.in.vEbx = 0;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_STATUS;
   VMMouseProto_SendCmd(&vmpc);
   status = vmpc.out.vEax;
   if ((status & VMMOUSE_ERROR) == VMMOUSE_ERROR) {
      VMwareLog(("VMMouseClient_GetInput: VMMOUSE_ERROR status, abort!\n"));
      return VMMOUSE_ERROR;
   }

   /*
    * We don't use the status flags, just get the words
    */
   numWords = status & 0x0000ffff;

   if ((numWords % 4) != 0) {
      VMwareLog(("VMMouseClient_GetInput: invalid status numWords, abort!\n"));
      return (0);
   }

   if (numWords == 0) {
      return (0);
   }

   /*
    * The VMMouse uses a 4-dword packet protocol:
    *	DWORD 0: Button State and per-packet flags
    *	DWORD 1: X position (absolute or relative)
    *	DWORD 2: Y position (absolute or relative)
    *	DWORD 3: Z position (relative)
    */
   /* Get 4 items at once */
   vmpc.in.vEbx = 4;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_DATA;
   VMMouseProto_SendCmd(&vmpc);
   packetInfo = vmpc.out.vEax;
   pvmmouseInput->Flags = (packetInfo & 0xffff0000) >> 16;
   pvmmouseInput->Buttons = (packetInfo & 0x0000ffff);

   /* Note that Z is always signed, and X/Y are signed in relative mode. */
   pvmmouseInput->X = (int)vmpc.out.vEbx;
   pvmmouseInput->Y = (int)vmpc.out.vEcx;
   pvmmouseInput->Z = (int)vmpc.out.vEdx;

   /*
    * Return number of packets (including this one) in queue.
    */
   return (numWords >> 2);
}


/*
 *----------------------------------------------------------------------------
 *
 * VMMouseClient_RequestRelative --
 *
 *      Request that the host switch to posting relative packets. It's just
 *      advisory, so we make no guarantees about if/when the switch will
 *      happen.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Host may start posting relative packets in the near future.
 *
 *----------------------------------------------------------------------------
 */

void
VMMouseClient_RequestRelative(void)
{
   VMMouseProtoCmd vmpc;

   VMwareLog(("VMMouseClient: requesting relative mode\n"));
   vmpc.in.vEbx = VMMOUSE_CMD_REQUEST_RELATIVE;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND;
   VMMouseProto_SendCmd(&vmpc);
}


/*
 *----------------------------------------------------------------------------
 *
 * VMMouseClient_RequestAbsolute --
 *
 *      Request that the host switch to posting absolute packets. It's just
 *      advisory, so we make no guarantees about if/when the switch will
 *      happen.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Host may start posting absolute packets in the near future.
 *
 *----------------------------------------------------------------------------
 */

void
VMMouseClient_RequestAbsolute(void)
{
   VMMouseProtoCmd vmpc;

   VMwareLog(("VMMouseClient: requesting absolute mode\n"));
   vmpc.in.vEbx = VMMOUSE_CMD_REQUEST_ABSOLUTE;
   vmpc.in.command = VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND;
   VMMouseProto_SendCmd(&vmpc);
}
