Release 4.11 drivers/input/mouse/psmouse-base.c
/*
* PS/2 mouse driver
*
* Copyright (c) 1999-2002 Vojtech Pavlik
* Copyright (c) 2003-2004 Dmitry Torokhov
*/
/*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#define psmouse_fmt(fmt) fmt
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/init.h>
#include <linux/libps2.h>
#include <linux/mutex.h>
#include "psmouse.h"
#include "synaptics.h"
#include "logips2pp.h"
#include "alps.h"
#include "hgpk.h"
#include "lifebook.h"
#include "trackpoint.h"
#include "touchkit_ps2.h"
#include "elantech.h"
#include "sentelic.h"
#include "cypress_ps2.h"
#include "focaltech.h"
#include "vmmouse.h"
#include "byd.h"
#define DRIVER_DESC "PS/2 mouse driver"
MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
static unsigned int psmouse_max_proto = PSMOUSE_AUTO;
static int psmouse_set_maxproto(const char *val, const struct kernel_param *);
static int psmouse_get_maxproto(char *buffer, const struct kernel_param *kp);
static const struct kernel_param_ops param_ops_proto_abbrev = {
.set = psmouse_set_maxproto,
.get = psmouse_get_maxproto,
};
#define param_check_proto_abbrev(name, p) __param_check(name, p, unsigned int)
module_param_named(proto, psmouse_max_proto, proto_abbrev, 0644);
MODULE_PARM_DESC(proto, "Highest protocol extension to probe (bare, imps, exps, any). Useful for KVM switches.");
static unsigned int psmouse_resolution = 200;
module_param_named(resolution, psmouse_resolution, uint, 0644);
MODULE_PARM_DESC(resolution, "Resolution, in dpi.");
static unsigned int psmouse_rate = 100;
module_param_named(rate, psmouse_rate, uint, 0644);
MODULE_PARM_DESC(rate, "Report rate, in reports per second.");
static bool psmouse_smartscroll = true;
module_param_named(smartscroll, psmouse_smartscroll, bool, 0644);
MODULE_PARM_DESC(smartscroll, "Logitech Smartscroll autorepeat, 1 = enabled (default), 0 = disabled.");
static unsigned int psmouse_resetafter = 5;
module_param_named(resetafter, psmouse_resetafter, uint, 0644);
MODULE_PARM_DESC(resetafter, "Reset device after so many bad packets (0 = never).");
static unsigned int psmouse_resync_time;
module_param_named(resync_time, psmouse_resync_time, uint, 0644);
MODULE_PARM_DESC(resync_time, "How long can mouse stay idle before forcing resync (in seconds, 0 = never).");
PSMOUSE_DEFINE_ATTR(protocol, S_IWUSR | S_IRUGO,
NULL,
psmouse_attr_show_protocol, psmouse_attr_set_protocol);
PSMOUSE_DEFINE_ATTR(rate, S_IWUSR | S_IRUGO,
(void *) offsetof(struct psmouse, rate),
psmouse_show_int_attr, psmouse_attr_set_rate);
PSMOUSE_DEFINE_ATTR(resolution, S_IWUSR | S_IRUGO,
(void *) offsetof(struct psmouse, resolution),
psmouse_show_int_attr, psmouse_attr_set_resolution);
PSMOUSE_DEFINE_ATTR(resetafter, S_IWUSR | S_IRUGO,
(void *) offsetof(struct psmouse, resetafter),
psmouse_show_int_attr, psmouse_set_int_attr);
PSMOUSE_DEFINE_ATTR(resync_time, S_IWUSR | S_IRUGO,
(void *) offsetof(struct psmouse, resync_time),
psmouse_show_int_attr, psmouse_set_int_attr);
static struct attribute *psmouse_attributes[] = {
&psmouse_attr_protocol.dattr.attr,
&psmouse_attr_rate.dattr.attr,
&psmouse_attr_resolution.dattr.attr,
&psmouse_attr_resetafter.dattr.attr,
&psmouse_attr_resync_time.dattr.attr,
NULL
};
static struct attribute_group psmouse_attribute_group = {
.attrs = psmouse_attributes,
};
/*
* psmouse_mutex protects all operations changing state of mouse
* (connecting, disconnecting, changing rate or resolution via
* sysfs). We could use a per-device semaphore but since there
* rarely more than one PS/2 mouse connected and since semaphore
* is taken in "slow" paths it is not worth it.
*/
static DEFINE_MUTEX(psmouse_mutex);
static struct workqueue_struct *kpsmoused_wq;
struct psmouse_protocol {
enum psmouse_type type;
bool maxproto;
bool ignore_parity; /* Protocol should ignore parity errors from KBC */
bool try_passthru; /* Try protocol also on passthrough ports */
const char *name;
const char *alias;
int (*detect)(struct psmouse *, bool);
int (*init)(struct psmouse *);
};
static void psmouse_report_standard_buttons(struct input_dev *dev, u8 buttons)
{
input_report_key(dev, BTN_LEFT, buttons & BIT(0));
input_report_key(dev, BTN_MIDDLE, buttons & BIT(2));
input_report_key(dev, BTN_RIGHT, buttons & BIT(1));
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Tissoires | 56 | 100.00% | 1 | 100.00% |
Total | 56 | 100.00% | 1 | 100.00% |
/*
* psmouse_process_byte() analyzes the PS/2 data stream and reports
* relevant events to the input module once full packet has arrived.
*/
psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse)
{
struct input_dev *dev = psmouse->dev;
unsigned char *packet = psmouse->packet;
if (psmouse->pktcnt < psmouse->pktsize)
return PSMOUSE_GOOD_DATA;
/* Full packet accumulated, process it */
switch (psmouse->type) {
case PSMOUSE_IMPS:
/* IntelliMouse has scroll wheel */
input_report_rel(dev, REL_WHEEL, -(signed char) packet[3]);
break;
case PSMOUSE_IMEX:
/* Scroll wheel and buttons on IntelliMouse Explorer */
switch (packet[3] & 0xC0) {
case 0x80: /* vertical scroll on IntelliMouse Explorer 4.0 */
input_report_rel(dev, REL_WHEEL, (int) (packet[3] & 32) - (int) (packet[3] & 31));
break;
case 0x40: /* horizontal scroll on IntelliMouse Explorer 4.0 */
input_report_rel(dev, REL_HWHEEL, (int) (packet[3] & 32) - (int) (packet[3] & 31));
break;
case 0x00:
case 0xC0:
input_report_rel(dev, REL_WHEEL, (int) (packet[3] & 8) - (int) (packet[3] & 7));
input_report_key(dev, BTN_SIDE, (packet[3] >> 4) & 1);
input_report_key(dev, BTN_EXTRA, (packet[3] >> 5) & 1);
break;
}
break;
case PSMOUSE_GENPS:
/* Report scroll buttons on NetMice */
input_report_rel(dev, REL_WHEEL, -(signed char) packet[3]);
/* Extra buttons on Genius NewNet 3D */
input_report_key(dev, BTN_SIDE, (packet[0] >> 6) & 1);
input_report_key(dev, BTN_EXTRA, (packet[0] >> 7) & 1);
break;
case PSMOUSE_THINKPS:
/* Extra button on ThinkingMouse */
input_report_key(dev, BTN_EXTRA, (packet[0] >> 3) & 1);
/*
* Without this bit of weirdness moving up gives wildly
* high Y changes.
*/
packet[1] |= (packet[0] & 0x40) << 1;
break;
case PSMOUSE_CORTRON:
/*
* Cortron PS2 Trackball reports SIDE button in the
* 4th bit of the first byte.
*/
input_report_key(dev, BTN_SIDE, (packet[0] >> 3) & 1);
packet[0] |= 0x08;
break;
default:
break;
}
/* Generic PS/2 Mouse */
psmouse_report_standard_buttons(dev,
packet[0] | psmouse->extra_buttons);
input_report_rel(dev, REL_X, packet[1] ? (int) packet[1] - (int) ((packet[0] << 4) & 0x100) : 0);
input_report_rel(dev, REL_Y, packet[2] ? (int) ((packet[0] << 3) & 0x100) - (int) packet[2] : 0);
input_sync(dev);
return PSMOUSE_FULL_PACKET;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Vojtech Pavlik | 243 | 52.37% | 5 | 41.67% |
Pozsar Balazs | 90 | 19.40% | 1 | 8.33% |
Dmitry Torokhov | 64 | 13.79% | 3 | 25.00% |
Peter Maydell | 35 | 7.54% | 1 | 8.33% |
Aristeu Rozanski | 27 | 5.82% | 1 | 8.33% |
Benjamin Tissoires | 5 | 1.08% | 1 | 8.33% |
Total | 464 | 100.00% | 12 | 100.00% |
void psmouse_queue_work(struct psmouse *psmouse, struct delayed_work *work,
unsigned long delay)
{
queue_delayed_work(kpsmoused_wq, work, delay);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Andres Salomon | 28 | 100.00% | 1 | 100.00% |
Total | 28 | 100.00% | 1 | 100.00% |
/*
* __psmouse_set_state() sets new psmouse state and resets all flags.
*/
static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state)
{
psmouse->state = new_state;
psmouse->pktcnt = psmouse->out_of_sync_cnt = 0;
psmouse->ps2dev.flags = 0;
psmouse->last = jiffies;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 40 | 86.96% | 2 | 66.67% |
Vojtech Pavlik | 6 | 13.04% | 1 | 33.33% |
Total | 46 | 100.00% | 3 | 100.00% |
/*
* psmouse_set_state() sets new psmouse state and resets all flags and
* counters while holding serio lock so fighting with interrupt handler
* is not a concern.
*/
void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state)
{
serio_pause_rx(psmouse->ps2dev.serio);
__psmouse_set_state(psmouse, new_state);
serio_continue_rx(psmouse->ps2dev.serio);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 32 | 82.05% | 2 | 50.00% |
Vojtech Pavlik | 7 | 17.95% | 2 | 50.00% |
Total | 39 | 100.00% | 4 | 100.00% |
/*
* psmouse_handle_byte() processes one byte of the input data stream
* by calling corresponding protocol handler.
*/
static int psmouse_handle_byte(struct psmouse *psmouse)
{
psmouse_ret_t rc = psmouse->protocol_handler(psmouse);
switch (rc) {
case PSMOUSE_BAD_DATA:
if (psmouse->state == PSMOUSE_ACTIVATED) {
psmouse_warn(psmouse,
"%s at %s lost sync at byte %d\n",
psmouse->name, psmouse->phys,
psmouse->pktcnt);
if (++psmouse->out_of_sync_cnt == psmouse->resetafter) {
__psmouse_set_state(psmouse, PSMOUSE_IGNORE);
psmouse_notice(psmouse,
"issuing reconnect request\n");
serio_reconnect(psmouse->ps2dev.serio);
return -1;
}
}
psmouse->pktcnt = 0;
break;
case PSMOUSE_FULL_PACKET:
psmouse->pktcnt = 0;
if (psmouse->out_of_sync_cnt) {
psmouse->out_of_sync_cnt = 0;
psmouse_notice(psmouse,
"%s at %s - driver resynced.\n",
psmouse->name, psmouse->phys);
}
break;
case PSMOUSE_GOOD_DATA:
break;
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 147 | 96.71% | 6 | 75.00% |
Peter Osterlund | 3 | 1.97% | 1 | 12.50% |
Vojtech Pavlik | 2 | 1.32% | 1 | 12.50% |
Total | 152 | 100.00% | 8 | 100.00% |
static void psmouse_handle_oob_data(struct psmouse *psmouse, u8 data)
{
switch (psmouse->oob_data_type) {
case PSMOUSE_OOB_NONE:
psmouse->oob_data_type = data;
break;
case PSMOUSE_OOB_EXTRA_BTNS:
psmouse_report_standard_buttons(psmouse->dev, data);
input_sync(psmouse->dev);
psmouse->extra_buttons = data;
psmouse->oob_data_type = PSMOUSE_OOB_NONE;
break;
default:
psmouse_warn(psmouse,
"unknown OOB_DATA type: 0x%02x\n",
psmouse->oob_data_type);
psmouse->oob_data_type = PSMOUSE_OOB_NONE;
break;
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Benjamin Tissoires | 83 | 100.00% | 1 | 100.00% |
Total | 83 | 100.00% | 1 | 100.00% |
/*
* psmouse_interrupt() handles incoming characters, either passing them
* for normal processing or gathering them as command response.
*/
static irqreturn_t psmouse_interrupt(struct serio *serio,
unsigned char data, unsigned int flags)
{
struct psmouse *psmouse = serio_get_drvdata(serio);
if (psmouse->state == PSMOUSE_IGNORE)
goto out;
if (unlikely((flags & SERIO_TIMEOUT) ||
((flags & SERIO_PARITY) && !psmouse->ignore_parity))) {
if (psmouse->state == PSMOUSE_ACTIVATED)
psmouse_warn(psmouse,
"bad data from KBC -%s%s\n",
flags & SERIO_TIMEOUT ? " timeout" : "",
flags & SERIO_PARITY ? " bad parity" : "");
ps2_cmd_aborted(&psmouse->ps2dev);
goto out;
}
if (flags & SERIO_OOB_DATA) {
psmouse_handle_oob_data(psmouse, data);
goto out;
}
if (unlikely(psmouse->ps2dev.flags & PS2_FLAG_ACK))
if (ps2_handle_ack(&psmouse->ps2dev, data))
goto out;
if (unlikely(psmouse->ps2dev.flags & PS2_FLAG_CMD))
if (ps2_handle_response(&psmouse->ps2dev, data))
goto out;
if (psmouse->state <= PSMOUSE_RESYNCING)
goto out;
if (psmouse->state == PSMOUSE_ACTIVATED &&
psmouse->pktcnt && time_after(jiffies, psmouse->last + HZ/2)) {
psmouse_info(psmouse, "%s at %s lost synchronization, throwing %d bytes away.\n",
psmouse->name, psmouse->phys, psmouse->pktcnt);
psmouse->badbyte = psmouse->packet[0];
__psmouse_set_state(psmouse, PSMOUSE_RESYNCING);
psmouse_queue_work(psmouse, &psmouse->resync_work, 0);
goto out;
}
psmouse->packet[psmouse->pktcnt++] = data;
/* Check if this is a new device announcement (0xAA 0x00) */
if (unlikely(psmouse->packet[0] == PSMOUSE_RET_BAT && psmouse->pktcnt <= 2)) {
if (psmouse->pktcnt == 1) {
psmouse->last = jiffies;
goto out;
}
if (psmouse->packet[1] == PSMOUSE_RET_ID ||
(psmouse->type == PSMOUSE_HGPK &&
psmouse->packet[1] == PSMOUSE_RET_BAT)) {
__psmouse_set_state(psmouse, PSMOUSE_IGNORE);
serio_reconnect(serio);
goto out;
}
/* Not a new device, try processing first byte normally */
psmouse->pktcnt = 1;
if (psmouse_handle_byte(psmouse))
goto out;
psmouse->packet[psmouse->pktcnt++] = data;
}
/*
* See if we need to force resync because mouse was idle for
* too long.
*/
if (psmouse->state == PSMOUSE_ACTIVATED &&
psmouse->pktcnt == 1 && psmouse->resync_time &&
time_after(jiffies, psmouse->last + psmouse->resync_time * HZ)) {
psmouse->badbyte = psmouse->packet[0];
__psmouse_set_state(psmouse, PSMOUSE_RESYNCING);
psmouse_queue_work(psmouse, &psmouse->resync_work, 0);
goto out;
}
psmouse->last = jiffies;
psmouse_handle_byte(psmouse);
out:
return IRQ_HANDLED;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 300 | 61.86% | 13 | 56.52% |
Vojtech Pavlik | 94 | 19.38% | 4 | 17.39% |
Peter Osterlund | 44 | 9.07% | 1 | 4.35% |
Benjamin Tissoires | 18 | 3.71% | 1 | 4.35% |
Zephaniah E. Hull | 17 | 3.51% | 1 | 4.35% |
Andres Salomon | 8 | 1.65% | 1 | 4.35% |
Andrew Morton | 3 | 0.62% | 1 | 4.35% |
Linus Torvalds | 1 | 0.21% | 1 | 4.35% |
Total | 485 | 100.00% | 23 | 100.00% |
/*
* psmouse_sliced_command() sends an extended PS/2 command to the mouse
* using sliced syntax, understood by advanced devices, such as Logitech
* or Synaptics touchpads. The command is encoded as:
* 0xE6 0xE8 rr 0xE8 ss 0xE8 tt 0xE8 uu where (rr*64)+(ss*16)+(tt*4)+uu
* is the command.
*/
int psmouse_sliced_command(struct psmouse *psmouse, unsigned char command)
{
int i;
if (ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11))
return -1;
for (i = 6; i >= 0; i -= 2) {
unsigned char d = (command >> i) & 3;
if (ps2_command(&psmouse->ps2dev, &d, PSMOUSE_CMD_SETRES))
return -1;
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 85 | 100.00% | 2 | 100.00% |
Total | 85 | 100.00% | 2 | 100.00% |
/*
* psmouse_reset() resets the mouse into power-on state.
*/
int psmouse_reset(struct psmouse *psmouse)
{
unsigned char param[2];
if (ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_RESET_BAT))
return -1;
if (param[0] != PSMOUSE_RET_BAT && param[1] != PSMOUSE_RET_ID)
return -1;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 58 | 100.00% | 2 | 100.00% |
Total | 58 | 100.00% | 2 | 100.00% |
/*
* Here we set the mouse resolution.
*/
void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution)
{
static const unsigned char params[] = { 0, 1, 2, 2, 3 };
unsigned char p;
if (resolution == 0 || resolution > 200)
resolution = 200;
p = params[resolution / 50];
ps2_command(&psmouse->ps2dev, &p, PSMOUSE_CMD_SETRES);
psmouse->resolution = 25 << p;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 81 | 100.00% | 1 | 100.00% |
Total | 81 | 100.00% | 1 | 100.00% |
/*
* Here we set the mouse report rate.
*/
static void psmouse_set_rate(struct psmouse *psmouse, unsigned int rate)
{
static const unsigned char rates[] = { 200, 100, 80, 60, 40, 20, 10, 0 };
unsigned char r;
int i = 0;
while (rates[i] > rate) i++;
r = rates[i];
ps2_command(&psmouse->ps2dev, &r, PSMOUSE_CMD_SETRATE);
psmouse->rate = r;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 87 | 100.00% | 1 | 100.00% |
Total | 87 | 100.00% | 1 | 100.00% |
/*
* Here we set the mouse scaling.
*/
static void psmouse_set_scale(struct psmouse *psmouse, enum psmouse_scale scale)
{
ps2_command(&psmouse->ps2dev, NULL,
scale == PSMOUSE_SCALE21 ? PSMOUSE_CMD_SETSCALE21 :
PSMOUSE_CMD_SETSCALE11);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Mathias Gottschlag | 33 | 100.00% | 1 | 100.00% |
Total | 33 | 100.00% | 1 | 100.00% |
/*
* psmouse_poll() - default poll handler. Everyone except for ALPS uses it.
*/
static int psmouse_poll(struct psmouse *psmouse)
{
return ps2_command(&psmouse->ps2dev, psmouse->packet,
PSMOUSE_CMD_POLL | (psmouse->pktsize << 8));
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 34 | 100.00% | 1 | 100.00% |
Total | 34 | 100.00% | 1 | 100.00% |
static bool psmouse_check_pnp_id(const char *id, const char * const ids[])
{
int i;
for (i = 0; ids[i]; i++)
if (!strcasecmp(id, ids[i]))
return true;
return false;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hans de Goede | 32 | 59.26% | 1 | 50.00% |
Dmitry Torokhov | 22 | 40.74% | 1 | 50.00% |
Total | 54 | 100.00% | 2 | 100.00% |
/*
* psmouse_matches_pnp_id - check if psmouse matches one of the passed in ids.
*/
bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[])
{
struct serio *serio = psmouse->ps2dev.serio;
char *p, *fw_id_copy, *save_ptr;
bool found = false;
if (strncmp(serio->firmware_id, "PNP: ", 5))
return false;
fw_id_copy = kstrndup(&serio->firmware_id[5],
sizeof(serio->firmware_id) - 5,
GFP_KERNEL);
if (!fw_id_copy)
return false;
save_ptr = fw_id_copy;
while ((p = strsep(&fw_id_copy, " ")) != NULL) {
if (psmouse_check_pnp_id(p, ids)) {
found = true;
break;
}
}
kfree(save_ptr);
return found;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Dmitry Torokhov | 120 | 87.59% | 1 | 50.00% |
Hans de Goede | 17 | 12.41% | 1 | 50.00% |
Total | 137 | 100.00% | 2 | 100.00% |
/*
* Genius NetMouse magic init.
*/
static int genius_detect(struct psmouse *psmouse, bool set_properties)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
unsigned char param[4];
param[0] = 3;
ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES);
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11);
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11);
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11);
ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO);
if (param[0] != 0x00 || param[1] != 0x33 || param[2] != 0x55)
return -1;
if (set_properties) {
__set_bit(BTN_MIDDLE, psmouse->dev->keybit);
__set_bit(BTN_EXTRA, psmouse->dev->keybit)