Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Driver for Virtual PS/2 Mouse on VMware and QEMU hypervisors. |
| 3 | * |
| 4 | * Copyright (C) 2014, VMware, Inc. All Rights Reserved. |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License version 2 as published by |
| 8 | * the Free Software Foundation. |
| 9 | * |
| 10 | * Twin device code is hugely inspired by the ALPS driver. |
| 11 | * Authors: |
| 12 | * Dmitry Torokhov <dmitry.torokhov@gmail.com> |
| 13 | * Thomas Hellstrom <thellstrom@vmware.com> |
| 14 | */ |
| 15 | |
| 16 | #include <linux/input.h> |
| 17 | #include <linux/serio.h> |
| 18 | #include <linux/libps2.h> |
| 19 | #include <linux/slab.h> |
| 20 | #include <linux/module.h> |
| 21 | #include <asm/hypervisor.h> |
| 22 | |
| 23 | #include "psmouse.h" |
| 24 | #include "vmmouse.h" |
| 25 | |
| 26 | #define VMMOUSE_PROTO_MAGIC 0x564D5868U |
| 27 | #define VMMOUSE_PROTO_PORT 0x5658 |
| 28 | |
| 29 | /* |
| 30 | * Main commands supported by the vmmouse hypervisor port. |
| 31 | */ |
| 32 | #define VMMOUSE_PROTO_CMD_GETVERSION 10 |
| 33 | #define VMMOUSE_PROTO_CMD_ABSPOINTER_DATA 39 |
| 34 | #define VMMOUSE_PROTO_CMD_ABSPOINTER_STATUS 40 |
| 35 | #define VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND 41 |
| 36 | #define VMMOUSE_PROTO_CMD_ABSPOINTER_RESTRICT 86 |
| 37 | |
| 38 | /* |
| 39 | * Subcommands for VMMOUSE_PROTO_CMD_ABSPOINTER_COMMAND |
| 40 | */ |
| 41 | #define VMMOUSE_CMD_ENABLE 0x45414552U |
| 42 | #define VMMOUSE_CMD_DISABLE 0x000000f5U |
| 43 | #define VMMOUSE_CMD_REQUEST_RELATIVE 0x4c455252U |
| 44 | #define VMMOUSE_CMD_REQUEST_ABSOLUTE 0x53424152U |
| 45 | |
| 46 | #define VMMOUSE_ERROR 0xffff0000U |
| 47 | |
| 48 | #define VMMOUSE_VERSION_ID 0x3442554aU |
| 49 | |
| 50 | #define VMMOUSE_RELATIVE_PACKET 0x00010000U |
| 51 | |
| 52 | #define VMMOUSE_LEFT_BUTTON 0x20 |
| 53 | #define VMMOUSE_RIGHT_BUTTON 0x10 |
| 54 | #define VMMOUSE_MIDDLE_BUTTON 0x08 |
| 55 | |
| 56 | /* |
| 57 | * VMMouse Restrict command |
| 58 | */ |
| 59 | #define VMMOUSE_RESTRICT_ANY 0x00 |
| 60 | #define VMMOUSE_RESTRICT_CPL0 0x01 |
| 61 | #define VMMOUSE_RESTRICT_IOPL 0x02 |
| 62 | |
| 63 | #define VMMOUSE_MAX_X 0xFFFF |
| 64 | #define VMMOUSE_MAX_Y 0xFFFF |
| 65 | |
| 66 | #define VMMOUSE_VENDOR "VMware" |
| 67 | #define VMMOUSE_NAME "VMMouse" |
| 68 | |
| 69 | /** |
| 70 | * struct vmmouse_data - private data structure for the vmmouse driver |
| 71 | * |
| 72 | * @abs_dev: "Absolute" device used to report absolute mouse movement. |
| 73 | * @phys: Physical path for the absolute device. |
| 74 | * @dev_name: Name attribute name for the absolute device. |
| 75 | */ |
| 76 | struct vmmouse_data { |
| 77 | struct input_dev *abs_dev; |
| 78 | char phys[32]; |
| 79 | char dev_name[128]; |
| 80 | }; |
| 81 | |
| 82 | /** |
| 83 | * Hypervisor-specific bi-directional communication channel |
| 84 | * implementing the vmmouse protocol. Should never execute on |
| 85 | * bare metal hardware. |
| 86 | */ |
| 87 | #define VMMOUSE_CMD(cmd, in1, out1, out2, out3, out4) \ |
| 88 | ({ \ |
| 89 | unsigned long __dummy1, __dummy2; \ |
| 90 | __asm__ __volatile__ ("inl %%dx" : \ |
| 91 | "=a"(out1), \ |
| 92 | "=b"(out2), \ |
| 93 | "=c"(out3), \ |
| 94 | "=d"(out4), \ |
| 95 | "=S"(__dummy1), \ |
| 96 | "=D"(__dummy2) : \ |
| 97 | "a"(VMMOUSE_PROTO_MAGIC), \ |
| 98 | "b"(in1), \ |
| 99 | "c"(VMMOUSE_PROTO_CMD_##cmd), \ |
| 100 | "d"(VMMOUSE_PROTO_PORT) : \ |
| 101 | "memory"); \ |
| 102 | }) |
| 103 | |
| 104 | /** |
| 105 | * vmmouse_report_button - report button state on the correct input device |
| 106 | * |
| 107 | * @psmouse: Pointer to the psmouse struct |
| 108 | * @abs_dev: The absolute input device |
| 109 | * @rel_dev: The relative input device |
| 110 | * @pref_dev: The preferred device for reporting |
| 111 | * @code: Button code |
| 112 | * @value: Button value |
| 113 | * |
| 114 | * Report @value and @code on @pref_dev, unless the button is already |
| 115 | * pressed on the other device, in which case the state is reported on that |
| 116 | * device. |
| 117 | */ |
| 118 | static void vmmouse_report_button(struct psmouse *psmouse, |
| 119 | struct input_dev *abs_dev, |
| 120 | struct input_dev *rel_dev, |
| 121 | struct input_dev *pref_dev, |
| 122 | unsigned int code, int value) |
| 123 | { |
| 124 | if (test_bit(code, abs_dev->key)) |
| 125 | pref_dev = abs_dev; |
| 126 | else if (test_bit(code, rel_dev->key)) |
| 127 | pref_dev = rel_dev; |
| 128 | |
| 129 | input_report_key(pref_dev, code, value); |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * vmmouse_report_events - process events on the vmmouse communications channel |
| 134 | * |
| 135 | * @psmouse: Pointer to the psmouse struct |
| 136 | * |
| 137 | * This function pulls events from the vmmouse communications channel and |
| 138 | * reports them on the correct (absolute or relative) input device. When the |
| 139 | * communications channel is drained, or if we've processed more than 255 |
| 140 | * psmouse commands, the function returns PSMOUSE_FULL_PACKET. If there is a |
| 141 | * host- or synchronization error, the function returns PSMOUSE_BAD_DATA in |
| 142 | * the hope that the caller will reset the communications channel. |
| 143 | */ |
| 144 | static psmouse_ret_t vmmouse_report_events(struct psmouse *psmouse) |
| 145 | { |
| 146 | struct input_dev *rel_dev = psmouse->dev; |
| 147 | struct vmmouse_data *priv = psmouse->private; |
| 148 | struct input_dev *abs_dev = priv->abs_dev; |
| 149 | struct input_dev *pref_dev; |
| 150 | u32 status, x, y, z; |
| 151 | u32 dummy1, dummy2, dummy3; |
| 152 | unsigned int queue_length; |
| 153 | unsigned int count = 255; |
| 154 | |
| 155 | while (count--) { |
| 156 | /* See if we have motion data. */ |
| 157 | VMMOUSE_CMD(ABSPOINTER_STATUS, 0, |
| 158 | status, dummy1, dummy2, dummy3); |
| 159 | if ((status & VMMOUSE_ERROR) == VMMOUSE_ERROR) { |
| 160 | psmouse_err(psmouse, "failed to fetch status data\n"); |
| 161 | /* |
| 162 | * After a few attempts this will result in |
| 163 | * reconnect. |
| 164 | */ |
| 165 | return PSMOUSE_BAD_DATA; |
| 166 | } |
| 167 | |
| 168 | queue_length = status & 0xffff; |
| 169 | if (queue_length == 0) |
| 170 | break; |
| 171 | |
| 172 | if (queue_length % 4) { |
| 173 | psmouse_err(psmouse, "invalid queue length\n"); |
| 174 | return PSMOUSE_BAD_DATA; |
| 175 | } |
| 176 | |
| 177 | /* Now get it */ |
| 178 | VMMOUSE_CMD(ABSPOINTER_DATA, 4, status, x, y, z); |
| 179 | |
| 180 | /* |
| 181 | * And report what we've got. Prefer to report button |
| 182 | * events on the same device where we report motion events. |
| 183 | * This doesn't work well with the mouse wheel, though. See |
| 184 | * below. Ideally we would want to report that on the |
| 185 | * preferred device as well. |
| 186 | */ |
| 187 | if (status & VMMOUSE_RELATIVE_PACKET) { |
| 188 | pref_dev = rel_dev; |
| 189 | input_report_rel(rel_dev, REL_X, (s32)x); |
| 190 | input_report_rel(rel_dev, REL_Y, -(s32)y); |
| 191 | } else { |
| 192 | pref_dev = abs_dev; |
| 193 | input_report_abs(abs_dev, ABS_X, x); |
| 194 | input_report_abs(abs_dev, ABS_Y, y); |
| 195 | } |
| 196 | |
| 197 | /* Xorg seems to ignore wheel events on absolute devices */ |
| 198 | input_report_rel(rel_dev, REL_WHEEL, -(s8)((u8) z)); |
| 199 | |
| 200 | vmmouse_report_button(psmouse, abs_dev, rel_dev, |
| 201 | pref_dev, BTN_LEFT, |
| 202 | status & VMMOUSE_LEFT_BUTTON); |
| 203 | vmmouse_report_button(psmouse, abs_dev, rel_dev, |
| 204 | pref_dev, BTN_RIGHT, |
| 205 | status & VMMOUSE_RIGHT_BUTTON); |
| 206 | vmmouse_report_button(psmouse, abs_dev, rel_dev, |
| 207 | pref_dev, BTN_MIDDLE, |
| 208 | status & VMMOUSE_MIDDLE_BUTTON); |
| 209 | input_sync(abs_dev); |
| 210 | input_sync(rel_dev); |
| 211 | } |
| 212 | |
| 213 | return PSMOUSE_FULL_PACKET; |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * vmmouse_process_byte - process data on the ps/2 channel |
| 218 | * |
| 219 | * @psmouse: Pointer to the psmouse struct |
| 220 | * |
| 221 | * When the ps/2 channel indicates that there is vmmouse data available, |
| 222 | * call vmmouse channel processing. Otherwise, continue to accept bytes. If |
| 223 | * there is a synchronization or communication data error, return |
| 224 | * PSMOUSE_BAD_DATA in the hope that the caller will reset the mouse. |
| 225 | */ |
| 226 | static psmouse_ret_t vmmouse_process_byte(struct psmouse *psmouse) |
| 227 | { |
| 228 | unsigned char *packet = psmouse->packet; |
| 229 | |
| 230 | switch (psmouse->pktcnt) { |
| 231 | case 1: |
| 232 | return (packet[0] & 0x8) == 0x8 ? |
| 233 | PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; |
| 234 | |
| 235 | case 2: |
| 236 | return PSMOUSE_GOOD_DATA; |
| 237 | |
| 238 | default: |
| 239 | return vmmouse_report_events(psmouse); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * vmmouse_disable - Disable vmmouse |
| 245 | * |
| 246 | * @psmouse: Pointer to the psmouse struct |
| 247 | * |
| 248 | * Tries to disable vmmouse mode. |
| 249 | */ |
| 250 | static void vmmouse_disable(struct psmouse *psmouse) |
| 251 | { |
| 252 | u32 status; |
| 253 | u32 dummy1, dummy2, dummy3, dummy4; |
| 254 | |
| 255 | VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_DISABLE, |
| 256 | dummy1, dummy2, dummy3, dummy4); |
| 257 | |
| 258 | VMMOUSE_CMD(ABSPOINTER_STATUS, 0, |
| 259 | status, dummy1, dummy2, dummy3); |
| 260 | |
| 261 | if ((status & VMMOUSE_ERROR) != VMMOUSE_ERROR) |
| 262 | psmouse_warn(psmouse, "failed to disable vmmouse device\n"); |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * vmmouse_enable - Enable vmmouse and request absolute mode. |
| 267 | * |
| 268 | * @psmouse: Pointer to the psmouse struct |
| 269 | * |
| 270 | * Tries to enable vmmouse mode. Performs basic checks and requests |
| 271 | * absolute vmmouse mode. |
| 272 | * Returns 0 on success, -ENODEV on failure. |
| 273 | */ |
| 274 | static int vmmouse_enable(struct psmouse *psmouse) |
| 275 | { |
| 276 | u32 status, version; |
| 277 | u32 dummy1, dummy2, dummy3, dummy4; |
| 278 | |
| 279 | /* |
| 280 | * Try enabling the device. If successful, we should be able to |
| 281 | * read valid version ID back from it. |
| 282 | */ |
| 283 | VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_ENABLE, |
| 284 | dummy1, dummy2, dummy3, dummy4); |
| 285 | |
| 286 | /* |
| 287 | * See if version ID can be retrieved. |
| 288 | */ |
| 289 | VMMOUSE_CMD(ABSPOINTER_STATUS, 0, status, dummy1, dummy2, dummy3); |
| 290 | if ((status & 0x0000ffff) == 0) { |
| 291 | psmouse_dbg(psmouse, "empty flags - assuming no device\n"); |
| 292 | return -ENXIO; |
| 293 | } |
| 294 | |
| 295 | VMMOUSE_CMD(ABSPOINTER_DATA, 1 /* single item */, |
| 296 | version, dummy1, dummy2, dummy3); |
| 297 | if (version != VMMOUSE_VERSION_ID) { |
| 298 | psmouse_dbg(psmouse, "Unexpected version value: %u vs %u\n", |
| 299 | (unsigned) version, VMMOUSE_VERSION_ID); |
| 300 | vmmouse_disable(psmouse); |
| 301 | return -ENXIO; |
| 302 | } |
| 303 | |
| 304 | /* |
| 305 | * Restrict ioport access, if possible. |
| 306 | */ |
| 307 | VMMOUSE_CMD(ABSPOINTER_RESTRICT, VMMOUSE_RESTRICT_CPL0, |
| 308 | dummy1, dummy2, dummy3, dummy4); |
| 309 | |
| 310 | VMMOUSE_CMD(ABSPOINTER_COMMAND, VMMOUSE_CMD_REQUEST_ABSOLUTE, |
| 311 | dummy1, dummy2, dummy3, dummy4); |
| 312 | |
| 313 | return 0; |
| 314 | } |
| 315 | |
| 316 | /* |
| 317 | * Array of supported hypervisors. |
| 318 | */ |
| 319 | static const struct hypervisor_x86 *vmmouse_supported_hypervisors[] = { |
| 320 | &x86_hyper_vmware, |
| 321 | #ifdef CONFIG_KVM_GUEST |
| 322 | &x86_hyper_kvm, |
| 323 | #endif |
| 324 | }; |
| 325 | |
| 326 | /** |
| 327 | * vmmouse_check_hypervisor - Check if we're running on a supported hypervisor |
| 328 | */ |
| 329 | static bool vmmouse_check_hypervisor(void) |
| 330 | { |
| 331 | int i; |
| 332 | |
| 333 | for (i = 0; i < ARRAY_SIZE(vmmouse_supported_hypervisors); i++) |
| 334 | if (vmmouse_supported_hypervisors[i] == x86_hyper) |
| 335 | return true; |
| 336 | |
| 337 | return false; |
| 338 | } |
| 339 | |
| 340 | /** |
| 341 | * vmmouse_detect - Probe whether vmmouse is available |
| 342 | * |
| 343 | * @psmouse: Pointer to the psmouse struct |
| 344 | * @set_properties: Whether to set psmouse name and vendor |
| 345 | * |
| 346 | * Returns 0 if vmmouse channel is available. Negative error code if not. |
| 347 | */ |
| 348 | int vmmouse_detect(struct psmouse *psmouse, bool set_properties) |
| 349 | { |
| 350 | u32 response, version, dummy1, dummy2; |
| 351 | |
| 352 | if (!vmmouse_check_hypervisor()) { |
| 353 | psmouse_dbg(psmouse, |
| 354 | "VMMouse not running on supported hypervisor.\n"); |
| 355 | return -ENXIO; |
| 356 | } |
| 357 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 358 | /* Check if the device is present */ |
| 359 | response = ~VMMOUSE_PROTO_MAGIC; |
| 360 | VMMOUSE_CMD(GETVERSION, 0, version, response, dummy1, dummy2); |
Sinclair Yeh | 60842ef | 2016-06-23 17:37:34 -0700 | [diff] [blame] | 361 | if (response != VMMOUSE_PROTO_MAGIC || version == 0xffffffffU) |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 362 | return -ENXIO; |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 363 | |
| 364 | if (set_properties) { |
| 365 | psmouse->vendor = VMMOUSE_VENDOR; |
| 366 | psmouse->name = VMMOUSE_NAME; |
| 367 | psmouse->model = version; |
| 368 | } |
| 369 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 370 | return 0; |
| 371 | } |
| 372 | |
| 373 | /** |
| 374 | * vmmouse_disconnect - Take down vmmouse driver |
| 375 | * |
| 376 | * @psmouse: Pointer to the psmouse struct |
| 377 | * |
| 378 | * Takes down vmmouse driver and frees resources set up in vmmouse_init(). |
| 379 | */ |
| 380 | static void vmmouse_disconnect(struct psmouse *psmouse) |
| 381 | { |
| 382 | struct vmmouse_data *priv = psmouse->private; |
| 383 | |
| 384 | vmmouse_disable(psmouse); |
| 385 | psmouse_reset(psmouse); |
| 386 | input_unregister_device(priv->abs_dev); |
| 387 | kfree(priv); |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 388 | } |
| 389 | |
| 390 | /** |
| 391 | * vmmouse_reconnect - Reset the ps/2 - and vmmouse connections |
| 392 | * |
| 393 | * @psmouse: Pointer to the psmouse struct |
| 394 | * |
| 395 | * Attempts to reset the mouse connections. Returns 0 on success and |
| 396 | * -1 on failure. |
| 397 | */ |
| 398 | static int vmmouse_reconnect(struct psmouse *psmouse) |
| 399 | { |
| 400 | int error; |
| 401 | |
| 402 | psmouse_reset(psmouse); |
| 403 | vmmouse_disable(psmouse); |
| 404 | error = vmmouse_enable(psmouse); |
| 405 | if (error) { |
| 406 | psmouse_err(psmouse, |
| 407 | "Unable to re-enable mouse when reconnecting, err: %d\n", |
| 408 | error); |
| 409 | return error; |
| 410 | } |
| 411 | |
| 412 | return 0; |
| 413 | } |
| 414 | |
| 415 | /** |
| 416 | * vmmouse_init - Initialize the vmmouse driver |
| 417 | * |
| 418 | * @psmouse: Pointer to the psmouse struct |
| 419 | * |
| 420 | * Requests the device and tries to enable vmmouse mode. |
| 421 | * If successful, sets up the input device for relative movement events. |
| 422 | * It also allocates another input device and sets it up for absolute motion |
| 423 | * events. Returns 0 on success and -1 on failure. |
| 424 | */ |
| 425 | int vmmouse_init(struct psmouse *psmouse) |
| 426 | { |
| 427 | struct vmmouse_data *priv; |
| 428 | struct input_dev *rel_dev = psmouse->dev, *abs_dev; |
| 429 | int error; |
| 430 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 431 | psmouse_reset(psmouse); |
| 432 | error = vmmouse_enable(psmouse); |
| 433 | if (error) |
Sinclair Yeh | 60842ef | 2016-06-23 17:37:34 -0700 | [diff] [blame] | 434 | return error; |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 435 | |
| 436 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
| 437 | abs_dev = input_allocate_device(); |
| 438 | if (!priv || !abs_dev) { |
| 439 | error = -ENOMEM; |
| 440 | goto init_fail; |
| 441 | } |
| 442 | |
| 443 | priv->abs_dev = abs_dev; |
| 444 | psmouse->private = priv; |
| 445 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 446 | /* Set up and register absolute device */ |
| 447 | snprintf(priv->phys, sizeof(priv->phys), "%s/input1", |
| 448 | psmouse->ps2dev.serio->phys); |
| 449 | |
| 450 | /* Mimic name setup for relative device in psmouse-base.c */ |
| 451 | snprintf(priv->dev_name, sizeof(priv->dev_name), "%s %s %s", |
| 452 | VMMOUSE_PSNAME, VMMOUSE_VENDOR, VMMOUSE_NAME); |
| 453 | abs_dev->phys = priv->phys; |
| 454 | abs_dev->name = priv->dev_name; |
| 455 | abs_dev->id.bustype = BUS_I8042; |
| 456 | abs_dev->id.vendor = 0x0002; |
| 457 | abs_dev->id.product = PSMOUSE_VMMOUSE; |
| 458 | abs_dev->id.version = psmouse->model; |
| 459 | abs_dev->dev.parent = &psmouse->ps2dev.serio->dev; |
| 460 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 461 | /* Set absolute device capabilities */ |
| 462 | input_set_capability(abs_dev, EV_KEY, BTN_LEFT); |
| 463 | input_set_capability(abs_dev, EV_KEY, BTN_RIGHT); |
| 464 | input_set_capability(abs_dev, EV_KEY, BTN_MIDDLE); |
| 465 | input_set_capability(abs_dev, EV_ABS, ABS_X); |
| 466 | input_set_capability(abs_dev, EV_ABS, ABS_Y); |
| 467 | input_set_abs_params(abs_dev, ABS_X, 0, VMMOUSE_MAX_X, 0, 0); |
| 468 | input_set_abs_params(abs_dev, ABS_Y, 0, VMMOUSE_MAX_Y, 0, 0); |
| 469 | |
Dmitry Torokhov | d4f1b06 | 2016-01-16 10:04:49 -0800 | [diff] [blame] | 470 | error = input_register_device(priv->abs_dev); |
| 471 | if (error) |
| 472 | goto init_fail; |
| 473 | |
| 474 | /* Add wheel capability to the relative device */ |
| 475 | input_set_capability(rel_dev, EV_REL, REL_WHEEL); |
| 476 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 477 | psmouse->protocol_handler = vmmouse_process_byte; |
| 478 | psmouse->disconnect = vmmouse_disconnect; |
| 479 | psmouse->reconnect = vmmouse_reconnect; |
| 480 | |
| 481 | return 0; |
| 482 | |
| 483 | init_fail: |
| 484 | vmmouse_disable(psmouse); |
| 485 | psmouse_reset(psmouse); |
| 486 | input_free_device(abs_dev); |
| 487 | kfree(priv); |
| 488 | psmouse->private = NULL; |
| 489 | |
Thomas Hellstrom | 8b8be51 | 2015-04-14 10:06:38 -0700 | [diff] [blame] | 490 | return error; |
| 491 | } |