2004-06-30 12:37:51

by Vojtech Pavlik

[permalink] [raw]
Subject: Experimental PS/2 driver with heuristic synchronization

Hi!

I wanted to try if my thoughts about how to reliably synchronize the
PS/2 mouse stream would work, and as a test I created this driver.

It supports only bare PS/2 protocol, because it's just an experiment.

The driver drops every 63rd byte from the input stream to simulate
missing bytes, yet it never loses sync, and the mouse is perfectly
usable.

Try it, take a look. It should be possible to extend this to all the
other protocols the current psmouse Linux driver supports.

[Nevertheless, we still should try to find the cause of the missing
bytes, as those don't happen in 2.4 on many of the affected machines.]

[email protected], 2004-06-30 14:32:06+02:00, [email protected]
input: PS/2 mouse stream autosynchronization experiment.

Signed-off-by: Vojtech Pavlik <[email protected]>


Kconfig | 11 +
Makefile | 1
ps2sync.c | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 368 insertions(+)


diff -Nru a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
--- a/drivers/input/mouse/Kconfig Wed Jun 30 14:36:18 2004
+++ b/drivers/input/mouse/Kconfig Wed Jun 30 14:36:18 2004
@@ -36,6 +36,17 @@
To compile this driver as a module, choose M here: the
module will be called psmouse.

+config MOUSE_PS2SYNC
+ tristate "Experimental PS/2 autosync mouse driver"
+ default y
+ depends on INPUT && INPUT_MOUSE
+ select SERIO
+ select SERIO_I8042 if PC
+ select SERIO_GSCPS2 if GSC
+ ---help---
+ This is an Experiment on autosyncing the PS/2 mouse data stream.
+ It may or may not work.
+
config MOUSE_SERIAL
tristate "Serial mouse"
depends on INPUT && INPUT_MOUSE
diff -Nru a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
--- a/drivers/input/mouse/Makefile Wed Jun 30 14:36:18 2004
+++ b/drivers/input/mouse/Makefile Wed Jun 30 14:36:18 2004
@@ -11,6 +11,7 @@
obj-$(CONFIG_MOUSE_MAPLE) += maplemouse.o
obj-$(CONFIG_MOUSE_PC110PAD) += pc110pad.o
obj-$(CONFIG_MOUSE_PS2) += psmouse.o
+obj-$(CONFIG_MOUSE_PS2SYNC) += ps2sync.o
obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o
obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o

diff -Nru a/drivers/input/mouse/ps2sync.c b/drivers/input/mouse/ps2sync.c
--- /dev/null Wed Dec 31 16:00:00 1969
+++ b/drivers/input/mouse/ps2sync.c Wed Jun 30 14:36:18 2004
@@ -0,0 +1,356 @@
+/*
+ * Experimantal PS/2 mouse driver
+ *
+ * Copyright (c) 1999-2004 Vojtech Pavlik
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+#include <linux/init.h>
+#include "psmouse.h"
+
+#define DRIVER_DESC "Experimental PS/2 mouse driver"
+
+MODULE_AUTHOR("Vojtech Pavlik <[email protected]>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+struct ps2sync {
+ struct input_dev dev;
+ struct serio *serio;
+
+ unsigned long flags;
+
+ unsigned char cmdbuf[8];
+ unsigned int cmdcnt;
+ unsigned int nak;
+
+ unsigned char data[5];
+ unsigned long time[5];
+ unsigned long lasttime;
+ unsigned int lastoffset;
+
+ char devname[64];
+ char phys[32];
+};
+
+static int ps2sync_score_data(unsigned char *data, unsigned long *time, int step)
+{
+ int score = 0;
+ int x, y;
+
+ if (~data[0] & 0x80) score += 4;
+ if (~data[0] & 0x40) score += 4;
+ if ( data[0] & 0x08) score += 4;
+
+ if (~data[0] & 0x04) score++;
+ if (~data[0] & 0x02) score++;
+ if (~data[0] & 0x01) score++;
+
+ x = abs(data[1] ? (int) data[1] - (int) ((data[0] << 4) & 0x100) : 0);
+ y = abs(data[2] ? (int) ((data[0] << 3) & 0x100) - (int) data[2] : 0);
+
+ if (x < 128) score++;
+ if (y < 128) score++;
+ if (x < 8) score++;
+ if (y < 8) score++;
+ if (x < 4) score++;
+ if (y < 4) score++;
+ if (x < 2) score++;
+ if (y < 2) score++;
+
+ if (time[1] < time[0] / 2 && time[2] < time[0] / 2)
+ score += 16;
+
+ if (!(step % 3))
+ score += 8;
+
+ return score;
+}
+
+static void ps2sync_process_packet(struct ps2sync *ps2sync, unsigned char *data, struct pt_regs *regs)
+{
+ struct input_dev *dev = &ps2sync->dev;
+
+ input_regs(dev, regs);
+
+ input_report_key(dev, BTN_LEFT, data[0] & 1);
+ input_report_key(dev, BTN_MIDDLE, (data[0] >> 2) & 1);
+ input_report_key(dev, BTN_RIGHT, (data[0] >> 1) & 1);
+
+ input_report_rel(dev, REL_X, data[1] ? (int) data[1] - (int) ((data[0] << 4) & 0x100) : 0);
+ input_report_rel(dev, REL_Y, data[2] ? (int) ((data[0] << 3) & 0x100) - (int) data[2] : 0);
+
+ input_sync(dev);
+
+ printk(KERN_INFO "ps2sync: Packet processed.\n");
+}
+
+static irqreturn_t ps2sync_interrupt(struct serio *serio,
+ unsigned char data, unsigned int flags, struct pt_regs *regs)
+{
+ struct ps2sync *ps2sync = serio->private;
+ int offset = 0, score = 0;
+ int i, t;
+
+ if (test_bit(PSMOUSE_FLAG_ACK, &ps2sync->flags))
+ switch (data) {
+ case 0x00:/* Workaround for mice which don't ACK the Get ID command */
+ case PSMOUSE_RET_ACK:
+ ps2sync->nak = 0;
+ if (ps2sync->cmdcnt) {
+ set_bit(PSMOUSE_FLAG_CMD, &ps2sync->flags);
+ set_bit(PSMOUSE_FLAG_CMD1, &ps2sync->flags);
+ set_bit(PSMOUSE_FLAG_ID, &ps2sync->flags);
+ }
+ clear_bit(PSMOUSE_FLAG_ACK, &ps2sync->flags);
+ if (data == 0x00) break;
+ goto out;
+ case PSMOUSE_RET_NAK:
+ ps2sync->nak = 1;
+ clear_bit(PSMOUSE_FLAG_ACK, &ps2sync->flags);
+ goto out;
+ }
+
+ if (test_bit(PSMOUSE_FLAG_CMD, &ps2sync->flags)) {
+
+ ps2sync->cmdcnt--;
+ ps2sync->cmdbuf[ps2sync->cmdcnt] = data;
+
+ if (ps2sync->cmdcnt == 1) {
+ if (data != 0xab && data != 0xac)
+ clear_bit(PSMOUSE_FLAG_ID, &ps2sync->flags);
+ clear_bit(PSMOUSE_FLAG_CMD1, &ps2sync->flags);
+ }
+
+ if (!ps2sync->cmdcnt)
+ clear_bit(PSMOUSE_FLAG_CMD, &ps2sync->flags);
+
+ goto out;
+ }
+
+
+ {
+ static int killbyte = 0;
+ if (!(killbyte++ % 63)) {
+ printk(KERN_INFO "ps2sync: ------------------- DROPPING A BYTE -----------------\n");
+ goto out;
+ }
+ }
+
+ for (i = 0; i < 4; i++) {
+ ps2sync->data[i] = ps2sync->data[i + 1];
+ ps2sync->time[i] = ps2sync->time[i + 1];
+ }
+
+ ps2sync->time[4] = jiffies - ps2sync->lasttime;
+ ps2sync->data[4] = data;
+
+ for (i = 0; i < 3; i++) {
+ t = ps2sync_score_data(ps2sync->data + i, ps2sync->time + i, i - ps2sync->lastoffset + 1);
+ if (t > score) {
+ score = t;
+ offset = i;
+ }
+ }
+
+ printk(KERN_INFO "ps2sync: data: %02x, time: %ld, score: %d\n",
+ data, ps2sync->time[4], score);
+
+ if (offset - ps2sync->lastoffset + 1 != 0 && score > 30)
+ ps2sync_process_packet(ps2sync, ps2sync->data + offset, regs);
+
+ ps2sync->lasttime = jiffies;
+ ps2sync->lastoffset = offset;
+
+out:
+
+ return IRQ_HANDLED;
+}
+
+static int ps2sync_sendbyte(struct ps2sync *ps2sync, unsigned char byte)
+{
+ int timeout = 200000; /* 200 msec */
+
+ set_bit(PSMOUSE_FLAG_ACK, &ps2sync->flags);
+ if (serio_write(ps2sync->serio, byte))
+ return -1;
+ while (test_bit(PSMOUSE_FLAG_ACK, &ps2sync->flags) && timeout--) udelay(1);
+ clear_bit(PSMOUSE_FLAG_ACK, &ps2sync->flags);
+
+ return -ps2sync->nak;
+}
+
+static int ps2sync_command(struct ps2sync *ps2sync, unsigned char *param, int command)
+{
+ int timeout = 500000; /* 500 msec */
+ int send = (command >> 12) & 0xf;
+ int receive = (command >> 8) & 0xf;
+ int i;
+
+ ps2sync->cmdcnt = receive;
+
+ if (command == PSMOUSE_CMD_RESET_BAT)
+ timeout = 4000000; /* 4 sec */
+
+ if (receive && param)
+ for (i = 0; i < receive; i++)
+ ps2sync->cmdbuf[(receive - 1) - i] = param[i];
+
+ if (command & 0xff)
+ if (ps2sync_sendbyte(ps2sync, command & 0xff))
+ return -1;
+
+ for (i = 0; i < send; i++)
+ if (ps2sync_sendbyte(ps2sync, param[i]))
+ return -1;
+
+ while (test_bit(PSMOUSE_FLAG_CMD, &ps2sync->flags) && timeout--) {
+
+ if (!test_bit(PSMOUSE_FLAG_CMD1, &ps2sync->flags)) {
+
+ if (command == PSMOUSE_CMD_RESET_BAT && timeout > 100000)
+ timeout = 100000;
+
+ if (command == PSMOUSE_CMD_GETID && !test_bit(PSMOUSE_FLAG_ID, &ps2sync->flags)) {
+ clear_bit(PSMOUSE_FLAG_CMD, &ps2sync->flags);
+ ps2sync->cmdcnt = 0;
+ break;
+ }
+ }
+
+ udelay(1);
+ }
+
+ clear_bit(PSMOUSE_FLAG_CMD, &ps2sync->flags);
+
+ if (param)
+ for (i = 0; i < receive; i++)
+ param[i] = ps2sync->cmdbuf[(receive - 1) - i];
+
+ if (command == PSMOUSE_CMD_RESET_BAT && ps2sync->cmdcnt == 1)
+ return 0;
+
+ if (ps2sync->cmdcnt)
+ return -1;
+
+ return 0;
+}
+
+static int ps2sync_probe(struct ps2sync *ps2sync)
+{
+ unsigned char param[2];
+
+ if (ps2sync_command(ps2sync, param, PSMOUSE_CMD_RESET_BAT))
+ return -1;
+
+ if (param[0] != PSMOUSE_RET_BAT && param[1] != PSMOUSE_RET_ID)
+ return -1;
+
+ return 0;
+}
+
+static int ps2sync_initialize(struct ps2sync *ps2sync)
+{
+ unsigned char param[2];
+
+ if (ps2sync_command(ps2sync, NULL, PSMOUSE_CMD_ENABLE))
+ return -1;
+
+ if (ps2sync_command(ps2sync, param, PSMOUSE_CMD_SETSTREAM))
+ return -1;
+
+ return 0;
+}
+
+static void ps2sync_disconnect(struct serio *serio)
+{
+ struct ps2sync *ps2sync;
+ ps2sync = serio->private;
+ input_unregister_device(&ps2sync->dev);
+ serio_close(serio);
+ kfree(ps2sync);
+}
+
+static void ps2sync_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct ps2sync *ps2sync;
+
+ if ((serio->type & SERIO_TYPE) != SERIO_8042)
+ return;
+
+ if (!(ps2sync = kmalloc(sizeof(struct ps2sync), GFP_KERNEL))) return;
+ memset(ps2sync, 0, sizeof(struct ps2sync));
+
+ init_input_dev(&ps2sync->dev);
+ ps2sync->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
+ ps2sync->dev.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT);
+ ps2sync->dev.relbit[0] = BIT(REL_X) | BIT(REL_Y);
+ ps2sync->serio = serio;
+ ps2sync->dev.private = ps2sync;
+ serio->private = ps2sync;
+
+ if (serio_open(serio, drv)) {
+ kfree(ps2sync);
+ serio->private = NULL;
+ return;
+ }
+
+ if (ps2sync_probe(ps2sync) < 0) {
+ serio_close(serio);
+ kfree(ps2sync);
+ serio->private = NULL;
+ return;
+ }
+
+ sprintf(ps2sync->devname, "PS/2 Experimental Mouse");
+ sprintf(ps2sync->phys, "%s/input0", serio->phys);
+
+ ps2sync->dev.name = ps2sync->devname;
+ ps2sync->dev.phys = ps2sync->phys;
+ ps2sync->dev.id.bustype = BUS_I8042;
+ ps2sync->dev.id.vendor = 0xeee1;
+ ps2sync->dev.id.product = 0x01;
+ ps2sync->dev.id.version = 0x00;
+
+ input_register_device(&ps2sync->dev);
+
+ printk(KERN_INFO "input: %s on %s\n", ps2sync->devname, serio->phys);
+
+ ps2sync_initialize(ps2sync);
+}
+
+static struct serio_driver ps2sync_drv = {
+ .driver = {
+ .name = "ps2sync",
+ },
+ .description = DRIVER_DESC,
+ .interrupt = ps2sync_interrupt,
+ .connect = ps2sync_connect,
+ .disconnect = ps2sync_disconnect,
+};
+
+int __init ps2sync_init(void)
+{
+ serio_register_driver(&ps2sync_drv);
+ return 0;
+}
+
+void __exit ps2sync_exit(void)
+{
+ serio_unregister_driver(&ps2sync_drv);
+}
+
+module_init(ps2sync_init);
+module_exit(ps2sync_exit);

--
Vojtech Pavlik
SuSE Labs, SuSE CR


2004-06-30 15:40:02

by Timothy Miller

[permalink] [raw]
Subject: Re: Experimental PS/2 driver with heuristic synchronization



Vojtech Pavlik wrote:
> Hi!
>
> I wanted to try if my thoughts about how to reliably synchronize the
> PS/2 mouse stream would work, and as a test I created this driver.
>


This is an awesome idea. I've always had intermittent problems with
mice freaking out under Linux. Most of the time, it only freaks out for
a few seconds, but sometimes, I have to ctrl-alt-F1+ctrl-alt-F7 to get
the mouse back.

A patck like yours has been needed for a long time. Good show!