/*
	This file is part of Fennix Drivers.

	Fennix Drivers is free software: you can redistribute it and/or
	modify it under the terms of the GNU General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	Fennix Drivers is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Fennix Drivers. If not, see <https://www.gnu.org/licenses/>.
*/

#include <driver.h>
#include <errno.h>
#include <fs.h>
#include <input.h>
#include <regs.h>
#include <base.h>
#include <aip.h>
#include <io.h>

enum RPCMessages
{
	MSG_OPEN,
	MSG_SENDSIZE,
	MSG_SENDPAYLOAD,
	MSG_RECVSIZE,
	MSG_RECVPAYLOAD,
	MSG_RECVSTATUS,
	MSG_CLOSE,
};

enum RPCStatus
{
	STATUS_SUCCESS = 0x1,
	STATUS_DORECV = 0x2,
	STATUS_CPT = 0x10,
	STATUS_HB = 0x80,
};

typedef struct
{
	union
	{
		uint32_t ax;
		uint32_t magic;
	};
	union
	{
		uint32_t bx;
		size_t size;
	};
	union
	{
		uint32_t cx;
		uint16_t command;
	};
	union
	{
		uint32_t dx;
		uint16_t port;
	};
	uint32_t si;
	uint32_t di;
} VMwareCommand;

#define VMWARE_MAGIC 0x564D5868

#define VMWARE_PORT 0x5658
#define VMWARE_PORTHB 0x5659

#define VMWARE_HYPERVISOR_HB 0x00000000
#define VMWARE_HYPERVISOR_OUT 0x00000001

#define CMD_GETVERSION 0xA
#define CMD_MESSAGE 0x1E
#define CMD_ABSPOINTER_DATA 0x27
#define CMD_ABSPOINTER_STATUS 0x28
#define CMD_ABSPOINTER_COMMAND 0x29

#define ABSPOINTER_ENABLE 0x45414552
#define ABSPOINTER_RELATIVE 0xF5
#define ABSPOINTER_ABSOLUTE 0x53424152

#define MESSAGE_RPCI 0x49435052
#define MESSAGE_TCLO 0x4f4c4354

#define FLAG_COOKIE 0x80000000

#define ToMsg(x) ((x) << 16 | CMD_MESSAGE)
#define HighWord(x) ((x & 0xFFFF0000) >> 16)

#define MESSAGE_HB_MSG 0

#define MESSAGE_OPEN_CHANNEL ToMsg(MSG_OPEN)
#define MESSAGE_CLOSE_CHANNEL ToMsg(MSG_CLOSE)

#define MESSAGE_SEND_SIZE ToMsg(MSG_SENDSIZE)
#define MESSAGE_SEND_PAYLOAD ToMsg(MSG_SENDPAYLOAD)

#define MESSAGE_RECV_SIZE ToMsg(MSG_RECVSIZE)
#define MESSAGE_RECV_PAYLOAD ToMsg(MSG_RECVPAYLOAD)
#define MESSAGE_RECV_STATUS ToMsg(MSG_RECVSTATUS)

#define VM_PORT(cmd, in_ebx, isi, idi,          \
				flags, magic,                   \
				ax, bx, cx, dx, si, di)         \
	__asm__ __volatile__("movw $0x5658, %%dx\n" \
						 "inl %%dx, %%eax\n"    \
						 : "=a"(ax),            \
						   "=b"(bx),            \
						   "=c"(cx),            \
						   "=d"(dx),            \
						   "=S"(si),            \
						   "=D"(di)             \
						 : "a"(magic),          \
						   "b"(in_ebx),         \
						   "c"(cmd),            \
						   "d"(flags),          \
						   "S"(isi),            \
						   "D"(idi) : "memory")

#define VM_PORT_HB_OUT(cmd, in_ecx, isi, idi,   \
					   flags, magic, bp,        \
					   ax, bx, cx, dx, si, di)  \
	__asm__ __volatile__("push %%rbp\n"         \
						 "mov %12, %%rbp\n"     \
						 "movw $0x5659, %%dx\n" \
						 "rep outsb\n"          \
						 "pop %%rbp\n"          \
						 : "=a"(ax),            \
						   "=b"(bx),            \
						   "=c"(cx),            \
						   "=d"(dx),            \
						   "=S"(si),            \
						   "=D"(di)             \
						 : "a"(magic),          \
						   "b"(cmd),            \
						   "c"(in_ecx),         \
						   "d"(flags),          \
						   "S"(isi),            \
						   "D"(idi),            \
						   "r"(bp) : "memory", "cc")

