| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Driver for Virtual PS/2 Mouse on VMware and QEMU hypervisors. | |
| * | |
| * Copyright (C) 2014, VMware, Inc. All Rights Reserved. | |
| * | |
| * Twin device code is hugely inspired by the ALPS driver. | |
| * Authors: | |
| * Dmitry Torokhov <dmitry.torokhov@gmail.com> | |
| * Thomas Hellstrom <thellstrom@vmware.com> | |
| */ | |
| #include <linux/input.h> | |
| #include <linux/serio.h> | |
| #include <linux/libps2.h> | |
| #include <linux/slab.h> | |
| #include <linux/module.h> | |
| #include <asm/hypervisor.h> | |
| #include <asm/vmware.h> | |
| #include "psmouse.h" | |
| #include "vmmouse.h" | |
| #define VMMOUSE_PROTO_MAGIC 0x564D5868U | |
| /* | |
| * Main commands supported by the vmmouse hypervisor port. | |
| */ | |
| #define VMMOUSE_PROTO_CMD_GETVERSION 10 | |
| #define VMMOUSE_PROTO_CMD_ABSPOINTER_DATA 39 | |
| #define VMMOUSE_PROTO_CMD_ABSPOINTER_STATUS 40 | |
| #define VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND 41 | |
| #define VMMOUSE_PROTO_CMD_ABSPOINTER_RESTRICT 86 | |
| /* | |
| * Subcommands for VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND | |
| */ | |
| #define VMMOUSE_CMD_ENABLE 0x45414552U | |
| #define VMMOUSE_CMD_DISABLE 0x000000f5U | |
| #define VMMOUSE_CMD_REQUEST_RELATIVE 0x4c455252U | |
| #define VMMOUSE_CMD_REQUEST_ABSOLUTE 0x53424152U | |
| #define VMMOUSE_ERROR 0xffff0000U | |
| #define VMMOUSE_VERSION_ID 0x3442554aU | |
| #define VMMOUSE_RELATIVE_PACKET 0x00010000U | |
| #define VMMOUSE_LEFT_BUTTON 0x20 | |
| #define VMMOUSE_RIGHT_BUTTON 0x10 | |
| #define VMMOUSE_MIDDLE_BUTTON 0x08 | |
| /* | |
| * VMMouse Restrict command | |
| */ | |
| #define VMMOUSE_RESTRICT_ANY 0x00 | |
| #define VMMOUSE_RESTRICT_CPL0 0x01 | |
| #define VMMOUSE_RESTRICT_IOPL 0x02 | |
| #define VMMOUSE_MAX_X 0xFFFF | |
| #define VMMOUSE_MAX_Y 0xFFFF | |
| #define VMMOUSE_VENDOR "VMware" | |
| #define VMMOUSE_NAME "VMMouse" | |
| /** | |
| * struct vmmouse_data - private data structure for the vmmouse driver | |
| * | |
| * @abs_dev: "Absolute" device used to report absolute mouse movement. | |
| * @phys: Physical path for the absolute device. | |
| * @dev_name: Name attribute name for the absolute device. | |
| */ | |
| struct vmmouse_data { | |
| struct input_dev *abs_dev; | |
| char phys[32]; | |
| char dev_name[128]; | |
| }; | |
| /* | |
| * Hypervisor-specific bi-directional communication channel | |
| * implementing the vmmouse protocol. Should never execute on | |
| * bare metal hardware. | |
| */ | |
| #define VMMOUSE_CMD(cmd, in1, out1, out2, out3, out4) \ | |
| ({ \ | |
| unsigned long __dummy1, __dummy2; \ | |
| __asm__ __volatile__ (VMWARE_HYPERCALL : \ | |
| "=a"(out1), \ | |
| "=b"(out2), \ | |
| "=c"(out3), \ | |
| "=d"(out4), \ | |
| "=S"(__dummy1), \ | |
| "=D"(__dummy2) : \ | |
| "a"(VMMOUSE_PROTO_MAGIC), \ | |
| "b"(in1), \ | |
| "c"(VMMOUSE_PROTO_CMD_##cmd), \ | |
| "d"(0) : \ | |
| "memory"); \ | |
| }) | |
| /** | |
| * vmmouse_report_button - report button state on the correct input device | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * @abs_dev: The absolute input device | |
| * @rel_dev: The relative input device | |
| * @pref_dev: The preferred device for reporting | |
| * @code: Button code | |
| * @value: Button value | |
| * | |
| * Report @value and @code on @pref_dev, unless the button is already | |
| * pressed on the other device, in which case the state is reported on that | |
| * device. | |
| */ | |
| static void vmmouse_report_button(struct psmouse *psmouse, | |
| struct input_dev *abs_dev, | |
| struct input_dev *rel_dev, | |
| struct input_dev *pref_dev, | |
| unsigned int code, int value) | |
| { | |
| if (test_bit(code, abs_dev->key)) | |
| pref_dev = abs_dev; | |
| else if (test_bit(code, rel_dev->key)) | |
| pref_dev = rel_dev; | |
| input_report_key(pref_dev, code, value); | |
| } | |
| /** | |
| * vmmouse_report_events - process events on the vmmouse communications channel | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * This function pulls events from the vmmouse communications channel and | |
| * reports them on the correct (absolute or relative) input device. When the | |
| * communications channel is drained, or if we've processed more than 255 | |
| * psmouse commands, the function returns PSMOUSE_FULL_PACKET. If there is a | |
| * host- or synchronization error, the function returns PSMOUSE_BAD_DATA in | |
| * the hope that the caller will reset the communications channel. | |
| */ | |
| static psmouse_ret_t vmmouse_report_events(struct psmouse *psmouse) | |
| { | |
| struct input_dev *rel_dev = psmouse->dev; | |
| struct vmmouse_data *priv = psmouse->private; | |
| struct input_dev *abs_dev = priv->abs_dev; | |
| struct input_dev *pref_dev; | |
| u32 status, x, y, z; | |
| u32 dummy1, dummy2, dummy3; | |
| unsigned int queue_length; | |
| unsigned int count = 255; | |
| while (count--) { | |
| /* See if we have motion data. */ | |
| VMMOUSE_CMD(ABSPOINTER_STATUS, 0, | |
| status, dummy1, dummy2, dummy3); | |
| if ((status & VMMOUSE_ERROR) == VMMOUSE_ERROR) { | |
| psmouse_err(psmouse, "failed to fetch status data\n"); | |
| /* | |
| * After a few attempts this will result in | |
| * reconnect. | |
| */ | |
| return PSMOUSE_BAD_DATA; | |
| } | |
| queue_length = status & 0xffff; | |
| if (queue_length == 0) | |
| break; | |
| if (queue_length % 4) { | |
| psmouse_err(psmouse, "invalid queue length\n"); | |
| return PSMOUSE_BAD_DATA; | |
| } | |
| /* Now get it */ | |
| VMMOUSE_CMD(ABSPOINTER_DATA, 4, status, x, y, z); | |
| /* | |
| * And report what we've got. Prefer to report button | |
| * events on the same device where we report motion events. | |
| * This doesn't work well with the mouse wheel, though. See | |
| * below. Ideally we would want to report that on the | |
| * preferred device as well. | |
| */ | |
| if (status & VMMOUSE_RELATIVE_PACKET) { | |
| pref_dev = rel_dev; | |
| input_report_rel(rel_dev, REL_X, (s32)x); | |
| input_report_rel(rel_dev, REL_Y, -(s32)y); | |
| } else { | |
| pref_dev = abs_dev; | |
| input_report_abs(abs_dev, ABS_X, x); | |
| input_report_abs(abs_dev, ABS_Y, y); | |
| } | |
| /* Xorg seems to ignore wheel events on absolute devices */ | |
| input_report_rel(rel_dev, REL_WHEEL, -(s8)((u8) z)); | |
| vmmouse_report_button(psmouse, abs_dev, rel_dev, | |
| pref_dev, BTN_LEFT, | |
| status & VMMOUSE_LEFT_BUTTON); | |
| vmmouse_report_button(psmouse, abs_dev, rel_dev, | |
| pref_dev, BTN_RIGHT, | |
| status & VMMOUSE_RIGHT_BUTTON); | |
| vmmouse_report_button(psmouse, abs_dev, rel_dev, | |
| pref_dev, BTN_MIDDLE, | |
| status & VMMOUSE_MIDDLE_BUTTON); | |
| input_sync(abs_dev); | |
| input_sync(rel_dev); | |
| } | |
| return PSMOUSE_FULL_PACKET; | |
| } | |
| /** | |
| * vmmouse_process_byte - process data on the ps/2 channel | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * When the ps/2 channel indicates that there is vmmouse data available, | |
| * call vmmouse channel processing. Otherwise, continue to accept bytes. If | |
| * there is a synchronization or communication data error, return | |
| * PSMOUSE_BAD_DATA in the hope that the caller will reset the mouse. | |
| */ | |
| static psmouse_ret_t vmmouse_process_byte(struct psmouse *psmouse) | |
| { | |
| unsigned char *packet = psmouse->packet; | |
| switch (psmouse->pktcnt) { | |
| case 1: | |
| return (packet[0] & 0x8) == 0x8 ? | |
| PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; | |
| case 2: | |
| return PSMOUSE_GOOD_DATA; | |
| default: | |
| return vmmouse_report_events(psmouse); | |
| } | |
| } | |
| /** | |
| * vmmouse_disable - Disable vmmouse | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * Tries to disable vmmouse mode. | |
| */ | |
| static void vmmouse_disable(struct psmouse *psmouse) | |
| { | |
| u32 status; | |
| u32 dummy1, dummy2, dummy3, dummy4; | |
| VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_DISABLE, | |
| dummy1, dummy2, dummy3, dummy4); | |
| VMMOUSE_CMD(ABSPOINTER_STATUS, 0, | |
| status, dummy1, dummy2, dummy3); | |
| if ((status & VMMOUSE_ERROR) != VMMOUSE_ERROR) | |
| psmouse_warn(psmouse, "failed to disable vmmouse device\n"); | |
| } | |
| /** | |
| * vmmouse_enable - Enable vmmouse and request absolute mode. | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * Tries to enable vmmouse mode. Performs basic checks and requests | |
| * absolute vmmouse mode. | |
| * Returns 0 on success, -ENODEV on failure. | |
| */ | |
| static int vmmouse_enable(struct psmouse *psmouse) | |
| { | |
| u32 status, version; | |
| u32 dummy1, dummy2, dummy3, dummy4; | |
| /* | |
| * Try enabling the device. If successful, we should be able to | |
| * read valid version ID back from it. | |
| */ | |
| VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_ENABLE, | |
| dummy1, dummy2, dummy3, dummy4); | |
| /* | |
| * See if version ID can be retrieved. | |
| */ | |
| VMMOUSE_CMD(ABSPOINTER_STATUS, 0, status, dummy1, dummy2, dummy3); | |
| if ((status & 0x0000ffff) == 0) { | |
| psmouse_dbg(psmouse, "empty flags - assuming no device\n"); | |
| return -ENXIO; | |
| } | |
| VMMOUSE_CMD(ABSPOINTER_DATA, 1 /* single item */, | |
| version, dummy1, dummy2, dummy3); | |
| if (version != VMMOUSE_VERSION_ID) { | |
| psmouse_dbg(psmouse, "Unexpected version value: %u vs %u\n", | |
| (unsigned) version, VMMOUSE_VERSION_ID); | |
| vmmouse_disable(psmouse); | |
| return -ENXIO; | |
| } | |
| /* | |
| * Restrict ioport access, if possible. | |
| */ | |
| VMMOUSE_CMD(ABSPOINTER_RESTRICT, VMMOUSE_RESTRICT_CPL0, | |
| dummy1, dummy2, dummy3, dummy4); | |
| VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_REQUEST_ABSOLUTE, | |
| dummy1, dummy2, dummy3, dummy4); | |
| return 0; | |
| } | |
| /* | |
| * Array of supported hypervisors. | |
| */ | |
| static enum x86_hypervisor_type vmmouse_supported_hypervisors[] = { | |
| X86_HYPER_VMWARE, | |
| X86_HYPER_KVM, | |
| }; | |
| /** | |
| * vmmouse_check_hypervisor - Check if we're running on a supported hypervisor | |
| */ | |
| static bool vmmouse_check_hypervisor(void) | |
| { | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(vmmouse_supported_hypervisors); i++) | |
| if (vmmouse_supported_hypervisors[i] == x86_hyper_type) | |
| return true; | |
| return false; | |
| } | |
| /** | |
| * vmmouse_detect - Probe whether vmmouse is available | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * @set_properties: Whether to set psmouse name and vendor | |
| * | |
| * Returns 0 if vmmouse channel is available. Negative error code if not. | |
| */ | |
| int vmmouse_detect(struct psmouse *psmouse, bool set_properties) | |
| { | |
| u32 response, version, dummy1, dummy2; | |
| if (!vmmouse_check_hypervisor()) { | |
| psmouse_dbg(psmouse, | |
| "VMMouse not running on supported hypervisor.\n"); | |
| return -ENXIO; | |
| } | |
| /* Check if the device is present */ | |
| response = ~VMMOUSE_PROTO_MAGIC; | |
| VMMOUSE_CMD(GETVERSION, 0, version, response, dummy1, dummy2); | |
| if (response != VMMOUSE_PROTO_MAGIC || version == 0xffffffffU) | |
| return -ENXIO; | |
| if (set_properties) { | |
| psmouse->vendor = VMMOUSE_VENDOR; | |
| psmouse->name = VMMOUSE_NAME; | |
| psmouse->model = version; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * vmmouse_disconnect - Take down vmmouse driver | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * Takes down vmmouse driver and frees resources set up in vmmouse_init(). | |
| */ | |
| static void vmmouse_disconnect(struct psmouse *psmouse) | |
| { | |
| struct vmmouse_data *priv = psmouse->private; | |
| vmmouse_disable(psmouse); | |
| psmouse_reset(psmouse); | |
| input_unregister_device(priv->abs_dev); | |
| kfree(priv); | |
| } | |
| /** | |
| * vmmouse_reconnect - Reset the ps/2 - and vmmouse connections | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * Attempts to reset the mouse connections. Returns 0 on success and | |
| * -1 on failure. | |
| */ | |
| static int vmmouse_reconnect(struct psmouse *psmouse) | |
| { | |
| int error; | |
| psmouse_reset(psmouse); | |
| vmmouse_disable(psmouse); | |
| error = vmmouse_enable(psmouse); | |
| if (error) { | |
| psmouse_err(psmouse, | |
| "Unable to re-enable mouse when reconnecting, err: %d\n", | |
| error); | |
| return error; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * vmmouse_init - Initialize the vmmouse driver | |
| * | |
| * @psmouse: Pointer to the psmouse struct | |
| * | |
| * Requests the device and tries to enable vmmouse mode. | |
| * If successful, sets up the input device for relative movement events. | |
| * It also allocates another input device and sets it up for absolute motion | |
| * events. Returns 0 on success and -1 on failure. | |
| */ | |
| int vmmouse_init(struct psmouse *psmouse) | |
| { | |
| struct vmmouse_data *priv; | |
| struct input_dev *rel_dev = psmouse->dev, *abs_dev; | |
| int error; | |
| psmouse_reset(psmouse); | |
| error = vmmouse_enable(psmouse); | |
| if (error) | |
| return error; | |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
| abs_dev = input_allocate_device(); | |
| if (!priv || !abs_dev) { | |
| error = -ENOMEM; | |
| goto init_fail; | |
| } | |
| priv->abs_dev = abs_dev; | |
| psmouse->private = priv; | |
| /* Set up and register absolute device */ | |
| snprintf(priv->phys, sizeof(priv->phys), "%s/input1", | |
| psmouse->ps2dev.serio->phys); | |
| /* Mimic name setup for relative device in psmouse-base.c */ | |
| snprintf(priv->dev_name, sizeof(priv->dev_name), "%s %s %s", | |
| VMMOUSE_PSNAME, VMMOUSE_VENDOR, VMMOUSE_NAME); | |
| abs_dev->phys = priv->phys; | |
| abs_dev->name = priv->dev_name; | |
| abs_dev->id.bustype = BUS_I8042; | |
| abs_dev->id.vendor = 0x0002; | |
| abs_dev->id.product = PSMOUSE_VMMOUSE; | |
| abs_dev->id.version = psmouse->model; | |
| abs_dev->dev.parent = &psmouse->ps2dev.serio->dev; | |
| /* Set absolute device capabilities */ | |
| input_set_capability(abs_dev, EV_KEY, BTN_LEFT); | |
| input_set_capability(abs_dev, EV_KEY, BTN_RIGHT); | |
| input_set_capability(abs_dev, EV_KEY, BTN_MIDDLE); | |
| input_set_capability(abs_dev, EV_ABS, ABS_X); | |
| input_set_capability(abs_dev, EV_ABS, ABS_Y); | |
| input_set_abs_params(abs_dev, ABS_X, 0, VMMOUSE_MAX_X, 0, 0); | |
| input_set_abs_params(abs_dev, ABS_Y, 0, VMMOUSE_MAX_Y, 0, 0); | |
| error = input_register_device(priv->abs_dev); | |
| if (error) | |
| goto init_fail; | |
| /* Add wheel capability to the relative device */ | |
| input_set_capability(rel_dev, EV_REL, REL_WHEEL); | |
| psmouse->protocol_handler = vmmouse_process_byte; | |
| psmouse->disconnect = vmmouse_disconnect; | |
| psmouse->reconnect = vmmouse_reconnect; | |
| return 0; | |
| init_fail: | |
| vmmouse_disable(psmouse); | |
| psmouse_reset(psmouse); | |
| input_free_device(abs_dev); | |
| kfree(priv); | |
| psmouse->private = NULL; | |
| return error; | |
| } |