--- linux/drivers/char/pc_keyb.c.orig Mon Sep 4 13:39:17 2000 +++ linux/drivers/char/pc_keyb.c Sun Nov 5 15:15:58 2000 @@ -13,6 +13,14 @@ * Code fixes to handle mouse ACKs properly. * C. Scott Ananian 1999-01-29. * + * Code fixes to *really* handle mouse ACKs properly. + * Julian Bradfield 1999-04-30. + * + * Implement exclusive access mechanism for aux device. + * This permits grabbing the mouse away from the X server, + * which is needed by fancy mice that have configurable features. + * Chris Hanson 2000-10-30. + * */ #include @@ -58,6 +66,8 @@ static void kbd_write_output_w(int data); #ifdef CONFIG_PSMOUSE static void aux_write_ack(int val); +static void aux_kill_fasync(struct fasync_struct *fasync, int s); +static int aux_release_ioctl(struct file *file); #endif spinlock_t kbd_controller_lock = SPIN_LOCK_UNLOCKED; @@ -81,13 +91,25 @@ static struct aux_queue *queue; /* Mouse data buffer. */ static int aux_count = 0; -/* used when we send commands to the mouse that expect an ACK. */ +/* used when we (as opposed to the user programs using the aux device) + send commands to the mouse that expect an ACK. */ static unsigned char mouse_reply_expected = 0; +/* used to make sure we read acks from the mouse before we write + another byte */ +static unsigned char mouse_ack_pending = 0; +#define MOUSE_ACK_TIMEOUT 5 /* actually, 1 seems to be enough usually */ #define AUX_INTS_OFF (KBD_MODE_KCC | KBD_MODE_DISABLE_MOUSE | KBD_MODE_SYS | KBD_MODE_KBD_INT) #define AUX_INTS_ON (KBD_MODE_KCC | KBD_MODE_SYS | KBD_MODE_MOUSE_INT | KBD_MODE_KBD_INT) #define MAX_RETRIES 60 /* some aux operations take long time*/ + +/* Support for exclusive access to the AUX device. */ +static struct file *aux_exclusive = 0; +static struct wait_queue *aux_exclusive_wait = NULL; +static unsigned long aux_last_write = 0; +#define AUX_GRAB _IO('M', 1) +#define AUX_RELEASE _IO('M', 2) #endif /* CONFIG_PSMOUSE */ /* @@ -392,6 +414,10 @@ static inline void handle_mouse_event(unsigned char scancode) { #ifdef CONFIG_PSMOUSE + if (mouse_ack_pending) + /* It needn't actually be an ack, it could be an echo; + but every byte sent to the mouse results in a byte back. */ + mouse_ack_pending = 0; if (mouse_reply_expected) { if (scancode == AUX_ACK) { mouse_reply_expected--; @@ -399,19 +425,18 @@ } mouse_reply_expected = 0; } - else if(scancode == AUX_RECONNECT){ - queue->head = queue->tail = 0; /* Flush input queue */ - /* ping the mouse :) */ - kb_wait(); - kbd_write_command(KBD_CCMD_WRITE_MOUSE); - kb_wait(); - kbd_write_output(AUX_ENABLE_DEV); - /* we expect an ACK in response. */ - mouse_reply_expected++; - kb_wait(); - return; - } - + else if (scancode == AUX_RECONNECT) { + queue->head = queue->tail = 0; /* Flush input queue */ + /* ping the mouse :) */ + kb_wait(); + kbd_write_command(KBD_CCMD_WRITE_MOUSE); + kb_wait(); + kbd_write_output(AUX_ENABLE_DEV); + /* we expect an ACK in response. */ + mouse_reply_expected++; + kb_wait(); + return; + } add_mouse_randomness(scancode); if (aux_count) { int head = queue->head; @@ -421,7 +446,7 @@ if (head != queue->tail) { queue->head = head; if (queue->fasync) - kill_fasync(queue->fasync, SIGIO); + aux_kill_fasync(queue->fasync, SIGIO); wake_up_interruptible(&queue->proc_list); } } @@ -440,31 +465,33 @@ unsigned char status = kbd_read_status(); unsigned int work = 10000; - while (status & KBD_STAT_OBF) { + while ((--work > 0) && (status & KBD_STAT_OBF)) { unsigned char scancode; scancode = kbd_read_input(); -# ifdef CHECK_RECONNECT_SCANCODE - printk(KERN_INFO "-=db=-: kbd_read_input() : scancode == %d\n",scancode); -# endif - if (status & KBD_STAT_MOUSE_OBF) { - handle_mouse_event(scancode); - } else { - if (do_acknowledge(scancode)) - handle_scancode(scancode, !(scancode & 0x80)); - mark_bh(KEYBOARD_BH); - } - status = kbd_read_status(); - - if(!work--) + /* Error bytes must be ignored to make the + Synaptics touchpads compaq use work */ +#if 1 + /* Ignore error bytes */ + if (!(status & (KBD_STAT_GTO | KBD_STAT_PERR))) +#endif { - printk(KERN_ERR "pc_keyb: controller jammed (0x%02X).\n", - status); - break; + if (status & KBD_STAT_MOUSE_OBF) + handle_mouse_event(scancode); + else { + if (do_acknowledge(scancode)) + handle_scancode(scancode, !(scancode & 0x80)); + mark_bh(KEYBOARD_BH); + } } + + status = kbd_read_status(); } + if (!work) + printk(KERN_ERR "pc_keyb: controller jammed (0x%02X).\n", status); + return status; } @@ -791,12 +818,23 @@ static void aux_write_dev(int val) { unsigned long flags; + int loop = 0; spin_lock_irqsave(&kbd_controller_lock, flags); kb_wait(); + /* If we haven't yet received the ack from the previous + write, we must wait for it to arrive; otherwise we + lose it. (At least, I think this is what is happening.) */ + while (mouse_ack_pending && loop++ < MOUSE_ACK_TIMEOUT ) { + mdelay(1); + handle_kbd_event(); + } + if (mouse_ack_pending) + printk(KERN_WARNING "mouse ack timeout\n"); kbd_write_command(KBD_CCMD_WRITE_MOUSE); kb_wait(); kbd_write_output(val); + mouse_ack_pending = 1; spin_unlock_irqrestore(&kbd_controller_lock, flags); } @@ -806,18 +844,52 @@ static void aux_write_ack(int val) { unsigned long flags; + int loop = 0; spin_lock_irqsave(&kbd_controller_lock, flags); kb_wait(); + while (mouse_ack_pending && loop++ < MOUSE_ACK_TIMEOUT) { + mdelay(1); + handle_kbd_event(); + } + if (mouse_ack_pending) + printk(KERN_WARNING "mouse ack timeout\n"); kbd_write_command(KBD_CCMD_WRITE_MOUSE); kb_wait(); kbd_write_output(val); - /* we expect an ACK in response. */ + mouse_ack_pending = 1; + /* we will deal with the ACK ourselves. */ mouse_reply_expected++; kb_wait(); spin_unlock_irqrestore(&kbd_controller_lock, flags); } +static void aux_kill_fasync(struct fasync_struct *fasync, int s) +{ + struct fasync_struct * fp; + struct fasync_struct fa; + + fp = fasync; + /* If someone has grabbed the AUX device, send signal only to + them and not to other processes. We could do this directly + if send_sigio was exported, but since it isn't we must + synthesize a "struct fasync_struct" to pass to + kill_fasync. */ + if (aux_exclusive) { + while (1) { + if (!fp) + return; + if (fp->fa_file == aux_exclusive) + break; + fp = fp->fa_next; + } + fa = (*fp); + fa.fa_next = NULL; + fp = &fa; + } + kill_fasync(fp, s); +} + static unsigned char get_from_queue(void) { unsigned char result; @@ -855,6 +927,8 @@ static int release_aux(struct inode * inode, struct file * file) { fasync_aux(-1, file, 0); + if (aux_exclusive == file) + aux_release_ioctl(file); if (--aux_count) return 0; kbd_write_cmd(AUX_INTS_OFF); /* Disable controller ints */ @@ -888,6 +962,33 @@ } /* + * Implement exclusive access mechanism. + */ + +#define AUX_ACCESS_ALLOWED(file) (!aux_exclusive || aux_exclusive == (file)) + +static ssize_t aux_wait_for_access(struct file * file) +{ + struct wait_queue wait = { current, NULL }; + + if (!AUX_ACCESS_ALLOWED(file)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + add_wait_queue(&aux_exclusive_wait, &wait); + do { + current->state = TASK_INTERRUPTIBLE; + schedule(); + } + while (!AUX_ACCESS_ALLOWED(file) + && !signal_pending(current)); + remove_wait_queue(&aux_exclusive_wait, &wait); + } + if (!AUX_ACCESS_ALLOWED(file)) + return -ERESTARTSYS; + return 0; +} + +/* * Put bytes from input queue to buffer. */ @@ -897,7 +998,11 @@ struct wait_queue wait = { current, NULL }; ssize_t i = count; unsigned char c; + ssize_t retval; + retval = aux_wait_for_access(file); + if (retval < 0) + return retval; if (queue_empty()) { if (file->f_flags & O_NONBLOCK) return -EAGAIN; @@ -932,8 +1037,11 @@ static ssize_t write_aux(struct file * file, const char * buffer, size_t count, loff_t *ppos) { - ssize_t retval = 0; + ssize_t retval; + retval = aux_wait_for_access(file); + if (retval < 0) + return retval; if (count) { ssize_t written = 0; @@ -949,6 +1057,7 @@ if (written) { retval = written; file->f_dentry->d_inode->i_mtime = CURRENT_TIME; + aux_last_write = jiffies; } } @@ -958,18 +1067,82 @@ static unsigned int aux_poll(struct file *file, poll_table * wait) { poll_wait(file, &queue->proc_list, wait); - if (!queue_empty()) + if (AUX_ACCESS_ALLOWED(file) && !queue_empty()) return POLLIN | POLLRDNORM; return 0; } +/* Wait this long after last write to mouse before allowing AUX_GRAB + to happen. This ensures that any outstanding mouse command is + completed. The ACK from the command is supposed to arrive in 25 + msec, and each subsequent status bytes are supposed to arrive + within 20 msec, so a command with 5 status bytes (I don't know any + this long) might take 125 msec. Fudge this up a bit to account for + additional delay introduced by bus locking. */ +#define AUX_GRAB_MIN_TIME (aux_last_write + (((200 * HZ) + 500) / 1000)) +#define AUX_GRAB_ALLOWED (aux_exclusive == 0 && (jiffies >= AUX_GRAB_MIN_TIME)) + +static void aux_grab_timeout(unsigned long data) +{ + wake_up_interruptible(&aux_exclusive_wait); +} + +static int aux_grab_ioctl(struct file *file) +{ + struct wait_queue wait = { current, NULL }; + struct timer_list timer; + + if (aux_exclusive == file) + return -EINVAL; + if (!AUX_GRAB_ALLOWED) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + init_timer (&timer); + timer.expires = AUX_GRAB_MIN_TIME; + timer.data = 0; + timer.function = aux_grab_timeout; + add_wait_queue(&aux_exclusive_wait, &wait); + add_timer(&timer); + do { + current->state = TASK_INTERRUPTIBLE; + schedule(); + } + while (!AUX_GRAB_ALLOWED && !signal_pending(current)); + del_timer(&timer); + remove_wait_queue(&aux_exclusive_wait, &wait); + } + if (!AUX_GRAB_ALLOWED) + return -ERESTARTSYS; + aux_exclusive = file; + return 0; +} + +static int aux_release_ioctl(struct file *file) +{ + if (aux_exclusive != file) + return -ENOENT; + aux_exclusive = 0; + wake_up_interruptible(&aux_exclusive_wait); + return 0; +} + +static int aux_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case AUX_GRAB: return aux_grab_ioctl(file); + case AUX_RELEASE: return aux_release_ioctl(file); + default: return -EINVAL; + } +} + struct file_operations psaux_fops = { NULL, /* seek */ read_aux, write_aux, NULL, /* readdir */ aux_poll, - NULL, /* ioctl */ + aux_ioctl, NULL, /* mmap */ open_aux, NULL, /* flush */ @@ -1006,6 +1179,8 @@ #endif /* INITIALIZE_MOUSE */ kbd_write_command(KBD_CCMD_MOUSE_DISABLE); /* Disable aux device. */ kbd_write_cmd(AUX_INTS_OFF); /* Disable controller ints. */ + + aux_last_write = jiffies; return 0; }