#define VM_PORT_HB_IN(cmd, in_ecx, isi, idi,    \
					  flags, magic, bp,         \
					  ax, bx, cx, dx, si, di)   \
	__asm__ __volatile__("push %%rbp\n"         \
						 "mov %12, %%rbp\n"     \
						 "movw $0x5659, %%dx\n" \
						 "rep insb\n"           \
						 "pop %%rbp\n"          \
						 : "=a"(ax),            \
						   "=b"(bx),            \
						   "=c"(cx),            \
						   "=d"(dx),            \
						   "=S"(si),            \
						   "=D"(di)             \
						 : "a"(magic),          \
						   "b"(cmd),            \
						   "c"(in_ecx),         \
						   "d"(flags),          \
						   "S"(isi),            \
						   "D"(idi),            \
						   "r"(bp) : "memory", "cc")

/* TODO:
	- use vmcall or vmmcall instead of "out" and "in" if available
*/

typedef struct
{
	int TCLOChannel;
	uint16_t ChannelID;
	uint32_t CookieHigh;
	uint32_t CookieLow;
} ToolboxContext;

dev_t MouseDevID = -1;

int __strcmp(const char *l, const char *r)
{
	for (; *l == *r && *l; l++, r++)
		;

	return *(unsigned char *)l - *(unsigned char *)r;
}

void __cpuid(uint32_t Function,
			 uint32_t *eax, uint32_t *ebx,
			 uint32_t *ecx, uint32_t *edx)
{
	asmv("cpuid"
		 : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
		 : "a"(Function));
}

bool __CheckHypervisorBit()
{
	uint32_t eax, ebx, ecx, edx;
	__cpuid(0x1, &eax, &ebx, &ecx, &edx);
	if (!(ecx & (1 << 31)))
		return false; /* Hypervisor not detected */
	return true;
}

bool __VMwareBackdoorHypervisors()
{
	const char hv[13] = {0};
	uint32_t eax, ebx, ecx, edx;
	__cpuid(0x40000000, &eax, &ebx, &ecx, &edx);

	*(uint32_t *)hv = ebx;
	*(uint32_t *)(hv + 4) = ecx;
	*(uint32_t *)(hv + 8) = edx;

	if (__strcmp(hv, "VMwareVMware") != 0 &&
		__strcmp(hv, "KVMKVMKVM") != 0 &&
		__strcmp(hv, "TCGTCGTCGTCG") != 0)
	{
		return false;
	}
	return true;
}

bool IsVMwareBackdoorAvailable()
{
	if (!__CheckHypervisorBit())
		return false;

	if (!__VMwareBackdoorHypervisors())
		return false;

	struct
	{
		union
		{
			uint32_t ax;
			uint32_t magic;
		};
		union
		{
			uint32_t bx;
			size_t size;
		};
		union
		{
			uint32_t cx;
			uint16_t command;
		};
		union
		{
			uint32_t dx;
			uint16_t port;
		};
		uint32_t si;
		uint32_t di;
	} cmd;

	cmd.si = cmd.di = 0;
	cmd.bx = ~0x564D5868;
	cmd.command = 0xA;
	cmd.magic = 0x564D5868;
	cmd.port = 0x5658;

	asmv("in %%dx, %0"
		 : "+a"(cmd.ax), "+b"(cmd.bx),
		   "+c"(cmd.cx), "+d"(cmd.dx),
		   "+S"(cmd.si), "+D"(cmd.di));

	if (cmd.bx != 0x564D5868 ||
		cmd.ax == 0xFFFFFFFF)
		return false;
	return true;
}

static int OpenMessageChannel(ToolboxContext *ctx, uint32_t Protocol)
{
	uintptr_t ax, bx, cx, dx, si = 0, di = 0;

	VM_PORT(MESSAGE_OPEN_CHANNEL,
			(Protocol | FLAG_COOKIE), si, di,
			0, VMWARE_MAGIC,
			ax, bx, cx, dx, si, di);

	if ((HighWord(cx) & STATUS_SUCCESS) == 0)
	{
		KernelLog("Failed to open message channel %#lx", Protocol);
		return -EINVAL;
	}

	DebugLog("Opened message channel %d (Protocol: %#lx)",
			 HighWord(dx), Protocol);
	ctx->ChannelID = (uint16_t)HighWord(dx);
	ctx->CookieHigh = si;
	ctx->CookieLow = di;
	return 0;
}

