psaux driver fixes

Roman Hodek (rnhodek@faui21j.informatik.uni-erlangen.de)
Thu, 22 Aug 96 11:11:52 +0200


Here is a new version of my revised psaux driver (it mainly fixes
keyboard lockups when opening the device, and does better error
checking).

Compared to the last version I posted here (that already was in 2.0.8,
but then removed again due to problems), it now correctly handles PS/2
mice that do NOT send any acknowledges. This seems to be the case with
some notebook built-in mice... It now works, too, for the people that
have reported problems with the former try. So, the patch should be
ready now for releasing it to the public... :-)

Though I've built the patch below relative to 2.0.11, it also cleanly
goes into 2.0.14 (nothing changed in psaux.c ...)

Roman

---------------------------------------------------------------------------
--- linux-2.0.11/drivers/char/psaux.c~ Tue Aug 6 08:36:02 1996
+++ linux-2.0.11/drivers/char/psaux.c Thu Aug 8 09:18:09 1996
@@ -25,6 +25,14 @@
* Rearranged SIGIO support to use code from tty_io. 9Sept95 ctm@ardi.com
*
* Modularised 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>
+ *
+ * Fixed keyboard lockups at open time (intervening kbd interrupts), handle
+ * RESEND replies, better error checking
+ * 3-Jul-96 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ *
+ * Error checking adapted for dynamically detecting whether the mouse sends
+ * ACKs at all.
+ * 7-Aug-96 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
*/

/* Uncomment the following line if your mouse needs initialization. */
@@ -57,8 +65,10 @@
#define AUX_STATUS 0x64 /* Aux device status reg */

/* aux controller status bits */
+#define AUX_KOBUF_FULL 0x01 /* output buffer (from controller) full */
#define AUX_OBUF_FULL 0x21 /* output buffer (from device) full */
#define AUX_IBUF_FULL 0x02 /* input buffer (to device) full */
+#define AUX_TIMEOUT 0x40 /* controller reports timeout */

/* aux controller commands */
#define AUX_CMD_WRITE 0x60 /* value to write to controller */
@@ -81,6 +91,14 @@
#define AUX_DISABLE_DEV 0xf5 /* disable aux device */
#define AUX_RESET 0xff /* reset aux device */

+/* kbd controller commands */
+#define KBD_DISABLE 0xad
+#define KBD_ENABLE 0xae
+
+/* replies */
+#define AUX_ACK 0xfa
+#define AUX_RESEND 0xfe
+
#define MAX_RETRIES 60 /* some aux operations take long time*/
#if defined(__alpha__) && !defined(CONFIG_PCI)
# define AUX_IRQ 9 /* Jensen is odd indeed */
@@ -120,8 +138,10 @@
static int aux_ready = 0;
static int aux_count = 0;
static int aux_present = 0;
+static enum {
+ SENDACK_dont_know, SENDACK_yes, SENDACK_probably_no, SENDACK_no
+} aux_sends_ack;
static int poll_aux_status(void);
-static int poll_aux_status_nosleep(void);
static int fasync_aux(struct inode *inode, struct file *filp, int on);

#ifdef CONFIG_82C710_MOUSE
@@ -136,49 +156,110 @@


/*
- * Write to aux device
+ * Write a byte to the kbd controller and wait for it being processed
*/

-static void aux_write_dev(int val)
+static int aux_write_byte(int val,int port)
{
- poll_aux_status();
- outb_p(AUX_MAGIC_WRITE,AUX_COMMAND); /* write magic cookie */
- poll_aux_status();
- outb_p(val,AUX_OUTPUT_PORT); /* write data */
+ outb_p(val, port);
+ return poll_aux_status();
}

/*
- * Write to device & handle returned ack
+ * Write to device, handle returned resend requests and wait for ack
+ *
+ * Dynamic detection whether mouse sends ACKs: 'psaux_sends_ack' starts in
+ * 'dont_know' state. If an ACK is received, it switches to 'yes' and missing
+ * ACKs result in an error in future. If no ACK arrives in 'dont_know' state,
+ * we go to 'probably_no' and reduce the time to wait for it, since it's
+ * rather unprobable that it was missing due to an transmission error, and
+ * waiting too long would be wasting time. If the next ACK misses, too, we go
+ * to a definitive 'no' state. In each state except 'yes', missing ACKs don't
+ * count as an error.
*/
-#if defined INITIALIZE_DEVICE
static int aux_write_ack(int val)
{
- int retries = 0;
+ int rv;

- poll_aux_status_nosleep();
- outb_p(AUX_MAGIC_WRITE,AUX_COMMAND);
- poll_aux_status_nosleep();
- outb_p(val,AUX_OUTPUT_PORT);
- poll_aux_status_nosleep();
-
- if ((inb(AUX_STATUS) & AUX_OBUF_FULL) == AUX_OBUF_FULL)
- {
- return (inb(AUX_INPUT_PORT));
+ repeat:
+ if (poll_aux_status() < 0)
+ return -1;
+ outb_p(AUX_MAGIC_WRITE, AUX_COMMAND);
+ if (poll_aux_status() < 0)
+ return -1;
+ outb_p(val, AUX_OUTPUT_PORT);
+
+ if ((rv = poll_aux_status()) < 0)
+ /* timeout */
+ return -1;
+ else if (rv == AUX_RESEND)
+ /* controller needs last byte again... */
+ goto repeat;
+ else if (rv == AUX_ACK) {
+ /* already got ACK */
+ aux_sends_ack = SENDACK_yes;
+ return 0;
+ }
+ else {
+ int max_retries, retries = 0, stat;
+
+ /* don't even try to wait for an ACK if we surely know it
+ * won't arrive... */
+ if (aux_sends_ack == SENDACK_no)
+ return 0;
+ /* if we don't really expect an ACK anymore, reduce the time
+ * to wait for it */
+ max_retries = MAX_RETRIES;
+ if (aux_sends_ack == SENDACK_probably_no)
+ max_retries /= 2;
+
+ /* wait for ACK from controller */
+ while (retries < max_retries) {
+ stat = inb_p(AUX_STATUS);
+ if ((stat & AUX_OBUF_FULL) == AUX_OBUF_FULL &&
+ inb_p(AUX_INPUT_PORT) == AUX_ACK) {
+ /* ok, now we know there are ACKs */
+ aux_sends_ack = SENDACK_yes;
+ return 0;
+ }
+ current->state = TASK_INTERRUPTIBLE;
+ current->timeout = jiffies + (5*HZ + 99) / 100;
+ schedule();
+ retries++;
+ }
+ switch( aux_sends_ack ) {
+ case SENDACK_yes:
+ /* If we know this mouse to sends ACKs, it's an error
+ * if it's missing */
+ return -1;
+ case SENDACK_dont_know:
+ /* seems we don't get ACKs... no error */
+ aux_sends_ack = SENDACK_probably_no;
+ return 0;
+ case SENDACK_probably_no:
+ default:
+ /* now it's for sure there're no ACKs */
+ aux_sends_ack = SENDACK_no;
+ return 0;
+ }
}
- return 0;
}
-#endif /* INITIALIZE_DEVICE */

/*
* Write aux device command
*/

-static void aux_write_cmd(int val)
+static int aux_write_cmd(int val)
{
- poll_aux_status();
- outb_p(AUX_CMD_WRITE,AUX_COMMAND);
- poll_aux_status();
- outb_p(val,AUX_OUTPUT_PORT);
+ if (poll_aux_status() < 0)
+ return -1;
+ outb_p(AUX_CMD_WRITE, AUX_COMMAND);
+ if (poll_aux_status() < 0)
+ return -1;
+ outb_p(val, AUX_OUTPUT_PORT);
+ if (poll_aux_status() < 0)
+ return -1;
+ return 0;
}


@@ -258,10 +339,19 @@
fasync_aux(inode, file, 0);
if (--aux_count)
return;
- aux_write_cmd(AUX_INTS_OFF); /* disable controller ints */
- poll_aux_status();
- outb_p(AUX_DISABLE,AUX_COMMAND); /* Disable Aux device */
+ /* disable keyboard to avoid clashes with multi-byte command sequences */
poll_aux_status();
+ if (aux_write_byte(KBD_DISABLE, AUX_COMMAND) < 0)
+ printk(KERN_ERR "psaux: controller timeout\n");
+ /* disable controller ints */
+ if (aux_write_cmd(AUX_INTS_OFF) < 0)
+ printk(KERN_ERR "psaux: controller timeout\n");
+ /* Disable Aux device */
+ if (aux_write_byte(AUX_DISABLE, AUX_COMMAND) < 0)
+ printk(KERN_ERR "psaux: controller timeout\n");
+ /* re-enable keyboard */
+ if (aux_write_byte(KBD_ENABLE, AUX_COMMAND) < 0)
+ printk(KERN_ERR "psaux: controller timeout\n");
free_irq(AUX_IRQ, NULL);
MOD_DEC_USE_COUNT;
}
@@ -306,7 +396,7 @@
return -ENODEV;
if (aux_count++)
return 0;
- if (!poll_aux_status()) {
+ if (poll_aux_status() < 0) {
aux_count--;
return -EBUSY;
}
@@ -317,12 +407,29 @@
}
MOD_INC_USE_COUNT;
poll_aux_status();
- outb_p(AUX_ENABLE,AUX_COMMAND); /* Enable Aux */
- aux_write_dev(AUX_ENABLE_DEV); /* enable aux device */
- aux_write_cmd(AUX_INTS_ON); /* enable controller ints */
- poll_aux_status();
+ /* disable keyboard to avoid clashes with multi-byte command sequences */
+ if (aux_write_byte(KBD_DISABLE, AUX_COMMAND) < 0)
+ goto open_error;
+ /* Enable Aux in kbd controller */
+ if (aux_write_byte(AUX_ENABLE, AUX_COMMAND) < 0)
+ goto open_error;
+ /* enable aux device */
+ if (aux_write_ack(AUX_ENABLE_DEV) < 0)
+ goto open_error;
+ /* enable controller ints */
+ if (aux_write_cmd(AUX_INTS_ON) < 0)
+ goto open_error;
+ /* re-enable keyboard */
+ if (aux_write_byte(KBD_ENABLE, AUX_COMMAND) < 0)
+ goto open_error;
+
aux_ready = 0;
return 0;
+ open_error:
+ printk( KERN_ERR "psaux: controller timeout\n" );
+ /* re-enable the keyboard on errors */
+ aux_write_byte(KBD_ENABLE, AUX_COMMAND);
+ return -EIO;
}

