Subject: kern/15839: PS/2 driver improvements
To: None <gnats-bugs@gnats.netbsd.org>
From: seebs <seebs@ged.plethora.net>
List: netbsd-bugs
Date: 03/08/2002 16:06:59
>Number:         15839
>Category:       kern
>Synopsis:       PS/2 driver can be improved
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Mar 08 15:09:00 PST 2002
>Closed-Date:
>Last-Modified:
>Originator:     seebs
>Release:        NetBSD 1.5ZA
>Organization:
>Environment:
System: NetBSD ged.plethora.net 1.5ZA NetBSD 1.5ZA (GED) #22: Fri Mar 8 15:56:46 CST 2002 seebs@ged.plethora.net:/usr/src/sys/arch/i386/compile/GED i386
Architecture: i386
Machine: i386
>Description:
	The pmsi driver cannot cope with a KVM switch which may occasionally
	forget about scroll wheels.  Furthermore, it is an offense against
	elegant design for the pmsi driver to be nearly identical to the
	standard pms driver, except that it does some weird magic, and it
	doesn't know about 5-button mice.

	Proposed, that psm.c should be revised to process multiple types of
	mice, and handle the protocol transparently.

	Secondly, once you've done this, the annoying tendency of a certain
	KVM switch, made by a vendor who shall rename nameless, but whose
	name would rhyme with "pelkin" if that were a word, to reset the
	mouse silently into plain-old psm mode creates problems.

	The solution is to check each non-initial byte of a packet for delay.
	The normal delay between PS/2 packet bytes seems to be around 1700us,
	plus or minus thirty.  An occasional byte may take up to 5000us to
	arrive.  A delay of, say, 30000us reliably indicates that the new byte
	is part of a different packet.  Thus, if the driver isn't expecting a
	new packet, and sees a long delay, it now resets the mouse, throwing
	the packet out in the process.

>How-To-Repeat:
	Buy a KVM switch.

>Fix:
	(This also implies throwing out psm_intelli.c, which we don't need.)

*** psm.c.orig	Fri Mar  8 09:36:48 2002
--- psm.c	Fri Mar  8 16:05:49 2002
***************
*** 30,35 ****
--- 30,36 ----
  #include <sys/systm.h>
  #include <sys/device.h>
  #include <sys/ioctl.h>
+ #include <sys/kthread.h>
  
  #include <machine/bus.h>
  
***************
*** 40,45 ****
--- 41,51 ----
  #include <dev/wscons/wsconsio.h>
  #include <dev/wscons/wsmousevar.h>
  
+ /* not needed */
+ #undef PS2DEBUG
+ 
+ enum pmsprotocol { PMS_UNKNOWN, PMS_STANDARD, PMS_SCROLL3, PMS_SCROLL5 };
+ 
  struct pms_softc {		/* driver status information */
  	struct device sc_dev;
  
***************
*** 52,61 ****
  #endif /* !PMS_DISABLE_POWERHOOK */
  	int inputstate;
  	u_int buttons, oldbuttons;	/* mouse button status */
! 	signed char dx;
! 	char inbuf[3];
  
  	struct device *sc_wsmousedev;
  };
  
  int pmsprobe __P((struct device *, struct cfdata *, void *));
--- 58,70 ----
  #endif /* !PMS_DISABLE_POWERHOOK */
  	int inputstate;
  	u_int buttons, oldbuttons;	/* mouse button status */
! 	signed char dx, dy, dz;
! 	enum pmsprotocol protocol;
! 	char inbuf[4];
  
  	struct device *sc_wsmousedev;
+ 	struct proc *sc_event_thread;
+ 	int sc_reset_flag;
  };
  
  int pmsprobe __P((struct device *, struct cfdata *, void *));
***************
*** 66,73 ****
--- 75,85 ----
  	sizeof(struct pms_softc), pmsprobe, pmsattach,
  };
  
+ static int	pms_protocol __P((pckbc_tag_t, pckbc_slot_t));
  static void	do_enable __P((struct pms_softc *));
  static void	do_disable __P((struct pms_softc *));
+ static void	pms_reset_thread __P((void*));
+ static void	pms_spawn_reset_thread __P((void*));
  int	pms_enable __P((void *));
  int	pms_ioctl __P((void *, u_long, caddr_t, int, struct proc *));
  void	pms_disable __P((void *));
***************
*** 81,86 ****
--- 93,140 ----
  	pms_disable,
  };
  