static void MessageClose(ToolboxContext *ctx)
{
	uintptr_t ax, bx, cx, dx,
		si = ctx->CookieHigh,
		di = ctx->CookieLow;

	VM_PORT(MESSAGE_CLOSE_CHANNEL,
			0, si, di,
			ctx->ChannelID << 16,
			VMWARE_MAGIC,
			ax, bx, cx, dx, si, di);

	DebugLog("Closed message channel %d", ctx->ChannelID);
}

static uintptr_t MessageSendHB(ToolboxContext *ctx,
							   const char *Message)
{
	uintptr_t ax, bx, cx, dx,
		si = (uintptr_t)Message,
		di = ctx->CookieLow,
		bp = ctx->CookieHigh;

	uint32_t ChannelID = ctx->ChannelID << 16;
	size_t Size = StringLength(Message);

	VM_PORT_HB_OUT((STATUS_SUCCESS << 16) | MESSAGE_HB_MSG,
				   Size, si, di,
				   VMWARE_HYPERVISOR_HB | ChannelID | VMWARE_HYPERVISOR_OUT,
				   VMWARE_MAGIC, bp,
				   ax, bx, cx, dx, si, di);

	return bx;
}

static uintptr_t MessageSendLB(ToolboxContext *ctx,
							   const char *Message)
{
	uintptr_t ax, bx,
		cx = STATUS_SUCCESS << 16,
		dx, si, di;

	size_t Size = StringLength(Message);
	while (Size &&
		   (HighWord(cx) & STATUS_SUCCESS))
	{
		uint32_t TotalBytes = MIN((uint32_t)Size, (uint32_t)4);
		uint32_t Word = 0;
		MemoryCopy(&Word, Message, TotalBytes);
		Message += TotalBytes;

		si = ctx->CookieHigh;
		di = ctx->CookieLow;

		VM_PORT(MESSAGE_SEND_PAYLOAD,
				Word, si, di,
				ctx->ChannelID << 16,
				VMWARE_MAGIC,
				ax, bx, cx, dx, si, di);
	}

	return cx;
}

static uintptr_t MessageReceiveHB(ToolboxContext *ctx,
								  char *Buffer,
								  size_t BufferSize)
{
	uintptr_t ax, bx, cx, dx,
		si = ctx->CookieHigh,
		di = (uintptr_t)Buffer,
		bp = ctx->CookieLow;

	uint32_t ChannelID = ctx->ChannelID << 16;

	VM_PORT_HB_IN((STATUS_SUCCESS << 16) | MESSAGE_HB_MSG,
				  BufferSize, si, di,
				  VMWARE_HYPERVISOR_HB | ChannelID | VMWARE_HYPERVISOR_OUT,
				  VMWARE_MAGIC, bp,
				  ax, bx, cx, dx, si, di);

	return bx;
}

static uintptr_t MessageReceiveLB(ToolboxContext *ctx,
								  char *Buffer,
								  size_t BufferSize)
{
	uintptr_t ax, bx,
		cx = STATUS_SUCCESS << 16,
		dx, si, di;

	while (BufferSize)
	{
		uint32_t TotalBytes = MIN((uint32_t)BufferSize, (uint32_t)4);

		si = ctx->CookieHigh;
		di = ctx->CookieLow;

		VM_PORT(MESSAGE_RECV_PAYLOAD,
				STATUS_SUCCESS, si, di,
				ctx->ChannelID << 16,
				VMWARE_MAGIC,
				ax, bx, cx, dx, si, di);

		if ((HighWord(cx) & STATUS_SUCCESS) == 0)
			break;

		MemoryCopy(Buffer, &bx, TotalBytes);
		Buffer += TotalBytes;
		BufferSize -= TotalBytes;
	}

	return cx;
}