#ifdef CONFIG_82C710_MOUSE
@@ -378,17 +485,31 @@
static int write_aux(struct inode * inode, struct file * file, const char * buffer, int count)
{
int i = count;
+ int rv = 0;
+
+ /* temporary disable keyboard to avoid clashes with multi-byte command
+ * sequence */
+ if (aux_write_byte(KBD_DISABLE, AUX_COMMAND) < 0)
+ return -EIO;

while (i--) {
- if (!poll_aux_status())
- return -EIO;
+ if (poll_aux_status() < 0) {
+ rv = -EIO;
+ break;
+ }
outb_p(AUX_MAGIC_WRITE,AUX_COMMAND);
- if (!poll_aux_status())
- return -EIO;
+ if (poll_aux_status() < 0) {
+ rv = -EIO;
+ break;
+ }
outb_p(get_user(buffer++),AUX_OUTPUT_PORT);
}
+ /* reenable keyboard */
+ if (poll_aux_status() < 0 || aux_write_byte(KBD_ENABLE, AUX_COMMAND) < 0)
+ rv = -EIO;
+
inode->i_mtime = CURRENT_TIME;
- return count;
+ return rv ? rv : count;
}


@@ -512,6 +633,8 @@
queue->head = queue->tail = 0;
queue->proc_list = NULL;
if (!qp_found) {
+ /* don't know yet whether this mouse sends ACKs or not */
+ aux_sends_ack = SENDACK_dont_know;
#if defined INITIALIZE_DEVICE
outb_p(AUX_ENABLE,AUX_COMMAND); /* Enable Aux */
aux_write_ack(AUX_SET_SAMPLE);
@@ -519,13 +642,11 @@
aux_write_ack(AUX_SET_RES);
aux_write_ack(3); /* 8 counts per mm */
aux_write_ack(AUX_SET_SCALE21); /* 2:1 scaling */
- poll_aux_status_nosleep();
#endif /* INITIALIZE_DEVICE */
- outb_p(AUX_DISABLE,AUX_COMMAND); /* Disable Aux device */
- poll_aux_status_nosleep();
- outb_p(AUX_CMD_WRITE,AUX_COMMAND);
- poll_aux_status_nosleep(); /* Disable interrupts */
- outb_p(AUX_INTS_OFF, AUX_OUTPUT_PORT); /* on the controller */
+ /* Disable Aux device and its interrupts on the controller */
+ if (aux_write_byte(AUX_DISABLE, AUX_COMMAND) < 0 ||
+ aux_write_cmd(AUX_INTS_OFF) < 0)
+ printk(KERN_ERR "psaux: controller timeout\n");
}
return 0;
}
@@ -533,7 +654,7 @@
#ifdef MODULE
int init_module(void)
{
- return psaux_init(); /*?? Bjorn */
+ return psaux_init();
}

void cleanup_module(void)
@@ -545,28 +666,18 @@
static int poll_aux_status(void)
{
int retries=0;
+ int reply=0;

- while ((inb(AUX_STATUS)&0x03) && retries < MAX_RETRIES) {
+ while ((inb(AUX_STATUS) & (AUX_KOBUF_FULL|AUX_IBUF_FULL)) &&
+ retries < MAX_RETRIES) {
if ((inb_p(AUX_STATUS) & AUX_OBUF_FULL) == AUX_OBUF_FULL)
- inb_p(AUX_INPUT_PORT);
+ reply = inb_p(AUX_INPUT_PORT);
current->state = TASK_INTERRUPTIBLE;
current->timeout = jiffies + (5*HZ + 99) / 100;
schedule();
retries++;
}
- return !(retries==MAX_RETRIES);
-}
-
-static int poll_aux_status_nosleep(void)
-{
- int retries = 0;
-
- while ((inb(AUX_STATUS)&0x03) && retries < 1000000) {
- if ((inb_p(AUX_STATUS) & AUX_OBUF_FULL) == AUX_OBUF_FULL)
- inb_p(AUX_INPUT_PORT);
- retries++;
- }
- return !(retries == 1000000);
+ return (retries==MAX_RETRIES) ? -1 : reply;
}

#ifdef CONFIG_82C710_MOUSE