+ static int
+ pms_protocol(tag, slot)
+ 	pckbc_tag_t tag;
+ 	pckbc_slot_t slot;
+ {
+ 	u_char cmd[2], resp[1];
+ 	int i, j, res;
+ 	struct {
+ 		int rates[3], response;
+ 		enum pmsprotocol p;
+ 	} protocols[] = {
+ 	  { { 200, 200, 80 }, 4, PMS_SCROLL5 },
+ 	  { { 200, 100, 80 }, 3, PMS_SCROLL3 },
+ 	  { { 0, 0, 0 }, 0, PMS_STANDARD },
+ 	};
+ 
+ 	for (j = 0; protocols[j].rates[0]; ++j) {
+ 		cmd[0] = PMS_SET_SAMPLE;
+ 		for (i = 0; i < 3; i++) {
+ 			cmd[1] = protocols[j].rates[i];
+ 			res = pckbc_poll_cmd(tag, slot, cmd, 2, 0, 0, 0);
+ 			if (res)
+ 				return 0;
+ 		}
+ 
+ 		cmd[0] = PMS_SEND_DEV_ID;
+ 		res = pckbc_poll_cmd(tag, slot, cmd, 1, 1, resp, 0);
+ 		if (res)
+ 			return 0;
+ 		if (resp[0] == protocols[j].response) {
+ #ifdef PS2DEBUG
+ 			printf("returning protocol %d\n", protocols[j].p);
+ #endif
+ 			return protocols[j].p;
+ 		}
+ 	}
+ #ifdef PS2DEBUG
+ 	printf("standard protocol\n");
+ #endif
+ 	return PMS_STANDARD;
+ }
+ 
  int
  pmsprobe(parent, match, aux)
  	struct device *parent;
***************
*** 150,155 ****
--- 204,219 ----
  		return;
  	}
  #endif
+ 	printf("pmsattach: about to get protocol\n");
+ 	res = pms_protocol(pa->pa_tag, pa->pa_slot);
+ 	if (!res) {
+ #ifdef DEBUG
+ 		printf("pmsattach: error setting protocol\n");
+ #endif
+ 		sc->protocol = PMS_STANDARD;
+ 	} else {
+ 		sc->protocol = res;
+ 	}
  
  	sc->inputstate = 0;
  	sc->oldbuttons = 0;
***************
*** 175,180 ****
--- 239,248 ----
  		printf("pmsattach: disable error\n");
  	pckbc_slot_enable(sc->sc_kbctag, sc->sc_kbcslot, 0);
  
+ 	sc->sc_reset_flag = 0;
+ 
+ 	kthread_create(pms_spawn_reset_thread, sc);
+ 
  #ifndef PMS_DISABLE_POWERHOOK
  	sc->sc_powerhook = powerhook_establish(pms_power, sc);
  #endif /* !PMS_DISABLE_POWERHOOK */
***************
*** 196,201 ****
--- 264,275 ----
  	res = pckbc_enqueue_cmd(sc->sc_kbctag, sc->sc_kbcslot, cmd, 1, 0, 1, 0);
  	if (res)
  		printf("pms_enable: command error\n");