static int MessageSend(ToolboxContext *ctx,
					   const char *Message)
{
	uintptr_t ax, bx, cx, dx, si, di;
	size_t Size = StringLength(Message);
	int Retries = 0;

	while (Retries < 2)
	{
		Retries++;
		si = ctx->CookieHigh;
		di = ctx->CookieLow;

		VM_PORT(MESSAGE_SEND_SIZE,
				Size, si, di,
				ctx->ChannelID << 16,
				VMWARE_MAGIC,
				ax, bx, cx, dx, si, di);

		if ((HighWord(cx) & STATUS_SUCCESS) == 0)
		{
			KernelLog("Failed to send message size for \"%s\": %d",
					  Message, cx);
			return -EINVAL;
		}

		bool HighBand = (HighWord(cx) & STATUS_HB) != 0;
		if (HighBand)
			bx = MessageSendHB(ctx, Message);
		else
			bx = MessageSendLB(ctx, Message);

		int status = HighWord(bx);

		if ((status & STATUS_SUCCESS) != 0)
		{
			DebugLog("Message \"%s\" sent", Message);
			return 0;
		}
		else if ((status & STATUS_CPT) == 0)
		{
			KernelLog("Checkpoint occurred for message \"%s\"", Message);
			continue;
		}
		else
			break;
	}

	KernelLog("Failed to send message \"%s\": %#lx", Message, bx);
	return -EINVAL;
}

static int MessageReceive(ToolboxContext *ctx,
						  char **Buffer,
						  size_t *BufferSize)
{
	uintptr_t ax, bx, cx, dx, si, di;
	int Retries = 0;

	*Buffer = NULL;
	*BufferSize = 0;

	char *ReplyBuf = NULL;
	size_t ReplyBufPages = 0;
	size_t ReplySize = 0;
	while (Retries < 2)
	{
		Retries++;
		si = ctx->CookieHigh;
		di = ctx->CookieLow;

		VM_PORT(MESSAGE_RECV_SIZE,
				0, si, di,
				ctx->ChannelID << 16,
				VMWARE_MAGIC,
				ax, bx, cx, dx, si, di);

		if ((HighWord(cx) & STATUS_SUCCESS) == 0)
		{
			KernelLog("Failed to receive message size: %d", cx);
			return -EINVAL;
		}
		else if ((HighWord(cx) & STATUS_DORECV) == 0)
		{
			DebugLog("No message to receive");
			return -EAGAIN;
		}

		ReplySize = bx;

		if (ReplyBuf != NULL)
			FreeMemory(ReplyBuf, ReplyBufPages);
		ReplyBufPages = ReplySize / 0x1000 + 1;
		ReplyBuf = AllocateMemory(ReplyBufPages);

		bool HighBand = (HighWord(cx) & STATUS_HB) != 0;
		if (HighBand)
			bx = MessageReceiveHB(ctx, ReplyBuf, ReplySize);
		else
			bx = MessageReceiveLB(ctx, ReplyBuf, ReplySize);

		if ((HighWord(bx) & STATUS_SUCCESS) == 0)
		{
			if ((HighWord(bx) & STATUS_CPT) == 0)
			{
				KernelLog("Checkpoint occurred for message payload");
				continue;
			}

			KernelLog("Failed to receive message payload: %d", HighWord(bx));
			FreeMemory(ReplyBuf, ReplyBufPages);
			return -EINVAL;
		}

		ReplyBuf[ReplySize] = '\0';

		si = ctx->CookieHigh;
		di = ctx->CookieLow;

		VM_PORT(MESSAGE_RECV_STATUS,
				STATUS_SUCCESS, si, di,
				ctx->ChannelID << 16,
				VMWARE_MAGIC,
				ax, bx, cx, dx, si, di);

		if ((HighWord(cx) & STATUS_SUCCESS) == 0)
		{
			if ((HighWord(cx) & STATUS_CPT) == 0)
			{
				KernelLog("Retrying message receive");
				continue;
			}

			KernelLog("Failed to receive message status: %d", HighWord(cx));
			FreeMemory(ReplyBuf, ReplyBufPages);
			return -EINVAL;
		}

		break;
	}

	if (ReplyBuf == NULL)
	{
		KernelLog("Failed to receive message");
		return -EINVAL;
	}

	*Buffer = ReplyBuf;
	*BufferSize = ReplySize;
	DebugLog("Received message \"%s\"", ReplyBuf);
	return 0;
}

static int SendRPCI(ToolboxContext *, const char *Request)
{
	ToolboxContext rpci_ctx = {0};
	int status = OpenMessageChannel(&rpci_ctx, MESSAGE_RPCI);
	if (status < 0)
	{
		KernelLog("Failed to open RPCI channel: %d", status);
		return status;
	}

	status = MessageSend(&rpci_ctx, Request);
	if (status < 0)
	{
		KernelLog("Failed to send RPCI request: %d", status);
		return status;
	}

	MessageClose(&rpci_ctx);
	return 0;
}

int MsgEqual(const char *haystack, const char *needle)
{
	return strstr(haystack, needle) == haystack;
}

static int DisplayGetSize(ToolboxContext *ctx)
{
	if (ctx->TCLOChannel != -1)
		MessageClose(ctx);
	OpenMessageChannel(ctx, MESSAGE_TCLO);

	char EmptyBuffer[256] = {'\0'};
	MessageSend(ctx, EmptyBuffer);

	while (true)
	{
		/* FIXME: buf memory leak */
		char *buf;
		size_t len;

		int status = MessageReceive(ctx, &buf, &len);
		if (status == -EAGAIN)
		{
			Sleep(1000);
			continue;
		}
		else if (status < 0)
		{
			KernelLog("Failed to receive message");
			return 1;
		}

		buf[StringLength(buf)] = '\0';
		if (MsgEqual(buf, "reset"))
		{
			if (MessageSend(ctx, "OK ATR toolbox") < 0)
				return 1;
		}
		else if (MsgEqual(buf, "ping"))
		{
			if (MessageSend(ctx, "OK ") < 0)
				return 1;
		}
		else if (MsgEqual(buf, "Capabilities_Register"))
		{
			SendRPCI(ctx, "tools.capability.resolution_set 1");
			SendRPCI(ctx, "tools.capability.resolution_server toolbox 1");
			SendRPCI(ctx, "tools.capability.display_topology_set 1");
			SendRPCI(ctx, "tools.capability.color_depth_set 1");
			SendRPCI(ctx, "tools.capability.resolution_min 0 0");
			SendRPCI(ctx, "tools.capability.unity 1");

			if (MessageSend(ctx, "OK ") < 0)
				return 1;
		}
		else if (MsgEqual(buf, "Resolution_Set"))
		{
			DebugLog("%s", buf);
			if (MessageSend(ctx, "OK ") < 0)
				return 1;
			MessageClose(ctx);
			return 0;
		}
		else
		{
			if (MessageSend(ctx, "ERROR Unknown command") < 0)
				return 1;
		}
	}
}

pid_t dst_id = -1;
pid_t dst_pid = -1;
ToolboxContext *tb_ctx = NULL;
void DisplayScaleThread()
{
	/* sizeof ToolboxContext */
	tb_ctx = AllocateMemory(1);
	Sleep(2000);

	while (true)
	{
		if (DisplayGetSize(tb_ctx) != 0)
			KernelLog("Failed to scale display");
		Sleep(1000);
	}
}

void CommandSend(VMwareCommand *cmd)
{
	cmd->magic = VMWARE_MAGIC;
	cmd->port = VMWARE_PORT;
	asm volatile("in %%dx, %0"
				 : "+a"(cmd->ax), "+b"(cmd->bx),
				   "+c"(cmd->cx), "+d"(cmd->dx),
				   "+S"(cmd->si), "+D"(cmd->di));
}

void Absolute()
{
	VMwareCommand cmd = {0};

	/* Enable */
	cmd.bx = ABSPOINTER_ENABLE;
	cmd.command = CMD_ABSPOINTER_COMMAND;
	CommandSend(&cmd);

	/* Status */
	cmd.bx = 0;
	cmd.command = CMD_ABSPOINTER_STATUS;
	CommandSend(&cmd);

	/* Read data (1) */
	cmd.bx = 1;
	cmd.command = CMD_ABSPOINTER_DATA;
	CommandSend(&cmd);

	/* Enable absolute */
	cmd.bx = ABSPOINTER_ABSOLUTE;
	cmd.command = CMD_ABSPOINTER_COMMAND;
	CommandSend(&cmd);
}

void Relative()
{
	VMwareCommand cmd = {0};
	cmd.bx = ABSPOINTER_RELATIVE;
	cmd.command = CMD_ABSPOINTER_COMMAND;
	CommandSend(&cmd);
}

InputReport ir = {0};
void InterruptHandler(TrapFrame *)
{
	uint8_t Data = inb(0x60);
	(void)Data;

	VMwareCommand cmd = {0};
	cmd.bx = 0;
	cmd.command = CMD_ABSPOINTER_STATUS;
	CommandSend(&cmd);

	if (cmd.ax == 0xFFFF0000)
	{
		KernelLog("VMware mouse is not connected?");
		Relative();
		Absolute();
		return;
	}

	if ((cmd.ax & 0xFFFF) < 4)
		return;

	cmd.bx = 4;
	cmd.command = CMD_ABSPOINTER_DATA;
	CommandSend(&cmd);

	int Buttons = (cmd.ax & 0xFFFF);

	/**
	 * How should I handle this?
	 * (cmd.[bx,cx] * Width) / 0xFFFF
	 * Maybe TODO: Width and Height API?
	 */
	uintptr_t AbsoluteX = cmd.bx;
	uintptr_t AbsoluteY = cmd.cx;

	ir.Type = INPUT_TYPE_MOUSE;
	ir.Device = MouseDevID;
	ir.Mouse.X = AbsoluteX;
	ir.Mouse.Y = AbsoluteY;
	ir.Mouse.Z = (int8_t)cmd.dx;
	ir.Mouse.Absolute = 1;
	ir.Mouse.LeftButton = Buttons & 0x20;
	ir.Mouse.RightButton = Buttons & 0x10;
	ir.Mouse.MiddleButton = Buttons & 0x08;
	// ir.Mouse.Button4 = 0x0;
	// ir.Mouse.Button5 = 0x0;
	// ir.Mouse.Button6 = 0x0;
	// ir.Mouse.Button7 = 0x0;
	// ir.Mouse.Button8 = 0x0;
	ReportInputEvent(&ir);
}

int __fs_Ioctl(struct Inode *, unsigned long Request, void *)
{
	switch (Request)
	{
	case 0x1:
		Relative();
		break;
	case 0x2:
		Absolute();
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

const struct InodeOperations MouseOps = {
	.Ioctl = __fs_Ioctl,
};

bool ToolboxSupported = false;
int DriverEntry()
{
	ToolboxContext tb_ctx = {0};
	/* Test if it's supported */
	int status = OpenMessageChannel(&tb_ctx, MESSAGE_TCLO);
	if (status == 0)
	{
		ToolboxSupported = true;
		MessageClose(&tb_ctx);
		dst_id = CreateKernelThread(0, "VMware Display Scale",
									(void *)DisplayScaleThread, NULL);
		dst_pid = GetCurrentProcess();
	}

	PS2WriteCommand(PS2_CMD_ENABLE_PORT_2);
	PS2WriteCommand(PS2_CMD_READ_CONFIG);
	PS2_CONFIGURATION config = {.Raw = PS2ReadData()};
	config.Port2Interrupt = 1;
	PS2WriteCommand(PS2_CMD_WRITE_CONFIG);
	PS2WriteData(config.Raw);

	PS2WriteCommand(PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
	PS2WriteData(PS2_MOUSE_CMD_SET_DEFAULTS);
	PS2ReadData();

	PS2WriteCommand(PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
	PS2WriteData(PS2_MOUSE_CMD_ENABLE_DATA_REPORTING);
	PS2ReadData();
	Absolute();

	/**
	 * If we have another driver using the PS/2 mouse, we need to
	 * override its interrupt handler.
	 */
	OverrideInterruptHandler(12, InterruptHandler);

	MouseDevID = RegisterDevice(INPUT_TYPE_MOUSE, &MouseOps);
	return 0;
}

int DriverFinal()
{
	PS2WriteCommand(PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
	PS2WriteData(PS2_MOUSE_CMD_DISABLE_DATA_REPORTING);

	Relative();

	UnregisterDevice(MouseDevID);

	if (ToolboxSupported)
	{
		KillThread(dst_id, dst_pid, 0);
		if (tb_ctx->TCLOChannel != -1)
			MessageClose(tb_ctx);
		FreeMemory(tb_ctx, 1);
	}
	return 0;
}

int DriverPanic()
{
	Relative();
	PS2WriteCommand(PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
	PS2WriteData(PS2_MOUSE_CMD_DISABLE_DATA_REPORTING);
	return 0;
}

int DriverProbe()
{
	if (!IsVMwareBackdoorAvailable())
		return -ENODEV;
	return 0;
}

DriverInfo("vmware",
		   "VMware Tools Driver",
		   "EnderIce2",
		   0, 0, 1,
		   "GPLv3");