+ 
+ 	res = pms_protocol(sc->sc_kbctag, sc->sc_kbcslot);
+ 	if (res)
+ 		sc->protocol = res;
+ 	else
+ 		printf("psm_enable: couldn't verify protocol\n");
  #if 0
  	{
  		u_char scmd[2];
***************
*** 331,368 ****
  	return (0);
  }
  
  /* Masks for the first byte of a packet */
  #define PS2LBUTMASK 0x01
  #define PS2RBUTMASK 0x02
  #define PS2MBUTMASK 0x04
  
  void pmsinput(vsc, data)
  void *vsc;
  int data;
  {
  	struct pms_softc *sc = vsc;
- 	signed char dy;
  	u_int changed;
  
  	if (!sc->sc_enabled) {
  		/* Interrupts are not expected.  Discard the byte. */
  		return;
  	}
! 
  	switch (sc->inputstate) {
  
  	case 0:
  		if ((data & 0xc0) == 0) { /* no ovfl, bit 3 == 1 too? */
  			sc->buttons = ((data & PS2LBUTMASK) ? 0x1 : 0) |
  			    ((data & PS2MBUTMASK) ? 0x2 : 0) |
  			    ((data & PS2RBUTMASK) ? 0x4 : 0);
  			++sc->inputstate;
  		} else {
  			printf("got data 0x%x\n", data);
  		}
  		break;
  
  	case 1:
  		sc->dx = data;
  		/* Bounding at -127 avoids a bug in XFree86. */
  		sc->dx = (sc->dx == -128) ? -127 : sc->dx;
--- 405,486 ----
  	return (0);
  }
  
+ 
+ static void pms_spawn_reset_thread(arg)
+ void *arg;
+ {
+ 	struct pms_softc *sc = arg;
+ 	kthread_create1(pms_reset_thread, sc, &sc->sc_event_thread,
+ 		"pms reset thread");
+ }
+ 
+ static void pms_reset_thread(arg)
+ void *arg;
+ {
+ 	struct pms_softc *sc = arg;
+         for (;;) {
+                 tsleep(&sc->sc_reset_flag, PWAIT, "pmsreset", 0);
+                 do_disable(sc);
+                 do_enable(sc);
+         }
+ }
+ 
  /* Masks for the first byte of a packet */
  #define PS2LBUTMASK 0x01
  #define PS2RBUTMASK 0x02
  #define PS2MBUTMASK 0x04
+ #define PS24BUTMASK 0x10
+ #define PS25BUTMASK 0x20
  
  void pmsinput(vsc, data)
  void *vsc;
  int data;
  {
  	struct pms_softc *sc = vsc;
  	u_int changed;
+ 	static unsigned long packet;
+ 	static struct timeval last, current;
  
  	if (!sc->sc_enabled) {
  		/* Interrupts are not expected.  Discard the byte. */
  		return;
  	}
! 	microtime(&current);
! 	if (sc->inputstate != 0) {
! 		long long diff;
! 		diff = (current.tv_sec - last.tv_sec) * 1000000;
! 		diff += (current.tv_usec - last.tv_usec);
! #ifdef PS2DEBUG
! 		printf("diff %lld usec\n", diff);
! #endif
! 		if (diff > 10000) {
! #if DEBUG
! 			printf("psm_input: unusual delay, spawning reset thread\n");
! #endif
! 			wakeup(&sc->sc_reset_flag);
! 			sc->inputstate = 0;
! 			return;
! 		}
! 	}
! 	last = current;
  	switch (sc->inputstate) {
  
  	case 0:
+ 		packet = (data & 0xff) << 24;
  		if ((data & 0xc0) == 0) { /* no ovfl, bit 3 == 1 too? */
  			sc->buttons = ((data & PS2LBUTMASK) ? 0x1 : 0) |
  			    ((data & PS2MBUTMASK) ? 0x2 : 0) |
  			    ((data & PS2RBUTMASK) ? 0x4 : 0);
  			++sc->inputstate;
  		} else {
+ #ifdef PS2DEBUG
  			printf("got data 0x%x\n", data);
+ #endif
  		}
  		break;
  
  	case 1:
+ 		packet |= (data & 0xff) << 16;
  		sc->dx = data;
  		/* Bounding at -127 avoids a bug in XFree86. */
  		sc->dx = (sc->dx == -128) ? -127 : sc->dx;
***************
*** 370,385 ****
  		break;
  
  	case 2:
! 		dy = data;
! 		dy = (dy == -128) ? -127 : dy;
  		sc->inputstate = 0;
  
  		changed = (sc->buttons ^ sc->oldbuttons);
  		sc->oldbuttons = sc->buttons;
  
! 		if (sc->dx || dy || changed)
  			wsmouse_input(sc->sc_wsmousedev,
! 				      sc->buttons, sc->dx, dy, 0,
  				      WSMOUSE_INPUT_DELTA);
  		break;
  	}
--- 488,539 ----
  		break;
  
  	case 2:
! 		packet |= (data & 0xff) << 8;
! 		sc->dy = data;
! 		sc->dy = (sc->dy == -128) ? -127 : sc->dy;
! 
! 		if (sc->protocol == PMS_STANDARD) {
! #ifdef PS2DEBUG
! 			printf("packet: 0x%08lx\n", packet);
! #endif
! 			sc->inputstate = 0;
! 			changed = (sc->buttons ^ sc->oldbuttons);
! 			sc->oldbuttons = sc->buttons;
! 
! 			if (sc->dx || sc->dy || changed)
! 				wsmouse_input(sc->sc_wsmousedev,
! 					      sc->buttons, sc->dx, sc->dy, 0,
! 					      WSMOUSE_INPUT_DELTA);
! 		} else
! 			++sc->inputstate;
! 		break;
! 
! 	case 3:
! 		packet |= (data & 0xff);
  		sc->inputstate = 0;
+                 if (sc->protocol == PMS_SCROLL5) {
+ 			sc->dz = data & 0xf;
+                 	if (sc->dz & 0x8)
+                 		sc->dz -= 16;
+                 	if (data & PS24BUTMASK)
+ 				sc->buttons |= 0x8;
+                 	if (data & PS25BUTMASK)
+ 				sc->buttons |= 0x10;
+                 } else {
+ 			sc->dz = data;
+                 	if (sc->dz == -128)
+ 				sc->dz = -127;
+                 }
+ #ifdef PS2DEBUG
+ 		printf("packet: 0x%08lx\n", packet);
+ #endif
  
  		changed = (sc->buttons ^ sc->oldbuttons);
  		sc->oldbuttons = sc->buttons;
  
! 		if (sc->dx || sc->dy || sc->dz || changed)
  			wsmouse_input(sc->sc_wsmousedev,
! 				      sc->buttons, sc->dx, sc->dy, sc->dz,
  				      WSMOUSE_INPUT_DELTA);
  		break;
  	}
>Release-Note:
>Audit-Trail:
>Unformatted: